Compare commits

...

119 Commits

Author SHA1 Message Date
Tom Wieczorek
5732f19d20 Update to 0.10.6 also in kustomization (#816) 2018-11-26 14:36:57 -08:00
Jesse Suen
0dfcd2f7d8 Update version to v0.10.6 2018-11-14 17:20:31 -08:00
Jesse Suen
7e49cff7e9 Fix issue preventing in-cluster app sync due to go-client changes (issue #774) 2018-11-14 17:17:08 -08:00
Conor Fennell
5d185b6584 add argo cluster permission to view logs (#766) 2018-11-13 18:47:49 -08:00
Jesse Suen
7bfa374b40 Bump up the default status/operation processors to 20/10 respectively 2018-11-13 18:18:21 -08:00
Jesse Suen
6c20e0f7d7 Bump version to v0.10.5 2018-11-13 15:28:02 -08:00
Jesse Suen
93d2dd7ed0 Update dependencies to k8s v1.12 and client-go v9.0 (#729)
Update dependencies to k8s v1.12 and client-go v9.0 (resolves #353)
Fix issue where applications could not be deleted on k8s v1.12 (resolves #718)
Refactor k8s dynamic resource libraries to promote code reuse
2018-11-13 14:59:12 -08:00
Jesse Suen
89ece31762 Update version to v0.10.4 2018-11-07 17:25:16 -08:00
Jesse Suen
d565a0a119 Health check is not discerning apiVersion when assessing CRDs (issue #753) (#754) 2018-11-07 17:24:20 -08:00
Mario Duarte
1e2b554f01 Fix nil pointer dereference in util/health (#723) 2018-11-07 17:24:14 -08:00
Alessandro Marrella
8cb2f5d7e4 Updated helm (#749) 2018-11-07 16:35:16 -08:00
Alexander Matyushentsev
c5814d5946 Update version to v0.10.3 2018-10-28 23:31:08 -07:00
Alexander Matyushentsev
a4a81d1de9 Fix applying TLS version settings (#731) 2018-10-28 23:30:00 -07:00
Tom Wieczorek
cb27cec021 Update to kustomize 1.0.10 (#728) (#728)
See also kubernetes-sigs/kustomize#514
2018-10-28 22:47:42 -07:00
Alexander Matyushentsev
e13e13e7ae Update manifests to v0.10.2 2018-10-25 11:52:17 -07:00
Tom Wieczorek
88d41f8efa Update to kustomize 1.0.9 (#722) 2018-10-25 11:51:22 -07:00
dthomson25
dbe09104a1 Fix app refresh err when k8s patch is too slow (#724) 2018-10-25 11:51:18 -07:00
Alexander Matyushentsev
6a18870ec1 Update manifests to v0.10.1 2018-10-24 12:57:02 -07:00
Jesse Suen
ca9f992fc2 Update version to v0.10.1 2018-10-24 11:40:52 -07:00
Jesse Suen
063ff34f00 Handle case where OIDC settings become invalid after dex server restart (issue #710) (#715) 2018-10-24 11:39:53 -07:00
Jesse Suen
a9980c3025 git clean also needs to clean files under gitignore (issue #711) (#712) 2018-10-19 22:11:25 -07:00
Alexander Matyushentsev
3f5967c83e Update manifests to v0.10.0 2018-10-19 14:29:37 -07:00
Jesse Suen
22b77f5b34 RBAC for cluster wide install was missing permissions to list events across namespaces (resolves #704) (#705) 2018-10-19 00:04:03 -07:00
Alexander Matyushentsev
c76db90437 Issue #628 - Remove RollbackOperation in favor of Sync with ParameterOverrides (#706) 2018-10-18 20:23:22 -07:00
Alexander Matyushentsev
080f7ff4e0 Add 0.10 changelog (#700) 2018-10-18 18:21:49 -07:00
Alexander Matyushentsev
c5730c8f5f Issue #672 - Metrics endpoint not reachable through the metrics kubernetes service (#692) 2018-10-17 09:44:12 -07:00
Alexander Matyushentsev
d46f284d9f Issue #690 - Increase GRPC message limit (#691) 2018-10-16 16:34:26 -07:00
Alexander Matyushentsev
221f19ae15 Add argocd-util cluster-kubeconfig command (#689) 2018-10-16 16:17:58 -07:00
Alexander Matyushentsev
550cb277df Issue #686 - Resource is always out of sync if it has only 'ksonnet.io/component' label (#688) 2018-10-15 12:57:30 -07:00
Alexander Matyushentsev
9c79af9340 Issue #682 - Operation stuck in 'in progress' state if application has no resources (#684) 2018-10-11 11:18:17 -04:00
Andrew Merenbach
1ba52c8880 Allow more fine-grained sync (closes #508) (#666) 2018-10-10 10:12:20 -07:00
Andrew Merenbach
92629067f7 Upgrade testify (#667)
* Update Gopkg.toml

* Update Gopkg.lock
2018-10-08 11:01:00 -07:00
Alexander Matyushentsev
93a808e65a Issue #627 - Cluster watch needs to be restarted when CRDs get created (#678) 2018-10-05 13:18:12 -04:00
Alexander Matyushentsev
bf99b251f8 Issue #679 - Default project is created without permission to deploy cluster level resources (#680) 2018-10-05 11:42:54 -04:00
Alexander Matyushentsev
f491540636 Issue #426 - Support public not-connected repo in app creation UI (#675) 2018-10-04 12:46:39 -04:00
dthomson25
7f84f7d541 Add project get permission automatically to roles (#665) 2018-10-01 12:44:06 -07:00
Alexander Matyushentsev
42b01f7126 Add v0.9.2 changelog (#662) 2018-09-28 13:14:06 -04:00
Andrew Merenbach
7e5c17939b Add errgroup dependency for Packr (#648) 2018-09-27 18:27:09 -07:00
Alexander Matyushentsev
d6937ec629 Issue #650 - Temporary ignore service catalog resources (#661) 2018-09-27 20:58:45 -04:00
Andrew Merenbach
f5a32f47d3 Update generated files (#660) 2018-09-27 13:07:16 -07:00
Jesse Suen
316fcc6126 Fix issue where argocd-server logged credentials in plain text during repo add (issue #653) 2018-09-27 12:48:23 -07:00
Jesse Suen
e163177a12 Switch to go-git for all remote git interactions including auth (issue #651) 2018-09-27 12:48:23 -07:00
Jesse Suen
1fe257c71e Do not append .git extension during normalization for Azure hosted git (issue #643) (#645) 2018-09-27 11:54:04 -07:00
Andrew Merenbach
1eaa813f28 Use ksonnet CLI instead of ksonnet libs (#590) (#626) 2018-09-27 11:52:08 -07:00
dthomson25
924dad8980 Normalize policies by always adding space after comma (#659) 2018-09-27 11:24:48 -07:00
dthomson25
1ba10a1a20 Remove default params from app history (#649) 2018-09-27 11:24:25 -07:00
Stephen Haynes
ab02e10791 update to kustomize 1.0.8 (#644) 2018-09-26 14:24:59 -07:00
Jesse Suen
dd94e5e5c3 Add version check during release to ensure compiled version is accurate (#646) 2018-09-26 07:40:42 -07:00
Jesse Suen
1fcb90c4d9 Documentation clarifications and fixes (#642) 2018-09-25 08:01:41 -07:00
Alexander Matyushentsev
523c7ddf82 Update getting_started.md with new version; update releasing steps (#641) 2018-09-24 15:43:06 -07:00
Jesse Suen
3577a68d2d Update documentation with auto-sync and projects (issue #521) (#616) 2018-09-24 15:27:30 -07:00
Alexander Matyushentsev
d963f5fcc5 Issue #639 - Repo server unable to execute ls-remote for private repos (#640) 2018-09-24 14:20:52 -07:00
Alexander Matyushentsev
bed82d68df Update changelog and fix release command dependency (#638) 2018-09-24 12:58:17 -07:00
Jesse Suen
359271dfa8 Update manifests to support in-cluster installations (#634) 2018-09-24 10:14:31 -07:00
Jesse Suen
0af77a0706 Add more event sources and provide better detail in event messages (issue #635) (#637)
* Expand SyncOperation to also store parameter overrides
Fix auto-sync when used with parameter overrides

* Add more event sources and provide better detail in event messages (issue #635)
2018-09-24 08:52:43 -07:00
Jesse Suen
e6efd79ad8 Support ability to use a helm values files from a URL (issue #624) 2018-09-21 16:05:42 -07:00
Jesse Suen
c953934d2e Simplify the RBAC resources to remove unnecessary sub-resources (issue #629) 2018-09-21 15:25:08 -07:00
Alexander Matyushentsev
5b4742d42b Issue #613 - Don't delete CRD (#630) 2018-09-21 10:29:32 -07:00
Jesse Suen
269f70df51 Trim git url during normalization (issue #614) (#623) 2018-09-20 16:26:17 -07:00
Jesse Suen
67177f933b Fix false OutOfSync condition when an explicit namespace is set in the config (#622) 2018-09-20 14:52:16 -07:00
Jesse Suen
606fdcded7 Rename server.crt/server.key to tls.crt/tls.key to integrate with Ingress (issue #617) 2018-09-20 12:49:23 -07:00
Alexander Matyushentsev
70b9db68b4 Issue #599 - Lazy enforcement of unknown cluster/namespace restricted resources (#612) 2018-09-20 09:48:54 -07:00
Jesse Suen
dc8a2f5d62 Support for exporting prometheus metrics about ArgoCD applications (#608) 2018-09-17 14:05:11 -07:00
Alexander Matyushentsev
8830cf9556 609 - Support restricting TLS version (#610) 2018-09-17 13:14:00 -07:00
Jesse Suen
bfb558eb92 Fix issue where helm hooks were being deployed as part of sync (issue #605) 2018-09-17 11:29:44 -07:00
Jesse Suen
505866a4c6 Support helm charts with dependencies and namespace sensitivity (issue #582) 2018-09-17 11:29:44 -07:00
Yuki Kodama
acd2de80fb Update getting started to point to v0.8.2 (#607) 2018-09-15 23:45:06 -07:00
Alexander Matyushentsev
0b08bf4537 Issue #523 - Use 'kubectl auth reconcile' for RBAC resources (#600) 2018-09-14 20:38:35 -07:00
Jesse Suen
223091482c Improve three-way diff to provide more accurate Sync status and diff result (issue #597) (#604) 2018-09-14 19:10:11 -07:00
Andrew Merenbach
4699946e1b Derive dedicated Dex deployment (#564)
Put Dex into its own deployment and service to decouple API server stability from auth token processing
2018-09-14 17:08:12 -07:00
Jesse Suen
097f87fd52 Improve remarshalling to use reflection/schema builders to handle all k8s core types (#603) 2018-09-14 16:17:20 -07:00
Alexander Matyushentsev
66b4f3a685 Issue #515 - handle concurrent settings initialization by api servers (#602) 2018-09-14 15:09:12 -07:00
Jesse Suen
02116d4bfc Fix comparison failure when app contains unregistered custom resource (issue #583) (#596) 2018-09-13 14:02:04 -07:00
Jesse Suen
fb17589af6 Fix race conditions in kube.GetResourcesWithLabel and DeleteResourceWithLabel (issue #587) (#593) 2018-09-13 13:58:47 -07:00
Alexander Matyushentsev
15ce7ea880 Issue #584 - ArgoCD fails to deploy resources list (#598) 2018-09-13 13:52:30 -07:00
Alexander Matyushentsev
57a3123a55 Issue #482 - Support IAM Authentication for managing external K8s clusters (#588) 2018-09-13 00:09:23 -07:00
Jesse Suen
32e96e4bb2 Fix app sync / wait panic in CLI 2018-09-12 23:41:42 -07:00
Jesse Suen
47ee26a77a Downgrade ksonnet from v0.12.0 to v0.11.0 due to quote unescape regression 2018-09-12 23:41:42 -07:00
dthomson25
9cd5d52fbc Add iat as path param for delete token http call (#586) 2018-09-12 19:49:20 -07:00
Alexander Matyushentsev
aa2afcd47b Issue #330 - Projects need controls on cluster-scoped resources (#558)
* Issue #330 - Projects need controls on cluster-scoped resources

* Issue #330 - Introduce namespace resources black-list
2018-09-11 15:10:47 -07:00
Jesse Suen
fd510e7933 Support an automated sync policy upon detection of OutOfSync status from git (#571) 2018-09-11 14:28:53 -07:00
Jesse Suen
e29d5b9634 In-memory implementation of ls-remote using go-git to reduce repo lock contention (#574) 2018-09-11 13:53:51 -07:00
Conor Fennell
2f9891b15b Issue #577 - Add rbac non resource url policy for argocd-manager-role (#578)
* Add rbac non resource url policy for argocd-manager-role
* allow all non resource urls to be added through rbac
2018-09-11 13:23:10 -07:00
Jesse Suen
c3ecd615ff Update getting started and docs to point to v0.8.1 (#575) 2018-09-10 19:05:47 -07:00
Jesse Suen
4e22a3cb21 Add link to SigApps video and update CHANGELOG for v0.8.1 (#572) 2018-09-10 16:08:08 -07:00
Jesse Suen
bc98b65190 Fix controller hot loop when app source contains bad manifests (issue #568) (#570) 2018-09-10 10:58:13 -07:00
Jesse Suen
02b756ef40 Fix issue where branch checkout did not have accurate git tree state (issue #567) (#569) 2018-09-10 10:55:12 -07:00
dthomson25
954706570c Reorder K8s resources to correct creation order (#551) 2018-09-10 10:14:14 -07:00
Alexander Matyushentsev
e2faf6970f Issue #527 - Support --in-cluster authentication without providing a kubeconfig (#559)
* Issue #527 - Support --in-cluster authentication without providing a kubeconfig

* Issue #527 - make sure resources are watched for 'local' cluster
2018-09-10 08:20:17 -07:00
Alexander Matyushentsev
a528ae9c12 Issue #553 - Turn on TLS for repo server (#563) 2018-09-08 00:17:29 +03:00
Alexander Matyushentsev
0a5871eba4 Issue #470 - K8s secrets need to be redacted in API server (#560) 2018-09-07 23:51:32 +03:00
Alexander Matyushentsev
27471d5249 Issue #540 - Support raw jsonnet as an application source (#561) 2018-09-07 21:15:19 +03:00
Alexander Matyushentsev
ed484c00db Issue 499 - fileFiles path should be relative to app directory (#552) 2018-09-05 23:37:26 +03:00
Jesse Suen
b868f26ca4 Update documentation for v0.8.0 (#550) 2018-09-04 22:31:21 -07:00
Jesse Suen
d7c04ae24c Update manifests to use v0.8.0
Make manifests friendly to `kubectl apply` semantics by omitting `data:` field
RBAC docs improvements
2018-09-04 17:58:50 -07:00
Jesse Suen
5bcf8c40e0 Minor improvements to token CLI (#549) 2018-09-04 17:57:31 -07:00
dthomson25
b8e30ed953 Add documentation on project roles and JWT tokens (#533)
* Add documentation on project roles and JWT tokens
* Add AppProject CRD to architecture.md
2018-09-04 17:57:00 -07:00
Jesse Suen
e3adb30ca7 Run all containers as an unprivileged user (resolves #528) (#546) 2018-09-04 13:47:00 -07:00
Jesse Suen
1e8c570c8a Fix argocd app wait printing incorrect Sync output (resolves #542) (#543)
Timeout condition was not printing final status.
2018-08-31 11:25:15 -07:00
Jesse Suen
40f2220f1d Fix issue where argocd could not sync to a tag (#541) 2018-08-31 11:24:14 -07:00
dthomson25
4da779c44c Add PVC healthcheck to controller (#501) (#537) 2018-08-28 16:40:32 -07:00
Jesse Suen
b54a5a3e25 Refactor Makefile/build to use a single Dockerfile. Update kustomize to v1.0.7 (#538) 2018-08-28 16:00:14 -07:00
dthomson25
f572bcff58 Create default project on startup (#535)
Implements to solve issue #514
2018-08-28 10:06:01 -07:00
Andrew Merenbach
d47b7e6128 Use gRPC error codes instead of fmt.Errorf (#532) 2018-08-27 17:54:29 -07:00
Andrew Merenbach
8d9e4faae9 Add health check on API server (#522)
* Add app health endpoints

* Update generated files

* Revert "Update generated files"

This reverts commit 40f490797645ed0f30d05785748e3919dea31b7f.

* Revert "Add app health endpoints"

This reverts commit 650688dd2ee4a533e29b7df69e0bbb2436eead6b.

* Add dedicated health endpoint

* Update generated files

* Slim down basic server

* Update generated files

* Update health server creation

* Fix import, endpoint casing

* Flesh out basic health check

* Add additional endpoints, fix check, thanks @jessesuen

* Fix errors

* Update generated files

* Simplify health check, update endpoint

* Update generated files

* Factor out health check code

* Update generated files

* Rm health endpoint

* Add healthz utility

* Log error instead of printing it

* Update comment

* Add liveness, readiness probes to manifest for API server

* Add health check test

* Tweak timeouts, endpoints in probes, thanks @jessesuen

* Tweak probes, thanks @jessesuen
2018-08-23 09:24:21 -07:00
Jesse Suen
cf630055b0 API discovery becomes best effort when partial resource list is returned (resolves #524) (#526) 2018-08-21 13:53:42 -07:00
dthomson25
a5870c894f Fix typo in sso.md (#518) 2018-08-21 09:10:31 -07:00
Andrew Merenbach
c236ee99d4 Use named FIFO so we can exit with non-zero status (#516) 2018-08-16 13:22:29 -07:00
Jesse Suen
130e242aa9 Fix build breakage (#517) 2018-08-15 18:31:43 -07:00
Jesse Suen
39f0a17d0d Add ability to delete a single application resource (issue #262) (#511) 2018-08-15 15:01:29 -07:00
Jesse Suen
da0682afa7 Support for kustomize app directories (#510) 2018-08-15 14:54:56 -07:00
Andrew Merenbach
3c755a2002 Upgrade Ksonnet (#506) 2018-08-15 12:56:41 -07:00
dthomson25
66f64fbf15 Add Project JWT tokens (#498)
Implemented Project JWT Tokens (#472) using #228 as the overall design
2018-08-15 12:54:24 -07:00
Alexander Matyushentsev
4c0a0e09e2 Issue 435 - pump ci logs to s3 (#509) 2018-08-14 01:59:43 +03:00
Alexander Matyushentsev
f8de6084ed Issue #458 - Add api to load project events (#504) 2018-08-10 01:55:43 +03:00
Andrew Merenbach
36624f9d89 Enable code coverage (#500)
* Update Gopkg.toml

* Update Gopkg.lock

* Add new test-coverage command

* Update .gitignore to ignore coverage.out

* Test injection of COVERALLS_TOKEN variable

* Add draft of .travis.yml

* Rm recursive coveralls token

* Ensure that goveralls gets installed

* Rm second Go version

* Update workflow with coverage testing

* Change service from argo to argo-ci

* Rm .travis.yml

* Try setting coveralls token more explicitly

* Try file-based instead of env-based token

* Try both methods of providing token

* Go back to just env-based token

* Update with another printout test

* Try using container, thanks @alexmt

* Simplify for now, take 2

* Rm quotes

* Move env to ci-builder template

* Rm coveralls token

* Add coverage badge for current branch, take 2

* Add else statement for output in case of missing token

* Ensure we use the race detector

* Don't install goveralls with dep ensure

* Update generated files

* Try ignoring intermediate files

* Don't use race detector for now

* Try new pattern to ignore

* Try different pattern now

* Try different ignore path

* Try a different ignore style

* Ignore generated protobuf files properly now

* Rm standalone test since we have test-coverage
2018-08-09 15:54:15 -07:00
Alexander Matyushentsev
cbf1e3419b Issue #489 - Static assets are being browser cached between upgrades (#502) 2018-08-08 21:10:40 +03:00
Andrew Merenbach
7c8cc41d4c Support explicit deny (#497)
* Honor deny in RBAC model

* Add explicit allow for roles

* Update tests

* Test explicit deny

* Test deny,allow=>deny and allow,deny=>deny
2018-08-08 09:33:44 -07:00
Andrew Merenbach
5dbbd0a76f Support UI cluster creation (#469)
* Add kubeconfig string to ClusterCreateRequest

* Update generated files

* Copy and adapt cluster management logic into db

* Add service account deletion to db

* Return errors from new DB methods

* Adapt InstallClusterManagerRBAC for db

* Update errors in db

* Return error if it occurs from db

* Integrate code to (un)install cluster manager

* Use invalid argument instead of failed precondition

* Set bearer token if error is nil

* Rm cluster RBAC install from CLI

* Rm cluster mgmt install from e2e

* Rm common/install.go

* Move install components into server/cluster, thanks @jessesuen

* Rm unneeded ctx arg

* Restore common/installer.go

* Replace all quoted percent-s with percent-q

* Refactor common/installer.go with error returns

* Return errors rather than exiting fatally

* Return proper number of args

* Slim down cluster methods again

* Simplify, simplify, simplify

* Return gRPC error if RBAC could not be installed

* Issue log entries, not print statements

* Fix log import

* Update generated files

* Refactor

* Major cleanup

* Unmarshal context now

* Put claims check after bearer token insertion

* Initial work to use Kubernetes manifest to create a cluster

* Pass context name now

* Wire up prototype

* Add missing parameter for e2e test

* Just read file directly

* Change how we read cluster server

* Support more attributes from localconfig

* Update generated files

* Support incluster flag

* Comment out unused field for now

* Rm previous NewCluster function

* Unmarshal kube config successfully

* Handle insecure clusters, too

* Use existing logic to get config, thanks @jessesuen

* Revert cluster.go to master version

* Update invocations of RBAC installation

* Fix e2e invocation

* Don't remove management account, thanks @jessesuen

* Fix missing error check in e2e test

* Fix missing clientset arg in e2e fixture

* Create kubeclientset for kubeconfig, thanks @jessesuen
2018-08-06 11:27:48 -07:00
209 changed files with 19177 additions and 4513 deletions

View File

@@ -29,7 +29,13 @@ spec:
- name: cmd
value: "{{item}}"
withItems:
- dep ensure && make cli lint test
- dep ensure && make cli lint
- name: test-coverage
template: ci-builder
arguments:
parameters:
- name: cmd
value: "dep ensure && go get github.com/mattn/goveralls && make test-coverage"
- name: test-e2e
template: ci-builder
arguments:
@@ -50,12 +56,22 @@ spec:
container:
image: argoproj/argo-cd-ci-builder:latest
command: [sh, -c]
args: ["{{inputs.parameters.cmd}}"]
args: ["mkfifo pipe; tee /tmp/logs.txt < pipe & {{inputs.parameters.cmd}} > pipe"]
workingDir: /go/src/github.com/argoproj/argo-cd
env:
- name: COVERALLS_TOKEN
valueFrom:
secretKeyRef:
name: coverall-token
key: coverall-token
resources:
requests:
memory: 1024Mi
cpu: 200m
outputs:
artifacts:
- name: logs
path: /tmp/logs.txt
- name: ci-dind
inputs:
@@ -70,7 +86,7 @@ spec:
container:
image: argoproj/argo-cd-ci-builder:latest
command: [sh, -c]
args: ["until docker ps; do sleep 3; done && {{inputs.parameters.cmd}}"]
args: ["mkfifo pipe; tee /tmp/logs.txt < pipe & until docker ps; do sleep 3; done && {{inputs.parameters.cmd}} > pipe"]
workingDir: /go/src/github.com/argoproj/argo-cd
env:
- name: DOCKER_HOST
@@ -85,4 +101,7 @@ spec:
securityContext:
privileged: true
mirrorVolumeMounts: true
outputs:
artifacts:
- name: logs
path: /tmp/logs.txt

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ dist/
# delve debug binaries
cmd/**/debug
debug.test
coverage.out

View File

@@ -1,5 +1,168 @@
# Changelog
## v0.10.0 (TBD)
### Changes since v0.9:
+ Allow more fine-grained sync (issue #508)
+ Display init container logs (issue #681)
+ Redirect to /auth/login instead of /login when SSO token is used for authenticaion (issue #348)
+ Support ability to use a helm values files from a URL (issue #624)
+ Support public not-connected repo in app creation UI (issue #426)
+ Use ksonnet CLI instead of ksonnet libs (issue #626)
+ We should be able to select the order of the `yaml` files while creating a Helm App (#664)
* Remove default params from app history (issue #556)
* Update to ksonnet v0.13.0
* Update to kustomize 1.0.8
- API Server fails to return apps due to grpc max message size limit (issue #690)
- App Creation UI for Helm Apps shows only files prefixed with `values-` (issue #663)
- App creation UI should allow specifying values files outside of helm app directory bug (issue #658)
- argocd-server logs credentials in plain text when adding git repositories (issue #653)
- Azure Repos do not work as a repository (issue #643)
- Better update conflict error handing during app editing (issue #685)
- Cluster watch needs to be restarted when CRDs get created (issue #627)
- Credentials not being accepted for Google Source Repositories (issue #651)
- Default project is created without permission to deploy cluster level resources (issue #679)
- Generate role token click resets policy changes (issue #655)
- Input type text instead of password on Connect repo panel (issue #693)
- Metrics endpoint not reachable through the metrics kubernetes service (issue #672)
- Operation stuck in 'in progress' state if application has no resources (issue #682)
- Project should influence options for cluster and namespace during app creation (issue #592)
- Repo server unable to execute ls-remote for private repos (issue #639)
- Resource is always out of sync if it has only 'ksonnet.io/component' label (issue #686)
- Resource nodes are 'jumping' on app details page (issue #683)
- Sync always suggest using latest revision instead of target UI bug (issue #669)
- Temporary ignore service catalog resources (issue #650)
## v0.9.2 (2018-09-28)
* Update to kustomize 1.0.8
- Fix issue where argocd-server logged credentials in plain text during repo add (issue #653)
- Credentials not being accepted for Google Source Repositories (issue #651)
- Azure Repos do not work as a repository (issue #643)
- Temporary ignore service catalog resources (issue #650)
- Normalize policies by always adding space after comma
## v0.9.1 (2018-09-24)
- Repo server unable to execute ls-remote for private repos (issue #639)
## v0.9.0 (2018-09-24)
### Notes about upgrading from v0.8
* Cluster wide resources should be allowed in default project (due to issue #330):
```
argocd project allow-cluster-resource default '*' '*'
```
* Projects now provide the ability to allow or deny deployments of cluster-scoped resources
(e.g. Namespaces, ClusterRoles, CustomResourceDefinitions). When upgrading from v0.8 to v0.9, to
match the behavior of v0.8 (which did not have restrictions on deploying resources) and continue to
allow deployment of cluster-scoped resources, an additional command should be run:
```bash
argocd proj allow-cluster-resource default '*' '*'
```
The above command allows the `default` project to deploy any cluster-scoped resources which matches
the behavior of v0.8.
* The secret keys in the argocd-secret containing the TLS certificate and key, has been renamed from
`server.crt` and `server.key` to the standard `tls.crt` and `tls.key` keys. This enables ArgoCD
to integrate better with Ingress and cert-manager. When upgrading to v0.9, the `server.crt` and
`server.key` keys in argocd-secret should be renamed to the new keys.
### Changes since v0.8:
+ Auto-sync option in application CRD instance (issue #79)
+ Support raw jsonnet as an application source (issue #540)
+ Reorder K8s resources to correct creation order (issue #102)
+ Redact K8s secrets from API server payloads (issue #470)
+ Support --in-cluster authentication without providing a kubeconfig (issue #527)
+ Special handling of CustomResourceDefinitions (issue #613)
+ ArgoCD should download helm chart dependencies (issue #582)
+ Export ArgoCD stats as prometheus style metrics (issue #513)
+ Support restricting TLS version (issue #609)
+ Use 'kubectl auth reconcile' before 'kubectl apply' (issue #523)
+ Projects need controls on cluster-scoped resources (issue #330)
+ Support IAM Authentication for managing external K8s clusters (issue #482)
+ Compatibility with cert manager (issue #617)
* Enable TLS for repo server (issue #553)
* Split out dex into it's own deployment (instead of sidecar) (issue #555)
+ [UI] Support selection of helm values files in App creation wizard (issue #499)
+ [UI] Support specifying source revision in App creation wizard allow (issue #503)
+ [UI] Improve resource diff rendering (issue #457)
+ [UI] Indicate number of ready containers in pod (issue #539)
+ [UI] Indicate when app is overriding parameters (issue #503)
+ [UI] Provide a YAML view of resources (issue #396)
+ [UI] Project Role/Token management from UI (issue #548)
+ [UI] App creation wizard should allow specifying source revision (issue #562)
+ [UI] Ability to modify application from UI (issue #615)
+ [UI] indicate when operation is in progress or has failed (issue #566)
- Fix issue where changes were not pulled when tracking a branch (issue #567)
- Lazy enforcement of unknown cluster/namespace restricted resources (issue #599)
- Fix controller hot loop when app source contains bad manifests (issue #568)
- Fix issue where ArgoCD fails to deploy when resources are in a K8s list format (issue #584)
- Fix comparison failure when app contains unregistered custom resource (issue #583)
- Fix issue where helm hooks were being deployed as part of sync (issue #605)
- Fix race conditions in kube.GetResourcesWithLabel and DeleteResourceWithLabel (issue #587)
- [UI] Fix issue where projects filter does not work when application got changed
- [UI] Creating apps from directories is not obvious (issue #565)
- Helm hooks are being deployed as resources (issue #605)
- Disagreement in three way diff calculation (issue #597)
- SIGSEGV in kube.GetResourcesWithLabel (issue #587)
- ArgoCD fails to deploy resources list (issue #584)
- Branch tracking not working properly (issue #567)
- Controller hot loop when application source has bad manifests (issue #568)
## v0.8.2 (2018-09-12)
- Downgrade ksonnet from v0.12.0 to v0.11.0 due to quote unescape regression
- Fix CLI panic when performing an initial `argocd sync/wait`
## v0.8.1 (2018-09-10)
+ [UI] Support selection of helm values files in App creation wizard (issue #499)
+ [UI] Support specifying source revision in App creation wizard allow (issue #503)
+ [UI] Improve resource diff rendering (issue #457)
+ [UI] Indicate number of ready containers in pod (issue #539)
+ [UI] Indicate when app is overriding parameters (issue #503)
+ [UI] Provide a YAML view of resources (issue #396)
- Fix issue where changes were not pulled when tracking a branch (issue #567)
- Fix controller hot loop when app source contains bad manifests (issue #568)
- [UI] Fix issue where projects filter does not work when application got changed
## v0.8.0 (2018-09-04)
### Notes about upgrading from v0.7
* The RBAC model has been improved to support explicit denies. What this means is that any previous
RBAC policy rules, need to be rewritten to include one extra column with the effect:
`allow` or `deny`. For example, if a rule was written like this:
```
p, my-org:my-team, applications, get, */*
```
It should be rewritten to look like this:
```
p, my-org:my-team, applications, get, */*, allow
```
### Changes since v0.7:
+ Support kustomize as an application source (issue #510)
+ Introduce project tokens for automation access (issue #498)
+ Add ability to delete a single application resource to support immutable updates (issue #262)
+ Update RBAC model to support explicit denies (issue #497)
+ Ability to view Kubernetes events related to application projects for auditing
+ Add PVC healthcheck to controller (issue #501)
+ Run all containers as an unprivileged user (issue #528)
* Upgrade ksonnet to v0.12.0
* Add readiness probes to API server (issue #522)
* Use gRPC error codes instead of fmt.Errorf (#532)
- API discovery becomes best effort when partial resource list is returned (issue #524)
- Fix `argocd app wait` printing incorrect Sync output (issue #542)
- Fix issue where argocd could not sync to a tag (#541)
- Fix issue where static assets were browser cached between upgrades (issue #489)
## v0.7.2 (2018-08-21)
- API discovery becomes best effort when partial resource list is returned (issue #524)
## v0.7.1 (2018-08-03)
+ Surface helm parameters to the application level (#485)
+ [UI] Improve application creation wizard (#459)

View File

@@ -6,6 +6,7 @@ Make sure you have following tools installed
* [protobuf](https://developers.google.com/protocol-buffers/)
* [ksonnet](https://github.com/ksonnet/ksonnet#install)
* [helm](https://github.com/helm/helm/releases)
* [kustomize](https://github.com/kubernetes-sigs/kustomize/releases)
* [go-swagger](https://github.com/go-swagger/go-swagger/blob/master/docs/install.md)
* [jq](https://stedolan.github.io/jq/)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).

136
Dockerfile Normal file
View File

@@ -0,0 +1,136 @@
####################################################################################################
# Builder image
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
# Also used as the image in CI jobs so needs all dependencies
####################################################################################################
FROM golang:1.10.3 as builder
RUN apt-get update && apt-get install -y \
git \
make \
wget \
gcc \
zip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /tmp
# Install docker
ENV DOCKER_VERSION=18.06.0
RUN curl -O https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}-ce.tgz && \
tar -xzf docker-${DOCKER_VERSION}-ce.tgz && \
mv docker/docker /usr/local/bin/docker && \
rm -rf ./docker
# Install dep
ENV DEP_VERSION=0.5.0
RUN wget https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 -O /usr/local/bin/dep && \
chmod +x /usr/local/bin/dep
# Install gometalinter
RUN curl -sLo- https://github.com/alecthomas/gometalinter/releases/download/v2.0.5/gometalinter-2.0.5-linux-amd64.tar.gz | \
tar -xzC "$GOPATH/bin" --exclude COPYING --exclude README.md --strip-components 1 -f- && \
ln -s $GOPATH/bin/gometalinter $GOPATH/bin/gometalinter.v2
# Install packr
ENV PACKR_VERSION=1.13.2
RUN wget https://github.com/gobuffalo/packr/releases/download/v${PACKR_VERSION}/packr_${PACKR_VERSION}_linux_amd64.tar.gz && \
tar -vxf packr*.tar.gz -C /tmp/ && \
mv /tmp/packr /usr/local/bin/packr
# Install kubectl
RUN curl -L -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
chmod +x /usr/local/bin/kubectl
# Install ksonnet
# NOTE: we frequently switch between tip of master ksonnet vs. official builds. Comment/uncomment
# the corresponding section to switch between the two options:
# Option 1: build ksonnet ourselves
#RUN go get -v -u github.com/ksonnet/ksonnet && mv ${GOPATH}/bin/ksonnet /usr/local/bin/ks
# Option 2: use official tagged ksonnet release
ENV KSONNET_VERSION=0.13.0
RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION}/ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
tar -C /tmp/ -xf ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
mv /tmp/ks_${KSONNET_VERSION}_linux_amd64/ks /usr/local/bin/ks
# Install helm
ENV HELM_VERSION=2.11.0
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
mv /tmp/linux-amd64/helm /usr/local/bin/helm
# Install kustomize
ENV KUSTOMIZE_VERSION=1.0.10
RUN curl -L -o /usr/local/bin/kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/kustomize
ENV AWS_IAM_AUTHENTICATOR_VERSION=0.3.0
RUN curl -L -o /usr/local/bin/aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/v0.3.0/heptio-authenticator-aws_${AWS_IAM_AUTHENTICATOR_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/aws-iam-authenticator
####################################################################################################
# ArgoCD Build stage which performs the actual build of ArgoCD binaries
####################################################################################################
FROM golang:1.10.3 as argocd-build
COPY --from=builder /usr/local/bin/dep /usr/local/bin/dep
COPY --from=builder /usr/local/bin/packr /usr/local/bin/packr
# A dummy directory is created under $GOPATH/src/dummy so we are able to use dep
# to install all the packages of our dep lock file
COPY Gopkg.toml ${GOPATH}/src/dummy/Gopkg.toml
COPY Gopkg.lock ${GOPATH}/src/dummy/Gopkg.lock
RUN cd ${GOPATH}/src/dummy && \
dep ensure -vendor-only && \
mv vendor/* ${GOPATH}/src/ && \
rmdir vendor
# Perform the build
WORKDIR /go/src/github.com/argoproj/argo-cd
COPY . .
ARG MAKE_TARGET="cli server controller repo-server argocd-util"
RUN make ${MAKE_TARGET}
####################################################################################################
# Final image
####################################################################################################
FROM debian:9.5-slim
RUN groupadd -g 999 argocd && \
useradd -r -u 999 -g argocd argocd && \
mkdir -p /home/argocd && \
chown argocd:argocd /home/argocd && \
apt-get update && \
apt-get install -y git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY --from=builder /usr/local/bin/ks /usr/local/bin/ks
COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/kubectl
COPY --from=builder /usr/local/bin/kustomize /usr/local/bin/kustomize
COPY --from=builder /usr/local/bin/aws-iam-authenticator /usr/local/bin/aws-iam-authenticator
# workaround ksonnet issue https://github.com/ksonnet/ksonnet/issues/298
ENV USER=argocd
COPY --from=argocd-build /go/src/github.com/argoproj/argo-cd/dist/* /usr/local/bin/
# Symlink argocd binaries under root for backwards compatibility that expect it under /
RUN ln -s /usr/local/bin/argocd /argocd && \
ln -s /usr/local/bin/argocd-server /argocd-server && \
ln -s /usr/local/bin/argocd-util /argocd-util && \
ln -s /usr/local/bin/argocd-application-controller /argocd-application-controller && \
ln -s /usr/local/bin/argocd-repo-server /argocd-repo-server
USER argocd
RUN helm init --client-only
WORKDIR /home/argocd
ARG BINARY
CMD ${BINARY}

View File

@@ -1,88 +0,0 @@
FROM debian:9.4 as builder
RUN apt-get update && apt-get install -y \
git \
make \
wget \
gcc \
zip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Install go
ENV GO_VERSION 1.10.3
ENV GO_ARCH amd64
ENV GOPATH /root/go
ENV PATH ${GOPATH}/bin:/usr/local/go/bin:${PATH}
RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
rm /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz
# Install protoc, dep, packr
ENV PROTOBUF_VERSION 3.5.1
RUN cd /usr/local && \
wget https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip && \
unzip protoc-*.zip && \
wget https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 -O /usr/local/bin/dep && \
chmod +x /usr/local/bin/dep && \
wget https://github.com/gobuffalo/packr/releases/download/v1.11.0/packr_1.11.0_linux_amd64.tar.gz && \
tar -vxf packr*.tar.gz -C /tmp/ && \
mv /tmp/packr /usr/local/bin/packr
# A dummy directory is created under $GOPATH/src/dummy so we are able to use dep
# to install all the packages of our dep lock file
COPY Gopkg.toml ${GOPATH}/src/dummy/Gopkg.toml
COPY Gopkg.lock ${GOPATH}/src/dummy/Gopkg.lock
RUN cd ${GOPATH}/src/dummy && \
dep ensure -vendor-only && \
mv vendor/* ${GOPATH}/src/ && \
rmdir vendor
# Perform the build
WORKDIR /root/go/src/github.com/argoproj/argo-cd
COPY . .
ARG MAKE_TARGET="cli server controller repo-server argocd-util"
RUN make ${MAKE_TARGET}
##############################################################
# This stage will pull in or build any CLI tooling we need for our final image
FROM golang:1.10 as cli-tooling
# NOTE: we frequently switch between tip of master ksonnet vs. official builds. Comment/uncomment
# the corresponding section to switch between the two options:
# Option 1: build ksonnet ourselves
#RUN go get -v -u github.com/ksonnet/ksonnet && mv ${GOPATH}/bin/ksonnet /ks
# Option 2: use official tagged ksonnet release
env KSONNET_VERSION=0.11.0
RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION}/ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
tar -C /tmp/ -xf ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
mv /tmp/ks_${KSONNET_VERSION}_linux_amd64/ks /ks
RUN curl -o /kubectl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
chmod +x /kubectl
env HELM_VERSION=2.9.1
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
mv /tmp/linux-amd64/helm /helm
##############################################################
FROM debian:9.3
RUN apt-get update && apt-get install -y git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY --from=cli-tooling /ks /usr/local/bin/ks
COPY --from=cli-tooling /helm /usr/local/bin/helm
COPY --from=cli-tooling /kubectl /usr/local/bin/kubectl
# workaround ksonnet issue https://github.com/ksonnet/ksonnet/issues/298
ENV USER=root
COPY --from=builder /root/go/src/github.com/argoproj/argo-cd/dist/* /
ARG BINARY
CMD /${BINARY}

View File

@@ -1,28 +0,0 @@
FROM golang:1.10.3
WORKDIR /tmp
RUN curl -O https://get.docker.com/builds/Linux/x86_64/docker-1.13.1.tgz && \
tar -xzf docker-1.13.1.tgz && \
mv docker/docker /usr/local/bin/docker && \
rm -rf ./docker && \
go get -u github.com/golang/dep/cmd/dep && \
go get -u gopkg.in/alecthomas/gometalinter.v2 && \
gometalinter.v2 --install
# Install kubectl
RUN curl -o /kubectl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
chmod +x /kubectl && mv /kubectl /usr/local/bin/kubectl
# Install ksonnet
env KSONNET_VERSION=0.11.0
RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION}/ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
tar -C /tmp/ -xf ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
mv /tmp/ks_${KSONNET_VERSION}_linux_amd64/ks /usr/local/bin/ks && \
rm -rf /tmp/ks_${KSONNET_VERSION}
# Install helm
env HELM_VERSION=2.9.1
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
mv /tmp/linux-amd64/helm /usr/local/bin/helm

795
Gopkg.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +1,63 @@
# Packages should only be added to the following list when we use them *outside* of our go code.
# (e.g. we want to build the binary to invoke as part of the build process, such as in
# generate-proto.sh). Normal use of golang packages should be added via `dep ensure`, and pinned
# with a [[constraint]] or [[override]] when version is important.
required = [
"github.com/golang/protobuf/protoc-gen-go",
"github.com/gogo/protobuf/protoc-gen-gofast",
"github.com/gogo/protobuf/protoc-gen-gogofast",
"golang.org/x/sync/errgroup",
"k8s.io/code-generator/cmd/go-to-protobuf",
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway",
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger",
"github.com/golang/protobuf/protoc-gen-go",
"golang.org/x/sync/errgroup",
]
[[constraint]]
name = "google.golang.org/grpc"
version = "1.9.2"
version = "1.15.0"
[[constraint]]
name = "github.com/gogo/protobuf"
version = "1.1.1"
# override github.com/grpc-ecosystem/go-grpc-middleware's constraint on master
[[override]]
name = "github.com/golang/protobuf"
version = "1.2.0"
[[constraint]]
name = "github.com/grpc-ecosystem/grpc-gateway"
version = "v1.3.1"
# override argo outdated dependency
[[override]]
branch = "release-1.10"
name = "k8s.io/api"
# prometheus does not believe in semversioning yet
[[constraint]]
name = "github.com/prometheus/client_golang"
revision = "7858729281ec582767b20e0d696b6041d995d5e0"
[[override]]
branch = "release-1.10"
name = "k8s.io/apimachinery"
[[constraint]]
branch = "release-1.12"
name = "k8s.io/api"
[[constraint]]
name = "k8s.io/apiextensions-apiserver"
branch = "release-1.10"
branch = "release-1.12"
[[constraint]]
branch = "release-1.10"
branch = "release-1.12"
name = "k8s.io/code-generator"
[[override]]
branch = "release-7.0"
[[constraint]]
branch = "release-9.0"
name = "k8s.io/client-go"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.1"
[[constraint]]
name = "github.com/ksonnet/ksonnet"
version = "v0.11.0"
version = "1.2.2"
[[constraint]]
name = "github.com/gobuffalo/packr"
version = "v1.11.0"
# override ksonnet's logrus dependency
[[override]]
name = "github.com/sirupsen/logrus"
revision = "ea8897e79973357ba785ac2533559a6297e83c44"
[[constraint]]
branch = "master"
name = "github.com/argoproj/pkg"

View File

@@ -62,25 +62,24 @@ cli: clean-debug
.PHONY: cli-linux
cli-linux: clean-debug
docker build --iidfile /tmp/argocd-linux-id --target builder --build-arg MAKE_TARGET="cli IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-linux-amd64" -f Dockerfile-argocd .
docker build --iidfile /tmp/argocd-linux-id --target argocd-build --build-arg MAKE_TARGET="cli IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-linux-amd64" .
docker create --name tmp-argocd-linux `cat /tmp/argocd-linux-id`
docker cp tmp-argocd-linux:/root/go/src/github.com/argoproj/argo-cd/dist/argocd-linux-amd64 dist/
docker cp tmp-argocd-linux:/go/src/github.com/argoproj/argo-cd/dist/argocd-linux-amd64 dist/
docker rm tmp-argocd-linux
.PHONY: cli-darwin
cli-darwin: clean-debug
docker build --iidfile /tmp/argocd-darwin-id --target builder --build-arg MAKE_TARGET="cli GOOS=darwin IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-darwin-amd64" -f Dockerfile-argocd .
docker build --iidfile /tmp/argocd-darwin-id --target argocd-build --build-arg MAKE_TARGET="cli GOOS=darwin IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-darwin-amd64" .
docker create --name tmp-argocd-darwin `cat /tmp/argocd-darwin-id`
docker cp tmp-argocd-darwin:/root/go/src/github.com/argoproj/argo-cd/dist/argocd-darwin-amd64 dist/
docker cp tmp-argocd-darwin:/go/src/github.com/argoproj/argo-cd/dist/argocd-darwin-amd64 dist/
docker rm tmp-argocd-darwin
.PHONY: argocd-util
argocd-util: clean-debug
CGO_ENABLED=0 go build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/argocd-util ./cmd/argocd-util
.PHONY: install-manifest
install-manifest:
if [ "${IMAGE_NAMESPACE}" = "" ] ; then echo "IMAGE_NAMESPACE must be set to build install manifest" ; exit 1 ; fi
.PHONY: manifests
manifests:
./hack/update-manifests.sh
.PHONY: server
@@ -89,7 +88,7 @@ server: clean-debug
.PHONY: server-image
server-image:
docker build --build-arg BINARY=argocd-server -t $(IMAGE_PREFIX)argocd-server:$(IMAGE_TAG) -f Dockerfile-argocd .
docker build --build-arg BINARY=argocd-server -t $(IMAGE_PREFIX)argocd-server:$(IMAGE_TAG) .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-server:$(IMAGE_TAG) ; fi
.PHONY: repo-server
@@ -98,7 +97,7 @@ repo-server:
.PHONY: repo-server-image
repo-server-image:
docker build --build-arg BINARY=argocd-repo-server -t $(IMAGE_PREFIX)argocd-repo-server:$(IMAGE_TAG) -f Dockerfile-argocd .
docker build --build-arg BINARY=argocd-repo-server -t $(IMAGE_PREFIX)argocd-repo-server:$(IMAGE_TAG) .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-repo-server:$(IMAGE_TAG) ; fi
.PHONY: controller
@@ -107,17 +106,17 @@ controller:
.PHONY: controller-image
controller-image:
docker build --build-arg BINARY=argocd-application-controller -t $(IMAGE_PREFIX)argocd-application-controller:$(IMAGE_TAG) -f Dockerfile-argocd .
docker build --build-arg BINARY=argocd-application-controller -t $(IMAGE_PREFIX)argocd-application-controller:$(IMAGE_TAG) .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-application-controller:$(IMAGE_TAG) ; fi
.PHONY: cli-image
cli-image:
docker build --build-arg BINARY=argocd -t $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) -f Dockerfile-argocd .
docker build --build-arg BINARY=argocd -t $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) ; fi
.PHONY: builder-image
builder-image:
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) -f Dockerfile-ci-builder .
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
.PHONY: lint
lint:
@@ -127,6 +126,11 @@ lint:
test:
go test -v `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
.PHONY: test-coverage
test-coverage:
go test -v -covermode=count -coverprofile=coverage.out `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
@if [ "$(COVERALLS_TOKEN)" != "" ] ; then goveralls -ignore `find . -name '*.pb*.go' | grep -v vendor/ | sed 's!^./!!' | paste -d, -s -` -coverprofile=coverage.out -service=argo-ci -repotoken "$(COVERALLS_TOKEN)"; else echo 'No COVERALLS_TOKEN env var specified. Skipping submission to Coveralls.io'; fi
.PHONY: test-e2e
test-e2e:
go test -v -failfast -timeout 20m ./test/e2e
@@ -144,9 +148,10 @@ clean: clean-debug
precheckin: test lint
.PHONY: release-precheck
release-precheck: install-manifest
release-precheck: manifests
@if [ "$(GIT_TREE_STATE)" != "clean" ]; then echo 'git tree state is $(GIT_TREE_STATE)' ; exit 1; fi
@if [ -z "$(GIT_TAG)" ]; then echo 'commit must be tagged to perform release' ; exit 1; fi
@if [ "$(GIT_TAG)" != "v`cat VERSION`" ]; then echo 'VERSION does not match git tag'; exit 1; fi
.PHONY: release
release: release-precheck precheckin cli-darwin cli-linux server-image controller-image repo-server-image cli-image

View File

@@ -1,3 +1,4 @@
[![Coverage Status](https://coveralls.io/repos/github/argoproj/argo-cd/badge.svg?branch=master)](https://coveralls.io/github/argoproj/argo-cd?branch=master)
# Argo CD - Declarative Continuous Delivery for Kubernetes
@@ -22,14 +23,21 @@ is provided for additional features.
Argo CD follows the **GitOps** pattern of using git repositories as the source of truth for defining
the desired application state. Kubernetes manifests can be specified in several ways:
* [ksonnet](https://ksonnet.io) applications
* [kustomize](https://kustomize.io) applications
* [helm](https://helm.sh) charts
* Simple directory of YAML/json manifests
* Plain directory of YAML/json manifests
Argo CD automates the deployment of the desired application states in the specified target environments.
Application deployments can track updates to branches, tags, or pinned to a specific version of
manifests at a git commit. See [tracking strategies](docs/tracking_strategies.md) for additional
details about the different tracking strategies available.
For a quick 10 minute overview of ArgoCD, check out the demo presented to the Sig Apps community
meeting:
[![Alt text](https://img.youtube.com/vi/aWDIQMbp1cc/0.jpg)](https://youtu.be/aWDIQMbp1cc?t=1m4s)
## Architecture
![Argo CD Architecture](docs/argocd_architecture.png)
@@ -47,6 +55,7 @@ For additional details, see [architecture overview](docs/architecture.md).
## Features
* Automated deployment of applications to specified target environments
* Flexibility in support for multiple config management tools (Ksonnet, Kustomize, Helm, plain-YAML)
* Continuous monitoring of deployed applications
* Automated or manual syncing of applications to its desired state
* Web and CLI based visualization of applications and differences between live vs. desired state
@@ -57,13 +66,11 @@ For additional details, see [architecture overview](docs/architecture.md).
* PreSync, Sync, PostSync hooks to support complex application rollouts (e.g.blue/green & canary upgrades)
* Audit trails for application events and API calls
* Parameter overrides for overriding ksonnet/helm parameters in git
* Service account/access key management for CI pipelines
## Development Status
* Argo CD is being used in production to deploy SaaS services at Intuit
## Roadmap
* Auto-sync toggle to directly apply git state changes to live state
* Service account/access key management for CI pipelines
* Support for additional config management tools (Kustomize?)
* Revamped UI, and feature parity with CLI
* Customizable application actions

View File

@@ -1 +1 @@
0.7.1
0.10.6

View File

@@ -12,6 +12,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
// load the gcp plugin (required to authenticate against GKE clusters).
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
// load the oidc plugin (required to authenticate with OpenID Connect).
@@ -23,7 +24,6 @@ import (
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/stats"
)
@@ -66,25 +66,14 @@ func newCommand() *cobra.Command {
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
// TODO (amatyushentsev): Use config map to store controller configuration
controllerConfig := controller.ApplicationControllerConfig{
Namespace: namespace,
InstanceID: "",
}
db := db.NewDB(namespace, kubeClient)
resyncDuration := time.Duration(appResyncPeriod) * time.Second
repoClientset := reposerver.NewRepositoryServerClientset(repoServerAddress)
appStateManager := controller.NewAppStateManager(db, appClient, repoClientset, namespace)
appController := controller.NewApplicationController(
namespace,
kubeClient,
appClient,
repoClientset,
db,
appStateManager,
resyncDuration,
&controllerConfig)
resyncDuration)
secretController := controller.NewSecretController(kubeClient, repoClientset, resyncDuration, namespace)
ctx, cancel := context.WithCancel(context.Background())

View File

@@ -17,6 +17,7 @@ import (
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/stats"
"github.com/argoproj/argo-cd/util/tls"
)
const (
@@ -27,7 +28,8 @@ const (
func newCommand() *cobra.Command {
var (
logLevel string
logLevel string
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
)
var command = cobra.Command{
Use: cliName,
@@ -37,7 +39,11 @@ func newCommand() *cobra.Command {
errors.CheckError(err)
log.SetLevel(level)
server := reposerver.NewServer(git.NewFactory(), newCache())
tlsConfigCustomizer, err := tlsConfigCustomizerSrc()
errors.CheckError(err)
server, err := reposerver.NewServer(git.NewFactory(), newCache(), tlsConfigCustomizer)
errors.CheckError(err)
grpc := server.CreateGRPC()
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
errors.CheckError(err)
@@ -57,6 +63,7 @@ func newCommand() *cobra.Command {
}
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
return &command
}

View File

@@ -17,18 +17,20 @@ import (
"github.com/argoproj/argo-cd/server"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/stats"
"github.com/argoproj/argo-cd/util/tls"
)
// NewCommand returns a new instance of an argocd command
func NewCommand() *cobra.Command {
var (
insecure bool
logLevel string
glogLevel int
clientConfig clientcmd.ClientConfig
staticAssetsDir string
repoServerAddress string
disableAuth bool
insecure bool
logLevel string
glogLevel int
clientConfig clientcmd.ClientConfig
staticAssetsDir string
repoServerAddress string
disableAuth bool
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
)
var command = &cobra.Command{
Use: cliName,
@@ -50,18 +52,22 @@ func NewCommand() *cobra.Command {
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
tlsConfigCustomizer, err := tlsConfigCustomizerSrc()
errors.CheckError(err)
kubeclientset := kubernetes.NewForConfigOrDie(config)
appclientset := appclientset.NewForConfigOrDie(config)
repoclientset := reposerver.NewRepositoryServerClientset(repoServerAddress)
argoCDOpts := server.ArgoCDServerOpts{
Insecure: insecure,
Namespace: namespace,
StaticAssetsDir: staticAssetsDir,
KubeClientset: kubeclientset,
AppClientset: appclientset,
RepoClientset: repoclientset,
DisableAuth: disableAuth,
Insecure: insecure,
Namespace: namespace,
StaticAssetsDir: staticAssetsDir,
KubeClientset: kubeclientset,
AppClientset: appclientset,
RepoClientset: repoclientset,
DisableAuth: disableAuth,
TLSConfigCustomizer: tlsConfigCustomizer,
}
stats.RegisterStackDumper()
@@ -86,5 +92,6 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&repoServerAddress, "repo-server", "localhost:8081", "Repo server address.")
command.Flags().BoolVar(&disableAuth, "disable-auth", false, "Disable client authentication")
command.AddCommand(cli.NewVersionCmd(cliName))
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(command)
return command
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/dex"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/settings"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
@@ -59,6 +60,7 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewImportCommand())
command.AddCommand(NewExportCommand())
command.AddCommand(NewSettingsCommand())
command.AddCommand(NewClusterConfig())
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
return command
@@ -366,7 +368,8 @@ func NewSettingsCommand() *cobra.Command {
errors.CheckError(err)
settingsMgr := settings.NewSettingsManager(kubeclientset, namespace)
_ = settings.UpdateSettings(superuserPassword, settingsMgr, updateSignature, updateSuperuser, namespace)
_, err = settings.UpdateSettings(superuserPassword, settingsMgr, updateSignature, updateSuperuser, namespace)
errors.CheckError(err)
},
}
command.Flags().BoolVar(&updateSuperuser, "update-superuser", false, "force updating the superuser password")
@@ -376,6 +379,42 @@ func NewSettingsCommand() *cobra.Command {
return command
}
// NewClusterConfig returns a new instance of `argocd-util cluster-kubeconfig` command
func NewClusterConfig() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
)
var command = &cobra.Command{
Use: "cluster-kubeconfig CLUSTER_URL OUTPUT_PATH",
Short: "Generates kubeconfig for the specified cluster",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
serverUrl := args[0]
output := args[1]
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, wasSpecified, err := clientConfig.Namespace()
errors.CheckError(err)
if !(wasSpecified) {
namespace = "argocd"
}
kubeclientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
cluster, err := db.NewDB(namespace, kubeclientset).GetCluster(context.Background(), serverUrl)
errors.CheckError(err)
err = kube.WriteKubeConfig(cluster.RESTConfig(), namespace, output)
errors.CheckError(err)
},
}
clientConfig = cli.AddKubectlFlagsToCmd(command)
return command
}
func main() {
if err := NewCommand().Execute(); err != nil {
fmt.Println(err)

View File

@@ -50,7 +50,9 @@ func NewAccountUpdatePasswordCommand(clientOpts *argocdclient.ClientOptions) *co
fmt.Print("\n")
}
if newPassword == "" {
newPassword = settings.ReadAndConfirmPassword()
var err error
newPassword, err = settings.ReadAndConfirmPassword()
errors.CheckError(err)
}
updatePasswordRequest := account.UpdatePasswordRequest{

View File

@@ -7,6 +7,7 @@ import (
"io"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"text/tabwriter"
@@ -118,6 +119,18 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
if len(appOpts.valuesFiles) > 0 {
app.Spec.Source.ValuesFiles = appOpts.valuesFiles
}
switch appOpts.syncPolicy {
case "automated":
app.Spec.SyncPolicy = &argoappv1.SyncPolicy{
Automated: &argoappv1.SyncPolicyAutomated{
Prune: appOpts.autoPrune,
},
}
case "none", "":
app.Spec.SyncPolicy = nil
default:
log.Fatalf("Invalid sync-policy: %s", appOpts.syncPolicy)
}
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
appCreateRequest := application.ApplicationCreateRequest{
@@ -181,6 +194,16 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
if len(app.Spec.Source.ValuesFiles) > 0 {
fmt.Printf(printOpFmtStr, "Helm Values:", strings.Join(app.Spec.Source.ValuesFiles, ","))
}
var syncPolicy string
if app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.Automated != nil {
syncPolicy = "Automated"
if app.Spec.SyncPolicy.Automated.Prune {
syncPolicy += " (Prune)"
}
} else {
syncPolicy = "<none>"
}
fmt.Printf(printOpFmtStr, "Sync Policy:", syncPolicy)
if len(app.Status.Conditions) > 0 {
fmt.Println()
@@ -312,6 +335,17 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
app.Spec.Destination.Namespace = appOpts.destNamespace
case "project":
app.Spec.Project = appOpts.project
case "sync-policy":
switch appOpts.syncPolicy {
case "automated":
app.Spec.SyncPolicy = &argoappv1.SyncPolicy{
Automated: &argoappv1.SyncPolicyAutomated{},
}
case "none":
app.Spec.SyncPolicy = nil
default:
log.Fatalf("Invalid sync-policy: %s", appOpts.syncPolicy)
}
}
})
if visited == 0 {
@@ -319,6 +353,13 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
c.HelpFunc()(c, args)
os.Exit(1)
}
if c.Flags().Changed("auto-prune") {
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
log.Fatal("Cannot set --auto-prune: application not configured with automatic sync")
}
app.Spec.SyncPolicy.Automated.Prune = appOpts.autoPrune
}
setParameterOverrides(app, appOpts.parameters)
oldOverrides := app.Spec.Source.ComponentParameterOverrides
updatedSpec, err := appIf.UpdateSpec(context.Background(), &application.ApplicationUpdateSpecRequest{
@@ -357,6 +398,8 @@ type appOptions struct {
parameters []string
valuesFiles []string
project string
syncPolicy string
autoPrune bool
}
func addAppFlags(command *cobra.Command, opts *appOptions) {
@@ -369,6 +412,8 @@ func addAppFlags(command *cobra.Command, opts *appOptions) {
command.Flags().StringArrayVarP(&opts.parameters, "parameter", "p", []string{}, "set a parameter override (e.g. -p guestbook=image=example/guestbook:latest)")
command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Helm values file(s) to use")
command.Flags().StringVar(&opts.project, "project", "", "Application project name")
command.Flags().StringVar(&opts.syncPolicy, "sync-policy", "", "Set the sync policy (one of: automated, none)")
command.Flags().BoolVar(&opts.autoPrune, "auto-prune", false, "Set automatic pruning when sync is automated")
}
// NewApplicationUnsetCommand returns a new instance of an `argocd app unset` command
@@ -657,7 +702,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
_, err := waitOnApplicationStatus(appIf, appName, timeout, watchSync, watchHealth, watchOperations)
_, err := waitOnApplicationStatus(appIf, appName, timeout, watchSync, watchHealth, watchOperations, nil)
errors.CheckError(err)
},
}
@@ -733,8 +778,6 @@ func printAppResources(w io.Writer, app *argoappv1.Application, showOperation bo
if opState != nil {
if opState.SyncResult != nil {
syncRes = opState.SyncResult
} else if opState.RollbackResult != nil {
syncRes = opState.RollbackResult
}
}
if syncRes != nil {
@@ -778,12 +821,17 @@ func printAppResources(w io.Writer, app *argoappv1.Application, showOperation bo
// NewApplicationSyncCommand returns a new instance of an `argocd app sync` command
func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
revision string
prune bool
dryRun bool
timeout uint
strategy string
force bool
revision string
resources *[]string
prune bool
dryRun bool
timeout uint
strategy string
force bool
)
const (
resourceFieldDelimiter = ":"
resourceFieldCount = 3
)
var command = &cobra.Command{
Use: "sync APPNAME",
@@ -796,11 +844,28 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
appName := args[0]
var syncResources []argoappv1.SyncOperationResource
if resources != nil {
syncResources = []argoappv1.SyncOperationResource{}
for _, r := range *resources {
fields := strings.Split(r, resourceFieldDelimiter)
if len(fields) != resourceFieldCount {
log.Fatalf("Resource should have GROUP%sKIND%sNAME, but instead got: %s", resourceFieldDelimiter, resourceFieldDelimiter, r)
}
rsrc := argoappv1.SyncOperationResource{
Group: fields[0],
Kind: fields[1],
Name: fields[2],
}
syncResources = append(syncResources, rsrc)
}
}
syncReq := application.ApplicationSyncRequest{
Name: &appName,
DryRun: dryRun,
Revision: revision,
Prune: prune,
Name: &appName,
DryRun: dryRun,
Revision: revision,
Resources: syncResources,
Prune: prune,
}
switch strategy {
case "apply":
@@ -816,7 +881,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
_, err := appIf.Sync(ctx, &syncReq)
errors.CheckError(err)
app, err := waitOnApplicationStatus(appIf, appName, timeout, false, false, true)
app, err := waitOnApplicationStatus(appIf, appName, timeout, false, false, true, syncResources)
errors.CheckError(err)
pruningRequired := 0
@@ -837,6 +902,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().BoolVar(&dryRun, "dry-run", false, "Preview apply without affecting cluster")
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
command.Flags().StringVar(&revision, "revision", "", "Sync to a specific revision. Preserves parameter overrides")
resources = command.Flags().StringArray("resource", nil, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
command.Flags().StringVar(&strategy, "strategy", "", "Sync strategy (one of: apply|hook)")
command.Flags().BoolVar(&force, "force", false, "Use a force apply")
@@ -845,23 +911,22 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
// ResourceState tracks the state of a resource when waiting on an application status.
type resourceState struct {
Kind string
Name string
PrevState string
Fields map[string]string
Updated bool
Kind string
Name string
Status string
Health string
Hook string
Message string
}
func newResourceState(kind, name, status, healthStatus, resType, message string) *resourceState {
func newResourceState(kind, name, status, health, hook, message string) *resourceState {
return &resourceState{
Kind: kind,
Name: name,
Fields: map[string]string{
"status": status,
"healthStatus": healthStatus,
"type": resType,
"message": message,
},
Kind: kind,
Name: name,
Status: status,
Health: health,
Hook: hook,
Message: message,
}
}
@@ -870,47 +935,107 @@ func (rs *resourceState) Key() string {
return fmt.Sprintf("%s/%s", rs.Kind, rs.Name)
}
// Merge merges the new state into the previous state, returning whether the
// new state contains any additional keys or different values from the old state.
func (rs *resourceState) Merge() bool {
if out := rs.String(); out != rs.PrevState {
rs.PrevState = out
return true
}
return false
}
func (rs *resourceState) String() string {
return fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s", rs.Kind, rs.Name, rs.Fields["status"], rs.Fields["healthStatus"], rs.Fields["type"], rs.Fields["message"])
return fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s", rs.Kind, rs.Name, rs.Status, rs.Health, rs.Hook, rs.Message)
}
// Update a resourceState with any different contents from another resourceState.
// Merge merges the new state with any different contents from another resourceState.
// Blank fields in the receiver state will be updated to non-blank.
// Non-blank fields in the receiver state will never be updated to blank.
func (rs *resourceState) Update(newState *resourceState) {
for k, v := range newState.Fields {
if v != "" {
rs.Fields[k] = v
// Returns whether or not any keys were updated.
func (rs *resourceState) Merge(newState *resourceState) bool {
updated := false
for _, field := range []string{"Status", "Health", "Hook", "Message"} {
v := reflect.ValueOf(rs).Elem().FieldByName(field)
currVal := v.String()
newVal := reflect.ValueOf(newState).Elem().FieldByName(field).String()
if newVal != "" && currVal != newVal {
v.SetString(newVal)
updated = true
}
}
return updated
}
func waitOnApplicationStatus(appClient application.ApplicationServiceClient, appName string, timeout uint, watchSync, watchHealth, watchOperations bool) (*argoappv1.Application, error) {
func calculateResourceStates(app *argoappv1.Application, syncResources []argoappv1.SyncOperationResource) map[string]*resourceState {
resStates := make(map[string]*resourceState)
for _, res := range app.Status.ComparisonResult.Resources {
obj, err := argoappv1.UnmarshalToUnstructured(res.TargetState)
errors.CheckError(err)
if obj == nil {
obj, err = argoappv1.UnmarshalToUnstructured(res.LiveState)
errors.CheckError(err)
}
if syncResources != nil && !argo.ContainsSyncResource(obj, syncResources) {
continue
}
newState := newResourceState(obj.GetKind(), obj.GetName(), string(res.Status), res.Health.Status, "", "")
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
var opResult *argoappv1.SyncOperationResult
if app.Status.OperationState != nil {
if app.Status.OperationState.SyncResult != nil {
opResult = app.Status.OperationState.SyncResult
}
}
if opResult == nil {
return resStates
}
for _, hook := range opResult.Hooks {
newState := newResourceState(hook.Kind, hook.Name, string(hook.Status), "", string(hook.Type), hook.Message)
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
for _, res := range opResult.Resources {
newState := newResourceState(res.Kind, res.Name, "", "", "", res.Message)
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
return resStates
}
func waitOnApplicationStatus(appClient application.ApplicationServiceClient, appName string, timeout uint, watchSync bool, watchHealth bool, watchOperation bool, syncResources []argoappv1.SyncOperationResource) (*argoappv1.Application, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
printFinalStatus := func() {
// get refreshed app before printing to show accurate sync/health status
app, err := appClient.Get(ctx, &application.ApplicationQuery{Name: &appName, Refresh: true})
errors.CheckError(err)
// refresh controls whether or not we refresh the app before printing the final status.
// We only want to do this when an operation is in progress, since operations are the only
// time when the sync status lags behind when an operation completes
refresh := false
fmt.Printf(printOpFmtStr, "Application:", appName)
printOperationResult(app.Status.OperationState)
printFinalStatus := func(app *argoappv1.Application) {
var err error
if refresh {
app, err = appClient.Get(context.Background(), &application.ApplicationQuery{Name: &appName, Refresh: true})
errors.CheckError(err)
}
fmt.Println()
fmt.Printf(printOpFmtStr, "Application:", app.Name)
if watchOperation {
printOperationResult(app.Status.OperationState)
}
if len(app.Status.ComparisonResult.Resources) > 0 {
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
printAppResources(w, app, true)
w := tabwriter.NewWriter(os.Stdout, 5, 0, 2, ' ', 0)
printAppResources(w, app, watchOperation)
_ = w.Flush()
}
}
@@ -918,89 +1043,47 @@ func waitOnApplicationStatus(appClient application.ApplicationServiceClient, app
if timeout != 0 {
time.AfterFunc(time.Duration(timeout)*time.Second, func() {
cancel()
printFinalStatus()
})
}
// print the initial components to format the tabwriter columns
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
w := tabwriter.NewWriter(os.Stdout, 5, 0, 2, ' ', 0)
fmt.Fprintln(w, "KIND\tNAME\tSTATUS\tHEALTH\tHOOK\tOPERATIONMSG")
_ = w.Flush()
prevStates := make(map[string]*resourceState)
conditionallyPrintOutput := func(w io.Writer, newState *resourceState) {
stateKey := newState.Key()
if prevState, found := prevStates[stateKey]; found {
prevState.Update(newState)
} else {
prevStates[stateKey] = newState
}
}
printCompResults := func(compResult *argoappv1.ComparisonResult) {
if compResult != nil {
for _, res := range compResult.Resources {
obj, err := argoappv1.UnmarshalToUnstructured(res.TargetState)
errors.CheckError(err)
if obj == nil {
obj, err = argoappv1.UnmarshalToUnstructured(res.LiveState)
errors.CheckError(err)
}
newState := newResourceState(obj.GetKind(), obj.GetName(), string(res.Status), res.Health.Status, "", "")
conditionallyPrintOutput(w, newState)
}
}
}
printOpResults := func(opResult *argoappv1.SyncOperationResult) {
if opResult != nil {
if opResult.Hooks != nil {
for _, hook := range opResult.Hooks {
newState := newResourceState(hook.Kind, hook.Name, string(hook.Status), "", string(hook.Type), hook.Message)
conditionallyPrintOutput(w, newState)
}
}
if opResult.Resources != nil {
for _, res := range opResult.Resources {
newState := newResourceState(res.Kind, res.Name, string(res.Status), "", "", res.Message)
conditionallyPrintOutput(w, newState)
}
}
}
}
appEventCh := watchApp(ctx, appClient, appName)
var app *argoappv1.Application
for appEvent := range appEventCh {
app := appEvent.Application
printCompResults(&app.Status.ComparisonResult)
if opState := app.Status.OperationState; opState != nil {
printOpResults(opState.SyncResult)
printOpResults(opState.RollbackResult)
app = &appEvent.Application
if app.Operation != nil {
refresh = true
}
for _, v := range prevStates {
if v.Merge() {
fmt.Fprintln(w, v)
}
}
_ = w.Flush()
// consider skipped checks successful
synced := !watchSync || app.Status.ComparisonResult.Status == argoappv1.ComparisonStatusSynced
healthy := !watchHealth || app.Status.Health.Status == argoappv1.HealthStatusHealthy
operational := !watchOperations || appEvent.Application.Operation == nil
operational := !watchOperation || appEvent.Application.Operation == nil
if len(app.Status.GetErrorConditions()) == 0 && synced && healthy && operational {
log.Printf("App %q matches desired state", appName)
printFinalStatus()
return &app, nil
printFinalStatus(app)
return app, nil
}
}
newStates := calculateResourceStates(app, syncResources)
for _, newState := range newStates {
var doPrint bool
stateKey := newState.Key()
if prevState, found := prevStates[stateKey]; found {
doPrint = prevState.Merge(newState)
} else {
prevStates[stateKey] = newState
doPrint = true
}
if doPrint {
fmt.Fprintln(w, prevStates[stateKey])
}
}
_ = w.Flush()
}
printFinalStatus(app)
return nil, fmt.Errorf("Timed out (%ds) waiting for app %q match desired state", timeout, appName)
}
@@ -1085,7 +1168,9 @@ func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra
for _, depInfo := range app.Status.History {
switch output {
case "wide":
paramStr := paramString(depInfo.Params)
manifest, err := appIf.GetManifests(context.Background(), &application.ApplicationManifestQuery{Name: &appName, Revision: depInfo.Revision})
errors.CheckError(err)
paramStr := paramString(manifest.GetParams())
fmt.Fprintf(w, "%d\t%s\t%s\t%s\n", depInfo.ID, depInfo.DeployedAt, depInfo.Revision, paramStr)
default:
fmt.Fprintf(w, "%d\t%s\t%s\n", depInfo.ID, depInfo.DeployedAt, depInfo.Revision)
@@ -1098,7 +1183,7 @@ func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra
return command
}
func paramString(params []argoappv1.ComponentParameter) string {
func paramString(params []*argoappv1.ComponentParameter) string {
if len(params) == 0 {
return ""
}
@@ -1149,7 +1234,7 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
})
errors.CheckError(err)
_, err = waitOnApplicationStatus(appIf, appName, timeout, false, false, true)
_, err = waitOnApplicationStatus(appIf, appName, timeout, false, false, true, nil)
errors.CheckError(err)
},
}
@@ -1164,8 +1249,6 @@ const defaultCheckTimeoutSeconds = 0
func printOperationResult(opState *argoappv1.OperationState) {
if opState.SyncResult != nil {
fmt.Printf(printOpFmtStr, "Operation:", "Sync")
} else if opState.RollbackResult != nil {
fmt.Printf(printOpFmtStr, "Operation:", "Rollback")
}
fmt.Printf(printOpFmtStr, "Phase:", opState.Phase)
fmt.Printf(printOpFmtStr, "Start:", opState.StartedAt)

View File

@@ -18,6 +18,7 @@ import (
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
@@ -43,8 +44,10 @@ func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientc
// NewClusterAddCommand returns a new instance of an `argocd cluster add` command
func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command {
var (
inCluster bool
upsert bool
inCluster bool
upsert bool
awsRoleArn string
awsClusterName string
)
var command = &cobra.Command{
Use: "add",
@@ -62,6 +65,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
if clstContext == nil {
log.Fatalf("Context %s does not exist in kubeconfig", args[0])
}
overrides := clientcmd.ConfigOverrides{
Context: *clstContext,
}
@@ -69,12 +73,23 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
// Install RBAC resources for managing the cluster
managerBearerToken := common.InstallClusterManagerRBAC(conf)
managerBearerToken := ""
var awsAuthConf *argoappv1.AWSAuthConfig
if awsClusterName != "" {
awsAuthConf = &argoappv1.AWSAuthConfig{
ClusterName: awsClusterName,
RoleARN: awsRoleArn,
}
} else {
// Install RBAC resources for managing the cluster
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
managerBearerToken, err = common.InstallClusterManagerRBAC(clientset)
errors.CheckError(err)
}
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
clst := NewCluster(args[0], conf, managerBearerToken)
clst := NewCluster(args[0], conf, managerBearerToken, awsAuthConf)
if inCluster {
clst.Server = common.KubernetesInternalAPIServerAddr
}
@@ -90,6 +105,8 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
command.PersistentFlags().StringVar(&pathOpts.LoadingRules.ExplicitPath, pathOpts.ExplicitFileFlag, pathOpts.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
command.Flags().BoolVar(&inCluster, "in-cluster", false, "Indicates ArgoCD resides inside this cluster and should connect using the internal k8s hostname (kubernetes.default.svc)")
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing cluster with the same name even if the spec differs")
command.Flags().StringVar(&awsClusterName, "aws-cluster-name", "", "AWS Cluster name if set then aws-iam-authenticator will be used to access cluster")
command.Flags().StringVar(&awsRoleArn, "aws-role-arn", "", "Optional AWS role arn. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.")
return command
}
@@ -132,7 +149,7 @@ func printKubeContexts(ca clientcmd.ConfigAccess) {
}
}
func NewCluster(name string, conf *rest.Config, managerBearerToken string) *argoappv1.Cluster {
func NewCluster(name string, conf *rest.Config, managerBearerToken string, awsAuthConf *argoappv1.AWSAuthConfig) *argoappv1.Cluster {
tlsClientConfig := argoappv1.TLSClientConfig{
Insecure: conf.TLSClientConfig.Insecure,
ServerName: conf.TLSClientConfig.ServerName,
@@ -161,6 +178,7 @@ func NewCluster(name string, conf *rest.Config, managerBearerToken string) *argo
Config: argoappv1.ClusterConfig{
BearerToken: managerBearerToken,
TLSClientConfig: tlsClientConfig,
AWSAuthConfig: awsAuthConf,
},
}
return &clst
@@ -202,9 +220,14 @@ func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
}
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
// clientset, err := kubernetes.NewForConfig(conf)
// errors.CheckError(err)
for _, clusterName := range args {
// TODO(jessesuen): find the right context and remove manager RBAC artifacts
// common.UninstallClusterManagerRBAC(conf)
// err := common.UninstallClusterManagerRBAC(clientset)
// errors.CheckError(err)
_, err := clusterIf.Delete(context.Background(), &cluster.ClusterQuery{Server: clusterName})
errors.CheckError(err)
}

View File

@@ -1,17 +1,20 @@
package commands
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"
"time"
timeutil "github.com/argoproj/pkg/time"
"github.com/dustin/go-humanize"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"strings"
"context"
"fmt"
"text/tabwriter"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
@@ -19,8 +22,11 @@ import (
"github.com/argoproj/argo-cd/server/project"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/git"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/apis/meta/v1"
projectutil "github.com/argoproj/argo-cd/util/project"
)
const (
policyTemplate = "p, proj:%s:%s, applications, %s, %s/%s, %s"
)
type projectOpts struct {
@@ -29,6 +35,12 @@ type projectOpts struct {
sources []string
}
type policyOpts struct {
action string
permission string
object string
}
func (opts *projectOpts) GetDestinations() []v1alpha1.ApplicationDestination {
destinations := make([]v1alpha1.ApplicationDestination, 0)
for _, destStr := range opts.destinations {
@@ -55,6 +67,7 @@ func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
os.Exit(1)
},
}
command.AddCommand(NewProjectRoleCommand(clientOpts))
command.AddCommand(NewProjectCreateCommand(clientOpts))
command.AddCommand(NewProjectDeleteCommand(clientOpts))
command.AddCommand(NewProjectListCommand(clientOpts))
@@ -63,14 +76,349 @@ func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
command.AddCommand(NewProjectRemoveDestinationCommand(clientOpts))
command.AddCommand(NewProjectAddSourceCommand(clientOpts))
command.AddCommand(NewProjectRemoveSourceCommand(clientOpts))
command.AddCommand(NewProjectAllowClusterResourceCommand(clientOpts))
command.AddCommand(NewProjectDenyClusterResourceCommand(clientOpts))
command.AddCommand(NewProjectAllowNamespaceResourceCommand(clientOpts))
command.AddCommand(NewProjectDenyNamespaceResourceCommand(clientOpts))
return command
}
func addProjFlags(command *cobra.Command, opts *projectOpts) {
command.Flags().StringVarP(&opts.description, "description", "", "desc", "Project description")
command.Flags().StringVarP(&opts.description, "description", "", "", "Project description")
command.Flags().StringArrayVarP(&opts.destinations, "dest", "d", []string{},
"Allowed deployment destination. Includes comma separated server url and namespace (e.g. https://192.168.99.100:8443,default")
command.Flags().StringArrayVarP(&opts.sources, "src", "s", []string{}, "Allowed deployment source repository URL.")
"Permitted destination server and namespace (e.g. https://192.168.99.100:8443,default)")
command.Flags().StringArrayVarP(&opts.sources, "src", "s", []string{}, "Permitted git source repository URL")
}
func addPolicyFlags(command *cobra.Command, opts *policyOpts) {
command.Flags().StringVarP(&opts.action, "action", "a", "", "Action to grant/deny permission on (e.g. get, create, list, update, delete)")
command.Flags().StringVarP(&opts.permission, "permission", "p", "allow", "Whether to allow or deny access to object with the action. This can only be 'allow' or 'deny'")
command.Flags().StringVarP(&opts.object, "object", "o", "", "Object within the project to grant/deny access. Use '*' for a wildcard. Will want access to '<project>/<object>'")
}
// NewProjectRoleCommand returns a new instance of the `argocd proj role` command
func NewProjectRoleCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
roleCommand := &cobra.Command{
Use: "role",
Short: "Manage a project's roles",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
}
roleCommand.AddCommand(NewProjectRoleListCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleGetCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleCreateCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleDeleteCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleCreateTokenCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleDeleteTokenCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleAddPolicyCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleRemovePolicyCommand(clientOpts))
return roleCommand
}
// NewProjectRoleAddPolicyCommand returns a new instance of an `argocd proj role add-policy` command
func NewProjectRoleAddPolicyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
opts policyOpts
)
var command = &cobra.Command{
Use: "add-policy PROJECT ROLE-NAME",
Short: "Add a policy to a project role",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
if len(opts.action) <= 0 {
log.Fatal("Action needs to longer than 0 characters")
}
if len(opts.object) <= 0 {
log.Fatal("Objects needs to longer than 0 characters")
}
projName := args[0]
roleName := args[1]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
errors.CheckError(err)
roleIndex, err := projectutil.GetRoleIndexByName(proj, roleName)
if err != nil {
log.Fatal(err)
}
role := proj.Spec.Roles[roleIndex]
policy := fmt.Sprintf(policyTemplate, proj.Name, role.Name, opts.action, proj.Name, opts.object, opts.permission)
proj.Spec.Roles[roleIndex].Policies = append(role.Policies, policy)
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
addPolicyFlags(command, &opts)
return command
}
// NewProjectRoleRemovePolicyCommand returns a new instance of an `argocd proj role remove-policy` command
func NewProjectRoleRemovePolicyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
opts policyOpts
)
var command = &cobra.Command{
Use: "remove-policy PROJECT ROLE-NAME",
Short: "Remove a policy from a role within a project",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
if opts.permission != "allow" && opts.permission != "deny" {
log.Fatal("Permission flag can only have the values 'allow' or 'deny'")
}
if len(opts.action) <= 0 {
log.Fatal("Action needs to longer than 0 characters")
}
if len(opts.object) <= 0 {
log.Fatal("Objects needs to longer than 0 characters")
}
projName := args[0]
roleName := args[1]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
errors.CheckError(err)
roleIndex, err := projectutil.GetRoleIndexByName(proj, roleName)
if err != nil {
log.Fatal(err)
}
role := proj.Spec.Roles[roleIndex]
policyToRemove := fmt.Sprintf(policyTemplate, proj.Name, role.Name, opts.action, proj.Name, opts.object, opts.permission)
duplicateIndex := -1
for i, policy := range role.Policies {
if policy == policyToRemove {
duplicateIndex = i
break
}
}
if duplicateIndex < 0 {
return
}
role.Policies[duplicateIndex] = role.Policies[len(role.Policies)-1]
proj.Spec.Roles[roleIndex].Policies = role.Policies[:len(role.Policies)-1]
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
addPolicyFlags(command, &opts)
return command
}
// NewProjectRoleCreateCommand returns a new instance of an `argocd proj role create` command
func NewProjectRoleCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
description string
)
var command = &cobra.Command{
Use: "create PROJECT ROLE-NAME",
Short: "Create a project role",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
roleName := args[1]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
errors.CheckError(err)
_, err = projectutil.GetRoleIndexByName(proj, roleName)
if err == nil {
return
}
proj.Spec.Roles = append(proj.Spec.Roles, v1alpha1.ProjectRole{Name: roleName, Description: description})
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
command.Flags().StringVarP(&description, "description", "", "", "Project description")
return command
}
// NewProjectRoleDeleteCommand returns a new instance of an `argocd proj role delete` command
func NewProjectRoleDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "delete PROJECT ROLE-NAME",
Short: "Delete a project role",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
roleName := args[1]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
errors.CheckError(err)
index, err := projectutil.GetRoleIndexByName(proj, roleName)
if err != nil {
return
}
proj.Spec.Roles[index] = proj.Spec.Roles[len(proj.Spec.Roles)-1]
proj.Spec.Roles = proj.Spec.Roles[:len(proj.Spec.Roles)-1]
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
return command
}
// NewProjectRoleCreateTokenCommand returns a new instance of an `argocd proj role create-token` command
func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
expiresIn string
)
var command = &cobra.Command{
Use: "create-token PROJECT ROLE-NAME",
Short: "Create a project token",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
roleName := args[1]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
duration, err := timeutil.ParseDuration(expiresIn)
errors.CheckError(err)
token, err := projIf.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projName, Role: roleName, ExpiresIn: int64(duration.Seconds())})
errors.CheckError(err)
fmt.Println(token.Token)
},
}
command.Flags().StringVarP(&expiresIn, "expires-in", "e", "0s", "Duration before the token will expire. (Default: No expiration)")
return command
}
// NewProjectRoleDeleteTokenCommand returns a new instance of an `argocd proj role delete-token` command
func NewProjectRoleDeleteTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "delete-token PROJECT ROLE-NAME ISSUED-AT",
Short: "Delete a project token",
Run: func(c *cobra.Command, args []string) {
if len(args) != 3 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
roleName := args[1]
issuedAt, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
log.Fatal(err)
}
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
_, err = projIf.DeleteToken(context.Background(), &project.ProjectTokenDeleteRequest{Project: projName, Role: roleName, Iat: issuedAt})
errors.CheckError(err)
},
}
return command
}
// NewProjectRoleListCommand returns a new instance of an `argocd proj roles list` command
func NewProjectRoleListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "list PROJECT",
Short: "List all the roles in a project",
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
project, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "ROLE-NAME\tDESCRIPTION\n")
for _, role := range project.Spec.Roles {
fmt.Fprintf(w, "%s\t%s\n", role.Name, role.Description)
}
_ = w.Flush()
},
}
return command
}
// NewProjectRoleGetCommand returns a new instance of an `argocd proj roles get` command
func NewProjectRoleGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "get PROJECT ROLE-NAME",
Short: "Get the details of a specific role",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
roleName := args[1]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
project, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
errors.CheckError(err)
index, err := projectutil.GetRoleIndexByName(project, roleName)
errors.CheckError(err)
role := project.Spec.Roles[index]
printRoleFmtStr := "%-15s%s\n"
fmt.Printf(printRoleFmtStr, "Role Name:", roleName)
fmt.Printf(printRoleFmtStr, "Description:", role.Description)
fmt.Printf("Policies:\n")
fmt.Printf("%s\n", project.ProjectPoliciesString())
fmt.Printf("JWT Tokens:\n")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "ID\tISSUED-AT\tEXPIRES-AT\n")
for _, token := range role.JWTTokens {
expiresAt := "<none>"
if token.ExpiresAt > 0 {
expiresAt = humanizeTimestamp(token.ExpiresAt)
}
fmt.Fprintf(w, "%d\t%s\t%s\n", token.IssuedAt, humanizeTimestamp(token.IssuedAt), expiresAt)
}
_ = w.Flush()
},
}
return command
}
func humanizeTimestamp(epoch int64) string {
ts := time.Unix(epoch, 0)
return fmt.Sprintf("%s (%s)", ts.Format(time.RFC3339), humanize.Time(ts))
}
// NewProjectCreateCommand returns a new instance of an `argocd proj create` command
@@ -242,8 +590,13 @@ func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
errors.CheckError(err)
for _, item := range proj.Spec.SourceRepos {
if item == "*" && item == url {
log.Info("Wildcard source repository is already defined in project")
return
}
if item == git.NormalizeGitURL(url) {
log.Fatal("Specified source repository is already defined in project")
log.Info("Specified source repository is already defined in project")
return
}
}
proj.Spec.SourceRepos = append(proj.Spec.SourceRepos, url)
@@ -254,6 +607,104 @@ func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
return command
}
func modifyProjectResourceCmd(cmdUse, cmdDesc string, clientOpts *argocdclient.ClientOptions, action func(proj *v1alpha1.AppProject, group string, kind string) bool) *cobra.Command {
return &cobra.Command{
Use: cmdUse,
Short: cmdDesc,
Run: func(c *cobra.Command, args []string) {
if len(args) != 3 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName, group, kind := args[0], args[1], args[2]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
errors.CheckError(err)
if action(proj, group, kind) {
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
}
},
}
}
// NewProjectAllowNamespaceResourceCommand returns a new instance of an `deny-cluster-resources` command
func NewProjectAllowNamespaceResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
use := "allow-namespace-resource PROJECT GROUP KIND"
desc := "Removes a namespaced API resource from the blacklist"
return modifyProjectResourceCmd(use, desc, clientOpts, func(proj *v1alpha1.AppProject, group string, kind string) bool {
index := -1
for i, item := range proj.Spec.NamespaceResourceBlacklist {
if item.Group == group && item.Kind == kind {
index = i
break
}
}
if index == -1 {
log.Info("Specified cluster resource is not blacklisted")
return false
}
proj.Spec.NamespaceResourceBlacklist = append(proj.Spec.NamespaceResourceBlacklist[:index], proj.Spec.NamespaceResourceBlacklist[index+1:]...)
return true
})
}
// NewProjectDenyNamespaceResourceCommand returns a new instance of an `argocd proj deny-namespace-resource` command
func NewProjectDenyNamespaceResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
use := "deny-namespace-resource PROJECT GROUP KIND"
desc := "Adds a namespaced API resource to the blacklist"
return modifyProjectResourceCmd(use, desc, clientOpts, func(proj *v1alpha1.AppProject, group string, kind string) bool {
for _, item := range proj.Spec.NamespaceResourceBlacklist {
if item.Group == group && item.Kind == kind {
log.Infof("Group '%s' and kind '%s' are already blacklisted in project", item.Group, item.Kind)
return false
}
}
proj.Spec.NamespaceResourceBlacklist = append(proj.Spec.NamespaceResourceBlacklist, v1.GroupKind{Group: group, Kind: kind})
return true
})
}
// NewProjectDenyClusterResourceCommand returns a new instance of an `deny-cluster-resource` command
func NewProjectDenyClusterResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
use := "deny-cluster-resource PROJECT GROUP KIND"
desc := "Removes a cluster-scoped API resource from the whitelist"
return modifyProjectResourceCmd(use, desc, clientOpts, func(proj *v1alpha1.AppProject, group string, kind string) bool {
index := -1
for i, item := range proj.Spec.ClusterResourceWhitelist {
if item.Group == group && item.Kind == kind {
index = i
break
}
}
if index == -1 {
log.Info("Specified cluster resource already denied in project")
return false
}
proj.Spec.ClusterResourceWhitelist = append(proj.Spec.ClusterResourceWhitelist[:index], proj.Spec.ClusterResourceWhitelist[index+1:]...)
return true
})
}
// NewProjectAllowClusterResourceCommand returns a new instance of an `argocd proj allow-cluster-resource` command
func NewProjectAllowClusterResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
use := "allow-cluster-resource PROJECT GROUP KIND"
desc := "Adds a cluster-scoped API resource to the whitelist"
return modifyProjectResourceCmd(use, desc, clientOpts, func(proj *v1alpha1.AppProject, group string, kind string) bool {
for _, item := range proj.Spec.ClusterResourceWhitelist {
if item.Group == group && item.Kind == kind {
log.Infof("Group '%s' and kind '%s' are already whitelisted in project", item.Group, item.Kind)
return false
}
}
proj.Spec.ClusterResourceWhitelist = append(proj.Spec.ClusterResourceWhitelist, v1.GroupKind{Group: group, Kind: kind})
return true
})
}
// NewProjectRemoveSourceCommand returns a new instance of an `argocd proj remove-src` command
func NewProjectRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
@@ -274,13 +725,17 @@ func NewProjectRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobr
index := -1
for i, item := range proj.Spec.SourceRepos {
if item == "*" && item == url {
index = i
break
}
if item == git.NormalizeGitURL(url) {
index = i
break
}
}
if index == -1 {
log.Fatal("Specified source repository does not exist in project")
log.Info("Specified source repository does not exist in project")
} else {
proj.Spec.SourceRepos = append(proj.Spec.SourceRepos[:index], proj.Spec.SourceRepos[index+1:]...)
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
@@ -324,9 +779,9 @@ func NewProjectListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
projects, err := projIf.List(context.Background(), &project.ProjectQuery{})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tDESTINATIONS\n")
fmt.Fprintf(w, "NAME\tDESCRIPTION\tDESTINATIONS\tSOURCES\tCLUSTER-RESOURCE-WHITELIST\tNAMESPACE-RESOURCE-BLACKLIST\n")
for _, p := range projects.Items {
fmt.Fprintf(w, "%s\t%s\t%v\n", p.Name, p.Spec.Description, p.Spec.Destinations)
fmt.Fprintf(w, "%s\t%s\t%v\t%v\t%v\t%v\n", p.Name, p.Spec.Description, p.Spec.Destinations, p.Spec.SourceRepos, p.Spec.ClusterResourceWhitelist, p.Spec.NamespaceResourceBlacklist)
}
_ = w.Flush()
},

View File

@@ -87,7 +87,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
}
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&sshPrivateKeyPath, "sshPrivateKeyPath", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
return command
}

View File

@@ -102,4 +102,8 @@ var ArgoCDManagerPolicyRules = []rbacv1.PolicyRule{
Resources: []string{"*"},
Verbs: []string{"*"},
},
{
NonResourceURLs: []string{"*"},
Verbs: []string{"*"},
},
}

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"time"
"github.com/argoproj/argo-cd/errors"
log "github.com/sirupsen/logrus"
apiv1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -12,7 +11,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
// CreateServiceAccount creates a service account
@@ -20,7 +18,7 @@ func CreateServiceAccount(
clientset kubernetes.Interface,
serviceAccountName string,
namespace string,
) {
) error {
serviceAccount := apiv1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
@@ -34,12 +32,13 @@ func CreateServiceAccount(
_, err := clientset.CoreV1().ServiceAccounts(namespace).Create(&serviceAccount)
if err != nil {
if !apierr.IsAlreadyExists(err) {
log.Fatalf("Failed to create service account '%s': %v\n", serviceAccountName, err)
return fmt.Errorf("Failed to create service account %q: %v", serviceAccountName, err)
}
fmt.Printf("ServiceAccount '%s' already exists\n", serviceAccountName)
return
log.Infof("ServiceAccount %q already exists", serviceAccountName)
return nil
}
fmt.Printf("ServiceAccount '%s' created\n", serviceAccountName)
log.Infof("ServiceAccount %q created", serviceAccountName)
return nil
}
// CreateClusterRole creates a cluster role
@@ -47,7 +46,7 @@ func CreateClusterRole(
clientset kubernetes.Interface,
clusterRoleName string,
rules []rbacv1.PolicyRule,
) {
) error {
clusterRole := rbacv1.ClusterRole{
TypeMeta: metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
@@ -62,16 +61,17 @@ func CreateClusterRole(
_, err := crclient.Create(&clusterRole)
if err != nil {
if !apierr.IsAlreadyExists(err) {
log.Fatalf("Failed to create ClusterRole '%s': %v\n", clusterRoleName, err)
return fmt.Errorf("Failed to create ClusterRole %q: %v", clusterRoleName, err)
}
_, err = crclient.Update(&clusterRole)
if err != nil {
log.Fatalf("Failed to update ClusterRole '%s': %v\n", clusterRoleName, err)
return fmt.Errorf("Failed to update ClusterRole %q: %v", clusterRoleName, err)
}
fmt.Printf("ClusterRole '%s' updated\n", clusterRoleName)
log.Infof("ClusterRole %q updated", clusterRoleName)
} else {
fmt.Printf("ClusterRole '%s' created\n", clusterRoleName)
log.Infof("ClusterRole %q created", clusterRoleName)
}
return nil
}
// CreateClusterRoleBinding create a ClusterRoleBinding
@@ -81,7 +81,7 @@ func CreateClusterRoleBinding(
serviceAccountName,
clusterRoleName string,
namespace string,
) {
) error {
roleBinding := rbacv1.ClusterRoleBinding{
TypeMeta: metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
@@ -106,22 +106,34 @@ func CreateClusterRoleBinding(
_, err := clientset.RbacV1().ClusterRoleBindings().Create(&roleBinding)
if err != nil {
if !apierr.IsAlreadyExists(err) {
log.Fatalf("Failed to create ClusterRoleBinding %s: %v\n", clusterBindingRoleName, err)
return fmt.Errorf("Failed to create ClusterRoleBinding %s: %v", clusterBindingRoleName, err)
}
fmt.Printf("ClusterRoleBinding '%s' already exists\n", clusterBindingRoleName)
return
log.Infof("ClusterRoleBinding %q already exists", clusterBindingRoleName)
return nil
}
fmt.Printf("ClusterRoleBinding '%s' created, bound '%s' to '%s'\n", clusterBindingRoleName, serviceAccountName, clusterRoleName)
log.Infof("ClusterRoleBinding %q created, bound %q to %q", clusterBindingRoleName, serviceAccountName, clusterRoleName)
return nil
}
// InstallClusterManagerRBAC installs RBAC resources for a cluster manager to operate a cluster. Returns a token
func InstallClusterManagerRBAC(conf *rest.Config) string {
func InstallClusterManagerRBAC(clientset kubernetes.Interface) (string, error) {
const ns = "kube-system"
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
CreateServiceAccount(clientset, ArgoCDManagerServiceAccount, ns)
CreateClusterRole(clientset, ArgoCDManagerClusterRole, ArgoCDManagerPolicyRules)
CreateClusterRoleBinding(clientset, ArgoCDManagerClusterRoleBinding, ArgoCDManagerServiceAccount, ArgoCDManagerClusterRole, ns)
var err error
err = CreateServiceAccount(clientset, ArgoCDManagerServiceAccount, ns)
if err != nil {
return "", err
}
err = CreateClusterRole(clientset, ArgoCDManagerClusterRole, ArgoCDManagerPolicyRules)
if err != nil {
return "", err
}
err = CreateClusterRoleBinding(clientset, ArgoCDManagerClusterRoleBinding, ArgoCDManagerServiceAccount, ArgoCDManagerClusterRole, ns)
if err != nil {
return "", err
}
var serviceAccount *apiv1.ServiceAccount
var secretName string
@@ -137,52 +149,51 @@ func InstallClusterManagerRBAC(conf *rest.Config) string {
return true, nil
})
if err != nil {
log.Fatalf("Failed to wait for service account secret: %v", err)
return "", fmt.Errorf("Failed to wait for service account secret: %v", err)
}
secret, err := clientset.CoreV1().Secrets(ns).Get(secretName, metav1.GetOptions{})
if err != nil {
log.Fatalf("Failed to retrieve secret '%s': %v", secretName, err)
return "", fmt.Errorf("Failed to retrieve secret %q: %v", secretName, err)
}
token, ok := secret.Data["token"]
if !ok {
log.Fatalf("Secret '%s' for service account '%s' did not have a token", secretName, serviceAccount)
return "", fmt.Errorf("Secret %q for service account %q did not have a token", secretName, serviceAccount)
}
return string(token)
return string(token), nil
}
// UninstallClusterManagerRBAC removes RBAC resources for a cluster manager to operate a cluster
func UninstallClusterManagerRBAC(conf *rest.Config) {
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
UninstallRBAC(clientset, "kube-system", ArgoCDManagerClusterRoleBinding, ArgoCDManagerClusterRole, ArgoCDManagerServiceAccount)
func UninstallClusterManagerRBAC(clientset kubernetes.Interface) error {
return UninstallRBAC(clientset, "kube-system", ArgoCDManagerClusterRoleBinding, ArgoCDManagerClusterRole, ArgoCDManagerServiceAccount)
}
// UninstallRBAC uninstalls RBAC related resources for a binding, role, and service account
func UninstallRBAC(clientset kubernetes.Interface, namespace, bindingName, roleName, serviceAccount string) {
func UninstallRBAC(clientset kubernetes.Interface, namespace, bindingName, roleName, serviceAccount string) error {
if err := clientset.RbacV1().ClusterRoleBindings().Delete(bindingName, &metav1.DeleteOptions{}); err != nil {
if !apierr.IsNotFound(err) {
log.Fatalf("Failed to delete ClusterRoleBinding: %v\n", err)
return fmt.Errorf("Failed to delete ClusterRoleBinding: %v", err)
}
fmt.Printf("ClusterRoleBinding '%s' not found\n", bindingName)
log.Infof("ClusterRoleBinding %q not found", bindingName)
} else {
fmt.Printf("ClusterRoleBinding '%s' deleted\n", bindingName)
log.Infof("ClusterRoleBinding %q deleted", bindingName)
}
if err := clientset.RbacV1().ClusterRoles().Delete(roleName, &metav1.DeleteOptions{}); err != nil {
if !apierr.IsNotFound(err) {
log.Fatalf("Failed to delete ClusterRole: %v\n", err)
return fmt.Errorf("Failed to delete ClusterRole: %v", err)
}
fmt.Printf("ClusterRole '%s' not found\n", roleName)
log.Infof("ClusterRole %q not found", roleName)
} else {
fmt.Printf("ClusterRole '%s' deleted\n", roleName)
log.Infof("ClusterRole %q deleted", roleName)
}
if err := clientset.CoreV1().ServiceAccounts(namespace).Delete(serviceAccount, &metav1.DeleteOptions{}); err != nil {
if !apierr.IsNotFound(err) {
log.Fatalf("Failed to delete ServiceAccount: %v\n", err)
return fmt.Errorf("Failed to delete ServiceAccount: %v", err)
}
fmt.Printf("ServiceAccount '%s' in namespace '%s' not found\n", serviceAccount, namespace)
log.Infof("ServiceAccount %q in namespace %q not found", serviceAccount, namespace)
} else {
fmt.Printf("ServiceAccount '%s' deleted\n", serviceAccount)
log.Infof("ServiceAccount %q deleted", serviceAccount)
}
return nil
}

View File

@@ -14,9 +14,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/strategicpatch"
@@ -46,6 +44,7 @@ const (
type ApplicationController struct {
namespace string
kubeClientset kubernetes.Interface
kubectl kube.Kubectl
applicationClientset appclientset.Interface
auditLogger *argo.AuditLogger
appRefreshQueue workqueue.RateLimitingInterface
@@ -70,28 +69,28 @@ func NewApplicationController(
kubeClientset kubernetes.Interface,
applicationClientset appclientset.Interface,
repoClientset reposerver.Clientset,
db db.ArgoDB,
appStateManager AppStateManager,
appResyncPeriod time.Duration,
config *ApplicationControllerConfig,
) *ApplicationController {
appRefreshQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
appOperationQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
return &ApplicationController{
db := db.NewDB(namespace, kubeClientset)
kubectlCmd := kube.KubectlCmd{}
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd)
ctrl := ApplicationController{
namespace: namespace,
kubeClientset: kubeClientset,
kubectl: kubectlCmd,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appRefreshQueue: appRefreshQueue,
appOperationQueue: appOperationQueue,
appRefreshQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
appOperationQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
appStateManager: appStateManager,
appInformer: newApplicationInformer(applicationClientset, appRefreshQueue, appOperationQueue, appResyncPeriod, config),
db: db,
statusRefreshTimeout: appResyncPeriod,
forceRefreshApps: make(map[string]bool),
forceRefreshAppsMutex: &sync.Mutex{},
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, "application-controller"),
}
ctrl.appInformer = ctrl.newApplicationInformer()
return &ctrl
}
// Run starts the Application CRD controller.
@@ -100,13 +99,14 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
defer ctrl.appRefreshQueue.ShutDown()
go ctrl.appInformer.Run(ctx.Done())
go ctrl.watchAppsResources()
if !cache.WaitForCacheSync(ctx.Done(), ctrl.appInformer.HasSynced) {
log.Error("Timed out waiting for caches to sync")
return
}
go ctrl.watchAppsResources()
for i := 0; i < statusProcessors; i++ {
go wait.Until(func() {
for ctrl.processAppRefreshQueueItem() {
@@ -142,14 +142,35 @@ func (ctrl *ApplicationController) isRefreshForced(appName string) bool {
// watchClusterResources watches for resource changes annotated with application label on specified cluster and schedule corresponding app refresh.
func (ctrl *ApplicationController) watchClusterResources(ctx context.Context, item appv1.Cluster) {
config := item.RESTConfig()
retryUntilSucceed(func() error {
ch, err := kube.WatchResourcesWithLabel(ctx, config, "", common.LabelApplicationName)
retryUntilSucceed(func() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered from panic: %v\n", r)
}
}()
config := item.RESTConfig()
watchStartTime := time.Now()
ch, err := ctrl.kubectl.WatchResources(ctx, config, "", func(gvk schema.GroupVersionKind) metav1.ListOptions {
ops := metav1.ListOptions{}
if !kube.IsCRDGroupVersionKind(gvk) {
ops.LabelSelector = common.LabelApplicationName
}
return ops
})
if err != nil {
return err
}
for event := range ch {
eventObj := event.Object.(*unstructured.Unstructured)
if kube.IsCRD(eventObj) {
// restart if new CRD has been created after watch started
if event.Type == watch.Added && watchStartTime.Before(eventObj.GetCreationTimestamp().Time) {
return fmt.Errorf("Restarting the watch because a new CRD was added.")
} else if event.Type == watch.Deleted {
return fmt.Errorf("Restarting the watch because a CRD was deleted.")
}
}
objLabels := eventObj.GetLabels()
if objLabels == nil {
objLabels = make(map[string]string)
@@ -160,26 +181,68 @@ func (ctrl *ApplicationController) watchClusterResources(ctx context.Context, it
}
}
return fmt.Errorf("resource updates channel has closed")
}, fmt.Sprintf("watch app resources on %s", config.Host), ctx, watchResourcesRetryTimeout)
}, fmt.Sprintf("watch app resources on %s", item.Server), ctx, watchResourcesRetryTimeout)
}
func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {
for _, obj := range apps {
if app, ok := obj.(*appv1.Application); ok && app.Spec.Destination.Server == cluster.Server {
return true
}
}
return false
}
// WatchAppsResources watches for resource changes annotated with application label on all registered clusters and schedule corresponding app refresh.
func (ctrl *ApplicationController) watchAppsResources() {
watchingClusters := make(map[string]context.CancelFunc)
watchingClusters := make(map[string]struct {
cancel context.CancelFunc
cluster *appv1.Cluster
})
retryUntilSucceed(func() error {
return ctrl.db.WatchClusters(context.Background(), func(event *db.ClusterEvent) {
cancel, ok := watchingClusters[event.Cluster.Server]
if event.Type == watch.Deleted && ok {
cancel()
clusterEventCallback := func(event *db.ClusterEvent) {
info, ok := watchingClusters[event.Cluster.Server]
hasApps := isClusterHasApps(ctrl.appInformer.GetStore().List(), event.Cluster)
// cluster resources must be watched only if cluster has at least one app
if (event.Type == watch.Deleted || !hasApps) && ok {
info.cancel()
delete(watchingClusters, event.Cluster.Server)
} else if event.Type != watch.Deleted && !ok {
} else if event.Type != watch.Deleted && !ok && hasApps {
ctx, cancel := context.WithCancel(context.Background())
watchingClusters[event.Cluster.Server] = cancel
watchingClusters[event.Cluster.Server] = struct {
cancel context.CancelFunc
cluster *appv1.Cluster
}{
cancel: cancel,
cluster: event.Cluster,
}
go ctrl.watchClusterResources(ctx, *event.Cluster)
}
})
}
onAppModified := func(obj interface{}) {
if app, ok := obj.(*appv1.Application); ok {
var cluster *appv1.Cluster
info, infoOk := watchingClusters[app.Spec.Destination.Server]
if infoOk {
cluster = info.cluster
} else {
cluster, _ = ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
}
if cluster != nil {
// trigger cluster event every time when app created/deleted to either start or stop watching resources
clusterEventCallback(&db.ClusterEvent{Cluster: cluster, Type: watch.Modified})
}
}
}
ctrl.appInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: onAppModified, DeleteFunc: onAppModified})
return ctrl.db.WatchClusters(context.Background(), clusterEventCallback)
}, "watch clusters", context.Background(), watchResourcesRetryTimeout)
<-context.Background().Done()
@@ -204,7 +267,7 @@ func retryUntilSucceed(action func() error, desc string, ctx context.Context, ti
log.Infof("Stop retrying %s", desc)
return
} else {
log.Warnf("Failed to %s: %v, retrying in %v", desc, err, timeout)
log.Warnf("Failed to %s: %+v, retrying in %v", desc, err, timeout)
time.Sleep(timeout)
}
}
@@ -252,12 +315,13 @@ func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext b
}
func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Application) {
log.Infof("Deleting resources for application %s", app.Name)
logCtx := log.WithField("application", app.Name)
logCtx.Infof("Deleting resources")
// Get refreshed application info, since informer app copy might be stale
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(app.Name, metav1.GetOptions{})
if err != nil {
if !errors.IsNotFound(err) {
log.Errorf("Unable to get refreshed application info prior deleting resources: %v", err)
logCtx.Errorf("Unable to get refreshed application info prior deleting resources: %v", err)
}
return
}
@@ -266,7 +330,7 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
if err == nil {
config := clst.RESTConfig()
err = kube.DeleteResourceWithLabel(config, app.Spec.Destination.Namespace, common.LabelApplicationName, app.Name)
err = kube.DeleteResourcesWithLabel(config, app.Spec.Destination.Namespace, common.LabelApplicationName, app.Name)
if err == nil {
app.SetCascadedDeletion(false)
var patch []byte
@@ -281,14 +345,14 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
}
}
if err != nil {
log.Errorf("Unable to delete application resources: %v", err)
ctrl.setAppCondition(app, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionDeletionError,
Message: err.Error(),
})
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonStatusRefreshed, Action: "refresh_status"}, v1.EventTypeWarning)
message := fmt.Sprintf("Unable to delete application resources: %v", err)
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonStatusRefreshed, Type: v1.EventTypeWarning}, message)
} else {
log.Infof("Successfully deleted resources for application %s", app.Name)
logCtx.Info("Successfully deleted resources")
}
}
@@ -320,11 +384,12 @@ func (ctrl *ApplicationController) setAppCondition(app *appv1.Application, condi
}
func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Application) {
logCtx := log.WithField("application", app.Name)
var state *appv1.OperationState
// Recover from any unexpected panics and automatically set the status to be failed
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
logCtx.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
state.Phase = appv1.OperationError
if rerr, ok := r.(error); ok {
state.Message = rerr.Error()
@@ -341,20 +406,20 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
// again. To detect this, always retrieve the latest version to ensure it is not stale.
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
log.Errorf("Failed to retrieve latest application state: %v", err)
logCtx.Errorf("Failed to retrieve latest application state: %v", err)
return
}
if !isOperationInProgress(freshApp) {
log.Infof("Skipping operation on stale application state (%s)", app.ObjectMeta.Name)
logCtx.Infof("Skipping operation on stale application state")
return
}
app = freshApp
state = app.Status.OperationState.DeepCopy()
log.Infof("Resuming in-progress operation. app: %s, phase: %s, message: %s", app.ObjectMeta.Name, state.Phase, state.Message)
logCtx.Infof("Resuming in-progress operation. phase: %s, message: %s", state.Phase, state.Message)
} else {
state = &appv1.OperationState{Phase: appv1.OperationRunning, Operation: *app.Operation, StartedAt: metav1.Now()}
ctrl.setOperationState(app, state)
log.Infof("Initialized new operation. app: %s, operation: %v", app.ObjectMeta.Name, *app.Operation)
logCtx.Infof("Initialized new operation: %v", *app.Operation)
}
ctrl.appStateManager.SyncAppState(app, state)
@@ -400,7 +465,6 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
// If operation is completed, clear the operation field to indicate no operation is
// in progress.
patch["operation"] = nil
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonResourceUpdated, Action: "refresh_status"}, v1.EventTypeNormal)
}
if reflect.DeepEqual(app.Status.OperationState, state) {
log.Infof("No operation updates necessary to '%s'. Skipping patch", app.Name)
@@ -416,6 +480,18 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
return err
}
log.Infof("updated '%s' operation (phase: %s)", app.Name, state.Phase)
if state.Phase.Completed() {
eventInfo := argo.EventInfo{Reason: argo.EventReasonOperationCompleted}
var message string
if state.Phase.Successful() {
eventInfo.Type = v1.EventTypeNormal
message = "Operation succeeded"
} else {
eventInfo.Type = v1.EventTypeWarning
message = fmt.Sprintf("Operation failed: %v", state.Message)
}
ctrl.auditLogger.LogAppEvent(app, eventInfo, message)
}
return nil
}, "Update application operation state", context.Background(), updateOperationStateTimeout)
}
@@ -475,10 +551,16 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
parameters = manifestInfo.Params
}
healthState, err := setApplicationHealth(comparisonResult)
healthState, err := setApplicationHealth(ctrl.kubectl, comparisonResult)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
}
syncErrCond := ctrl.autoSync(app, comparisonResult)
if syncErrCond != nil {
conditions = append(conditions, *syncErrCond)
}
ctrl.updateAppStatus(app, comparisonResult, healthState, parameters, conditions)
return
}
@@ -486,18 +568,20 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
// needRefreshAppStatus answers if application status needs to be refreshed.
// Returns true if application never been compared, has changed or comparison result has expired.
func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application, statusRefreshTimeout time.Duration) bool {
logCtx := log.WithFields(log.Fields{"application": app.Name})
var reason string
expired := app.Status.ComparisonResult.ComparedAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
if ctrl.isRefreshForced(app.Name) {
reason = "force refresh"
} else if app.Status.ComparisonResult.Status == appv1.ComparisonStatusUnknown {
} else if app.Status.ComparisonResult.Status == appv1.ComparisonStatusUnknown && expired {
reason = "comparison status unknown"
} else if !app.Spec.Source.Equals(app.Status.ComparisonResult.ComparedTo) {
reason = "spec.source differs"
} else if app.Status.ComparisonResult.ComparedAt.Add(statusRefreshTimeout).Before(time.Now().UTC()) {
} else if expired {
reason = fmt.Sprintf("comparison expired. comparedAt: %v, expiry: %v", app.Status.ComparisonResult.ComparedAt, statusRefreshTimeout)
}
if reason != "" {
log.Infof("Refreshing application '%s' status (%s)", app.Name, reason)
logCtx.Infof("Refreshing app status (%s)", reason)
return true
}
return false
@@ -536,6 +620,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
appv1.ApplicationConditionUnknownError: true,
appv1.ApplicationConditionComparisonError: true,
appv1.ApplicationConditionSharedResourceWarning: true,
appv1.ApplicationConditionSyncError: true,
}
appConditions := make([]appv1.ApplicationCondition, 0)
for i := 0; i < len(app.Status.Conditions); i++ {
@@ -557,7 +642,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
}
// setApplicationHealth updates the health statuses of all resources performed in the comparison
func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
func setApplicationHealth(kubectl kube.Kubectl, comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
var savedErr error
appHealth := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
if comparisonResult.Status == appv1.ComparisonStatusUnknown {
@@ -572,7 +657,7 @@ func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.Heal
if err != nil {
return nil, err
}
healthState, err := health.GetAppHealth(&obj)
healthState, err := health.GetAppHealth(kubectl, &obj)
if err != nil && savedErr == nil {
savedErr = err
}
@@ -594,12 +679,21 @@ func (ctrl *ApplicationController) updateAppStatus(
parameters []*appv1.ComponentParameter,
conditions []appv1.ApplicationCondition,
) {
logCtx := log.WithFields(log.Fields{"application": app.Name})
modifiedApp := app.DeepCopy()
if comparisonResult != nil {
modifiedApp.Status.ComparisonResult = *comparisonResult
log.Infof("App %s comparison result: prev: %s. current: %s", app.Name, app.Status.ComparisonResult.Status, comparisonResult.Status)
if app.Status.ComparisonResult.Status != comparisonResult.Status {
message := fmt.Sprintf("Updated sync status: %s -> %s", app.Status.ComparisonResult.Status, comparisonResult.Status)
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonResourceUpdated, Type: v1.EventTypeNormal}, message)
}
logCtx.Infof("Comparison result: prev: %s. current: %s", app.Status.ComparisonResult.Status, comparisonResult.Status)
}
if healthState != nil {
if modifiedApp.Status.Health.Status != healthState.Status {
message := fmt.Sprintf("Updated health status: %s -> %s", modifiedApp.Status.Health.Status, healthState.Status)
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonResourceUpdated, Type: v1.EventTypeNormal}, message)
}
modifiedApp.Status.Health = *healthState
}
if parameters != nil {
@@ -613,59 +707,104 @@ func (ctrl *ApplicationController) updateAppStatus(
}
origBytes, err := json.Marshal(app)
if err != nil {
log.Errorf("Error updating application %s (marshal orig app): %v", app.Name, err)
logCtx.Errorf("Error updating (marshal orig app): %v", err)
return
}
modifiedBytes, err := json.Marshal(modifiedApp)
if err != nil {
log.Errorf("Error updating application %s (marshal modified app): %v", app.Name, err)
logCtx.Errorf("Error updating (marshal modified app): %v", err)
return
}
patch, err := strategicpatch.CreateTwoWayMergePatch(origBytes, modifiedBytes, appv1.Application{})
if err != nil {
log.Errorf("Error calculating patch for app %s update: %v", app.Name, err)
logCtx.Errorf("Error calculating patch for update: %v", err)
return
}
if string(patch) == "{}" {
log.Infof("No status changes to %s. Skipping patch", app.Name)
logCtx.Infof("No status changes. Skipping patch")
return
}
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
_, err = appClient.Patch(app.Name, types.MergePatchType, patch)
if err != nil {
log.Warnf("Error updating application %s: %v", app.Name, err)
logCtx.Warnf("Error updating application: %v", err)
} else {
log.Infof("Application %s update successful", app.Name)
logCtx.Infof("Update successful")
}
}
func newApplicationInformer(
appClientset appclientset.Interface,
appQueue workqueue.RateLimitingInterface,
appOperationQueue workqueue.RateLimitingInterface,
appResyncPeriod time.Duration,
config *ApplicationControllerConfig) cache.SharedIndexInformer {
// autoSync will initiate a sync operation for an application configured with automated sync
func (ctrl *ApplicationController) autoSync(app *appv1.Application, comparisonResult *appv1.ComparisonResult) *appv1.ApplicationCondition {
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
return nil
}
logCtx := log.WithFields(log.Fields{"application": app.Name})
if app.Operation != nil {
logCtx.Infof("Skipping auto-sync: another operation is in progress")
return nil
}
// Only perform auto-sync if we detect OutOfSync status. This is to prevent us from attempting
// a sync when application is already in a Synced or Unknown state
if comparisonResult.Status != appv1.ComparisonStatusOutOfSync {
logCtx.Infof("Skipping auto-sync: application status is %s", comparisonResult.Status)
return nil
}
desiredCommitSHA := comparisonResult.Revision
appInformerFactory := appinformers.NewFilteredSharedInformerFactory(
appClientset,
appResyncPeriod,
config.Namespace,
func(options *metav1.ListOptions) {
var instanceIDReq *labels.Requirement
var err error
if config.InstanceID != "" {
instanceIDReq, err = labels.NewRequirement(common.LabelKeyApplicationControllerInstanceID, selection.Equals, []string{config.InstanceID})
} else {
instanceIDReq, err = labels.NewRequirement(common.LabelKeyApplicationControllerInstanceID, selection.DoesNotExist, nil)
}
if err != nil {
panic(err)
}
// It is possible for manifests to remain OutOfSync even after a sync/kubectl apply (e.g.
// auto-sync with pruning disabled). We need to ensure that we do not keep Syncing an
// application in an infinite loop. To detect this, we only attempt the Sync if the revision
// and parameter overrides are different from our most recent sync operation.
if alreadyAttemptedSync(app, desiredCommitSHA) {
if app.Status.OperationState.Phase != appv1.OperationSucceeded {
logCtx.Warnf("Skipping auto-sync: failed previous sync attempt to %s", desiredCommitSHA)
message := fmt.Sprintf("Failed sync attempt to %s: %s", desiredCommitSHA, app.Status.OperationState.Message)
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: message}
}
logCtx.Infof("Skipping auto-sync: most recent sync already to %s", desiredCommitSHA)
return nil
}
options.FieldSelector = fields.Everything().String()
labelSelector := labels.NewSelector().Add(*instanceIDReq)
options.LabelSelector = labelSelector.String()
op := appv1.Operation{
Sync: &appv1.SyncOperation{
Revision: desiredCommitSHA,
Prune: app.Spec.SyncPolicy.Automated.Prune,
ParameterOverrides: app.Spec.Source.ComponentParameterOverrides,
},
}
appIf := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
_, err := argo.SetAppOperation(context.Background(), appIf, ctrl.auditLogger, app.Name, &op)
if err != nil {
logCtx.Errorf("Failed to initiate auto-sync to %s: %v", desiredCommitSHA, err)
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: err.Error()}
}
message := fmt.Sprintf("Initiated automated sync to '%s'", desiredCommitSHA)
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonOperationStarted, Type: v1.EventTypeNormal}, message)
logCtx.Info(message)
return nil
}
// alreadyAttemptedSync returns whether or not the most recent sync was performed against the
// commitSHA and with the same parameter overrides which are currently set in the app
func alreadyAttemptedSync(app *appv1.Application, commitSHA string) bool {
if app.Status.OperationState == nil || app.Status.OperationState.Operation.Sync == nil || app.Status.OperationState.SyncResult == nil {
return false
}
if app.Status.OperationState.SyncResult.Revision != commitSHA {
return false
}
if !reflect.DeepEqual(appv1.ParameterOverrides(app.Spec.Source.ComponentParameterOverrides), app.Status.OperationState.Operation.Sync.ParameterOverrides) {
return false
}
return true
}
func (ctrl *ApplicationController) newApplicationInformer() cache.SharedIndexInformer {
appInformerFactory := appinformers.NewFilteredSharedInformerFactory(
ctrl.applicationClientset,
ctrl.statusRefreshTimeout,
ctrl.namespace,
func(options *metav1.ListOptions) {},
)
informer := appInformerFactory.Argoproj().V1alpha1().Applications().Informer()
informer.AddEventHandler(
@@ -673,23 +812,32 @@ func newApplicationInformer(
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
appQueue.Add(key)
appOperationQueue.Add(key)
ctrl.appRefreshQueue.Add(key)
ctrl.appOperationQueue.Add(key)
}
},
UpdateFunc: func(old, new interface{}) {
key, err := cache.MetaNamespaceKeyFunc(new)
if err == nil {
appQueue.Add(key)
appOperationQueue.Add(key)
if err != nil {
return
}
oldApp, oldOK := old.(*appv1.Application)
newApp, newOK := new.(*appv1.Application)
if oldOK && newOK {
if toggledAutomatedSync(oldApp, newApp) {
log.WithField("application", newApp.Name).Info("Enabled automated sync")
ctrl.forceAppRefresh(newApp.Name)
}
}
ctrl.appRefreshQueue.Add(key)
ctrl.appOperationQueue.Add(key)
},
DeleteFunc: func(obj interface{}) {
// IndexerInformer uses a delta queue, therefore for deletes we have to use this
// key function.
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err == nil {
appQueue.Add(key)
ctrl.appRefreshQueue.Add(key)
}
},
},
@@ -700,3 +848,17 @@ func newApplicationInformer(
func isOperationInProgress(app *appv1.Application) bool {
return app.Status.OperationState != nil && !app.Status.OperationState.Phase.Completed()
}
// toggledAutomatedSync tests if an app went from auto-sync disabled to enabled.
// if it was toggled to be enabled, the informer handler will force a refresh
func toggledAutomatedSync(old *appv1.Application, new *appv1.Application) bool {
if new.Spec.SyncPolicy == nil || new.Spec.SyncPolicy.Automated == nil {
return false
}
// auto-sync is enabled. check if it was previously disabled
if old.Spec.SyncPolicy == nil || old.Spec.SyncPolicy.Automated == nil {
return true
}
// nothing changed
return false
}

View File

@@ -0,0 +1,231 @@
package controller
import (
"testing"
"time"
"github.com/ghodss/yaml"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
reposerver "github.com/argoproj/argo-cd/reposerver/mocks"
"github.com/stretchr/testify/assert"
)
func newFakeController(apps ...runtime.Object) *ApplicationController {
kubeClientset := fake.NewSimpleClientset()
appClientset := appclientset.NewSimpleClientset(apps...)
repoClientset := reposerver.Clientset{}
return NewApplicationController(
"argocd",
kubeClientset,
appClientset,
&repoClientset,
time.Minute,
)
}
var fakeApp = `
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
destination:
namespace: dummy-namespace
server: https://localhost:6443
project: default
source:
path: some/path
repoURL: https://github.com/argoproj/argocd-example-apps.git
syncPolicy:
automated: {}
status:
operationState:
finishedAt: 2018-09-21T23:50:29Z
message: successfully synced
operation:
sync:
revision: HEAD
phase: Succeeded
startedAt: 2018-09-21T23:50:25Z
syncResult:
resources:
- kind: RoleBinding
message: |-
rolebinding.rbac.authorization.k8s.io/always-outofsync reconciled
rolebinding.rbac.authorization.k8s.io/always-outofsync configured
name: always-outofsync
namespace: default
status: Synced
revision: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
`
func newFakeApp() *argoappv1.Application {
var app argoappv1.Application
err := yaml.Unmarshal([]byte(fakeApp), &app)
if err != nil {
panic(err)
}
return &app
}
func TestAutoSync(t *testing.T) {
app := newFakeApp()
ctrl := newFakeController(app)
compRes := argoappv1.ComparisonResult{
Status: argoappv1.ComparisonStatusOutOfSync,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond := ctrl.autoSync(app, &compRes)
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications("argocd").Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
assert.NotNil(t, app.Operation)
assert.NotNil(t, app.Operation.Sync)
assert.False(t, app.Operation.Sync.Prune)
}
func TestSkipAutoSync(t *testing.T) {
// Verify we skip when we previously synced to it in our most recent history
// Set current to 'aaaaa', desired to 'aaaa' and mark system OutOfSync
app := newFakeApp()
ctrl := newFakeController(app)
compRes := argoappv1.ComparisonResult{
Status: argoappv1.ComparisonStatusOutOfSync,
Revision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
}
cond := ctrl.autoSync(app, &compRes)
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications("argocd").Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
assert.Nil(t, app.Operation)
// Verify we skip when we are already Synced (even if revision is different)
app = newFakeApp()
ctrl = newFakeController(app)
compRes = argoappv1.ComparisonResult{
Status: argoappv1.ComparisonStatusSynced,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond = ctrl.autoSync(app, &compRes)
assert.Nil(t, cond)
app, err = ctrl.applicationClientset.ArgoprojV1alpha1().Applications("argocd").Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
assert.Nil(t, app.Operation)
// Verify we skip when auto-sync is disabled
app = newFakeApp()
app.Spec.SyncPolicy = nil
ctrl = newFakeController(app)
compRes = argoappv1.ComparisonResult{
Status: argoappv1.ComparisonStatusOutOfSync,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond = ctrl.autoSync(app, &compRes)
assert.Nil(t, cond)
app, err = ctrl.applicationClientset.ArgoprojV1alpha1().Applications("argocd").Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
assert.Nil(t, app.Operation)
// Verify we skip when previous sync attempt failed and return error condition
// Set current to 'aaaaa', desired to 'bbbbb' and add 'bbbbb' to failure history
app = newFakeApp()
app.Status.OperationState = &argoappv1.OperationState{
Operation: argoappv1.Operation{
Sync: &argoappv1.SyncOperation{},
},
Phase: argoappv1.OperationFailed,
SyncResult: &argoappv1.SyncOperationResult{
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
},
}
ctrl = newFakeController(app)
compRes = argoappv1.ComparisonResult{
Status: argoappv1.ComparisonStatusOutOfSync,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond = ctrl.autoSync(app, &compRes)
assert.NotNil(t, cond)
app, err = ctrl.applicationClientset.ArgoprojV1alpha1().Applications("argocd").Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
assert.Nil(t, app.Operation)
}
// TestAutoSyncIndicateError verifies we skip auto-sync and return error condition if previous sync failed
func TestAutoSyncIndicateError(t *testing.T) {
app := newFakeApp()
app.Spec.Source.ComponentParameterOverrides = []argoappv1.ComponentParameter{
{
Name: "a",
Value: "1",
},
}
ctrl := newFakeController(app)
compRes := argoappv1.ComparisonResult{
Status: argoappv1.ComparisonStatusOutOfSync,
Revision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
}
app.Status.OperationState = &argoappv1.OperationState{
Operation: argoappv1.Operation{
Sync: &argoappv1.SyncOperation{
ParameterOverrides: argoappv1.ParameterOverrides{
{
Name: "a",
Value: "1",
},
},
},
},
Phase: argoappv1.OperationFailed,
SyncResult: &argoappv1.SyncOperationResult{
Revision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
}
cond := ctrl.autoSync(app, &compRes)
assert.NotNil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications("argocd").Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
assert.Nil(t, app.Operation)
}
// TestAutoSyncParameterOverrides verifies we auto-sync if revision is same but parameter overrides are different
func TestAutoSyncParameterOverrides(t *testing.T) {
app := newFakeApp()
app.Spec.Source.ComponentParameterOverrides = []argoappv1.ComponentParameter{
{
Name: "a",
Value: "1",
},
}
ctrl := newFakeController(app)
compRes := argoappv1.ComparisonResult{
Status: argoappv1.ComparisonStatusOutOfSync,
Revision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
}
app.Status.OperationState = &argoappv1.OperationState{
Operation: argoappv1.Operation{
Sync: &argoappv1.SyncOperation{
ParameterOverrides: argoappv1.ParameterOverrides{
{
Name: "a",
Value: "2", // this value changed
},
},
},
},
Phase: argoappv1.OperationFailed,
SyncResult: &argoappv1.SyncOperationResult{
Revision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
}
cond := ctrl.autoSync(app, &compRes)
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications("argocd").Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
assert.NotNil(t, app.Operation)
}

View File

@@ -3,16 +3,9 @@ package controller
import (
"context"
"encoding/json"
"runtime/debug"
"time"
"runtime/debug"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/db"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -25,6 +18,12 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/git"
)
type SecretController struct {
@@ -93,22 +92,13 @@ func (ctrl *SecretController) getRepoConnectionState(repo *v1alpha1.Repository)
ModifiedAt: repo.ConnectionState.ModifiedAt,
Status: v1alpha1.ConnectionStatusUnknown,
}
closer, client, err := ctrl.repoClientset.NewRepositoryClient()
if err != nil {
log.Errorf("Unable to create repository client: %v", err)
return state
}
defer util.Close(closer)
_, err = client.ListDir(context.Background(), &repository.ListDirRequest{Repo: repo, Path: ".gitignore"})
err := git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey)
if err == nil {
state.Status = v1alpha1.ConnectionStatusSuccessful
} else {
state.Status = v1alpha1.ConnectionStatusFailed
state.Message = err.Error()
}
return state
}

View File

@@ -7,6 +7,7 @@ import (
"time"
log "github.com/sirupsen/logrus"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
@@ -35,10 +36,11 @@ type AppStateManager interface {
SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
}
// ksonnetAppStateManager allows to compare application using KSonnet CLI
type ksonnetAppStateManager struct {
// appStateManager allows to compare application using KSonnet CLI
type appStateManager struct {
db db.ArgoDB
appclientset appclientset.Interface
kubectl kubeutil.Kubectl
repoClientset reposerver.Clientset
namespace string
}
@@ -83,7 +85,7 @@ func groupLiveObjects(liveObjs []*unstructured.Unstructured, targetObjs []*unstr
return liveByFullName
}
func (s *ksonnetAppStateManager) getTargetObjs(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, *repository.ManifestResponse, error) {
func (s *appStateManager) getTargetObjs(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, *repository.ManifestResponse, error) {
repo := s.getRepo(app.Spec.Source.RepoURL)
conn, repoClient, err := s.repoClientset.NewRepositoryClient()
if err != nil {
@@ -121,6 +123,7 @@ func (s *ksonnetAppStateManager) getTargetObjs(app *v1alpha1.Application, revisi
ComponentParameterOverrides: mfReqOverrides,
AppLabel: app.Name,
ValueFiles: app.Spec.Source.ValuesFiles,
Namespace: app.Spec.Destination.Namespace,
})
if err != nil {
return nil, nil, err
@@ -140,7 +143,7 @@ func (s *ksonnetAppStateManager) getTargetObjs(app *v1alpha1.Application, revisi
return targetObjs, manifestInfo, nil
}
func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetObjs []*unstructured.Unstructured) (
func (s *appStateManager) getLiveObjs(app *v1alpha1.Application, targetObjs []*unstructured.Unstructured) (
[]*unstructured.Unstructured, map[string]*unstructured.Unstructured, error) {
// Get the REST config for the cluster corresponding to the environment
@@ -168,7 +171,10 @@ func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetOb
controlledLiveObj := make([]*unstructured.Unstructured, len(targetObjs))
// Move live resources which have corresponding target object to controlledLiveObj
dynClientPool := dynamic.NewDynamicClientPool(restConfig)
dynamicIf, err := dynamic.NewForConfig(restConfig)
if err != nil {
return nil, nil, err
}
disco, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
return nil, nil, err
@@ -182,30 +188,29 @@ func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetOb
// of ArgoCD. In order to determine that it is truly missing, we fall back to perform a
// direct lookup of the resource by name. See issue #141
gvk := targetObj.GroupVersionKind()
dclient, err := dynClientPool.ClientForGroupVersionKind(gvk)
if err != nil {
return nil, nil, err
}
apiResource, err := kubeutil.ServerResourceForGroupVersionKind(disco, gvk)
if err != nil {
return nil, nil, err
}
liveObj, err = kubeutil.GetLiveResource(dclient, targetObj, apiResource, app.Spec.Destination.Namespace)
if err != nil {
return nil, nil, err
if !apierr.IsNotFound(err) {
return nil, nil, err
}
// If we get here, the app is comprised of a custom resource which has yet to be registered
} else {
liveObj, err = kubeutil.GetLiveResource(dynamicIf, targetObj, apiResource, app.Spec.Destination.Namespace)
if err != nil {
return nil, nil, err
}
}
}
controlledLiveObj[i] = liveObj
delete(liveObjByFullName, fullName)
}
return controlledLiveObj, liveObjByFullName, nil
}
// CompareAppState compares application git state to the live app state, using the specified
// revision and supplied overrides. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
func (s *ksonnetAppStateManager) CompareAppState(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) (
func (s *appStateManager) CompareAppState(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) (
*v1alpha1.ComparisonResult, *repository.ManifestResponse, []v1alpha1.ApplicationCondition, error) {
failedToLoadObjs := false
@@ -318,6 +323,10 @@ func (s *ksonnetAppStateManager) CompareAppState(app *v1alpha1.Application, revi
Resources: resources,
Status: comparisonStatus,
}
if manifestInfo != nil {
compResult.Revision = manifestInfo.Revision
}
return &compResult, manifestInfo, conditions, nil
}
@@ -360,7 +369,7 @@ func getResourceFullName(obj *unstructured.Unstructured) string {
return fmt.Sprintf("%s:%s", obj.GetKind(), obj.GetName())
}
func (s *ksonnetAppStateManager) getRepo(repoURL string) *v1alpha1.Repository {
func (s *appStateManager) getRepo(repoURL string) *v1alpha1.Repository {
repo, err := s.db.GetRepository(context.Background(), repoURL)
if err != nil {
// If we couldn't retrieve from the repo service, assume public repositories
@@ -369,7 +378,7 @@ func (s *ksonnetAppStateManager) getRepo(repoURL string) *v1alpha1.Repository {
return repo
}
func (s *ksonnetAppStateManager) persistDeploymentInfo(
func (s *appStateManager) persistDeploymentInfo(
app *v1alpha1.Application, revision string, envParams []*v1alpha1.ComponentParameter, overrides *[]v1alpha1.ComponentParameter) error {
params := make([]v1alpha1.ComponentParameter, len(envParams))
@@ -384,7 +393,6 @@ func (s *ksonnetAppStateManager) persistDeploymentInfo(
history := append(app.Status.History, v1alpha1.DeploymentInfo{
ComponentParameterOverrides: app.Spec.Source.ComponentParameterOverrides,
Revision: revision,
Params: params,
DeployedAt: metav1.NewTime(time.Now().UTC()),
ID: nextID,
})
@@ -411,10 +419,12 @@ func NewAppStateManager(
appclientset appclientset.Interface,
repoClientset reposerver.Clientset,
namespace string,
kubectl kubeutil.Kubectl,
) AppStateManager {
return &ksonnetAppStateManager{
return &appStateManager{
db: db,
appclientset: appclientset,
kubectl: kubectl,
repoClientset: repoClientset,
namespace: namespace,
}

52
controller/state_test.go Normal file
View File

@@ -0,0 +1,52 @@
package controller
import (
"testing"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var podManifest = []byte(`
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: nginx:1.7.9
name: nginx
resources:
requests:
cpu: 0.2
`)
func newPod() *unstructured.Unstructured {
var un unstructured.Unstructured
err := yaml.Unmarshal(podManifest, &un)
if err != nil {
panic(err)
}
return &un
}
func TestIsHook(t *testing.T) {
pod := newPod()
assert.False(t, isHook(pod))
pod.SetAnnotations(map[string]string{"helm.sh/hook": "post-install"})
assert.True(t, isHook(pod))
pod = newPod()
pod.SetAnnotations(map[string]string{"argocd.argoproj.io/hook": "PreSync"})
assert.True(t, isHook(pod))
pod = newPod()
pod.SetAnnotations(map[string]string{"argocd.argoproj.io/hook": "Skip"})
assert.False(t, isHook(pod))
pod = newPod()
pod.SetAnnotations(map[string]string{"argocd.argoproj.io/hook": "Unknown"})
assert.False(t, isHook(pod))
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"sync"
@@ -29,13 +30,16 @@ import (
type syncContext struct {
appName string
proj *appv1.AppProject
comparison *appv1.ComparisonResult
config *rest.Config
dynClientPool dynamic.ClientPool
disco *discovery.DiscoveryClient
dynamicIf dynamic.Interface
disco discovery.DiscoveryInterface
kubectl kube.Kubectl
namespace string
syncOp *appv1.SyncOperation
syncRes *appv1.SyncOperationResult
syncResources []appv1.SyncOperationResource
opState *appv1.OperationState
manifestInfo *repository.ManifestResponse
log *log.Entry
@@ -43,8 +47,8 @@ type syncContext struct {
lock sync.Mutex
}
func (s *ksonnetAppStateManager) SyncAppState(app *appv1.Application, state *appv1.OperationState) {
// Sync requests are usually requested with ambiguous revisions (e.g. master, HEAD, v1.2.3).
func (s *appStateManager) SyncAppState(app *appv1.Application, state *appv1.OperationState) {
// Sync requests might be requested with ambiguous revisions (e.g. master, HEAD, v1.2.3).
// This can change meaning when resuming operations (e.g a hook sync). After calculating a
// concrete git commit SHA, the SHA is remembered in the status.operationState.syncResult and
// rollbackResult fields. This ensures that when resuming an operation, we sync to the same
@@ -52,10 +56,13 @@ func (s *ksonnetAppStateManager) SyncAppState(app *appv1.Application, state *app
var revision string
var syncOp appv1.SyncOperation
var syncRes *appv1.SyncOperationResult
var syncResources []appv1.SyncOperationResource
var overrides []appv1.ComponentParameter
if state.Operation.Sync != nil {
syncOp = *state.Operation.Sync
syncResources = syncOp.Resources
overrides = []appv1.ComponentParameter(state.Operation.Sync.ParameterOverrides)
if state.SyncResult != nil {
syncRes = state.SyncResult
revision = state.SyncResult.Revision
@@ -63,34 +70,6 @@ func (s *ksonnetAppStateManager) SyncAppState(app *appv1.Application, state *app
syncRes = &appv1.SyncOperationResult{}
state.SyncResult = syncRes
}
} else if state.Operation.Rollback != nil {
var deploymentInfo *appv1.DeploymentInfo
for _, info := range app.Status.History {
if info.ID == app.Operation.Rollback.ID {
deploymentInfo = &info
break
}
}
if deploymentInfo == nil {
state.Phase = appv1.OperationFailed
state.Message = fmt.Sprintf("application %s does not have deployment with id %v", app.Name, app.Operation.Rollback.ID)
return
}
// Rollback is just a convenience around Sync
syncOp = appv1.SyncOperation{
Revision: deploymentInfo.Revision,
DryRun: state.Operation.Rollback.DryRun,
Prune: state.Operation.Rollback.Prune,
SyncStrategy: &appv1.SyncStrategy{Apply: &appv1.SyncStrategyApply{}},
}
overrides = deploymentInfo.ComponentParameterOverrides
if state.RollbackResult != nil {
syncRes = state.RollbackResult
revision = state.RollbackResult.Revision
} else {
syncRes = &appv1.SyncOperationResult{}
state.RollbackResult = syncRes
}
} else {
state.Phase = appv1.OperationFailed
state.Message = "Invalid operation request: no operation specified"
@@ -103,6 +82,7 @@ func (s *ksonnetAppStateManager) SyncAppState(app *appv1.Application, state *app
// Take the value in the requested operation. We will resolve this to a SHA later.
revision = syncOp.Revision
}
comparison, manifestInfo, conditions, err := s.CompareAppState(app, revision, overrides)
if err != nil {
state.Phase = appv1.OperationError
@@ -132,23 +112,38 @@ func (s *ksonnetAppStateManager) SyncAppState(app *appv1.Application, state *app
}
restConfig := clst.RESTConfig()
dynClientPool := dynamic.NewDynamicClientPool(restConfig)
disco, err := discovery.NewDiscoveryClientForConfig(restConfig)
dynamicIf, err := dynamic.NewForConfig(restConfig)
if err != nil {
state.Phase = appv1.OperationError
state.Message = fmt.Sprintf("Failed to initialize dynamic client: %v", err)
return
}
disco, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
state.Phase = appv1.OperationError
state.Message = fmt.Sprintf("Failed to initialize discovery client: %v", err)
return
}
proj, err := argo.GetAppProject(&app.Spec, s.appclientset, s.namespace)
if err != nil {
state.Phase = appv1.OperationError
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
return
}
syncCtx := syncContext{
appName: app.Name,
proj: proj,
comparison: comparison,
config: restConfig,
dynClientPool: dynClientPool,
dynamicIf: dynamicIf,
disco: disco,
kubectl: s.kubectl,
namespace: app.Spec.Destination.Namespace,
syncOp: &syncOp,
syncRes: syncRes,
syncResources: syncResources,
opState: state,
manifestInfo: manifestInfo,
log: log.WithFields(log.Fields{"application": app.Name}),
@@ -160,7 +155,7 @@ func (s *ksonnetAppStateManager) SyncAppState(app *appv1.Application, state *app
syncCtx.sync()
}
if !syncOp.DryRun && syncCtx.opState.Phase.Successful() {
if !syncOp.DryRun && len(syncOp.Resources) == 0 && syncCtx.opState.Phase.Successful() {
err := s.persistDeploymentInfo(app, manifestInfo.Revision, manifestInfo.Params, nil)
if err != nil {
state.Phase = appv1.OperationError
@@ -249,13 +244,22 @@ func (sc *syncContext) generateSyncTasks() ([]syncTask, bool) {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("Failed to unmarshal target object: %v", err))
return nil, false
}
syncTask := syncTask{
liveObj: liveObj,
targetObj: targetObj,
if sc.syncResources == nil || argo.ContainsSyncResource(liveObj, sc.syncResources) || argo.ContainsSyncResource(targetObj, sc.syncResources) {
syncTask := syncTask{
liveObj: liveObj,
targetObj: targetObj,
}
syncTasks = append(syncTasks, syncTask)
}
syncTasks = append(syncTasks, syncTask)
}
return syncTasks, true
if len(syncTasks) == 0 {
if len(sc.comparison.Resources) == 0 {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("Application has no resources"))
} else {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("Specified resources filter does not match any application resource"))
}
}
return syncTasks, len(syncTasks) > 0
}
// startedPreSyncPhase detects if we already started the PreSync stage of a sync operation.
@@ -310,12 +314,13 @@ func (sc *syncContext) applyObject(targetObj *unstructured.Unstructured, dryRun
Kind: targetObj.GetKind(),
Namespace: sc.namespace,
}
message, err := kube.ApplyResource(sc.config, targetObj, sc.namespace, dryRun, force)
message, err := sc.kubectl.ApplyResource(sc.config, targetObj, sc.namespace, dryRun, force)
if err != nil {
resDetails.Message = err.Error()
resDetails.Status = appv1.ResourceDetailsSyncFailed
return resDetails
}
resDetails.Message = message
resDetails.Status = appv1.ResourceDetailsSynced
return resDetails
@@ -333,7 +338,7 @@ func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dr
resDetails.Message = "pruned (dry run)"
resDetails.Status = appv1.ResourceDetailsSyncedAndPruned
} else {
err := kube.DeleteResource(sc.config, liveObj, sc.namespace)
err := sc.kubectl.DeleteResource(sc.config, liveObj, sc.namespace)
if err != nil {
resDetails.Message = err.Error()
resDetails.Status = appv1.ResourceDetailsSyncFailed
@@ -349,26 +354,49 @@ func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dr
return resDetails
}
func hasCRDOfGroupKind(tasks []syncTask, group, kind string) bool {
for _, task := range tasks {
if kube.IsCRD(task.targetObj) {
crdGroup, ok, err := unstructured.NestedString(task.targetObj.Object, "spec", "group")
if err != nil || !ok {
continue
}
crdKind, ok, err := unstructured.NestedString(task.targetObj.Object, "spec", "names", "kind")
if err != nil || !ok {
continue
}
if group == crdGroup && crdKind == kind {
return true
}
}
}
return false
}
// performs a apply based sync of the given sync tasks (possibly pruning the objects).
// If update is true, will updates the resource details with the result.
// Or if the prune/apply failed, will also update the result.
func (sc *syncContext) doApplySync(syncTasks []syncTask, dryRun, force, update bool) bool {
syncSuccessful := true
// apply all resources in parallel
var createTasks []syncTask
var pruneTasks []syncTask
for _, syncTask := range syncTasks {
if syncTask.targetObj == nil {
pruneTasks = append(pruneTasks, syncTask)
} else {
createTasks = append(createTasks, syncTask)
}
}
sort.Sort(newKindSorter(createTasks, resourceOrder))
var wg sync.WaitGroup
for _, task := range syncTasks {
for _, task := range pruneTasks {
wg.Add(1)
go func(t syncTask) {
defer wg.Done()
var resDetails appv1.ResourceDetails
if t.targetObj == nil {
resDetails = sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
} else {
if isHook(t.targetObj) {
return
}
resDetails = sc.applyObject(t.targetObj, dryRun, force)
}
resDetails = sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
if !resDetails.Status.Successful() {
syncSuccessful = false
}
@@ -378,6 +406,76 @@ func (sc *syncContext) doApplySync(syncTasks []syncTask, dryRun, force, update b
}(task)
}
wg.Wait()
processCreateTasks := func(tasks []syncTask, gvk schema.GroupVersionKind) {
serverRes, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
if err != nil {
// Special case for custom resources: if custom resource definition is not supported by the cluster by defined in application then
// skip verification using `kubectl apply --dry-run` and since CRD should be created during app synchronization.
if dryRun && apierr.IsNotFound(err) && hasCRDOfGroupKind(createTasks, gvk.Group, gvk.Kind) {
return
} else {
syncSuccessful = false
for _, task := range tasks {
sc.setResourceDetails(&appv1.ResourceDetails{
Name: task.targetObj.GetName(),
Kind: task.targetObj.GetKind(),
Namespace: sc.namespace,
Message: err.Error(),
Status: appv1.ResourceDetailsSyncFailed,
})
}
return
}
}
if !sc.proj.IsResourcePermitted(metav1.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, serverRes.Namespaced) {
syncSuccessful = false
for _, task := range tasks {
sc.setResourceDetails(&appv1.ResourceDetails{
Name: task.targetObj.GetName(),
Kind: task.targetObj.GetKind(),
Namespace: sc.namespace,
Message: fmt.Sprintf("Resource %s:%s is not permitted in project %s.", gvk.Group, gvk.Kind, sc.proj.Name),
Status: appv1.ResourceDetailsSyncFailed,
})
}
return
}
var createWg sync.WaitGroup
for i := range tasks {
createWg.Add(1)
go func(t syncTask) {
defer createWg.Done()
if isHook(t.targetObj) {
return
}
resDetails := sc.applyObject(t.targetObj, dryRun, force)
if !resDetails.Status.Successful() {
syncSuccessful = false
}
if update || !resDetails.Status.Successful() {
sc.setResourceDetails(&resDetails)
}
}(tasks[i])
}
createWg.Wait()
}
var tasksGroup []syncTask
for _, task := range createTasks {
//Only wait if the type of the next task is different than the previous type
if len(tasksGroup) > 0 && tasksGroup[0].targetObj.GetKind() != task.targetObj.GetKind() {
processCreateTasks(tasksGroup, tasksGroup[0].targetObj.GroupVersionKind())
tasksGroup = []syncTask{task}
} else {
tasksGroup = append(tasksGroup, task)
}
}
if len(tasksGroup) > 0 {
processCreateTasks(tasksGroup, tasksGroup[0].targetObj.GroupVersionKind())
}
return syncSuccessful
}
@@ -412,7 +510,7 @@ func (sc *syncContext) doHookSync(syncTasks []syncTask, hooks []*unstructured.Un
// already started the post-sync phase, then we do not need to perform the health check.
postSyncHooks, _ := sc.getHooks(appv1.HookTypePostSync)
if len(postSyncHooks) > 0 && !sc.startedPostSyncPhase() {
healthState, err := setApplicationHealth(sc.comparison)
healthState, err := setApplicationHealth(sc.kubectl, sc.comparison)
sc.log.Infof("PostSync application health check: %s", healthState.Status)
if err != nil {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("failed to check application health: %v", err))
@@ -534,15 +632,12 @@ func (sc *syncContext) runHook(hook *unstructured.Unstructured, hookType appv1.H
}
gvk := hook.GroupVersionKind()
dclient, err := sc.dynClientPool.ClientForGroupVersionKind(gvk)
if err != nil {
return false, err
}
apiResource, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
if err != nil {
return false, err
}
resIf := dclient.Resource(apiResource, sc.namespace)
resource := kube.ToGroupVersionResource(gvk.GroupVersion().String(), apiResource)
resIf := kube.ToResourceInterface(sc.dynamicIf, apiResource, resource, sc.namespace)
var liveObj *unstructured.Unstructured
existing, err := resIf.Get(hook.GetName(), metav1.GetOptions{})
@@ -555,7 +650,7 @@ func (sc *syncContext) runHook(hook *unstructured.Unstructured, hookType appv1.H
if err != nil {
sc.log.Warnf("Failed to set application label on hook %v: %v", hook, err)
}
_, err := kube.ApplyResource(sc.config, hook, sc.namespace, false, false)
_, err := sc.kubectl.ApplyResource(sc.config, hook, sc.namespace, false, false)
if err != nil {
return false, fmt.Errorf("Failed to create %s hook %s '%s': %v", hookType, gvk, hook.GetName(), err)
}
@@ -627,7 +722,7 @@ func isHelmHook(obj *unstructured.Unstructured) bool {
if annotations == nil {
return false
}
_, ok := annotations[common.AnnotationHook]
_, ok := annotations[common.AnnotationHelmHook]
return ok
}
@@ -845,14 +940,97 @@ func (sc *syncContext) deleteHook(name, kind, apiVersion string) error {
Version: groupVersion[1],
Kind: kind,
}
dclient, err := sc.dynClientPool.ClientForGroupVersionKind(gvk)
if err != nil {
return err
}
apiResource, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
if err != nil {
return err
}
resIf := dclient.Resource(apiResource, sc.namespace)
resource := kube.ToGroupVersionResource(gvk.GroupVersion().String(), apiResource)
resIf := kube.ToResourceInterface(sc.dynamicIf, apiResource, resource, sc.namespace)
return resIf.Delete(name, &metav1.DeleteOptions{})
}
// This code is mostly taken from https://github.com/helm/helm/blob/release-2.10/pkg/tiller/kind_sorter.go
// sortOrder is an ordering of Kinds.
type sortOrder []string
// resourceOrder represents the correct order of Kubernetes resources within a manifest
var resourceOrder sortOrder = []string{
"Namespace",
"ResourceQuota",
"LimitRange",
"PodSecurityPolicy",
"Secret",
"ConfigMap",
"StorageClass",
"PersistentVolume",
"PersistentVolumeClaim",
"ServiceAccount",
"CustomResourceDefinition",
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"StatefulSet",
"Job",
"CronJob",
"Ingress",
"APIService",
}
type kindSorter struct {
ordering map[string]int
manifests []syncTask
}
func newKindSorter(m []syncTask, s sortOrder) *kindSorter {
o := make(map[string]int, len(s))
for v, k := range s {
o[k] = v
}
return &kindSorter{
manifests: m,
ordering: o,
}
}
func (k *kindSorter) Len() int { return len(k.manifests) }
func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] }
func (k *kindSorter) Less(i, j int) bool {
a := k.manifests[i].targetObj
if a == nil {
return false
}
b := k.manifests[j].targetObj
if b == nil {
return true
}
first, aok := k.ordering[a.GetKind()]
second, bok := k.ordering[b.GetKind()]
// if same kind (including unknown) sub sort alphanumeric
if first == second {
// if both are unknown and of different kind sort by kind alphabetically
if !aok && !bok && a.GetKind() != b.GetKind() {
return a.GetKind() < b.GetKind()
}
return a.GetName() < b.GetName()
}
// unknown kind is last
if !aok {
return false
}
if !bok {
return true
}
// sort different kinds
return first < second
}

View File

@@ -1,26 +1,402 @@
package controller
import (
"context"
"fmt"
"sort"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
fakedisco "k8s.io/client-go/discovery/fake"
"k8s.io/client-go/rest"
testcore "k8s.io/client-go/testing"
)
func newTestSyncCtx() *syncContext {
type kubectlOutput struct {
output string
err error
}
type mockKubectlCmd struct {
commands map[string]kubectlOutput
events chan watch.Event
}
func (k mockKubectlCmd) WatchResources(
ctx context.Context, config *rest.Config, namespace string, selector func(kind schema.GroupVersionKind) v1.ListOptions) (chan watch.Event, error) {
return k.events, nil
}
func (k mockKubectlCmd) DeleteResource(config *rest.Config, obj *unstructured.Unstructured, namespace string) error {
command, ok := k.commands[obj.GetName()]
if !ok {
return nil
}
return command.err
}
func (k mockKubectlCmd) ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string, dryRun, force bool) (string, error) {
command, ok := k.commands[obj.GetName()]
if !ok {
return "", nil
}
return command.output, command.err
}
// ConvertToVersion converts an unstructured object into the specified group/version
func (k mockKubectlCmd) ConvertToVersion(obj *unstructured.Unstructured, group, version string) (*unstructured.Unstructured, error) {
return obj, nil
}
func newTestSyncCtx(resources ...*v1.APIResourceList) *syncContext {
fakeDisco := &fakedisco.FakeDiscovery{Fake: &testcore.Fake{}}
fakeDisco.Resources = append(resources, &v1.APIResourceList{
APIResources: []v1.APIResource{
{Kind: "pod", Namespaced: true},
{Kind: "deployment", Namespaced: true},
{Kind: "service", Namespaced: true},
},
})
kube.FlushServerResourcesCache()
return &syncContext{
comparison: &v1alpha1.ComparisonResult{},
config: &rest.Config{},
namespace: "test-namespace",
syncOp: &v1alpha1.SyncOperation{},
opState: &v1alpha1.OperationState{},
log: log.WithFields(log.Fields{"application": "fake-app"}),
syncRes: &v1alpha1.SyncOperationResult{},
syncOp: &v1alpha1.SyncOperation{
Prune: true,
SyncStrategy: &v1alpha1.SyncStrategy{
Apply: &v1alpha1.SyncStrategyApply{},
},
},
proj: &v1alpha1.AppProject{
ObjectMeta: v1.ObjectMeta{
Name: "test",
},
Spec: v1alpha1.AppProjectSpec{
ClusterResourceWhitelist: []v1.GroupKind{
{Group: "*", Kind: "*"},
},
},
},
opState: &v1alpha1.OperationState{},
disco: fakeDisco,
log: log.WithFields(log.Fields{"application": "fake-app"}),
}
}
func TestSyncCreateInSortedOrder(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "",
TargetState: "{\"kind\":\"pod\"}",
}, {
LiveState: "",
TargetState: "{\"kind\":\"service\"}",
},
},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 2)
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "pod" {
assert.Equal(t, v1alpha1.ResourceDetailsSynced, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "service" {
assert.Equal(t, v1alpha1.ResourceDetailsSynced, syncCtx.syncRes.Resources[i].Status)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncCreateNotWhitelistedClusterResources(t *testing.T) {
syncCtx := newTestSyncCtx(&v1.APIResourceList{
GroupVersion: v1alpha1.SchemeGroupVersion.String(),
APIResources: []v1.APIResource{
{Name: "workflows", Namespaced: false, Kind: "Workflow", Group: "argoproj.io"},
{Name: "application", Namespaced: false, Kind: "Application", Group: "argoproj.io"},
},
}, &v1.APIResourceList{
GroupVersion: "rbac.authorization.k8s.io/v1",
APIResources: []v1.APIResource{
{Name: "clusterroles", Namespaced: false, Kind: "ClusterRole", Group: "rbac.authorization.k8s.io"},
},
})
syncCtx.proj.Spec.ClusterResourceWhitelist = []v1.GroupKind{
{Group: "argoproj.io", Kind: "*"},
}
syncCtx.kubectl = mockKubectlCmd{}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "",
TargetState: `{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "ClusterRole", "metadata": {"name": "argo-ui-cluster-role" }}`,
}},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResourceDetailsSyncFailed, syncCtx.syncRes.Resources[0].Status)
assert.Contains(t, syncCtx.syncRes.Resources[0].Message, "not permitted in project")
}
func TestSyncBlacklistedNamespacedResources(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.proj.Spec.NamespaceResourceBlacklist = []v1.GroupKind{
{Group: "*", Kind: "deployment"},
}
syncCtx.kubectl = mockKubectlCmd{}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "",
TargetState: "{\"kind\":\"deployment\"}",
}},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResourceDetailsSyncFailed, syncCtx.syncRes.Resources[0].Status)
assert.Contains(t, syncCtx.syncRes.Resources[0].Message, "not permitted in project")
}
func TestSyncSuccessfully(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "",
TargetState: "{\"kind\":\"service\"}",
}, {
LiveState: "{\"kind\":\"pod\"}",
TargetState: "",
},
},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 2)
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "pod" {
assert.Equal(t, v1alpha1.ResourceDetailsSyncedAndPruned, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "service" {
assert.Equal(t, v1alpha1.ResourceDetailsSynced, syncCtx.syncRes.Resources[i].Status)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncDeleteSuccessfully(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "{\"kind\":\"service\"}",
TargetState: "",
}, {
LiveState: "{\"kind\":\"pod\"}",
TargetState: "",
},
},
}
syncCtx.sync()
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "pod" {
assert.Equal(t, v1alpha1.ResourceDetailsSyncedAndPruned, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "service" {
assert.Equal(t, v1alpha1.ResourceDetailsSyncedAndPruned, syncCtx.syncRes.Resources[i].Status)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncCreateFailure(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{
commands: map[string]kubectlOutput{
"test-service": {
output: "",
err: fmt.Errorf("error: error validating \"test.yaml\": error validating data: apiVersion not set; if you choose to ignore these errors, turn validation off with --validate=false"),
},
},
}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "",
TargetState: "{\"kind\":\"service\", \"metadata\":{\"name\":\"test-service\"}}",
},
},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResourceDetailsSyncFailed, syncCtx.syncRes.Resources[0].Status)
}
func TestSyncPruneFailure(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{
commands: map[string]kubectlOutput{
"test-service": {
output: "",
err: fmt.Errorf(" error: timed out waiting for \"test-service\" to be synced"),
},
},
}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "{\"kind\":\"service\", \"metadata\":{\"name\":\"test-service\"}}",
TargetState: "",
},
},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResourceDetailsSyncFailed, syncCtx.syncRes.Resources[0].Status)
}
func TestRunWorkflows(t *testing.T) {
// syncCtx := newTestSyncCtx()
// syncCtx.doWorkflowSync(nil, nil)
}
func unsortedManifest() []syncTask {
return []syncTask{
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
}
}
func sortedManifest() []syncTask {
return []syncTask{
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
}
}
func TestSortKubernetesResourcesSuccessfully(t *testing.T) {
unsorted := unsortedManifest()
ks := newKindSorter(unsorted, resourceOrder)
sort.Sort(ks)
expectedOrder := sortedManifest()
assert.Equal(t, len(unsorted), len(expectedOrder))
for i, sorted := range unsorted {
assert.Equal(t, expectedOrder[i], sorted)
}
}
func TestSortManifestHandleNil(t *testing.T) {
task := syncTask{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
}
manifest := []syncTask{
{},
task,
}
ks := newKindSorter(manifest, resourceOrder)
sort.Sort(ks)
assert.Equal(t, task, manifest[0])
assert.Nil(t, manifest[1].targetObj)
}

View File

@@ -9,8 +9,14 @@
## Features
* [Application Sources](application_sources.md)
* [Application Parameters](parameters.md)
* [Projects](projects.md)
* [Automated Sync](auto_sync.md)
* [Resource Health](health.md)
* [Resource Hooks](resource_hooks.md)
* [Single Sign On](sso.md)
* [Webhooks](webhook.md)
* [RBAC](rbac.md)
* [RBAC](rbac.md)
## Other
* [Configuring Ingress](ingress.md)
* [F.A.Q.](faq.md)

View File

@@ -3,8 +3,9 @@
ArgoCD supports several different ways in which kubernetes manifests can be defined:
* [ksonnet](https://ksonnet.io) applications
* [kustomize](https://kustomize.io) applications
* [helm](https://helm.sh) charts
* Simple directory of YAML/json manifests
* Directory of YAML/json/jsonnet manifests
Some additional considerations should be made when deploying apps of a particular type:

View File

@@ -50,3 +50,29 @@ spec:
server: https://kubernetes.default.svc
namespace: default
```
### AppProject CRD (Custom Resource Definition)
The AppProject CRD is the Kubernetes resource object representing a grouping of applications. It is defined by three key pieces of information:
* `sourceRepos` reference to the reposities that applications within the project can pull manifests from.
* `destinations` reference to clusters and namespaces that applications within the project can deploy into.
* `roles` list of entities with defintions of their access to resources within the project.
An example spec is as follows:
```
spec:
description: Description of the project
destinations:
- namespace: default
server: https://kubernetes.default.svc
roles:
- description: Description of the role
jwtTokens:
- iat: 1535390316
name: role-name
policies:
- p, proj:proj-name:role-name, applications, get, proj-name/*, allow
- p, proj:proj-name:role-name, applications, sync, proj-name/*, deny
sourceRepos:
- https://github.com/argoproj/argocd-example-apps.git
```

51
docs/auto_sync.md Normal file
View File

@@ -0,0 +1,51 @@
# Automated Sync Policy
ArgoCD has the ability to automatically sync an application when it detects differences between
the desired manifests in git, and the live state in the cluster. A benefit of automatic sync is that
CI/CD pipelines no longer need direct access to the ArgoCD API server to perform the deployment.
Instead, the pipeline makes a commit and push to the git repository with the changes to the
manifests in the tracking git repo.
To configure automated sync run:
```bash
argocd app set <APPNAME> --sync-policy automated
```
Alternatively, if creating the application an application manifest, specify a syncPolicy with an
`automated` policy.
```yaml
spec:
syncPolicy:
automated: {}
```
## Automatic Pruning
By default (and as a safety mechanism), automated sync will not delete resources when ArgoCD detects
the resource is no longer defined in git. To prune the resources, a manual sync can always be
performed (with pruning checked). Pruning can also be enabled to happen automatically as part of the
automated sync by running:
```bash
argocd app set <APPNAME> --auto-prune
```
Or by setting the prune option to true in the automated sync policy:
```yaml
spec:
syncPolicy:
automated:
prune: true
```
## Automated Sync Semantics
* An automated sync will only be performed if the application is OutOfSync. Applications in a
Synced or error state will not attempt automated sync.
* Automated sync will only attempt one synchronization per unique combination of commit SHA1 and
application parameters. If the most recent successful sync in the history was already performed
against the same commit-SHA and parameters, a second sync will not be attempted.
* Automatic sync will not reattempt a sync if the previous sync attempt against the same commit-SHA
and parameters had failed.
* Rollback cannot be performed against an application with automated sync enabled.

16
docs/faq.md Normal file
View File

@@ -0,0 +1,16 @@
# FAQ
## Why is my application still `OutOfSync` immediately after a successful Sync?
It is possible for an application to still be `OutOfSync` even immediately after a successful Sync
operation. Some reasons for this might be:
* There may be problems in manifests themselves, which may contain extra/unknown fields from the
actual K8s spec. These extra fields would get dropped when querying Kubernetes for the live state,
resulting in an `OutOfSync` status indicating a missing field was detected.
* The sync was performed (with pruning disabled), and there are resources which need to be deleted.
* A mutating webhook altered the manifest after it was submitted to Kubernetes
To debug `OutOfSync` issues, run the `app diff` command to see the differences between git and live:
```
argocd app diff APPNAME
```

View File

@@ -7,15 +7,15 @@ An example guestbook application is provided to demonstrate how ArgoCD works.
* Have a [kubeconfig](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) file (default location is `~/.kube/config`).
## 1. Install ArgoCD
```
```bash
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v0.7.1/manifests/install.yaml
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v0.9.2/manifests/install.yaml
```
This will create a new namespace, `argocd`, where ArgoCD services and application resources will live.
NOTE:
* On GKE with RBAC enabled, you may need to grant your account the ability to create new cluster roles
```
```bash
kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
```
@@ -24,86 +24,86 @@ kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=c
Download the latest ArgoCD version:
On Mac:
```
```bash
brew install argoproj/tap/argocd
```
On Linux:
```
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.7.1/argocd-linux-amd64
```bash
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.9.2/argocd-linux-amd64
chmod +x /usr/local/bin/argocd
```
## 3. Open access to ArgoCD API server
## 3. Access the ArgoCD API server
By default, the ArgoCD API server is not exposed with an external IP. To expose the API server,
change the service type to `LoadBalancer`:
By default, the ArgoCD API server is not exposed with an external IP. To access the API server,
choose one of the following means to expose the ArgoCD API server:
```
### Service Type LoadBalancer
Change the argocd-server service type to `LoadBalancer`:
```bash
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
```
## 4. Login to the server from the CLI
### Ingress
Follow the [ingress documentation](ingress.md) on how to configure ArgoCD with ingress.
Login with using the `admin` user. The initial password is autogenerated to be the pod name of the
### Port Forwarding
`kubectl port-forward` can also be used to connect to the API server without exposing the service.
The API server can be accessed using the localhost address/port.
## 4. Login using the CLI
Login as the `admin` user. The initial password is autogenerated to be the pod name of the
ArgoCD API server. This can be retrieved with the command:
```
```bash
kubectl get pods -n argocd -l app=argocd-server -o name | cut -d'/' -f 2
```
Using the above password, login to ArgoCD's external IP:
On Minikube:
```
argocd login $(minikube service argocd-server -n argocd --url | cut -d'/' -f 3) --name minikube
```
Other clusters:
```
```bash
kubectl get svc -n argocd argocd-server
argocd login <EXTERNAL-IP>
```
After logging in, change the password using the command:
```
```bash
argocd account update-password
argocd relogin
```
## 5. Register a cluster to deploy apps to
## 5. Register a cluster to deploy apps to (optional)
We will now register a cluster to deploy applications to. First list all clusters contexts in your
kubconfig:
```
This step registers a cluster's credentials to ArgoCD, and is only necessary when deploying to
an external cluster. When deploying internally (to the same cluster that ArgoCD is running in),
https://kubernetes.default.svc should be used as the application's K8s API server address.
First list all clusters contexts in your current kubconfig:
```bash
argocd cluster add
```
Choose a context name from the list and supply it to `argocd cluster add CONTEXTNAME`. For example,
for minikube context, run:
```
argocd cluster add minikube --in-cluster
for docker-for-desktop context, run:
```bash
argocd cluster add docker-for-desktop
```
The above command installs an `argocd-manager` ServiceAccount and ClusterRole into the cluster
associated with the supplied kubectl context. ArgoCD uses the service account token to perform its
associated with the supplied kubectl context. ArgoCD uses this service account token to perform its
management tasks (i.e. deploy/monitoring).
The `--in-cluster` option indicates that the cluster we are registering, is the same cluster that
ArgoCD is running in. This allows ArgoCD to connect to the cluster using the internal kubernetes
hostname (kubernetes.default.svc). When registering a cluster external to ArgoCD, the `--in-cluster`
flag should be omitted.
## 6. Create the application from a git repository
## 6. Create an application from a git repository location
### Creating apps via UI
Open a browser to the ArgoCD external UI, and login using the credentials set in step 4.
On Minikube:
```
minikube service argocd-server -n argocd
```
Open a browser to the ArgoCD external UI, and login using the credentials set in step 4, and the
external IP/hostname set in step 4.
Connect a git repository containing your apps. An example repository containing a sample
guestbook application is available at https://github.com/argoproj/argocd-example-apps.git.
@@ -122,8 +122,8 @@ After connecting a git repository, select the guestbook application for creation
Applications can be also be created using the ArgoCD CLI:
```
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
```bash
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path ksonnet-guestbook
```
## 7. Sync (deploy) the application
@@ -131,10 +131,10 @@ argocd app create guestbook-default --repo https://github.com/argoproj/argocd-ex
Once the guestbook application is created, you can now view its status:
From UI:
![create app](assets/guestbook-app.png)
![guestbook app](assets/guestbook-app.png)
From CLI:
```
```bash
$ argocd app get guestbook-default
Name: guestbook-default
Server: https://kubernetes.default.svc
@@ -153,7 +153,7 @@ Deployment guestbook-ui OutOfSync
The application status is initially in an `OutOfSync` state, since the application has yet to be
deployed, and no Kubernetes resources have been created. To sync (deploy) the application, run:
```
```bash
$ argocd app sync guestbook-default
Application: guestbook-default
Operation: Sync
@@ -166,12 +166,12 @@ Deployment guestbook-ui deployment.apps "guestbook-ui" created
```
This command retrieves the manifests from git repository and performs a `kubectl apply` of the
manifests. The guestbook app is now running and you can now view its resource
components, logs, events, and assessed health:
manifests. The guestbook app is now running and you can now view its resource components, logs,
events, and assessed health status:
![view app](assets/guestbook-tree.png)
## 8. Next Steps
ArgoCD supports additional features such as SSO, WebHooks, RBAC, Projects. See the rest of
the [documentation](./) for details.
ArgoCD supports additional features such as automated sync, SSO, WebHooks, RBAC, Projects. See the
rest of the [documentation](./) for details.

View File

@@ -6,7 +6,6 @@ surfaced to the overall Application health status as a whole. The following chec
specific types of kuberentes resources:
### Deployment, ReplicaSet, StatefulSet DaemonSet
* Observed generation is equal to desired generation.
* Number of **updated** replicas equals the number of desired replicas.
@@ -16,3 +15,6 @@ with at least one value for `hostname` or `IP`.
### Ingress
* The `status.loadBalancer.ingress` list is non-empty, with at least one value for `hostname` or `IP`.
### PersistentVolumeClaim
* The `status.phase` is `Bound`

126
docs/ingress.md Normal file
View File

@@ -0,0 +1,126 @@
# Ingress Configuration
ArgoCD runs both a gRPC server (used by the CLI), as well as a HTTP/HTTPS server (used by the UI).
Both protocols are exposed by the argocd-server service object on the following ports:
* 443 - gRPC/HTTPS
* 80 - HTTP (redirects to HTTPS)
There are several ways how Ingress can be configured.
## [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx)
### Option 1: ssl-passthrough
Because ArgoCD serves multiple protocols (gRPC/HTTPS) on the same port (443), this provides a
challenge when attempting to define a single nginx ingress object and rule for the argocd-service,
since the `nginx.ingress.kubernetes.io/backend-protocol` [annotation](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#backend-protocol)
accepts only a single value for the backend protocol (e.g. HTTP, HTTPS, GRPC, GRPCS).
In order to expose the ArgoCD API server with a single ingress rule and hostname, the
`nginx.ingress.kubernetes.io/ssl-passthrough` [annotation](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#ssl-passthrough)
must be used to passthrough TLS connections and terminate TLS at the ArgoCD API server.
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: argocd-server-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
spec:
rules:
- host: argocd.example.com
http:
paths:
- backend:
serviceName: argocd-server
servicePort: https
```
The above rule terminates TLS at the ArgoCD API server, which detects the protocol being used,
and responds appropriately. Note that the `nginx.ingress.kubernetes.io/ssl-passthrough` annotation
requires that the `--enable-ssl-passthrough` flag be added to the command line arguments to
`nginx-ingress-controller`.
### Option 2: Multiple ingress objects and hosts
Since ingress-nginx Ingress supports only a single protocol per Ingress object, an alternative
way would be to define two Ingress objects. One for HTTP/HTTPS, and the other for gRPC:
HTTP/HTTPS Ingress:
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: argocd-server-http-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
rules:
- http:
paths:
- backend:
serviceName: argocd-server
servicePort: http
host: argocd.example.com
tls:
- hosts:
- argocd.example.com
secretName: argocd-secret
```
gRPC Ingress:
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: argocd-server-grpc-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
spec:
rules:
- http:
paths:
- backend:
serviceName: argocd-server
servicePort: https
host: grpc.argocd.example.com
tls:
- hosts:
- grpc.argocd.example.com
secretName: argocd-secret
```
The API server should then be run with TLS disabled. Edit the `argocd-server` deployment to add the
`--insecure` flag to the argocd-server command:
```yaml
spec:
template:
spec:
name: argocd-server
containers:
- command:
- /argocd-server
- --staticassets
- /shared/app
- --repo-server
- argocd-repo-server:8081
- --insecure
```
The obvious disadvantage to this approach is that this technique require two separate hostnames for
the API server -- one for gRPC and the other for HTTP/HTTPS. However it allow TLS termination to
happen at the ingress controller.
## AWS Application Load Balancers (ALBs) and Classic ELB (HTTP mode)
Neither ALBs and Classic ELB in HTTP mode, do not have full support for HTTP2/gRPC which is the
protocol used by the `argocd` CLI. Thus, when using an AWS load balancer, either Classic ELB in
passthrough mode is needed, or NLBs.

View File

@@ -0,0 +1,47 @@
# ArgoCD Release Instructions
1. Tag, build, and push argo-cd-ui
```bash
cd argo-cd-ui
git checkout -b release-X.Y
git tag vX.Y.Z
git push upstream release-X.Y --tags
IMAGE_NAMESPACE=argoproj IMAGE_TAG=vX.Y.Z DOCKER_PUSH=true yarn docker
```
2. Create release-X.Y branch (if creating initial X.Y release)
```bash
git checkout -b release-X.Y
git push upstream release-X.Y
```
3. Update VERSION and manifests with new version
```bash
vi VERSION # ensure value is desired X.Y.Z semantic version
vi manifests/base/kustomization.yaml # update with new image tags
make manifests
git commit -a -m "Update manifests to vX.Y.Z"
git push upstream release-X.Y
```
4. Tag, build, and push release to docker hub
```bash
git tag vX.Y.Z
make release IMAGE_NAMESPACE=argoproj IMAGE_TAG=vX.Y.Z DOCKER_PUSH=true
git push upstream vX.Y.Z
```
5. Update argocd brew formula
```bash
git clone https://github.com/argoproj/homebrew-tap
cd homebrew-tap
shasum -a 256 ~/go/src/github.com/argoproj/argo-cd/dist/argocd-darwin-amd64
# edit argocd.rb with version and checksum
git commit -a -m "Update argocd to vX.Y.Z"
git push
```
6. Update documentation:
* Edit CHANGELOG.md with release notes
* Update getting_started.md with new version
* Create GitHub release from new tag and upload binaries (e.g. dist/argocd-darwin-amd64)

181
docs/projects.md Normal file
View File

@@ -0,0 +1,181 @@
## Projects
Projects provide a logical grouping of applications, which is useful when ArgoCD is used by multiple
teams. Projects provide the following features:
* ability to restrict *what* may be deployed (the git source repositories)
* ability to restrict *where* apps may be deployed (the destination clusters and namespaces)
* ability to control what type of objects may be deployed (e.g. RBAC, CRDs, DaemonSets, NetworkPolicy etc...)
### The default project
Every application belongs to a single project. If unspecified, an application belongs to the
`default` project, which is created automatically and by default, permits deployments from any
source repo, to any cluster, and all resource Kinds. The default project can be modified, but not
deleted. When initially created, it's specification is configured to be the most permissive:
```yaml
spec:
sourceRepos:
- '*'
destinations:
- namespace: '*'
server: '*'
clusterResourceWhitelist:
- group: '*'
kind: '*'
```
### Creating Projects
Additional projects can be created to give separate teams different levels of access to namespaces.
The following command creates a new project `myproject` which can deploy applications to namespace
`mynamespace` of cluster `https://kubernetes.default.svc`. The permitted git source repository is
set to `https://github.com/argoproj/argocd-example-apps.git` repository.
```
argocd proj create myproject -d https://kubernetes.default.svc,mynamespace -s https://github.com/argoproj/argocd-example-apps.git
```
### Managing Projects
Permitted source git repositories are managed using commands:
```bash
argocd project add-source <PROJECT> <REPO>
argocd project remove-source <PROJECT> <REPO>
```
Permitted destination clusters and namespaces are managed with the commands:
```
argocd project add-destination <PROJECT> <CLUSTER>,<NAMESPACE>
argocd project remove-destination <PROJECT> <CLUSTER>,<NAMESPACE>
```
Permitted destination K8s resource kinds are managed with the commands. Note that namespaced-scoped
resources are restricted via a blacklist, whereas cluster-scoped resources are restricted via
whitelist.
```
argocd project allow-cluster-resource <PROJECT> <GROUP> <KIND>
argocd project allow-namespace-resource <PROJECT> <GROUP> <KIND>
argocd project deny-cluster-resource <PROJECT> <GROUP> <KIND>
argocd project deny-namespace-resource <PROJECT> <GROUP> <KIND>
```
### Assign application to a project
The application project can be changed using `app set` command. In order to change the project of
an app, the user must have permissions to access the new project.
```
argocd app set guestbook-default --project myproject
```
### Configuring RBAC with projects
Once projects have been defined, RBAC rules can be written to restrict access to the applications
in the project. The following example configures RBAC for two GitHub teams: `team1` and `team2`,
both in the GitHub org, `some-github-org`. There are two projects, `project-a` and `project-b`.
`team1` can only manage applications in `project-a`, while `team2` can only manage applications in
`project-b`. Both `team1` and `team2` have the ability to manage repositories.
*ConfigMap `argocd-rbac-cm` example:*
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
data:
policy.default: ""
policy.csv: |
p, some-github-org:team1, applications, *, project-a/*, allow
p, some-github-org:team2, applications, *, project-a/*, allow
p, role:org-admin, repositories, get, *, allow
p, role:org-admin, repositories, create, *, allow
p, role:org-admin, repositories, update, *, allow
p, role:org-admin, repositories, delete, *, allow
g, some-github-org:team1, org-admin
g, some-github-org:team2, org-admin
```
## Project Roles
Projects include a feature called roles that enable automated access to a project's applications.
These can be used to give a CI pipeline a restricted set of permissions. For example, a CI system
may only be able to sync a single app (but not change its source or destination).
Projects can have multiple roles, and those roles can have different access granted to them. These
permissions are called policies, and they are stored within the role as a list of policy strings.
A role's policy can only grant access to that role and are limited to applications within the role's
project. However, the policies have an option for granting wildcard access to any application
within a project.
In order to create roles in a project and add policies to a role, a user will need permission to
update a project. The following commands can be used to manage a role.
```bash
argoproj proj role list
argoproj proj role get
argoproj proj role create
argoproj proj role delete
argoproj proj role add-policy
argoproj proj role remove-policy
```
Project roles in itself are not useful without generating a token to associate to that role. ArgoCD
supports JWT tokens as the means to authenticate to a role. Since the JWT token is
associated with a role's policies, any changes to the role's policies will immediately take effect
for that JWT token.
The following commands are used to manage the JWT tokens.
```bash
argoproj proj role create-token PROJECT ROLE-NAME
argoproj proj role delete-token PROJECT ROLE-NAME ISSUED-AT
```
Since the JWT tokens aren't stored in ArgoCD, they can only be retrieved when they are created. A
user can leverage them in the cli by either passing them in using the `--auth-token` flag or setting
the ARGOCD_AUTH_TOKEN environment variable. The JWT tokens can be used until they expire or are
revoked. The JWT tokens can created with or without an expiration, but the default on the cli is
creates them without an expirations date. Even if a token has not expired, it cannot be used if
the token has been revoked.
Below is an example of leveraging a JWT token to access a guestbook application. It makes the
assumption that the user already has a project named myproject and an application called
guestbook-default.
```bash
PROJ=myproject
APP=guestbook-default
ROLE=get-role
argocd proj role create $PROJ $ROLE
argocd proj role create-token $PROJ $ROLE -e 10m
JWT=<value from command above>
argocd proj role list $PROJ
argocd proj role get $PROJ $ROLE
# This command will fail because the JWT Token associated with the project role does not have a policy to allow access to the application
argocd app get $APP --auth-token $JWT
# Adding a policy to grant access to the application for the new role
argocd proj role add-policy $PROJ $ROLE --action get --permission allow --object $APP
argocd app get $PROJ-$ROLE --auth-token $JWT
# Removing the policy we added and adding one with a wildcard.
argocd proj role remove-policy $PROJ $TOKEN -a get -o $PROJ-$TOKEN
argocd proj role remove-policy $PROJ $TOKEN -a get -o '*'
# The wildcard allows us to access the application due to the wildcard.
argocd app get $PROJ-$TOKEN --auth-token $JWT
argocd proj role get $PROJ
argocd proj role get $PROJ $ROLE
# Revoking the JWT token
argocd proj role delete-token $PROJ $ROLE <id field from the last command>
# This will fail since the JWT Token was deleted for the project role.
argocd app get $APP --auth-token $JWT
```

View File

@@ -2,102 +2,39 @@
## Overview
The feature RBAC allows restricting access to ArgoCD resources. ArgoCD does not have own user management system and has only one built-in user `admin`. The `admin` user is a
superuser and it has full access. RBAC requires configuring [SSO](./sso.md) integration. Once [SSO](./sso.md) is connected you can define RBAC roles and map roles to groups.
The RBAC feature enables restriction of access to ArgoCD resources. ArgoCD does not have its own
user management system and has only one built-in user `admin`. The `admin` user is a superuser and
it has unrestricted access to the system. RBAC requires [SSO configuration](./sso.md). Once SSO is
configured, additional RBAC roles can be defined, and SSO groups can man be mapped to roles.
## Configure RBAC
RBAC configuration allows defining roles and groups. ArgoCD has two pre-defined roles: role `role:readonly` which provides read-only access to all resources and role `role:admin`
which provides full access. Role definitions are available in [builtin-policy.csv](../util/rbac/builtin-policy.csv) file.
RBAC configuration allows defining roles and groups. ArgoCD has two pre-defined roles:
* `role:readonly` - read-only access to all resources
* `role:admin` - unrestricted access to all resources
These role definitions can be seen in [builtin-policy.csv](../util/rbac/builtin-policy.csv)
Additional roles and groups can be configured in `argocd-rbac-cm` ConfigMap. The example below custom role `org-admin`. The role is assigned to any user which belongs to
`your-github-org:your-team` group. All other users get `role:readonly` and cannot modify ArgoCD settings.
Additional roles and groups can be configured in `argocd-rbac-cm` ConfigMap. The example below
configures a custom role, named `org-admin`. The role is assigned to any user which belongs to
`your-github-org:your-team` group. All other users get the default policy of `role:readonly`,
which cannot modify ArgoCD settings.
*ConfigMap `argocd-rbac-cm` example:*
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
data:
policy.default: role:readonly
policy.csv: |
p, role:org-admin, applications, *, */*
p, role:org-admin, applications/*, *, */*
p, role:org-admin, clusters, get, *
p, role:org-admin, repositories, get, *
p, role:org-admin, repositories/apps, get, *
p, role:org-admin, repositories, create, *
p, role:org-admin, repositories, update, *
p, role:org-admin, repositories, delete, *
p, role:org-admin, applications, *, */*, allow
p, role:org-admin, clusters, get, *, allow
p, role:org-admin, repositories, get, *, allow
p, role:org-admin, repositories, create, *, allow
p, role:org-admin, repositories, update, *, allow
p, role:org-admin, repositories, delete, *, allow
g, your-github-org:your-team, role:org-admin
kind: ConfigMap
metadata:
name: argocd-rbac-cm
```
## Configure Projects
Argo projects allow grouping applications which is useful if ArgoCD is used by multiple teams. Additionally, projects restrict source repositories and destination
Kubernetes clusters which can be used by applications belonging to the project.
### 1. Create new project
Following command creates project `myproject` which can deploy applications to namespace `default` of cluster `https://kubernetes.default.svc`. The valid application source is defined in the `https://github.com/argoproj/argocd-example-apps.git` repository.
```
argocd proj create myproject -d https://kubernetes.default.svc,default -s https://github.com/argoproj/argocd-example-apps.git
```
Project sources and destinations can be managed using commands
```
argocd project add-destination
argocd project remove-destination
argocd project add-source
argocd project remove-source
```
### 2. Assign application to a project
Each application belongs to a project. By default, all application belongs to the default project which provides access to any source repo/cluster. The application project can be
changes using `app set` command:
```
argocd app set guestbook-default --project myproject
```
### 3. Update RBAC rules
Following example configure admin access for two teams. Each team has access only two application of one project (`team1` can access `default` project and `team2` can access
`myproject` project).
*ConfigMap `argocd-rbac-cm` example:*
```yaml
apiVersion: v1
data:
policy.default: ""
policy.csv: |
p, role:team1-admin, applications, *, default/*
p, role:team1-admin, applications/*, *, default/*
p, role:team1-admin, applications, *, myproject/*
p, role:team1-admin, applications/*, *, myproject/*
p, role:org-admin, clusters, get, *
p, role:org-admin, repositories, get, *
p, role:org-admin, repositories/apps, get, *
p, role:org-admin, repositories, create, *
p, role:org-admin, repositories, update, *
p, role:org-admin, repositories, delete, *
g, role:team1-admin, org-admin
g, role:team2-admin, org-admin
g, your-github-org:your-team1, role:team1-admin
g, your-github-org:your-team2, role:team2-admin
kind: ConfigMap
metadata:
name: argocd-rbac-cm
```

View File

@@ -35,7 +35,7 @@ kubectl edit configmap argocd-cm
[GitHub connector](https://github.com/coreos/dex/blob/master/Documentation/connectors/github.md)
documentation for explanation of the fields. A minimal config should populate the clientID,
clientSecret generated in Step 1.
* You will very likely want to restrict logins to one ore more GitHub organization. In the
* You will very likely want to restrict logins to one or more GitHub organization. In the
`connectors.config.orgs` list, add one or more GitHub organizations. Any member of the org will
then be able to login to ArgoCD to perform management tasks.

View File

@@ -4,13 +4,14 @@ An ArgoCD application spec provides several different ways of track kubernetes r
git. This document describes the different techniques and the means of deploying those manifests to
the target environment.
## Branch Tracking
## HEAD / Branch Tracking
If a branch name is specified, ArgoCD will continually compare live state against the resource
manifests defined at the tip of the specified branch.
If a branch name, or a symbolic reference (like HEAD) is specified, ArgoCD will continually compare
live state against the resource manifests defined at the tip of the specified branch or the
deferenced commit of the symbolic reference.
To redeploy an application, a user makes changes to the manifests, and commit/pushes those the
changes to the tracked branch, which will then be detected by ArgoCD controller.
changes to the tracked branch/symbolic reference, which will then be detected by ArgoCD controller.
## Tag Tracking
@@ -33,9 +34,9 @@ which is pinned to a commit, is by updating the tracking revision in the applica
commit containing the new manifests. Note that [parameter overrides](parameters.md) can still be set
on an application which is pinned to a revision.
## Auto-Sync [(Not Yet Implemented)]((https://github.com/argoproj/argo-cd/issues/79))
## Automated Sync
In all tracking strategies, the application will have the option to sync automatically. If auto-sync
In all tracking strategies, the application has the option to sync automatically. If [auto-sync](auto_sync.md)
is configured, the new resources manifests will be applied automatically -- as soon as a difference
is detected between the target state (git) and live state. If auto-sync is disabled, a manual sync
will be needed using the Argo UI, CLI, or API.

View File

@@ -64,8 +64,8 @@ for i in ${PROTO_FILES}; do
# Path to the google API gateway annotations.proto will be different depending if we are
# building natively (e.g. from workspace) vs. part of a docker build.
if [ -f /.dockerenv ]; then
GOOGLE_PROTO_API_PATH=/root/go/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis
GOGO_PROTOBUF_PATH=/root/go/src/github.com/gogo/protobuf
GOOGLE_PROTO_API_PATH=$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis
GOGO_PROTOBUF_PATH=$GOPATH/src/github.com/gogo/protobuf
else
GOOGLE_PROTO_API_PATH=${PROJECT_ROOT}/vendor/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis
GOGO_PROTOBUF_PATH=${PROJECT_ROOT}/vendor/github.com/gogo/protobuf
@@ -117,6 +117,6 @@ clean_swagger() {
/usr/bin/find "${SWAGGER_ROOT}" -name '*.swagger.json' -delete
}
collect_swagger server 15
collect_swagger server 21
clean_swagger server
clean_swagger reposerver

View File

@@ -1,168 +0,0 @@
package main
import (
"context"
"fmt"
"hash/fnv"
"log"
"os"
"strings"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/git"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
// origRepoURLToSecretName hashes repo URL to the secret name using a formula.
// Part of the original repo name is incorporated for debugging purposes
func origRepoURLToSecretName(repo string) string {
repo = git.NormalizeGitURL(repo)
h := fnv.New32a()
_, _ = h.Write([]byte(repo))
parts := strings.Split(strings.TrimSuffix(repo, ".git"), "/")
return fmt.Sprintf("repo-%s-%v", strings.ToLower(parts[len(parts)-1]), h.Sum32())
}
// repoURLToSecretName hashes repo URL to the secret name using a formula.
// Part of the original repo name is incorporated for debugging purposes
func repoURLToSecretName(repo string) string {
repo = strings.ToLower(git.NormalizeGitURL(repo))
h := fnv.New32a()
_, _ = h.Write([]byte(repo))
parts := strings.Split(strings.TrimSuffix(repo, ".git"), "/")
return fmt.Sprintf("repo-%s-%v", parts[len(parts)-1], h.Sum32())
}
// RenameSecret renames a Kubernetes secret in a given namespace.
func renameSecret(namespace, oldName, newName string) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
overrides := clientcmd.ConfigOverrides{}
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &overrides)
log.Printf("Renaming secret %q to %q in namespace %q\n", oldName, newName, namespace)
config, err := clientConfig.ClientConfig()
if err != nil {
log.Println("Could not retrieve client config: ", err)
return
}
kubeclientset := kubernetes.NewForConfigOrDie(config)
repoSecret, err := kubeclientset.CoreV1().Secrets(namespace).Get(oldName, metav1.GetOptions{})
if err != nil {
log.Println("Could not retrieve old secret: ", err)
return
}
repoSecret.ObjectMeta.Name = newName
repoSecret.ObjectMeta.ResourceVersion = ""
repoSecret, err = kubeclientset.CoreV1().Secrets(namespace).Create(repoSecret)
if err != nil {
log.Println("Could not create new secret: ", err)
return
}
err = kubeclientset.CoreV1().Secrets(namespace).Delete(oldName, &metav1.DeleteOptions{})
if err != nil {
log.Println("Could not remove old secret: ", err)
}
}
// RenameRepositorySecrets ensures that repository secrets use the new naming format.
func renameRepositorySecrets(clientOpts argocdclient.ClientOptions, namespace string) {
conn, repoIf := argocdclient.NewClientOrDie(&clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
repos, err := repoIf.List(context.Background(), &repository.RepoQuery{})
if err != nil {
log.Println("An error occurred, so skipping secret renaming: ", err)
return
}
log.Println("Renaming repository secrets...")
for _, repo := range repos.Items {
oldSecretName := origRepoURLToSecretName(repo.Repo)
newSecretName := repoURLToSecretName(repo.Repo)
if oldSecretName != newSecretName {
log.Printf("Repo %q had its secret name change, so updating\n", repo.Repo)
renameSecret(namespace, oldSecretName, newSecretName)
}
}
}
/*
// PopulateAppDestinations ensures that apps have a Server and Namespace set explicitly.
func populateAppDestinations(clientOpts argocdclient.ClientOptions) {
conn, appIf := argocdclient.NewClientOrDie(&clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
apps, err := appIf.List(context.Background(), &application.ApplicationQuery{})
if err != nil {
log.Println("An error occurred, so skipping destination population: ", err)
return
}
log.Println("Populating app Destination fields")
for _, app := range apps.Items {
changed := false
log.Printf("Ensuring destination field is populated on app %q\n", app.ObjectMeta.Name)
if app.Spec.Destination.Server == "" {
if app.Status.ComparisonResult.Status == appv1.ComparisonStatusUnknown || app.Status.ComparisonResult.Status == appv1.ComparisonStatusError {
log.Printf("App %q was missing Destination.Server, but could not fill it in: %s", app.ObjectMeta.Name, app.Status.ComparisonResult.Status)
} else {
log.Printf("App %q was missing Destination.Server, so setting to %q\n", app.ObjectMeta.Name, app.Status.ComparisonResult.Server)
app.Spec.Destination.Server = app.Status.ComparisonResult.Server
changed = true
}
}
if app.Spec.Destination.Namespace == "" {
if app.Status.ComparisonResult.Status == appv1.ComparisonStatusUnknown || app.Status.ComparisonResult.Status == appv1.ComparisonStatusError {
log.Printf("App %q was missing Destination.Namespace, but could not fill it in: %s", app.ObjectMeta.Name, app.Status.ComparisonResult.Status)
} else {
log.Printf("App %q was missing Destination.Namespace, so setting to %q\n", app.ObjectMeta.Name, app.Status.ComparisonResult.Namespace)
app.Spec.Destination.Namespace = app.Status.ComparisonResult.Namespace
changed = true
}
}
if changed {
_, err = appIf.UpdateSpec(context.Background(), &application.ApplicationSpecRequest{
AppName: app.Name,
Spec: &app.Spec,
})
if err != nil {
log.Println("An error occurred (but continuing anyway): ", err)
}
}
}
}
*/
func main() {
if len(os.Args) < 3 {
log.Fatalf("USAGE: %s SERVER NAMESPACE\n", os.Args[0])
}
server, namespace := os.Args[1], os.Args[2]
log.Printf("Using argocd server %q and namespace %q\n", server, namespace)
isLocalhost := false
switch {
case strings.HasPrefix(server, "localhost:"):
isLocalhost = true
case strings.HasPrefix(server, "127.0.0.1:"):
isLocalhost = true
}
clientOpts := argocdclient.ClientOptions{
ServerAddr: server,
Insecure: true,
PlainText: isLocalhost,
}
renameRepositorySecrets(clientOpts, namespace)
//populateAppDestinations(clientOpts)
}

View File

@@ -1,11 +1,22 @@
#!/bin/sh
IMAGE_NAMESPACE=${IMAGE_NAMESPACE:='argoproj'}
IMAGE_TAG=${IMAGE_TAG:='latest'}
SRCROOT="$( cd "$(dirname "$0")/.." ; pwd -P )"
AUTOGENMSG="# This is an auto-generated file. DO NOT EDIT"
for i in "$(ls manifests/components/*.yaml)"; do
sed -i '' 's@\( image: \(.*\)/\(argocd-.*\):.*\)@ image: '"${IMAGE_NAMESPACE}"'/\3:'"${IMAGE_TAG}"'@g' $i
done
update_image () {
if [ ! -z "${IMAGE_NAMESPACE}" ]; then
sed -i '' 's| image: \(.*\)/\(argocd-.*\)| image: '"${IMAGE_NAMESPACE}"'/\2|g' ${1}
fi
if [ ! -z "${IMAGE_TAG}" ]; then
sed -i '' 's|\( image: .*/argocd-.*\)\:.*|\1:'"${IMAGE_TAG}"'|g' ${1}
fi
}
echo "${AUTOGENMSG}" > ${SRCROOT}/manifests/install.yaml
kustomize build ${SRCROOT}/manifests/cluster-install >> ${SRCROOT}/manifests/install.yaml
update_image ${SRCROOT}/manifests/install.yaml
echo "${AUTOGENMSG}" > ${SRCROOT}/manifests/namespace-install.yaml
kustomize build ${SRCROOT}/manifests/base >> ${SRCROOT}/manifests/namespace-install.yaml
update_image ${SRCROOT}/manifests/namespace-install.yaml
echo "# This is an auto-generated file. DO NOT EDIT" > manifests/install.yaml
cat manifests/components/*.yaml >> manifests/install.yaml

16
manifests/README.md Normal file
View File

@@ -0,0 +1,16 @@
# ArgoCD Installation Manifests
Two sets of installation manifests are provided:
* [install.yaml](install.yaml) - Standard ArgoCD installation with cluster-admin access. Use this
manifest set if you plan to use ArgoCD to deploy applications in the same cluster that ArgoCD runs
in (i.e. kubernetes.svc.default). Will still be able to deploy to external clusters with inputted
credentials.
* [namespace-install.yaml](namespace-install.yaml) - Installation of ArgoCD which requires only
namespace level privileges (does not need cluster roles). Use this manifest set if you do not
need ArgoCD to deploy applications in the same cluster that ArgoCD runs in, and will rely solely
on inputted cluster credentials. An example of using this set of manifests is if you run several
ArgoCD instances for different teams, where each instance will bedeploying applications to
external clusters. Will still be possible to deploy to the same cluster (kubernetes.svc.default)
with inputted credentials (i.e. `argocd cluster add <CONTEXT> --in-cluster`).

View File

@@ -1,4 +1,3 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -13,7 +12,14 @@ spec:
app: application-controller
spec:
containers:
- command: [/argocd-application-controller, --repo-server, 'argocd-repo-server:8081']
image: argoproj/argocd-application-controller:v0.7.1
- command:
- /argocd-application-controller
- --repo-server
- argocd-repo-server:8081
- --status-processors
- "20"
- --operation-processors
- "10"
image: argoproj/argocd-application-controller:latest
name: application-controller
serviceAccountName: application-controller

View File

@@ -1,4 +1,3 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:

View File

@@ -1,4 +1,3 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:

View File

@@ -1,4 +1,3 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:

View File

@@ -1,4 +1,3 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:

View File

@@ -1,4 +1,3 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:

View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
# data:
# # ArgoCD's externally facing base URL. Required for configuring SSO
# # url: https://argo-cd-demo.argoproj.io
#
# # A dex connector configuration. See documentation on how to configure SSO:
# # https://github.com/argoproj/argo-cd/blob/master/docs/sso.md#2-configure-argocd-for-sso
# dex.config: |
# connectors:
# # GitHub example
# - type: github
# id: github
# name: GitHub
# config:
# clientID: aabbccddeeff00112233
# clientSecret: $dex.github.clientSecret
# orgs:
# - name: your-github-org
# teams:
# - red-team

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: argocd-metrics
spec:
ports:
- name: http
protocol: TCP
port: 8082
targetPort: 8082
selector:
app: argocd-server

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
# data:
# # An RBAC policy .csv file containing additional policy and role definitions.
# # See https://github.com/argoproj/argo-cd/blob/master/docs/rbac.md on how to write RBAC policies.
# policy.csv: |
# # Give all members of "my-org:team-alpha" the ability to sync apps in "my-project"
# p, my-org:team-alpha, applications, sync, my-project/*, allow
# # Make all members of "my-org:team-beta" admins
# g, my-org:team-beta, role:admin
#
# # The default role ArgoCD will fall back to, when authorizing API requests
# policy.default: role:readonly

View File

@@ -1,4 +1,3 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -12,9 +11,15 @@ spec:
labels:
app: argocd-repo-server
spec:
automountServiceAccountToken: false
containers:
- name: argocd-repo-server
image: argoproj/argocd-repo-server:v0.7.1
image: argoproj/argocd-repo-server:latest
command: [/argocd-repo-server]
ports:
- containerPort: 8081
- containerPort: 8081
readinessProbe:
tcpSocket:
port: 8081
initialDelaySeconds: 5
periodSeconds: 10

View File

@@ -1,4 +1,3 @@
---
apiVersion: v1
kind: Service
metadata:

View File

@@ -0,0 +1,26 @@
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
type: Opaque
# data:
# # TLS certificate and private key for API server.
# # Autogenerated with a self-signed ceritificate if keys are missing.
# tls.crt:
# tls.key:
#
# # bcrypt hash of the admin password and it's last modified time. Autogenerated on initial
# # startup. To reset a forgotten password, delete both keys and restart argocd-server.
# admin.password:
# admin.passwordMtime:
#
# # random server signature key for session validation. Autogenerated on initial startup
# server.secretkey:
#
# # The following keys hold the shared secret for authenticating GitHub/GitLab/BitBucket webhook
# # events. To enable webhooks, configure one or more of the following keys with the shared git
# # provider webhook secret. The payload URL configured in the git provider should use the
# # /api/webhook endpoint of your ArgoCD instance (e.g. https://argocd.example.com/api/webhook)
# github.webhook.secret:
# gitlab.webhook.secret:
# bitbucket.webhook.uuid:

View File

@@ -1,4 +1,3 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -14,31 +13,25 @@ spec:
spec:
serviceAccountName: argocd-server
initContainers:
- name: copyutil
image: argoproj/argocd-server:v0.7.1
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
- name: ui
image: argoproj/argocd-ui:v0.7.1
image: argoproj/argocd-ui:latest
command: [cp, -r, /app, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
containers:
- name: argocd-server
image: argoproj/argocd-server:v0.7.1
image: argoproj/argocd-server:latest
command: [/argocd-server, --staticassets, /shared/app, --repo-server, 'argocd-repo-server:8081']
volumeMounts:
- mountPath: /shared
name: static-files
- name: dex
image: quay.io/coreos/dex:v2.10.0
command: [/shared/argocd-util, rundex]
volumeMounts:
- mountPath: /shared
name: static-files
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
volumes:
- emptyDir: {}
name: static-files

View File

@@ -1,4 +1,3 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:

View File

@@ -1,4 +1,3 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:

View File

@@ -1,4 +1,3 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:

View File

@@ -1,4 +1,3 @@
---
apiVersion: v1
kind: Service
metadata:

View File

@@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: dex-server
spec:
selector:
matchLabels:
app: dex-server
template:
metadata:
labels:
app: dex-server
spec:
serviceAccountName: dex-server
initContainers:
- name: copyutil
image: argoproj/argocd-server:latest
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
containers:
- name: dex
image: quay.io/dexidp/dex:v2.11.0
command: [/shared/argocd-util, rundex]
ports:
- containerPort: 5556
- containerPort: 5557
volumeMounts:
- mountPath: /shared
name: static-files
volumes:
- emptyDir: {}
name: static-files

View File

@@ -0,0 +1,14 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dex-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- get
- list
- watch

View File

@@ -0,0 +1,11 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dex-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dex-server-role
subjects:
- kind: ServiceAccount
name: dex-server

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: dex-server

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: dex-server
spec:
ports:
- name: http
protocol: TCP
port: 5556
targetPort: 5556
- name: grpc
protocol: TCP
port: 5557
targetPort: 5557
selector:
app: dex-server

View File

@@ -0,0 +1,33 @@
resources:
- application-crd.yaml
- appproject-crd.yaml
- argocd-cm.yaml
- argocd-secret.yaml
- argocd-rbac-cm.yaml
- application-controller-sa.yaml
- application-controller-role.yaml
- application-controller-rolebinding.yaml
- application-controller-deployment.yaml
- argocd-server-sa.yaml
- argocd-server-role.yaml
- argocd-server-rolebinding.yaml
- argocd-server-deployment.yaml
- argocd-server-service.yaml
- argocd-metrics-service.yaml
- argocd-repo-server-deployment.yaml
- argocd-repo-server-service.yaml
- dex-server-sa.yaml
- dex-server-role.yaml
- dex-server-rolebinding.yaml
- dex-server-deployment.yaml
- dex-server-service.yaml
imageTags:
- name: argoproj/argocd-server
newTag: v0.10.6
- name: argoproj/argocd-ui
newTag: v0.10.6
- name: argoproj/argocd-repo-server
newTag: v0.10.6
- name: argoproj/argocd-application-controller
newTag: v0.10.6

View File

@@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: application-controller-clusterrole
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'

View File

@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: application-controller-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: application-controller-clusterrole
subjects:
- kind: ServiceAccount
name: application-controller
namespace: argocd

View File

@@ -0,0 +1,24 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-server-clusterrole
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- list
- apiGroups:
- ""
resources:
- pods
- pods/log
verbs:
- get

View File

@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-server-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-server-clusterrole
subjects:
- kind: ServiceAccount
name: argocd-server
namespace: argocd

View File

@@ -0,0 +1,8 @@
bases:
- ../base
resources:
- application-controller-clusterrole.yaml
- application-controller-clusterrolebinding.yaml
- argocd-server-clusterrole.yaml
- argocd-server-clusterrolebinding.yaml

View File

@@ -1,14 +0,0 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
data:
# See https://github.com/argoproj/argo-cd/blob/master/docs/sso.md#2-configure-argocd-for-sso
# for more details about how to setup data config needed for sso
# URL is the external URL of ArgoCD
#url:
# A dex connector configuration
#dex.config:

View File

@@ -1,24 +0,0 @@
---
# NOTE: the values in this secret will be populated by the initial startup of the API
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
type: Opaque
# bcrypt hash of the admin password
#admin.password:
# random server signature key for session validation
#server.secretkey:
# TLS certificate and private key for API server
#server.crt:
#server.key:
# The following keys hold the shared secret for authenticating GitHub/GitLab/BitBucket webhook
# events. To enable webhooks, configure one or more of the following keys with the shared git
# provider webhook secret. The payload URL configured in the git provider should use the
# /api/webhook endpoint of your ArgoCD instance (e.g. https://argocd.example.com/api/webhook)
#github.webhook.secret:
#gitlab.webhook.secret:
#bitbucket.webhook.uuid:

View File

@@ -1,26 +0,0 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
data:
# policy.csv holds the CSV file policy file which contains additional policy and role definitions.
# ArgoCD defines two built-in roles:
# * role:readonly - readonly access to all objects
# * role:admin - admin access to all objects
# The built-in policy can be seen under util/rbac/builtin-policy.csv
#policy.csv: ""
# There are two policy formats:
# 1. Applications (which belong to a project):
# p, <user/group>, <resource>, <action>, <project>/<object>
# 2. All other resources:
# p, <user/group>, <resource>, <action>, <object>
# For example, the following rule gives all members of 'my-org:team1' the ability to sync
# applications in the project named: my-project
# p, my-org:team1, applications, sync, my-project/*
# policy.default holds the default policy which will ArgoCD will fall back to, when authorizing
# a user for API requests
policy.default: role:readonly

View File

@@ -1,5 +1,4 @@
# This is an auto-generated file. DO NOT EDIT
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
@@ -30,73 +29,19 @@ spec:
version: v1alpha1
---
apiVersion: v1
kind: ConfigMap
kind: ServiceAccount
metadata:
name: argocd-cm
data:
# See https://github.com/argoproj/argo-cd/blob/master/docs/sso.md#2-configure-argocd-for-sso
# for more details about how to setup data config needed for sso
# URL is the external URL of ArgoCD
#url:
# A dex connector configuration
#dex.config:
---
# NOTE: the values in this secret will be populated by the initial startup of the API
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
type: Opaque
# bcrypt hash of the admin password
#admin.password:
# random server signature key for session validation
#server.secretkey:
# TLS certificate and private key for API server
#server.crt:
#server.key:
# The following keys hold the shared secret for authenticating GitHub/GitLab/BitBucket webhook
# events. To enable webhooks, configure one or more of the following keys with the shared git
# provider webhook secret. The payload URL configured in the git provider should use the
# /api/webhook endpoint of your ArgoCD instance (e.g. https://argocd.example.com/api/webhook)
#github.webhook.secret:
#gitlab.webhook.secret:
#bitbucket.webhook.uuid:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
data:
# policy.csv holds the CSV file policy file which contains additional policy and role definitions.
# ArgoCD defines two built-in roles:
# * role:readonly - readonly access to all objects
# * role:admin - admin access to all objects
# The built-in policy can be seen under util/rbac/builtin-policy.csv
#policy.csv: ""
# There are two policy formats:
# 1. Applications (which belong to a project):
# p, <user/group>, <resource>, <action>, <project>/<object>
# 2. All other resources:
# p, <user/group>, <resource>, <action>, <object>
# For example, the following rule gives all members of 'my-org:team1' the ability to sync
# applications in the project named: my-project
# p, my-org:team1, applications, sync, my-project/*
# policy.default holds the default policy which will ArgoCD will fall back to, when authorizing
# a user for API requests
policy.default: role:readonly
name: application-controller
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: application-controller
name: argocd-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dex-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
@@ -133,43 +78,6 @@ rules:
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: application-controller-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: application-controller-role
subjects:
- kind: ServiceAccount
name: application-controller
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-controller
spec:
selector:
matchLabels:
app: application-controller
template:
metadata:
labels:
app: application-controller
spec:
containers:
- command: [/argocd-application-controller, --repo-server, 'argocd-repo-server:8081']
image: argoproj/argocd-application-controller:v0.7.1
name: application-controller
serviceAccountName: application-controller
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
@@ -211,6 +119,74 @@ rules:
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dex-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: application-controller-clusterrole
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-server-clusterrole
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- list
- apiGroups:
- ""
resources:
- pods
- pods/log
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: application-controller-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: application-controller-role
subjects:
- kind: ServiceAccount
name: application-controller
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argocd-server-role-binding
@@ -222,49 +198,83 @@ subjects:
- kind: ServiceAccount
name: argocd-server
---
apiVersion: apps/v1
kind: Deployment
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dex-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dex-server-role
subjects:
- kind: ServiceAccount
name: dex-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: application-controller-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: application-controller-clusterrole
subjects:
- kind: ServiceAccount
name: application-controller
namespace: argocd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-server-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-server-clusterrole
subjects:
- kind: ServiceAccount
name: argocd-server
namespace: argocd
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
---
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
name: argocd-metrics
spec:
ports:
- name: http
port: 8082
protocol: TCP
targetPort: 8082
selector:
matchLabels:
app: argocd-server
template:
metadata:
labels:
app: argocd-server
spec:
serviceAccountName: argocd-server
initContainers:
- name: copyutil
image: argoproj/argocd-server:v0.7.1
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
- name: ui
image: argoproj/argocd-ui:v0.7.1
command: [cp, -r, /app, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
containers:
- name: argocd-server
image: argoproj/argocd-server:v0.7.1
command: [/argocd-server, --staticassets, /shared/app, --repo-server, 'argocd-repo-server:8081']
volumeMounts:
- mountPath: /shared
name: static-files
- name: dex
image: quay.io/coreos/dex:v2.10.0
command: [/shared/argocd-util, rundex]
volumeMounts:
- mountPath: /shared
name: static-files
volumes:
- emptyDir: {}
name: static-files
app: argocd-server
---
apiVersion: v1
kind: Service
metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
targetPort: 8081
selector:
app: argocd-repo-server
---
apiVersion: v1
kind: Service
@@ -273,16 +283,59 @@ metadata:
spec:
ports:
- name: http
protocol: TCP
port: 80
protocol: TCP
targetPort: 8080
- name: https
protocol: TCP
port: 443
protocol: TCP
targetPort: 8080
selector:
app: argocd-server
---
apiVersion: v1
kind: Service
metadata:
name: dex-server
spec:
ports:
- name: http
port: 5556
protocol: TCP
targetPort: 5556
- name: grpc
port: 5557
protocol: TCP
targetPort: 5557
selector:
app: dex-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-controller
spec:
selector:
matchLabels:
app: application-controller
template:
metadata:
labels:
app: application-controller
spec:
containers:
- command:
- /argocd-application-controller
- --repo-server
- argocd-repo-server:8081
- --status-processors
- "20"
- --operation-processors
- "10"
image: argoproj/argocd-application-controller:v0.10.6
name: application-controller
serviceAccountName: application-controller
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -296,20 +349,103 @@ spec:
labels:
app: argocd-repo-server
spec:
automountServiceAccountToken: false
containers:
- name: argocd-repo-server
image: argoproj/argocd-repo-server:v0.7.1
command: [/argocd-repo-server]
- command:
- /argocd-repo-server
image: argoproj/argocd-repo-server:v0.10.6
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8081
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
---
apiVersion: v1
kind: Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-repo-server
name: argocd-server
spec:
ports:
- port: 8081
targetPort: 8081
selector:
app: argocd-repo-server
matchLabels:
app: argocd-server
template:
metadata:
labels:
app: argocd-server
spec:
containers:
- command:
- /argocd-server
- --staticassets
- /shared/app
- --repo-server
- argocd-repo-server:8081
image: argoproj/argocd-server:v0.10.6
name: argocd-server
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
volumeMounts:
- mountPath: /shared
name: static-files
initContainers:
- command:
- cp
- -r
- /app
- /shared
image: argoproj/argocd-ui:v0.10.6
name: ui
volumeMounts:
- mountPath: /shared
name: static-files
serviceAccountName: argocd-server
volumes:
- emptyDir: {}
name: static-files
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dex-server
spec:
selector:
matchLabels:
app: dex-server
template:
metadata:
labels:
app: dex-server
spec:
containers:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.11.0
name: dex
ports:
- containerPort: 5556
- containerPort: 5557
volumeMounts:
- mountPath: /shared
name: static-files
initContainers:
- command:
- cp
- /argocd-util
- /shared
image: argoproj/argocd-server:v0.10.6
name: copyutil
volumeMounts:
- mountPath: /shared
name: static-files
serviceAccountName: dex-server
volumes:
- emptyDir: {}
name: static-files

View File

@@ -0,0 +1,384 @@
# This is an auto-generated file. DO NOT EDIT
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: applications.argoproj.io
spec:
group: argoproj.io
names:
kind: Application
plural: applications
shortNames:
- app
scope: Namespaced
version: v1alpha1
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: appprojects.argoproj.io
spec:
group: argoproj.io
names:
kind: AppProject
plural: appprojects
shortNames:
- appproj
- appprojs
scope: Namespaced
version: v1alpha1
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: application-controller
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dex-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: application-controller-role
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- watch
- list
- patch
- update
- apiGroups:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argocd-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get
- list
- watch
- update
- delete
- patch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dex-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: application-controller-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: application-controller-role
subjects:
- kind: ServiceAccount
name: application-controller
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argocd-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: argocd-server-role
subjects:
- kind: ServiceAccount
name: argocd-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dex-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dex-server-role
subjects:
- kind: ServiceAccount
name: dex-server
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
---
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
name: argocd-metrics
spec:
ports:
- name: http
port: 8082
protocol: TCP
targetPort: 8082
selector:
app: argocd-server
---
apiVersion: v1
kind: Service
metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
targetPort: 8081
selector:
app: argocd-repo-server
---
apiVersion: v1
kind: Service
metadata:
name: argocd-server
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
- name: https
port: 443
protocol: TCP
targetPort: 8080
selector:
app: argocd-server
---
apiVersion: v1
kind: Service
metadata:
name: dex-server
spec:
ports:
- name: http
port: 5556
protocol: TCP
targetPort: 5556
- name: grpc
port: 5557
protocol: TCP
targetPort: 5557
selector:
app: dex-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-controller
spec:
selector:
matchLabels:
app: application-controller
template:
metadata:
labels:
app: application-controller
spec:
containers:
- command:
- /argocd-application-controller
- --repo-server
- argocd-repo-server:8081
- --status-processors
- "20"
- --operation-processors
- "10"
image: argoproj/argocd-application-controller:v0.10.6
name: application-controller
serviceAccountName: application-controller
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-repo-server
spec:
selector:
matchLabels:
app: argocd-repo-server
template:
metadata:
labels:
app: argocd-repo-server
spec:
automountServiceAccountToken: false
containers:
- command:
- /argocd-repo-server
image: argoproj/argocd-repo-server:v0.10.6
name: argocd-repo-server
ports:
- containerPort: 8081
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-server
spec:
selector:
matchLabels:
app: argocd-server
template:
metadata:
labels:
app: argocd-server
spec:
containers:
- command:
- /argocd-server
- --staticassets
- /shared/app
- --repo-server
- argocd-repo-server:8081
image: argoproj/argocd-server:v0.10.6
name: argocd-server
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
volumeMounts:
- mountPath: /shared
name: static-files
initContainers:
- command:
- cp
- -r
- /app
- /shared
image: argoproj/argocd-ui:v0.10.6
name: ui
volumeMounts:
- mountPath: /shared
name: static-files
serviceAccountName: argocd-server
volumes:
- emptyDir: {}
name: static-files
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dex-server
spec:
selector:
matchLabels:
app: dex-server
template:
metadata:
labels:
app: dex-server
spec:
containers:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.11.0
name: dex
ports:
- containerPort: 5556
- containerPort: 5557
volumeMounts:
- mountPath: /shared
name: static-files
initContainers:
- command:
- cp
- /argocd-util
- /shared
image: argoproj/argocd-server:v0.10.6
name: copyutil
volumeMounts:
- mountPath: /shared
name: static-files
serviceAccountName: dex-server
volumes:
- emptyDir: {}
name: static-files

View File

@@ -40,6 +40,8 @@ const (
EnvArgoCDServer = "ARGOCD_SERVER"
// EnvArgoCDAuthToken is the environment variable to look for an ArgoCD auth token
EnvArgoCDAuthToken = "ARGOCD_AUTH_TOKEN"
// MaxGRPCMessageSize contains max grpc message size
MaxGRPCMessageSize = 100 * 1024 * 1024
)
// Client defines an interface for interaction with an Argo CD server.
@@ -277,7 +279,8 @@ func (c *client) NewConn() (*grpc.ClientConn, error) {
endpointCredentials := jwtCredentials{
Token: c.AuthToken,
}
return grpc_util.BlockingDial(context.Background(), "tcp", c.ServerAddr, creds, grpc.WithPerRPCCredentials(endpointCredentials))
return grpc_util.BlockingDial(context.Background(), "tcp", c.ServerAddr, creds,
grpc.WithPerRPCCredentials(endpointCredentials), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize)))
}
func (c *client) tlsConfig() (*tls.Config, error) {

File diff suppressed because it is too large Load Diff

View File

@@ -8,11 +8,19 @@ package github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1;
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1alpha1";
// AWSAuthConfig is an AWS IAM authentication configuration
message AWSAuthConfig {
// ClusterName contains AWS cluster name
optional string clusterName = 1;
// RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.
optional string roleARN = 2;
}
// AppProject is a definition of AppProject resource.
// +genclient
// +genclient:noStatus
@@ -41,6 +49,14 @@ message AppProjectSpec {
// Description contains optional project description
optional string description = 3;
repeated ProjectRole roles = 4;
// ClusterResourceWhitelist contains list of whitelisted cluster level resources
repeated k8s.io.apimachinery.pkg.apis.meta.v1.GroupKind clusterResourceWhitelist = 5;
// NamespaceResourceBlacklist contains list of blacklisted namespace level resources
repeated k8s.io.apimachinery.pkg.apis.meta.v1.GroupKind namespaceResourceBlacklist = 6;
}
// Application is a definition of Application resource.
@@ -115,6 +131,9 @@ message ApplicationSpec {
// Project is a application project name. Empty name means that application belongs to 'default' project.
optional string project = 3;
// SyncPolicy controls when a sync will be performed
optional SyncPolicy syncPolicy = 4;
}
// ApplicationStatus contains information about application status in target environment.
@@ -174,6 +193,9 @@ message ClusterConfig {
// TLSClientConfig contains settings to enable transport layer security
optional TLSClientConfig tlsClientConfig = 4;
// AWSAuthConfig contains IAM authentication configuration
optional AWSAuthConfig awsAuthConfig = 5;
}
// ClusterList is a collection of Clusters.
@@ -192,6 +214,8 @@ message ComparisonResult {
optional string status = 5;
repeated ResourceState resources = 6;
optional string revision = 7;
}
// ComponentParameter contains information about component parameter value
@@ -214,8 +238,6 @@ message ConnectionState {
// DeploymentInfo contains information relevant to an application deployment
message DeploymentInfo {
repeated ComponentParameter params = 1;
optional string revision = 2;
repeated ComponentParameter componentParameterOverrides = 3;
@@ -252,11 +274,16 @@ message HookStatus {
optional string message = 6;
}
// JWTToken holds the issuedAt and expiresAt values of a token
message JWTToken {
optional int64 iat = 1;
optional int64 exp = 2;
}
// Operation contains requested operation parameters.
message Operation {
optional SyncOperation sync = 1;
optional RollbackOperation rollback = 2;
}
// OperationState contains information about state of currently performing operation on application.
@@ -273,9 +300,6 @@ message OperationState {
// SyncResult is the result of a Sync operation
optional SyncOperationResult syncResult = 4;
// RollbackResult is the result of a Rollback operation
optional SyncOperationResult rollbackResult = 5;
// StartedAt contains time of operation start
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time startedAt = 6;
@@ -283,6 +307,27 @@ message OperationState {
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time finishedAt = 7;
}
// ParameterOverrides masks the value so protobuf can generate
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
message ParameterOverrides {
// items, if empty, will result in an empty slice
repeated ComponentParameter items = 1;
}
// ProjectRole represents a role that has access to a project
message ProjectRole {
optional string name = 1;
optional string description = 2;
// Policies Stores a list of casbin formated strings that define access policies for the role in the project.
repeated string policies = 3;
repeated JWTToken jwtTokens = 4;
}
// Repository is a Git repository holding application configurations
message Repository {
optional string repo = 1;
@@ -335,17 +380,10 @@ message ResourceState {
optional HealthStatus health = 5;
}
message RollbackOperation {
optional int64 id = 1;
optional bool prune = 2;
optional bool dryRun = 3;
}
// SyncOperation contains sync operation details.
message SyncOperation {
// Revision is the git revision in which to sync the application to
// Revision is the git revision in which to sync the application to.
// If omitted, will use the revision specified in app spec.
optional string revision = 1;
// Prune deletes resources that are no longer tracked in git
@@ -356,6 +394,23 @@ message SyncOperation {
// SyncStrategy describes how to perform the sync
optional SyncStrategy syncStrategy = 4;
// ParameterOverrides applies any parameter overrides as part of the sync
// If nil, uses the parameter override set in application.
// If empty, sets no parameter overrides
optional ParameterOverrides parameterOverrides = 5;
// Resources describes which resources to sync
repeated SyncOperationResource resources = 6;
}
// SyncOperationResource contains resources to sync.
message SyncOperationResource {
optional string group = 1;
optional string kind = 2;
optional string name = 3;
}
// SyncOperationResult represent result of sync operation
@@ -370,12 +425,24 @@ message SyncOperationResult {
repeated HookStatus hooks = 3;
}
// SyncStrategy indicates the
// SyncPolicy controls when a sync will be performed in response to updates in git
message SyncPolicy {
// Automated will keep an application synced to the target revision
optional SyncPolicyAutomated automated = 1;
}
// SyncPolicyAutomated controls the behavior of an automated sync
message SyncPolicyAutomated {
// Prune will prune resources automatically as part of automated sync (default: false)
optional bool prune = 1;
}
// SyncStrategy controls the manner in which a sync is performed
message SyncStrategy {
// Apply wil perform a `kubectl apply` to perform the sync. This is the default strategy
// Apply wil perform a `kubectl apply` to perform the sync.
optional SyncStrategyApply apply = 1;
// Hook will submit any referenced resources to perform the sync
// Hook will submit any referenced resources to perform the sync. This is the default strategy
optional SyncStrategyHook hook = 2;
}

View File

@@ -2,6 +2,8 @@ package v1alpha1
import (
"encoding/json"
fmt "fmt"
"path/filepath"
"reflect"
"strings"
@@ -9,14 +11,32 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd/api"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/util/git"
)
// SyncOperationResource contains resources to sync.
type SyncOperationResource struct {
Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"`
Name string `json:"name" protobuf:"bytes,3,opt,name=name"`
}
// HasIdentity determines whether a sync operation is identified by a manifest.
func (r SyncOperationResource) HasIdentity(u *unstructured.Unstructured) bool {
gvk := u.GroupVersionKind()
if u.GetName() == r.Name && gvk.Kind == r.Kind && gvk.Group == r.Group {
return true
}
return false
}
// SyncOperation contains sync operation details.
type SyncOperation struct {
// Revision is the git revision in which to sync the application to
// Revision is the git revision in which to sync the application to.
// If omitted, will use the revision specified in app spec.
Revision string `json:"revision,omitempty" protobuf:"bytes,1,opt,name=revision"`
// Prune deletes resources that are no longer tracked in git
Prune bool `json:"prune,omitempty" protobuf:"bytes,2,opt,name=prune"`
@@ -24,18 +44,26 @@ type SyncOperation struct {
DryRun bool `json:"dryRun,omitempty" protobuf:"bytes,3,opt,name=dryRun"`
// SyncStrategy describes how to perform the sync
SyncStrategy *SyncStrategy `json:"syncStrategy,omitempty" protobuf:"bytes,4,opt,name=syncStrategy"`
// ParameterOverrides applies any parameter overrides as part of the sync
// If nil, uses the parameter override set in application.
// If empty, sets no parameter overrides
ParameterOverrides ParameterOverrides `json:"parameterOverrides" protobuf:"bytes,5,opt,name=parameterOverrides"`
// Resources describes which resources to sync
Resources []SyncOperationResource `json:"resources,omitempty" protobuf:"bytes,6,opt,name=resources"`
}
type RollbackOperation struct {
ID int64 `json:"id" protobuf:"bytes,1,opt,name=id"`
Prune bool `json:"prune,omitempty" protobuf:"bytes,2,opt,name=prune"`
DryRun bool `json:"dryRun,omitempty" protobuf:"bytes,3,opt,name=dryRun"`
// ParameterOverrides masks the value so protobuf can generate
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type ParameterOverrides []ComponentParameter
func (po ParameterOverrides) String() string {
return fmt.Sprintf("%v", []ComponentParameter(po))
}
// Operation contains requested operation parameters.
type Operation struct {
Sync *SyncOperation `json:"sync,omitempty" protobuf:"bytes,1,opt,name=sync"`
Rollback *RollbackOperation `json:"rollback,omitempty" protobuf:"bytes,2,opt,name=rollback"`
Sync *SyncOperation `json:"sync,omitempty" protobuf:"bytes,1,opt,name=sync"`
}
type OperationPhase string
@@ -70,19 +98,29 @@ type OperationState struct {
Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"`
// SyncResult is the result of a Sync operation
SyncResult *SyncOperationResult `json:"syncResult,omitempty" protobuf:"bytes,4,opt,name=syncResult"`
// RollbackResult is the result of a Rollback operation
RollbackResult *SyncOperationResult `json:"rollbackResult,omitempty" protobuf:"bytes,5,opt,name=rollbackResult"`
// StartedAt contains time of operation start
StartedAt metav1.Time `json:"startedAt" protobuf:"bytes,6,opt,name=startedAt"`
// FinishedAt contains time of operation completion
FinishedAt *metav1.Time `json:"finishedAt" protobuf:"bytes,7,opt,name=finishedAt"`
}
// SyncStrategy indicates the
// SyncPolicy controls when a sync will be performed in response to updates in git
type SyncPolicy struct {
// Automated will keep an application synced to the target revision
Automated *SyncPolicyAutomated `json:"automated,omitempty" protobuf:"bytes,1,opt,name=automated"`
}
// SyncPolicyAutomated controls the behavior of an automated sync
type SyncPolicyAutomated struct {
// Prune will prune resources automatically as part of automated sync (default: false)
Prune bool `json:"prune,omitempty" protobuf:"bytes,1,opt,name=prune"`
}
// SyncStrategy controls the manner in which a sync is performed
type SyncStrategy struct {
// Apply wil perform a `kubectl apply` to perform the sync. This is the default strategy
// Apply wil perform a `kubectl apply` to perform the sync.
Apply *SyncStrategyApply `json:"apply,omitempty" protobuf:"bytes,1,opt,name=apply"`
// Hook will submit any referenced resources to perform the sync
// Hook will submit any referenced resources to perform the sync. This is the default strategy
Hook *SyncStrategyHook `json:"hook,omitempty" protobuf:"bytes,2,opt,name=hook"`
}
@@ -171,7 +209,6 @@ type ResourceDetails struct {
// DeploymentInfo contains information relevant to an application deployment
type DeploymentInfo struct {
Params []ComponentParameter `json:"params" protobuf:"bytes,1,name=params"`
Revision string `json:"revision" protobuf:"bytes,2,opt,name=revision"`
ComponentParameterOverrides []ComponentParameter `json:"componentParameterOverrides,omitempty" protobuf:"bytes,3,opt,name=componentParameterOverrides"`
DeployedAt metav1.Time `json:"deployedAt" protobuf:"bytes,4,opt,name=deployedAt"`
@@ -218,6 +255,8 @@ type ApplicationSpec struct {
Destination ApplicationDestination `json:"destination" protobuf:"bytes,2,name=destination"`
// Project is a application project name. Empty name means that application belongs to 'default' project.
Project string `json:"project" protobuf:"bytes,3,name=project"`
// SyncPolicy controls when a sync will be performed
SyncPolicy *SyncPolicy `json:"syncPolicy,omitempty" protobuf:"bytes,4,name=syncPolicy"`
}
// ComponentParameter contains information about component parameter value
@@ -283,8 +322,10 @@ const (
ApplicationConditionDeletionError = "DeletionError"
// ApplicationConditionInvalidSpecError indicates that application source is invalid
ApplicationConditionInvalidSpecError = "InvalidSpecError"
// ApplicationComparisonError indicates controller failed to compare application state
// ApplicationConditionComparisonError indicates controller failed to compare application state
ApplicationConditionComparisonError = "ComparisonError"
// ApplicationConditionSyncError indicates controller failed to automatically sync the application
ApplicationConditionSyncError = "SyncError"
// ApplicationConditionUnknownError indicates an unknown controller error
ApplicationConditionUnknownError = "UnknownError"
// ApplicationConditionSharedResourceWarning indicates that controller detected resources which belongs to more than one application
@@ -305,6 +346,7 @@ type ComparisonResult struct {
ComparedTo ApplicationSource `json:"comparedTo" protobuf:"bytes,2,opt,name=comparedTo"`
Status ComparisonStatus `json:"status" protobuf:"bytes,5,opt,name=status,casttype=ComparisonStatus"`
Resources []ResourceState `json:"resources" protobuf:"bytes,6,opt,name=resources"`
Revision string `json:"revision" protobuf:"bytes,7,opt,name=revision"`
}
type HealthStatus struct {
@@ -374,6 +416,15 @@ type ClusterList struct {
Items []Cluster `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// AWSAuthConfig is an AWS IAM authentication configuration
type AWSAuthConfig struct {
// ClusterName contains AWS cluster name
ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,1,opt,name=clusterName"`
// RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.
RoleARN string `json:"roleARN,omitempty" protobuf:"bytes,2,opt,name=roleARN"`
}
// ClusterConfig is the configuration attributes. This structure is subset of the go-client
// rest.Config with annotations added for marshalling.
type ClusterConfig struct {
@@ -388,6 +439,9 @@ type ClusterConfig struct {
// TLSClientConfig contains settings to enable transport layer security
TLSClientConfig `json:"tlsClientConfig" protobuf:"bytes,4,opt,name=tlsClientConfig"`
// AWSAuthConfig contains IAM authentication configuration
AWSAuthConfig *AWSAuthConfig `json:"awsAuthConfig" protobuf:"bytes,5,opt,name=awsAuthConfig"`
}
// TLSClientConfig contains settings to enable transport layer security
@@ -443,6 +497,17 @@ type AppProject struct {
Spec AppProjectSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
}
// ProjectPoliciesString returns Casbin formated string of a project's polcies for each role
func (proj *AppProject) ProjectPoliciesString() string {
var policies []string
for _, role := range proj.Spec.Roles {
projectPolicy := fmt.Sprintf("p, proj:%s:%s, projects, get, %s, allow", proj.ObjectMeta.Name, role.Name, proj.ObjectMeta.Name)
policies = append(policies, projectPolicy)
policies = append(policies, role.Policies...)
}
return strings.Join(policies, "\n")
}
// AppProjectSpec represents
type AppProjectSpec struct {
// SourceRepos contains list of git repository URLs which can be used for deployment
@@ -453,15 +518,29 @@ type AppProjectSpec struct {
// Description contains optional project description
Description string `json:"description,omitempty" protobuf:"bytes,3,opt,name=description"`
Roles []ProjectRole `json:"roles,omitempty" protobuf:"bytes,4,rep,name=roles"`
// ClusterResourceWhitelist contains list of whitelisted cluster level resources
ClusterResourceWhitelist []metav1.GroupKind `json:"clusterResourceWhitelist,omitempty" protobuf:"bytes,5,opt,name=clusterResourceWhitelist"`
// NamespaceResourceBlacklist contains list of blacklisted namespace level resources
NamespaceResourceBlacklist []metav1.GroupKind `json:"namespaceResourceBlacklist,omitempty" protobuf:"bytes,6,opt,name=namespaceResourceBlacklist"`
}
func GetDefaultProject(namespace string) AppProject {
return AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: common.DefaultAppProjectName,
Namespace: namespace,
},
}
// ProjectRole represents a role that has access to a project
type ProjectRole struct {
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
Description string `json:"description" protobuf:"bytes,2,opt,name=description"`
// Policies Stores a list of casbin formated strings that define access policies for the role in the project.
Policies []string `json:"policies" protobuf:"bytes,3,rep,name=policies"`
JWTTokens []JWTToken `json:"jwtTokens" protobuf:"bytes,4,rep,name=jwtTokens"`
}
// JWTToken holds the issuedAt and expiresAt values of a token
type JWTToken struct {
IssuedAt int64 `json:"iat,omitempty" protobuf:"int64,1,opt,name=iat"`
ExpiresAt int64 `json:"exp,omitempty" protobuf:"int64,2,opt,name=exp"`
}
func (app *Application) getFinalizerIndex(name string) int {
@@ -524,16 +603,35 @@ func (spec ApplicationSpec) GetProject() string {
return spec.Project
}
func (proj AppProject) IsDefault() bool {
return proj.Name == "" || proj.Name == common.DefaultAppProjectName
func isResourceInList(res metav1.GroupKind, list []metav1.GroupKind) bool {
for _, item := range list {
ok, err := filepath.Match(item.Kind, res.Kind)
if ok && err == nil {
ok, err = filepath.Match(item.Group, res.Group)
if ok && err == nil {
return true
}
}
}
return false
}
func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
if proj.IsDefault() {
return true
func (proj AppProject) IsResourcePermitted(res metav1.GroupKind, namespaced bool) bool {
if namespaced {
return !isResourceInList(res, proj.Spec.NamespaceResourceBlacklist)
} else {
return isResourceInList(res, proj.Spec.ClusterResourceWhitelist)
}
}
// IsSourcePermitted validates if the provided application's source is a one of the allowed sources for the project.
func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
normalizedURL := git.NormalizeGitURL(src.RepoURL)
for _, repoURL := range proj.Spec.SourceRepos {
if repoURL == "*" {
return true
}
if git.NormalizeGitURL(repoURL) == normalizedURL {
return true
}
@@ -541,13 +639,13 @@ func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
return false
}
// IsDestinationPermitted validiates if the provided application's destination is one of the allowed destinations for the project
func (proj AppProject) IsDestinationPermitted(dst ApplicationDestination) bool {
if proj.IsDefault() {
return true
}
for _, item := range proj.Spec.Destinations {
if item.Server == dst.Server && item.Namespace == dst.Namespace {
return true
if item.Server == dst.Server || item.Server == "*" {
if item.Namespace == dst.Namespace || item.Namespace == "*" {
return true
}
}
}
return false
@@ -555,18 +653,42 @@ func (proj AppProject) IsDestinationPermitted(dst ApplicationDestination) bool {
// RESTConfig returns a go-client REST config from cluster
func (c *Cluster) RESTConfig() *rest.Config {
if c.Server == common.KubernetesInternalAPIServerAddr && c.Config.Username == "" && c.Config.Password == "" && c.Config.BearerToken == "" {
config, err := rest.InClusterConfig()
if err != nil {
panic("Unable to create in-cluster config")
}
return config
}
tlsClientConfig := rest.TLSClientConfig{
Insecure: c.Config.TLSClientConfig.Insecure,
ServerName: c.Config.TLSClientConfig.ServerName,
CertData: c.Config.TLSClientConfig.CertData,
KeyData: c.Config.TLSClientConfig.KeyData,
CAData: c.Config.TLSClientConfig.CAData,
}
if c.Config.AWSAuthConfig != nil {
args := []string{"token", "-i", c.Config.AWSAuthConfig.ClusterName}
if c.Config.AWSAuthConfig.RoleARN != "" {
args = append(args, "-r", c.Config.AWSAuthConfig.RoleARN)
}
return &rest.Config{
Host: c.Server,
TLSClientConfig: tlsClientConfig,
ExecProvider: &api.ExecConfig{
APIVersion: "client.authentication.k8s.io/v1alpha1",
Command: "aws-iam-authenticator",
Args: args,
},
}
}
return &rest.Config{
Host: c.Server,
Username: c.Config.Username,
Password: c.Config.Password,
BearerToken: c.Config.BearerToken,
TLSClientConfig: rest.TLSClientConfig{
Insecure: c.Config.TLSClientConfig.Insecure,
ServerName: c.Config.TLSClientConfig.ServerName,
CertData: c.Config.TLSClientConfig.CertData,
KeyData: c.Config.TLSClientConfig.KeyData,
CAData: c.Config.TLSClientConfig.CAData,
},
Host: c.Server,
Username: c.Config.Username,
Password: c.Config.Password,
BearerToken: c.Config.BearerToken,
TLSClientConfig: tlsClientConfig,
}
}

View File

@@ -9,6 +9,22 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AWSAuthConfig) DeepCopyInto(out *AWSAuthConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSAuthConfig.
func (in *AWSAuthConfig) DeepCopy() *AWSAuthConfig {
if in == nil {
return nil
}
out := new(AWSAuthConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppProject) DeepCopyInto(out *AppProject) {
*out = *in
@@ -82,6 +98,23 @@ func (in *AppProjectSpec) DeepCopyInto(out *AppProjectSpec) {
*out = make([]ApplicationDestination, len(*in))
copy(*out, *in)
}
if in.Roles != nil {
in, out := &in.Roles, &out.Roles
*out = make([]ProjectRole, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ClusterResourceWhitelist != nil {
in, out := &in.ClusterResourceWhitelist, &out.ClusterResourceWhitelist
*out = make([]v1.GroupKind, len(*in))
copy(*out, *in)
}
if in.NamespaceResourceBlacklist != nil {
in, out := &in.NamespaceResourceBlacklist, &out.NamespaceResourceBlacklist
*out = make([]v1.GroupKind, len(*in))
copy(*out, *in)
}
return
}
@@ -228,6 +261,15 @@ func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
*out = *in
in.Source.DeepCopyInto(&out.Source)
out.Destination = in.Destination
if in.SyncPolicy != nil {
in, out := &in.SyncPolicy, &out.SyncPolicy
if *in == nil {
*out = nil
} else {
*out = new(SyncPolicy)
(*in).DeepCopyInto(*out)
}
}
return
}
@@ -324,6 +366,15 @@ func (in *Cluster) DeepCopy() *Cluster {
func (in *ClusterConfig) DeepCopyInto(out *ClusterConfig) {
*out = *in
in.TLSClientConfig.DeepCopyInto(&out.TLSClientConfig)
if in.AWSAuthConfig != nil {
in, out := &in.AWSAuthConfig, &out.AWSAuthConfig
if *in == nil {
*out = nil
} else {
*out = new(AWSAuthConfig)
**out = **in
}
}
return
}
@@ -430,11 +481,6 @@ func (in *ConnectionState) DeepCopy() *ConnectionState {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DeploymentInfo) DeepCopyInto(out *DeploymentInfo) {
*out = *in
if in.Params != nil {
in, out := &in.Params, &out.Params
*out = make([]ComponentParameter, len(*in))
copy(*out, *in)
}
if in.ComponentParameterOverrides != nil {
in, out := &in.ComponentParameterOverrides, &out.ComponentParameterOverrides
*out = make([]ComponentParameter, len(*in))
@@ -486,6 +532,22 @@ func (in *HookStatus) DeepCopy() *HookStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JWTToken) DeepCopyInto(out *JWTToken) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTToken.
func (in *JWTToken) DeepCopy() *JWTToken {
if in == nil {
return nil
}
out := new(JWTToken)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Operation) DeepCopyInto(out *Operation) {
*out = *in
@@ -498,15 +560,6 @@ func (in *Operation) DeepCopyInto(out *Operation) {
(*in).DeepCopyInto(*out)
}
}
if in.Rollback != nil {
in, out := &in.Rollback, &out.Rollback
if *in == nil {
*out = nil
} else {
*out = new(RollbackOperation)
**out = **in
}
}
return
}
@@ -533,15 +586,6 @@ func (in *OperationState) DeepCopyInto(out *OperationState) {
(*in).DeepCopyInto(*out)
}
}
if in.RollbackResult != nil {
in, out := &in.RollbackResult, &out.RollbackResult
if *in == nil {
*out = nil
} else {
*out = new(SyncOperationResult)
(*in).DeepCopyInto(*out)
}
}
in.StartedAt.DeepCopyInto(&out.StartedAt)
if in.FinishedAt != nil {
in, out := &in.FinishedAt, &out.FinishedAt
@@ -565,6 +609,32 @@ func (in *OperationState) DeepCopy() *OperationState {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProjectRole) DeepCopyInto(out *ProjectRole) {
*out = *in
if in.Policies != nil {
in, out := &in.Policies, &out.Policies
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.JWTTokens != nil {
in, out := &in.JWTTokens, &out.JWTTokens
*out = make([]JWTToken, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectRole.
func (in *ProjectRole) DeepCopy() *ProjectRole {
if in == nil {
return nil
}
out := new(ProjectRole)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Repository) DeepCopyInto(out *Repository) {
*out = *in
@@ -669,22 +739,6 @@ func (in *ResourceState) DeepCopy() *ResourceState {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RollbackOperation) DeepCopyInto(out *RollbackOperation) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RollbackOperation.
func (in *RollbackOperation) DeepCopy() *RollbackOperation {
if in == nil {
return nil
}
out := new(RollbackOperation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncOperation) DeepCopyInto(out *SyncOperation) {
*out = *in
@@ -697,6 +751,16 @@ func (in *SyncOperation) DeepCopyInto(out *SyncOperation) {
(*in).DeepCopyInto(*out)
}
}
if in.ParameterOverrides != nil {
in, out := &in.ParameterOverrides, &out.ParameterOverrides
*out = make(ParameterOverrides, len(*in))
copy(*out, *in)
}
if in.Resources != nil {
in, out := &in.Resources, &out.Resources
*out = make([]SyncOperationResource, len(*in))
copy(*out, *in)
}
return
}
@@ -710,6 +774,22 @@ func (in *SyncOperation) DeepCopy() *SyncOperation {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncOperationResource) DeepCopyInto(out *SyncOperationResource) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncOperationResource.
func (in *SyncOperationResource) DeepCopy() *SyncOperationResource {
if in == nil {
return nil
}
out := new(SyncOperationResource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncOperationResult) DeepCopyInto(out *SyncOperationResult) {
*out = *in
@@ -750,6 +830,47 @@ func (in *SyncOperationResult) DeepCopy() *SyncOperationResult {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncPolicy) DeepCopyInto(out *SyncPolicy) {
*out = *in
if in.Automated != nil {
in, out := &in.Automated, &out.Automated
if *in == nil {
*out = nil
} else {
*out = new(SyncPolicyAutomated)
**out = **in
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncPolicy.
func (in *SyncPolicy) DeepCopy() *SyncPolicy {
if in == nil {
return nil
}
out := new(SyncPolicy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncPolicyAutomated) DeepCopyInto(out *SyncPolicyAutomated) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncPolicyAutomated.
func (in *SyncPolicyAutomated) DeepCopy() *SyncPolicyAutomated {
if in == nil {
return nil
}
out := new(SyncPolicyAutomated)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncStrategy) DeepCopyInto(out *SyncStrategy) {
*out = *in

View File

@@ -2,7 +2,6 @@ package versioned
import (
argoprojv1alpha1 "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/typed/application/v1alpha1"
glog "github.com/golang/glog"
discovery "k8s.io/client-go/discovery"
rest "k8s.io/client-go/rest"
flowcontrol "k8s.io/client-go/util/flowcontrol"
@@ -56,7 +55,6 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
if err != nil {
glog.Errorf("failed to create the DiscoveryClient: %v", err)
return nil, err
}
return &cs, nil

View File

@@ -23,9 +23,10 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset {
}
}
fakePtr := testing.Fake{}
fakePtr.AddReactor("*", "*", testing.ObjectReaction(o))
fakePtr.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
cs := &Clientset{}
cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
cs.AddReactor("*", "*", testing.ObjectReaction(o))
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
gvr := action.GetResource()
ns := action.GetNamespace()
watch, err := o.Watch(gvr, ns)
@@ -35,7 +36,7 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset {
return true, watch, nil
})
return &Clientset{fakePtr, &fakediscovery.FakeDiscovery{Fake: &fakePtr}}
return cs
}
// Clientset implements clientset.Interface. Meant to be embedded into a

View File

@@ -6,15 +6,14 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
util_runtime "k8s.io/apimachinery/pkg/util/runtime"
)
var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)
var parameterCodec = runtime.NewParameterCodec(scheme)
func init() {
v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
AddToScheme(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
argoprojv1alpha1.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
@@ -27,10 +26,13 @@ func init() {
// )
//
// kclientset, _ := kubernetes.NewForConfig(c)
// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
//
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
// correctly.
func AddToScheme(scheme *runtime.Scheme) {
argoprojv1alpha1.AddToScheme(scheme)
var AddToScheme = localSchemeBuilder.AddToScheme
func init() {
v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
util_runtime.Must(AddToScheme(scheme))
}

View File

@@ -6,15 +6,14 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
util_runtime "k8s.io/apimachinery/pkg/util/runtime"
)
var Scheme = runtime.NewScheme()
var Codecs = serializer.NewCodecFactory(Scheme)
var ParameterCodec = runtime.NewParameterCodec(Scheme)
func init() {
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
AddToScheme(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
argoprojv1alpha1.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
@@ -27,10 +26,13 @@ func init() {
// )
//
// kclientset, _ := kubernetes.NewForConfig(c)
// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
//
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
// correctly.
func AddToScheme(scheme *runtime.Scheme) {
argoprojv1alpha1.AddToScheme(scheme)
var AddToScheme = localSchemeBuilder.AddToScheme
func init() {
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
util_runtime.Must(AddToScheme(Scheme))
}

View File

@@ -44,7 +44,7 @@ func (c *FakeApplications) List(opts v1.ListOptions) (result *v1alpha1.Applicati
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.ApplicationList{}
list := &v1alpha1.ApplicationList{ListMeta: obj.(*v1alpha1.ApplicationList).ListMeta}
for _, item := range obj.(*v1alpha1.ApplicationList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)

View File

@@ -44,7 +44,7 @@ func (c *FakeAppProjects) List(opts v1.ListOptions) (result *v1alpha1.AppProject
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.AppProjectList{}
list := &v1alpha1.AppProjectList{ListMeta: obj.(*v1alpha1.AppProjectList).ListMeta}
for _, item := range obj.(*v1alpha1.AppProjectList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)

View File

@@ -14,12 +14,16 @@ import (
cache "k8s.io/client-go/tools/cache"
)
// SharedInformerOption defines the functional option type for SharedInformerFactory.
type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory
type sharedInformerFactory struct {
client versioned.Interface
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
lock sync.Mutex
defaultResync time.Duration
customResync map[reflect.Type]time.Duration
informers map[reflect.Type]cache.SharedIndexInformer
// startedInformers is used for tracking which informers have been started.
@@ -27,23 +31,62 @@ type sharedInformerFactory struct {
startedInformers map[reflect.Type]bool
}
// NewSharedInformerFactory constructs a new instance of sharedInformerFactory
// WithCustomResyncConfig sets a custom resync period for the specified informer types.
func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
for k, v := range resyncConfig {
factory.customResync[reflect.TypeOf(k)] = v
}
return factory
}
}
// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.
func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.tweakListOptions = tweakListOptions
return factory
}
}
// WithNamespace limits the SharedInformerFactory to the specified namespace.
func WithNamespace(namespace string) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.namespace = namespace
return factory
}
}
// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.
func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {
return NewFilteredSharedInformerFactory(client, defaultResync, v1.NamespaceAll, nil)
return NewSharedInformerFactoryWithOptions(client, defaultResync)
}
// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.
// Listers obtained via this SharedInformerFactory will be subject to the same filters
// as specified here.
// Deprecated: Please use NewSharedInformerFactoryWithOptions instead
func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {
return &sharedInformerFactory{
return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))
}
// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.
func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {
factory := &sharedInformerFactory{
client: client,
namespace: namespace,
tweakListOptions: tweakListOptions,
namespace: v1.NamespaceAll,
defaultResync: defaultResync,
informers: make(map[reflect.Type]cache.SharedIndexInformer),
startedInformers: make(map[reflect.Type]bool),
customResync: make(map[reflect.Type]time.Duration),
}
// Apply all options
for _, opt := range options {
factory = opt(factory)
}
return factory
}
// Start initializes all requested informers.
@@ -92,7 +135,13 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal
if exists {
return informer
}
informer = newFunc(f.client, f.defaultResync)
resyncPeriod, exists := f.customResync[informerType]
if !exists {
resyncPeriod = f.defaultResync
}
informer = newFunc(f.client, resyncPeriod)
f.informers[informerType] = informer
return informer

View File

@@ -1,10 +1,13 @@
package reposerver
import (
"crypto/tls"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/util"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
// Clientset represets repository server api clients
@@ -17,7 +20,7 @@ type clientSet struct {
}
func (c *clientSet) NewRepositoryClient() (util.Closer, repository.RepositoryServiceClient, error) {
conn, err := grpc.Dial(c.address, grpc.WithInsecure())
conn, err := grpc.Dial(c.address, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})))
if err != nil {
log.Errorf("Unable to connect to repository service with address %s", c.address)
return nil, nil, err

View File

@@ -11,9 +11,12 @@ import (
"strings"
"time"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/google/go-jsonnet"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
@@ -21,8 +24,9 @@ import (
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/helm"
ksutil "github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/kustomize"
)
const (
@@ -35,6 +39,7 @@ type AppSourceType string
const (
AppSourceKsonnet AppSourceType = "ksonnet"
AppSourceHelm AppSourceType = "helm"
AppSourceKustomize AppSourceType = "kustomize"
AppSourceDirectory AppSourceType = "directory"
)
@@ -56,17 +61,7 @@ func NewService(gitFactory git.ClientFactory, cache cache.Cache) *Service {
// ListDir lists the contents of a GitHub repo
func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, error) {
appRepoPath := tempRepoPath(q.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer s.repoLock.Unlock(appRepoPath)
gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey)
err := gitClient.Init()
if err != nil {
return nil, err
}
commitSHA, err := gitClient.LsRemote(q.Revision)
gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision)
if err != nil {
return nil, err
}
@@ -78,7 +73,9 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er
return &res, nil
}
err = checkoutRevision(gitClient, q.Revision)
s.repoLock.Lock(gitClient.Root())
defer s.repoLock.Unlock(gitClient.Root())
commitSHA, err = checkoutRevision(gitClient, commitSHA)
if err != nil {
return nil, err
}
@@ -92,7 +89,7 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er
Items: lsFiles,
}
err = s.cache.Set(&cache.Item{
Key: cacheKey,
Key: listDirCacheKey(commitSHA, q),
Object: &res,
Expiration: DefaultRepoCacheExpiration,
})
@@ -103,16 +100,21 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er
}
func (s *Service) GetFile(ctx context.Context, q *GetFileRequest) (*GetFileResponse, error) {
appRepoPath := tempRepoPath(q.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer s.repoLock.Unlock(appRepoPath)
gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey)
err := gitClient.Init()
gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision)
if err != nil {
return nil, err
}
err = checkoutRevision(gitClient, q.Revision)
cacheKey := getFileCacheKey(commitSHA, q)
var res GetFileResponse
err = s.cache.Get(cacheKey, &res)
if err == nil {
log.Infof("getfile cache hit: %s", cacheKey)
return &res, nil
}
s.repoLock.Lock(gitClient.Root())
defer s.repoLock.Unlock(gitClient.Root())
commitSHA, err = checkoutRevision(gitClient, commitSHA)
if err != nil {
return nil, err
}
@@ -120,36 +122,27 @@ func (s *Service) GetFile(ctx context.Context, q *GetFileRequest) (*GetFileRespo
if err != nil {
return nil, err
}
res := GetFileResponse{
res = GetFileResponse{
Data: data,
}
err = s.cache.Set(&cache.Item{
Key: getFileCacheKey(commitSHA, q),
Object: &res,
Expiration: DefaultRepoCacheExpiration,
})
if err != nil {
log.Warnf("getfile cache set error %s: %v", cacheKey, err)
}
return &res, nil
}
func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*ManifestResponse, error) {
var res ManifestResponse
if git.IsCommitSHA(q.Revision) {
cacheKey := manifestCacheKey(q.Revision, q)
err := s.cache.Get(cacheKey, res)
if err == nil {
log.Infof("manifest cache hit: %s", cacheKey)
return &res, nil
}
}
appRepoPath := tempRepoPath(q.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer s.repoLock.Unlock(appRepoPath)
gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey)
err := gitClient.Init()
if err != nil {
return nil, err
}
commitSHA, err := gitClient.LsRemote(q.Revision)
gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision)
if err != nil {
return nil, err
}
cacheKey := manifestCacheKey(commitSHA, q)
var res ManifestResponse
err = s.cache.Get(cacheKey, &res)
if err == nil {
log.Infof("manifest cache hit: %s", cacheKey)
@@ -161,11 +154,13 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
log.Infof("manifest cache miss: %s", cacheKey)
}
err = checkoutRevision(gitClient, q.Revision)
s.repoLock.Lock(gitClient.Root())
defer s.repoLock.Unlock(gitClient.Root())
commitSHA, err = checkoutRevision(gitClient, commitSHA)
if err != nil {
return nil, err
}
appPath := path.Join(appRepoPath, q.Path)
appPath := path.Join(gitClient.Root(), q.Path)
genRes, err := generateManifests(appPath, q)
if err != nil {
@@ -174,7 +169,7 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
res = *genRes
res.Revision = commitSHA
err = s.cache.Set(&cache.Item{
Key: cacheKey,
Key: manifestCacheKey(commitSHA, q),
Object: res,
Expiration: DefaultRepoCacheExpiration,
})
@@ -188,16 +183,20 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
func generateManifests(appPath string, q *ManifestRequest) (*ManifestResponse, error) {
var targetObjs []*unstructured.Unstructured
var params []*v1alpha1.ComponentParameter
var env *app.EnvironmentSpec
var dest *v1alpha1.ApplicationDestination
var err error
appSourceType := IdentifyAppSourceTypeByAppDir(appPath)
switch appSourceType {
case AppSourceKsonnet:
targetObjs, params, env, err = ksShow(appPath, q.Environment, q.ComponentParameterOverrides)
targetObjs, params, dest, err = ksShow(appPath, q.Environment, q.ComponentParameterOverrides)
case AppSourceHelm:
h := helm.NewHelmApp(appPath)
targetObjs, err = h.Template(q.AppLabel, q.ValueFiles, q.ComponentParameterOverrides)
err = h.DependencyBuild()
if err != nil {
return nil, err
}
targetObjs, err = h.Template(q.AppLabel, q.Namespace, q.ValueFiles, q.ComponentParameterOverrides)
if err != nil {
return nil, err
}
@@ -205,36 +204,58 @@ func generateManifests(appPath string, q *ManifestRequest) (*ManifestResponse, e
if err != nil {
return nil, err
}
case AppSourceKustomize:
k := kustomize.NewKustomizeApp(appPath)
targetObjs, err = k.Build()
case AppSourceDirectory:
targetObjs, err = findManifests(appPath)
}
if err != nil {
return nil, err
}
// TODO(jessesuen): we need to sort objects based on their dependency order of creation
manifests := make([]string, len(targetObjs))
for i, target := range targetObjs {
if q.AppLabel != "" {
err = kube.SetLabel(target, common.LabelApplicationName, q.AppLabel)
manifests := make([]string, 0)
for _, obj := range targetObjs {
var targets []*unstructured.Unstructured
if obj.IsList() {
err = obj.EachListItem(func(object runtime.Object) error {
unstructuredObj, ok := object.(*unstructured.Unstructured)
if ok {
targets = append(targets, unstructuredObj)
return nil
} else {
return fmt.Errorf("resource list item has unexpected type")
}
})
if err != nil {
return nil, err
}
} else {
targets = []*unstructured.Unstructured{obj}
}
manifestStr, err := json.Marshal(target.Object)
if err != nil {
return nil, err
for _, target := range targets {
if q.AppLabel != "" && !kube.IsCRD(target) {
err = kube.SetLabel(target, common.LabelApplicationName, q.AppLabel)
if err != nil {
return nil, err
}
}
manifestStr, err := json.Marshal(target.Object)
if err != nil {
return nil, err
}
manifests = append(manifests, string(manifestStr))
}
manifests[i] = string(manifestStr)
}
res := ManifestResponse{
Manifests: manifests,
Params: params,
}
if env != nil {
res.Namespace = env.Destination.Namespace
res.Server = env.Destination.Server
if dest != nil {
res.Namespace = dest.Namespace
res.Server = dest.Server
}
return &res, nil
}
@@ -252,6 +273,9 @@ func IdentifyAppSourceTypeByAppDir(appDirPath string) AppSourceType {
if pathExists(path.Join(appDirPath, "Chart.yaml")) {
return AppSourceHelm
}
if pathExists(path.Join(appDirPath, "kustomization.yaml")) {
return AppSourceKustomize
}
return AppSourceDirectory
}
@@ -263,45 +287,53 @@ func IdentifyAppSourceTypeByAppPath(appFilePath string) AppSourceType {
if strings.HasSuffix(appFilePath, "Chart.yaml") {
return AppSourceHelm
}
if strings.HasSuffix(appFilePath, "kustomization.yaml") {
return AppSourceKustomize
}
return AppSourceDirectory
}
// checkoutRevision is a convenience function to initialize a repo, fetch, and checkout a revision
func checkoutRevision(gitClient git.Client, revision string) error {
err := gitClient.Fetch()
// Returns the 40 character commit SHA after the checkout has been performed
func checkoutRevision(gitClient git.Client, commitSHA string) (string, error) {
err := gitClient.Init()
if err != nil {
return err
return "", status.Errorf(codes.Internal, "Failed to initialize git repo: %v", err)
}
err = gitClient.Reset()
err = gitClient.Fetch()
if err != nil {
log.Warn(err)
return "", status.Errorf(codes.Internal, "Failed to fetch git repo: %v", err)
}
err = gitClient.Checkout(revision)
err = gitClient.Checkout(commitSHA)
if err != nil {
return err
return "", status.Errorf(codes.Internal, "Failed to checkout %s: %v", commitSHA, err)
}
return nil
return gitClient.CommitSHA()
}
func manifestCacheKey(commitSHA string, q *ManifestRequest) string {
pStr, _ := json.Marshal(q.ComponentParameterOverrides)
valuesFiles := strings.Join(q.ValueFiles, ",")
return fmt.Sprintf("mfst|%s|%s|%s|%s|%s|%s", q.AppLabel, q.Path, q.Environment, commitSHA, string(pStr), valuesFiles)
return fmt.Sprintf("mfst|%s|%s|%s|%s|%s|%s|%s", q.AppLabel, q.Path, q.Environment, commitSHA, string(pStr), valuesFiles, q.Namespace)
}
func listDirCacheKey(commitSHA string, q *ListDirRequest) string {
return fmt.Sprintf("ldir|%s|%s", q.Path, commitSHA)
}
func getFileCacheKey(commitSHA string, q *GetFileRequest) string {
return fmt.Sprintf("gfile|%s|%s", q.Path, commitSHA)
}
// ksShow runs `ks show` in an app directory after setting any component parameter overrides
func ksShow(appPath, envName string, overrides []*v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, []*v1alpha1.ComponentParameter, *app.EnvironmentSpec, error) {
ksApp, err := ksutil.NewKsonnetApp(appPath)
func ksShow(appPath, envName string, overrides []*v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, []*v1alpha1.ComponentParameter, *v1alpha1.ApplicationDestination, error) {
ksApp, err := ksonnet.NewKsonnetApp(appPath)
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to load application from %s: %v", appPath, err)
return nil, nil, nil, status.Errorf(codes.FailedPrecondition, "unable to load application from %s: %v", appPath, err)
}
params, err := ksApp.ListEnvParams(envName)
if err != nil {
return nil, nil, nil, fmt.Errorf("Failed to list ksonnet app params: %v", err)
return nil, nil, nil, status.Errorf(codes.InvalidArgument, "Failed to list ksonnet app params: %v", err)
}
if overrides != nil {
for _, override := range overrides {
@@ -311,25 +343,24 @@ func ksShow(appPath, envName string, overrides []*v1alpha1.ComponentParameter) (
}
}
}
appSpec := ksApp.App()
env, err := appSpec.Environment(envName)
dest, err := ksApp.Destination(envName)
if err != nil {
return nil, nil, nil, fmt.Errorf("environment '%s' does not exist in ksonnet app", envName)
return nil, nil, nil, status.Errorf(codes.NotFound, "environment %q does not exist in ksonnet app", envName)
}
targetObjs, err := ksApp.Show(envName)
if err != nil {
return nil, nil, nil, err
}
return targetObjs, params, env, nil
return targetObjs, params, dest, nil
}
var manifestFile = regexp.MustCompile(`^.*\.(yaml|yml|json)$`)
var manifestFile = regexp.MustCompile(`^.*\.(yaml|yml|json|jsonnet)$`)
// findManifests looks at all yaml files in a directory and unmarshals them into a list of unstructured objects
func findManifests(appPath string) ([]*unstructured.Unstructured, error) {
files, err := ioutil.ReadDir(appPath)
if err != nil {
return nil, fmt.Errorf("Failed to read dir %s: %v", appPath, err)
return nil, status.Errorf(codes.FailedPrecondition, "Failed to read dir %s: %v", appPath, err)
}
var objs []*unstructured.Unstructured
for _, f := range files {
@@ -344,9 +375,32 @@ func findManifests(appPath string) ([]*unstructured.Unstructured, error) {
var obj unstructured.Unstructured
err = json.Unmarshal(out, &obj)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshal '%s': %v", f.Name(), err)
return nil, status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
objs = append(objs, &obj)
} else if strings.HasSuffix(f.Name(), ".jsonnet") {
vm := jsonnet.MakeVM()
vm.Importer(&jsonnet.FileImporter{
JPaths: []string{appPath},
})
jsonStr, err := vm.EvaluateSnippet(f.Name(), string(out))
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Failed to evaluate jsonnet %q: %v", f.Name(), err)
}
// attempt to unmarshal either array or single object
var jsonObjs []*unstructured.Unstructured
err = json.Unmarshal([]byte(jsonStr), &jsonObjs)
if err == nil {
objs = append(objs, jsonObjs...)
} else {
var jsonObj unstructured.Unstructured
err = json.Unmarshal([]byte(jsonStr), &jsonObj)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Failed to unmarshal generated json %q: %v", f.Name(), err)
}
objs = append(objs, &jsonObj)
}
} else {
yamlObjs, err := kube.SplitYAML(string(out))
if err != nil {
@@ -354,7 +408,7 @@ func findManifests(appPath string) ([]*unstructured.Unstructured, error) {
// If we get here, we had a multiple objects in a single YAML file which had some
// valid k8s objects, but errors parsing others (within the same file). It's very
// likely the user messed up a portion of the YAML, so report on that.
return nil, fmt.Errorf("Failed to unmarshal '%s': %v", f.Name(), err)
return nil, status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, it might be a unrelated YAML file which we will ignore
continue
@@ -374,3 +428,18 @@ func pathExists(name string) bool {
}
return true
}
// newClientResolveRevision is a helper to perform the common task of instantiating a git client
// and resolving a revision to a commit SHA
func (s *Service) newClientResolveRevision(repo *v1alpha1.Repository, revision string) (git.Client, string, error) {
appRepoPath := tempRepoPath(repo.Repo)
gitClient, err := s.gitFactory.NewClient(repo.Repo, appRepoPath, repo.Username, repo.Password, repo.SSHPrivateKey)
if err != nil {
return nil, "", err
}
commitSHA, err := gitClient.LsRemote(revision)
if err != nil {
return nil, "", err
}
return gitClient, commitSHA, nil
}

View File

@@ -1,29 +1,15 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: reposerver/repository/repository.proto
/*
Package repository is a generated protocol buffer package.
It is generated from these files:
reposerver/repository/repository.proto
It has these top-level messages:
ManifestRequest
ManifestResponse
ListDirRequest
FileList
GetFileRequest
GetFileResponse
*/
package repository
package repository // import "github.com/argoproj/argo-cd/reposerver/repository"
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
import _ "github.com/gogo/protobuf/gogoproto"
import _ "google.golang.org/genproto/googleapis/api/annotations"
import _ "k8s.io/api/core/v1"
import github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
import context "golang.org/x/net/context"
import grpc "google.golang.org/grpc"
@@ -43,21 +29,53 @@ const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
// ManifestRequest is a query for manifest generation.
type ManifestRequest struct {
Repo *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo" json:"repo,omitempty"`
Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"`
Environment string `protobuf:"bytes,4,opt,name=environment,proto3" json:"environment,omitempty"`
AppLabel string `protobuf:"bytes,5,opt,name=appLabel,proto3" json:"appLabel,omitempty"`
ComponentParameterOverrides []*github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.ComponentParameter `protobuf:"bytes,6,rep,name=componentParameterOverrides" json:"componentParameterOverrides,omitempty"`
ValueFiles []string `protobuf:"bytes,7,rep,name=valueFiles" json:"valueFiles,omitempty"`
Repo *v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo" json:"repo,omitempty"`
Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"`
Environment string `protobuf:"bytes,4,opt,name=environment,proto3" json:"environment,omitempty"`
AppLabel string `protobuf:"bytes,5,opt,name=appLabel,proto3" json:"appLabel,omitempty"`
ComponentParameterOverrides []*v1alpha1.ComponentParameter `protobuf:"bytes,6,rep,name=componentParameterOverrides" json:"componentParameterOverrides,omitempty"`
ValueFiles []string `protobuf:"bytes,7,rep,name=valueFiles" json:"valueFiles,omitempty"`
Namespace string `protobuf:"bytes,8,opt,name=namespace,proto3" json:"namespace,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ManifestRequest) Reset() { *m = ManifestRequest{} }
func (m *ManifestRequest) String() string { return proto.CompactTextString(m) }
func (*ManifestRequest) ProtoMessage() {}
func (*ManifestRequest) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{0} }
func (m *ManifestRequest) Reset() { *m = ManifestRequest{} }
func (m *ManifestRequest) String() string { return proto.CompactTextString(m) }
func (*ManifestRequest) ProtoMessage() {}
func (*ManifestRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_49651600e73b0b40, []int{0}
}
func (m *ManifestRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *ManifestRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_ManifestRequest.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *ManifestRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ManifestRequest.Merge(dst, src)
}
func (m *ManifestRequest) XXX_Size() int {
return m.Size()
}
func (m *ManifestRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ManifestRequest.DiscardUnknown(m)
}
func (m *ManifestRequest) GetRepo() *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository {
var xxx_messageInfo_ManifestRequest proto.InternalMessageInfo
func (m *ManifestRequest) GetRepo() *v1alpha1.Repository {
if m != nil {
return m.Repo
}
@@ -92,7 +110,7 @@ func (m *ManifestRequest) GetAppLabel() string {
return ""
}
func (m *ManifestRequest) GetComponentParameterOverrides() []*github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.ComponentParameter {
func (m *ManifestRequest) GetComponentParameterOverrides() []*v1alpha1.ComponentParameter {
if m != nil {
return m.ComponentParameterOverrides
}
@@ -106,18 +124,56 @@ func (m *ManifestRequest) GetValueFiles() []string {
return nil
}
type ManifestResponse struct {
Manifests []string `protobuf:"bytes,1,rep,name=manifests" json:"manifests,omitempty"`
Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
Server string `protobuf:"bytes,3,opt,name=server,proto3" json:"server,omitempty"`
Revision string `protobuf:"bytes,4,opt,name=revision,proto3" json:"revision,omitempty"`
Params []*github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.ComponentParameter `protobuf:"bytes,5,rep,name=params" json:"params,omitempty"`
func (m *ManifestRequest) GetNamespace() string {
if m != nil {
return m.Namespace
}
return ""
}
func (m *ManifestResponse) Reset() { *m = ManifestResponse{} }
func (m *ManifestResponse) String() string { return proto.CompactTextString(m) }
func (*ManifestResponse) ProtoMessage() {}
func (*ManifestResponse) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{1} }
type ManifestResponse struct {
Manifests []string `protobuf:"bytes,1,rep,name=manifests" json:"manifests,omitempty"`
Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
Server string `protobuf:"bytes,3,opt,name=server,proto3" json:"server,omitempty"`
Revision string `protobuf:"bytes,4,opt,name=revision,proto3" json:"revision,omitempty"`
Params []*v1alpha1.ComponentParameter `protobuf:"bytes,5,rep,name=params" json:"params,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ManifestResponse) Reset() { *m = ManifestResponse{} }
func (m *ManifestResponse) String() string { return proto.CompactTextString(m) }
func (*ManifestResponse) ProtoMessage() {}
func (*ManifestResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_49651600e73b0b40, []int{1}
}
func (m *ManifestResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *ManifestResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_ManifestResponse.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *ManifestResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ManifestResponse.Merge(dst, src)
}
func (m *ManifestResponse) XXX_Size() int {
return m.Size()
}
func (m *ManifestResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ManifestResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ManifestResponse proto.InternalMessageInfo
func (m *ManifestResponse) GetManifests() []string {
if m != nil {
@@ -147,7 +203,7 @@ func (m *ManifestResponse) GetRevision() string {
return ""
}
func (m *ManifestResponse) GetParams() []*github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.ComponentParameter {
func (m *ManifestResponse) GetParams() []*v1alpha1.ComponentParameter {
if m != nil {
return m.Params
}
@@ -156,17 +212,48 @@ func (m *ManifestResponse) GetParams() []*github_com_argoproj_argo_cd_pkg_apis_a
// ListDirRequest requests a repository directory structure
type ListDirRequest struct {
Repo *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo" json:"repo,omitempty"`
Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"`
Repo *v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo" json:"repo,omitempty"`
Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ListDirRequest) Reset() { *m = ListDirRequest{} }
func (m *ListDirRequest) String() string { return proto.CompactTextString(m) }
func (*ListDirRequest) ProtoMessage() {}
func (*ListDirRequest) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{2} }
func (m *ListDirRequest) Reset() { *m = ListDirRequest{} }
func (m *ListDirRequest) String() string { return proto.CompactTextString(m) }
func (*ListDirRequest) ProtoMessage() {}
func (*ListDirRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_49651600e73b0b40, []int{2}
}
func (m *ListDirRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *ListDirRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_ListDirRequest.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *ListDirRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ListDirRequest.Merge(dst, src)
}
func (m *ListDirRequest) XXX_Size() int {
return m.Size()
}
func (m *ListDirRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ListDirRequest.DiscardUnknown(m)
}
func (m *ListDirRequest) GetRepo() *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository {
var xxx_messageInfo_ListDirRequest proto.InternalMessageInfo
func (m *ListDirRequest) GetRepo() *v1alpha1.Repository {
if m != nil {
return m.Repo
}
@@ -189,13 +276,44 @@ func (m *ListDirRequest) GetPath() string {
// FileList returns the contents of the repo of a ListDir request
type FileList struct {
Items []string `protobuf:"bytes,1,rep,name=items" json:"items,omitempty"`
Items []string `protobuf:"bytes,1,rep,name=items" json:"items,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *FileList) Reset() { *m = FileList{} }
func (m *FileList) String() string { return proto.CompactTextString(m) }
func (*FileList) ProtoMessage() {}
func (*FileList) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{3} }
func (m *FileList) Reset() { *m = FileList{} }
func (m *FileList) String() string { return proto.CompactTextString(m) }
func (*FileList) ProtoMessage() {}
func (*FileList) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_49651600e73b0b40, []int{3}
}
func (m *FileList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *FileList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_FileList.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *FileList) XXX_Merge(src proto.Message) {
xxx_messageInfo_FileList.Merge(dst, src)
}
func (m *FileList) XXX_Size() int {
return m.Size()
}
func (m *FileList) XXX_DiscardUnknown() {
xxx_messageInfo_FileList.DiscardUnknown(m)
}
var xxx_messageInfo_FileList proto.InternalMessageInfo
func (m *FileList) GetItems() []string {
if m != nil {
@@ -206,17 +324,48 @@ func (m *FileList) GetItems() []string {
// GetFileRequest return
type GetFileRequest struct {
Repo *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo" json:"repo,omitempty"`
Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"`
Repo *v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo" json:"repo,omitempty"`
Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GetFileRequest) Reset() { *m = GetFileRequest{} }
func (m *GetFileRequest) String() string { return proto.CompactTextString(m) }
func (*GetFileRequest) ProtoMessage() {}
func (*GetFileRequest) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{4} }
func (m *GetFileRequest) Reset() { *m = GetFileRequest{} }
func (m *GetFileRequest) String() string { return proto.CompactTextString(m) }
func (*GetFileRequest) ProtoMessage() {}
func (*GetFileRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_49651600e73b0b40, []int{4}
}
func (m *GetFileRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *GetFileRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_GetFileRequest.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *GetFileRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetFileRequest.Merge(dst, src)
}
func (m *GetFileRequest) XXX_Size() int {
return m.Size()
}
func (m *GetFileRequest) XXX_DiscardUnknown() {
xxx_messageInfo_GetFileRequest.DiscardUnknown(m)
}
func (m *GetFileRequest) GetRepo() *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository {
var xxx_messageInfo_GetFileRequest proto.InternalMessageInfo
func (m *GetFileRequest) GetRepo() *v1alpha1.Repository {
if m != nil {
return m.Repo
}
@@ -239,13 +388,44 @@ func (m *GetFileRequest) GetPath() string {
// GetFileResponse returns the contents of the file of a GetFile request
type GetFileResponse struct {
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GetFileResponse) Reset() { *m = GetFileResponse{} }
func (m *GetFileResponse) String() string { return proto.CompactTextString(m) }
func (*GetFileResponse) ProtoMessage() {}
func (*GetFileResponse) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{5} }
func (m *GetFileResponse) Reset() { *m = GetFileResponse{} }
func (m *GetFileResponse) String() string { return proto.CompactTextString(m) }
func (*GetFileResponse) ProtoMessage() {}
func (*GetFileResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_49651600e73b0b40, []int{5}
}
func (m *GetFileResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *GetFileResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_GetFileResponse.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *GetFileResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetFileResponse.Merge(dst, src)
}
func (m *GetFileResponse) XXX_Size() int {
return m.Size()
}
func (m *GetFileResponse) XXX_DiscardUnknown() {
xxx_messageInfo_GetFileResponse.DiscardUnknown(m)
}
var xxx_messageInfo_GetFileResponse proto.InternalMessageInfo
func (m *GetFileResponse) GetData() []byte {
if m != nil {
@@ -292,7 +472,7 @@ func NewRepositoryServiceClient(cc *grpc.ClientConn) RepositoryServiceClient {
func (c *repositoryServiceClient) GenerateManifest(ctx context.Context, in *ManifestRequest, opts ...grpc.CallOption) (*ManifestResponse, error) {
out := new(ManifestResponse)
err := grpc.Invoke(ctx, "/repository.RepositoryService/GenerateManifest", in, out, c.cc, opts...)
err := c.cc.Invoke(ctx, "/repository.RepositoryService/GenerateManifest", in, out, opts...)
if err != nil {
return nil, err
}
@@ -301,7 +481,7 @@ func (c *repositoryServiceClient) GenerateManifest(ctx context.Context, in *Mani
func (c *repositoryServiceClient) ListDir(ctx context.Context, in *ListDirRequest, opts ...grpc.CallOption) (*FileList, error) {
out := new(FileList)
err := grpc.Invoke(ctx, "/repository.RepositoryService/ListDir", in, out, c.cc, opts...)
err := c.cc.Invoke(ctx, "/repository.RepositoryService/ListDir", in, out, opts...)
if err != nil {
return nil, err
}
@@ -310,7 +490,7 @@ func (c *repositoryServiceClient) ListDir(ctx context.Context, in *ListDirReques
func (c *repositoryServiceClient) GetFile(ctx context.Context, in *GetFileRequest, opts ...grpc.CallOption) (*GetFileResponse, error) {
out := new(GetFileResponse)
err := grpc.Invoke(ctx, "/repository.RepositoryService/GetFile", in, out, c.cc, opts...)
err := c.cc.Invoke(ctx, "/repository.RepositoryService/GetFile", in, out, opts...)
if err != nil {
return nil, err
}
@@ -483,6 +663,15 @@ func (m *ManifestRequest) MarshalTo(dAtA []byte) (int, error) {
i += copy(dAtA[i:], s)
}
}
if len(m.Namespace) > 0 {
dAtA[i] = 0x42
i++
i = encodeVarintRepository(dAtA, i, uint64(len(m.Namespace)))
i += copy(dAtA[i:], m.Namespace)
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
@@ -546,6 +735,9 @@ func (m *ManifestResponse) MarshalTo(dAtA []byte) (int, error) {
i += n
}
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
@@ -586,6 +778,9 @@ func (m *ListDirRequest) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintRepository(dAtA, i, uint64(len(m.Path)))
i += copy(dAtA[i:], m.Path)
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
@@ -619,6 +814,9 @@ func (m *FileList) MarshalTo(dAtA []byte) (int, error) {
i += copy(dAtA[i:], s)
}
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
@@ -659,6 +857,9 @@ func (m *GetFileRequest) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintRepository(dAtA, i, uint64(len(m.Path)))
i += copy(dAtA[i:], m.Path)
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
@@ -683,6 +884,9 @@ func (m *GetFileResponse) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintRepository(dAtA, i, uint64(len(m.Data)))
i += copy(dAtA[i:], m.Data)
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
@@ -730,6 +934,13 @@ func (m *ManifestRequest) Size() (n int) {
n += 1 + l + sovRepository(uint64(l))
}
}
l = len(m.Namespace)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
@@ -760,6 +971,9 @@ func (m *ManifestResponse) Size() (n int) {
n += 1 + l + sovRepository(uint64(l))
}
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
@@ -778,6 +992,9 @@ func (m *ListDirRequest) Size() (n int) {
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
@@ -790,6 +1007,9 @@ func (m *FileList) Size() (n int) {
n += 1 + l + sovRepository(uint64(l))
}
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
@@ -808,6 +1028,9 @@ func (m *GetFileRequest) Size() (n int) {
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
@@ -818,6 +1041,9 @@ func (m *GetFileResponse) Size() (n int) {
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
@@ -890,7 +1116,7 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error {
return io.ErrUnexpectedEOF
}
if m.Repo == nil {
m.Repo = &github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository{}
m.Repo = &v1alpha1.Repository{}
}
if err := m.Repo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
@@ -1038,7 +1264,7 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ComponentParameterOverrides = append(m.ComponentParameterOverrides, &github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.ComponentParameter{})
m.ComponentParameterOverrides = append(m.ComponentParameterOverrides, &v1alpha1.ComponentParameter{})
if err := m.ComponentParameterOverrides[len(m.ComponentParameterOverrides)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
@@ -1072,6 +1298,35 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error {
}
m.ValueFiles = append(m.ValueFiles, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Namespace = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
@@ -1084,6 +1339,7 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error {
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
@@ -1264,7 +1520,7 @@ func (m *ManifestResponse) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Params = append(m.Params, &github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.ComponentParameter{})
m.Params = append(m.Params, &v1alpha1.ComponentParameter{})
if err := m.Params[len(m.Params)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
@@ -1281,6 +1537,7 @@ func (m *ManifestResponse) Unmarshal(dAtA []byte) error {
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
@@ -1346,7 +1603,7 @@ func (m *ListDirRequest) Unmarshal(dAtA []byte) error {
return io.ErrUnexpectedEOF
}
if m.Repo == nil {
m.Repo = &github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository{}
m.Repo = &v1alpha1.Repository{}
}
if err := m.Repo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
@@ -1422,6 +1679,7 @@ func (m *ListDirRequest) Unmarshal(dAtA []byte) error {
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
@@ -1501,6 +1759,7 @@ func (m *FileList) Unmarshal(dAtA []byte) error {
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
@@ -1566,7 +1825,7 @@ func (m *GetFileRequest) Unmarshal(dAtA []byte) error {
return io.ErrUnexpectedEOF
}
if m.Repo == nil {
m.Repo = &github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository{}
m.Repo = &v1alpha1.Repository{}
}
if err := m.Repo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
@@ -1642,6 +1901,7 @@ func (m *GetFileRequest) Unmarshal(dAtA []byte) error {
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
@@ -1723,6 +1983,7 @@ func (m *GetFileResponse) Unmarshal(dAtA []byte) error {
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
@@ -1837,44 +2098,47 @@ var (
ErrIntOverflowRepository = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("reposerver/repository/repository.proto", fileDescriptorRepository) }
var fileDescriptorRepository = []byte{
// 576 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xae, 0xc9, 0x5f, 0xb3, 0x41, 0xb4, 0xac, 0x22, 0x64, 0x39, 0x51, 0x64, 0x59, 0x02, 0xe5,
0x82, 0xad, 0x84, 0x0b, 0x17, 0x84, 0x04, 0x85, 0x0a, 0xa9, 0x55, 0x91, 0x39, 0xc1, 0x05, 0x6d,
0x9c, 0xc1, 0x59, 0x62, 0xef, 0x2e, 0xbb, 0x1b, 0x4b, 0xbc, 0x03, 0x12, 0x0f, 0xc0, 0x0b, 0x71,
0xe4, 0x11, 0x50, 0x6e, 0x7d, 0x0b, 0xe4, 0x8d, 0x1d, 0x3b, 0x6d, 0xd4, 0x0b, 0xaa, 0xd4, 0xdb,
0xcc, 0x37, 0xb3, 0xf3, 0xcd, 0x7e, 0x33, 0x1a, 0xf4, 0x44, 0x82, 0xe0, 0x0a, 0x64, 0x06, 0x32,
0x30, 0x26, 0xd5, 0x5c, 0x7e, 0xaf, 0x99, 0xbe, 0x90, 0x5c, 0x73, 0x8c, 0x2a, 0xc4, 0xe9, 0xc7,
0x3c, 0xe6, 0x06, 0x0e, 0x72, 0x6b, 0x93, 0xe1, 0x0c, 0x63, 0xce, 0xe3, 0x04, 0x02, 0x22, 0x68,
0x40, 0x18, 0xe3, 0x9a, 0x68, 0xca, 0x99, 0x2a, 0xa2, 0xde, 0xf2, 0xb9, 0xf2, 0x29, 0x37, 0xd1,
0x88, 0x4b, 0x08, 0xb2, 0x49, 0x10, 0x03, 0x03, 0x49, 0x34, 0xcc, 0x8b, 0x9c, 0x77, 0x31, 0xd5,
0x8b, 0xd5, 0xcc, 0x8f, 0x78, 0x1a, 0x10, 0x69, 0x28, 0xbe, 0x1a, 0xe3, 0x69, 0x34, 0x0f, 0xc4,
0x32, 0xce, 0x1f, 0xab, 0x80, 0x08, 0x91, 0xd0, 0xc8, 0x14, 0x0f, 0xb2, 0x09, 0x49, 0xc4, 0x82,
0x5c, 0x2b, 0xe5, 0xfd, 0x68, 0xa0, 0xa3, 0x73, 0xc2, 0xe8, 0x17, 0x50, 0x3a, 0x84, 0x6f, 0x2b,
0x50, 0x1a, 0x7f, 0x44, 0xcd, 0xfc, 0x13, 0xb6, 0xe5, 0x5a, 0xe3, 0xde, 0xf4, 0x8d, 0x5f, 0xb1,
0xf9, 0x25, 0x9b, 0x31, 0x3e, 0x47, 0x73, 0x5f, 0x2c, 0x63, 0x3f, 0x67, 0xf3, 0x6b, 0x6c, 0x7e,
0xc9, 0xe6, 0x87, 0x5b, 0x2d, 0x42, 0x53, 0x12, 0x3b, 0xe8, 0x50, 0x42, 0x46, 0x15, 0xe5, 0xcc,
0xbe, 0xe7, 0x5a, 0xe3, 0x6e, 0xb8, 0xf5, 0x31, 0x46, 0x4d, 0x41, 0xf4, 0xc2, 0x6e, 0x18, 0xdc,
0xd8, 0xd8, 0x45, 0x3d, 0x60, 0x19, 0x95, 0x9c, 0xa5, 0xc0, 0xb4, 0xdd, 0x34, 0xa1, 0x3a, 0x94,
0x57, 0x24, 0x42, 0x9c, 0x91, 0x19, 0x24, 0x76, 0x6b, 0x53, 0xb1, 0xf4, 0xf1, 0x4f, 0x0b, 0x0d,
0x22, 0x9e, 0x0a, 0xce, 0x80, 0xe9, 0xf7, 0x44, 0x92, 0x14, 0x34, 0xc8, 0x8b, 0x0c, 0xa4, 0xa4,
0x73, 0x50, 0x76, 0xdb, 0x6d, 0x8c, 0x7b, 0xd3, 0xf3, 0xff, 0xf8, 0xe0, 0xeb, 0x6b, 0xd5, 0xc3,
0x9b, 0x18, 0xf1, 0x08, 0xa1, 0x8c, 0x24, 0x2b, 0x78, 0x4b, 0x13, 0x50, 0x76, 0xc7, 0x6d, 0x8c,
0xbb, 0x61, 0x0d, 0xf1, 0x2e, 0x2d, 0x74, 0x5c, 0x8d, 0x43, 0x09, 0xce, 0x14, 0xe0, 0x21, 0xea,
0xa6, 0x05, 0xa6, 0x6c, 0xcb, 0xbc, 0xa9, 0x80, 0x3c, 0xca, 0x48, 0x0a, 0x4a, 0x90, 0x08, 0x0a,
0x4d, 0x2b, 0x00, 0x3f, 0x42, 0xed, 0xcd, 0xd2, 0x16, 0xb2, 0x16, 0xde, 0xce, 0x20, 0x9a, 0x57,
0x06, 0x01, 0xa8, 0x2d, 0xf2, 0xd6, 0x95, 0xdd, 0xba, 0x0d, 0x81, 0x8a, 0xe2, 0xde, 0x2f, 0x0b,
0x3d, 0x38, 0xa3, 0x4a, 0x9f, 0x50, 0x79, 0xf7, 0x36, 0xcf, 0x73, 0xd1, 0x61, 0x3e, 0x92, 0xbc,
0x41, 0xdc, 0x47, 0x2d, 0xaa, 0x21, 0x2d, 0xc5, 0xdf, 0x38, 0xa6, 0xff, 0x53, 0xd0, 0x79, 0xd6,
0x1d, 0xec, 0xff, 0x31, 0x3a, 0xda, 0x36, 0x57, 0xec, 0x11, 0x46, 0xcd, 0x39, 0xd1, 0xc4, 0x74,
0x77, 0x3f, 0x34, 0xf6, 0xf4, 0xd2, 0x42, 0x0f, 0x2b, 0xae, 0x0f, 0x20, 0x33, 0x1a, 0x01, 0xbe,
0x40, 0xc7, 0xa7, 0xc5, 0xa1, 0x28, 0xb7, 0x11, 0x0f, 0xfc, 0xda, 0xad, 0xbb, 0x72, 0x32, 0x9c,
0xe1, 0xfe, 0xe0, 0x86, 0xd8, 0x3b, 0xc0, 0x2f, 0x50, 0xa7, 0x18, 0x35, 0x76, 0xea, 0xa9, 0xbb,
0xf3, 0x77, 0xfa, 0xf5, 0x58, 0x29, 0xbf, 0x77, 0x80, 0x4f, 0x50, 0xa7, 0xf8, 0xcc, 0xee, 0xf3,
0x5d, 0xf9, 0x9d, 0xc1, 0xde, 0x58, 0xd9, 0xc4, 0xab, 0x97, 0xbf, 0xd7, 0x23, 0xeb, 0xcf, 0x7a,
0x64, 0xfd, 0x5d, 0x8f, 0xac, 0x4f, 0x93, 0x9b, 0x8e, 0xe8, 0xde, 0x63, 0x3f, 0x6b, 0x9b, 0x9b,
0xf9, 0xec, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x12, 0x81, 0x49, 0x46, 0x0c, 0x06, 0x00, 0x00,
func init() {
proto.RegisterFile("reposerver/repository/repository.proto", fileDescriptor_repository_49651600e73b0b40)
}
var fileDescriptor_repository_49651600e73b0b40 = []byte{
// 584 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x55, 0xdd, 0x8a, 0xd3, 0x40,
0x14, 0xde, 0x6c, 0xbb, 0xdd, 0x76, 0x2a, 0xee, 0x3a, 0x14, 0x09, 0x69, 0x29, 0x21, 0xa0, 0xf4,
0xc6, 0x84, 0xd6, 0x1b, 0x6f, 0x44, 0xd0, 0xd5, 0x45, 0xd8, 0x65, 0x25, 0x5e, 0xe9, 0x8d, 0x4c,
0xd3, 0x63, 0x3a, 0x36, 0x99, 0x19, 0x67, 0xa6, 0x01, 0x9f, 0xc2, 0x07, 0xf0, 0x0d, 0x7c, 0x12,
0x2f, 0x7d, 0x04, 0xe9, 0xdd, 0xbe, 0x85, 0x64, 0x9a, 0x34, 0x69, 0xb7, 0xec, 0x8d, 0x08, 0x7b,
0x77, 0xe6, 0x3b, 0x27, 0xdf, 0x77, 0xfe, 0x38, 0x41, 0x8f, 0x25, 0x08, 0xae, 0x40, 0x66, 0x20,
0x03, 0x63, 0x52, 0xcd, 0xe5, 0xb7, 0x9a, 0xe9, 0x0b, 0xc9, 0x35, 0xc7, 0xa8, 0x42, 0x9c, 0x5e,
0xcc, 0x63, 0x6e, 0xe0, 0x20, 0xb7, 0xd6, 0x11, 0xce, 0x20, 0xe6, 0x3c, 0x4e, 0x20, 0x20, 0x82,
0x06, 0x84, 0x31, 0xae, 0x89, 0xa6, 0x9c, 0xa9, 0xc2, 0xeb, 0x2d, 0x9e, 0x29, 0x9f, 0x72, 0xe3,
0x8d, 0xb8, 0x84, 0x20, 0x1b, 0x07, 0x31, 0x30, 0x90, 0x44, 0xc3, 0xac, 0x88, 0x79, 0x1b, 0x53,
0x3d, 0x5f, 0x4e, 0xfd, 0x88, 0xa7, 0x01, 0x91, 0x46, 0xe2, 0x8b, 0x31, 0x9e, 0x44, 0xb3, 0x40,
0x2c, 0xe2, 0xfc, 0x63, 0x15, 0x10, 0x21, 0x12, 0x1a, 0x19, 0xf2, 0x20, 0x1b, 0x93, 0x44, 0xcc,
0xc9, 0x0d, 0x2a, 0xef, 0x67, 0x03, 0x9d, 0x5c, 0x12, 0x46, 0x3f, 0x83, 0xd2, 0x21, 0x7c, 0x5d,
0x82, 0xd2, 0xf8, 0x03, 0x6a, 0xe6, 0x45, 0xd8, 0x96, 0x6b, 0x8d, 0xba, 0x93, 0xd7, 0x7e, 0xa5,
0xe6, 0x97, 0x6a, 0xc6, 0xf8, 0x14, 0xcd, 0x7c, 0xb1, 0x88, 0xfd, 0x5c, 0xcd, 0xaf, 0xa9, 0xf9,
0xa5, 0x9a, 0x1f, 0x6e, 0x7a, 0x11, 0x1a, 0x4a, 0xec, 0xa0, 0xb6, 0x84, 0x8c, 0x2a, 0xca, 0x99,
0x7d, 0xe8, 0x5a, 0xa3, 0x4e, 0xb8, 0x79, 0x63, 0x8c, 0x9a, 0x82, 0xe8, 0xb9, 0xdd, 0x30, 0xb8,
0xb1, 0xb1, 0x8b, 0xba, 0xc0, 0x32, 0x2a, 0x39, 0x4b, 0x81, 0x69, 0xbb, 0x69, 0x5c, 0x75, 0x28,
0x67, 0x24, 0x42, 0x5c, 0x90, 0x29, 0x24, 0xf6, 0xd1, 0x9a, 0xb1, 0x7c, 0xe3, 0xef, 0x16, 0xea,
0x47, 0x3c, 0x15, 0x9c, 0x01, 0xd3, 0xef, 0x88, 0x24, 0x29, 0x68, 0x90, 0x57, 0x19, 0x48, 0x49,
0x67, 0xa0, 0xec, 0x96, 0xdb, 0x18, 0x75, 0x27, 0x97, 0xff, 0x50, 0xe0, 0xab, 0x1b, 0xec, 0xe1,
0x6d, 0x8a, 0x78, 0x88, 0x50, 0x46, 0x92, 0x25, 0xbc, 0xa1, 0x09, 0x28, 0xfb, 0xd8, 0x6d, 0x8c,
0x3a, 0x61, 0x0d, 0xc1, 0x03, 0xd4, 0x61, 0x24, 0x05, 0x25, 0x48, 0x04, 0x76, 0xdb, 0x94, 0x53,
0x01, 0xde, 0xb5, 0x85, 0x4e, 0xab, 0x61, 0x29, 0xc1, 0x99, 0x82, 0xfc, 0x93, 0xb4, 0xc0, 0x94,
0x6d, 0x19, 0xc6, 0x0a, 0xd8, 0x26, 0x3c, 0xdc, 0x21, 0xc4, 0x0f, 0x51, 0x6b, 0xbd, 0xd2, 0x45,
0xd3, 0x8b, 0xd7, 0xd6, 0x98, 0x9a, 0x3b, 0x63, 0x02, 0xd4, 0x12, 0x79, 0x61, 0xca, 0x3e, 0xfa,
0x1f, 0xed, 0x2b, 0xc8, 0xbd, 0x1f, 0x16, 0xba, 0x7f, 0x41, 0x95, 0x3e, 0xa3, 0xf2, 0xee, 0xed,
0xa5, 0xe7, 0xa2, 0x76, 0x3e, 0xb0, 0x3c, 0x41, 0xdc, 0x43, 0x47, 0x54, 0x43, 0x5a, 0x36, 0x7f,
0xfd, 0x30, 0xf9, 0x9f, 0x83, 0xce, 0xa3, 0xee, 0x60, 0xfe, 0x8f, 0xd0, 0xc9, 0x26, 0xb9, 0x62,
0x8f, 0x30, 0x6a, 0xce, 0x88, 0x26, 0x26, 0xbb, 0x7b, 0xa1, 0xb1, 0x27, 0xd7, 0x16, 0x7a, 0x50,
0x69, 0xbd, 0x07, 0x99, 0xd1, 0x08, 0xf0, 0x15, 0x3a, 0x3d, 0x2f, 0xce, 0x48, 0xb9, 0x8d, 0xb8,
0xef, 0xd7, 0x2e, 0xe1, 0xce, 0x41, 0x71, 0x06, 0xfb, 0x9d, 0x6b, 0x61, 0xef, 0x00, 0x3f, 0x47,
0xc7, 0xc5, 0xa8, 0xb1, 0x53, 0x0f, 0xdd, 0x9e, 0xbf, 0xd3, 0xab, 0xfb, 0xca, 0xf6, 0x7b, 0x07,
0xf8, 0x0c, 0x1d, 0x17, 0xc5, 0x6c, 0x7f, 0xbe, 0xdd, 0x7e, 0xa7, 0xbf, 0xd7, 0x57, 0x26, 0xf1,
0xf2, 0xc5, 0xaf, 0xd5, 0xd0, 0xfa, 0xbd, 0x1a, 0x5a, 0x7f, 0x56, 0x43, 0xeb, 0xe3, 0xf8, 0xb6,
0x13, 0xbb, 0xf7, 0x57, 0x30, 0x6d, 0x99, 0x8b, 0xfa, 0xf4, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff,
0x22, 0x8e, 0xa1, 0x51, 0x2a, 0x06, 0x00, 0x00,
}

View File

@@ -17,6 +17,7 @@ message ManifestRequest {
string appLabel = 5;
repeated github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ComponentParameter componentParameterOverrides = 6;
repeated string valueFiles = 7;
string namespace = 8;
}
message ManifestResponse {

View File

@@ -6,14 +6,24 @@ import (
"github.com/stretchr/testify/assert"
)
func TestGenerateManifestInDir(t *testing.T) {
func TestGenerateYamlManifestInDir(t *testing.T) {
// update this value if we add/remove manifests
const countOfManifests = 22
q := ManifestRequest{}
res1, err := generateManifests("../../manifests/components", &q)
res1, err := generateManifests("../../manifests/base", &q)
assert.Nil(t, err)
assert.True(t, len(res1.Manifests) == 16) // update this value if we add/remove manifests
assert.Equal(t, len(res1.Manifests), countOfManifests)
// this will test concatenated manifests to verify we split YAMLs correctly
res2, err := generateManifests("../../manifests", &q)
res2, err := generateManifests("./testdata/concatenated", &q)
assert.Nil(t, err)
assert.True(t, len(res2.Manifests) == len(res1.Manifests))
assert.Equal(t, 3, len(res2.Manifests))
}
func TestGenerateJsonnetManifestInDir(t *testing.T) {
q := ManifestRequest{}
res1, err := generateManifests("./testdata/jsonnet", &q)
assert.Nil(t, err)
assert.Equal(t, len(res1.Manifests), 2)
}

View File

@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa1
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa2
---
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa3
---

View File

@@ -0,0 +1,58 @@
local params = import 'params.libsonnet';
[
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": params.name
},
"spec": {
"ports": [
{
"port": params.servicePort,
"targetPort": params.containerPort
}
],
"selector": {
"app": params.name
},
"type": params.type
}
},
{
"apiVersion": "apps/v1beta2",
"kind": "Deployment",
"metadata": {
"name": params.name
},
"spec": {
"replicas": params.replicas,
"selector": {
"matchLabels": {
"app": params.name
},
},
"template": {
"metadata": {
"labels": {
"app": params.name
}
},
"spec": {
"containers": [
{
"image": params.image,
"name": params.name,
"ports": [
{
"containerPort": params.containerPort
}
]
}
]
}
}
}
}
]

View File

@@ -0,0 +1,8 @@
{
containerPort: 80,
image: "gcr.io/heptio-images/ks-guestbook-demo:0.2",
name: "guestbook-ui",
replicas: 1,
servicePort: 80,
type: "LoadBalancer",
}

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