1
0
mirror of synced 2026-03-01 11:16:56 +00:00

Compare commits

...

204 Commits

Author SHA1 Message Date
Stefan Prodan
503e5ec950 Merge pull request #358 from fluxcd/update-components
Update toolkit components
2020-10-19 17:56:48 +03:00
fluxcdbot
414aeb0ac3 Update toolkit components 2020-10-19 14:23:20 +00:00
Stefan Prodan
55b8544d32 Merge pull request #350 from allymparker/source-git-secret-ref
Add secret-ref flag to create source git
2020-10-19 17:21:14 +03:00
Ally Parker
2d67ea5f7f Add secret-ref flag to git source
Add secret-ref flag to Helm source

Add secret-ref to bucket source
2020-10-19 14:49:14 +01:00
Stefan Prodan
4eaf72fa3e Merge pull request #356 from StupidScience/custom-flags
Implement custom flags for options with validation rules
2020-10-19 13:41:47 +03:00
“Anton
95ef3c1782 Update docs 2020-10-19 13:09:53 +03:00
“Anton
b3ef410fb7 Add source bucket provider flag 2020-10-19 13:05:56 +03:00
“Anton
0c55bca218 Add helm chart source flag 2020-10-19 12:55:34 +03:00
“Anton
5fd28439dc Add kustomization source and decryption provider flags 2020-10-19 12:46:10 +03:00
“Anton
a58c18e992 Refresh bootstrap and install docs 2020-10-17 23:47:20 +03:00
“Anton
058dfdfcd6 Move flags and utils to internal packages 2020-10-17 23:35:09 +03:00
Stefan Prodan
637fdac28a Merge pull request #354 from fluxcd/windows-exe
Add Windows OS to CLI install docs
2020-10-17 15:43:54 +03:00
Stefan Prodan
a8b667780a Add Windows OS to CLI install docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-17 14:05:17 +03:00
Stefan Prodan
b4f13e10d3 Merge pull request #351 from fluxcd/gitlab-https-auth
Add GitLab HTTPS auth to bootstrap options
2020-10-17 13:15:26 +03:00
Stefan Prodan
d0eb55fb86 Merge branch 'gitlab-https-auth' of https://github.com/fluxcd/toolkit into gitlab-https-auth 2020-10-17 13:08:38 +03:00
Stefan Prodan
b041dbd14f Add GitLab HTTPS auth to bootstrap options
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-17 09:27:47 +00:00
Stefan Prodan
0ab814fbc4 Merge pull request #348 from fluxcd/update-components
Update toolkit components
2020-10-17 12:26:46 +03:00
fluxcdbot
a2144b1759 Update toolkit components 2020-10-17 07:21:01 +00:00
Stefan Prodan
82eb3b5ccf Merge pull request #353 from fluxcd/build/remove-docs-hotfix
Remove documentation version hotfix
2020-10-17 10:05:00 +03:00
Hidde Beydals
7515080c63 Remove documentation version hotfix
And re-use the same logic to properly compare current and next release
versions in update workflow.
2020-10-16 22:19:58 +02:00
Hidde Beydals
07015c5172 Merge pull request #352 from fluxcd/refactor-create-reconcile-resume
Refactor create, reconcile and resume commands
2020-10-16 20:47:39 +02:00
Hidde Beydals
19918cd342 Refactor create, reconcile and resume cmds
* Take ObservedGeneration into account in readiness checks where
  applicable
* Reduce amount of code (and duplicate GETs) by working with pointers
  where possible
* Improve logged messages to properly take resource names into account
  and better describe processes
2020-10-16 20:17:39 +02:00
Stefan Prodan
16f0ed548e Add GitLab HTTPS auth to bootstrap options
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-16 20:57:21 +03:00
Stefan Prodan
0d1600275d Merge pull request #349 from fluxcd/windows-os-cli
Publish CLI binary for Windows
2020-10-16 17:30:59 +03:00
Stefan Prodan
768f32bd2d Publish CLI binary for Windows
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-16 16:32:11 +03:00
Stefan Prodan
d86798a7c6 Merge pull request #345 from staceypotter/patch-2
updated featured talks + upcoming meetups
2020-10-16 10:50:49 +03:00
Stefan Prodan
7718c6a2a5 Merge pull request #340 from fluxcd/bootstrap-validate-manifests
Enable client side validation on bootstrap
2020-10-16 10:41:47 +03:00
Stefan Prodan
a0616ac2cb Merge pull request #334 from StupidScience/windows-support
Get rid of shell-out and invoke command directly via exec
2020-10-16 10:41:31 +03:00
Stefan Prodan
23bbe0d52b Merge pull request #346 from staceypotter/patch-3
updated with featured talks + upcoming meetups
2020-10-16 10:41:07 +03:00
Stefan Prodan
6d9e7758a2 Merge pull request #347 from fluxcd/rebase-action
Add rebase action
2020-10-16 10:39:48 +03:00
Stefan Prodan
f81a80c2da Add rebase action
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-16 10:31:50 +03:00
Stefan Prodan
ab64619a41 Merge pull request #344 from smorimoto/fossa-action
Use fossa-contrib/fossa-action instead
2020-10-16 10:23:33 +03:00
Stacey Potter
18bdd26be8 updated with featured talks + upcoming meetups 2020-10-15 19:57:21 -07:00
Stacey Potter
c5f3a674d6 updated featured talks + upcoming meetups 2020-10-15 19:52:07 -07:00
Sora Morimoto
f95e17b144 Use fossa-contrib/fossa-action instead
Signed-off-by: Sora Morimoto <sora@morimoto.io>
2020-10-16 08:16:19 +09:00
“Anton
4989826124 Get rid of shell-out and invoke kubetcl directly via exec 2020-10-15 20:38:44 +03:00
Stefan Prodan
f23a551ae3 Enable client side validation on bootstrap
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-15 18:04:35 +03:00
Stefan Prodan
bd2df01b12 Merge pull request #343 from fluxcd/alert-provider-fix
Set alert provider secretRef only if specified
2020-10-15 18:03:23 +03:00
Stefan Prodan
880ef303e8 Set alert provider secretRef only if specified
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-15 17:51:43 +03:00
Michael Bridgen
b4ed1a0bdc Merge pull request #341 from fluxcd/update-image-auto-progress
Mark the design tasks for image automation as done
2020-10-15 12:23:33 +01:00
Michael Bridgen
7a2eae0699 Mark the design tasks for image automation as done
This leaves the implementation tasks as not done -- a bit of a
simplification, since there's some implementation done, and some
design left to do, but it's close enough at this level.

I estimated that having the design basics figured out is worth 30%
overall.
2020-10-15 12:12:04 +01:00
Stefan Prodan
4464252cae Merge pull request #337 from fluxcd/monitoring
Add cluster stats dashboard to monitoring stack
2020-10-14 17:12:30 +03:00
Stefan Prodan
ca8cf44200 Add cluster stats dashboard to docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-14 16:34:46 +03:00
Stefan Prodan
4565165579 Add cluster stats dashboard
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-14 16:16:56 +03:00
Stefan Prodan
a37be432a9 Merge pull request #333 from fluxcd/docs-update-v0.1.6
Update docs website
2020-10-14 12:11:30 +03:00
Stefan Prodan
90591e852d Update docs website
- add notification-controller/api commands to index
- move diagrams to docs website
- update CRDs docs

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-14 10:35:29 +03:00
Stefan Prodan
13f7d44a06 Merge pull request #327 from nanikjava/f-fix-306
Add ARM instruction to get started guide
2020-10-13 23:55:58 +03:00
Nanik
7dc8aa66a6 Add ARM instruction under staging and production bootstrap 2020-10-14 07:44:16 +11:00
Stefan Prodan
6cf28ab718 Merge pull request #331 from fluxcd/update-components
Update toolkit components
2020-10-13 23:41:39 +03:00
fluxcdbot
f461c5e8b7 Update toolkit components 2020-10-13 17:47:46 +00:00
Stefan Prodan
9433bdf4ad Merge pull request #332 from fluxcd/uninstall-fix
Uninstall improvements
2020-10-13 20:43:48 +03:00
Stefan Prodan
d2d494e079 Uninstall improvements
- ignore not found errors when deleting objects
- remove the CR/CRDs before deleting the cluster role binding
- capture kubectl exist code

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-13 20:30:17 +03:00
Stefan Prodan
e64b35cde8 Merge pull request #302 from phillebaba/feature/notification-controller
Add notification controller CLI commands
2020-10-13 18:31:16 +03:00
Philip Laine
d32e8c6e98 Remove aliases 2020-10-13 12:21:45 +02:00
Philip Laine
55cee488bc Update docs 2020-10-13 11:11:55 +02:00
Philip Laine
65b8942416 Fix minor issues 2020-10-13 11:11:55 +02:00
Philip Laine
94cf7c329c Update docs 2020-10-13 11:11:55 +02:00
Philip Laine
8eac7d6b4d Implement table output 2020-10-13 11:11:55 +02:00
Philip Laine
7ebb34de80 Add receiver command 2020-10-13 11:11:55 +02:00
Philip Laine
6ea84906ac Add alert commands 2020-10-13 11:11:54 +02:00
Philip Laine
f7971a871a Add alert provider commands 2020-10-13 11:11:54 +02:00
Stefan Prodan
54b35b7c2b Merge pull request #323 from fluxcd/monitoring
docs: Install the monitoring stack with gotk
2020-10-12 13:04:43 +03:00
Stefan Prodan
ca970b4ffb docs: Install the monitoring stack with gotk
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-12 09:50:43 +03:00
Stefan Prodan
8a96e32679 Update Prometheus and Grafana
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-11 15:13:16 +03:00
Stefan Prodan
fc4d01b3e5 Allow scraping and webhooks
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-11 13:48:06 +03:00
Stefan Prodan
b6afc8f6ce Merge pull request #317 from staceypotter/patch-1
Add featured talks and meetups section
2020-10-09 09:03:14 +03:00
Stefan Prodan
c481a431be Merge branch 'main' into patch-1 2020-10-09 08:54:45 +03:00
Stacey Potter
0a7b82793e removed header link
Removed "(Check out our [Upcoming Meetups](#upcoming-meetups)!)" from the top of the page.
2020-10-08 22:54:04 -07:00
Stacey Potter
3653236bcb added "featured talks" section
+ upcoming meetups & header link
2020-10-08 15:31:10 -07:00
Stefan Prodan
a2eee72015 Merge pull request #318 from fluxcd/install-pkg
Introduce install package
2020-10-08 14:01:25 +03:00
Stefan Prodan
5672646278 Use install pkg in CLI
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-08 12:47:25 +03:00
Stefan Prodan
c4d3fa7a48 init install pkg
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-08 02:31:33 +03:00
Stacey Potter
32b0267b9f Added GOTK guide walk-through
Added invite for Leigh's GOTK guide walk-through on Oct 19 (10am PT/18:00 BST) to the Community Section. LMK if this should be somewhere else - or condensed (wasn't sure how much info to include). Thanks! :)
2020-10-07 11:43:14 -07:00
Stefan Prodan
a7b41a4b8c Merge pull request #315 from rieger-jared/bug/update-notification-api-in-docs
Update api versions in guides
2020-10-07 18:10:38 +03:00
Jared Rieger
bfd6d14bf3 Update api versions in guides 2020-10-07 17:00:43 +02:00
Daniel Holbach
469de31218 Merge pull request #313 from dholbach/update-frontpage
close parentheses, make link to guide more obvious
2020-10-06 14:51:18 +02:00
Daniel Holbach
2ce9823b3a close parentheses, make link to guide more obvious 2020-10-06 14:34:21 +02:00
Stefan Prodan
e8b3d09ddf Merge pull request #310 from fluxcd/mod-cleanup
Use GitHub actions from fluxcd/pkg@main
2020-10-06 11:09:46 +03:00
stefanprodan
f613c01803 Update blang/semver to v4
Signed-off-by: stefanprodan <stefan.prodan@gmail.com>
2020-10-06 10:50:32 +03:00
stefanprodan
a5a5908fb5 Use GitHub actions from fluxcd/pkg@main 2020-10-06 10:50:02 +03:00
Hidde Beydals
5313a0ed47 Merge pull request #305 from fluxcd/docs/get-cmds-columns
Update guide to include column output commands
2020-10-05 18:26:51 +02:00
Hidde Beydals
1c15eebd7c Merge pull request #307 from fluxcd/bugfix-get-source
Display proper revision for sources
2020-10-05 18:26:28 +02:00
Hidde Beydals
f3cab6e177 Display proper revision for sources
Includes a change to an empty revision string if the reconciler has not
produced an artifact yet, as this will otherwise result in a nil
pointer dereference.
2020-10-05 18:16:22 +02:00
Hidde Beydals
c0623334ee Update guide to include column output commands 2020-10-05 18:11:01 +02:00
Hidde Beydals
d41bd6b6b1 Merge pull request #299 from circa10a/main
Switch get commands to use tables for output
2020-10-05 12:34:49 +02:00
Hidde Beydals
c9b4a8eef5 Merge pull request #301 from fluxcd/multi-context-kubeconfig
Support multi-path KUBECONFIG
2020-10-05 08:45:20 +02:00
circa10a
3619cb8bd1 Switch get commands to use tables for output
Signed-off-by: circa10a <caleblemoine@gmail.com>
2020-10-04 16:34:04 -05:00
Hidde Beydals
8e2316ba62 Support multi-path KUBECONFIG
Ref:
https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable
2020-10-04 18:22:03 +02:00
Stefan Prodan
495abf42ef Merge pull request #298 from yiannistri/sealed-secrets-docs
Update api versions in docs
2020-10-03 21:54:30 +03:00
Yiannis
5372dd633e Update api versions in docs 2020-10-03 19:08:16 +01:00
Stefan Prodan
5efa1ebe88 Merge pull request #297 from fluxcd/opt-out-network-policy
Add option to disable the network policy at install time
2020-10-03 19:15:00 +03:00
stefanprodan
07677ed4a7 Add option to disable the network policy at install time 2020-10-03 17:35:55 +03:00
Hidde Beydals
73e5640109 Merge pull request #295 from fluxcd/update-components
Update toolkit components
2020-10-02 20:39:49 +02:00
fluxcdbot
bdbded8588 Update toolkit components 2020-10-02 18:20:24 +00:00
Hidde Beydals
e0fbf8920d Merge pull request #290 from fluxcd/bug-get-break
Remove faulty `break` from get commands
2020-10-02 14:01:19 +02:00
Hidde Beydals
7b2227bfac Remove faulty break from get commands 2020-10-02 13:34:48 +02:00
Hidde Beydals
12866ca7ba Merge pull request #289 from fluxcd/fix-kustomization-depends-on
Fix Kusomization depends-on mapping
2020-10-02 13:01:47 +02:00
stefanprodan
1427b1537e Fix Kusomization depends-on mapping 2020-10-02 13:43:05 +03:00
Stefan Prodan
6ceb133bb5 Merge pull request #287 from fluxcd/go-1.15
Update Go to v1.15
2020-10-01 23:45:55 +03:00
stefanprodan
4ab67aaf90 Update Go to v1.15 2020-10-01 23:38:25 +03:00
Stefan Prodan
6cce0a3901 Merge pull request #286 from fluxcd/main-branch
Change default branch to main
2020-10-01 22:55:48 +03:00
stefanprodan
fa67789350 Change default branch to main 2020-10-01 22:45:45 +03:00
Stefan Prodan
16adeb1373 Merge pull request #285 from fluxcd/bootstrap-default-branch
Change the bootstrap default branch to main
2020-10-01 22:31:20 +03:00
stefanprodan
29c1cf1237 Retry setting annotation on conflict 2020-10-01 22:23:59 +03:00
stefanprodan
0e52065893 Change the bootstrap default branch to main 2020-10-01 21:47:33 +03:00
Hidde Beydals
3972c08efc Merge pull request #284 from fluxcd/hotfix-resource-to-string
Remove creationTimestamp leading spaces
2020-10-01 20:25:03 +02:00
Hidde Beydals
1a679ee05d Remove creationTimestamp leading spaces 2020-10-01 20:17:59 +02:00
Stefan Prodan
b2286055b0 Merge pull request #283 from fluxcd/roadmap-v0.1-update
Roadmap v0.1 update
2020-10-01 18:16:28 +03:00
Hidde Beydals
249d0d43de docs: hotfix component versions 2020-10-01 16:39:48 +02:00
stefanprodan
536630cbcd Update roadmap after v0.1 release 2020-10-01 16:59:04 +03:00
Hidde Beydals
70d0bfce15 Merge pull request #281 from fluxcd/meta-reconciler-improv 2020-10-01 14:31:00 +02:00
Hidde Beydals
84e36ed847 Use LastHandledReconcileAt in reconcile commands 2020-10-01 14:20:23 +02:00
Stefan Prodan
3d4ff687b5 Merge pull request #250 from phillebaba/git-status-docs
Update docs for git notifiers
2020-10-01 14:05:53 +03:00
Philip Laine
edddf65d0e Update docs for git notifiers 2020-10-01 12:55:01 +02:00
Hidde Beydals
f216273008 Make use of GetCondition from pkg/apis/meta 2020-09-30 23:23:28 +02:00
Hidde Beydals
3295ef4fcf Merge pull request #282 from mewzherder/patch-2
Fix broken link to Get Started Guide
2020-09-30 23:23:01 +02:00
mewzherder
4ea0e04852 Fix broken link to Get Started Guide 2020-09-30 14:12:57 -07:00
Hidde Beydals
414c0b3a8d Merge pull request #237 from fluxcd/rename-ns
Rename 'gitops-system' namespace to 'gotk-system'
2020-09-30 22:39:27 +02:00
Hidde Beydals
ff6a1c14be Rename 'gitops-system' namespace to 'gotk-system'
To align with the project name, and the group introduced in #236.
2020-09-30 22:32:26 +02:00
Stefan Prodan
0e1a862e34 Merge pull request #280 from fluxcd/api-v0.1
Update components to v0.1
2020-09-30 23:20:56 +03:00
stefanprodan
d902c71a6f Update components to v0.1 2020-09-30 23:09:21 +03:00
Daniel Holbach
b3f6d5206a Merge pull request #279 from mewzherder/patch-1
Add mailing list link to Community section
2020-09-30 21:33:46 +02:00
mewzherder
ff0d80e84f Add mailing list link to Community section 2020-09-30 12:07:52 -07:00
Stefan Prodan
cf080330ba Merge pull request #276 from fluxcd/kustomize-bootstrap-dir
Generate a kustomize package for the bootstrap manifests
2020-09-29 08:45:52 +03:00
stefanprodan
2373bacb0c Generate a kustomize package for the bootstrap manifests 2020-09-29 08:36:28 +03:00
Stefan Prodan
938edefea1 Merge pull request #273 from fluxcd/tenant-customization
Add support for multi-namespace tenant ownership
2020-09-28 13:11:04 +03:00
stefanprodan
393be92632 Add support for multi-namespace tenant ownership 2020-09-26 16:42:36 +03:00
Stefan Prodan
267440142e Merge pull request #270 from fluxcd/hr-ks-bucket-source
Add support for Bucket sources to Kustomizations/HRs
2020-09-24 10:03:55 +03:00
stefanprodan
d11fa476e1 Refactor object kind/name parsing 2020-09-23 18:19:33 +03:00
stefanprodan
bd2994f9ab Add support for Bucket sources to Kustomizations/HRs 2020-09-23 16:44:31 +03:00
Stefan Prodan
2806feb468 Merge pull request #269 from fluxcd/update-components
Update toolkit components
2020-09-23 16:07:21 +03:00
fluxcdbot
8e40069e67 Update toolkit components 2020-09-23 12:57:46 +00:00
Stefan Prodan
24d7d8dcc4 Merge pull request #267 from fluxcd/bucket-cmds
Implement bucket CRUD commands
2020-09-23 12:42:35 +03:00
stefanprodan
34147d7694 Add validation for bucket providers 2020-09-23 12:30:14 +03:00
stefanprodan
83bd245bfd Add Bucket API spec to docs 2020-09-23 12:17:10 +03:00
stefanprodan
a02452ccb9 Implement bucket CRUD commands 2020-09-23 12:04:28 +03:00
Hidde Beydals
a8f72564f4 Merge pull request #265 from fluxcd/docs/readme-community-sec
Add community section to `README.md`
2020-09-23 10:20:46 +02:00
Hidde Beydals
6e7b311696 Add community section to README.md 2020-09-23 10:07:47 +02:00
Stefan Prodan
2bda0eb761 Merge pull request #262 from dholbach/fix-261
Add community section
2020-09-23 09:57:16 +03:00
Daniel Holbach
c2661fb79e Add community section
fixes: #261
2020-09-22 17:37:47 +02:00
Daniel Holbach
4e70fd704b remove superfluous spaces 2020-09-22 17:25:22 +02:00
Stefan Prodan
85d03a9946 Merge pull request #260 from fluxcd/dependson-ns
Add support for namespace/name format in depends-on
2020-09-22 18:20:21 +03:00
stefanprodan
1c7994a353 Add support for namespace/name format in depends-on 2020-09-22 18:11:00 +03:00
fluxcdbot
347c2b6bcc Update toolkit components 2020-09-22 13:17:27 +00:00
Stefan Prodan
19d9953a48 Merge pull request #259 from fluxcd/fix-bootstrap-sync
Always apply sync manifests on bootstrap
2020-09-22 15:13:08 +03:00
stefanprodan
7b4e815a8d Always apply sync manifests on bootstrap 2020-09-22 14:58:51 +03:00
Hidde Beydals
59011c9517 Merge pull request #258 from fluxcd/ci-release-replace
ci: take "v" of RELEASE_VERSION into account
2020-09-22 11:54:17 +02:00
Hidde Beydals
39d5c9e525 ci: take "v" of RELEASE_VERSION into account 2020-09-22 11:36:42 +02:00
Stefan Prodan
e6d553c8ff Merge pull request #256 from fluxcd/install-from-archives
Use release archive as kustomize base
2020-09-22 11:56:00 +03:00
Hidde Beydals
fecd777d30 ci: update replace pattern to match release zip 2020-09-22 10:41:46 +02:00
stefanprodan
ce64b1fc09 Use release archive as kustomize base 2020-09-22 09:58:07 +03:00
stefanprodan
857e442376 Update kustomize/api to v0.6.2 2020-09-22 09:57:22 +03:00
Stefan Prodan
b0e407bf30 Merge pull request #254 from fluxcd/uninstall-fix
Suspend bootstrap kustomization on uninstall
2020-09-22 09:29:23 +03:00
Stefan Prodan
cad64ba044 Merge pull request #253 from fluxcd/bootstrap-required-components
Validate bootstrap required components
2020-09-22 09:20:42 +03:00
stefanprodan
85801f18ae Add uninstall section to install guide 2020-09-22 08:55:40 +03:00
stefanprodan
f93a40a18b Suspend bootstrap kustomization on uninstall 2020-09-22 08:54:49 +03:00
stefanprodan
9b0c44162a Validate bootstrap required components 2020-09-22 08:23:01 +03:00
Stefan Prodan
a66ac1594b Merge pull request #247 from fluxcd/arm-v7
Add support for ARMv7
2020-09-21 10:47:46 +03:00
stefanprodan
101e7bbce0 Add support for ARMv7 2020-09-21 10:37:55 +03:00
Stefan Prodan
0e969bbaad Merge pull request #240 from idvoretskyi/idvoretskyi-fossa
FOSSA scan enabled
2020-09-16 19:16:03 +03:00
Ihor Dvoretskyi
621b4c8213 FOSSA scan enabled
Signed-off-by: Ihor Dvoretskyi <ihor@linux.com>
2020-09-16 16:02:23 +00:00
Stefan Prodan
8ca6e44de7 Merge pull request #236 from fluxcd/tenant-cmd
Add create tenant command
2020-09-16 18:50:32 +03:00
stefanprodan
8ee2c972d3 Publish docs on releases instead of master push 2020-09-16 18:40:39 +03:00
stefanprodan
fafcf09570 Add -n shorthand for namespace flag 2020-09-16 15:10:34 +03:00
stefanprodan
0b14328024 Use the group version for HR APIVersion 2020-09-16 13:59:46 +03:00
Hidde Beydals
2d2b24f589 Merge pull request #239 from fluxcd/readme-update
docs: add more content to the README.md
2020-09-15 18:16:04 +02:00
Hidde Beydals
346d8f7583 docs: add more content to the README.md 2020-09-15 18:01:10 +02:00
stefanprodan
2096532f5d Add create tenant command 2020-09-15 16:49:48 +03:00
stefanprodan
4136eb1444 Add export resource helper function 2020-09-15 16:36:13 +03:00
Stefan Prodan
67e0acd044 Merge pull request #235 from stealthybox/shell-completions
Support bash, fish, zsh, and powershell shell completions as separate sub-commands
2020-09-15 12:15:47 +03:00
leigh capili
cbcdfc5f6c Support bash, fish, zsh, and powershell shell completions as separate sub-commands 2020-09-14 16:35:55 -06:00
Stefan Prodan
288d952686 Merge pull request #231 from fluxcd/kc-v0.0.12
Update kustomize-controller to v0.0.12
2020-09-14 18:34:56 +03:00
stefanprodan
c36e02bba9 Update kustomize-controller to v0.0.12 2020-09-14 18:20:05 +03:00
Stefan Prodan
c121a4c0f5 Merge pull request #229 from fluxcd/log-level
Add log level flag
2020-09-14 12:56:06 +03:00
stefanprodan
2bac5aabee Add log level flag 2020-09-14 12:35:24 +03:00
Stefan Prodan
97ff225bc0 Merge pull request #227 from fluxcd/watch-all-namespaces
Add watch all namespaces flag
2020-09-14 12:23:14 +03:00
stefanprodan
d5e78b9f80 Add watch all namespaces flag 2020-09-13 10:38:15 +03:00
Stefan Prodan
3f98affd5a Merge pull request #223 from fluxcd/update-components
Update toolkit components
2020-09-12 12:11:32 +03:00
stefanprodan
531c2bcf00 Import fluxcd/pkg/runtime 2020-09-12 12:04:37 +03:00
fluxcdbot
dd5505918a Update toolkit components 2020-09-12 08:54:22 +00:00
Stefan Prodan
4a30a69eb4 Merge pull request #226 from fluxcd/bot-pr
Use fluxcdbot token in CI
2020-09-12 11:51:31 +03:00
stefanprodan
38b302e5a5 Use fluxcdbot token in CI 2020-09-12 11:44:41 +03:00
Hidde Beydals
ea010895a0 docs: update Helm roadmap 2020-09-11 15:31:15 +02:00
Stefan Prodan
7b88512698 Merge pull request #222 from fluxcd/rbac-patch-events
Add PATCH rule to crd-controller role for events
2020-09-11 09:21:50 +03:00
Hidde Beydals
1ff24d9285 Add PATCH rule to crd-controller role for events
During high custom resource count / low interval tests, I was greated
with a `cannot patch resource "events"` message. This happened due to
event compaction, where it will perform a patch instead of a create.
By giving the role the permission to do so this should no longer pose
a problem.
2020-09-10 20:57:59 +02:00
Stefan Prodan
ebf742d272 Merge pull request #219 from fluxcd/bootstrap-branch
Add branch flag to bootstrap cmd
2020-09-09 17:59:30 +03:00
stefanprodan
a7b1b04920 Add bootstrap reinstall e2e test 2020-09-09 17:27:56 +03:00
stefanprodan
1218d6abe8 Add branch flag to bootstrap cmd 2020-09-09 16:41:48 +03:00
Stefan Prodan
f9d546676b Merge pull request #218 from fluxcd/refac
Add GitHub bootstrap e2e test
2020-09-09 14:15:33 +03:00
stefanprodan
afef6960b9 Cleanup GitHub e2e repo 2020-09-09 14:03:26 +03:00
stefanprodan
f6626b8975 Add GitHub bootstrap e2e test 2020-09-09 08:56:55 +03:00
stefanprodan
b1e66f81ab Add label validation 2020-09-08 20:08:28 +03:00
Stefan Prodan
9cc018e618 Merge pull request #216 from fluxcd/labels
Add label arg to create commands
2020-09-08 18:14:31 +03:00
stefanprodan
797cd9bea2 Add label arg to create commands 2020-09-08 18:03:04 +03:00
Hidde Beydals
9dbfca3d7a Merge pull request #212 from fluxcd/refactor-hr-source-kind-validation
Refactor HelmRelease chart source kind validation
2020-09-07 12:02:34 +02:00
Hidde Beydals
f18d1efdcb Refactor HelmRelease chart source kind validation 2020-09-07 11:13:15 +02:00
stefanprodan
29a9b89224 Set Homebrew formula name 2020-09-07 12:04:27 +03:00
Stefan Prodan
11b5b9808b Merge pull request #211 from fluxcd/homebrew
Publish Homebrew formula
2020-09-07 11:55:10 +03:00
stefanprodan
ca1f84d22b Publish Homebrew formula 2020-09-07 11:41:06 +03:00
Stefan Prodan
d6c6c88e6e Merge pull request #209 from fluxcd/sops
Mozilla SOPS
2020-09-06 15:02:36 +03:00
stefanprodan
ee33702463 Add Mozilla SOPS guide to docs 2020-09-06 14:24:33 +03:00
stefanprodan
8b6995e9ec Add decryption flags to create kustomization cmd 2020-09-06 13:56:56 +03:00
Stefan Prodan
36ba8f0fcb Merge pull request #207 from fluxcd/kustomize-controller-v0.0.10
Update kustomize-controller to v0.0.10
2020-09-05 11:19:33 +03:00
stefanprodan
bfb560ec50 Update kustomize-controller to v0.0.10 2020-09-05 11:04:41 +03:00
217 changed files with 9159 additions and 1797 deletions

78
.github/workflows/bootstrap.yaml vendored Normal file
View File

@@ -0,0 +1,78 @@
name: bootstrap
on:
push:
branches:
- '*'
jobs:
github:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Restore Go cache
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.14.x
- name: Setup Kubernetes
uses: engineerd/setup-kind@v0.4.0
- name: Set outputs
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Build
run: sudo go build -o ./bin/gotk ./cmd/gotk
- name: bootstrap init
run: |
./bin/gotk bootstrap github --manifests ./manifests/install/ \
--owner=fluxcd-testing \
--repository=gotk-test-${{ steps.vars.outputs.sha_short }} \
--branch=main \
--path=test-cluster
env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: bootstrap no-op
run: |
./bin/gotk bootstrap github --manifests ./manifests/install/ \
--owner=fluxcd-testing \
--repository=gotk-test-${{ steps.vars.outputs.sha_short }} \
--branch=main \
--path=test-cluster
env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: uninstall
run: |
./bin/gotk suspend kustomization gotk-system
./bin/gotk uninstall --resources --crds -s
- name: bootstrap reinstall
run: |
./bin/gotk bootstrap github --manifests ./manifests/install/ \
--owner=fluxcd-testing \
--repository=gotk-test-${{ steps.vars.outputs.sha_short }} \
--branch=main \
--path=test-cluster
env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: delete repository
run: |
./bin/gotk bootstrap github --manifests ./manifests/install/ \
--owner=fluxcd-testing \
--repository=gotk-test-${{ steps.vars.outputs.sha_short }} \
--branch=main \
--path=test-cluster \
--delete
env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: Debug failure
if: failure()
run: |
kubectl -n gotk-system get all
kubectl -n gotk-system logs deploy/source-controller
kubectl -n gotk-system logs deploy/kustomize-controller

View File

@@ -3,7 +3,7 @@ on:
push:
branches:
- docs*
- master
- main
jobs:
build:
@@ -13,42 +13,45 @@ jobs:
- name: Checkout master
uses: actions/checkout@v1
- name: Copy assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
controller_version() {
sed -n "s/\(.*$1\/.*?ref=\)//p;n" "manifests/bases/$1/kustomization.yaml"
sed -n "s/.*$1\/archive\/\(.*\).zip.*/\1/p;n" manifests/bases/$1/kustomization.yaml
}
{
# source-controller CRDs
SOURCE_VER=$(controller_version source-controller)
curl -# -f "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/api/source.md" > docs/components/source/api.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1alpha1/gitrepositories.md" > docs/components/source/gitrepositories.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1alpha1/helmrepositories.md" > docs/components/source/helmrepositories.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1alpha1/helmcharts.md" > docs/components/source/helmcharts.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/api/source.md" > docs/components/source/api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1beta1/gitrepositories.md" > docs/components/source/gitrepositories.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1beta1/helmrepositories.md" > docs/components/source/helmrepositories.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1beta1/helmcharts.md" > docs/components/source/helmcharts.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1beta1/buckets.md" > docs/components/source/buckets.md
}
{
# kustomize-controller CRDs
KUSTOMIZE_VER=$(controller_version kustomize-controller)
curl -# -f "https://raw.githubusercontent.com/fluxcd/kustomize-controller/$KUSTOMIZE_VER/docs/api/kustomize.md" > docs/components/kustomize/api.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/kustomize-controller/$KUSTOMIZE_VER/docs/spec/v1alpha1/kustomization.md" > docs/components/kustomize/kustomization.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/kustomize-controller/$KUSTOMIZE_VER/docs/api/kustomize.md" > docs/components/kustomize/api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/kustomize-controller/$KUSTOMIZE_VER/docs/spec/v1beta1/kustomization.md" > docs/components/kustomize/kustomization.md
}
{
# helm-controller CRDs
HELM_VER=$(controller_version helm-controller)
curl -# -f "https://raw.githubusercontent.com/fluxcd/helm-controller/$HELM_VER/docs/api/helmrelease.md" > docs/components/helm/api.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/helm-controller/$HELM_VER/docs/spec/v2alpha1/helmreleases.md" > docs/components/helm/helmreleases.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/helm-controller/$HELM_VER/docs/api/helmrelease.md" > docs/components/helm/api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/helm-controller/$HELM_VER/docs/spec/v2beta1/helmreleases.md" > docs/components/helm/helmreleases.md
}
{
# notification-controller CRDs
NOTIFICATION_VER=$(controller_version notification-controller)
curl -# -f "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/api/notification.md" > docs/components/notification/api.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1alpha1/event.md" > docs/components/notification/event.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1alpha1/alert.md" > docs/components/notification/alert.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1alpha1/provider.md" > docs/components/notification/provider.md
curl -# -f "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1alpha1/receiver.md" > docs/components/notification/receiver.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/api/notification.md" > docs/components/notification/api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/event.md" > docs/components/notification/event.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/alert.md" > docs/components/notification/alert.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/provider.md" > docs/components/notification/provider.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/receiver.md" > docs/components/notification/receiver.md
}
{

View File

@@ -4,7 +4,7 @@ on:
pull_request:
push:
branches:
- master
- main
jobs:
kind:
@@ -20,9 +20,9 @@ jobs:
restore-keys: |
${{ runner.os }}-go-
- name: Setup Go
uses: actions/setup-go@v2-beta
uses: actions/setup-go@v2
with:
go-version: 1.14.x
go-version: 1.15.x
- name: Setup Kubernetes
uses: engineerd/setup-kind@v0.4.0
with:
@@ -41,23 +41,27 @@ jobs:
- name: gotk check --pre
run: |
./bin/gotk check --pre
- name: gotk install --version
run: |
./bin/gotk install --version=master --namespace=test --verbose --components="source-controller,kustomize-controller"
- name: gotk uninstall
run: |
./bin/gotk uninstall --namespace=test --crds --silent
- name: gotk install --manifests
run: |
./bin/gotk install --manifests ./manifests/install/ --version=""
./bin/gotk install --manifests ./manifests/install/
- name: gotk create source git
run: |
./bin/gotk create source git podinfo \
--url https://github.com/stefanprodan/podinfo \
--tag-semver=">=3.2.3"
- name: gotk create source git export apply
run: |
./bin/gotk create source git podinfo-export \
--url https://github.com/stefanprodan/podinfo \
--tag-semver=">=3.2.3" \
--export | kubectl apply -f -
./bin/gotk delete source git podinfo-export --silent
- name: gotk get sources git
run: |
./bin/gotk get sources git
- name: gotk get sources git --all-namespaces
run: |
./bin/gotk get sources git --all-namespaces
- name: gotk create kustomization
run: |
./bin/gotk create kustomization podinfo \
@@ -69,12 +73,15 @@ jobs:
--health-check="Deployment/frontend.dev" \
--health-check="Deployment/backend.dev" \
--health-check-timeout=3m
- name: gotk sync kustomization --with-source
- name: gotk reconcile kustomization --with-source
run: |
./bin/gotk reconcile kustomization podinfo --with-source
- name: gotk get kustomizations
run: |
./bin/gotk get kustomizations
- name: gotk get kustomizations --all-namespaces
run: |
./bin/gotk get kustomizations --all-namespaces
- name: gotk suspend kustomization
run: |
./bin/gotk suspend kustomization podinfo
@@ -105,9 +112,15 @@ jobs:
--target-namespace=default \
--source=GitRepository/podinfo \
--chart=./charts/podinfo
- name: gotk reconcile helmrelease --with-source
run: |
./bin/gotk reconcile helmrelease podinfo-git --with-source
- name: gotk get helmreleases
run: |
./bin/gotk get helmreleases
- name: gotk get helmreleases --all-namespaces
run: |
./bin/gotk get helmreleases --all-namespaces
- name: gotk export helmrelease
run: |
./bin/gotk export hr --all
@@ -126,11 +139,14 @@ jobs:
- name: gotk check
run: |
./bin/gotk check
- name: gotk uninstall
run: |
./bin/gotk uninstall --crds --silent
- name: Debug failure
if: failure()
run: |
kubectl version --client --short
kubectl -n gitops-system get all
kubectl -n gitops-system get kustomizations -oyaml
kubectl -n gitops-system logs deploy/source-controller
kubectl -n gitops-system logs deploy/kustomize-controller
kubectl -n gotk-system get all
kubectl -n gotk-system get kustomizations -oyaml
kubectl -n gotk-system logs deploy/source-controller
kubectl -n gotk-system logs deploy/kustomize-controller

25
.github/workflows/fossa.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: FOSSA
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "^1.14.x"
- name: Add GOPATH to GITHUB_ENV
run: echo "GOPATH=$(go env GOPATH)" >>"$GITHUB_ENV"
- name: Add GOPATH to GITHUB_PATH
run: echo "$GOPATH/bin" >>"$GITHUB_PATH"
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@v1
with:
# FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }}

21
.github/workflows/rebase.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: rebase
on:
pull_request:
types: [opened]
issue_comment:
types: [created]
jobs:
rebase:
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') && (github.event.comment.author_association == 'CONTRIBUTOR' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.3.1
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

View File

@@ -16,7 +16,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.14.x
go-version: 1.15.x
- name: Download release notes utility
env:
GH_REL_URL: https://github.com/buchanae/github-release-notes/releases/download/0.2.0/github-release-notes-linux-amd64-0.2.0.tar.gz
@@ -25,8 +25,10 @@ jobs:
run: |
echo 'CHANGELOG' > /tmp/release.txt
github-release-notes -org fluxcd -repo toolkit -since-latest-release -include-author >> /tmp/release.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@master
uses: fluxcd/pkg//actions/kustomize@main
- name: Generate manifests tarball
run: |
mkdir -p ./output
@@ -82,3 +84,4 @@ jobs:
args: release --release-notes=/tmp/release.txt --skip-validate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

View File

@@ -1,8 +1,9 @@
name: Update Components
on:
workflow_dispatch:
schedule:
- cron: "*/10 * * * *"
- cron: "0 * * * *"
jobs:
update-components:
@@ -18,11 +19,11 @@ jobs:
bump_version() {
local RELEASE_VERSION=$(curl -s https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name')
local CURRENT_VERSION=$(sed -n "s/\(.*$1\/.*?ref=\)//p;n" "manifests/bases/$1/kustomization.yaml")
local CURRENT_VERSION=$(sed -n "s/.*$1\/archive\/\(.*\).zip.*/\1/p;n" manifests/bases/$1/kustomization.yaml)
if [[ "${RELEASE_VERSION}" != "${CURRENT_VERSION}" ]]; then
# bump kustomize
sed -i "s/\($1\/.*?ref=\).*/\1${RELEASE_VERSION}/g" "manifests/bases/$1/kustomization.yaml"
sed -i "s/\($1\/archive\/\)v.*\(.zip\/\/$1-\).*\(\/config.*\)/\1${RELEASE_VERSION}\2${RELEASE_VERSION/v}\3/g" "manifests/bases/$1/kustomization.yaml"
if [[ ! -z $(go list -m all | grep "github.com/fluxcd/$1/api" | awk '{print $2}') ]]; then
# bump go mod
@@ -54,10 +55,10 @@ jobs:
id: cpr
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.BOT_GITHUB_TOKEN }}
commit-message: Update toolkit components
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
author: fluxcdbot <fluxcdbot@users.noreply.github.com>
title: Update toolkit components
body: |
${{ steps.update.outputs.pr_body }}

View File

@@ -6,12 +6,30 @@ builds:
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
- arm
goarm:
- 7
env:
- CGO_ENABLED=0
archives:
- name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
files:
- none*
brews:
- name: gotk
tap:
owner: fluxcd
name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
folder: Formula
homepage: "https://toolkit.fluxcd.io/"
description: "GitOps Toolkit CLI"
dependencies:
- name: kubectl
type: optional
test: |
system "#{bin}/gotk --version"

View File

@@ -29,7 +29,7 @@ For announcements we use a mailing list as well. Simply subscribe to
[flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev)
to join the conversation (there you can also add calendar invites
to your Google calendar for our [Flux
meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/edit#)).
meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view)).
## Understanding the GitOps Toolkit

View File

@@ -2,11 +2,93 @@
[![e2e](https://github.com/fluxcd/toolkit/workflows/e2e/badge.svg)](https://github.com/fluxcd/toolkit/actions)
[![report](https://goreportcard.com/badge/github.com/fluxcd/toolkit)](https://goreportcard.com/report/github.com/fluxcd/toolkit)
[![license](https://img.shields.io/github/license/fluxcd/toolkit.svg)](https://github.com/fluxcd/toolkit/blob/master/LICENSE)
[![license](https://img.shields.io/github/license/fluxcd/toolkit.svg)](https://github.com/fluxcd/toolkit/blob/main/LICENSE)
[![release](https://img.shields.io/github/release/fluxcd/toolkit/all.svg)](https://github.com/fluxcd/toolkit/releases)
![overview](docs/diagrams/gitops-toolkit.png)
Experimental toolkit for assembling CD pipelines the GitOps way.
The GitOps Toolkit is a set of composable APIs and specialized tools
that can be used to build a Continuous Delivery platform on top of Kubernetes.
![overview](docs/diagrams/gotk-feature.png)
These tools are build with Kubernetes controller-runtime libraries, and they
can be dynamically configured with Kubernetes custom resources either by
cluster admins or by other automated tools.
The GitOps Toolkit components interact with each other via Kubernetes
events and are responsible for the reconciliation of their designated API objects.
To get started with the toolkit please read the [docs](https://toolkit.fluxcd.io/).
## `gotk` installation
With Homebrew:
```sh
brew tap fluxcd/tap
brew install gotk
```
With Bash:
```sh
curl -s https://toolkit.fluxcd.io/install.sh | sudo bash
# enable completions in ~/.bash_profile
. <(gotk completion bash)
```
Binaries for macOS, Windows and Linux AMD64/ARM are available to download on the
[release page](https://github.com/fluxcd/toolkit/releases).
Verify that your cluster satisfies the prerequisites with:
```sh
gotk check --pre
```
## Get started
To get started with the GitOps Toolkit, start [browsing the documentation](https://toolkit.fluxcd.io)
or get started with one of the following guides:
- [Get started with GitOps Toolkit (deep dive)](https://toolkit.fluxcd.io/get-started/)
- [Installation](https://toolkit.fluxcd.io/guides/installation/)
- [Manage Helm Releases](https://toolkit.fluxcd.io/guides/helmreleases/)
- [Setup Notifications](https://toolkit.fluxcd.io/guides/notifications/)
- [Setup Webhook Receivers](https://toolkit.fluxcd.io/guides/webhook-receivers/)
## Components
- [Toolkit CLI](https://toolkit.fluxcd.io/cmd/gotk/)
- [Source Controller](https://toolkit.fluxcd.io/components/source/controller/)
- [GitRepository CRD](https://toolkit.fluxcd.io/components/source/gitrepositories/)
- [HelmRepository CRD](https://toolkit.fluxcd.io/components/source/helmrepositories/)
- [HelmChart CRD](https://toolkit.fluxcd.io/components/source/helmcharts/)
- [Bucket CRD](https://toolkit.fluxcd.io/components/source/buckets/)
- [Kustomize Controller](https://toolkit.fluxcd.io/components/kustomize/controller/)
- [Kustomization CRD](https://toolkit.fluxcd.io/components/kustomize/kustomization/)
- [Helm Controller](https://toolkit.fluxcd.io/components/helm/controller/)
- [HelmRelease CRD](https://toolkit.fluxcd.io/components/helm/helmreleases/)
- [Notification Controller](https://toolkit.fluxcd.io/components/notification/controller/)
- [Provider CRD](https://toolkit.fluxcd.io/components/notification/provider/)
- [Alert CRD](https://toolkit.fluxcd.io/components/notification/alert/)
- [Receiver CRD](https://toolkit.fluxcd.io/components/notification/receiver/)
## Community
The GitOps Toolkit 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))
- Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/)
- Join the [planning discussions](https://github.com/fluxcd/toolkit/discussions)
- And if you are completely new to the GitOps Toolkit, take a look at our [Get Started guide](https://toolkit.fluxcd.io/get-started/) and give us feedback
- To be part of the conversation about Flux's development, [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
- 12 Oct 2020 - [Rawkode Live: Introduction to GitOps Toolkit with Stefan Prodan](https://youtu.be/HqTzuOBP0eY)
- 4 Sep 2020 - [KubeCon/CloudNativeCon 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 Meetups
- 19 Oct 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 1](https://www.meetup.com/GitOps-Community/events/273640196/)
- 2 Nov 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 2](https://www.meetup.com/GitOps-Community/events/273934676/)
We are looking forward to seeing you with us!

View File

@@ -19,6 +19,7 @@ package main
import (
"context"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
@@ -34,8 +35,12 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/flags"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/fluxcd/toolkit/pkg/install"
)
var bootstrapCmd = &cobra.Command{
@@ -45,15 +50,21 @@ var bootstrapCmd = &cobra.Command{
}
var (
bootstrapVersion string
bootstrapComponents []string
bootstrapRegistry string
bootstrapImagePullSecret string
bootstrapArch string
bootstrapVersion string
bootstrapComponents []string
bootstrapRegistry string
bootstrapImagePullSecret string
bootstrapArch flags.Arch = "amd64"
bootstrapBranch string
bootstrapWatchAllNamespaces bool
bootstrapNetworkPolicy bool
bootstrapLogLevel flags.LogLevel = "info"
bootstrapManifestsPath string
bootstrapRequiredComponents = []string{"source-controller", "kustomize-controller"}
)
const (
bootstrapBranch = "master"
bootstrapDefaultBranch = "main"
bootstrapInstallManifest = "toolkit-components.yaml"
bootstrapSourceManifest = "toolkit-source.yaml"
bootstrapKustomizationManifest = "toolkit-kustomization.yaml"
@@ -68,54 +79,86 @@ func init() {
"container registry where the toolkit images are published")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapImagePullSecret, "image-pull-secret", "",
"Kubernetes secret name used for pulling the toolkit images from a private registry")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArch, "arch", "amd64",
"arch can be amd64 or arm64")
bootstrapCmd.PersistentFlags().Var(&bootstrapArch, "arch", bootstrapArch.Description())
bootstrapCmd.PersistentFlags().StringVar(&bootstrapBranch, "branch", bootstrapDefaultBranch,
"default branch (for GitHub this must match the default branch setting for the organization)")
rootCmd.AddCommand(bootstrapCmd)
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapWatchAllNamespaces, "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")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapNetworkPolicy, "network-policy", true,
"deny ingress access to the toolkit controllers from other namespaces using network policies")
bootstrapCmd.PersistentFlags().Var(&bootstrapLogLevel, "log-level", bootstrapLogLevel.Description())
bootstrapCmd.PersistentFlags().StringVar(&bootstrapManifestsPath, "manifests", "", "path to the manifest directory")
bootstrapCmd.PersistentFlags().MarkHidden("manifests")
}
func generateInstallManifests(targetPath, namespace, tmpDir string) (string, error) {
gotkDir := path.Join(tmpDir, ".gotk")
defer os.RemoveAll(gotkDir)
if err := os.MkdirAll(gotkDir, os.ModePerm); err != nil {
return "", fmt.Errorf("generating manifests failed: %w", err)
func bootstrapValidate() error {
for _, component := range bootstrapRequiredComponents {
if !utils.ContainsItemString(bootstrapComponents, component) {
return fmt.Errorf("component %s is required", component)
}
}
if err := genInstallManifests(bootstrapVersion, namespace, bootstrapComponents, bootstrapRegistry, bootstrapImagePullSecret, bootstrapArch, gotkDir); err != nil {
return "", fmt.Errorf("generating manifests failed: %w", err)
}
return nil
}
func generateInstallManifests(targetPath, namespace, tmpDir string, localManifests string) (string, error) {
manifestsDir := path.Join(tmpDir, targetPath, namespace)
if err := os.MkdirAll(manifestsDir, os.ModePerm); err != nil {
return "", fmt.Errorf("generating manifests failed: %w", err)
return "", fmt.Errorf("creating manifests dir failed: %w", err)
}
manifest := path.Join(manifestsDir, bootstrapInstallManifest)
if err := buildKustomization(gotkDir, manifest); err != nil {
return "", fmt.Errorf("build kustomization failed: %w", err)
opts := install.Options{
BaseURL: localManifests,
Version: bootstrapVersion,
Namespace: namespace,
Components: bootstrapComponents,
Registry: bootstrapRegistry,
ImagePullSecret: bootstrapImagePullSecret,
Arch: bootstrapArch.String(),
WatchAllNamespaces: bootstrapWatchAllNamespaces,
NetworkPolicy: bootstrapNetworkPolicy,
LogLevel: bootstrapLogLevel.String(),
NotificationController: defaultNotification,
ManifestsFile: fmt.Sprintf("%s.yaml", namespace),
Timeout: timeout,
}
if localManifests == "" {
opts.BaseURL = install.MakeDefaultOptions().BaseURL
}
output, err := install.Generate(opts)
if err != nil {
return "", fmt.Errorf("generating install manifests failed: %w", err)
}
if err := ioutil.WriteFile(manifest, output, os.ModePerm); err != nil {
return "", fmt.Errorf("generating install manifests failed: %w", err)
}
return manifest, nil
}
func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error {
command := fmt.Sprintf("kubectl apply -f %s", manifestPath)
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
kubectlArgs := []string{"apply", "-f", manifestPath}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
}
for _, deployment := range components {
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
namespace, deployment, timeout.String())
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
}
}
return nil
}
func generateSyncManifests(url, name, namespace, targetPath, tmpDir string, interval time.Duration) error {
gvk := sourcev1.GroupVersion.WithKind("GitRepository")
func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir string, interval time.Duration) error {
gvk := sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)
gitRepository := sourcev1.GitRepository{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
@@ -131,7 +174,7 @@ func generateSyncManifests(url, name, namespace, targetPath, tmpDir string, inte
Duration: interval,
},
Reference: &sourcev1.GitRepositoryRef{
Branch: "master",
Branch: branch,
},
SecretRef: &corev1.LocalObjectReference{
Name: name,
@@ -144,11 +187,11 @@ func generateSyncManifests(url, name, namespace, targetPath, tmpDir string, inte
return err
}
if err := utils.writeFile(string(gitData), filepath.Join(tmpDir, targetPath, namespace, bootstrapSourceManifest)); err != nil {
if err := utils.WriteFile(string(gitData), filepath.Join(tmpDir, targetPath, namespace, bootstrapSourceManifest)); err != nil {
return err
}
gvk = kustomizev1.GroupVersion.WithKind("Kustomization")
gvk = kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)
kustomization := kustomizev1.Kustomization{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
@@ -164,10 +207,11 @@ func generateSyncManifests(url, name, namespace, targetPath, tmpDir string, inte
},
Path: fmt.Sprintf("./%s", strings.TrimPrefix(targetPath, "./")),
Prune: true,
SourceRef: kustomizev1.CrossNamespaceObjectReference{
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: sourcev1.GitRepositoryKind,
Name: name,
},
Validation: "client",
},
}
@@ -176,7 +220,11 @@ func generateSyncManifests(url, name, namespace, targetPath, tmpDir string, inte
return err
}
if err := utils.writeFile(string(ksData), filepath.Join(tmpDir, targetPath, namespace, bootstrapKustomizationManifest)); err != nil {
if err := utils.WriteFile(string(ksData), filepath.Join(tmpDir, targetPath, namespace, bootstrapKustomizationManifest)); err != nil {
return err
}
if err := utils.GenerateKustomizationYaml(filepath.Join(tmpDir, targetPath, namespace)); err != nil {
return err
}
@@ -184,20 +232,22 @@ func generateSyncManifests(url, name, namespace, targetPath, tmpDir string, inte
}
func applySyncManifests(ctx context.Context, kubeClient client.Client, name, namespace, targetPath, tmpDir string) error {
command := fmt.Sprintf("kubectl apply -f %s", filepath.Join(tmpDir, targetPath, namespace))
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
kubectlArgs := []string{"apply", "-k", filepath.Join(tmpDir, targetPath, namespace)}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, kubectlArgs...); err != nil {
return err
}
logger.Waitingf("waiting for cluster sync")
var gitRepository sourcev1.GitRepository
if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
isGitRepositoryReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &gitRepository)); err != nil {
return err
}
var kustomization kustomizev1.Kustomization
if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil {
isKustomizationReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &kustomization)); err != nil {
return err
}

View File

@@ -28,6 +28,7 @@ import (
"github.com/spf13/cobra"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/toolkit/internal/utils"
)
var bootstrapGitHubCmd = &cobra.Command{
@@ -55,6 +56,9 @@ the bootstrap command will perform an upgrade if needed.`,
# Run bootstrap for a private repo hosted on GitHub Enterprise
gotk bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain>
# Run bootstrap for a an existing repository with a branch named main
gotk bootstrap github --owner=<organization> --repository=<repo name> --branch=main
`,
RunE: bootstrapGitHubCmdRun,
}
@@ -68,6 +72,7 @@ var (
ghHostname string
ghPath string
ghTeams []string
ghDelete bool
)
const (
@@ -84,6 +89,9 @@ func init() {
bootstrapGitHubCmd.Flags().StringVar(&ghHostname, "hostname", git.GitHubDefaultHostname, "GitHub hostname")
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)")
bootstrapGitHubCmd.Flags().MarkHidden("delete")
bootstrapCmd.AddCommand(bootstrapGitHubCmd)
}
@@ -93,8 +101,8 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("%s environment variable not found", git.GitHubTokenName)
}
if !utils.containsItemString(supportedArch, bootstrapArch) {
return fmt.Errorf("arch %s is not supported, can be %v", bootstrapArch, supportedArch)
if err := bootstrapValidate(); err != nil {
return err
}
repository, err := git.NewRepository(ghRepository, ghOwner, ghHostname, ghToken, "gotk", ghOwner+"@users.noreply.github.com")
@@ -107,11 +115,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
IsPersonal: ghPersonal,
}
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
tmpDir, err := ioutil.TempDir("", namespace)
if err != nil {
return err
@@ -121,6 +124,14 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if ghDelete {
if err := provider.DeleteRepository(ctx, repository); err != nil {
return err
}
logger.Successf("repository deleted")
return nil
}
// create GitHub repository if doesn't exists
logger.Actionf("connecting to %s", ghHostname)
changed, err := provider.CreateRepository(ctx, repository)
@@ -152,7 +163,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
// generate install manifests
logger.Generatef("generating manifests")
manifest, err := generateInstallManifests(ghPath, namespace, tmpDir)
manifest, err := generateInstallManifests(ghPath, namespace, tmpDir, bootstrapManifestsPath)
if err != nil {
return err
}
@@ -173,6 +184,11 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("components are up to date")
}
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
// determine if repo synchronization is working
isInstall := shouldInstallManifests(ctx, kubeClient, namespace)
@@ -211,28 +227,25 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
}
// configure repo synchronization
if isInstall {
// generate source and kustomization manifests
logger.Actionf("generating sync manifests")
if err := generateSyncManifests(repository.GetSSH(), namespace, namespace, ghPath, tmpDir, ghInterval); err != nil {
return err
}
logger.Actionf("generating sync manifests")
if err := generateSyncManifests(repository.GetSSH(), bootstrapBranch, namespace, namespace, ghPath, tmpDir, ghInterval); err != nil {
return err
}
// commit and push manifests
if changed, err = repository.Commit(ctx, path.Join(ghPath, namespace), "Add manifests"); err != nil {
// commit and push manifests
if changed, err = repository.Commit(ctx, path.Join(ghPath, namespace), "Add manifests"); err != nil {
return err
} else if changed {
if err := repository.Push(ctx); err != nil {
return err
} else if changed {
if err := repository.Push(ctx); err != nil {
return err
}
logger.Successf("sync manifests pushed")
}
logger.Successf("sync manifests pushed")
}
// apply manifests and waiting for sync
logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, namespace, namespace, ghPath, tmpDir); err != nil {
return err
}
// apply manifests and waiting for sync
logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, namespace, namespace, ghPath, tmpDir); err != nil {
return err
}
if withErrors {

View File

@@ -26,8 +26,11 @@ import (
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/toolkit/internal/utils"
)
var bootstrapGitLabCmd = &cobra.Command{
@@ -41,17 +44,23 @@ the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a GitLab API token and export it as an env var
export GITLAB_TOKEN=<my-token>
# Run bootstrap for a private repo owned by a GitLab group
# Run bootstrap for a private repo using HTTPS token authentication
gotk bootstrap gitlab --owner=<group> --repository=<repo name>
# Run bootstrap for a private repo using SSH authentication
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --ssh-hostname=gitlab.com
# Run bootstrap for a repository path
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster
# Run bootstrap for a public repository on a personal account
gotk bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
gotk bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
# Run bootstrap for a private repo hosted on a GitLab server
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain>
# Run bootstrap for a an existing repository with a branch named main
gotk bootstrap gitlab --owner=<organization> --repository=<repo name> --branch=main
`,
RunE: bootstrapGitLabCmdRun,
}
@@ -74,7 +83,7 @@ func init() {
bootstrapGitLabCmd.Flags().BoolVar(&glPrivate, "private", true, "is private repository")
bootstrapGitLabCmd.Flags().DurationVar(&glInterval, "interval", time.Minute, "sync interval")
bootstrapGitLabCmd.Flags().StringVar(&glHostname, "hostname", git.GitLabDefaultHostname, "GitLab hostname")
bootstrapGitLabCmd.Flags().StringVar(&glSSHHostname, "ssh-hostname", "", "GitLab SSH hostname, defaults to hostname if not specified")
bootstrapGitLabCmd.Flags().StringVar(&glSSHHostname, "ssh-hostname", "", "GitLab SSH hostname, when specified a deploy key will be added to the repository")
bootstrapGitLabCmd.Flags().StringVar(&glPath, "path", "", "repository path, when specified the cluster sync will be scoped to this path")
bootstrapCmd.AddCommand(bootstrapGitLabCmd)
@@ -86,8 +95,8 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("%s environment variable not found", git.GitLabTokenName)
}
if !utils.containsItemString(supportedArch, bootstrapArch) {
return fmt.Errorf("arch %s is not supported, can be %v", bootstrapArch, supportedArch)
if err := bootstrapValidate(); err != nil {
return err
}
repository, err := git.NewRepository(glRepository, glOwner, glHostname, glToken, "gotk", glOwner+"@users.noreply.gitlab.com")
@@ -104,7 +113,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
IsPersonal: glPersonal,
}
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -136,7 +145,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
// generate install manifests
logger.Generatef("generating manifests")
manifest, err := generateInstallManifests(glPath, namespace, tmpDir)
manifest, err := generateInstallManifests(glPath, namespace, tmpDir, bootstrapManifestsPath)
if err != nil {
return err
}
@@ -169,54 +178,71 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("install completed")
}
// setup SSH deploy key
if shouldCreateDeployKey(ctx, kubeClient, namespace) {
logger.Actionf("configuring deploy key")
u, err := url.Parse(repository.GetSSH())
if err != nil {
return fmt.Errorf("git URL parse failed: %w", err)
}
repoURL := repository.GetURL()
key, err := generateDeployKey(ctx, kubeClient, u, namespace)
if err != nil {
return fmt.Errorf("generating deploy key failed: %w", err)
}
if glSSHHostname != "" {
// setup SSH deploy key
repoURL = repository.GetSSH()
if shouldCreateDeployKey(ctx, kubeClient, namespace) {
logger.Actionf("configuring deploy key")
u, err := url.Parse(repoURL)
if err != nil {
return fmt.Errorf("git URL parse failed: %w", err)
}
keyName := "gotk"
if glPath != "" {
keyName = fmt.Sprintf("gotk-%s", glPath)
}
key, err := generateDeployKey(ctx, kubeClient, u, namespace)
if err != nil {
return fmt.Errorf("generating deploy key failed: %w", err)
}
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
keyName := "gotk"
if glPath != "" {
keyName = fmt.Sprintf("gotk-%s", glPath)
}
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
return err
} else if changed {
logger.Successf("deploy key configured")
}
}
} else {
// setup HTTPS token auth
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
Namespace: namespace,
},
StringData: map[string]string{
"username": "git",
"password": glToken,
},
}
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err
} else if changed {
logger.Successf("deploy key configured")
}
}
// configure repo synchronization
if isInstall {
// generate source and kustomization manifests
logger.Actionf("generating sync manifests")
if err := generateSyncManifests(repository.GetSSH(), namespace, namespace, glPath, tmpDir, glInterval); err != nil {
return err
}
logger.Actionf("generating sync manifests")
if err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, glPath, tmpDir, glInterval); err != nil {
return err
}
// commit and push manifests
if changed, err = repository.Commit(ctx, path.Join(glPath, namespace), "Add manifests"); err != nil {
// commit and push manifests
if changed, err = repository.Commit(ctx, path.Join(glPath, namespace), "Add manifests"); err != nil {
return err
} else if changed {
if err := repository.Push(ctx); err != nil {
return err
} else if changed {
if err := repository.Push(ctx); err != nil {
return err
}
logger.Successf("sync manifests pushed")
}
logger.Successf("sync manifests pushed")
}
// apply manifests and waiting for sync
logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, namespace, namespace, glPath, tmpDir); err != nil {
return err
}
// apply manifests and waiting for sync
logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, namespace, namespace, glPath, tmpDir); err != nil {
return err
}
logger.Successf("bootstrap finished")

View File

@@ -18,13 +18,15 @@ package main
import (
"context"
"fmt"
"encoding/json"
"os"
"os/exec"
"strings"
"github.com/blang/semver"
"github.com/blang/semver/v4"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
@@ -48,6 +50,10 @@ var (
checkComponents []string
)
type kubectlVersion struct {
ClientVersion *apimachineryversion.Info `json:"clientVersion"`
}
func init() {
checkCmd.Flags().BoolVarP(&checkPre, "pre", "", false,
"only run pre-installation checks")
@@ -97,14 +103,20 @@ func kubectlCheck(ctx context.Context, version string) bool {
return false
}
command := "kubectl version --client --short | awk '{ print $3 }'"
output, err := utils.execCommand(ctx, ModeCapture, command)
kubectlArgs := []string{"version", "--client", "--output", "json"}
output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubectlArgs...)
if err != nil {
logger.Failuref("kubectl version can't be determined")
return false
}
v, err := semver.ParseTolerant(output)
kv := &kubectlVersion{}
if err = json.Unmarshal([]byte(output), kv); err != nil {
logger.Failuref("kubectl version output can't be unmarshaled")
return false
}
v, err := semver.ParseTolerant(kv.ClientVersion.GitVersion)
if err != nil {
logger.Failuref("kubectl version can't be parsed")
return false
@@ -161,9 +173,8 @@ func componentsCheck() bool {
ok := true
for _, deployment := range checkComponents {
command := fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
namespace, deployment, timeout.String())
if output, err := utils.execCommand(ctx, ModeCapture, command); err != nil {
kubectlArgs := []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubectlArgs...); err != nil {
logger.Failuref("%s: %s", deployment, strings.TrimSuffix(output, "\n"))
ok = false
} else {

View File

@@ -17,26 +17,13 @@ limitations under the License.
package main
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion",
Short: "Generates bash completion scripts",
Example: `To load completion run
. <(gotk completion)
To configure your bash shell to load completions for each session add to your bashrc
# ~/.bashrc or ~/.profile
. <(gotk completion)
`,
Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenBashCompletion(os.Stdout)
},
Short: "Generates completion scripts for various shells",
Long: "The completion sub-command generates completion scripts for various shells",
}
func init() {

View File

@@ -0,0 +1,44 @@
/*
Copyright 2020 The Flux CD contributors.
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 (
"os"
"github.com/spf13/cobra"
)
var completionBashCmd = &cobra.Command{
Use: "bash",
Short: "Generates bash completion scripts",
Example: `To load completion run
. <(gotk completion bash)
To configure your bash shell to load completions for each session add to your bashrc
# ~/.bashrc or ~/.profile
command -v gotk >/dev/null && . <(gotk completion bash)
`,
Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenBashCompletion(os.Stdout)
},
}
func init() {
completionCmd.AddCommand(completionBashCmd)
}

View File

@@ -0,0 +1,45 @@
/*
Copyright 2020 The Flux CD contributors.
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 (
"os"
"github.com/spf13/cobra"
)
var completionFishCmd = &cobra.Command{
Use: "fish",
Short: "Generates fish completion scripts",
Example: `To load completion run
. <(gotk completion fish)
To configure your fish shell to load completions for each session write this script to your completions dir:
gotk completion fish > ~/.config/fish/completions/gotk
See http://fishshell.com/docs/current/index.html#completion-own for more details
`,
Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenFishCompletion(os.Stdout, true)
},
}
func init() {
completionCmd.AddCommand(completionFishCmd)
}

View File

@@ -0,0 +1,51 @@
/*
Copyright 2020 The Flux CD contributors.
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 (
"os"
"github.com/spf13/cobra"
)
var completionPowerShellCmd = &cobra.Command{
Use: "powershell",
Short: "Generates powershell completion scripts",
Example: `To load completion run
. <(gotk completion powershell)
To configure your powershell shell to load completions for each session add to your powershell profile
Windows:
cd "$env:USERPROFILE\Documents\WindowsPowerShell\Modules"
gotk completion >> gotk-completion.ps1
Linux:
cd "${XDG_CONFIG_HOME:-"$HOME/.config/"}/powershell/modules"
gotk completion >> gotk-completions.ps1
`,
Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenPowerShellCompletion(os.Stdout)
},
}
func init() {
completionCmd.AddCommand(completionPowerShellCmd)
}

View File

@@ -0,0 +1,52 @@
/*
Copyright 2020 The Flux CD contributors.
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 (
"os"
"github.com/spf13/cobra"
)
var completionZshCmd = &cobra.Command{
Use: "zsh",
Short: "Generates zsh completion scripts",
Example: `To load completion run
. <(gotk completion zsh) && compdef _gotk gotk
To configure your zsh shell to load completions for each session add to your zshrc
# ~/.zshrc or ~/.profile
command -v gotk >/dev/null && . <(gotk completion zsh) && compdef _gotk gotk
or write a cached file in one of the completion directories in your ${fpath}:
echo "${fpath// /\n}" | grep -i completion
gotk completions zsh > _gotk
mv _gotk ~/.oh-my-zsh/completions # oh-my-zsh
mv _gotk ~/.zprezto/modules/completion/external/src/ # zprezto
`,
Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenZshCompletion(os.Stdout)
},
}
func init() {
completionCmd.AddCommand(completionZshCmd)
}

View File

@@ -17,8 +17,12 @@ limitations under the License.
package main
import (
"fmt"
"strings"
"time"
"k8s.io/apimachinery/pkg/util/validation"
"github.com/spf13/cobra"
)
@@ -31,10 +35,38 @@ var createCmd = &cobra.Command{
var (
interval time.Duration
export bool
labels []string
)
func init() {
createCmd.PersistentFlags().DurationVarP(&interval, "interval", "", time.Minute, "source sync interval")
createCmd.PersistentFlags().BoolVar(&export, "export", false, "export in YAML format to stdout")
createCmd.PersistentFlags().StringSliceVar(&labels, "label", nil,
"set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)")
rootCmd.AddCommand(createCmd)
}
func parseLabels() (map[string]string, error) {
result := make(map[string]string)
for _, label := range labels {
// validate key value pair
parts := strings.Split(label, "=")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid label format '%s', must be key=value", label)
}
// validate label name
if errors := validation.IsQualifiedName(parts[0]); len(errors) > 0 {
return nil, fmt.Errorf("invalid label '%s': %v", parts[0], errors)
}
// validate label value
if errors := validation.IsValidLabelValue(parts[1]); len(errors) > 0 {
return nil, fmt.Errorf("invalid label value '%s': %v", parts[1], errors)
}
result[parts[0]] = parts[1]
}
return result, nil
}

192
cmd/gotk/create_alert.go Normal file
View File

@@ -0,0 +1,192 @@
/*
Copyright 2020 The Flux CD contributors.
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/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"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"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var createAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Create or update a Alert resource",
Long: "The create alert command generates a Alert resource.",
Example: ` # Create an Alert for kustomization events
gotk create alert \
--event-severity info \
--event-source Kustomization/gotk-system \
--provider-ref slack \
gotk-system
`,
RunE: createAlertCmdRun,
}
var (
aProviderRef string
aEventSeverity string
aEventSources []string
)
func init() {
createAlertCmd.Flags().StringVar(&aProviderRef, "provider-ref", "", "reference to provider")
createAlertCmd.Flags().StringVar(&aEventSeverity, "event-severity", "", "severity of events to send alerts for")
createAlertCmd.Flags().StringArrayVar(&aEventSources, "event-source", []string{}, "sources that should generate alerts (<kind>/<name>)")
createCmd.AddCommand(createAlertCmd)
}
func createAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Alert name is required")
}
name := args[0]
if aProviderRef == "" {
return fmt.Errorf("provider ref is required")
}
eventSources := []notificationv1.CrossNamespaceObjectReference{}
for _, eventSource := range aEventSources {
kind, name := utils.ParseObjectKindName(eventSource)
if kind == "" {
return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", eventSource)
}
eventSources = append(eventSources, notificationv1.CrossNamespaceObjectReference{
Kind: kind,
Name: name,
})
}
if len(eventSources) == 0 {
return fmt.Errorf("at least one event source is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
if !export {
logger.Generatef("generating Alert")
}
alert := notificationv1.Alert{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: notificationv1.AlertSpec{
ProviderRef: corev1.LocalObjectReference{
Name: aProviderRef,
},
EventSeverity: aEventSeverity,
EventSources: eventSources,
Suspend: false,
},
}
if export {
return exportAlert(alert)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
logger.Actionf("applying Alert")
namespacedName, err := upsertAlert(ctx, kubeClient, &alert)
if err != nil {
return err
}
logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert %s is ready", name)
return nil
}
func upsertAlert(ctx context.Context, kubeClient client.Client,
alert *notificationv1.Alert) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: alert.GetNamespace(),
Name: alert.GetName(),
}
var existing notificationv1.Alert
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, alert); err != nil {
return namespacedName, err
} else {
logger.Successf("Alert created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = alert.Labels
existing.Spec = alert.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
alert = &existing
logger.Successf("Alert updated")
return namespacedName, nil
}
func isAlertReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, alert *notificationv1.Alert) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, alert)
if err != nil {
return false, err
}
if c := meta.GetCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -0,0 +1,190 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
)
var createAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Create or update a Provider resource",
Long: "The create alert-provider command generates a Provider resource.",
Example: ` # Create a Provider for a Slack channel
gotk create alert-provider slack \
--type slack \
--channel general \
--address https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
--secret-ref webhook-url
# Create a Provider for a Github repository
gotk create alert-provider github-podinfo \
--type github \
--address https://github.com/stefanprodan/podinfo \
--secret-ref github-token
`,
RunE: createAlertProviderCmdRun,
}
var (
apType string
apChannel string
apUsername string
apAddress string
apSecretRef string
)
func init() {
createAlertProviderCmd.Flags().StringVar(&apType, "type", "", "type of provider")
createAlertProviderCmd.Flags().StringVar(&apChannel, "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(&apAddress, "address", "", "path to either the git repository, chat provider or webhook")
createAlertProviderCmd.Flags().StringVar(&apSecretRef, "secret-ref", "", "name of secret containing authentication token")
createCmd.AddCommand(createAlertProviderCmd)
}
func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Provider name is required")
}
name := args[0]
if apType == "" {
return fmt.Errorf("Provider type is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
if !export {
logger.Generatef("generating Provider")
}
provider := notificationv1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: notificationv1.ProviderSpec{
Type: apType,
Channel: apChannel,
Username: apUsername,
Address: apAddress,
},
}
if apSecretRef != "" {
provider.Spec.SecretRef = &corev1.LocalObjectReference{
Name: apSecretRef,
}
}
if export {
return exportAlertProvider(provider)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
logger.Actionf("applying Provider")
namespacedName, err := upsertAlertProvider(ctx, kubeClient, &provider)
if err != nil {
return err
}
logger.Waitingf("waiting for Provider reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil {
return err
}
logger.Successf("Provider %s is ready", name)
return nil
}
func upsertAlertProvider(ctx context.Context, kubeClient client.Client,
provider *notificationv1.Provider) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: provider.GetNamespace(),
Name: provider.GetName(),
}
var existing notificationv1.Provider
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, provider); err != nil {
return namespacedName, err
} else {
logger.Successf("Provider created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = provider.Labels
existing.Spec = provider.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
provider = &existing
logger.Successf("Provider updated")
return namespacedName, nil
}
func isAlertProviderReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, provider *notificationv1.Provider) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, provider)
if err != nil {
return false, err
}
if c := meta.GetCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -20,21 +20,21 @@ import (
"context"
"fmt"
"io/ioutil"
"strings"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/flags"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
)
var createHelmReleaseCmd = &cobra.Command{
@@ -55,6 +55,12 @@ var createHelmReleaseCmd = &cobra.Command{
--source=GitRepository/podinfo \
--chart=./charts/podinfo
# Create a HelmRelease with a chart from a Bucket source
gotk create hr podinfo \
--interval=10m \
--source=Bucket/podinfo \
--chart=./charts/podinfo
# Create a HelmRelease with values from a local YAML file
gotk create hr podinfo \
--source=HelmRepository/podinfo \
@@ -85,7 +91,7 @@ var createHelmReleaseCmd = &cobra.Command{
var (
hrName string
hrSource string
hrSource flags.HelmChartSource
hrDependsOn []string
hrChart string
hrChartVersion string
@@ -94,11 +100,11 @@ var (
)
func init() {
createHelmReleaseCmd.Flags().StringVar(&hrName, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<hr-name>'")
createHelmReleaseCmd.Flags().StringVar(&hrSource, "source", "", "source that contains the chart (<kind>/<name>)")
createHelmReleaseCmd.Flags().StringVar(&hrName, "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().StringVar(&hrChart, "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().StringArrayVar(&hrDependsOn, "depends-on", nil, "HelmReleases that must be ready before this release can be installed")
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().StringVar(&hrTargetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace")
createHelmReleaseCmd.Flags().StringVar(&hrValuesFile, "values", "", "local path to the values.yaml file")
createCmd.AddCommand(createHelmReleaseCmd)
@@ -106,45 +112,32 @@ func init() {
func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("release name is required")
return fmt.Errorf("HelmRelease name is required")
}
name := args[0]
if hrSource == "" {
return fmt.Errorf("source is required")
}
hrSourceElements := strings.Split(hrSource, "/")
if len(hrSourceElements) != 2 {
return fmt.Errorf("source must be in format <kind>/<name>")
}
hrSourceKind, hrSourceName := hrSourceElements[0], hrSourceElements[1]
if hrSourceKind != sourcev1.HelmRepositoryKind && hrSourceKind != sourcev1.GitRepositoryKind {
return fmt.Errorf("source kind must be one of: %s", []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind})
}
if hrChart == "" {
return fmt.Errorf("chart name or path is required")
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
sourceLabels, err := parseLabels()
if err != nil {
return err
}
if !export {
logger.Generatef("generating release")
logger.Generatef("generating HelmRelease")
}
helmRelease := helmv2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: helmv2.HelmReleaseSpec{
ReleaseName: hrName,
DependsOn: hrDependsOn,
DependsOn: utils.MakeDependsOn(hrDependsOn),
Interval: metav1.Duration{
Duration: interval,
},
@@ -154,8 +147,8 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
Chart: hrChart,
Version: hrChartVersion,
SourceRef: helmv2.CrossNamespaceObjectReference{
Kind: hrSourceKind,
Name: hrSourceName,
Kind: hrSource.Kind,
Name: hrSource.Name,
},
},
},
@@ -181,43 +174,33 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
return exportHelmRelease(helmRelease)
}
logger.Actionf("applying release")
if err := upsertHelmRelease(ctx, kubeClient, helmRelease); err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
logger.Waitingf("waiting for reconciliation")
chartName := fmt.Sprintf("%s-%s", namespace, name)
if err := wait.PollImmediate(pollInterval, timeout,
isHelmChartReady(ctx, kubeClient, chartName, namespace)); err != nil {
return err
}
if err := wait.PollImmediate(pollInterval, timeout,
isHelmReleaseReady(ctx, kubeClient, name, namespace)); err != nil {
return err
}
logger.Successf("release %s is ready", name)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return fmt.Errorf("release failed: %w", err)
return err
}
if helmRelease.Status.LastAppliedRevision != "" {
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
} else {
return fmt.Errorf("reconciliation failed")
logger.Actionf("applying HelmRelease")
namespacedName, err := upsertHelmRelease(ctx, kubeClient, &helmRelease)
if err != nil {
return err
}
logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmReleaseReady(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
return err
}
logger.Successf("HelmRelease %s is ready", name)
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
return nil
}
func upsertHelmRelease(ctx context.Context, kubeClient client.Client, helmRelease helmv2.HelmRelease) error {
func upsertHelmRelease(ctx context.Context, kubeClient client.Client,
helmRelease *helmv2.HelmRelease) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: helmRelease.GetNamespace(),
Name: helmRelease.GetName(),
@@ -227,50 +210,39 @@ func upsertHelmRelease(ctx context.Context, kubeClient client.Client, helmReleas
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &helmRelease); err != nil {
return err
if err := kubeClient.Create(ctx, helmRelease); err != nil {
return namespacedName, err
} else {
logger.Successf("release created")
return nil
logger.Successf("HelmRelease created")
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = helmRelease.Labels
existing.Spec = helmRelease.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
logger.Successf("release updated")
return nil
helmRelease = &existing
logger.Successf("HelmRelease updated")
return namespacedName, nil
}
func isHelmChartReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isHelmReleaseReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionFunc {
return func() (bool, error) {
var helmChart sourcev1.HelmChart
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmChart)
err := kubeClient.Get(ctx, namespacedName, helmRelease)
if err != nil {
if apierrors.IsNotFound(err) {
return false, nil
}
return false, err
}
for _, condition := range helmChart.Status.Conditions {
if condition.Type == helmv2.ReadyCondition {
if condition.Status == corev1.ConditionTrue {
return true, nil
} else if condition.Status == corev1.ConditionFalse {
return false, fmt.Errorf(condition.Message)
}
}
// Confirm the state we are observing is for the current generation
if helmRelease.Generation != helmRelease.Status.ObservedGeneration {
return false, nil
}
return false, nil
return meta.HasReadyCondition(helmRelease.Status.Conditions), nil
}
}

View File

@@ -30,15 +30,18 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/flags"
"github.com/fluxcd/toolkit/internal/utils"
)
var createKsCmd = &cobra.Command{
Use: "kustomization [name]",
Aliases: []string{"ks"},
Short: "Create or update a Kustomization resource",
Long: "The kustomization source create command generates a Kustomize resource for a given GitRepository source.",
Long: "The kustomization source create command generates a Kustomize resource for a given source.",
Example: ` # Create a Kustomization resource from a source at a given path
gotk create kustomization contour \
--source=contour \
@@ -59,53 +62,50 @@ var createKsCmd = &cobra.Command{
--interval=5m \
--validation=client
# Create a Kustomization resource that runs under a service account
gotk create kustomization webapp \
--source=webapp \
--path="./deploy/overlays/staging" \
# Create a Kustomization resource that references a Bucket
gotk create kustomization secrets \
--source=Bucket/secrets \
--prune=true \
--interval=5m \
--validation=client \
--sa-name=reconclier \
--sa-namespace=staging
--interval=5m
`,
RunE: createKsCmdRun,
}
var (
ksSource string
ksPath string
ksPrune bool
ksDependsOn []string
ksValidation string
ksHealthCheck []string
ksHealthTimeout time.Duration
ksSAName string
ksSANamespace string
ksSource flags.KustomizationSource
ksPath string
ksPrune bool
ksDependsOn []string
ksValidation string
ksHealthCheck []string
ksHealthTimeout time.Duration
ksSAName string
ksSANamespace string
ksDecryptionProvider flags.DecryptionProvider
ksDecryptionSecret string
)
func init() {
createKsCmd.Flags().StringVar(&ksSource, "source", "", "GitRepository name")
createKsCmd.Flags().Var(&ksSource, "source", ksSource.Description())
createKsCmd.Flags().StringVar(&ksPath, "path", "./", "path to the directory containing the Kustomization file")
createKsCmd.Flags().BoolVar(&ksPrune, "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().DurationVar(&ksHealthTimeout, "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().StringArrayVar(&ksDependsOn, "depends-on", nil, "Kustomization that must be ready before this Kustomization can be applied")
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().StringVar(&ksSAName, "sa-name", "", "service account name")
createKsCmd.Flags().StringVar(&ksSANamespace, "sa-namespace", "", "service account namespace")
createKsCmd.Flags().Var(&ksDecryptionProvider, "decryption-provider", ksDecryptionProvider.Description())
createKsCmd.Flags().StringVar(&ksDecryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
createCmd.AddCommand(createKsCmd)
}
func createKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("kustomization name is required")
return fmt.Errorf("Kustomization name is required")
}
name := args[0]
if ksSource == "" {
return fmt.Errorf("source is required")
}
if ksPath == "" {
return fmt.Errorf("path is required")
}
@@ -114,24 +114,30 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
}
if !export {
logger.Generatef("generating kustomization")
logger.Generatef("generating Kustomization")
}
ksLabels, err := parseLabels()
if err != nil {
return err
}
kustomization := kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: ksLabels,
},
Spec: kustomizev1.KustomizationSpec{
DependsOn: ksDependsOn,
DependsOn: utils.MakeDependsOn(ksDependsOn),
Interval: metav1.Duration{
Duration: interval,
},
Path: ksPath,
Prune: ksPrune,
SourceRef: kustomizev1.CrossNamespaceObjectReference{
Kind: sourcev1.GitRepositoryKind,
Name: ksSource,
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: ksSource.Kind,
Name: ksSource.Name,
},
Suspend: false,
Validation: ksValidation,
@@ -139,31 +145,39 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
}
if len(ksHealthCheck) > 0 {
healthChecks := make([]kustomizev1.WorkloadReference, 0)
healthChecks := make([]kustomizev1.CrossNamespaceObjectReference, 0)
for _, w := range ksHealthCheck {
kindObj := strings.Split(w, "/")
if len(kindObj) != 2 {
return fmt.Errorf("invalid health check '%s' must be in the format 'kind/name.namespace' %v", w, kindObj)
}
kind := kindObj[0]
//TODO: (stefan) extend this list with all the kstatus builtin kinds
kinds := map[string]bool{
"Deployment": true,
"DaemonSet": true,
"StatefulSet": true,
"Deployment": true,
"DaemonSet": true,
"StatefulSet": true,
helmv2.HelmReleaseKind: true,
}
if !kinds[kind] {
return fmt.Errorf("invalid health check kind '%s' can be Deployment, DaemonSet or StatefulSet", kind)
return fmt.Errorf("invalid health check kind '%s' can be HelmRelease, Deployment, DaemonSet or StatefulSet", kind)
}
nameNs := strings.Split(kindObj[1], ".")
if len(nameNs) != 2 {
return fmt.Errorf("invalid health check '%s' must be in the format 'kind/name.namespace'", w)
}
healthChecks = append(healthChecks, kustomizev1.WorkloadReference{
check := kustomizev1.CrossNamespaceObjectReference{
Kind: kind,
Name: nameNs[0],
Namespace: nameNs[1],
})
}
if kind == helmv2.HelmReleaseKind {
check.APIVersion = helmv2.GroupVersion.String()
}
healthChecks = append(healthChecks, check)
}
kustomization.Spec.HealthChecks = healthChecks
kustomization.Spec.Timeout = &metav1.Duration{
@@ -178,6 +192,16 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
}
}
if ksDecryptionProvider != "" {
kustomization.Spec.Decryption = &kustomizev1.Decryption{
Provider: ksDecryptionProvider.String(),
}
if ksDecryptionSecret != "" {
kustomization.Spec.Decryption.SecretRef = &corev1.LocalObjectReference{Name: ksDecryptionSecret}
}
}
if export {
return exportKs(kustomization)
}
@@ -185,43 +209,30 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
logger.Actionf("applying kustomization")
if err := upsertKustomization(ctx, kubeClient, kustomization); err != nil {
logger.Actionf("applying Kustomization")
namespacedName, err := upsertKustomization(ctx, kubeClient, &kustomization)
if err != nil {
return err
}
logger.Waitingf("waiting for kustomization sync")
logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil {
isKustomizationReady(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
return err
}
logger.Successf("Kustomization %s is ready", name)
logger.Successf("kustomization %s is ready", name)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
return fmt.Errorf("kustomization sync failed: %w", err)
}
if kustomization.Status.LastAppliedRevision != "" {
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
} else {
return fmt.Errorf("kustomization sync failed")
}
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
return nil
}
func upsertKustomization(ctx context.Context, kubeClient client.Client, kustomization kustomizev1.Kustomization) error {
func upsertKustomization(ctx context.Context, kubeClient client.Client,
kustomization *kustomizev1.Kustomization) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: kustomization.GetNamespace(),
Name: kustomization.GetName(),
@@ -231,45 +242,45 @@ func upsertKustomization(ctx context.Context, kubeClient client.Client, kustomiz
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &kustomization); err != nil {
return err
if err := kubeClient.Create(ctx, kustomization); err != nil {
return namespacedName, err
} else {
logger.Successf("kustomization created")
return nil
logger.Successf("Kustomization created")
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = kustomization.Labels
existing.Spec = kustomization.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
logger.Successf("kustomization updated")
return nil
kustomization = &existing
logger.Successf("Kustomization updated")
return namespacedName, nil
}
func isKustomizationReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isKustomizationReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionFunc {
return func() (bool, error) {
var kustomization kustomizev1.Kustomization
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &kustomization)
err := kubeClient.Get(ctx, namespacedName, kustomization)
if err != nil {
return false, err
}
for _, condition := range kustomization.Status.Conditions {
if condition.Type == sourcev1.ReadyCondition {
if condition.Status == corev1.ConditionTrue {
return true, nil
} else if condition.Status == corev1.ConditionFalse {
return false, fmt.Errorf(condition.Message)
}
// Confirm the state we are observing is for the current generation
if kustomization.Generation != kustomization.Status.ObservedGeneration {
return false, nil
}
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil

202
cmd/gotk/create_receiver.go Normal file
View File

@@ -0,0 +1,202 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
)
var createReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Create or update a Receiver resource",
Long: "The create receiver command generates a Receiver resource.",
Example: ` # Create a Receiver
gotk create receiver github-receiver \
--type github \
--event ping \
--event push \
--secret-ref webhook-token \
--resource GitRepository/webapp \
--resource HelmRepository/webapp
`,
RunE: createReceiverCmdRun,
}
var (
rcvType string
rcvSecretRef string
rcvEvents []string
rcvResources []string
)
func init() {
createReceiverCmd.Flags().StringVar(&rcvType, "type", "", "")
createReceiverCmd.Flags().StringVar(&rcvSecretRef, "secret-ref", "", "")
createReceiverCmd.Flags().StringArrayVar(&rcvEvents, "event", []string{}, "")
createReceiverCmd.Flags().StringArrayVar(&rcvResources, "resource", []string{}, "")
createCmd.AddCommand(createReceiverCmd)
}
func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Receiver name is required")
}
name := args[0]
if rcvType == "" {
return fmt.Errorf("Receiver type is required")
}
if rcvSecretRef == "" {
return fmt.Errorf("secret ref is required")
}
resources := []notificationv1.CrossNamespaceObjectReference{}
for _, resource := range rcvResources {
kind, name := utils.ParseObjectKindName(resource)
if kind == "" {
return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", resource)
}
resources = append(resources, notificationv1.CrossNamespaceObjectReference{
Kind: kind,
Name: name,
})
}
if len(resources) == 0 {
return fmt.Errorf("atleast one resource is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
if !export {
logger.Generatef("generating Receiver")
}
receiver := notificationv1.Receiver{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: notificationv1.ReceiverSpec{
Type: rcvType,
Events: rcvEvents,
Resources: resources,
SecretRef: corev1.LocalObjectReference{
Name: rcvSecretRef,
},
Suspend: false,
},
}
if export {
return exportReceiver(receiver)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
logger.Actionf("applying Receiver")
namespacedName, err := upsertReceiver(ctx, kubeClient, &receiver)
if err != nil {
return err
}
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver %s is ready", name)
logger.Successf("generated webhook URL %s", receiver.Status.URL)
return nil
}
func upsertReceiver(ctx context.Context, kubeClient client.Client,
receiver *notificationv1.Receiver) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: receiver.GetNamespace(),
Name: receiver.GetName(),
}
var existing notificationv1.Receiver
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, receiver); err != nil {
return namespacedName, err
} else {
logger.Successf("Receiver created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = receiver.Labels
existing.Spec = receiver.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
receiver = &existing
logger.Successf("Receiver updated")
return namespacedName, nil
}
func isReceiverReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, receiver)
if err != nil {
return false, err
}
if c := meta.GetCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -0,0 +1,227 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"os"
"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"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/flags"
"github.com/fluxcd/toolkit/internal/utils"
)
var createSourceBucketCmd = &cobra.Command{
Use: "bucket [name]",
Short: "Create or update a Bucket source",
Long: `
The create source bucket command generates a Bucket resource and waits for it to be downloaded.
For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`,
Example: ` # Create a source from a Buckets using static authentication
gotk create source bucket podinfo \
--bucket-name=podinfo \
--endpoint=minio.minio.svc.cluster.local:9000 \
--insecure=true \
--access-key=myaccesskey \
--secret-key=mysecretkey \
--interval=10m
# Create a source from an Amazon S3 Bucket using IAM authentication
gotk create source bucket podinfo \
--bucket-name=podinfo \
--provider=aws \
--endpoint=s3.amazonaws.com \
--region=us-east-1 \
--interval=10m
`,
RunE: createSourceBucketCmdRun,
}
var (
sourceBucketName string
sourceBucketProvider = flags.SourceBucketProvider(sourcev1.GenericBucketProvider)
sourceBucketEndpoint string
sourceBucketAccessKey string
sourceBucketSecretKey string
sourceBucketRegion string
sourceBucketInsecure bool
sourceBucketSecretRef string
)
func init() {
createSourceBucketCmd.Flags().Var(&sourceBucketProvider, "provider", sourceBucketProvider.Description())
createSourceBucketCmd.Flags().StringVar(&sourceBucketName, "bucket-name", "", "the bucket name")
createSourceBucketCmd.Flags().StringVar(&sourceBucketEndpoint, "endpoint", "", "the bucket endpoint address")
createSourceBucketCmd.Flags().StringVar(&sourceBucketAccessKey, "access-key", "", "the bucket access key")
createSourceBucketCmd.Flags().StringVar(&sourceBucketSecretKey, "secret-key", "", "the bucket secret key")
createSourceBucketCmd.Flags().StringVar(&sourceBucketRegion, "region", "", "the bucket region")
createSourceBucketCmd.Flags().BoolVar(&sourceBucketInsecure, "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")
createSourceCmd.AddCommand(createSourceBucketCmd)
}
func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Bucket source name is required")
}
name := args[0]
if sourceBucketName == "" {
return fmt.Errorf("bucket-name is required")
}
if sourceBucketEndpoint == "" {
return fmt.Errorf("endpoint is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
tmpDir, err := ioutil.TempDir("", name)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
bucket := &sourcev1.Bucket{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: sourcev1.BucketSpec{
BucketName: sourceBucketName,
Provider: sourceBucketProvider.String(),
Insecure: sourceBucketInsecure,
Endpoint: sourceBucketEndpoint,
Region: sourceBucketRegion,
Interval: metav1.Duration{
Duration: interval,
},
},
}
if sourceHelmSecretRef != "" {
bucket.Spec.SecretRef = &corev1.LocalObjectReference{
Name: sourceBucketSecretRef,
}
}
if export {
return exportBucket(*bucket)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
logger.Generatef("generating Bucket source")
if sourceBucketSecretRef == "" {
secretName := fmt.Sprintf("bucket-%s", name)
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
StringData: map[string]string{},
}
if sourceBucketAccessKey != "" && sourceBucketSecretKey != "" {
secret.StringData["accesskey"] = sourceBucketAccessKey
secret.StringData["secretkey"] = sourceBucketSecretKey
}
if len(secret.StringData) > 0 {
logger.Actionf("applying secret with the bucket credentials")
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err
}
bucket.Spec.SecretRef = &corev1.LocalObjectReference{
Name: secretName,
}
logger.Successf("authentication configured")
}
}
logger.Actionf("applying Bucket source")
namespacedName, err := upsertBucket(ctx, kubeClient, bucket)
if err != nil {
return err
}
logger.Waitingf("waiting for Bucket source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isBucketReady(ctx, kubeClient, namespacedName, bucket)); err != nil {
return err
}
logger.Successf("Bucket source reconciliation completed")
if bucket.Status.Artifact == nil {
return fmt.Errorf("Bucket source reconciliation but no artifact was found")
}
logger.Successf("fetched revision: %s", bucket.Status.Artifact.Revision)
return nil
}
func upsertBucket(ctx context.Context, kubeClient client.Client,
bucket *sourcev1.Bucket) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: bucket.GetNamespace(),
Name: bucket.GetName(),
}
var existing sourcev1.Bucket
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, bucket); err != nil {
return namespacedName, err
} else {
logger.Successf("Bucket source created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = bucket.Labels
existing.Spec = bucket.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
bucket = &existing
logger.Successf("Bucket source updated")
return namespacedName, nil
}

View File

@@ -25,7 +25,11 @@ import (
"os"
"time"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/flags"
"github.com/fluxcd/toolkit/internal/utils"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
@@ -83,15 +87,17 @@ For private Git repositories, the basic authentication credentials are stored in
}
var (
sourceGitURL string
sourceGitBranch string
sourceGitTag string
sourceGitSemver string
sourceGitUsername string
sourceGitPassword string
sourceGitKeyAlgorithm PublicKeyAlgorithm = "rsa"
sourceGitRSABits RSAKeyBits = 2048
sourceGitECDSACurve = ECDSACurve{elliptic.P384()}
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() {
@@ -104,13 +110,14 @@ func init() {
createSourceGitCmd.Flags().Var(&sourceGitKeyAlgorithm, "ssh-key-algorithm", sourceGitKeyAlgorithm.Description())
createSourceGitCmd.Flags().Var(&sourceGitRSABits, "ssh-rsa-bits", sourceGitRSABits.Description())
createSourceGitCmd.Flags().Var(&sourceGitECDSACurve, "ssh-ecdsa-curve", sourceGitECDSACurve.Description())
createSourceGitCmd.Flags().StringVarP(&sourceGitSecretRef, "secret-ref", "", "", "the name of an existing secret containing SSH or basic credentials")
createSourceCmd.AddCommand(createSourceGitCmd)
}
func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
return fmt.Errorf("GitRepository source name is required")
}
name := args[0]
@@ -129,10 +136,16 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("git URL parse failed: %w", err)
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
gitRepository := sourcev1.GitRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: sourcev1.GitRepositorySpec{
URL: sourceGitURL,
@@ -152,20 +165,27 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
}
if export {
if sourceGitSecretRef != "" {
gitRepository.Spec.SecretRef = &corev1.LocalObjectReference{
Name: sourceGitSecretRef,
}
}
return exportGit(gitRepository)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
withAuth := false
// TODO(hidde): move all auth prep to separate func?
if u.Scheme == "ssh" {
if sourceGitSecretRef != "" {
withAuth = true
} else if u.Scheme == "ssh" {
logger.Actionf("generating deploy key pair")
pair, err := generateKeyPair(ctx)
if err != nil {
@@ -227,42 +247,35 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("authentication configured")
}
logger.Generatef("generating source")
logger.Generatef("generating GitRepository source")
if withAuth {
secretName := name
if sourceGitSecretRef != "" {
secretName = sourceGitSecretRef
}
gitRepository.Spec.SecretRef = &corev1.LocalObjectReference{
Name: name,
Name: secretName,
}
}
logger.Actionf("applying source")
if err := upsertGitRepository(ctx, kubeClient, gitRepository); err != nil {
return err
}
logger.Waitingf("waiting for git sync")
if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
return err
}
logger.Successf("git sync completed")
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &gitRepository)
logger.Actionf("applying GitRepository source")
namespacedName, err := upsertGitRepository(ctx, kubeClient, &gitRepository)
if err != nil {
return fmt.Errorf("git sync failed: %w", err)
return err
}
if gitRepository.Status.Artifact != nil {
logger.Successf("fetched revision: %s", gitRepository.Status.Artifact.Revision)
} else {
return fmt.Errorf("git sync failed, artifact not found")
logger.Waitingf("waiting for GitRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil {
return err
}
logger.Successf("GitRepository source reconciliation completed")
if gitRepository.Status.Artifact == nil {
return fmt.Errorf("GitRepository source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision: %s", gitRepository.Status.Artifact.Revision)
return nil
}
@@ -323,7 +336,8 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S
return nil
}
func upsertGitRepository(ctx context.Context, kubeClient client.Client, gitRepository sourcev1.GitRepository) error {
func upsertGitRepository(ctx context.Context, kubeClient client.Client,
gitRepository *sourcev1.GitRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: gitRepository.GetNamespace(),
Name: gitRepository.GetName(),
@@ -333,45 +347,40 @@ func upsertGitRepository(ctx context.Context, kubeClient client.Client, gitRepos
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &gitRepository); err != nil {
return err
if err := kubeClient.Create(ctx, gitRepository); err != nil {
return namespacedName, err
} else {
logger.Successf("source created")
return nil
logger.Successf("GitRepository source created")
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = gitRepository.Labels
existing.Spec = gitRepository.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
logger.Successf("source updated")
return nil
gitRepository = &existing
logger.Successf("GitRepository source updated")
return namespacedName, nil
}
func isGitRepositoryReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isGitRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, gitRepository *sourcev1.GitRepository) wait.ConditionFunc {
return func() (bool, error) {
var gitRepository sourcev1.GitRepository
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &gitRepository)
err := kubeClient.Get(ctx, namespacedName, gitRepository)
if err != nil {
return false, err
}
for _, condition := range gitRepository.Status.Conditions {
if condition.Type == sourcev1.ReadyCondition {
if condition.Status == corev1.ConditionTrue {
return true, nil
} else if condition.Status == corev1.ConditionFalse {
return false, fmt.Errorf(condition.Message)
}
if c := meta.GetCondition(gitRepository.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil

View File

@@ -19,18 +19,20 @@ package main
import (
"context"
"fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/spf13/cobra"
"io/ioutil"
"net/url"
"os"
"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"
"k8s.io/apimachinery/pkg/util/wait"
"net/url"
"os"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var createSourceHelmCmd = &cobra.Command{
@@ -61,12 +63,13 @@ For private Helm repositories, the basic authentication credentials are stored i
}
var (
sourceHelmURL string
sourceHelmUsername string
sourceHelmPassword string
sourceHelmCertFile string
sourceHelmKeyFile string
sourceHelmCAFile string
sourceHelmURL string
sourceHelmUsername string
sourceHelmPassword string
sourceHelmCertFile string
sourceHelmKeyFile string
sourceHelmCAFile string
sourceHelmSecretRef string
)
func init() {
@@ -76,21 +79,26 @@ func init() {
createSourceHelmCmd.Flags().StringVar(&sourceHelmCertFile, "cert-file", "", "TLS authentication cert file path")
createSourceHelmCmd.Flags().StringVar(&sourceHelmKeyFile, "key-file", "", "TLS authentication key file path")
createSourceHelmCmd.Flags().StringVar(&sourceHelmCAFile, "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")
createSourceCmd.AddCommand(createSourceHelmCmd)
}
func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
return fmt.Errorf("HelmRepository source name is required")
}
name := args[0]
secretName := fmt.Sprintf("helm-%s", name)
if sourceHelmURL == "" {
return fmt.Errorf("url is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
tmpDir, err := ioutil.TempDir("", name)
if err != nil {
return err
@@ -101,10 +109,11 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("url parse failed: %w", err)
}
helmRepository := sourcev1.HelmRepository{
helmRepository := &sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: sourcev1.HelmRepositorySpec{
URL: sourceHelmURL,
@@ -114,98 +123,97 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
},
}
if sourceHelmSecretRef != "" {
helmRepository.Spec.SecretRef = &corev1.LocalObjectReference{
Name: sourceHelmSecretRef,
}
}
if export {
return exportHelmRepository(helmRepository)
return exportHelmRepository(*helmRepository)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
logger.Generatef("generating source")
logger.Generatef("generating HelmRepository source")
if sourceHelmSecretRef == "" {
secretName := fmt.Sprintf("helm-%s", name)
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
StringData: map[string]string{},
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
StringData: map[string]string{},
}
if sourceHelmUsername != "" && sourceHelmPassword != "" {
secret.StringData["username"] = sourceHelmUsername
secret.StringData["password"] = sourceHelmPassword
}
if sourceHelmCertFile != "" && sourceHelmKeyFile != "" {
cert, err := ioutil.ReadFile(sourceHelmCertFile)
if err != nil {
return fmt.Errorf("failed to read repository cert file '%s': %w", sourceHelmCertFile, err)
}
secret.StringData["certFile"] = string(cert)
key, err := ioutil.ReadFile(sourceHelmKeyFile)
if err != nil {
return fmt.Errorf("failed to read repository key file '%s': %w", sourceHelmKeyFile, err)
}
secret.StringData["keyFile"] = string(key)
}
if sourceHelmCAFile != "" {
ca, err := ioutil.ReadFile(sourceHelmCAFile)
if err != nil {
return fmt.Errorf("failed to read repository CA file '%s': %w", sourceHelmCAFile, err)
}
secret.StringData["caFile"] = string(ca)
}
if len(secret.StringData) > 0 {
logger.Actionf("applying secret with repository credentials")
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err
}
helmRepository.Spec.SecretRef = &corev1.LocalObjectReference{
Name: secretName,
}
logger.Successf("authentication configured")
}
}
if sourceHelmUsername != "" && sourceHelmPassword != "" {
secret.StringData["username"] = sourceHelmUsername
secret.StringData["password"] = sourceHelmPassword
}
if sourceHelmCertFile != "" && sourceHelmKeyFile != "" {
cert, err := ioutil.ReadFile(sourceHelmCertFile)
if err != nil {
return fmt.Errorf("failed to read repository cert file '%s': %w", sourceHelmCertFile, err)
}
secret.StringData["certFile"] = string(cert)
key, err := ioutil.ReadFile(sourceHelmKeyFile)
if err != nil {
return fmt.Errorf("failed to read repository key file '%s': %w", sourceHelmKeyFile, err)
}
secret.StringData["keyFile"] = string(key)
}
if sourceHelmCAFile != "" {
ca, err := ioutil.ReadFile(sourceHelmCAFile)
if err != nil {
return fmt.Errorf("failed to read repository CA file '%s': %w", sourceHelmCAFile, err)
}
secret.StringData["caFile"] = string(ca)
}
if len(secret.StringData) > 0 {
logger.Actionf("applying secret with repository credentials")
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err
}
helmRepository.Spec.SecretRef = &corev1.LocalObjectReference{
Name: secretName,
}
logger.Successf("authentication configured")
}
logger.Actionf("applying source")
if err := upsertHelmRepository(ctx, kubeClient, helmRepository); err != nil {
logger.Actionf("applying HelmRepository source")
namespacedName, err := upsertHelmRepository(ctx, kubeClient, helmRepository)
if err != nil {
return err
}
logger.Waitingf("waiting for index download")
logger.Waitingf("waiting for HelmRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
isHelmRepositoryReady(ctx, kubeClient, namespacedName, helmRepository)); err != nil {
return err
}
logger.Successf("HelmRepository source reconciliation completed")
logger.Successf("index download completed")
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
if helmRepository.Status.Artifact == nil {
return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found")
}
err = kubeClient.Get(ctx, namespacedName, &helmRepository)
if err != nil {
return fmt.Errorf("helm index failed: %w", err)
}
if helmRepository.Status.Artifact != nil {
logger.Successf("fetched revision: %s", helmRepository.Status.Artifact.Revision)
} else {
return fmt.Errorf("index download failed, artifact not found")
}
logger.Successf("fetched revision: %s", helmRepository.Status.Artifact.Revision)
return nil
}
func upsertHelmRepository(ctx context.Context, kubeClient client.Client, helmRepository sourcev1.HelmRepository) error {
func upsertHelmRepository(ctx context.Context, kubeClient client.Client,
helmRepository *sourcev1.HelmRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: helmRepository.GetNamespace(),
Name: helmRepository.GetName(),
@@ -215,45 +223,22 @@ func upsertHelmRepository(ctx context.Context, kubeClient client.Client, helmRep
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &helmRepository); err != nil {
return err
if err := kubeClient.Create(ctx, helmRepository); err != nil {
return namespacedName, err
} else {
logger.Successf("source created")
return nil
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = helmRepository.Labels
existing.Spec = helmRepository.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
helmRepository = &existing
logger.Successf("source updated")
return nil
}
func exportHelmRepository(source sourcev1.HelmRepository) error {
gvk := sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)
export := sourcev1.HelmRepository{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: source.Name,
Namespace: source.Namespace,
},
Spec: source.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(string(data))
return nil
return namespacedName, nil
}

258
cmd/gotk/create_tenant.go Normal file
View File

@@ -0,0 +1,258 @@
/*
Copyright 2020 The Flux CD contributors.
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 (
"bytes"
"context"
"fmt"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
)
var createTenantCmd = &cobra.Command{
Use: "tenant",
Short: "Create or update a tenant",
Long: `
The create tenant command generates namespaces and role bindings to limit the
reconcilers scope to the tenant namespaces.`,
Example: ` # Create a tenant with access to a namespace
gotk create tenant dev-team \
--with-namespace=frontend \
--label=environment=dev
# Generate tenant namespaces and role bindings in YAML format
gotk create tenant dev-team \
--with-namespace=frontend \
--with-namespace=backend \
--export > dev-team.yaml
`,
RunE: createTenantCmdRun,
}
const (
tenantLabel = "toolkit.fluxcd.io/tenant"
tenantRoleBinding = "gotk-reconciler"
)
var (
tenantNamespaces []string
tenantClusterRole string
)
func init() {
createTenantCmd.Hidden = true
createTenantCmd.Flags().StringSliceVar(&tenantNamespaces, "with-namespace", nil, "namespace belonging to this tenant")
createTenantCmd.Flags().StringVar(&tenantClusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding")
createCmd.AddCommand(createTenantCmd)
}
func createTenantCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("tenant name is required")
}
tenant := args[0]
if err := validation.IsQualifiedName(tenant); len(err) > 0 {
return fmt.Errorf("invalid tenant name '%s': %v", tenant, err)
}
if tenantClusterRole == "" {
return fmt.Errorf("cluster-role is required")
}
if tenantNamespaces == nil {
return fmt.Errorf("with-namespace is required")
}
var namespaces []corev1.Namespace
var roleBindings []rbacv1.RoleBinding
for _, ns := range tenantNamespaces {
if err := validation.IsQualifiedName(ns); len(err) > 0 {
return fmt.Errorf("invalid namespace '%s': %v", ns, err)
}
objLabels, err := parseLabels()
if err != nil {
return err
}
objLabels[tenantLabel] = tenant
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
Labels: objLabels,
},
}
namespaces = append(namespaces, namespace)
roleBinding := rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: tenantRoleBinding,
Namespace: ns,
Labels: objLabels,
},
Subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
Name: fmt.Sprintf("gotk:%s:reconciler", ns),
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: tenantClusterRole,
},
}
roleBindings = append(roleBindings, roleBinding)
}
if export {
for i, _ := range tenantNamespaces {
if err := exportTenant(namespaces[i], roleBindings[1]); err != nil {
return err
}
}
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
for i, _ := range tenantNamespaces {
logger.Actionf("applying namespace %s", namespaces[i].Name)
if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
return err
}
logger.Actionf("applying role binding %s", roleBindings[i].Name)
if err := upsertRoleBinding(ctx, kubeClient, roleBindings[i]); err != nil {
return err
}
}
logger.Successf("tenant setup completed")
return nil
}
func upsertNamespace(ctx context.Context, kubeClient client.Client, namespace corev1.Namespace) error {
namespacedName := types.NamespacedName{
Namespace: namespace.GetNamespace(),
Name: namespace.GetName(),
}
var existing corev1.Namespace
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &namespace); err != nil {
return err
} else {
return nil
}
}
return err
}
if !equality.Semantic.DeepDerivative(namespace.Labels, existing.Labels) {
existing.Labels = namespace.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 {
namespacedName := types.NamespacedName{
Namespace: roleBinding.GetNamespace(),
Name: roleBinding.GetName(),
}
var existing rbacv1.RoleBinding
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &roleBinding); err != nil {
return err
} else {
return nil
}
}
return err
}
if !equality.Semantic.DeepDerivative(roleBinding.Subjects, existing.Subjects) ||
!equality.Semantic.DeepDerivative(roleBinding.RoleRef, existing.RoleRef) ||
!equality.Semantic.DeepDerivative(roleBinding.Labels, existing.Labels) {
if err := kubeClient.Delete(ctx, &existing); err != nil {
return err
}
if err := kubeClient.Create(ctx, &roleBinding); err != nil {
return err
}
}
return nil
}
func exportTenant(namespace corev1.Namespace, roleBinding rbacv1.RoleBinding) error {
namespace.TypeMeta = metav1.TypeMeta{
APIVersion: "v1",
Kind: "Namespace",
}
data, err := yaml.Marshal(namespace)
if err != nil {
return err
}
fmt.Println("---")
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
fmt.Println(resourceToString(data))
roleBinding.TypeMeta = metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
Kind: "RoleBinding",
}
data, err = yaml.Marshal(roleBinding)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

88
cmd/gotk/delete_alert.go Normal file
View File

@@ -0,0 +1,88 @@
/*
Copyright 2020 The Flux CD contributors.
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/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var deleteAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Delete a Alert resource",
Long: "The delete alert command removes the given Alert from the cluster.",
Example: ` # Delete an Alert and the Kubernetes resources created by it
gotk delete alert main
`,
RunE: deleteAlertCmdRun,
}
func init() {
deleteCmd.AddCommand(deleteAlertCmd)
}
func deleteAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
if !deleteSilent {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Alert",
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
logger.Actionf("deleting alert %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &alert)
if err != nil {
return err
}
logger.Successf("alert deleted")
return nil
}

View File

@@ -0,0 +1,88 @@
/*
Copyright 2020 The Flux CD contributors.
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/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var deleteAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Delete a Provider resource",
Long: "The delete alert-provider command removes the given Provider from the cluster.",
Example: ` # Delete a Provider and the Kubernetes resources created by it
gotk delete alert-provider slack
`,
RunE: deleteAlertProviderCmdRun,
}
func init() {
deleteCmd.AddCommand(deleteAlertProviderCmd)
}
func deleteAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("provider name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil {
return err
}
if !deleteSilent {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Provider",
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
logger.Actionf("deleting provider %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &alertProvider)
if err != nil {
return err
}
logger.Successf("provider deleted")
return nil
}

View File

@@ -24,7 +24,8 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var deleteHelmReleaseCmd = &cobra.Command{
@@ -51,7 +52,7 @@ func deleteHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}

View File

@@ -20,7 +20,8 @@ import (
"context"
"fmt"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
@@ -50,7 +51,7 @@ func deleteKsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}

View File

@@ -0,0 +1,88 @@
/*
Copyright 2020 The Flux CD contributors.
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/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var deleteReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Delete a Receiver resource",
Long: "The delete receiver command removes the given Receiver from the cluster.",
Example: ` # Delete an Receiver and the Kubernetes resources created by it
gotk delete receiver main
`,
RunE: deleteReceiverCmdRun,
}
func init() {
deleteCmd.AddCommand(deleteReceiverCmd)
}
func deleteReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
if !deleteSilent {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Receiver",
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
logger.Actionf("deleting receiver %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &receiver)
if err != nil {
return err
}
logger.Successf("receiver deleted")
return nil
}

View File

@@ -0,0 +1,87 @@
/*
Copyright 2020 The Flux CD contributors.
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"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
)
var deleteSourceBucketCmd = &cobra.Command{
Use: "bucket [name]",
Short: "Delete a Bucket source",
Long: "The delete source bucket command deletes the given Bucket from the cluster.",
Example: ` # Delete a Bucket source
gotk delete source bucket podinfo
`,
RunE: deleteSourceBucketCmdRun,
}
func init() {
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)
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

@@ -20,7 +20,8 @@ import (
"context"
"fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
@@ -49,7 +50,7 @@ func deleteSourceGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}

View File

@@ -20,7 +20,8 @@ import (
"context"
"fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
@@ -49,7 +50,7 @@ func deleteSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}

View File

@@ -17,6 +17,8 @@ limitations under the License.
package main
import (
"bytes"
"github.com/spf13/cobra"
)
@@ -35,3 +37,9 @@ func init() {
rootCmd.AddCommand(exportCmd)
}
func resourceToString(data []byte) string {
data = bytes.Replace(data, []byte(" creationTimestamp: null\n"), []byte(""), 1)
data = bytes.Replace(data, []byte("status: {}\n"), []byte(""), 1)
return string(data)
}

120
cmd/gotk/export_alert.go Normal file
View File

@@ -0,0 +1,120 @@
/*
Copyright 2020 The Flux CD contributors.
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var exportAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Export Alert resources in YAML format",
Long: "The export alert command exports one or all Alert resources in YAML format.",
Example: ` # Export all Alert resources
gotk export alert --all > alerts.yaml
# Export a Alert
gotk export alert main > main.yaml
`,
RunE: exportAlertCmdRun,
}
func init() {
exportCmd.AddCommand(exportAlertCmd)
}
func exportAlertCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 {
return fmt.Errorf("name is required")
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
if exportAll {
var list notificationv1.AlertList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no alerts found in %s namespace", namespace)
return nil
}
for _, alert := range list.Items {
if err := exportAlert(alert); err != nil {
return err
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
return exportAlert(alert)
}
return nil
}
func exportAlert(alert notificationv1.Alert) error {
gvk := notificationv1.GroupVersion.WithKind("Alert")
export := notificationv1.Alert{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: alert.Name,
Namespace: alert.Namespace,
Labels: alert.Labels,
Annotations: alert.Annotations,
},
Spec: alert.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

View File

@@ -0,0 +1,120 @@
/*
Copyright 2020 The Flux CD contributors.
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var exportAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Export Provider resources in YAML format",
Long: "The export alert-provider command exports one or all Provider resources in YAML format.",
Example: ` # Export all Provider resources
gotk export alert-provider --all > alert-providers.yaml
# Export a Provider
gotk export alert-provider slack > slack.yaml
`,
RunE: exportAlertProviderCmdRun,
}
func init() {
exportCmd.AddCommand(exportAlertProviderCmd)
}
func exportAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 {
return fmt.Errorf("name is required")
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
if exportAll {
var list notificationv1.ProviderList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no alertproviders found in %s namespace", namespace)
return nil
}
for _, alertProvider := range list.Items {
if err := exportAlertProvider(alertProvider); err != nil {
return err
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil {
return err
}
return exportAlertProvider(alertProvider)
}
return nil
}
func exportAlertProvider(alertProvider notificationv1.Provider) error {
gvk := notificationv1.GroupVersion.WithKind("Provider")
export := notificationv1.Provider{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: alertProvider.Name,
Namespace: alertProvider.Namespace,
Labels: alertProvider.Labels,
Annotations: alertProvider.Annotations,
},
Spec: alertProvider.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

View File

@@ -26,7 +26,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var exportHelmReleaseCmd = &cobra.Command{
@@ -55,7 +56,7 @@ func exportHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -68,7 +69,7 @@ func exportHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}
if len(list.Items) == 0 {
logger.Failuref("no kustomizations found in %s namespace", namespace)
logger.Failuref("no helmrelease found in %s namespace", namespace)
return nil
}
@@ -101,8 +102,10 @@ func exportHelmRelease(helmRelease helmv2.HelmRelease) error {
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: helmRelease.Name,
Namespace: helmRelease.Namespace,
Name: helmRelease.Name,
Namespace: helmRelease.Namespace,
Labels: helmRelease.Labels,
Annotations: helmRelease.Annotations,
},
Spec: helmRelease.Spec,
}
@@ -113,6 +116,6 @@ func exportHelmRelease(helmRelease helmv2.HelmRelease) error {
}
fmt.Println("---")
fmt.Println(string(data))
fmt.Println(resourceToString(data))
return nil
}

View File

@@ -20,12 +20,14 @@ import (
"context"
"fmt"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var exportKsCmd = &cobra.Command{
@@ -54,7 +56,7 @@ func exportKsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -100,8 +102,10 @@ func exportKs(kustomization kustomizev1.Kustomization) error {
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: kustomization.Name,
Namespace: kustomization.Namespace,
Name: kustomization.Name,
Namespace: kustomization.Namespace,
Labels: kustomization.Labels,
Annotations: kustomization.Annotations,
},
Spec: kustomization.Spec,
}
@@ -112,6 +116,6 @@ func exportKs(kustomization kustomizev1.Kustomization) error {
}
fmt.Println("---")
fmt.Println(string(data))
fmt.Println(resourceToString(data))
return nil
}

120
cmd/gotk/export_receiver.go Normal file
View File

@@ -0,0 +1,120 @@
/*
Copyright 2020 The Flux CD contributors.
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var exportReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Export Receiver resources in YAML format",
Long: "The export receiver command exports one or all Receiver resources in YAML format.",
Example: ` # Export all Receiver resources
gotk export receiver --all > receivers.yaml
# Export a Receiver
gotk export receiver main > main.yaml
`,
RunE: exportReceiverCmdRun,
}
func init() {
exportCmd.AddCommand(exportReceiverCmd)
}
func exportReceiverCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 {
return fmt.Errorf("name is required")
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
if exportAll {
var list notificationv1.ReceiverList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no receivers found in %s namespace", namespace)
return nil
}
for _, receiver := range list.Items {
if err := exportReceiver(receiver); err != nil {
return err
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
return exportReceiver(receiver)
}
return nil
}
func exportReceiver(receiver notificationv1.Receiver) error {
gvk := notificationv1.GroupVersion.WithKind("Receiver")
export := notificationv1.Receiver{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: receiver.Name,
Namespace: receiver.Namespace,
Labels: receiver.Labels,
Annotations: receiver.Annotations,
},
Spec: receiver.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

View File

@@ -0,0 +1,167 @@
/*
Copyright 2020 The Flux CD contributors.
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var exportSourceBucketCmd = &cobra.Command{
Use: "bucket [name]",
Short: "Export Bucket sources in YAML format",
Long: "The export source git command exports on or all Bucket sources in YAML format.",
Example: ` # Export all Bucket sources
gotk export source bucket --all > sources.yaml
# Export a Bucket source including the static credentials
gotk export source bucket my-bucket --with-credentials > source.yaml
`,
RunE: exportSourceBucketCmdRun,
}
func init() {
exportSourceCmd.AddCommand(exportSourceBucketCmd)
}
func exportSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 {
return fmt.Errorf("name is required")
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
if exportAll {
var list sourcev1.BucketList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no source found in %s namespace", namespace)
return nil
}
for _, bucket := range list.Items {
if err := exportBucket(bucket); err != nil {
return err
}
if exportSourceWithCred {
if err := exportBucketCredentials(ctx, kubeClient, bucket); err != nil {
return err
}
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var bucket sourcev1.Bucket
err = kubeClient.Get(ctx, namespacedName, &bucket)
if err != nil {
return err
}
if err := exportBucket(bucket); err != nil {
return err
}
if exportSourceWithCred {
return exportBucketCredentials(ctx, kubeClient, bucket)
}
}
return nil
}
func exportBucket(source sourcev1.Bucket) error {
gvk := sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)
export := sourcev1.Bucket{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: source.Name,
Namespace: source.Namespace,
Labels: source.Labels,
Annotations: source.Annotations,
},
Spec: source.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}
func exportBucketCredentials(ctx context.Context, kubeClient client.Client, source sourcev1.Bucket) error {
if source.Spec.SecretRef != nil {
namespacedName := types.NamespacedName{
Namespace: source.Namespace,
Name: source.Spec.SecretRef.Name,
}
var cred corev1.Secret
err := kubeClient.Get(ctx, namespacedName, &cred)
if err != nil {
return fmt.Errorf("failed to retrieve secret %s, error: %w", namespacedName.Name, err)
}
exported := corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: namespacedName.Name,
Namespace: namespacedName.Namespace,
},
Data: cred.Data,
Type: cred.Type,
}
data, err := yaml.Marshal(exported)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
}
return nil
}

View File

@@ -20,13 +20,15 @@ import (
"context"
"fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var exportSourceGitCmd = &cobra.Command{
@@ -54,7 +56,7 @@ func exportSourceGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -110,8 +112,10 @@ func exportGit(source sourcev1.GitRepository) error {
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: source.Name,
Namespace: source.Namespace,
Name: source.Name,
Namespace: source.Namespace,
Labels: source.Labels,
Annotations: source.Annotations,
},
Spec: source.Spec,
}
@@ -122,7 +126,7 @@ func exportGit(source sourcev1.GitRepository) error {
}
fmt.Println("---")
fmt.Println(string(data))
fmt.Println(resourceToString(data))
return nil
}
@@ -157,7 +161,7 @@ func exportGitCredentials(ctx context.Context, kubeClient client.Client, source
}
fmt.Println("---")
fmt.Println(string(data))
fmt.Println(resourceToString(data))
}
return nil
}

View File

@@ -20,13 +20,15 @@ import (
"context"
"fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var exportSourceHelmCmd = &cobra.Command{
@@ -54,7 +56,7 @@ func exportSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -102,6 +104,32 @@ func exportSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
func exportHelmRepository(source sourcev1.HelmRepository) error {
gvk := sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)
export := sourcev1.HelmRepository{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: source.Name,
Namespace: source.Namespace,
Labels: source.Labels,
Annotations: source.Annotations,
},
Spec: source.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}
func exportHelmCredentials(ctx context.Context, kubeClient client.Client, source sourcev1.HelmRepository) error {
if source.Spec.SecretRef != nil {
namespacedName := types.NamespacedName{
@@ -133,7 +161,7 @@ func exportHelmCredentials(ctx context.Context, kubeClient client.Client, source
}
fmt.Println("---")
fmt.Println(string(data))
fmt.Println(resourceToString(data))
}
return nil
}

View File

@@ -26,6 +26,10 @@ var getCmd = &cobra.Command{
Long: "The get sub-commands print the statuses of sources and resources.",
}
var allNamespaces bool
func init() {
getCmd.PersistentFlags().BoolVarP(&allNamespaces, "all-namespaces", "A", false,
"list the requested object(s) across all namespaces")
rootCmd.AddCommand(getCmd)
}

103
cmd/gotk/get_alert.go Normal file
View File

@@ -0,0 +1,103 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"os"
"strconv"
"strings"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
)
var getAlertCmd = &cobra.Command{
Use: "alerts",
Short: "Get Alert statuses",
Long: "The get alert command prints the statuses of the resources.",
Example: ` # List all Alerts and their status
gotk get alerts
`,
RunE: getAlertCmdRun,
}
func init() {
getCmd.AddCommand(getAlertCmd)
}
func getAlertCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list notificationv1.AlertList
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no alerts found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, alert := range list.Items {
row := []string{}
if c := meta.GetCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
alert.GetName(),
//alert.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
string(c.Status),
c.Message,
}
} else {
row = []string{
alert.GetName(),
//alert.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{alert.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

View File

@@ -0,0 +1,97 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"os"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
)
var getAlertProviderCmd = &cobra.Command{
Use: "alert-providers",
Short: "Get Provider statuses",
Long: "The get alert-provider command prints the statuses of the resources.",
Example: ` # List all Providers and their status
gotk get alert-providers
`,
RunE: getAlertProviderCmdRun,
}
func init() {
getCmd.AddCommand(getAlertProviderCmd)
}
func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list notificationv1.ProviderList
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no providers found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, provider := range list.Items {
row := []string{}
if c := meta.GetCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
provider.GetName(),
string(c.Status),
c.Message,
}
} else {
row = []string{
provider.GetName(),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{provider.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

View File

@@ -18,12 +18,18 @@ package main
import (
"context"
"os"
"strconv"
"strings"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
)
var getHelmReleaseCmd = &cobra.Command{
@@ -45,13 +51,17 @@ func getHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list helmv2.HelmReleaseList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
@@ -61,30 +71,35 @@ func getHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
header := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, helmRelease := range list.Items {
if helmRelease.Spec.Suspend {
logger.Successf("%s is suspended", helmRelease.GetName())
continue
}
isInitialized := false
for _, condition := range helmRelease.Status.Conditions {
if condition.Type == helmv2.ReadyCondition {
if condition.Status != corev1.ConditionFalse {
if helmRelease.Status.LastAppliedRevision != "" {
logger.Successf("%s last applied revision %s", helmRelease.GetName(), helmRelease.Status.LastAppliedRevision)
} else {
logger.Successf("%s reconciling", helmRelease.GetName())
}
} else {
logger.Failuref("%s %s", helmRelease.GetName(), condition.Message)
}
isInitialized = true
break
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 !isInitialized {
logger.Failuref("%s is not ready", helmRelease.GetName())
if allNamespaces {
row = append([]string{helmRelease.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

View File

@@ -18,8 +18,14 @@ package main
import (
"context"
"os"
"strconv"
"strings"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -44,13 +50,17 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list kustomizev1.KustomizationList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
@@ -60,30 +70,35 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
header := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, kustomization := range list.Items {
if kustomization.Spec.Suspend {
logger.Successf("%s is suspended", kustomization.GetName())
continue
}
isInitialized := false
for _, condition := range kustomization.Status.Conditions {
if condition.Type == kustomizev1.ReadyCondition {
if condition.Status != corev1.ConditionFalse {
if kustomization.Status.LastAppliedRevision != "" {
logger.Successf("%s last applied revision %s", kustomization.GetName(), kustomization.Status.LastAppliedRevision)
} else {
logger.Successf("%s reconciling", kustomization.GetName())
}
} else {
logger.Failuref("%s %s", kustomization.GetName(), condition.Message)
}
isInitialized = true
break
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 !isInitialized {
logger.Failuref("%s is not ready", kustomization.GetName())
if allNamespaces {
row = append([]string{kustomization.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

98
cmd/gotk/get_receiver.go Normal file
View File

@@ -0,0 +1,98 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"os"
"strconv"
"strings"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
)
var getReceiverCmd = &cobra.Command{
Use: "receivers",
Short: "Get Receiver statuses",
Long: "The get receiver command prints the statuses of the resources.",
Example: ` # List all Receiver and their status
gotk get receivers
`,
RunE: getReceiverCmdRun,
}
func init() {
getCmd.AddCommand(getReceiverCmd)
}
func getReceiverCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list notificationv1.ReceiverList
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no receivers found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, receiver := range list.Items {
row := []string{}
if c := meta.GetCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
receiver.GetName(),
strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
string(c.Status),
c.Message,
}
} else {
row = []string{
receiver.GetName(),
strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

View File

@@ -0,0 +1,103 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"os"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var getSourceBucketCmd = &cobra.Command{
Use: "bucket",
Short: "Get Bucket source statuses",
Long: "The get sources bucket command prints the status of the Bucket sources.",
Example: ` # List all Buckets and their status
gotk get sources bucket
`,
RunE: getSourceBucketCmdRun,
}
func init() {
getSourceCmd.AddCommand(getSourceBucketCmd)
}
func getSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list sourcev1.BucketList
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
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

@@ -18,8 +18,12 @@ package main
import (
"context"
"os"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -43,38 +47,57 @@ func getSourceGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list sourcev1.GitRepositoryList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no sources found in %s namespace", namespace)
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 {
isInitialized := false
for _, condition := range source.Status.Conditions {
if condition.Type == sourcev1.ReadyCondition {
if condition.Status != corev1.ConditionFalse {
logger.Successf("%s last fetched revision: %s", source.GetName(), source.Status.Artifact.Revision)
} else {
logger.Failuref("%s %s", source.GetName(), condition.Message)
}
isInitialized = true
break
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 !isInitialized {
logger.Failuref("%s is not ready", source.GetName())
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

View File

@@ -18,8 +18,12 @@ package main
import (
"context"
"os"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -43,38 +47,57 @@ func getSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list sourcev1.HelmRepositoryList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no sources found in %s namespace", namespace)
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 {
isInitialized := false
for _, condition := range source.Status.Conditions {
if condition.Type == sourcev1.ReadyCondition {
if condition.Status != corev1.ConditionFalse {
logger.Successf("%s last fetched revision: %s", source.GetName(), source.Status.Artifact.Revision)
} else {
logger.Failuref("%s %s", source.GetName(), condition.Message)
}
isInitialized = true
break
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 !isInitialized {
logger.Failuref("%s is not ready", source.GetName())
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

View File

@@ -20,18 +20,15 @@ import (
"context"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/krusty"
"github.com/fluxcd/pkg/untar"
"github.com/fluxcd/toolkit/internal/flags"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/fluxcd/toolkit/pkg/install"
)
var installCmd = &cobra.Command{
@@ -39,8 +36,8 @@ var installCmd = &cobra.Command{
Short: "Install the toolkit components",
Long: `The install command deploys the toolkit components in the specified namespace.
If a previous version is installed, then an in-place upgrade will be performed.`,
Example: ` # Install the latest version in the gitops-systems namespace
gotk install --version=latest --namespace=gitops-systems
Example: ` # Install the latest version in the gotk-system namespace
gotk install --version=latest --namespace=gotk-system
# Dry-run install for a specific version and a series of components
gotk install --dry-run --version=v0.0.7 --components="source-controller,kustomize-controller"
@@ -49,20 +46,23 @@ If a previous version is installed, then an in-place upgrade will be performed.`
gotk install --dry-run --verbose
# Write install manifests to file
gotk install --export > gitops-system.yaml
gotk install --export > gotk-system.yaml
`,
RunE: installCmdRun,
}
var (
installExport bool
installDryRun bool
installManifestsPath string
installVersion string
installComponents []string
installRegistry string
installImagePullSecret string
installArch string
installExport bool
installDryRun bool
installManifestsPath string
installVersion string
installComponents []string
installRegistry string
installImagePullSecret string
installArch flags.Arch = "amd64"
installWatchAllNamespaces bool
installNetworkPolicy bool
installLogLevel flags.LogLevel = "info"
)
func init() {
@@ -74,33 +74,25 @@ func init() {
"toolkit version")
installCmd.Flags().StringSliceVar(&installComponents, "components", defaultComponents,
"list of components, accepts comma-separated values")
installCmd.Flags().StringVar(&installManifestsPath, "manifests", "",
"path to the manifest directory, dev only")
installCmd.Flags().StringVar(&installManifestsPath, "manifests", "", "path to the manifest directory")
installCmd.Flags().MarkHidden("manifests")
installCmd.Flags().StringVar(&installRegistry, "registry", "ghcr.io/fluxcd",
"container registry where the toolkit images are published")
installCmd.Flags().StringVar(&installImagePullSecret, "image-pull-secret", "",
"Kubernetes secret name used for pulling the toolkit images from a private registry")
installCmd.Flags().StringVar(&installArch, "arch", "amd64",
"arch can be amd64 or arm64")
installCmd.Flags().Var(&installArch, "arch", installArch.Description())
installCmd.Flags().BoolVar(&installWatchAllNamespaces, "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")
installCmd.Flags().Var(&installLogLevel, "log-level", installLogLevel.Description())
installCmd.Flags().BoolVar(&installNetworkPolicy, "network-policy", true,
"deny ingress access to the toolkit controllers from other namespaces using network policies")
rootCmd.AddCommand(installCmd)
}
func installCmdRun(cmd *cobra.Command, args []string) error {
if !utils.containsItemString(supportedArch, installArch) {
return fmt.Errorf("arch %s is not supported, can be %v", installArch, supportedArch)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
var kustomizePath string
if installVersion == "" && !strings.HasPrefix(installManifestsPath, "github.com/") {
if _, err := os.Stat(installManifestsPath); err != nil {
return fmt.Errorf("manifests not found: %w", err)
}
kustomizePath = installManifestsPath
}
tmpDir, err := ioutil.TempDir("", namespace)
if err != nil {
return err
@@ -110,48 +102,62 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
if !installExport {
logger.Generatef("generating manifests")
}
if kustomizePath == "" {
err = genInstallManifests(installVersion, namespace, installComponents, installRegistry, installImagePullSecret, installArch, tmpDir)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
kustomizePath = tmpDir
opts := install.Options{
BaseURL: installManifestsPath,
Version: installVersion,
Namespace: namespace,
Components: installComponents,
Registry: installRegistry,
ImagePullSecret: installImagePullSecret,
Arch: installArch.String(),
WatchAllNamespaces: installWatchAllNamespaces,
NetworkPolicy: installNetworkPolicy,
LogLevel: installLogLevel.String(),
NotificationController: defaultNotification,
ManifestsFile: fmt.Sprintf("%s.yaml", namespace),
Timeout: timeout,
}
if installManifestsPath == "" {
opts.BaseURL = install.MakeDefaultOptions().BaseURL
}
output, err := install.Generate(opts)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
manifest := path.Join(tmpDir, fmt.Sprintf("%s.yaml", namespace))
if err := buildKustomization(kustomizePath, manifest); err != nil {
if err := ioutil.WriteFile(manifest, output, os.ModePerm); err != nil {
return fmt.Errorf("install failed: %w", err)
}
command := fmt.Sprintf("cat %s", manifest)
if yaml, err := utils.execCommand(ctx, ModeCapture, command); err != nil {
return fmt.Errorf("install failed: %w", err)
} else {
if verbose {
fmt.Print(yaml)
} else if installExport {
fmt.Println("---")
fmt.Println("# GitOps Toolkit revision", installVersion, time.Now().Format(time.RFC3339))
fmt.Println("# Components:", strings.Join(installComponents, ","))
fmt.Print(yaml)
fmt.Println("---")
return nil
}
}
logger.Successf("manifests build completed")
logger.Actionf("installing components in %s namespace", namespace)
applyOutput := ModeStderrOS
yaml := string(output)
if verbose {
applyOutput = ModeOS
fmt.Print(yaml)
} else if installExport {
fmt.Println("---")
fmt.Println("# GitOps Toolkit revision", installVersion)
fmt.Println("# Components:", strings.Join(installComponents, ","))
fmt.Print(yaml)
fmt.Println("---")
return nil
}
dryRun := ""
logger.Successf("manifests build completed")
logger.Actionf("installing components in %s namespace", namespace)
applyOutput := utils.ModeStderrOS
if verbose {
applyOutput = utils.ModeOS
}
kubectlArgs := []string{"apply", "-f", manifest}
if installDryRun {
dryRun = "--dry-run=client"
applyOutput = ModeOS
args = append(args, "--dry-run=client")
applyOutput = utils.ModeOS
}
command = fmt.Sprintf("cat %s | kubectl apply -f- %s", manifest, dryRun)
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
}
@@ -164,9 +170,8 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("verifying installation")
for _, deployment := range installComponents {
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
namespace, deployment, timeout.String())
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
} else {
logger.Successf("%s ready", deployment)
@@ -176,220 +181,3 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("install finished")
return nil
}
var namespaceTmpl = `---
apiVersion: v1
kind: Namespace
metadata:
name: {{.Namespace}}
`
var labelsTmpl = `---
apiVersion: builtin
kind: LabelTransformer
metadata:
name: labels
labels:
app.kubernetes.io/instance: {{.Namespace}}
app.kubernetes.io/version: "{{.Version}}"
fieldSpecs:
- path: metadata/labels
create: true
`
var kustomizationTmpl = `---
{{- $eventsAddr := .EventsAddr }}
{{- $registry := .Registry }}
{{- $arch := .Arch }}
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: {{.Namespace}}
transformers:
- labels.yaml
resources:
- namespace.yaml
- policies.yaml
- roles
{{- range .Components }}
- {{.}}.yaml
{{- end }}
patches:
- path: node-selector.yaml
target:
kind: Deployment
patchesJson6902:
{{- range $i, $component := .Components }}
{{- if ne $component "notification-controller" }}
- target:
group: apps
version: v1
kind: Deployment
name: {{$component}}
patch: |-
- op: replace
path: /spec/template/spec/containers/0/args/0
value: --events-addr={{$eventsAddr}}
{{- end }}
{{- end }}
{{- if $registry }}
images:
{{- range $i, $component := .Components }}
- name: fluxcd/{{$component}}
{{- if eq $arch "amd64" }}
newName: {{$registry}}/{{$component}}
{{- else }}
newName: {{$registry}}/{{$component}}-{{$arch}}
{{- end }}
{{- end }}
{{- end }}
`
var kustomizationRolesTmpl = `---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- rbac.yaml
nameSuffix: -{{.Namespace}}
`
var nodeSelectorTmpl = `---
apiVersion: apps/v1
kind: Deployment
metadata:
name: all
spec:
template:
spec:
nodeSelector:
kubernetes.io/arch: {{.Arch}}
kubernetes.io/os: linux
{{- if .ImagePullSecret }}
imagePullSecrets:
- name: {{.ImagePullSecret}}
{{- end }}
`
func downloadManifests(version string, tmpDir string) error {
ghURL := "https://github.com/fluxcd/toolkit/releases/latest/download/manifests.tar.gz"
if strings.HasPrefix(version, "v") {
ghURL = fmt.Sprintf("https://github.com/fluxcd/toolkit/releases/download/%s/manifests.tar.gz", version)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
req, err := http.NewRequest("GET", ghURL, nil)
if err != nil {
return fmt.Errorf("failed to create HTTP request for %s, error: %w", ghURL, err)
}
// download
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return fmt.Errorf("failed to download artifact from %s, error: %w", ghURL, err)
}
defer resp.Body.Close()
// check response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("faild to download artifact from %s, status: %s", ghURL, resp.Status)
}
// extract
if _, err = untar.Untar(resp.Body, tmpDir); err != nil {
return fmt.Errorf("faild to untar manifests from %s, error: %w", ghURL, err)
}
return nil
}
func genInstallManifests(version string, namespace string, components []string, registry, imagePullSecret, arch, tmpDir string) error {
eventsAddr := ""
if utils.containsItemString(components, defaultNotification) {
eventsAddr = fmt.Sprintf("http://%s/", defaultNotification)
}
model := struct {
Version string
Namespace string
Components []string
EventsAddr string
Registry string
ImagePullSecret string
Arch string
}{
Version: version,
Namespace: namespace,
Components: components,
EventsAddr: eventsAddr,
Registry: registry,
ImagePullSecret: imagePullSecret,
Arch: arch,
}
if err := downloadManifests(version, tmpDir); err != nil {
return err
}
if err := utils.execTemplate(model, namespaceTmpl, path.Join(tmpDir, "namespace.yaml")); err != nil {
return fmt.Errorf("generate namespace failed: %w", err)
}
if err := utils.execTemplate(model, labelsTmpl, path.Join(tmpDir, "labels.yaml")); err != nil {
return fmt.Errorf("generate labels failed: %w", err)
}
if err := utils.execTemplate(model, nodeSelectorTmpl, path.Join(tmpDir, "node-selector.yaml")); err != nil {
return fmt.Errorf("generate node selector failed: %w", err)
}
if err := utils.execTemplate(model, kustomizationTmpl, path.Join(tmpDir, "kustomization.yaml")); err != nil {
return fmt.Errorf("generate kustomization failed: %w", err)
}
if err := os.MkdirAll(path.Join(tmpDir, "roles"), os.ModePerm); err != nil {
return fmt.Errorf("generate roles failed: %w", err)
}
if err := utils.execTemplate(model, kustomizationRolesTmpl, path.Join(tmpDir, "roles/kustomization.yaml")); err != nil {
return fmt.Errorf("generate roles failed: %w", err)
}
if err := utils.copyFile(filepath.Join(tmpDir, "rbac.yaml"), filepath.Join(tmpDir, "roles/rbac.yaml")); err != nil {
return fmt.Errorf("generate rbac failed: %w", err)
}
return nil
}
func buildKustomization(base, manifests string) error {
kfile := filepath.Join(base, "kustomization.yaml")
fs := filesys.MakeFsOnDisk()
if !fs.Exists(kfile) {
return fmt.Errorf("%s not found", kfile)
}
opt := krusty.MakeDefaultOptions()
k := krusty.MakeKustomizer(fs, opt)
m, err := k.Run(base)
if err != nil {
return err
}
resources, err := m.AsYaml()
if err != nil {
return err
}
if err := fs.WriteFile(manifests, resources); err != nil {
return err
}
return nil
}

View File

@@ -54,7 +54,7 @@ var rootCmd = &cobra.Command{
gotk get sources git
# Trigger a GitRepository source reconciliation
gotk reconcile source git gitops-system
gotk reconcile source git gotk-system
# Export GitRepository sources in YAML format
gotk export source git --all > sources.yaml
@@ -98,7 +98,6 @@ var (
namespace string
timeout time.Duration
verbose bool
utils Utils
pollInterval = 2 * time.Second
logger gotklog.Logger = printLogger{}
)
@@ -106,18 +105,14 @@ var (
var (
defaultComponents = []string{"source-controller", "kustomize-controller", "helm-controller", "notification-controller"}
defaultVersion = "latest"
defaultNamespace = "gitops-system"
defaultNamespace = "gotk-system"
defaultNotification = "notification-controller"
supportedArch = []string{"arm64", "amd64"}
)
func init() {
rootCmd.PersistentFlags().StringVar(&namespace, "namespace", defaultNamespace,
"the namespace scope for this operation")
rootCmd.PersistentFlags().DurationVarP(&timeout, "timeout", "", 5*time.Minute,
"timeout for this operation")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "", false,
"print generated objects")
rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", defaultNamespace, "the namespace scope for this operation")
rootCmd.PersistentFlags().DurationVar(&timeout, "timeout", 5*time.Minute, "timeout for this operation")
rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "print generated objects")
}
func main() {

View File

@@ -0,0 +1,93 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var reconcileAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Reconcile an Alert",
Long: `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing alert
gotk reconcile alert main
`,
RunE: reconcileAlertCmdRun,
}
func init() {
reconcileCmd.AddCommand(reconcileAlertCmd)
}
func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
logger.Actionf("annotating Alert %s in %s namespace", name, namespace)
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
if alert.Annotations == nil {
alert.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
alert.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert annotated")
logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert reconciliation completed")
return nil
}

View File

@@ -0,0 +1,93 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var reconcileAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Reconcile a Provider",
Long: `The reconcile alert-provider command triggers a reconciliation of a Provider resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing provider
gotk reconcile alert-provider slack
`,
RunE: reconcileAlertProviderCmdRun,
}
func init() {
reconcileCmd.AddCommand(reconcileAlertProviderCmd)
}
func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Provider name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
logger.Actionf("annotating Provider %s in %s namespace", name, namespace)
var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil {
return err
}
if alertProvider.Annotations == nil {
alertProvider.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
alertProvider.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &alertProvider); err != nil {
return err
}
logger.Successf("Provider annotated")
logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertProviderReady(ctx, kubeClient, namespacedName, &alertProvider)); err != nil {
return err
}
logger.Successf("Provider reconciliation completed")
return nil
}

View File

@@ -21,14 +21,18 @@ import (
"fmt"
"time"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/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"
helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileHrCmd = &cobra.Command{
@@ -65,7 +69,7 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -84,71 +88,71 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
if syncHrWithSource {
switch helmRelease.Spec.Chart.Spec.SourceRef.Kind {
case sourcev1.HelmRepositoryKind:
err = syncSourceHelmCmdRun(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
err = reconcileSourceHelmCmdRun(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
case sourcev1.GitRepositoryKind:
err = syncSourceGitCmdRun(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
err = reconcileSourceGitCmdRun(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
case sourcev1.BucketKind:
err = reconcileSourceBucketCmdRun(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
}
if err != nil {
return err
}
} else {
logger.Actionf("annotating HelmRelease %s in %s namespace", name, namespace)
if helmRelease.Annotations == nil {
helmRelease.Annotations = map[string]string{
helmv2.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
helmRelease.Annotations[helmv2.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &helmRelease); err != nil {
return err
}
logger.Successf("HelmRelease annotated")
}
lastHandledReconcileAt := helmRelease.Status.LastHandledReconcileAt
logger.Actionf("annotating HelmRelease %s in %s namespace", name, namespace)
if err := requestHelmReleaseReconciliation(ctx, kubeClient, namespacedName, &helmRelease); err != nil {
return err
}
logger.Successf("HelmRelease annotated")
logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmReleaseReady(ctx, kubeClient, name, namespace)); err != nil {
helmReleaseReconciliationHandled(ctx, kubeClient, namespacedName, &helmRelease, lastHandledReconcileAt),
); err != nil {
return err
}
logger.Successf("HelmRelease reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
if err != nil {
return err
}
if helmRelease.Status.LastAppliedRevision != "" {
logger.Successf("reconciled revision %s", helmRelease.Status.LastAppliedRevision)
} else {
return fmt.Errorf("HelmRelease reconciliation failed")
if c := meta.GetCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionFalse:
return fmt.Errorf("HelmRelease reconciliation failed: %s", c.Message)
default:
logger.Successf("reconciled revision %s", helmRelease.Status.LastAppliedRevision)
}
}
return nil
}
func isHelmReleaseReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func helmReleaseReconciliationHandled(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease, lastHandledReconcileAt string) wait.ConditionFunc {
return func() (bool, error) {
var helmRelease helmv2.HelmRelease
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmRelease)
err := kubeClient.Get(ctx, namespacedName, helmRelease)
if err != nil {
return false, err
}
for _, condition := range helmRelease.Status.Conditions {
if condition.Type == helmv2.ReadyCondition {
if condition.Status == corev1.ConditionTrue {
return true, nil
} else if condition.Status == corev1.ConditionFalse && helmRelease.Status.LastAttemptedRevision != "" {
return false, fmt.Errorf(condition.Message)
}
}
}
return false, nil
return helmRelease.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
}
}
func requestHelmReleaseReconciliation(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if err := kubeClient.Get(ctx, namespacedName, helmRelease); err != nil {
return err
}
if helmRelease.Annotations == nil {
helmRelease.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
helmRelease.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
return kubeClient.Update(ctx, helmRelease)
})
}

View File

@@ -21,10 +21,18 @@ import (
"fmt"
"time"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileKsCmd = &cobra.Command{
@@ -47,21 +55,21 @@ var (
)
func init() {
reconcileKsCmd.Flags().BoolVar(&syncKsWithSource, "with-source", false, "reconcile kustomization source")
reconcileKsCmd.Flags().BoolVar(&syncKsWithSource, "with-source", false, "reconcile Kustomization source")
reconcileCmd.AddCommand(reconcileKsCmd)
}
func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("kustomization name is required")
return fmt.Errorf("Kustomization name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -70,7 +78,6 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
Namespace: namespace,
Name: name,
}
var kustomization kustomizev1.Kustomization
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
@@ -78,42 +85,68 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
}
if syncKsWithSource {
err := syncSourceGitCmdRun(nil, []string{kustomization.Spec.SourceRef.Name})
switch kustomization.Spec.SourceRef.Kind {
case sourcev1.GitRepositoryKind:
err = reconcileSourceGitCmdRun(nil, []string{kustomization.Spec.SourceRef.Name})
case sourcev1.BucketKind:
err = reconcileSourceBucketCmdRun(nil, []string{kustomization.Spec.SourceRef.Name})
}
if err != nil {
return err
}
} else {
logger.Actionf("annotating kustomization %s in %s namespace", name, namespace)
if kustomization.Annotations == nil {
kustomization.Annotations = map[string]string{
kustomizev1.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
kustomization.Annotations[kustomizev1.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &kustomization); err != nil {
return err
}
logger.Successf("kustomization annotated")
}
logger.Waitingf("waiting for kustomization reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil {
lastHandledReconcileAt := kustomization.Status.LastHandledReconcileAt
logger.Actionf("annotating Kustomization %s in %s namespace", name, namespace)
if err := requestKustomizeReconciliation(ctx, kubeClient, namespacedName, &kustomization); err != nil {
return err
}
logger.Successf("Kustomization annotated")
logger.Successf("kustomization reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate(
pollInterval, timeout,
kustomizeReconciliationHandled(ctx, kubeClient, namespacedName, &kustomization, lastHandledReconcileAt),
); err != nil {
return err
}
logger.Successf("Kustomization reconciliation completed")
if kustomization.Status.LastAppliedRevision != "" {
logger.Successf("reconciled revision %s", kustomization.Status.LastAppliedRevision)
} else {
return fmt.Errorf("kustomization sync failed")
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionFalse:
return fmt.Errorf("Kustomization reconciliation failed")
default:
logger.Successf("reconciled revision %s", kustomization.Status.LastAppliedRevision)
}
}
return nil
}
func kustomizeReconciliationHandled(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization, lastHandledReconcileAt string) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, kustomization)
if err != nil {
return false, err
}
return kustomization.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
}
}
func requestKustomizeReconciliation(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if err := kubeClient.Get(ctx, namespacedName, kustomization); err != nil {
return err
}
if kustomization.Annotations == nil {
kustomization.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
kustomization.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
return kubeClient.Update(ctx, kustomization)
})
}

View File

@@ -0,0 +1,95 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var reconcileReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Reconcile a Receiver",
Long: `The reconcile receiver command triggers a reconciliation of a Receiver resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing receiver
gotk reconcile receiver main
`,
RunE: reconcileReceiverCmdRun,
}
func init() {
reconcileCmd.AddCommand(reconcileReceiverCmd)
}
func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
logger.Actionf("annotating Receiver %s in %s namespace", name, namespace)
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
if receiver.Annotations == nil {
receiver.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
receiver.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver annotated")
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver reconciliation completed")
return nil
}

View File

@@ -0,0 +1,125 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileSourceBucketCmd = &cobra.Command{
Use: "bucket [name]",
Short: "Reconcile a Bucket source",
Long: `The reconcile source command triggers a reconciliation of a Bucket resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing source
gotk reconcile source bucket podinfo
`,
RunE: reconcileSourceBucketCmdRun,
}
func init() {
reconcileSourceCmd.AddCommand(reconcileSourceBucketCmd)
}
func reconcileSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
logger.Actionf("annotating Bucket source %s in %s namespace", name, namespace)
var bucket sourcev1.Bucket
err = kubeClient.Get(ctx, namespacedName, &bucket)
if err != nil {
return err
}
if bucket.Annotations == nil {
bucket.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
bucket.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &bucket); err != nil {
return err
}
logger.Successf("Bucket source annotated")
logger.Waitingf("waiting for Bucket source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isBucketReady(ctx, kubeClient, namespacedName, &bucket)); err != nil {
return err
}
logger.Successf("Bucket source reconciliation completed")
if bucket.Status.Artifact == nil {
return fmt.Errorf("Bucket source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision %s", bucket.Status.Artifact.Revision)
return nil
}
func isBucketReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, bucket)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if bucket.Generation != bucket.Status.ObservedGeneration {
return false, nil
}
if c := meta.GetCondition(bucket.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -19,11 +19,16 @@ package main
import (
"context"
"fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"time"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileSourceGitCmd = &cobra.Command{
@@ -33,14 +38,14 @@ var reconcileSourceGitCmd = &cobra.Command{
Example: ` # Trigger a git pull for an existing source
gotk reconcile source git podinfo
`,
RunE: syncSourceGitCmdRun,
RunE: reconcileSourceGitCmdRun,
}
func init() {
reconcileSourceCmd.AddCommand(reconcileSourceGitCmd)
}
func syncSourceGitCmdRun(cmd *cobra.Command, args []string) error {
func reconcileSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
}
@@ -49,7 +54,7 @@ func syncSourceGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -59,7 +64,7 @@ func syncSourceGitCmdRun(cmd *cobra.Command, args []string) error {
Name: name,
}
logger.Actionf("annotating source %s in %s namespace", name, namespace)
logger.Actionf("annotating GitRepository source %s in %s namespace", name, namespace)
var gitRepository sourcev1.GitRepository
err = kubeClient.Get(ctx, namespacedName, &gitRepository)
if err != nil {
@@ -68,33 +73,26 @@ func syncSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if gitRepository.Annotations == nil {
gitRepository.Annotations = map[string]string{
sourcev1.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
gitRepository.Annotations[sourcev1.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
gitRepository.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &gitRepository); err != nil {
return err
}
logger.Successf("source annotated")
logger.Successf("GitRepository source annotated")
logger.Waitingf("waiting for reconciliation")
logger.Waitingf("waiting for GitRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil {
return err
}
logger.Successf("GitRepository source reconciliation completed")
logger.Successf("git reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &gitRepository)
if err != nil {
return err
}
if gitRepository.Status.Artifact != nil {
logger.Successf("fetched revision %s", gitRepository.Status.Artifact.Revision)
} else {
return fmt.Errorf("git reconciliation failed, artifact not found")
if gitRepository.Status.Artifact == nil {
return fmt.Errorf("GitRepository source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision %s", gitRepository.Status.Artifact.Revision)
return nil
}

View File

@@ -21,13 +21,16 @@ import (
"fmt"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileSourceHelmCmd = &cobra.Command{
@@ -37,23 +40,23 @@ var reconcileSourceHelmCmd = &cobra.Command{
Example: ` # Trigger a reconciliation for an existing source
gotk reconcile source helm podinfo
`,
RunE: syncSourceHelmCmdRun,
RunE: reconcileSourceHelmCmdRun,
}
func init() {
reconcileSourceCmd.AddCommand(reconcileSourceHelmCmd)
}
func syncSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
return fmt.Errorf("HelmRepository source name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -63,7 +66,7 @@ func syncSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
Name: name,
}
logger.Actionf("annotating source %s in %s namespace", name, namespace)
logger.Actionf("annotating HelmRepository source %s in %s namespace", name, namespace)
var helmRepository sourcev1.HelmRepository
err = kubeClient.Get(ctx, namespacedName, &helmRepository)
if err != nil {
@@ -72,57 +75,49 @@ func syncSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if helmRepository.Annotations == nil {
helmRepository.Annotations = map[string]string{
sourcev1.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
helmRepository.Annotations[sourcev1.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
helmRepository.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &helmRepository); err != nil {
return err
}
logger.Successf("source annotated")
logger.Successf("HelmRepository source annotated")
logger.Waitingf("waiting for reconciliation")
logger.Waitingf("waiting for HelmRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
isHelmRepositoryReady(ctx, kubeClient, namespacedName, &helmRepository)); err != nil {
return err
}
logger.Successf("HelmRepository source reconciliation completed")
logger.Successf("helm reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &helmRepository)
if err != nil {
return err
}
if helmRepository.Status.Artifact != nil {
logger.Successf("fetched revision %s", helmRepository.Status.Artifact.Revision)
} else {
return fmt.Errorf("helm reconciliation failed, artifact not found")
if helmRepository.Status.Artifact == nil {
return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision %s", helmRepository.Status.Artifact.Revision)
return nil
}
func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRepository *sourcev1.HelmRepository) wait.ConditionFunc {
return func() (bool, error) {
var helmRepository sourcev1.HelmRepository
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmRepository)
err := kubeClient.Get(ctx, namespacedName, helmRepository)
if err != nil {
return false, err
}
for _, condition := range helmRepository.Status.Conditions {
if condition.Type == sourcev1.ReadyCondition {
if condition.Status == corev1.ConditionTrue {
return true, nil
} else if condition.Status == corev1.ConditionFalse {
return false, fmt.Errorf(condition.Message)
}
// Confirm the state we are observing is for the current generation
if helmRepository.Generation != helmRepository.Status.ObservedGeneration {
return false, nil
}
if c := meta.GetCondition(helmRepository.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil

111
cmd/gotk/resume_alert.go Normal file
View File

@@ -0,0 +1,111 @@
/*
Copyright 2020 The Flux CD contributors.
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/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var resumeAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Resume a suspended Alert",
Long: `The resume command marks a previously suspended Alert resource for reconciliation and waits for it to
finish the apply.`,
Example: ` # Resume reconciliation for an existing Alert
gotk resume alert main
`,
RunE: resumeAlertCmdRun,
}
func init() {
resumeCmd.AddCommand(resumeAlertCmd)
}
func resumeAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
logger.Actionf("resuming Alert %s in %s namespace", name, namespace)
alert.Spec.Suspend = false
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert resumed")
logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertResumed(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert reconciliation completed")
return nil
}
func isAlertResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, alert *notificationv1.Alert) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, alert)
if err != nil {
return false, err
}
if c := meta.GetCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
if c.Reason == meta.SuspendedReason {
return false, nil
}
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -20,13 +20,16 @@ import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
)
var resumeHrCmd = &cobra.Command{
@@ -54,7 +57,7 @@ func resumeHrCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -78,50 +81,37 @@ func resumeHrCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmReleaseResumed(ctx, kubeClient, name, namespace)); err != nil {
isHelmReleaseResumed(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
return err
}
logger.Successf("HelmRelease reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
if err != nil {
return err
}
if helmRelease.Status.LastAppliedRevision != "" {
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
} else {
return fmt.Errorf("HelmRelease reconciliation failed")
}
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
return nil
}
func isHelmReleaseResumed(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isHelmReleaseResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionFunc {
return func() (bool, error) {
var helmRelease helmv2.HelmRelease
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmRelease)
err := kubeClient.Get(ctx, namespacedName, helmRelease)
if err != nil {
return false, err
}
for _, condition := range helmRelease.Status.Conditions {
if condition.Type == helmv2.ReadyCondition {
if condition.Status == corev1.ConditionTrue {
return true, nil
} else if condition.Status == corev1.ConditionFalse {
if condition.Reason == helmv2.SuspendedReason {
return false, nil
}
// Confirm the state we are observing is for the current generation
if helmRelease.Generation != helmRelease.Status.ObservedGeneration {
return false, err
}
return false, fmt.Errorf(condition.Message)
if c := meta.GetCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
if c.Reason == meta.SuspendedReason {
return false, nil
}
return false, fmt.Errorf(c.Message)
}
}
return false, nil

View File

@@ -20,8 +20,10 @@ import (
"context"
"fmt"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
@@ -47,14 +49,14 @@ func init() {
func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("kustomization name is required")
return fmt.Errorf("Kustomization name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
@@ -69,59 +71,46 @@ func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
return err
}
logger.Actionf("resuming kustomization %s in %s namespace", name, namespace)
logger.Actionf("resuming Kustomization %s in %s namespace", name, namespace)
kustomization.Spec.Suspend = false
if err := kubeClient.Update(ctx, &kustomization); err != nil {
return err
}
logger.Successf("kustomization resumed")
logger.Successf("Kustomization resumed")
logger.Waitingf("waiting for kustomization sync")
logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationResumed(ctx, kubeClient, name, namespace)); err != nil {
isKustomizationResumed(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
return err
}
logger.Successf("Kustomization reconciliation completed")
logger.Successf("kustomization sync completed")
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
return err
}
if kustomization.Status.LastAppliedRevision != "" {
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
} else {
return fmt.Errorf("kustomization sync failed")
}
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
return nil
}
func isKustomizationResumed(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isKustomizationResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionFunc {
return func() (bool, error) {
var kustomization kustomizev1.Kustomization
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &kustomization)
err := kubeClient.Get(ctx, namespacedName, kustomization)
if err != nil {
return false, err
}
for _, condition := range kustomization.Status.Conditions {
if condition.Type == sourcev1.ReadyCondition {
if condition.Status == corev1.ConditionTrue {
return true, nil
} else if condition.Status == corev1.ConditionFalse {
if condition.Reason == kustomizev1.SuspendedReason {
return false, nil
}
// Confirm the state we are observing is for the current generation
if kustomization.Generation != kustomization.Status.ObservedGeneration {
return false, nil
}
return false, fmt.Errorf(condition.Message)
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
if c.Reason == meta.SuspendedReason {
return false, nil
}
return false, fmt.Errorf(c.Message)
}
}
return false, nil

112
cmd/gotk/resume_receiver.go Normal file
View File

@@ -0,0 +1,112 @@
/*
Copyright 2020 The Flux CD contributors.
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/fluxcd/pkg/apis/meta"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var resumeReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Resume a suspended Receiver",
Long: `The resume command marks a previously suspended Receiver resource for reconciliation and waits for it to
finish the apply.`,
Example: ` # Resume reconciliation for an existing Receiver
gotk resume receiver main
`,
RunE: resumeReceiverCmdRun,
}
func init() {
resumeCmd.AddCommand(resumeReceiverCmd)
}
func resumeReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
logger.Actionf("resuming Receiver %s in %s namespace", name, namespace)
receiver.Spec.Suspend = false
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver resumed")
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isReceiverResumed(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver reconciliation completed")
return nil
}
func isReceiverResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, receiver)
if err != nil {
return false, err
}
if c := meta.GetCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
if c.Reason == meta.SuspendedReason {
return false, nil
}
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

76
cmd/gotk/suspend_alert.go Normal file
View File

@@ -0,0 +1,76 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var suspendAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Suspend reconciliation of Alert",
Long: "The suspend command disables the reconciliation of a Alert resource.",
Example: ` # Suspend reconciliation for an existing Alert
gotk suspend alert main
`,
RunE: suspendAlertCmdRun,
}
func init() {
suspendCmd.AddCommand(suspendAlertCmd)
}
func suspendAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
logger.Actionf("suspending Alert %s in %s namespace", name, namespace)
alert.Spec.Suspend = true
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert suspended")
return nil
}

View File

@@ -23,7 +23,8 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var suspendHrCmd = &cobra.Command{
@@ -50,7 +51,7 @@ func suspendHrCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}

View File

@@ -19,7 +19,9 @@ package main
import (
"context"
"fmt"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
)
@@ -48,7 +50,7 @@ func suspendKsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}

View File

@@ -0,0 +1,76 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var suspendReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Suspend reconciliation of Receiver",
Long: "The suspend command disables the reconciliation of a Receiver resource.",
Example: ` # Suspend reconciliation for an existing Receiver
gotk suspend receiver main
`,
RunE: suspendReceiverCmdRun,
}
func init() {
suspendCmd.AddCommand(suspendReceiverCmd)
}
func suspendReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
logger.Actionf("suspending Receiver %s in %s namespace", name, namespace)
receiver.Spec.Suspend = true
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver suspended")
return nil
}

View File

@@ -22,9 +22,12 @@ import (
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/internal/utils"
)
var uninstallCmd = &cobra.Command{
@@ -32,10 +35,10 @@ var uninstallCmd = &cobra.Command{
Short: "Uninstall the toolkit components",
Long: "The uninstall command removes the namespace, cluster roles, cluster role bindings and CRDs from the cluster.",
Example: ` # Dry-run uninstall of all components
gotk uninstall --dry-run --namespace=gitops-system
gotk uninstall --dry-run --namespace=gotk-system
# Uninstall all components and delete custom resource definitions
gotk uninstall --resources --crds --namespace=gitops-system
gotk uninstall --resources --crds --namespace=gotk-system
`,
RunE: uninstallCmdRun,
}
@@ -48,7 +51,7 @@ var (
)
func init() {
uninstallCmd.Flags().BoolVar(&uninstallResources, "resources", false,
uninstallCmd.Flags().BoolVar(&uninstallResources, "resources", true,
"removes custom resources such as Kustomizations, GitRepositories and HelmRepositories")
uninstallCmd.Flags().BoolVar(&uninstallCRDs, "crds", false,
"removes all CRDs previously installed")
@@ -64,10 +67,13 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
dryRun := ""
if uninstallDryRun {
dryRun = "--dry-run=client"
} else if !uninstallSilent {
kubeClient, err := utils.KubeClient(kubeconfig)
if err != nil {
return err
}
dryRun := "--dry-run=server"
if !uninstallDryRun && !uninstallSilent {
prompt := promptui.Prompt{
Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", namespace),
IsConfirm: true,
@@ -77,31 +83,62 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
}
}
if uninstallResources {
// suspend bootstrap kustomization if it exists
kustomizationName := types.NamespacedName{
Namespace: namespace,
Name: namespace,
}
var kustomization kustomizev1.Kustomization
if err := kubeClient.Get(ctx, kustomizationName, &kustomization); err == nil {
kustomization.Spec.Suspend = true
if err := kubeClient.Update(ctx, &kustomization); err != nil {
return fmt.Errorf("unable to suspend kustomization '%s': %w", kustomizationName.String(), err)
}
}
if uninstallResources || uninstallCRDs {
logger.Actionf("uninstalling custom resources")
for _, kind := range []string{
kustomizev1.KustomizationKind,
sourcev1.GitRepositoryKind,
sourcev1.HelmRepositoryKind,
helmv2.HelmReleaseKind,
} {
command := fmt.Sprintf("kubectl -n %s delete %s --all --timeout=%s %s",
namespace, kind, timeout.String(), dryRun)
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
return fmt.Errorf("uninstall failed")
kubectlArgs := []string{
"-n", namespace,
"delete", kind, "--all", "--ignore-not-found",
"--timeout", timeout.String(),
}
if uninstallDryRun {
kubectlArgs = append(kubectlArgs, dryRun)
}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("uninstall failed: %w", err)
}
}
}
kinds := "namespace,clusterroles,clusterrolebindings"
var kinds []string
if uninstallCRDs {
kinds += ",crds"
kinds = append(kinds, "crds")
}
kinds = append(kinds, "clusterroles,clusterrolebindings", "namespace")
logger.Actionf("uninstalling components")
command := fmt.Sprintf("kubectl delete %s -l app.kubernetes.io/instance=%s --timeout=%s %s",
kinds, namespace, timeout.String(), dryRun)
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
return fmt.Errorf("uninstall failed")
for _, kind := range kinds {
kubectlArgs := []string{
"delete", kind,
"-l", fmt.Sprintf("app.kubernetes.io/instance=%s", namespace),
"--ignore-not-found", "--timeout", timeout.String(),
}
if uninstallDryRun {
kubectlArgs = append(kubectlArgs, dryRun)
}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("uninstall failed: %w", err)
}
}
logger.Successf("uninstall finished")

View File

@@ -1,177 +0,0 @@
/*
Copyright 2020 The Flux CD contributors.
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 (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"text/template"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
)
type Utils struct {
}
type ExecMode string
const (
ModeOS ExecMode = "os.stderr|stdout"
ModeStderrOS ExecMode = "os.stderr"
ModeCapture ExecMode = "capture.stderr|stdout"
)
func (*Utils) execCommand(ctx context.Context, mode ExecMode, command string) (string, error) {
var stdoutBuf, stderrBuf bytes.Buffer
c := exec.CommandContext(ctx, "/bin/sh", "-c", command)
if mode == ModeStderrOS {
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
}
if mode == ModeOS {
c.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
}
if mode == ModeStderrOS || mode == ModeOS {
if err := c.Run(); err != nil {
return "", err
} else {
return "", nil
}
}
if mode == ModeCapture {
c.Stdout = &stdoutBuf
c.Stderr = &stderrBuf
if err := c.Run(); err != nil {
return stderrBuf.String(), err
} else {
return stdoutBuf.String(), nil
}
}
return "", nil
}
func (*Utils) execTemplate(obj interface{}, tmpl, filename string) error {
t, err := template.New("tmpl").Parse(tmpl)
if err != nil {
return err
}
var data bytes.Buffer
writer := bufio.NewWriter(&data)
if err := t.Execute(writer, obj); err != nil {
return err
}
if err := writer.Flush(); err != nil {
return err
}
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = io.WriteString(file, data.String())
if err != nil {
return err
}
return file.Sync()
}
func (*Utils) kubeClient(config string) (client.Client, error) {
cfg, err := clientcmd.BuildConfigFromFlags("", config)
if err != nil {
return nil, fmt.Errorf("Kubernetes client initialization failed: %w", err)
}
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = sourcev1.AddToScheme(scheme)
_ = kustomizev1.AddToScheme(scheme)
_ = helmv2.AddToScheme(scheme)
kubeClient, err := client.New(cfg, client.Options{
Scheme: scheme,
})
if err != nil {
return nil, fmt.Errorf("Kubernetes client initialization failed: %w", err)
}
return kubeClient, nil
}
func (*Utils) writeFile(content, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = io.WriteString(file, content)
if err != nil {
return err
}
return file.Sync()
}
func (*Utils) copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return out.Close()
}
func (*Utils) containsItemString(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -25,7 +25,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
gotk get sources git
# Trigger a GitRepository source reconciliation
gotk reconcile source git gitops-system
gotk reconcile source git gotk-system
# Export GitRepository sources in YAML format
gotk export source git --all > sources.yaml
@@ -69,7 +69,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
```
-h, --help help for gotk
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
@@ -78,7 +78,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
* [gotk bootstrap](gotk_bootstrap.md) - Bootstrap toolkit components
* [gotk check](gotk_check.md) - Check requirements and installation
* [gotk completion](gotk_completion.md) - Generates bash completion scripts
* [gotk completion](gotk_completion.md) - Generates completion scripts for various shells
* [gotk create](gotk_create.md) - Create or update sources and resources
* [gotk delete](gotk_delete.md) - Delete sources and resources
* [gotk export](gotk_export.md) - Export resources in YAML format

View File

@@ -9,19 +9,23 @@ The bootstrap sub-commands bootstrap the toolkit components on the targeted Git
### Options
```
--arch string arch can be amd64 or arm64 (default "amd64")
--arch arch cluster architecture, available options are: (amd64, arm, arm64) (default amd64)
--branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main")
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
-h, --help help for bootstrap
--image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry
--log-level logLevel log level, available options are: (debug, info, error) (default info)
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
-v, --version string toolkit version (default "latest")
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```

View File

@@ -35,6 +35,9 @@ gotk bootstrap github [flags]
# Run bootstrap for a private repo hosted on GitHub Enterprise
gotk bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain>
# Run bootstrap for a an existing repository with a branch named main
gotk bootstrap github --owner=<organization> --repository=<repo name> --branch=main
```
### Options
@@ -54,15 +57,19 @@ gotk bootstrap github [flags]
### Options inherited from parent commands
```
--arch string arch can be amd64 or arm64 (default "amd64")
--arch arch cluster architecture, available options are: (amd64, arm, arm64) (default amd64)
--branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main")
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
--image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
--log-level logLevel log level, available options are: (debug, info, error) (default info)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
-v, --version string toolkit version (default "latest")
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
```
### SEE ALSO

View File

@@ -20,18 +20,24 @@ gotk bootstrap gitlab [flags]
# Create a GitLab API token and export it as an env var
export GITLAB_TOKEN=<my-token>
# Run bootstrap for a private repo owned by a GitLab group
# Run bootstrap for a private repo using HTTPS token authentication
gotk bootstrap gitlab --owner=<group> --repository=<repo name>
# Run bootstrap for a private repo using SSH authentication
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --ssh-hostname=gitlab.com
# Run bootstrap for a repository path
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster
# Run bootstrap for a public repository on a personal account
gotk bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
gotk bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
# Run bootstrap for a private repo hosted on a GitLab server
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain>
# Run bootstrap for a an existing repository with a branch named main
gotk bootstrap gitlab --owner=<organization> --repository=<repo name> --branch=main
```
### Options
@@ -45,21 +51,25 @@ gotk bootstrap gitlab [flags]
--personal is personal repository
--private is private repository (default true)
--repository string GitLab repository name
--ssh-hostname string GitLab SSH hostname, defaults to hostname if not specified
--ssh-hostname string GitLab SSH hostname, when specified a deploy key will be added to the repository
```
### Options inherited from parent commands
```
--arch string arch can be amd64 or arm64 (default "amd64")
--arch arch cluster architecture, available options are: (amd64, arm, arm64) (default amd64)
--branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main")
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
--image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
--log-level logLevel log level, available options are: (debug, info, error) (default info)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
-v, --version string toolkit version (default "latest")
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
```
### SEE ALSO

View File

@@ -34,7 +34,7 @@ gotk check [flags]
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```

View File

@@ -1,28 +1,10 @@
## gotk completion
Generates bash completion scripts
Generates completion scripts for various shells
### Synopsis
Generates bash completion scripts
```
gotk completion [flags]
```
### Examples
```
To load completion run
. <(gotk completion)
To configure your bash shell to load completions for each session add to your bashrc
# ~/.bashrc or ~/.profile
. <(gotk completion)
```
The completion sub-command generates completion scripts for various shells
### Options
@@ -34,7 +16,7 @@ To configure your bash shell to load completions for each session add to your ba
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
@@ -42,4 +24,8 @@ To configure your bash shell to load completions for each session add to your ba
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk completion bash](gotk_completion_bash.md) - Generates bash completion scripts
* [gotk completion fish](gotk_completion_fish.md) - Generates fish completion scripts
* [gotk completion powershell](gotk_completion_powershell.md) - Generates powershell completion scripts
* [gotk completion zsh](gotk_completion_zsh.md) - Generates zsh completion scripts

View File

@@ -0,0 +1,45 @@
## gotk completion bash
Generates bash completion scripts
### Synopsis
Generates bash completion scripts
```
gotk completion bash [flags]
```
### Examples
```
To load completion run
. <(gotk completion bash)
To configure your bash shell to load completions for each session add to your bashrc
# ~/.bashrc or ~/.profile
command -v gotk >/dev/null && . <(gotk completion bash)
```
### Options
```
-h, --help help for bash
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk completion](gotk_completion.md) - Generates completion scripts for various shells

View File

@@ -0,0 +1,46 @@
## gotk completion fish
Generates fish completion scripts
### Synopsis
Generates fish completion scripts
```
gotk completion fish [flags]
```
### Examples
```
To load completion run
. <(gotk completion fish)
To configure your fish shell to load completions for each session write this script to your completions dir:
gotk completion fish > ~/.config/fish/completions/gotk
See http://fishshell.com/docs/current/index.html#completion-own for more details
```
### Options
```
-h, --help help for fish
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk completion](gotk_completion.md) - Generates completion scripts for various shells

View File

@@ -0,0 +1,52 @@
## gotk completion powershell
Generates powershell completion scripts
### Synopsis
Generates powershell completion scripts
```
gotk completion powershell [flags]
```
### Examples
```
To load completion run
. <(gotk completion powershell)
To configure your powershell shell to load completions for each session add to your powershell profile
Windows:
cd "$env:USERPROFILE\Documents\WindowsPowerShell\Modules"
gotk completion >> gotk-completion.ps1
Linux:
cd "${XDG_CONFIG_HOME:-"$HOME/.config/"}/powershell/modules"
gotk completion >> gotk-completions.ps1
```
### Options
```
-h, --help help for powershell
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk completion](gotk_completion.md) - Generates completion scripts for various shells

View File

@@ -0,0 +1,53 @@
## gotk completion zsh
Generates zsh completion scripts
### Synopsis
Generates zsh completion scripts
```
gotk completion zsh [flags]
```
### Examples
```
To load completion run
. <(gotk completion zsh) && compdef _gotk gotk
To configure your zsh shell to load completions for each session add to your zshrc
# ~/.zshrc or ~/.profile
command -v gotk >/dev/null && . <(gotk completion zsh) && compdef _gotk gotk
or write a cached file in one of the completion directories in your ${fpath}:
echo "${fpath// /\n}" | grep -i completion
gotk completions zsh > _gotk
mv _gotk ~/.oh-my-zsh/completions # oh-my-zsh
mv _gotk ~/.zprezto/modules/completion/external/src/ # zprezto
```
### Options
```
-h, --help help for zsh
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk completion](gotk_completion.md) - Generates completion scripts for various shells

View File

@@ -12,13 +12,14 @@ The create sub-commands generate sources and resources.
--export export in YAML format to stdout
-h, --help help for create
--interval duration source sync interval (default 1m0s)
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
@@ -26,7 +27,10 @@ The create sub-commands generate sources and resources.
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk create alert](gotk_create_alert.md) - Create or update a Alert resource
* [gotk create alert-provider](gotk_create_alert-provider.md) - Create or update a Provider resource
* [gotk create helmrelease](gotk_create_helmrelease.md) - Create or update a HelmRelease resource
* [gotk create kustomization](gotk_create_kustomization.md) - Create or update a Kustomization resource
* [gotk create receiver](gotk_create_receiver.md) - Create or update a Receiver resource
* [gotk create source](gotk_create_source.md) - Create or update sources

View File

@@ -0,0 +1,57 @@
## gotk create alert-provider
Create or update a Provider resource
### Synopsis
The create alert-provider command generates a Provider resource.
```
gotk create alert-provider [name] [flags]
```
### Examples
```
# Create a Provider for a Slack channel
gotk create alert-provider slack \
--type slack \
--channel general \
--address https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
--secret-ref webhook-url
# Create a Provider for a Github repository
gotk create alert-provider github-podinfo \
--type github \
--address https://github.com/stefanprodan/podinfo \
--secret-ref github-token
```
### Options
```
--address string path to either the git repository, chat provider or webhook
--channel string channel to send messages to in the case of a chat provider
-h, --help help for alert-provider
--secret-ref string name of secret containing authentication token
--type string type of provider
--username string bot username used by the provider
```
### Options inherited from parent commands
```
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk create](gotk_create.md) - Create or update sources and resources

View File

@@ -0,0 +1,49 @@
## gotk create alert
Create or update a Alert resource
### Synopsis
The create alert command generates a Alert resource.
```
gotk create alert [name] [flags]
```
### Examples
```
# Create an Alert for kustomization events
gotk create alert \
--event-severity info \
--event-source Kustomization/gotk-system \
--provider-ref slack \
gotk-system
```
### Options
```
--event-severity string severity of events to send alerts for
--event-source stringArray sources that should generate alerts (<kind>/<name>)
-h, --help help for alert
--provider-ref string reference to provider
```
### Options inherited from parent commands
```
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk create](gotk_create.md) - Create or update sources and resources

View File

@@ -26,6 +26,12 @@ gotk create helmrelease [name] [flags]
--source=GitRepository/podinfo \
--chart=./charts/podinfo
# Create a HelmRelease with a chart from a Bucket source
gotk create hr podinfo \
--interval=10m \
--source=Bucket/podinfo \
--chart=./charts/podinfo
# Create a HelmRelease with values from a local YAML file
gotk create hr podinfo \
--source=HelmRepository/podinfo \
@@ -58,10 +64,10 @@ gotk create helmrelease [name] [flags]
```
--chart string Helm chart name or path
--chart-version string Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)
--depends-on stringArray HelmReleases that must be ready before this release can be installed
--depends-on stringArray HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'
-h, --help help for helmrelease
--release-name string name used for the Helm release, defaults to a composition of '[<target-namespace>-]<hr-name>'
--source string source that contains the chart (<kind>/<name>)
--release-name string name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'
--source helmChartSource source that contains the chart in the format '<kind>/<name>',where kind can be one of: (HelmRepository, GitRepository, Bucket)
--target-namespace string namespace to install this release, defaults to the HelmRelease namespace
--values string local path to the values.yaml file
```
@@ -72,7 +78,8 @@ gotk create helmrelease [name] [flags]
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```

View File

@@ -4,7 +4,7 @@ Create or update a Kustomization resource
### Synopsis
The kustomization source create command generates a Kustomize resource for a given GitRepository source.
The kustomization source create command generates a Kustomize resource for a given source.
```
gotk create kustomization [name] [flags]
@@ -33,31 +33,29 @@ gotk create kustomization [name] [flags]
--interval=5m \
--validation=client
# Create a Kustomization resource that runs under a service account
gotk create kustomization webapp \
--source=webapp \
--path="./deploy/overlays/staging" \
# Create a Kustomization resource that references a Bucket
gotk create kustomization secrets \
--source=Bucket/secrets \
--prune=true \
--interval=5m \
--validation=client \
--sa-name=reconclier \
--sa-namespace=staging
--interval=5m
```
### Options
```
--depends-on stringArray Kustomization that must be ready before this Kustomization can be applied
--health-check stringArray workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'
--health-check-timeout duration timeout of health checking operations (default 2m0s)
-h, --help help for kustomization
--path string path to the directory containing the Kustomization file (default "./")
--prune enable garbage collection
--sa-name string service account name
--sa-namespace string service account namespace
--source string GitRepository name
--validation string validate the manifests before applying them on the cluster, can be 'client' or 'server'
--decryption-provider decryptionProvider decryption provider, available options are: (sops)
--decryption-secret string set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption
--depends-on stringArray Kustomization that must be ready before this Kustomization can be applied, supported formats '<name>' and '<namespace>/<name>'
--health-check stringArray workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'
--health-check-timeout duration timeout of health checking operations (default 2m0s)
-h, --help help for kustomization
--path string path to the directory containing the Kustomization file (default "./")
--prune enable garbage collection
--sa-name string service account name
--sa-namespace string service account namespace
--source kustomizationSource source that contains the Kubernetes manifests in the format '[<kind>/]<name>',where kind can be one of: (GitRepository, Bucket), if kind is not specified it defaults to GitRepository
--validation string validate the manifests before applying them on the cluster, can be 'client' or 'server'
```
### Options inherited from parent commands
@@ -66,7 +64,8 @@ gotk create kustomization [name] [flags]
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```

View File

@@ -0,0 +1,52 @@
## gotk create receiver
Create or update a Receiver resource
### Synopsis
The create receiver command generates a Receiver resource.
```
gotk create receiver [name] [flags]
```
### Examples
```
# Create a Receiver
gotk create receiver github-receiver \
--type github \
--event ping \
--event push \
--secret-ref webhook-token \
--resource GitRepository/webapp \
--resource HelmRepository/webapp
```
### Options
```
--event stringArray
-h, --help help for receiver
--resource stringArray
--secret-ref string
--type string
```
### Options inherited from parent commands
```
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk create](gotk_create.md) - Create or update sources and resources

View File

@@ -18,7 +18,8 @@ The create source sub-commands generate sources.
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
@@ -26,6 +27,7 @@ The create source sub-commands generate sources.
### SEE ALSO
* [gotk create](gotk_create.md) - Create or update sources and resources
* [gotk create source bucket](gotk_create_source_bucket.md) - Create or update a Bucket source
* [gotk create source git](gotk_create_source_git.md) - Create or update a GitRepository source
* [gotk create source helm](gotk_create_source_helm.md) - Create or update a HelmRepository source

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