Compare commits

...

181 Commits

Author SHA1 Message Date
Jesse Suen
08c63ec234 Update install manifests to v0.6.2 2018-07-20 22:18:27 -07:00
Jesse Suen
41f950fd43 Bump version to v0.6.2 2018-07-20 21:52:38 -07:00
Jesse Suen
826ee0dfa0 Health check was using wrong converter for statefulsets, daemonset, replicasets (#439) 2018-07-20 21:49:43 -07:00
Jesse Suen
a48151f07a Fix regression where deployment health check incorrectly reported Healthy
Bump verison to v0.6.1
2018-07-18 00:08:03 -07:00
Jesse Suen
82fda1c7af Add UI GIF, docs for application health, resource hooks, tweaks to README.md (#429) 2018-07-17 18:03:54 -07:00
Alexander Matyushentsev
d108129972 Issue #428 - Add GKE specific installation instructions (#430) 2018-07-18 04:03:17 +03:00
Alexander Matyushentsev
6124ab1b3e Issue #351 - forward dex error message to login page (#425)
* Issue #351 - forward dex error message to login page

* Address reviewer notes: sort imports, move regex to package level var
2018-07-18 00:48:38 +03:00
Alexander Matyushentsev
e294a315fc Add RBAC documentation (#423) 2018-07-17 22:03:27 +03:00
Alexander Matyushentsev
3baed5295e Fix app creation command in getting_started.md (#422) 2018-07-17 20:05:50 +03:00
Jesse Suen
a5334ecde7 Update getting_started.md with new install instructions.
Generate install.yaml from IMAGE_NAMESPACE IMAGE_TAG values
Fix panic in initializeSettings()
2018-07-17 01:58:18 -07:00
Jesse Suen
9f35bad93f Rework installation process to apply from install.yaml (#421)
* update getting started to work for post 0.6
* create central install manifest from individual manifests
* point e2e tests to correct manifests dir
* Update roles required by api-server and application-controller to include CRUD on appproject CRD.
* Added back explanations of keys in the secret manifests

NOTE: install.yaml will need change to use a hard wired version (e.g. v0.6.0) in a subsequent checkin.
2018-07-16 18:37:41 -07:00
Alexander Matyushentsev
972f639051 Issue #419 - Add ability to dump heap profile by sending SIGUSR2 (#420)
* Issue #419 - Add ability to dump heap profile by sending SIGUSR2

* Regenerate Gopkg.lock
2018-07-17 03:48:30 +03:00
Jesse Suen
99cc4f8d39 Clean up RBAC policy rule format for non-project based resources (#418) 2018-07-16 15:00:14 -07:00
Alexander Matyushentsev
273f99b293 Issue #414 - fix nil pointer in 'argocd cluster add' (#416)
* Issue #414 - fix nil pointer in 'argocd cluster add'

* Add missing nil check
2018-07-16 23:54:12 +03:00
Jesse Suen
48ef2e919e Rename 'users' service into 'account' service (#415) 2018-07-16 13:52:40 -07:00
Jesse Suen
65b1b083ee Switch repo-server to use in-memory cache in lieu of redis. Periodically dump stats (#413) 2018-07-16 10:20:16 -07:00
Jesse Suen
c0367ed595 Add support for hook deletion policies (OnSuccess, OnFailure) (resolves #374) (#412) 2018-07-16 10:15:53 -07:00
JazminGonzalez-Rivero
062b13e92a Create User Service to support password management (#411)
* create User Api

* update UsersPasswordRequest to UpdatePasswordRequest

* update UserResponse to UpdatePasswordResponse

* current password only needs to be entered once
2018-07-16 02:34:35 -07:00
Jesse Suen
39b9f4d31a Add ability to terminate a running operation (resolves #379) (#409) 2018-07-13 17:13:31 -07:00
Alexander Matyushentsev
39701d0455 Issue #404 - Unset application parameters (#405) 2018-07-14 00:59:50 +03:00
Alexander Matyushentsev
6fbf78ef52 Issue #407 - fix nil pointer dereference in GetSpecErrors (#408) 2018-07-14 00:56:56 +03:00
Andrew Merenbach
7e0cd01758 Fix production swagger (#403)
* Factor out filepath

* Use packr to serve swagger.json

* Pass packr.Box instead of string, thanks @alexmt

* Fix test for Swagger UI server
2018-07-13 11:11:37 -07:00
JazminGonzalez-Rivero
76bf77eded ( resolves 375 ) add admin password util. (#394)
* add ability to update password whenneeded

* refactor structire so settingsverifier in settings util

* consolidate more

* move MakeSignature test

* re kick off build

* reretrigger build
2018-07-13 13:45:49 -04:00
Jesse Suen
97189f300e Fix issue where ingress incorrectly was converting to v1 instead of extensions/v1beta1 (#397) 2018-07-13 10:05:44 -07:00
Jesse Suen
078b2339bd Apply logic was ignoring kubectl apply failures (#395) 2018-07-13 10:03:56 -07:00
Andrew Merenbach
4915490cbb Change default connection state (#388) 2018-07-12 14:25:34 -07:00
Jesse Suen
f65859bc25 Label hooks so that the cluster resource watch will be notified about completions (#387) 2018-07-12 14:01:54 -07:00
Alexander Matyushentsev
543ae3cf13 Issue #364 - Assess health of StatefulSets, DaemonSets, ReplicaSets (#391) 2018-07-13 00:01:28 +03:00
Alexander Matyushentsev
610a4510cf Issue #385 - Sync and health status should be unknown if controller unable to load target/live state (#386) 2018-07-12 22:40:21 +03:00
Jesse Suen
7b92977889 Improve logging. Prevent some unecessary patches to app (#383) 2018-07-12 12:39:46 -07:00
Alexander Matyushentsev
a17806c37c Issue #349 - argocd wait passes when an ingress object failed (#384) 2018-07-12 22:38:25 +03:00
Jesse Suen
d6b87b2047 If manifest query is a commit sha, check cache first to prevent locking git repo (#382) 2018-07-12 04:15:12 -07:00
Jesse Suen
9d921f65f3 Various fixes to sync logic (#370)
* move hook resource state into SyncResult (from operation state)
* fix rollback to use apply based sync
* re-assess sync/health status between each sync phases
* PostSync hook should wait until application is Healthy (resolves #363)
2018-07-11 19:12:30 -07:00
Alexander Matyushentsev
32ba7f468c Issue #304 - Print information about app conditions (#371)
* Issue #304 - Print information about app conditions

* Reviewer notes: Remove unnecessary space

* Reviewer notes: use comma to separate conditions
2018-07-12 00:03:20 +03:00
Andrew Merenbach
9cddd4c368 Force app refresh after sync (#373) 2018-07-11 13:45:41 -07:00
Alexander Matyushentsev
8db465c699 Issue #277 - Warning message if controller detect resources which belongs to multiple application (#372)
* Issue #277 - Warning message if controller detect resources which belongs to multiple application

* Reviewer notes: add missing nil check
2018-07-11 23:00:48 +03:00
Jesse Suen
eb1caf2231 Use hook strategy as the default when performing a sync (#368) 2018-07-10 16:02:55 -07:00
Alexander Matyushentsev
adb84f3d03 Issue #210 - Application controller should not erase previously collected info about resources if repo or cluster is not available (#367) 2018-07-11 00:45:18 +03:00
JazminGonzalez-Rivero
67acd29541 move install functionality from custom to kubectl apply (#327)
* check for keys on server startup

* move manifest to it's own folder

* revert Gopkg.lock changes

* add default password warnings

* update getting started docs

* remove install dependency from e2e test

* fix test pathing

* readding 02* manifests

* set url to blank as default

* update sso docs

* update getting_started to include namespace

* make defaultSetting internal

* remove extra check, should be caught by  settingsMgr.GetSettings() error check

* fix manifests path

* error if configmap is missing, but replace if secret missing

* fix getting started

* set password to hostname

* update comment for initializeSettings

* remove unneeded bitbucket.webhook.uuid

* Gopkg.lock modified
2018-07-10 17:26:07 -04:00
Jesse Suen
41976122d5 app-name label was inadvertently injected into spec.selector if selector was omitted from v1beta1 specs (resolves #335) (#366)
Bump version to v0.6.0
2018-07-10 10:51:58 -07:00
Jesse Suen
d5b973c15a Fix git authentication implementation when using using SSH key (resolves #339) (#362) 2018-07-10 10:50:56 -07:00
Jesse Suen
3a6892a011 Cascade deletion is decided during app deletion, instead of app creation (resolves #301) (#361) 2018-07-10 10:50:29 -07:00
Jesse Suen
b092e1bc7d Remove unnecessary role privileges from api server (resolves #319). Fix linting issues (#359) 2018-07-09 13:53:45 -07:00
Jesse Suen
9d7d1989e9 Consolidate printing of hook resources with application resources (#358) 2018-07-09 13:46:23 -07:00
Alexander Matyushentsev
7d4dd0fdd5 Issue #304 - Add spec validation condition to application CRD (#360) 2018-07-09 20:45:03 +03:00
Jesse Suen
364415f83a Move k8s health assessment into a stand-alone library. Use kubectl convert to statically assess health (#356) 2018-07-09 10:13:56 -07:00
Jesse Suen
d633f6b299 Support for PreSync, Sync, PostSync resource hooks (#350)
* Rewrite controller sync logic to support workflow-based sync

* Redesign hook implementation to support generic resources as hooks
2018-07-07 00:54:06 -07:00
Andrew Merenbach
bf2fe9d33f Add missing health status (#354)
* Add missing health status

* Update swagger.json

* Default app and service health to Missing

* Fill in Missing status if status field is blank

* Simplify missing injection
2018-07-06 16:33:25 -07:00
Andrew Merenbach
c2fde1ddc0 Retry sync and rollback (#347)
* Add timeout to sync and rollback commands

* Make defaultCheckTimeoutSeconds package-global

* Add cancel to context for rollback, sync

* Add cancel after timeout to rollback/sync

* Assign appName earlier in sync

* Don't unnecessarily reassign ctx

* Use full func for timeout after all

* Try applying wait logic

* Add sync/health flags to rollback/sync

* Slight cleanup to realign with spec

* Clean up, still broken

* Adapt waitUntilOPerationCompleted, thanks @jessesuen

* Log fatal immediately after timeout

* Move fatal log back

* Reduce diff further

* Rm two blank lines
2018-07-03 15:35:58 -07:00
Jesse Suen
ebb47580a0 Refactor to enable application server unit testing. Add app create unit test. 2018-06-30 02:25:32 -07:00
Jesse Suen
4e533c90c2 Fix issue where --disable-auth did not disable RBAC properly (resolves #332) (#344) 2018-06-29 22:08:02 -07:00
Alexander Matyushentsev
9a14134f65 Issue #343 - Getting permission Denied application destination is not permitted in project (#345) 2018-06-29 21:56:24 -07:00
Andrew Merenbach
e58bdab492 Remove namespace from app URL (#334) 2018-06-28 13:57:52 -07:00
Jesse Suen
279b01a180 Refresh flag to sync should be optional, not required 2018-06-27 16:31:41 -07:00
Alexander Matyushentsev
f046884ae0 Issue #295 - Add repositories to project spec (#331) 2018-06-27 16:15:26 -07:00
Andrew Merenbach
f4ce59650d Idempotent cluster create (#328)
* Add upsert field to cluster create request

* Update generated files

* Add idempotence check to cluster

* Add command-line flag for upsert cluster

* Fix error logging in repository, cluster, application create

* Check Server instead of Name
2018-06-27 13:40:32 -07:00
Andrew Merenbach
bc49af56c0 RBAC import/export (#325)
* Simplify export with functional approach

* Add comment

* Export/import RBAC config map now
2018-06-27 13:10:39 -07:00
Andrew Merenbach
3e7116c427 Enable CGO on Linux builds (#320)
* Take first shot at enable CGO on Linux

* Simplify CGO_ENABLED flag check

* Use curly braces for consistency

* Build CLI with CGO if possible, thanks @jessesuen
2018-06-26 14:29:11 -07:00
Alexander Matyushentsev
12b3ec6989 Issue #295 - Allow editing project destinations using CLI (#317)
* Issue #295 - Allow editing projcet destinations using CLI

* Apply reviewer notes

* Fix error message in 'argocd project' cli

* Convert project name to positional argument
2018-06-26 13:49:31 -07:00
Andrew Merenbach
e536cc183a Idempotent repo add (#321)
* Add upsert field for repo creation

* Update generated files

* Update repo specs

* Redact existing repo

* Use one exit point to reduce chance of redaction error

* Set error to nil when appropriate

* Handle claims more properly

* Process error better

* Fix comparison, rm unneeded assignment

* Use pointers for repo RBAC name

* Apply repoRBACName in two other places

* DRY

* Don't nest unnecessarily

* Revert repoRBACName change to simplify diff

* Rearrange repo upsert check, thanks @jessesuen
2018-06-26 11:51:51 -07:00
Andrew Merenbach
63348fa903 Rm swagger.json from reposerver (#318) 2018-06-25 13:54:09 -07:00
Andrew Merenbach
ab00aef75e Generate swagger files (#278)
* Generate swagger files

* Add basic Swagger definitions

* Add reposerver swagger file

* Consolidate swagger files

* Move swagger files to swagger-ui directory instead

* Put swagger files in swagger-ui

* Fix order of operations

* Move back to swagger directory

* Serve API server swagger files raw for now

* Serve reposerver swagger files from API server

* Move back to subdirectories, thanks @alexmt

* Fix comment on application Rollback

* Update two more comments

* Fix comment in session.proto

* Update generated code

* Update generated swagger docs

* Fix comment for delete actions in cluster and repository swagger

* Set expected collisions and invoke mixins

* Update generated code

* Create swagger mixins from codegen

* Move swagger.json location, thanks @jazminGonzalez-Rivero

* Add ref cleanup for swagger combined

* Make fewer temp files when generating swagger

* Delete intermediate swagger files

* Serve new file at /swagger.json

* Set up UI server

* Update package lock

* Commit generated swagger.json files

* Add install commands for swagger

* Use ReDoc server instead of Swagger UI server

* Update lockfile

* Make URL paths more consistent

* Update package lock

* Separate out handlers for Swagger UI, JSON

* Rm unnecessary CORS headers

...since we're serving from the app server

* Simplify serving

* Further simplify serving code

* Update package lock

* Factor out swagger serving into util

* Add test for Swagger server

* Use ServeSwaggerUI method to run tests

* Update package lock

* Don't generate swagger for reposerver

* Reset to master Gopkg.lock and server/server.go

* Merge in prev change to server/server.go

* Redo changes to Gopkg.lock

* Fix number of conflicts

* Update generated swagger.json for server

* Fix issue with project feature error
2018-06-25 13:49:38 -07:00
Jesse Suen
653f9d3913 Remove local git credential test to prevent clobbering of OSX keychain credentials (resolves #315) 2018-06-24 03:39:05 -07:00
Alexander Matyushentsev
21c3fb905b Projects bug fixes: GET /api/v1/projects should return default project, (#313)
prevent creating/updating default project, make sure user cannot move
app to project unless it is permitted
2018-06-23 11:38:35 -07:00
JazminGonzalez-Rivero
4353f736d4 add validation to argocd app set -p (#309)
* Add CheckValidParam function

* fix typo
2018-06-22 14:22:30 -07:00
Jesse Suen
f3712313ba Update webhook.md to remove unnecessary step to restart server 2018-06-22 13:52:50 -07:00
Jesse Suen
b2d8fcb36e Update sso.md to remove unnecessary step to restart server 2018-06-22 13:50:52 -07:00
Alexander Matyushentsev
81021839d5 Issue #295 - implement app destination permissions validation (#310)
* Issue #295 - implement app destination permissions validation

* Apply reviewer notes. Use project to check application access. Update project access checks

* Use GetProject() instead of project to make sure default value is inferred

* Apply reviewer notes
2018-06-22 10:05:57 -07:00
Edward Lee
7c36dd6c56 Fix LICENSE copyright text 2018-06-21 01:03:50 -07:00
Jesse Suen
687e0b0dea Update getting_started.md with example repo and new UI steps 2018-06-20 18:40:49 -07:00
Jesse Suen
834e22d7b1 Support cluster management using the internal k8s API address https://kubernetes.default.svc (#307) 2018-06-20 16:50:15 -07:00
Alexander Matyushentsev
1e29f98924 Issue #295 - add project CRD, basic API and CLI implementation (#299) 2018-06-20 14:48:31 -07:00
Jesse Suen
933f3da538 Support diffing a local ksonnet app to the live application state (resolves #239) (#298) 2018-06-20 13:57:55 -07:00
Jesse Suen
a7fa2fd256 Add ability to show last operation result in app get. Show path in app list -o wide (#297) 2018-06-19 02:04:45 -07:00
Jesse Suen
0de1a3b20a Update dependencies: ksonnet v0.11, golang v1.10, debian v9.4 (#296) 2018-06-18 14:34:10 -07:00
Jesse Suen
bcc114ec60 Add ability to force a refresh of an app during get (resolves #269) (#293) 2018-06-18 10:22:58 -07:00
Jesse Suen
1148fae419 Add clean-debug make target to prevent packr from boxing debug artifacts into binaries 2018-06-15 14:31:26 -07:00
Jesse Suen
d7188c29f8 Remove redundant 'argocd' namespace from manifests 2018-06-15 14:20:28 -07:00
Jesse Suen
4b97732659 Automatically restart API server upon certificate changes (#292) 2018-06-15 14:16:50 -07:00
Jesse Suen
8ff98cc6e1 Add RBAC unit test for wildcards with sub-resources 2018-06-14 12:50:16 -07:00
Jesse Suen
cf0c324a74 Add unit test for using resource & action wildcards in a RBAC policy. Bump version to v0.5.2 2018-06-14 12:41:26 -07:00
Jesse Suen
69119a21cd Update getting_started.md to point to v0.5.1 2018-06-14 11:13:05 -07:00
Alexander Matyushentsev
16fa41d25b Issue #275 - Application controller fails to get app state if app has resource without name (#285) 2018-06-14 09:08:22 -07:00
Alexander Matyushentsev
4e170c2033 Update version to v0.5.1 2018-06-13 14:30:35 -07:00
Alexander Matyushentsev
3fbbe940a1 Issue #283 - API server incorrectly compose application fully qualified name for RBAC check (#284) 2018-06-13 13:05:39 -07:00
Alexander Matyushentsev
271b57e5c5 Issue #260 - Rate limiter is preventing force refreshes (e.g. webhook) from functioning (#282) 2018-06-13 11:34:33 -07:00
Andrew Merenbach
df0e2e4015 Fail app sync if prune flag is required (#276)
* Add status field to resource details

* Update generated code

* Set up const message responses

* Check number of resources requiring pruning

* Fix imports

* Use string, thanks @alexmt

* Update generated code
2018-06-12 10:54:11 -07:00
Alexander Matyushentsev
9fa622d63b Issue #280 - It is impossible to restrict application access by repository URL (#281)
* Issue #280 - It is impossible to restrict application access by repository URL

* Apply reviewer note
2018-06-12 10:43:16 -07:00
Alexander Matyushentsev
fed2149174 Add progressing deadline to test app to fix e2e tets slowness 2018-06-12 08:54:47 -07:00
Alexander Matyushentsev
aa4291183b Take into account number of unavailable replicas to decided if deployment is healthy or not (#270)
* Take into account number of unavailable replicas to decided if deployment is healthy or not

* Run one controller for all e2e tests to reduce tests duration

* Apply reviewer notes: use logic from kubectl/rollout_status.go to check deployment health
2018-06-07 11:05:46 -07:00
Alexander Matyushentsev
0d3fc9648f Issue #271 - perform three way diff only if resource has expected state and live state with last-applied-configuration annotation (#274) 2018-06-07 10:29:36 -07:00
Jesse Suen
339138b576 Remove hard requirement of initializing OIDC app during server startup (resolves #272) 2018-06-07 02:07:53 -07:00
Jesse Suen
666769f9d9 Fix issue preventing proper parsing of claims subject in RBAC enforcement 2018-06-07 00:17:00 -07:00
Jesse Suen
8fc594bd2b Add missing list, patch, update verbs to application-controller-role 2018-06-06 17:51:18 -07:00
Andrew Merenbach
8cf8ad7e24 Tweak flags for import/export, thanks @jessesuen (#268) 2018-06-06 16:32:30 -07:00
Andrew Merenbach
0818f698e6 Support resource import/export (#255)
* Add initial prototype for export

* Add client opts to argocd-util

* Make flags local

* Support output to file without piping

* Add comment to NewExportCommand

* Vastly clean up output, thanks @alexmt @jessesuen

* Nullify operation, thanks @alexmt

* Add additional error check

* Rm extraneous fmt.Sprint

* Clone export command to import

* Flesh out import feature

* Use const string for YAML separator

* Don't export enclosing lists

* Almost finished prototyping import

* Create settings now, too

* Create all resources now

* Nullify certificate before export

* Add JSON annotations, update comment

* Warn, don't fail, if cluster/repo already exist, thanks @alexmt

* Use minus instead of stdin/stdout, thanks @jessesuen
2018-06-06 14:53:14 -07:00
Jesse Suen
44a33b0a5f Repo names containing underscores were not being accepted (resolves #258) 2018-06-06 14:26:43 -07:00
wanghong230
85078bdb66 fix #120 refactor the rbac code to support customizable claims enforcement function (#265) 2018-06-06 14:20:34 -07:00
Jesse Suen
30a3dba7ad argocd-server needs to be built using packr to bundle RBAC policy files. Update packr (resolves #266) 2018-06-06 12:35:25 -07:00
Jesse Suen
0afc671723 Retry argocd app wait connection errors from EOF watch. Show detailed state changes 2018-06-06 11:24:49 -07:00
Jesse Suen
12e7447e9f Implement RBAC support (issue #120) (#263)
* introduce rbac library around casbin
* supports claims enforcement by iteration through user's groups
* supports filtering of resources by level of access
* policy loader and automatic updates from configmap
* support for builtin and userdefined policies
2018-06-05 21:44:13 -07:00
Alexander Matyushentsev
b675e79b89 Add path to API /application/{repo}/ksonnet response (#264)
* Add path to API /application/{repo}/ksonnet response

* Fix indentation
2018-06-05 14:37:26 -07:00
Jesse Suen
febdccfb58 Introduce argocd app manifests for printing the application manifests from git or live (#261) 2018-06-05 12:59:29 -07:00
Alexander Matyushentsev
54835a0d93 Implement workaround for https://github.com/golang/go/issues/21955 (#256) 2018-06-04 13:52:07 -07:00
Jesse Suen
423fe3487c REST payload of create/update for repos and cluster should be actual object 2018-05-31 18:15:33 -07:00
Jesse Suen
98cb3f7950 Bump version to v0.5.0 2018-05-31 17:56:23 -07:00
Jesse Suen
371492bf5c Handle case where upsert could be nil. Use proper error codes. More RESTful endpoints 2018-05-31 17:54:27 -07:00
Jesse Suen
7df831e96d Clean up .proto definitions for consistency and reduction of pointer usage (#253) 2018-05-31 17:21:09 -07:00
Alexander Matyushentsev
f0be1bd251 Fix bug secret controller which is causing update loop in secret controller (#251) 2018-05-31 16:06:41 -07:00
Alexander Matyushentsev
948341a885 ListDir should not fail if Redis is down (#252) 2018-05-31 16:06:30 -07:00
Alexander Matyushentsev
1b2bf8ce0e GET /cluster/<clustername> API should not panic if invalid cluster url is provided (#250) 2018-05-31 15:13:07 -07:00
Andrew Merenbach
4f68a0f634 Wrap method signatures (#249)
* Update application create to use upsert attribute

* Update CLI interface

* Use pointer to upsert

* Rename DeleteApplicationRequest for parity

* Add new ApplicationUpdateRequest wrapper

* Rename RepoUpdateRequest => RepoRESTUpdateRequest

* Add new RepositoryUpdateRequest

* Rename ClusterUpdateRequest -> ClusterRESTUpdateRequest

* Fix var names

* Update var use

* Use intermediate vars for clarity

* Update generated code

* Update mocks

* Update e2e cluster creation
2018-05-31 14:21:08 -07:00
Alexander Matyushentsev
e785abeb8f Issue #244 - Cluster/Repository connection status (#248) 2018-05-31 13:44:19 -07:00
Jesse Suen
3acca5095e Add argocd app unset command to unset parameter overrides. Bump version to v0.4.5 2018-05-31 02:55:35 -07:00
Jesse Suen
5a62286127 Cookie token was not parsed properly when mixed with other site cookies 2018-05-31 02:37:15 -07:00
Jesse Suen
5452aff0be Add ability to show parameters and overrides in CLI (resolves #240) (#247) 2018-05-30 15:41:11 -07:00
Andrew Merenbach
0f4f1262af Add Events API endpoint (#237)
* Flesh out initial endpoint

* Update generated code

* Update prototype for list of events

* Update endpoints

* Update initialization of app service

* Use proper interfaces here

* Use event list

* Use preexisting events list struct

* Simplify initial architecture significantly

* Rename ListDirResponse => FileList, thanks @jessesuen

* Rm unneeded error check

* Narrow down event query, thanks @alexmt

* Use tests to fix bug

* Don't reinvent the wheel

* Rm comment

* Add Uid field, thanks @alexmt @jessesuen

* Update generated files

* Support external clusters, thanks @alexmt

* Filter by proper namespace
2018-05-30 15:30:58 -07:00
Jesse Suen
4e7f68ccba Update version to 0.4.4 2018-05-30 14:03:16 -07:00
Alexander Matyushentsev
96c05babe0 Issue #238 - add upsert flag to 'argocd app create' command (#245) 2018-05-30 13:49:20 -07:00
Andrew Merenbach
6b78cddb19 Add repo browsing endpoint (#229)
* Add skeleton ListDir endpoint

* Update proto with path field

* Add first working file retrieval

* Update git client to support paths

* Update proto file

* Flesh out prototype code for retrieving files

* Create repo server with repoclientset

* Rm unneeded test code

* Update generated code

* Use HTTP queries instead of URL components

* Error out properly

* Add missing fixture test

* Rm commented endpoint, thanks @alexmt

* Skip invalid app specs
2018-05-24 19:09:52 -07:00
Alexander Matyushentsev
12596ff936 Issue #233 - Controller does not persist rollback operation result (#234) 2018-05-24 10:50:33 -07:00
Jesse Suen
a240f1b2b9 Bump version to 0.5.0 2018-05-23 11:18:50 -07:00
Jesse Suen
f6da19672e Support subscribing to settings updates and auto-restart of dex and API server (resolves #174) (#227) 2018-05-23 10:01:07 -07:00
Jesse Suen
e81d30be9b Update getting_started.md to point to v0.4.3 2018-05-22 15:02:51 -07:00
Alexander Matyushentsev
13b090e3bd Issue #147 - App sync frequently fails due to concurrent app modification (#226) 2018-05-22 09:43:17 -07:00
Alexander Matyushentsev
d0479e6ddc Issue # 223 - Remove app finalizers during e2e fixture teardown (#225) 2018-05-22 09:23:42 -07:00
Andrew Merenbach
1432827006 Add error fields to cluster/repo, shell output (#200)
* Add error fields to cluster/repo, shell output

* Add missing format strings, thanks @alexmt

* Rename Error => Message

* Set JSON keys, thanks @jessesuen

* Update generated code
2018-05-22 08:48:24 -07:00
Jesse Suen
89bf4eac71 Bump version to 0.4.3 2018-05-21 15:27:01 -07:00
Jesse Suen
07aac0bdae Move local branch deletion as part of git Reset() (resolves #185) (#222) 2018-05-21 15:21:09 -07:00
Andrew Merenbach
61220b8d0d Fix exit code for app wait (#219) 2018-05-21 10:17:10 -07:00
Jesse Suen
4e470aaf09 Remove context name prompt during login. (#218)
* Show URL in argocd app get
* Rename force flag to cascade in argocd app delete
* Remove interactive context name prompt during login which broke login automation
* Rename apiclient.ServerClient to Client
2018-05-21 01:10:02 -07:00
Jesse Suen
76922b620b Update version to 0.4.2 2018-05-21 01:05:47 -07:00
Andrew Merenbach
ac0f623eda Add argocd app wait command (#216)
* Update CLI, server for wait request

* Update generated code

* Remove generated code

* Add timeout function, and use it

* Get first working prototype

* Properly fail and print success/fail messages

* Add missing reference pointer

* Remove unreachable code

* Show current state of all checks

* Print atypical health output status now

* Update short command description, thanks @jessesuen

* Use server-side watch command

* Use watch API

* Clean up wait function to use new API better

* Rm unused const, satisfy linter on caps names

* Rename channel and set direction

* Add infinite timeout by default
2018-05-18 11:50:01 -07:00
Jesse Suen
afd5450882 Bump version to v0.4.1 2018-05-17 18:31:46 -07:00
Jesse Suen
c17266fc21 Add documentation on how to configure SSO and Webhooks 2018-05-17 18:28:04 -07:00
Andrew Merenbach
f62c825495 Manifest endpoint (#207)
* Add manifests endpoint

* Draft app.go changes

* Fix some issues with imports, symbols, args

* Reduce duplication between components

* Revert "Reduce duplication between components"

This reverts commit 87b166885d53778683bc0a0a826671c2c67dc082.

* Add ManifestQuery type, thanks @jessesuen

* Add required/optional flags to protobuf

* Update generated code

* Add missing pointer dereferences

* Default to app target revision, thanks @jessesuen

* Account for nil
2018-05-17 16:33:04 -07:00
Jesse Suen
45f44dd4be Add v0.4.0 changelog 2018-05-17 03:10:41 -07:00
Jesse Suen
9c0daebfe0 Fix diff falsely reporting OutOfSync due to namespace/annotation defaulting 2018-05-17 00:41:50 -07:00
Jesse Suen
f2a0ca5609 Add intelligence in diff libray to perform three-way diff from last-applied-configuration annotation (resolves #199) 2018-05-16 16:47:30 -07:00
Alexander Matyushentsev
e04d315853 Issue #118 - app delete should be done through controller using finalizers (#206)
* Issue #118 - app delete should be done through controller using finalizers

* Apply reviewer notes: introduce application

* Apply reviewer notes: fix app deletion
2018-05-16 16:30:28 -07:00
Jesse Suen
daec697658 Update ksonnet to v0.10.2 (resolves #208) 2018-05-15 23:25:38 -07:00
Alexander Matyushentsev
7ad5670710 Make sure api server started during fixture setup (#209) 2018-05-15 22:49:55 -07:00
Alexander Matyushentsev
8036423373 Implement App management and repo management e2e tests (#205)
* Implement App management and repo management e2e tests

* Apply reviewer notes; fix compilation error
2018-05-15 12:42:44 -07:00
Alexander Matyushentsev
8039228a9d Add last update time to operation status, fix operation status patching (#204)
* Add last update time to operation status, fix operation status patching

* Rename lastUpdateTime to startAt and finishedAt
2018-05-15 11:35:10 -07:00
Andrew Merenbach
b1103af429 Rename recent deployments to history (#201)
* Rename RecentDeployments to History

* Update generated code
2018-05-15 11:05:46 -07:00
Jesse Suen
d67ad5acfd Add connect timeouts when interacting with SSH git repos (resolves #131) (#203) 2018-05-15 10:30:31 -07:00
Jesse Suen
c9df9c17b7 Default Spec.Source.TargetRevision to HEAD server-side if unspecified (issue #190) 2018-05-15 03:21:21 -07:00
Jesse Suen
8fa46b02b0 Remove SyncPolicy (issue #190) 2018-05-15 03:05:34 -07:00
Jesse Suen
92c481330d App creation was not defaulting to server and namespace defined in app.yaml 2018-05-15 01:35:43 -07:00
Jesse Suen
2664db3e40 Refactor application controller sync/apply loop (#202)
* Refactor application controller sync/apply loop
* always run kubectl apply --dry-run before the actual apply
* remove incorrect logic skip apply if comparator reported Synced
* rename status to phase
* distinguish failures from errors
* consolidate fields between OperationState and SyncOperationResult
* Disable migration code which referenced removed fields
2018-05-15 00:36:11 -07:00
Andrew Merenbach
6b554e5f4e Add 0.3.0 to 0.4.0 migration utility (#186)
* Add temp migrate utility

* Fix errors, separate out migrate for now

* Update script with suggestions from @jessesuen

* Check for localhost, server address now; print with log, not fmt

* Add more log lines, standardize output, fix args

* Improve feedback, thanks @alexmt

* Rename migration script, thanks @jessesuen

* Don't run UpdateSpec unless a change has occurred

* Move migrate => hack/migrate, thanks @jessesuen
2018-05-14 14:35:01 -07:00
Alexander Matyushentsev
2bc0dff135 Issue #146 - Render health status information in 'app list' and 'app get' commands (#198) 2018-05-14 13:21:06 -07:00
Jesse Suen
c61795f71a Add 'database' library for CRUD operations against repos and clusters. Redact sensitive information (#196) 2018-05-14 11:36:08 -07:00
Jesse Suen
a8a7491bf0 Handle potential panic when argo install settings run against an empty configmap 2018-05-11 18:47:45 -07:00
Alexander Matyushentsev
d1c7c4fcaf Issue #187 - implement argo settings install command (#193) 2018-05-11 11:50:53 -07:00
Alexander Matyushentsev
3dbbcf8918 Move sync logic to contoller (#180)
* Issue #119 - Move sync logic to contoller

* Implement app compare/sycn e2e test

* Fix panic in kube ApplyResource method

* Apply reviewer notes: add separate rollback operation instead of reusing sync for sync and rollback
2018-05-11 11:50:32 -07:00
Jesse Suen
0cfd1ad05f Update feature list with SSO and Webhook integration 2018-05-10 17:51:51 -07:00
Jesse Suen
bfa4e233b7 cli will look to spec.destination.server and namespace when displaying apps 2018-05-10 17:39:13 -07:00
Jesse Suen
dc662da3d6 Support OAuth2 login flow from CLI (resolves #172) (#181)
* Support OAuth2 login flow from CLI (resolves #172)
* Refactor SessionManager to handle local and OAuth2 logins.
* argo login will request permanent credentials after OAuth2 flow
* Implement proper OIDC app state nonce. Add explicit `--sso` flag to `argo login`
2018-05-10 15:43:58 -07:00
Jesse Suen
4107d2422b Fix linting errors 2018-05-08 16:42:12 -07:00
Andrew Merenbach
b83eac5dc2 Make ApplicationSpec.Destination non-optional, non-pointer (#177)
* Make Destination a non-pointer field

* Rm nil checks for destination; update initialization

* Update codegen

* Rm ResolveServerNamespace function, thanks @jessesuen
2018-05-08 14:09:33 -07:00
Jesse Suen
bb51837c56 Do not delete namespace or CRD during uninstall unless explicitly stated (resolves #167) (#173) 2018-05-08 12:56:59 -07:00
Jesse Suen
5bbb4fe1a1 Cache kubernetes API resource discovery (resolves #170) (#176) 2018-05-08 12:56:15 -07:00
Andrew Merenbach
b5c20e9b46 Trim spaces server-side in GitHub usernames (#171) 2018-05-07 14:56:48 -07:00
Andrew Merenbach
1e1ab636e0 Don't fail when new app has same spec as old (#168) 2018-05-07 13:20:38 -07:00
Jesse Suen
7348553897 Improve CI build stability 2018-05-07 12:44:14 -07:00
Jesse Suen
5f65a5128a Introduce caching layer to repo server to improve query response times (#165) 2018-05-07 11:31:00 -07:00
Alexander Matyushentsev
d9c12e7271 Issue #146 - ArgoCD applications should have a rolled up health status (#164)
* Issue #146 - ArgoCD applications should have a rolled up health status

* Apply reviewer notes: rename healthState to health; rename HealthState to HealthStatus
2018-05-07 08:38:25 -07:00
Jesse Suen
fb2d6b4aff Refactor repo server and git client (#163)
* added a general purpose GetFile RPC and remove GetKsonnetApp RPC
* reposerver did not talk to kubernetes -- removed all k8s imports
* git client refactored to simply method signatures
2018-05-06 20:51:17 -07:00
Andrew Merenbach
3f4ec0ab22 Expand Git repo URL normalization (#162)
* Expand unit tests for Git functions

* Update tests for IsSSHUrl

* Add TODO

* Add EnsureSuffix tests

* Add EnsureSuffix function

* Lowercase repo name in secret name

* Expand normalization and related tests

* Add tests for EnsurePrefix

* Rm redundant strings.ToLower

* Update repository names to fix broken tests

* Expand tests some more to include missing .git suffix, thanks @jessesuen

* Add additional repository tests, thanks @jessesuen

* Fix typo in comment
2018-05-04 17:31:04 -07:00
Jesse Suen
ac938fe8a3 Add GitHub webhook handling to fast-track controller application reprocessing (#160)
* Add GitHub webhook handling to fast-track application controller reprocessing
* Add GitLab and Bitbucket webhook support. Add unit tests.
2018-05-04 17:01:57 -07:00
Alexander Matyushentsev
dc1e8796fb Disable authentication for settings service 2018-05-04 16:13:22 -07:00
Alexander Matyushentsev
8c5d59c60c Issue #157 - If argocd token is expired server should return 401 instead of 500 (#158) 2018-05-04 09:48:46 -07:00
Jesse Suen
13558b7ce8 Revert change to redact credentials since logic is reused by controller 2018-05-03 16:42:21 -07:00
Alexander Matyushentsev
3b2b3dacf5 Update version 2018-05-03 15:58:09 -07:00
Alexander Matyushentsev
1b2f89995c Issue #155 - Application update failes due to concurrent access (#156) 2018-05-03 15:55:01 -07:00
Jesse Suen
0479fcdf82 Add settings endpoint so frontend can show/hide SSO login button. Rename config to settings (#153) 2018-05-03 11:18:44 -07:00
Andrew Merenbach
a04465466d Add workflow for blue-green deployments (#148)
* Add prototype script and temp README

* Clean up code and support command-line args

* Flesh out logic more now

* Start workflow

* Update Blue-Green workflow

* Rm original example script

* Update comments

* Add argo parameters; use compact output for jq

* Fix some missing values, use workflow parameters

* Separate out necessary parameters/outputs

* Get bluegreen workflow working

* Mv bluegreen.yaml to workflows/, thanks @jessesuen

* Rm TODO statements
2018-05-03 09:52:46 -07:00
Jesse Suen
670921df90 SSO Support (#152)
This change implements SSO support.

dex is run as a sidecar to the ArgoCD API server, which fronts dex using a reverse proxy. The end result is that the ArgoCD acts as an OIDC provider serving under /api/dex. The login flow begins at /auth/login, which redirects to the Dex's OAuth2 consent page and ultimately directed to the IdP provider's login page, where they enter their credentials. After logging in, the OAuth2 redirect flows back to the client app, ultimately reaching /auth/callback, where the OIDC token claims are signed, and persisted in the users's cookie.

The dex configuration YAML is formulated during startup (through the argocd-util utility), with the configuration values taken from the argocd-cm configmap and the argocd-secret.

The build process was refactored to build argocd-util statically, so that it could be run inside off-the-shelf dex, which is built from alpine. Also, build speed was improved by expanding the default make targets in the Dockerfile, to avoid rebuilding each binary from scratch

Session management was refactored to use more bare-bones jwt library constructs, so we could reuse code from the user/password flow vs. OAuth2 flow.

* Initial SSO support. Run dex as sidecar. Generate dex config from ArgoCD cm and secret
* Sign and write SSO claims to JWT cookie during SSO login. Refactor session manager
* Build argo-util statically so it can run in dex sidecar. Redirect after SSO login
* Simplify app creation process to not require communication to dex gRPC server
2018-05-02 22:02:26 -07:00
Edward Lee
18f7e17d7a Added OWNERS file 2018-05-02 18:24:50 -07:00
Andrew Merenbach
a2aede0441 Redact sensitive repo/cluster information upon retrieval (#150)
* Redact sensitive cluster information upon retrieval

* Redact git username for now, too

* Revert "Redact git username for now, too"

This reverts commit d9e2eba37e.
2018-05-01 16:58:03 -07:00
205 changed files with 28947 additions and 5955 deletions

View File

@@ -21,9 +21,7 @@ spec:
- name: cmd
value: "{{item}}"
withItems:
- make controller-image
- make server-image
- make repo-server-image
- make controller-image server-image repo-server-image
- name: test
template: ci-builder
arguments:
@@ -31,8 +29,7 @@ spec:
- name: cmd
value: "{{item}}"
withItems:
- dep ensure && make lint
- dep ensure && make test test-e2e
- dep ensure && make cli lint test test-e2e
- name: ci-builder
inputs:
@@ -49,6 +46,10 @@ spec:
command: [sh, -c]
args: ["{{inputs.parameters.cmd}}"]
workingDir: /go/src/github.com/argoproj/argo-cd
resources:
requests:
memory: 1024Mi
cpu: 200m
- name: ci-dind
inputs:
@@ -68,6 +69,10 @@ spec:
env:
- name: DOCKER_HOST
value: 127.0.0.1
resources:
requests:
memory: 1024Mi
cpu: 200m
sidecars:
- name: dind
image: docker:17.10-dind

14
CHANGELOG.md Normal file
View File

@@ -0,0 +1,14 @@
# Changelog
## v0.4.0 (2018-05-17)
+ SSO Integration
+ GitHub Webhook
+ Add application health status
+ Sync/Rollback/Delete is asynchronously handled by controller
* Refactor CRUD operation on clusters and repos
* Sync will always perform kubectl apply
* Synced Status considers last-applied-configuration annotatoin
* Server & namespace are mandatory fields (still inferred from app.yaml)
* Manifests are memoized in repo server
- Fix connection timeouts to SSH repos

View File

@@ -1,10 +1,14 @@
## Requirements
Make sure you have following tools installed [golang](https://golang.org/), [dep](https://github.com/golang/dep), [protobuf](https://developers.google.com/protocol-buffers/),
Make sure you have following tools installed [docker](https://docs.docker.com/install/#supported-platforms), [golang](https://golang.org/), [dep](https://github.com/golang/dep), [protobuf](https://developers.google.com/protocol-buffers/), [ksonnet](https://github.com/ksonnet/ksonnet#install), [go-swagger](https://github.com/go-swagger/go-swagger/blob/master/docs/install.md), and [jq](https://stedolan.github.io/jq/)
[kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
```
$ brew install go dep protobuf kubectl
$ brew tap go-swagger/go-swagger
$ brew install go dep protobuf kubectl ksonnet/tap/ks jq go-swagger
$ go get -u github.com/golang/protobuf/protoc-gen-go
$ go get -u github.com/go-swagger/go-swagger/cmd/swagger
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
```
Nice to have [gometalinter](https://github.com/alecthomas/gometalinter) and [goreman](https://github.com/mattn/goreman):
@@ -20,6 +24,11 @@ $ go get -u github.com/argoproj/argo-cd
$ dep ensure
$ make
```
NOTE: The make command can take a while, and we recommend building the specific component you are working on
* `make cli` - Make the argocd CLI tool
* `make server` - Make the API/repo/controller server
* `make codegen` - Builds protobuf and swagger files
* `make argocd-util` - Make the administrator's utility, used for certain tasks such as import/export
## Running locally
@@ -38,3 +47,10 @@ $ kubectl create -f install/manifests/01_application-crd.yaml
```
$ goreman start
```
## Troubleshooting
* Ensure argocd is installed: ./dist/argocd install
* Ensure you're logged in: ./dist/argocd login --username admin --password <whatever password you set at install> localhost:8080
* Ensure that roles are configured: kubectl create -f install/manifests/02c_argocd-rbac-cm.yaml
* Ensure minikube is running: minikube stop && minikube start
* Ensure Argo CD is aware of minikube: ./dist/argocd cluster add minikube

View File

@@ -1,13 +0,0 @@
Copyright 2017-2018 The Argo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License..

View File

@@ -1,4 +1,4 @@
FROM debian:9.3 as builder
FROM debian:9.4 as builder
RUN apt-get update && apt-get install -y \
git \
@@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Install go
ENV GO_VERSION 1.9.3
ENV GO_VERSION 1.10.3
ENV GO_ARCH amd64
ENV GOPATH /root/go
ENV PATH ${GOPATH}/bin:/usr/local/go/bin:${PATH}
@@ -25,7 +25,7 @@ RUN cd /usr/local && \
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.10.4/packr_1.10.4_linux_amd64.tar.gz && \
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
@@ -40,9 +40,9 @@ RUN cd ${GOPATH}/src/dummy && \
rmdir vendor
# Perform the build
ARG MAKE_TARGET
WORKDIR /root/go/src/github.com/argoproj/argo-cd
COPY . .
ARG MAKE_TARGET="cli server controller repo-server argocd-util"
RUN make ${MAKE_TARGET}
@@ -58,7 +58,7 @@ FROM golang:1.10 as cli-tooling
#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.10.1
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
@@ -72,13 +72,12 @@ 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/*
ARG BINARY
COPY --from=builder /root/go/src/github.com/argoproj/argo-cd/dist/${BINARY} /${BINARY}
COPY --from=cli-tooling /ks /usr/local/bin/ks
COPY --from=cli-tooling /kubectl /usr/local/bin/kubectl
# workaround ksonnet issue https://github.com/ksonnet/ksonnet/issues/298
ENV USER=root
ENV BINARY=$BINARY
CMD /$BINARY
COPY --from=builder /root/go/src/github.com/argoproj/argo-cd/dist/* /
ARG BINARY
CMD /${BINARY}

View File

@@ -1,4 +1,4 @@
FROM golang:1.9.2
FROM golang:1.10.3
WORKDIR /tmp
@@ -11,10 +11,11 @@ RUN curl -O https://get.docker.com/builds/Linux/x86_64/docker-1.13.1.tgz && \
gometalinter.v2 --install
# Install kubectl
RUN curl -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
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.10.1
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 && \

373
Gopkg.lock generated
View File

@@ -15,6 +15,12 @@
]
revision = "c02ca9a983da5807ddf7d796784928f5be4afd09"
[[projects]]
name = "github.com/Knetic/govaluate"
packages = ["."]
revision = "d216395917cc49052c7c7094cf57f09657ca08a8"
version = "v3.0.0"
[[projects]]
name = "github.com/PuerkitoBio/purell"
packages = ["."]
@@ -26,12 +32,55 @@
packages = ["."]
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
[[projects]]
name = "github.com/argoproj/argo"
packages = [
"pkg/apis/workflow",
"pkg/apis/workflow/v1alpha1"
]
revision = "ac241c95c13f08e868cd6f5ee32c9ce273e239ff"
version = "v2.1.1"
[[projects]]
name = "github.com/asaskevich/govalidator"
packages = ["."]
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
version = "v9"
[[projects]]
name = "github.com/blang/semver"
packages = ["."]
revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f"
version = "v3.5.1"
[[projects]]
name = "github.com/casbin/casbin"
packages = [
".",
"config",
"effect",
"model",
"persist",
"persist/file-adapter",
"rbac",
"rbac/default-role-manager",
"util"
]
revision = "d71629e497929858300c38cd442098c178121c30"
version = "v1.5.0"
[[projects]]
name = "github.com/coreos/dex"
packages = ["api"]
revision = "218d671a96865df2a4cf7f310efb99b8bfc5a5e2"
version = "v2.10.0"
[[projects]]
branch = "v2"
name = "github.com/coreos/go-oidc"
packages = ["."]
revision = "1180514eaf4d9f38d0d19eef639a1d695e066e72"
[[projects]]
branch = "master"
name = "github.com/daaku/go.zipexe"
@@ -56,14 +105,25 @@
".",
"log"
]
revision = "26b41036311f2da8242db402557a0dbd09dc83da"
version = "v2.6.0"
revision = "3658237ded108b4134956c1b3050349d93e7b895"
version = "v2.7.1"
[[projects]]
name = "github.com/ghodss/yaml"
packages = ["."]
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/go-openapi/analysis"
packages = ["."]
revision = "5957818e100395077187fb7ef3b8a28227af06c6"
[[projects]]
branch = "master"
name = "github.com/go-openapi/errors"
packages = ["."]
revision = "b2b2befaf267d082d779bcef52d682a47c779517"
[[projects]]
branch = "master"
@@ -77,23 +137,80 @@
packages = ["."]
revision = "36d33bfe519efae5632669801b180bf1a245da3b"
[[projects]]
branch = "master"
name = "github.com/go-openapi/loads"
packages = ["."]
revision = "2a2b323bab96e6b1fdee110e57d959322446e9c9"
[[projects]]
branch = "master"
name = "github.com/go-openapi/runtime"
packages = [
".",
"logger",
"middleware",
"middleware/denco",
"middleware/header",
"middleware/untyped",
"security"
]
revision = "cd9d8ed52e4b4665463cbc655500e4faa09c3c16"
[[projects]]
branch = "master"
name = "github.com/go-openapi/spec"
packages = ["."]
revision = "1de3e0542de65ad8d75452a595886fdd0befb363"
[[projects]]
branch = "master"
name = "github.com/go-openapi/strfmt"
packages = ["."]
revision = "481808443b00a14745fada967cb5eeff0f9b1df2"
[[projects]]
branch = "master"
name = "github.com/go-openapi/swag"
packages = ["."]
revision = "84f4bee7c0a6db40e3166044c7983c1c32125429"
[[projects]]
branch = "master"
name = "github.com/go-openapi/validate"
packages = ["."]
revision = "b0a3ed684d0fdd3e1eda00433382188ce8aa7169"
[[projects]]
name = "github.com/go-redis/cache"
packages = [
".",
"internal/lrucache",
"internal/singleflight"
]
revision = "c58ada1e23a3b66593f81c70572c20a0bb805a90"
version = "v6.3.5"
[[projects]]
name = "github.com/go-redis/redis"
packages = [
".",
"internal",
"internal/consistenthash",
"internal/hashtag",
"internal/pool",
"internal/proto",
"internal/singleflight",
"internal/util"
]
revision = "877867d2845fbaf86798befe410b6ceb6f5c29a3"
version = "v6.10.2"
[[projects]]
name = "github.com/gobuffalo/packr"
packages = ["."]
revision = "6434a292ac52e6964adebfdce3f9ce6d9f16be01"
version = "v1.10.4"
revision = "7f4074995d431987caaa35088199f13c44b24440"
version = "v1.11.0"
[[projects]]
name = "github.com/gogo/protobuf"
@@ -141,7 +258,11 @@
packages = [
"jsonpb",
"proto",
"protoc-gen-go",
"protoc-gen-go/descriptor",
"protoc-gen-go/generator",
"protoc-gen-go/grpc",
"protoc-gen-go/plugin",
"ptypes",
"ptypes/any",
"ptypes/duration",
@@ -151,12 +272,6 @@
]
revision = "e09c5db296004fbe3f74490e84dcd62c3c5ddb1b"
[[projects]]
branch = "master"
name = "github.com/google/btree"
packages = ["."]
revision = "e89373fe6b4a7413d7acd6da1725b83ef713e6e4"
[[projects]]
name = "github.com/google/go-jsonnet"
packages = [
@@ -182,15 +297,6 @@
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/gregjones/httpcache"
packages = [
".",
"diskcache"
]
revision = "2bcd89a1743fd4b373f7370ce8ddc14dfbd18229"
[[projects]]
branch = "master"
name = "github.com/grpc-ecosystem/go-grpc-middleware"
@@ -209,6 +315,14 @@
[[projects]]
name = "github.com/grpc-ecosystem/grpc-gateway"
packages = [
"protoc-gen-grpc-gateway",
"protoc-gen-grpc-gateway/descriptor",
"protoc-gen-grpc-gateway/generator",
"protoc-gen-grpc-gateway/gengateway",
"protoc-gen-grpc-gateway/httprule",
"protoc-gen-swagger",
"protoc-gen-swagger/genswagger",
"protoc-gen-swagger/options",
"runtime",
"runtime/internal",
"utilities"
@@ -249,12 +363,6 @@
revision = "e7c7f3b33712573affdcc7a107218e7926b9a05b"
version = "1.0.6"
[[projects]]
name = "github.com/juju/ratelimit"
packages = ["."]
revision = "59fac5042749a5afb9af70e813da1dd5474f0167"
version = "1.0.1"
[[projects]]
branch = "master"
name = "github.com/kardianos/osext"
@@ -269,6 +377,7 @@
"pkg/component",
"pkg/docparser",
"pkg/lib",
"pkg/log",
"pkg/node",
"pkg/params",
"pkg/prototype",
@@ -277,8 +386,8 @@
"pkg/util/kslib",
"pkg/util/strings"
]
revision = "8c44a5b1545d3d03135f610170ef0167129294bc"
version = "v0.10.1"
revision = "e943ae55d4fe256c8330a047ce8426ad9dac110c"
version = "v0.11.0"
[[projects]]
name = "github.com/ksonnet/ksonnet-lib"
@@ -291,8 +400,8 @@
"ksonnet-gen/nodemaker",
"ksonnet-gen/printer"
]
revision = "d15220fdcdd07fd377894abff6276d86cb2d776d"
version = "v0.1.3"
revision = "dfcaa3d01d0c4948cb596403c35e966c774f2678"
version = "v0.1.8"
[[projects]]
branch = "master"
@@ -306,15 +415,15 @@
[[projects]]
branch = "master"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b"
[[projects]]
name = "github.com/peterbourgon/diskv"
name = "github.com/patrickmn/go-cache"
packages = ["."]
revision = "5f041e8faa004a95c88a202771f4cc3e991971e6"
version = "v2.0.1"
revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0"
version = "v2.1.0"
[[projects]]
name = "github.com/pkg/errors"
@@ -328,6 +437,21 @@
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/pquerna/cachecontrol"
packages = [
".",
"cacheobject"
]
revision = "525d0eb5f91d30e3b1548de401b7ef9ea6898520"
[[projects]]
branch = "master"
name = "github.com/qiangmzsx/string-adapter"
packages = ["."]
revision = "38f25303bb0cd40e674a6fac01e0171ab905f5a1"
[[projects]]
name = "github.com/sergi/go-diff"
packages = ["diffmatchpatch"]
@@ -337,8 +461,13 @@
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
revision = "ea8897e79973357ba785ac2533559a6297e83c44"
[[projects]]
branch = "master"
name = "github.com/skratchdot/open-golang"
packages = ["open"]
revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c"
[[projects]]
name = "github.com/soheilhy/cmux"
@@ -381,6 +510,15 @@
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
name = "github.com/vmihailenco/msgpack"
packages = [
".",
"codes"
]
revision = "a053f3dac71df214bfe8b367f34220f0029c9c02"
version = "v3.3.1"
[[projects]]
name = "github.com/yudai/gojsondiff"
packages = [
@@ -402,6 +540,8 @@
packages = [
"bcrypt",
"blowfish",
"ed25519",
"ed25519/internal/edwards25519",
"ssh/terminal"
]
revision = "432090b8f568c018896cd8a0fb0345872bbac6ce"
@@ -469,6 +609,12 @@
]
revision = "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1"
[[projects]]
branch = "master"
name = "golang.org/x/time"
packages = ["rate"]
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
[[projects]]
branch = "master"
name = "golang.org/x/tools"
@@ -482,6 +628,7 @@
name = "google.golang.org/appengine"
packages = [
".",
"datastore",
"internal",
"internal/app_identity",
"internal/base",
@@ -536,21 +683,52 @@
revision = "8e4536a86ab602859c20df5ebfd0bd4228d08655"
version = "v1.10.0"
[[projects]]
name = "gopkg.in/go-playground/webhooks.v3"
packages = [
".",
"bitbucket",
"github",
"gitlab"
]
revision = "5580947e3ec83427ef5f6f2392eddca8dde5d99a"
version = "v3.11.0"
[[projects]]
name = "gopkg.in/inf.v0"
packages = ["."]
revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
version = "v0.9.0"
[[projects]]
branch = "v2"
name = "gopkg.in/mgo.v2"
packages = [
"bson",
"internal/json"
]
revision = "3f83fa5005286a7fe593b055f0d7771a7dce4655"
[[projects]]
name = "gopkg.in/square/go-jose.v2"
packages = [
".",
"cipher",
"json"
]
revision = "76dd09796242edb5b897103a75df2645c028c960"
version = "v2.1.6"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
[[projects]]
branch = "release-1.9"
branch = "release-1.10"
name = "k8s.io/api"
packages = [
"admission/v1beta1",
"admissionregistration/v1alpha1",
"admissionregistration/v1beta1",
"apps/v1",
@@ -569,6 +747,7 @@
"core/v1",
"events/v1beta1",
"extensions/v1beta1",
"imagepolicy/v1alpha1",
"networking/v1",
"policy/v1beta1",
"rbac/v1",
@@ -580,10 +759,10 @@
"storage/v1alpha1",
"storage/v1beta1"
]
revision = "acf347b865f29325eb61f4cd2df11e86e073a5ee"
revision = "8b7507fac302640dd5f1efbf9643199952cc58db"
[[projects]]
branch = "release-1.9"
branch = "release-1.10"
name = "k8s.io/apiextensions-apiserver"
packages = [
"pkg/apis/apiextensions",
@@ -592,20 +771,23 @@
"pkg/client/clientset/clientset/scheme",
"pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
]
revision = "b89f5ce12ce6e022fc3e9d7586d61346e694d56e"
revision = "b13a681559816a9c14f93086bbeeed1c7baf2bcb"
[[projects]]
branch = "release-1.9"
branch = "release-1.10"
name = "k8s.io/apimachinery"
packages = [
"pkg/api/equality",
"pkg/api/errors",
"pkg/api/meta",
"pkg/api/resource",
"pkg/apimachinery",
"pkg/apimachinery/announced",
"pkg/apimachinery/registered",
"pkg/apis/meta/internalversion",
"pkg/apis/meta/v1",
"pkg/apis/meta/v1/unstructured",
"pkg/apis/meta/v1alpha1",
"pkg/apis/meta/v1beta1",
"pkg/conversion",
"pkg/conversion/queryparams",
"pkg/fields",
@@ -627,27 +809,70 @@
"pkg/util/framer",
"pkg/util/intstr",
"pkg/util/json",
"pkg/util/mergepatch",
"pkg/util/net",
"pkg/util/runtime",
"pkg/util/sets",
"pkg/util/strategicpatch",
"pkg/util/validation",
"pkg/util/validation/field",
"pkg/util/wait",
"pkg/util/yaml",
"pkg/version",
"pkg/watch",
"third_party/forked/golang/json",
"third_party/forked/golang/reflect"
]
revision = "19e3f5aa3adca672c153d324e6b7d82ff8935f03"
revision = "f6313580a4d36c7c74a3d845dda6e116642c4f90"
[[projects]]
branch = "release-6.0"
branch = "release-7.0"
name = "k8s.io/client-go"
packages = [
"discovery",
"discovery/fake",
"dynamic",
"dynamic/fake",
"informers",
"informers/admissionregistration",
"informers/admissionregistration/v1alpha1",
"informers/admissionregistration/v1beta1",
"informers/apps",
"informers/apps/v1",
"informers/apps/v1beta1",
"informers/apps/v1beta2",
"informers/autoscaling",
"informers/autoscaling/v1",
"informers/autoscaling/v2beta1",
"informers/batch",
"informers/batch/v1",
"informers/batch/v1beta1",
"informers/batch/v2alpha1",
"informers/certificates",
"informers/certificates/v1beta1",
"informers/core",
"informers/core/v1",
"informers/events",
"informers/events/v1beta1",
"informers/extensions",
"informers/extensions/v1beta1",
"informers/internalinterfaces",
"informers/networking",
"informers/networking/v1",
"informers/policy",
"informers/policy/v1beta1",
"informers/rbac",
"informers/rbac/v1",
"informers/rbac/v1alpha1",
"informers/rbac/v1beta1",
"informers/scheduling",
"informers/scheduling/v1alpha1",
"informers/settings",
"informers/settings/v1alpha1",
"informers/storage",
"informers/storage/v1",
"informers/storage/v1alpha1",
"informers/storage/v1beta1",
"kubernetes",
"kubernetes/fake",
"kubernetes/scheme",
@@ -707,7 +932,34 @@
"kubernetes/typed/storage/v1alpha1/fake",
"kubernetes/typed/storage/v1beta1",
"kubernetes/typed/storage/v1beta1/fake",
"listers/admissionregistration/v1alpha1",
"listers/admissionregistration/v1beta1",
"listers/apps/v1",
"listers/apps/v1beta1",
"listers/apps/v1beta2",
"listers/autoscaling/v1",
"listers/autoscaling/v2beta1",
"listers/batch/v1",
"listers/batch/v1beta1",
"listers/batch/v2alpha1",
"listers/certificates/v1beta1",
"listers/core/v1",
"listers/events/v1beta1",
"listers/extensions/v1beta1",
"listers/networking/v1",
"listers/policy/v1beta1",
"listers/rbac/v1",
"listers/rbac/v1alpha1",
"listers/rbac/v1beta1",
"listers/scheduling/v1alpha1",
"listers/settings/v1alpha1",
"listers/storage/v1",
"listers/storage/v1alpha1",
"listers/storage/v1beta1",
"pkg/apis/clientauthentication",
"pkg/apis/clientauthentication/v1alpha1",
"pkg/version",
"plugin/pkg/client/auth/exec",
"plugin/pkg/client/auth/gcp",
"plugin/pkg/client/auth/oidc",
"rest",
@@ -730,19 +982,21 @@
"util/homedir",
"util/integer",
"util/jsonpath",
"util/retry",
"util/workqueue"
]
revision = "9389c055a838d4f208b699b3c7c51b70f2368861"
revision = "26a26f55b28aa1b338fbaf6fbbe0bcd76aed05e0"
[[projects]]
branch = "release-1.9"
branch = "release-1.10"
name = "k8s.io/code-generator"
packages = [
"cmd/go-to-protobuf",
"cmd/go-to-protobuf/protobuf",
"pkg/util",
"third_party/forked/golang/reflect"
]
revision = "91d3f6a57905178524105a085085901bb73bd3dc"
revision = "9de8e796a74d16d2a285165727d04c185ebca6dc"
[[projects]]
branch = "master"
@@ -759,12 +1013,29 @@
[[projects]]
branch = "master"
name = "k8s.io/kube-openapi"
packages = ["pkg/common"]
packages = [
"pkg/common",
"pkg/util/proto"
]
revision = "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
[[projects]]
name = "k8s.io/kubernetes"
packages = [
"pkg/apis/apps",
"pkg/apis/autoscaling",
"pkg/apis/batch",
"pkg/apis/core",
"pkg/apis/extensions",
"pkg/apis/networking",
"pkg/kubectl/scheme"
]
revision = "81753b10df112992bf51bbc2c2f85208aad78335"
version = "v1.10.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "8e7142f84554c6f1665ef18e0fb906f82de8cd802b0211c4a46ec1ad228b8b7e"
inputs-digest = "2eaad7537e4d4bc23be336a9e96c3e589cefccf6913125a37bf9b069bf02b5e9"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -3,6 +3,9 @@ required = [
"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",
]
[[constraint]]
@@ -13,25 +16,25 @@ required = [
name = "github.com/grpc-ecosystem/grpc-gateway"
version = "v1.3.1"
# override ksonnet's release-1.8 dependency
# override argo outdated dependency
[[override]]
branch = "release-1.9"
branch = "release-1.10"
name = "k8s.io/api"
[[override]]
branch = "release-1.10"
name = "k8s.io/apimachinery"
[[constraint]]
branch = "release-1.9"
name = "k8s.io/api"
[[constraint]]
name = "k8s.io/apiextensions-apiserver"
branch = "release-1.9"
branch = "release-1.10"
[[constraint]]
branch = "release-1.9"
branch = "release-1.10"
name = "k8s.io/code-generator"
[[constraint]]
branch = "release-6.0"
[[override]]
branch = "release-7.0"
name = "k8s.io/client-go"
[[constraint]]
@@ -40,9 +43,13 @@ required = [
[[constraint]]
name = "github.com/ksonnet/ksonnet"
version = "v0.10.1"
version = "v0.11.0"
[[constraint]]
name = "github.com/gobuffalo/packr"
version = "v1.11.0"
# override ksonnet's logrus dependency
[[override]]
name = "github.com/sirupsen/logrus"
version = "v1.0.3"
revision = "ea8897e79973357ba785ac2533559a6297e83c44"

View File

@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2017-2018 The Argo Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -41,7 +41,7 @@ IMAGE_PREFIX=${IMAGE_NAMESPACE}/
endif
.PHONY: all
all: cli server-image controller-image repo-server-image
all: cli server-image controller-image repo-server-image argocd-util
.PHONY: protogen
protogen:
@@ -57,30 +57,40 @@ codegen: protogen clientgen
# NOTE: we use packr to do the build instead of go, since we embed .yaml files into the go binary.
# This enables ease of maintenance of the yaml files.
.PHONY: cli
cli:
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/${CLI_NAME} ./cmd/argocd
cli: clean-debug
${PACKR_CMD} build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/${CLI_NAME} ./cmd/argocd
.PHONY: cli-linux
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 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 rm tmp-argocd-linux
.PHONY: cli-darwin
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 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 rm tmp-argocd-darwin
.PHONY: server
server:
CGO_ENABLED=0 go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd/argocd-server
.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
echo "# This is an auto-generated file. DO NOT EDIT" > manifests/install.yaml
cat manifests/components/*.yaml | sed 's@\( image: argoproj/\(.*\):latest\)@ image: '"${IMAGE_NAMESPACE}"'/\2:'"${IMAGE_TAG}"'@g' >> manifests/install.yaml
.PHONY: server
server: clean-debug
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd/argocd-server
.PHONY: server-image
server-image:
docker build --build-arg BINARY=argocd-server --build-arg MAKE_TARGET=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) -f Dockerfile-argocd .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-server:$(IMAGE_TAG) ; fi
.PHONY: repo-server
@@ -89,7 +99,7 @@ repo-server:
.PHONY: repo-server-image
repo-server-image:
docker build --build-arg BINARY=argocd-repo-server --build-arg MAKE_TARGET=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) -f Dockerfile-argocd .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-repo-server:$(IMAGE_TAG) ; fi
.PHONY: controller
@@ -98,12 +108,12 @@ controller:
.PHONY: controller-image
controller-image:
docker build --build-arg BINARY=argocd-application-controller --build-arg MAKE_TARGET=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) -f Dockerfile-argocd .
@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 --build-arg MAKE_TARGET=cli -t $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) -f Dockerfile-argocd .
docker build --build-arg BINARY=argocd -t $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) -f Dockerfile-argocd .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) ; fi
.PHONY: builder-image
@@ -122,15 +132,20 @@ test:
test-e2e:
go test ./test/e2e
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in packr boxes
.PHONY: clean-debug
clean-debug:
-find ${CURRENT_DIR} -name debug.test | xargs rm -f
.PHONY: clean
clean:
clean: clean-debug
-rm -rf ${CURRENT_DIR}/dist
.PHONY: precheckin
precheckin: test lint
.PHONY: release-precheck
release-precheck:
release-precheck: install-manifest
@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

8
OWNERS Normal file
View File

@@ -0,0 +1,8 @@
owners:
- alexmt
- jessesuen
approvers:
- alexmt
- jessesuen
- merenbach

View File

@@ -1,3 +1,4 @@
controller: go run ./cmd/argocd-application-controller/main.go --app-resync 10
api-server: go run ./cmd/argocd-server/main.go --insecure
repo-server: go run ./cmd/argocd-repo-server/main.go
controller: go run ./cmd/argocd-application-controller/main.go
api-server: go run ./cmd/argocd-server/main.go --insecure --disable-auth
repo-server: go run ./cmd/argocd-repo-server/main.go --loglevel debug
dex: sh -c "go run ./cmd/argocd-util/main.go gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p 5556:5556 -p 5557:5557 -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/coreos/dex:v2.10.0 serve /dex.yaml"

View File

@@ -1,9 +1,11 @@
# Argo CD - GitOps Continuous Delivery for Kubernetes
# Argo CD - Declarative Continuous Delivery for Kubernetes
## What is Argo CD?
Argo CD is a declarative, continuous delivery service based on ksonnet for Kubernetes.
Argo CD is a declarative, continuous delivery service based on **ksonnet** for Kubernetes.
![Argo CD UI](docs/argocd-ui.gif)
## Why Argo CD?
@@ -12,13 +14,14 @@ Application deployment and lifecycle management should be automated, auditable,
## Getting Started
Follow our [getting started guide](docs/getting_started.md).
Follow our [getting started guide](docs/getting_started.md). Further [documentation](docs/)
is provided for additional features.
## How it works
Argo CD uses git repositories as the source of truth for defining the desired application state as
well as the target deployment environments. Kubernetes manifests are specified as
[ksonnet](https://ksonnet.io) applications. Argo CD automates the deployment of the desired
Argo CD follows the **GitOps** pattern of using git repositories as the source of truth for defining the
desired application state. Kubernetes manifests are specified as [ksonnet](https://ksonnet.io)
applications. Argo CD automates the deployment of the desired
application states in the specified target environments.
![Argo CD Architecture](docs/argocd_architecture.png)
@@ -41,9 +44,13 @@ For additional details, see [architecture overview](docs/architecture.md).
* Automated deployment of applications to specified target environments
* Continuous monitoring of deployed applications
* Automated or manual syncing of applications to its target state
* Web and CLI based visualization of applications and differences between live vs. target state
* Automated or manual syncing of applications to its desired state
* Web and CLI based visualization of applications and differences between live vs. desired state
* Rollback/Roll-anywhere to any application state committed in the git repository
* Health assessment statuses on all components of the application
* SSO Integration (OIDC, OAuth2, LDAP, SAML 2.0, GitLab, Microsoft, LinkedIn)
* Webhook Integration (GitHub, BitBucket, GitLab)
* PreSync, Sync, PostSync hooks to support complex application rollouts (e.g.blue/green & canary upgrades)
## What is ksonnet?
@@ -67,10 +74,10 @@ Imagine we have a single guestbook application deployed in following environment
| Environment | K8s Version | Application Image | DB Connection String | Environment Vars | Sidecars |
|---------------|-------------|------------------------|-----------------------|------------------|---------------|
| minikube | 1.10.0 | jesse/guestbook:latest | sql://locahost/db | DEBUG=true | |
| dev | 1.9.0 | app/guestbook:latest | sql://dev-test/db | DEBUG=true | |
| staging | 1.8.0 | app/guestbook:e3c0263 | sql://staging/db | | istio,dnsmasq |
| us-west-1 | 1.8.0 | app/guestbook:abc1234 | sql://prod/db | FOO_FEATURE=true | istio,dnsmasq |
| us-west-2 | 1.8.0 | app/guestbook:abc1234 | sql://prod/db | | istio,dnsmasq |
| dev | 1.11.0 | app/guestbook:latest | sql://dev-test/db | DEBUG=true | |
| staging | 1.10.0 | app/guestbook:e3c0263 | sql://staging/db | | istio,dnsmasq |
| us-west-1 | 1.9.0 | app/guestbook:abc1234 | sql://prod/db | FOO_FEATURE=true | istio,dnsmasq |
| us-west-2 | 1.10.0 | app/guestbook:abc1234 | sql://prod/db | | istio,dnsmasq |
| us-east-1 | 1.9.0 | app/guestbook:abc1234 | sql://prod/db | BAR_FEATURE=true | istio,dnsmasq |
Ksonnet:
@@ -81,11 +88,10 @@ Ksonnet:
concise definition of kubernetes manifests
## Development Status
* Argo CD is in early development
* Argo CD is being used in production to deploy SaaS services at Intuit
## Roadmap
* PreSync, PostSync, OutOfSync hooks
* Customized application actions as Argo workflows
* Blue/Green & canary upgrades
* SSO Integration
* GitHub & Docker webhooks
* Audit trails for application events and API calls
* Service account/access key management for CI pipelines
* Revamped UI
* Customizable application actions

View File

@@ -1 +1 @@
0.4.0
0.6.2

View File

@@ -2,27 +2,29 @@ package main
import (
"context"
"flag"
"fmt"
"os"
"strconv"
"time"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/controller"
"github.com/argoproj/argo-cd/errors"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/server/cluster"
apirepository "github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/util/cli"
log "github.com/sirupsen/logrus"
"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).
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
"github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/controller"
"github.com/argoproj/argo-cd/errors"
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"
)
const (
@@ -34,11 +36,13 @@ const (
func newCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
repoServerAddress string
workers int
logLevel string
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
repoServerAddress string
statusProcessors int
operationProcessors int
logLevel string
glogLevel int
)
var command = cobra.Command{
Use: cliName,
@@ -48,6 +52,11 @@ func newCommand() *cobra.Command {
errors.CheckError(err)
log.SetLevel(level)
// Set the glog level for the k8s go-client
_ = flag.CommandLine.Parse([]string{})
_ = flag.Lookup("logtostderr").Value.Set("true")
_ = flag.Lookup("v").Value.Set(strconv.Itoa(glogLevel))
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
@@ -62,28 +71,32 @@ func newCommand() *cobra.Command {
Namespace: namespace,
InstanceID: "",
}
db := db.NewDB(namespace, kubeClient)
resyncDuration := time.Duration(appResyncPeriod) * time.Second
apiRepoServer := apirepository.NewServer(namespace, kubeClient, appClient)
apiClusterServer := cluster.NewServer(namespace, kubeClient, appClient)
clusterService := cluster.NewServer(namespace, kubeClient, appClient)
appComparator := controller.NewKsonnetAppComparator(clusterService)
repoClientset := reposerver.NewRepositoryServerClientset(repoServerAddress)
appStateManager := controller.NewAppStateManager(db, appClient, repoClientset, namespace)
appController := controller.NewApplicationController(
namespace,
kubeClient,
appClient,
reposerver.NewRepositoryServerClientset(repoServerAddress),
apiRepoServer,
apiClusterServer,
appComparator,
repoClientset,
db,
appStateManager,
resyncDuration,
&controllerConfig)
secretController := controller.NewSecretController(kubeClient, repoClientset, resyncDuration, namespace)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
log.Infof("Application Controller (version: %s) starting (namespace: %s)", argocd.GetVersion(), namespace)
go appController.Run(ctx, workers)
stats.RegisterStackDumper()
stats.StartStatsTicker(10 * time.Minute)
stats.RegisterHeapDumper("memprofile")
go secretController.Run(ctx)
go appController.Run(ctx, statusProcessors, operationProcessors)
// Wait forever
select {}
},
@@ -92,8 +105,10 @@ func newCommand() *cobra.Command {
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().Int64Var(&appResyncPeriod, "app-resync", defaultAppResyncPeriod, "Time period in seconds for application resync.")
command.Flags().StringVar(&repoServerAddress, "repo-server", "localhost:8081", "Repo server address.")
command.Flags().IntVar(&workers, "workers", 1, "Number of application workers")
command.Flags().IntVar(&statusProcessors, "status-processors", 1, "Number of application status processors")
command.Flags().IntVar(&operationProcessors, "operation-processors", 1, "Number of application operation processors")
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
command.Flags().IntVar(&glogLevel, "gloglevel", 0, "Set the glog logging level")
return &command
}

View File

@@ -4,22 +4,19 @@ import (
"fmt"
"net"
"os"
"time"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/ksonnet"
log "github.com/sirupsen/logrus"
"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).
"github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/reposerver"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/stats"
)
const (
@@ -30,8 +27,7 @@ const (
func newCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
logLevel string
logLevel string
)
var command = cobra.Command{
Use: cliName,
@@ -41,37 +37,39 @@ func newCommand() *cobra.Command {
errors.CheckError(err)
log.SetLevel(level)
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
kubeClientset := kubernetes.NewForConfigOrDie(config)
server := reposerver.NewServer(kubeClientset, namespace)
nativeGitClient, err := git.NewNativeGitClient()
errors.CheckError(err)
grpc := server.CreateGRPC(nativeGitClient)
server := reposerver.NewServer(git.NewFactory(), newCache())
grpc := server.CreateGRPC()
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
errors.CheckError(err)
ksVers, err := ksonnet.KsonnetVersion()
errors.CheckError(err)
log.Infof("argocd-repo-server %s serving on %s (namespace: %s)", argocd.GetVersion(), listener.Addr(), namespace)
log.Infof("argocd-repo-server %s serving on %s", argocd.GetVersion(), listener.Addr())
log.Infof("ksonnet version: %s", ksVers)
stats.RegisterStackDumper()
stats.StartStatsTicker(10 * time.Minute)
stats.RegisterHeapDumper("memprofile")
err = grpc.Serve(listener)
errors.CheckError(err)
return nil
},
}
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
return &command
}
func newCache() cache.Cache {
return cache.NewInMemoryCache(repository.DefaultRepoCacheExpiration)
// client := redis.NewClient(&redis.Options{
// Addr: "localhost:6379",
// Password: "",
// DB: 0,
// })
// return cache.NewRedisCache(client, repository.DefaultRepoCacheExpiration)
}
func main() {
if err := newCommand().Execute(); err != nil {
fmt.Println(err)

View File

@@ -1,15 +1,22 @@
package commands
import (
"context"
"flag"
"strconv"
"time"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"github.com/argoproj/argo-cd/errors"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/server"
"github.com/argoproj/argo-cd/util/cli"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"github.com/argoproj/argo-cd/util/stats"
)
// NewCommand returns a new instance of an argocd command
@@ -17,6 +24,7 @@ func NewCommand() *cobra.Command {
var (
insecure bool
logLevel string
glogLevel int
clientConfig clientcmd.ClientConfig
staticAssetsDir string
repoServerAddress string
@@ -31,6 +39,11 @@ func NewCommand() *cobra.Command {
errors.CheckError(err)
log.SetLevel(level)
// Set the glog level for the k8s go-client
_ = flag.CommandLine.Parse([]string{})
_ = flag.Lookup("logtostderr").Value.Set("true")
_ = flag.Lookup("v").Value.Set(strconv.Itoa(glogLevel))
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
@@ -50,8 +63,18 @@ func NewCommand() *cobra.Command {
RepoClientset: repoclientset,
DisableAuth: disableAuth,
}
argocd := server.NewServer(argoCDOpts)
argocd.Run()
stats.RegisterStackDumper()
stats.StartStatsTicker(10 * time.Minute)
stats.RegisterHeapDumper("memprofile")
for {
argocd := server.NewServer(argoCDOpts)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
argocd.Run(ctx, 8080)
cancel()
}
},
}
@@ -59,6 +82,7 @@ func NewCommand() *cobra.Command {
command.Flags().BoolVar(&insecure, "insecure", false, "Run server without TLS")
command.Flags().StringVar(&staticAssetsDir, "staticassets", "", "Static assets directory path")
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
command.Flags().IntVar(&glogLevel, "gloglevel", 0, "Set the glog logging level")
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))

384
cmd/argocd-util/main.go Normal file
View File

@@ -0,0 +1,384 @@
package main
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"syscall"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"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/settings"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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).
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)
const (
// CLIName is the name of the CLI
cliName = "argocd-util"
// YamlSeparator separates sections of a YAML file
yamlSeparator = "\n---\n"
)
// NewCommand returns a new instance of an argocd command
func NewCommand() *cobra.Command {
var (
logLevel string
)
var command = &cobra.Command{
Use: cliName,
Short: "argocd-util has internal tools used by ArgoCD",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
},
}
command.AddCommand(cli.NewVersionCmd(cliName))
command.AddCommand(NewRunDexCommand())
command.AddCommand(NewGenDexConfigCommand())
command.AddCommand(NewImportCommand())
command.AddCommand(NewExportCommand())
command.AddCommand(NewSettingsCommand())
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
return command
}
func NewRunDexCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
)
var command = cobra.Command{
Use: "rundex",
Short: "Runs dex generating a config using settings from the ArgoCD configmap and secret",
RunE: func(c *cobra.Command, args []string) error {
_, err := exec.LookPath("dex")
errors.CheckError(err)
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
kubeClientset := kubernetes.NewForConfigOrDie(config)
settingsMgr := settings.NewSettingsManager(kubeClientset, namespace)
settings, err := settingsMgr.GetSettings()
errors.CheckError(err)
ctx := context.Background()
settingsMgr.StartNotifier(ctx, settings)
updateCh := make(chan struct{}, 1)
settingsMgr.Subscribe(updateCh)
for {
var cmd *exec.Cmd
dexCfgBytes, err := dex.GenerateDexConfigYAML(settings)
errors.CheckError(err)
if len(dexCfgBytes) == 0 {
log.Infof("dex is not configured")
} else {
err = ioutil.WriteFile("/tmp/dex.yaml", dexCfgBytes, 0644)
errors.CheckError(err)
log.Info(string(dexCfgBytes))
cmd = exec.Command("dex", "serve", "/tmp/dex.yaml")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Start()
errors.CheckError(err)
}
// loop until the dex config changes
for {
<-updateCh
newDexCfgBytes, err := dex.GenerateDexConfigYAML(settings)
errors.CheckError(err)
if string(newDexCfgBytes) != string(dexCfgBytes) {
log.Infof("dex config modified. restarting dex")
if cmd != nil && cmd.Process != nil {
err = cmd.Process.Signal(syscall.SIGTERM)
errors.CheckError(err)
_, err = cmd.Process.Wait()
errors.CheckError(err)
}
break
} else {
log.Infof("dex config unmodified")
}
}
}
},
}
clientConfig = cli.AddKubectlFlagsToCmd(&command)
return &command
}
func NewGenDexConfigCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
out string
)
var command = cobra.Command{
Use: "gendexcfg",
Short: "Generates a dex config from ArgoCD settings",
RunE: func(c *cobra.Command, args []string) error {
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
kubeClientset := kubernetes.NewForConfigOrDie(config)
settingsMgr := settings.NewSettingsManager(kubeClientset, namespace)
settings, err := settingsMgr.GetSettings()
errors.CheckError(err)
dexCfgBytes, err := dex.GenerateDexConfigYAML(settings)
errors.CheckError(err)
if len(dexCfgBytes) == 0 {
log.Infof("dex is not configured")
return nil
}
if out == "" {
fmt.Printf(string(dexCfgBytes))
} else {
err = ioutil.WriteFile(out, dexCfgBytes, 0644)
errors.CheckError(err)
}
return nil
},
}
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().StringVarP(&out, "out", "o", "", "Output to the specified file instead of stdout")
return &command
}
// NewImportCommand defines a new command for exporting Kubernetes and Argo CD resources.
func NewImportCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
)
var command = cobra.Command{
Use: "import SOURCE",
Short: "Import Argo CD data from stdin (specify `-') or a file",
RunE: func(c *cobra.Command, args []string) error {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
var (
input []byte
err error
newSettings *settings.ArgoCDSettings
newRepos []*v1alpha1.Repository
newClusters []*v1alpha1.Cluster
newApps []*v1alpha1.Application
newRBACCM *apiv1.ConfigMap
)
if in := args[0]; in == "-" {
input, err = ioutil.ReadAll(os.Stdin)
errors.CheckError(err)
} else {
input, err = ioutil.ReadFile(in)
errors.CheckError(err)
}
inputStrings := strings.Split(string(input), yamlSeparator)
err = yaml.Unmarshal([]byte(inputStrings[0]), &newSettings)
errors.CheckError(err)
err = yaml.Unmarshal([]byte(inputStrings[1]), &newRepos)
errors.CheckError(err)
err = yaml.Unmarshal([]byte(inputStrings[2]), &newClusters)
errors.CheckError(err)
err = yaml.Unmarshal([]byte(inputStrings[3]), &newApps)
errors.CheckError(err)
err = yaml.Unmarshal([]byte(inputStrings[4]), &newRBACCM)
errors.CheckError(err)
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
kubeClientset := kubernetes.NewForConfigOrDie(config)
settingsMgr := settings.NewSettingsManager(kubeClientset, namespace)
err = settingsMgr.SaveSettings(newSettings)
errors.CheckError(err)
db := db.NewDB(namespace, kubeClientset)
_, err = kubeClientset.CoreV1().ConfigMaps(namespace).Create(newRBACCM)
errors.CheckError(err)
for _, repo := range newRepos {
_, err := db.CreateRepository(context.Background(), repo)
if err != nil {
log.Warn(err)
}
}
for _, cluster := range newClusters {
_, err := db.CreateCluster(context.Background(), cluster)
if err != nil {
log.Warn(err)
}
}
appClientset := appclientset.NewForConfigOrDie(config)
for _, app := range newApps {
out, err := appClientset.ArgoprojV1alpha1().Applications(namespace).Create(app)
errors.CheckError(err)
log.Println(out)
}
return nil
},
}
clientConfig = cli.AddKubectlFlagsToCmd(&command)
return &command
}
// NewExportCommand defines a new command for exporting Kubernetes and Argo CD resources.
func NewExportCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
out string
)
var command = cobra.Command{
Use: "export",
Short: "Export all Argo CD data to stdout (default) or a file",
RunE: func(c *cobra.Command, args []string) error {
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
kubeClientset := kubernetes.NewForConfigOrDie(config)
settingsMgr := settings.NewSettingsManager(kubeClientset, namespace)
settings, err := settingsMgr.GetSettings()
errors.CheckError(err)
// certificate data is included in secrets that are exported alongside
settings.Certificate = nil
db := db.NewDB(namespace, kubeClientset)
clusters, err := db.ListClusters(context.Background())
errors.CheckError(err)
repos, err := db.ListRepositories(context.Background())
errors.CheckError(err)
appClientset := appclientset.NewForConfigOrDie(config)
apps, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(metav1.ListOptions{})
errors.CheckError(err)
rbacCM, err := kubeClientset.CoreV1().ConfigMaps(namespace).Get(common.ArgoCDRBACConfigMapName, metav1.GetOptions{})
errors.CheckError(err)
// remove extraneous cruft from output
rbacCM.ObjectMeta = metav1.ObjectMeta{
Name: rbacCM.ObjectMeta.Name,
}
// remove extraneous cruft from output
for idx, app := range apps.Items {
apps.Items[idx].ObjectMeta = metav1.ObjectMeta{
Name: app.ObjectMeta.Name,
Finalizers: app.ObjectMeta.Finalizers,
}
apps.Items[idx].Status = v1alpha1.ApplicationStatus{
History: app.Status.History,
}
apps.Items[idx].Operation = nil
}
// take a list of exportable objects, marshal them to YAML,
// and return a string joined by a delimiter
output := func(delimiter string, oo ...interface{}) string {
out := make([]string, 0)
for _, o := range oo {
data, err := yaml.Marshal(o)
errors.CheckError(err)
out = append(out, string(data))
}
return strings.Join(out, delimiter)
}(yamlSeparator, settings, repos.Items, clusters.Items, apps.Items, rbacCM)
if out == "-" {
fmt.Println(output)
} else {
err = ioutil.WriteFile(out, []byte(output), 0644)
errors.CheckError(err)
}
return nil
},
}
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().StringVarP(&out, "out", "o", "-", "Output to the specified file instead of stdout")
return &command
}
// NewSettingsCommand returns a new instance of `argocd-util settings` command
func NewSettingsCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
updateSuperuser bool
superuserPassword string
updateSignature bool
)
var command = &cobra.Command{
Use: "settings",
Short: "Creates or updates ArgoCD settings",
Long: "Creates or updates ArgoCD settings",
Run: func(c *cobra.Command, args []string) {
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)
settingsMgr := settings.NewSettingsManager(kubeclientset, namespace)
_ = settings.UpdateSettings(superuserPassword, settingsMgr, updateSignature, updateSuperuser, namespace)
},
}
command.Flags().BoolVar(&updateSuperuser, "update-superuser", false, "force updating the superuser password")
command.Flags().StringVar(&superuserPassword, "superuser-password", "", "password for super user")
command.Flags().BoolVar(&updateSignature, "update-signature", false, "force updating the server-side token signing signature")
clientConfig = cli.AddKubectlFlagsToCmd(command)
return command
}
func main() {
if err := NewCommand().Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,72 @@
package commands
import (
"context"
"fmt"
"os"
"syscall"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/server/account"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/settings"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
func NewAccountCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "account",
Short: "Manage account settings",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
}
command.AddCommand(NewAccountUpdatePasswordCommand(clientOpts))
return command
}
func NewAccountUpdatePasswordCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
currentPassword string
newPassword string
)
var command = &cobra.Command{
Use: "update-password",
Short: "Update password",
Run: func(c *cobra.Command, args []string) {
if len(args) != 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
if currentPassword == "" {
fmt.Print("*** Enter current password: ")
password, err := terminal.ReadPassword(syscall.Stdin)
errors.CheckError(err)
currentPassword = string(password)
fmt.Print("\n")
}
if newPassword == "" {
newPassword = settings.ReadAndConfirmPassword()
}
updatePasswordRequest := account.UpdatePasswordRequest{
NewPassword: newPassword,
CurrentPassword: currentPassword,
}
conn, usrIf := argocdclient.NewClientOrDie(clientOpts).NewAccountClientOrDie()
defer util.Close(conn)
_, err := usrIf.UpdatePassword(context.Background(), &updatePasswordRequest)
errors.CheckError(err)
fmt.Printf("Password updated\n")
},
}
command.Flags().StringVar(&currentPassword, "current-password", "", "current password you wish to change")
command.Flags().StringVar(&newPassword, "new-password", "", "new password you want to update to")
return command
}

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,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
)
var command = &cobra.Command{
Use: "add",
Short: fmt.Sprintf("%s cluster add CONTEXT", cliName),
@@ -71,12 +75,21 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
clst := NewCluster(args[0], conf, managerBearerToken)
clst, err = clusterIf.Create(context.Background(), clst)
if inCluster {
clst.Server = common.KubernetesInternalAPIServerAddr
}
clstCreateReq := cluster.ClusterCreateRequest{
Cluster: clst,
Upsert: upsert,
}
clst, err = clusterIf.Create(context.Background(), &clstCreateReq)
errors.CheckError(err)
fmt.Printf("Cluster '%s' added\n", clst.Name)
},
}
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")
return command
}
@@ -96,9 +109,20 @@ func printKubeContexts(ca clientcmd.ConfigAccess) {
}
sort.Strings(contextNames)
if config.Clusters == nil {
return
}
for _, name := range contextNames {
// ignore malformed kube config entries
context := config.Contexts[name]
if context == nil {
continue
}
cluster := config.Clusters[context.Cluster]
if cluster == nil {
continue
}
prefix := " "
if config.CurrentContext == name {
prefix = "*"
@@ -200,9 +224,9 @@ func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
clusters, err := clusterIf.List(context.Background(), &cluster.ClusterQuery{})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "SERVER\tNAME\n")
fmt.Fprintf(w, "SERVER\tNAME\tSTATUS\tMESSAGE\n")
for _, c := range clusters.Items {
fmt.Fprintf(w, "%s\t%s\n", c.Server, c.Name)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", c.Server, c.Name, c.ConnectionState.Status, c.ConnectionState.Message)
}
_ = w.Flush()
},

View File

@@ -1,46 +0,0 @@
package commands
import (
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/install"
"github.com/argoproj/argo-cd/util/cli"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
)
// NewInstallCommand returns a new instance of `argocd install` command
func NewInstallCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
installOpts install.InstallOptions
)
var command = &cobra.Command{
Use: "install",
Short: "Install Argo CD",
Long: "Install Argo CD",
Run: func(c *cobra.Command, args []string) {
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, wasSpecified, err := clientConfig.Namespace()
errors.CheckError(err)
if wasSpecified {
installOpts.Namespace = namespace
}
installer, err := install.NewInstaller(conf, installOpts)
errors.CheckError(err)
installer.Install()
},
}
command.Flags().BoolVar(&installOpts.Upgrade, "upgrade", false, "upgrade controller/ui deployments and configmap if already installed")
command.Flags().BoolVar(&installOpts.DryRun, "dry-run", false, "print the kubernetes manifests to stdout instead of installing")
command.Flags().BoolVar(&installOpts.ConfigSuperuser, "config-superuser", false, "create or update a superuser username and password")
command.Flags().BoolVar(&installOpts.CreateSignature, "create-signature", false, "create or update the server-side token signing signature")
command.Flags().StringVar(&installOpts.ConfigMap, "config-map", "", "apply settings from a Kubernetes config map")
command.Flags().StringVar(&installOpts.ControllerImage, "controller-image", install.DefaultControllerImage, "use a specified controller image")
command.Flags().StringVar(&installOpts.ServerImage, "server-image", install.DefaultServerImage, "use a specified api server image")
command.Flags().StringVar(&installOpts.UIImage, "ui-image", install.DefaultUIImage, "use a specified ui image")
command.Flags().StringVar(&installOpts.RepoServerImage, "repo-server-image", install.DefaultRepoServerImage, "use a specified repo server image")
command.Flags().StringVar(&installOpts.ImagePullPolicy, "image-pull-policy", "", "set the image pull policy of the pod specs")
clientConfig = cli.AddKubectlFlagsToCmd(command)
return command
}

View File

@@ -1,28 +1,38 @@
package commands
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"os"
"strings"
"strconv"
"time"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/server/session"
"github.com/argoproj/argo-cd/server/settings"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/localconfig"
jwt "github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
"github.com/skratchdot/open-golang/open"
"github.com/spf13/cobra"
"golang.org/x/oauth2"
)
// NewLoginCommand returns a new instance of `argocd login` command
func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
name string
ctxName string
username string
password string
sso bool
)
var command = &cobra.Command{
Use: "login SERVER",
@@ -38,35 +48,62 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
errors.CheckError(err)
if !tlsTestResult.TLS {
if !globalClientOpts.PlainText {
askToProceed("WARNING: server is not configured with TLS. Proceed (y/n)? ")
if !cli.AskToProceed("WARNING: server is not configured with TLS. Proceed (y/n)? ") {
os.Exit(1)
}
globalClientOpts.PlainText = true
}
} else if tlsTestResult.InsecureErr != nil {
if !globalClientOpts.Insecure {
askToProceed(fmt.Sprintf("WARNING: server certificate had error: %s. Proceed insecurely (y/n)? ", tlsTestResult.InsecureErr))
if !cli.AskToProceed(fmt.Sprintf("WARNING: server certificate had error: %s. Proceed insecurely (y/n)? ", tlsTestResult.InsecureErr)) {
os.Exit(1)
}
globalClientOpts.Insecure = true
}
}
username, password = cli.PromptCredentials(username, password)
clientOpts := argocdclient.ClientOptions{
ConfigPath: "",
ServerAddr: server,
Insecure: globalClientOpts.Insecure,
PlainText: globalClientOpts.PlainText,
}
conn, sessionIf := argocdclient.NewClientOrDie(&clientOpts).NewSessionClientOrDie()
defer util.Close(conn)
acdClient := argocdclient.NewClientOrDie(&clientOpts)
setConn, setIf := acdClient.NewSettingsClientOrDie()
defer util.Close(setConn)
sessionRequest := session.SessionCreateRequest{
Username: username,
Password: password,
if ctxName == "" {
ctxName = server
}
createdSession, err := sessionIf.Create(context.Background(), &sessionRequest)
errors.CheckError(err)
fmt.Printf("user %q logged in successfully\n", username)
// Perform the login
var tokenString string
if !sso {
tokenString = passwordLogin(acdClient, username, password)
} else {
acdSet, err := setIf.Get(context.Background(), &settings.SettingsQuery{})
errors.CheckError(err)
if !ssoConfigured(acdSet) {
log.Fatalf("ArgoCD instance is not configured with SSO")
}
tokenString = oauth2Login(server, clientOpts.PlainText)
// The token which we just received from the OAuth2 flow, was from dex. ArgoCD
// currently does not back dex with any kind of persistent storage (it is run
// in-memory). As a result, this token cannot be used in any permanent capacity.
// Restarts of dex will result in a different signing key, and sessions becoming
// invalid. Instead we turn-around and ask ArgoCD to re-sign the token (who *does*
// have persistence of signing keys), and is what we store in the config. Should we
// ever decide to have a database layer for dex, the next line can be removed.
tokenString = tokenLogin(acdClient, tokenString)
}
parser := &jwt.Parser{
SkipClaimsValidation: true,
}
claims := jwt.MapClaims{}
_, _, err = parser.ParseUnverified(tokenString, &claims)
errors.CheckError(err)
fmt.Printf("'%s' logged in successfully\n", userDisplayName(claims))
// login successful. Persist the config
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
errors.CheckError(err)
@@ -79,46 +116,161 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
Insecure: globalClientOpts.Insecure,
})
localCfg.UpsertUser(localconfig.User{
Name: server,
AuthToken: createdSession.Token,
Name: ctxName,
AuthToken: tokenString,
})
if name == "" {
name = server
if ctxName == "" {
ctxName = server
}
localCfg.CurrentContext = name
localCfg.CurrentContext = ctxName
localCfg.UpsertContext(localconfig.ContextRef{
Name: name,
User: server,
Name: ctxName,
User: ctxName,
Server: server,
})
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
errors.CheckError(err)
fmt.Printf("Context '%s' updated\n", ctxName)
},
}
command.Flags().StringVar(&name, "name", "", "name to use for the context")
command.Flags().StringVar(&ctxName, "name", "", "name to use for the context")
command.Flags().StringVar(&username, "username", "", "the username of an account to authenticate")
command.Flags().StringVar(&password, "password", "", "the password of an account to authenticate")
command.Flags().BoolVar(&sso, "sso", false, "Perform SSO login")
return command
}
func askToProceed(message string) {
proceed := ""
acceptedAnswers := map[string]bool{
"y": true,
"yes": true,
"n": true,
"no": true,
func userDisplayName(claims jwt.MapClaims) string {
if email, ok := claims["email"]; ok && email != nil {
return email.(string)
}
for !acceptedAnswers[proceed] {
fmt.Print(message)
reader := bufio.NewReader(os.Stdin)
proceedRaw, err := reader.ReadString('\n')
errors.CheckError(err)
proceed = strings.TrimSpace(proceedRaw)
}
if proceed == "no" || proceed == "n" {
os.Exit(1)
if name, ok := claims["name"]; ok && name != nil {
return name.(string)
}
return claims["sub"].(string)
}
func ssoConfigured(set *settings.Settings) bool {
return set.DexConfig != nil && len(set.DexConfig.Connectors) > 0
}
// getFreePort asks the kernel for a free open port that is ready to use.
func getFreePort() (int, error) {
ln, err := net.Listen("tcp", "[::]:0")
if err != nil {
return 0, err
}
return ln.Addr().(*net.TCPAddr).Port, ln.Close()
}
// oauth2Login opens a browser, runs a temporary HTTP server to delegate OAuth2 login flow and returns the JWT token
func oauth2Login(host string, plaintext bool) string {
ctx := context.Background()
port, err := getFreePort()
errors.CheckError(err)
var scheme = "https"
if plaintext {
scheme = "http"
}
conf := &oauth2.Config{
ClientID: common.ArgoCDCLIClientAppID,
Scopes: []string{"openid", "profile", "email", "groups", "offline_access"},
Endpoint: oauth2.Endpoint{
AuthURL: fmt.Sprintf("%s://%s%s/auth", scheme, host, common.DexAPIEndpoint),
TokenURL: fmt.Sprintf("%s://%s%s/token", scheme, host, common.DexAPIEndpoint),
},
RedirectURL: fmt.Sprintf("http://localhost:%d/auth/callback", port),
}
srv := &http.Server{Addr: ":" + strconv.Itoa(port)}
var tokenString string
loginCompleted := make(chan struct{})
callbackHandler := func(w http.ResponseWriter, r *http.Request) {
defer func() {
loginCompleted <- struct{}{}
}()
// Authorization redirect callback from OAuth2 auth flow.
if errMsg := r.FormValue("error"); errMsg != "" {
http.Error(w, errMsg+": "+r.FormValue("error_description"), http.StatusBadRequest)
log.Fatal(errMsg)
return
}
code := r.FormValue("code")
if code == "" {
errMsg := fmt.Sprintf("no code in request: %q", r.Form)
http.Error(w, errMsg, http.StatusBadRequest)
log.Fatal(errMsg)
return
}
tok, err := conf.Exchange(ctx, code)
errors.CheckError(err)
log.Info("Authentication successful")
var ok bool
tokenString, ok = tok.Extra("id_token").(string)
if !ok {
errMsg := "no id_token in token response"
http.Error(w, errMsg, http.StatusInternalServerError)
log.Fatal(errMsg)
return
}
log.Debugf("Token: %s", tokenString)
successPage := `
<div style="height:100px; width:100%!; display:flex; flex-direction: column; justify-content: center; align-items:center; background-color:#2ecc71; color:white; font-size:22"><div>Authentication successful!</div></div>
<p style="margin-top:20px; font-size:18; text-align:center">Authentication was successful, you can now return to CLI. This page will close automatically</p>
<script>window.onload=function(){setTimeout(this.close, 4000)}</script>
`
fmt.Fprintf(w, successPage)
}
http.HandleFunc("/auth/callback", callbackHandler)
// add transport for self-signed certificate to context
sslcli := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
ctx = context.WithValue(ctx, oauth2.HTTPClient, sslcli)
// Redirect user to login & consent page to ask for permission for the scopes specified above.
log.Info("Opening browser for authentication")
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
log.Infof("Authentication URL: %s", url)
time.Sleep(1 * time.Second)
err = open.Run(url)
errors.CheckError(err)
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
<-loginCompleted
_ = srv.Shutdown(ctx)
return tokenString
}
func passwordLogin(acdClient argocdclient.Client, username, password string) string {
username, password = cli.PromptCredentials(username, password)
sessConn, sessionIf := acdClient.NewSessionClientOrDie()
defer util.Close(sessConn)
sessionRequest := session.SessionCreateRequest{
Username: username,
Password: password,
}
createdSession, err := sessionIf.Create(context.Background(), &sessionRequest)
errors.CheckError(err)
return createdSession.Token
}
func tokenLogin(acdClient argocdclient.Client, token string) string {
sessConn, sessionIf := acdClient.NewSessionClientOrDie()
defer util.Close(sessConn)
sessionRequest := session.SessionCreateRequest{
Token: token,
}
createdSession, err := sessionIf.Create(context.Background(), &sessionRequest)
errors.CheckError(err)
return createdSession.Token
}

View File

@@ -0,0 +1,335 @@
package commands
import (
"os"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"strings"
"context"
"fmt"
"text/tabwriter"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"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"
)
type projectOpts struct {
description string
destinations []string
sources []string
}
func (opts *projectOpts) GetDestinations() []v1alpha1.ApplicationDestination {
destinations := make([]v1alpha1.ApplicationDestination, 0)
for _, destStr := range opts.destinations {
parts := strings.Split(destStr, ",")
if len(parts) != 2 {
log.Fatalf("Expected destination of the form: server,namespace. Received: %s", destStr)
} else {
destinations = append(destinations, v1alpha1.ApplicationDestination{
Server: parts[0],
Namespace: parts[1],
})
}
}
return destinations
}
// NewProjectCommand returns a new instance of an `argocd proj` command
func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "proj",
Short: "Manage projects",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
}
command.AddCommand(NewProjectCreateCommand(clientOpts))
command.AddCommand(NewProjectDeleteCommand(clientOpts))
command.AddCommand(NewProjectListCommand(clientOpts))
command.AddCommand(NewProjectSetCommand(clientOpts))
command.AddCommand(NewProjectAddDestinationCommand(clientOpts))
command.AddCommand(NewProjectRemoveDestinationCommand(clientOpts))
command.AddCommand(NewProjectAddSourceCommand(clientOpts))
command.AddCommand(NewProjectRemoveSourceCommand(clientOpts))
return command
}
func addProjFlags(command *cobra.Command, opts *projectOpts) {
command.Flags().StringVarP(&opts.description, "description", "", "desc", "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.")
}
// NewProjectCreateCommand returns a new instance of an `argocd proj create` command
func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
opts projectOpts
)
var command = &cobra.Command{
Use: "create PROJECT",
Short: "Create a project",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
proj := v1alpha1.AppProject{
ObjectMeta: v1.ObjectMeta{Name: projName},
Spec: v1alpha1.AppProjectSpec{
Description: opts.description,
Destinations: opts.GetDestinations(),
SourceRepos: opts.sources,
},
}
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
_, err := projIf.Create(context.Background(), &project.ProjectCreateRequest{Project: &proj})
errors.CheckError(err)
},
}
addProjFlags(command, &opts)
return command
}
// NewProjectSetCommand returns a new instance of an `argocd proj set` command
func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
opts projectOpts
)
var command = &cobra.Command{
Use: "set PROJECT",
Short: "Set project parameters",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
errors.CheckError(err)
visited := 0
c.Flags().Visit(func(f *pflag.Flag) {
visited++
switch f.Name {
case "description":
proj.Spec.Description = opts.description
case "dest":
proj.Spec.Destinations = opts.GetDestinations()
case "src":
proj.Spec.SourceRepos = opts.sources
}
})
if visited == 0 {
log.Error("Please set at least one option to update")
c.HelpFunc()(c, args)
os.Exit(1)
}
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
addProjFlags(command, &opts)
return command
}
// NewProjectAddDestinationCommand returns a new instance of an `argocd proj add-destination` command
func NewProjectAddDestinationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "add-destination PROJECT SERVER NAMESPACE",
Short: "Add project destination",
Run: func(c *cobra.Command, args []string) {
if len(args) != 3 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
server := args[1]
namespace := 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)
for _, dest := range proj.Spec.Destinations {
if dest.Namespace == namespace && dest.Server == server {
log.Fatal("Specified destination is already defined in project")
}
}
proj.Spec.Destinations = append(proj.Spec.Destinations, v1alpha1.ApplicationDestination{Server: server, Namespace: namespace})
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
return command
}
// NewProjectRemoveDestinationCommand returns a new instance of an `argocd proj remove-destination` command
func NewProjectRemoveDestinationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "remove-destination PROJECT SERVER NAMESPACE",
Short: "Remove project destination",
Run: func(c *cobra.Command, args []string) {
if len(args) != 3 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
server := args[1]
namespace := 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)
index := -1
for i, dest := range proj.Spec.Destinations {
if dest.Namespace == namespace && dest.Server == server {
index = i
break
}
}
if index == -1 {
log.Fatal("Specified destination does not exist in project")
} else {
proj.Spec.Destinations = append(proj.Spec.Destinations[:index], proj.Spec.Destinations[index+1:]...)
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
}
},
}
return command
}
// NewProjectAddSourceCommand returns a new instance of an `argocd proj add-src` command
func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "add-source PROJECT URL",
Short: "Add project source repository",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
url := 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)
for _, item := range proj.Spec.SourceRepos {
if item == git.NormalizeGitURL(url) {
log.Fatal("Specified source repository is already defined in project")
}
}
proj.Spec.SourceRepos = append(proj.Spec.SourceRepos, url)
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
return command
}
// NewProjectRemoveSourceCommand returns a new instance of an `argocd proj remove-src` command
func NewProjectRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "remove-source PROJECT URL",
Short: "Remove project source repository",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName := args[0]
url := 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 := -1
for i, item := range proj.Spec.SourceRepos {
if item == git.NormalizeGitURL(url) {
index = i
break
}
}
if index == -1 {
log.Fatal("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})
errors.CheckError(err)
}
},
}
return command
}
// NewProjectDeleteCommand returns a new instance of an `argocd proj delete` command
func NewProjectDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "delete PROJECT",
Short: "Delete project",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
for _, name := range args {
_, err := projIf.Delete(context.Background(), &project.ProjectQuery{Name: name})
errors.CheckError(err)
}
},
}
return command
}
// NewProjectListCommand returns a new instance of an `argocd proj list` command
func NewProjectListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "list",
Short: "List projects",
Run: func(c *cobra.Command, args []string) {
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
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")
for _, p := range projects.Items {
fmt.Fprintf(w, "%s\t%s\t%v\n", p.Name, p.Spec.Description, p.Spec.Destinations)
}
_ = w.Flush()
},
}
return command
}

View File

@@ -7,6 +7,9 @@ import (
"os"
"text/tabwriter"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
@@ -14,8 +17,6 @@ import (
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// NewRepoCommand returns a new instance of an `argocd repo` command
@@ -39,10 +40,11 @@ func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
repo appsv1.Repository
upsert bool
sshPrivateKeyPath string
)
var command = &cobra.Command{
Use: "add",
Use: "add REPO",
Short: "Add git repository credentials",
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
@@ -57,20 +59,28 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
}
repo.SSHPrivateKey = string(keyData)
}
err := git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey)
// First test the repo *without* username/password. This gives us a hint on whether this
// is a private repo.
// NOTE: it is important not to run git commands to test git credentials on the user's
// system since it may mess with their git credential store (e.g. osx keychain).
// See issue #315
err := git.TestRepo(repo.Repo, "", "", repo.SSHPrivateKey)
if err != nil {
if repo.Username != "" && repo.Password != "" || git.IsSshURL(repo.Repo) {
// if everything was supplied or repo URL is SSH url, one of the inputs was definitely bad
if git.IsSSHURL(repo.Repo) {
// If we failed using git SSH credentials, then the repo is automatically bad
log.Fatal(err)
}
// If we can't test the repo, it's probably private. Prompt for credentials and try again.
// If we can't test the repo, it's probably private. Prompt for credentials and
// let the server test it.
repo.Username, repo.Password = cli.PromptCredentials(repo.Username, repo.Password)
err = git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey)
}
errors.CheckError(err)
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
createdRepo, err := repoIf.Create(context.Background(), &repo)
repoCreateReq := repository.RepoCreateRequest{
Repo: &repo,
Upsert: upsert,
}
createdRepo, err := repoIf.Create(context.Background(), &repoCreateReq)
errors.CheckError(err)
fmt.Printf("repository '%s' added\n", createdRepo.Repo)
},
@@ -78,13 +88,14 @@ 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().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
return command
}
// NewRepoRemoveCommand returns a new instance of an `argocd repo list` command
func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rm",
Use: "rm REPO",
Short: "Remove git repository credentials",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
@@ -113,9 +124,9 @@ func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
repos, err := repoIf.List(context.Background(), &repository.RepoQuery{})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "REPO\tUSER\n")
fmt.Fprintf(w, "REPO\tUSER\tSTATUS\tMESSAGE\n")
for _, r := range repos.Items {
fmt.Fprintf(w, "%s\t%s\n", r.Repo, r.Username)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", r.Repo, r.Username, r.ConnectionState.Status, r.ConnectionState.Message)
}
_ = w.Flush()
},

View File

@@ -28,9 +28,9 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewApplicationCommand(&clientOpts))
command.AddCommand(NewLoginCommand(&clientOpts))
command.AddCommand(NewRepoCommand(&clientOpts))
command.AddCommand(NewInstallCommand())
command.AddCommand(NewUninstallCommand())
command.AddCommand(NewContextCommand(&clientOpts))
command.AddCommand(NewProjectCommand(&clientOpts))
command.AddCommand(NewAccountCommand(&clientOpts))
defaultLocalConfigPath, err := localconfig.DefaultLocalConfigPath()
errors.CheckError(err)

View File

@@ -1,36 +0,0 @@
package commands
import (
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/install"
"github.com/argoproj/argo-cd/util/cli"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
)
// NewUninstallCommand returns a new instance of `argocd install` command
func NewUninstallCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
installOpts install.InstallOptions
)
var command = &cobra.Command{
Use: "uninstall",
Short: "Uninstall Argo CD",
Long: "Uninstall Argo CD",
Run: func(c *cobra.Command, args []string) {
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, wasSpecified, err := clientConfig.Namespace()
errors.CheckError(err)
if wasSpecified {
installOpts.Namespace = namespace
}
installer, err := install.NewInstaller(conf, installOpts)
errors.CheckError(err)
installer.Uninstall()
},
}
clientConfig = cli.AddKubectlFlagsToCmd(command)
return command
}

View File

@@ -1,8 +1,9 @@
package common
import (
"github.com/argoproj/argo-cd/pkg/apis/application"
rbacv1 "k8s.io/api/rbac/v1"
"github.com/argoproj/argo-cd/pkg/apis/application"
)
const (
@@ -14,12 +15,44 @@ const (
// SecretTypeCluster indicates a secret type of cluster
SecretTypeCluster = "cluster"
// AuthCookieName is the HTTP cookie name where we store our auth token
AuthCookieName = "argocd.token"
// ResourcesFinalizerName is a number of application CRD finalizer
ResourcesFinalizerName = "resources-finalizer." + MetadataPrefix
// KubernetesInternalAPIServerAddr is address of the k8s API server when accessing internal to the cluster
KubernetesInternalAPIServerAddr = "https://kubernetes.default.svc"
)
const (
ArgoCDAdminUsername = "admin"
ArgoCDSecretName = "argocd-secret"
ArgoCDConfigMapName = "argocd-cm"
ArgoCDAdminUsername = "admin"
ArgoCDSecretName = "argocd-secret"
ArgoCDConfigMapName = "argocd-cm"
ArgoCDRBACConfigMapName = "argocd-rbac-cm"
)
const (
// DexAPIEndpoint is the endpoint where we serve the Dex API server
DexAPIEndpoint = "/api/dex"
// LoginEndpoint is ArgoCD's shorthand login endpoint which redirects to dex's OAuth 2.0 provider's consent page
LoginEndpoint = "/auth/login"
// CallbackEndpoint is ArgoCD's final callback endpoint we reach after OAuth 2.0 login flow has been completed
CallbackEndpoint = "/auth/callback"
// ArgoCDClientAppName is name of the Oauth client app used when registering our web app to dex
ArgoCDClientAppName = "ArgoCD"
// ArgoCDClientAppID is the Oauth client ID we will use when registering our app to dex
ArgoCDClientAppID = "argo-cd"
// ArgoCDCLIClientAppName is name of the Oauth client app used when registering our CLI to dex
ArgoCDCLIClientAppName = "ArgoCD CLI"
// ArgoCDCLIClientAppID is the Oauth client ID we will use when registering our CLI to dex
ArgoCDCLIClientAppID = "argo-cd-cli"
// EnvVarSSODebug is an environment variable to enable additional OAuth debugging in the API server
EnvVarSSODebug = "ARGOCD_SSO_DEBUG"
// EnvVarRBACDebug is an environment variable to enable additional RBAC debugging in the API server
EnvVarRBACDebug = "ARGOCD_RBAC_DEBUG"
// DefaultAppProjectName contains name of default app project. The default app project allows deploying application to any cluster.
DefaultAppProjectName = "default"
)
var (
@@ -29,11 +62,28 @@ var (
// LabelKeySecretType contains the type of argocd secret (either 'cluster' or 'repo')
LabelKeySecretType = MetadataPrefix + "/secret-type"
// AnnotationConnectionStatus contains connection state status
AnnotationConnectionStatus = MetadataPrefix + "/connection-status"
// AnnotationConnectionMessage contains additional information about connection status
AnnotationConnectionMessage = MetadataPrefix + "/connection-message"
// AnnotationConnectionModifiedAt contains timestamp when connection state had been modified
AnnotationConnectionModifiedAt = MetadataPrefix + "/connection-modified-at"
// AnnotationHook contains the hook type of a resource
AnnotationHook = MetadataPrefix + "/hook"
// AnnotationHookDeletePolicy is the policy of deleting a hook
AnnotationHookDeletePolicy = MetadataPrefix + "/hook-delete-policy"
// LabelKeyApplicationControllerInstanceID is the label which allows to separate application among multiple running application controllers.
LabelKeyApplicationControllerInstanceID = application.ApplicationFullName + "/controller-instanceid"
// LabelApplicationName is the label which indicates that resource belongs to application with the specified name
LabelApplicationName = application.ApplicationFullName + "/app-name"
// AnnotationKeyRefresh is the annotation key in the application which is updated with an
// arbitrary value (i.e. timestamp) on a git event, to force the controller to wake up and
// re-evaluate the application
AnnotationKeyRefresh = application.ApplicationFullName + "/refresh"
)
// ArgoCDManagerServiceAccount is the name of the service account for managing a cluster

696
controller/appcontroller.go Normal file
View File

@@ -0,0 +1,696 @@
package controller
import (
"context"
"encoding/json"
"fmt"
"reflect"
"runtime/debug"
"sync"
"time"
log "github.com/sirupsen/logrus"
"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/types"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"github.com/argoproj/argo-cd/common"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/health"
"github.com/argoproj/argo-cd/util/kube"
)
const (
watchResourcesRetryTimeout = 10 * time.Second
updateOperationStateTimeout = 1 * time.Second
)
// ApplicationController is the controller for application resources.
type ApplicationController struct {
namespace string
kubeClientset kubernetes.Interface
applicationClientset appclientset.Interface
appRefreshQueue workqueue.RateLimitingInterface
appOperationQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
appStateManager AppStateManager
statusRefreshTimeout time.Duration
repoClientset reposerver.Clientset
db db.ArgoDB
forceRefreshApps map[string]bool
forceRefreshAppsMutex *sync.Mutex
}
type ApplicationControllerConfig struct {
InstanceID string
Namespace string
}
// NewApplicationController creates new instance of ApplicationController.
func NewApplicationController(
namespace string,
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{
namespace: namespace,
kubeClientset: kubeClientset,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appRefreshQueue: appRefreshQueue,
appOperationQueue: appOperationQueue,
appStateManager: appStateManager,
appInformer: newApplicationInformer(applicationClientset, appRefreshQueue, appOperationQueue, appResyncPeriod, config),
db: db,
statusRefreshTimeout: appResyncPeriod,
forceRefreshApps: make(map[string]bool),
forceRefreshAppsMutex: &sync.Mutex{},
}
}
// Run starts the Application CRD controller.
func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int, operationProcessors int) {
defer runtime.HandleCrash()
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
}
for i := 0; i < statusProcessors; i++ {
go wait.Until(func() {
for ctrl.processAppRefreshQueueItem() {
}
}, time.Second, ctx.Done())
}
for i := 0; i < operationProcessors; i++ {
go wait.Until(func() {
for ctrl.processAppOperationQueueItem() {
}
}, time.Second, ctx.Done())
}
<-ctx.Done()
}
func (ctrl *ApplicationController) forceAppRefresh(appName string) {
ctrl.forceRefreshAppsMutex.Lock()
defer ctrl.forceRefreshAppsMutex.Unlock()
ctrl.forceRefreshApps[appName] = true
}
func (ctrl *ApplicationController) isRefreshForced(appName string) bool {
ctrl.forceRefreshAppsMutex.Lock()
defer ctrl.forceRefreshAppsMutex.Unlock()
_, ok := ctrl.forceRefreshApps[appName]
if ok {
delete(ctrl.forceRefreshApps, appName)
}
return ok
}
// 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)
if err != nil {
return err
}
for event := range ch {
eventObj := event.Object.(*unstructured.Unstructured)
objLabels := eventObj.GetLabels()
if objLabels == nil {
objLabels = make(map[string]string)
}
if appName, ok := objLabels[common.LabelApplicationName]; ok {
ctrl.forceAppRefresh(appName)
ctrl.appRefreshQueue.Add(ctrl.namespace + "/" + appName)
}
}
return fmt.Errorf("resource updates channel has closed")
}, fmt.Sprintf("watch app resources on %s", config.Host), ctx, watchResourcesRetryTimeout)
}
// 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)
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()
delete(watchingClusters, event.Cluster.Server)
} else if event.Type != watch.Deleted && !ok {
ctx, cancel := context.WithCancel(context.Background())
watchingClusters[event.Cluster.Server] = cancel
go ctrl.watchClusterResources(ctx, *event.Cluster)
}
})
}, "watch clusters", context.Background(), watchResourcesRetryTimeout)
<-context.Background().Done()
}
// retryUntilSucceed keep retrying given action with specified timeout until action succeed or specified context is done.
func retryUntilSucceed(action func() error, desc string, ctx context.Context, timeout time.Duration) {
ctxCompleted := false
go func() {
select {
case <-ctx.Done():
ctxCompleted = true
}
}()
for {
err := action()
if err == nil {
return
}
if err != nil {
if ctxCompleted {
log.Infof("Stop retrying %s", desc)
return
} else {
log.Warnf("Failed to %s: %v, retrying in %v", desc, err, timeout)
time.Sleep(timeout)
}
}
}
}
func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext bool) {
appKey, shutdown := ctrl.appOperationQueue.Get()
if shutdown {
processNext = false
return
} else {
processNext = true
}
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
ctrl.appOperationQueue.Done(appKey)
}()
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(appKey.(string))
if err != nil {
log.Errorf("Failed to get application '%s' from informer index: %+v", appKey, err)
return
}
if !exists {
// This happens after app was deleted, but the work queue still had an entry for it.
return
}
app, ok := obj.(*appv1.Application)
if !ok {
log.Warnf("Key '%s' in index is not an application", appKey)
return
}
if app.Operation != nil {
ctrl.processRequestedAppOperation(app)
} else if app.DeletionTimestamp != nil && app.CascadedDeletion() {
ctrl.finalizeApplicationDeletion(app)
}
return
}
func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Application) {
log.Infof("Deleting resources for application %s", app.Name)
// 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)
}
return
}
clst, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err == nil {
config := clst.RESTConfig()
err = kube.DeleteResourceWithLabel(config, app.Spec.Destination.Namespace, common.LabelApplicationName, app.Name)
if err == nil {
app.SetCascadedDeletion(false)
var patch []byte
patch, err = json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"finalizers": app.Finalizers,
},
})
if err == nil {
_, err = ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Patch(app.Name, types.MergePatchType, patch)
}
}
}
if err != nil {
log.Errorf("Unable to delete application resources: %v", err)
ctrl.setAppCondition(app, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionDeletionError,
Message: err.Error(),
})
} else {
log.Infof("Successfully deleted resources for application %s", app.Name)
}
}
func (ctrl *ApplicationController) setAppCondition(app *appv1.Application, condition appv1.ApplicationCondition) {
index := -1
for i, exiting := range app.Status.Conditions {
if exiting.Type == condition.Type {
index = i
break
}
}
if index > -1 {
app.Status.Conditions[index] = condition
} else {
app.Status.Conditions = append(app.Status.Conditions, condition)
}
var patch []byte
patch, err := json.Marshal(map[string]interface{}{
"status": map[string]interface{}{
"conditions": app.Status.Conditions,
},
})
if err == nil {
_, err = ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Patch(app.Name, types.MergePatchType, patch)
}
if err != nil {
log.Errorf("Unable to set application condition: %v", err)
}
}
func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Application) {
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())
state.Phase = appv1.OperationError
if rerr, ok := r.(error); ok {
state.Message = rerr.Error()
} else {
state.Message = fmt.Sprintf("%v", r)
}
ctrl.setOperationState(app, state)
}
}()
if isOperationInProgress(app) {
// If we get here, we are about process an operation but we notice it is already in progress.
// We need to detect if the app object we pulled off the informer is stale and doesn't
// reflect the fact that the operation is completed. We don't want to perform the operation
// 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)
return
}
if !isOperationInProgress(freshApp) {
log.Infof("Skipping operation on stale application state (%s)", app.ObjectMeta.Name)
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)
} 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)
}
ctrl.appStateManager.SyncAppState(app, state)
if state.Phase == appv1.OperationRunning {
// It's possible for an app to be terminated while we were operating on it. We do not want
// to clobber the Terminated state with Running. Get the latest app state to check for this.
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})
if err == nil {
if freshApp.Status.OperationState != nil && freshApp.Status.OperationState.Phase == appv1.OperationTerminating {
state.Phase = appv1.OperationTerminating
state.Message = "operation is terminating"
// after this, we will get requeued to the workqueue, but next time the
// SyncAppState will operate in a Terminating phase, allowing the worker to perform
// cleanup (e.g. delete jobs, workflows, etc...)
}
}
}
ctrl.setOperationState(app, state)
if state.Phase.Completed() {
// if we just completed an operation, force a refresh so that UI will report up-to-date
// sync/health information
ctrl.forceAppRefresh(app.ObjectMeta.Name)
}
}
func (ctrl *ApplicationController) setOperationState(app *appv1.Application, state *appv1.OperationState) {
retryUntilSucceed(func() error {
if state.Phase == "" {
// expose any bugs where we neglect to set phase
panic("no phase was set")
}
if state.Phase.Completed() {
now := metav1.Now()
state.FinishedAt = &now
}
patch := map[string]interface{}{
"status": map[string]interface{}{
"operationState": state,
},
}
if state.Phase.Completed() {
// If operation is completed, clear the operation field to indicate no operation is
// in progress.
patch["operation"] = nil
}
if reflect.DeepEqual(app.Status.OperationState, state) {
log.Infof("No operation updates necessary to '%s'. Skipping patch", app.Name)
return nil
}
patchJSON, err := json.Marshal(patch)
if err != nil {
return err
}
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace)
_, err = appClient.Patch(app.Name, types.MergePatchType, patchJSON)
if err != nil {
return err
}
log.Infof("updated '%s' operation (phase: %s)", app.Name, state.Phase)
return nil
}, "Update application operation state", context.Background(), updateOperationStateTimeout)
}
func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext bool) {
appKey, shutdown := ctrl.appRefreshQueue.Get()
if shutdown {
processNext = false
return
}
processNext = true
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
ctrl.appRefreshQueue.Done(appKey)
}()
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(appKey.(string))
if err != nil {
log.Errorf("Failed to get application '%s' from informer index: %+v", appKey, err)
return
}
if !exists {
// This happens after app was deleted, but the work queue still had an entry for it.
return
}
app, ok := obj.(*appv1.Application)
if !ok {
log.Warnf("Key '%s' in index is not an application", appKey)
return
}
if !ctrl.needRefreshAppStatus(app, ctrl.statusRefreshTimeout) {
return
}
app = app.DeepCopy()
conditions, hasErrors := ctrl.refreshAppConditions(app)
if hasErrors {
comparisonResult := app.Status.ComparisonResult.DeepCopy()
comparisonResult.Status = appv1.ComparisonStatusUnknown
health := app.Status.Health.DeepCopy()
health.Status = appv1.HealthStatusUnknown
ctrl.updateAppStatus(app, comparisonResult, health, nil, conditions)
return
}
comparisonResult, manifestInfo, compConditions, err := ctrl.appStateManager.CompareAppState(app, "", nil)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
} else {
conditions = append(conditions, compConditions...)
}
var parameters []*appv1.ComponentParameter
if manifestInfo != nil {
parameters = manifestInfo.Params
}
healthState, err := setApplicationHealth(comparisonResult)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
}
ctrl.updateAppStatus(app, comparisonResult, healthState, parameters, conditions)
return
}
// 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 {
var reason string
if ctrl.isRefreshForced(app.Name) {
reason = "force refresh"
} else if app.Status.ComparisonResult.Status == appv1.ComparisonStatusUnknown {
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()) {
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)
return true
}
return false
}
func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application) ([]appv1.ApplicationCondition, bool) {
conditions := make([]appv1.ApplicationCondition, 0)
proj, err := argo.GetAppProject(&app.Spec, ctrl.applicationClientset, ctrl.namespace)
if err != nil {
if errors.IsNotFound(err) {
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("Application referencing project %s which does not exist", app.Spec.Project),
})
} else {
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionUnknownError,
Message: err.Error(),
})
}
} else {
specConditions, err := argo.GetSpecErrors(context.Background(), &app.Spec, proj, ctrl.repoClientset, ctrl.db)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionUnknownError,
Message: err.Error(),
})
} else {
conditions = append(conditions, specConditions...)
}
}
// List of condition types which have to be reevaluated by controller; all remaining conditions should stay as is.
reevaluateTypes := map[appv1.ApplicationConditionType]bool{
appv1.ApplicationConditionInvalidSpecError: true,
appv1.ApplicationConditionUnknownError: true,
appv1.ApplicationConditionComparisonError: true,
appv1.ApplicationConditionSharedResourceWarning: true,
}
appConditions := make([]appv1.ApplicationCondition, 0)
for i := 0; i < len(app.Status.Conditions); i++ {
condition := app.Status.Conditions[i]
if _, ok := reevaluateTypes[condition.Type]; !ok {
appConditions = append(appConditions, condition)
}
}
hasErrors := false
for i := range conditions {
condition := conditions[i]
appConditions = append(appConditions, condition)
if condition.IsError() {
hasErrors = true
}
}
return appConditions, hasErrors
}
// setApplicationHealth updates the health statuses of all resources performed in the comparison
func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
appHealth := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
if comparisonResult.Status == appv1.ComparisonStatusUnknown {
appHealth.Status = appv1.HealthStatusUnknown
}
for i, resource := range comparisonResult.Resources {
if resource.LiveState == "null" {
resource.Health = appv1.HealthStatus{Status: appv1.HealthStatusMissing}
} else {
var obj unstructured.Unstructured
err := json.Unmarshal([]byte(resource.LiveState), &obj)
if err != nil {
return nil, err
}
healthState, err := health.GetAppHealth(&obj)
if err != nil {
return nil, err
}
resource.Health = *healthState
}
comparisonResult.Resources[i] = resource
if health.IsWorse(appHealth.Status, resource.Health.Status) {
appHealth.Status = resource.Health.Status
}
}
return &appHealth, nil
}
// updateAppStatus persists updates to application status. Detects if there patch
func (ctrl *ApplicationController) updateAppStatus(
app *appv1.Application,
comparisonResult *appv1.ComparisonResult,
healthState *appv1.HealthStatus,
parameters []*appv1.ComponentParameter,
conditions []appv1.ApplicationCondition,
) {
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 healthState != nil {
modifiedApp.Status.Health = *healthState
}
if parameters != nil {
modifiedApp.Status.Parameters = make([]appv1.ComponentParameter, len(parameters))
for i := range parameters {
modifiedApp.Status.Parameters[i] = *parameters[i]
}
}
if conditions != nil {
modifiedApp.Status.Conditions = conditions
}
origBytes, err := json.Marshal(app)
if err != nil {
log.Errorf("Error updating application %s (marshal orig app): %v", app.Name, err)
return
}
modifiedBytes, err := json.Marshal(modifiedApp)
if err != nil {
log.Errorf("Error updating application %s (marshal modified app): %v", app.Name, 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)
return
}
if string(patch) == "{}" {
log.Infof("No status changes to %s. Skipping patch", app.Name)
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)
} else {
log.Infof("Application %s update successful", app.Name)
}
}
func newApplicationInformer(
appClientset appclientset.Interface,
appQueue workqueue.RateLimitingInterface,
appOperationQueue workqueue.RateLimitingInterface,
appResyncPeriod time.Duration,
config *ApplicationControllerConfig) cache.SharedIndexInformer {
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)
}
options.FieldSelector = fields.Everything().String()
labelSelector := labels.NewSelector().Add(*instanceIDReq)
options.LabelSelector = labelSelector.String()
},
)
informer := appInformerFactory.Argoproj().V1alpha1().Applications().Informer()
informer.AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
appQueue.Add(key)
appOperationQueue.Add(key)
}
},
UpdateFunc: func(old, new interface{}) {
key, err := cache.MetaNamespaceKeyFunc(new)
if err == nil {
appQueue.Add(key)
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)
}
},
},
)
return informer
}
func isOperationInProgress(app *appv1.Application) bool {
return app.Status.OperationState != nil && !app.Status.OperationState.Phase.Completed()
}

View File

@@ -1,253 +0,0 @@
package controller
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/server/cluster"
"github.com/argoproj/argo-cd/util/diff"
kubeutil "github.com/argoproj/argo-cd/util/kube"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
)
// AppComparator defines methods which allow to compare application spec and actual application state.
type AppComparator interface {
CompareAppState(server string, namespace string, targetObjs []*unstructured.Unstructured, app *v1alpha1.Application) (*v1alpha1.ComparisonResult, error)
}
// KsonnetAppComparator allows to compare application using KSonnet CLI
type KsonnetAppComparator struct {
clusterService cluster.ClusterServiceServer
}
// groupLiveObjects deduplicate list of kubernetes resources and choose correct version of resource: if resource has corresponding expected application resource then method pick
// kubernetes resource with matching version, otherwise chooses single kubernetes resource with any version
func (ks *KsonnetAppComparator) groupLiveObjects(liveObjs []*unstructured.Unstructured, targetObjs []*unstructured.Unstructured) map[string]*unstructured.Unstructured {
targetByFullName := make(map[string]*unstructured.Unstructured)
for _, obj := range targetObjs {
targetByFullName[getResourceFullName(obj)] = obj
}
liveListByFullName := make(map[string][]*unstructured.Unstructured)
for _, obj := range liveObjs {
list := liveListByFullName[getResourceFullName(obj)]
if list == nil {
list = make([]*unstructured.Unstructured, 0)
}
list = append(list, obj)
liveListByFullName[getResourceFullName(obj)] = list
}
liveByFullName := make(map[string]*unstructured.Unstructured)
for fullName, list := range liveListByFullName {
targetObj := targetByFullName[fullName]
var liveObj *unstructured.Unstructured
if targetObj != nil {
for i := range list {
if list[i].GetAPIVersion() == targetObj.GetAPIVersion() {
liveObj = list[i]
break
}
}
} else {
liveObj = list[0]
}
if liveObj != nil {
liveByFullName[getResourceFullName(liveObj)] = liveObj
}
}
return liveByFullName
}
// CompareAppState compares application spec and real app state using KSonnet
func (ks *KsonnetAppComparator) CompareAppState(
server string,
namespace string,
targetObjs []*unstructured.Unstructured,
app *v1alpha1.Application) (*v1alpha1.ComparisonResult, error) {
log.Infof("Comparing app %s state in cluster %s (namespace: %s)", app.ObjectMeta.Name, server, namespace)
// Get the REST config for the cluster corresponding to the environment
clst, err := ks.clusterService.Get(context.Background(), &cluster.ClusterQuery{Server: server})
if err != nil {
return nil, err
}
restConfig := clst.RESTConfig()
// Retrieve the live versions of the objects
liveObjs, err := kubeutil.GetResourcesWithLabel(restConfig, namespace, common.LabelApplicationName, app.Name)
if err != nil {
return nil, err
}
liveObjByFullName := ks.groupLiveObjects(liveObjs, targetObjs)
controlledLiveObj := make([]*unstructured.Unstructured, len(targetObjs))
// Move live resources which have corresponding target object to controlledLiveObj
dynClientPool := dynamic.NewDynamicClientPool(restConfig)
disco, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
return nil, err
}
for i, targetObj := range targetObjs {
fullName := getResourceFullName(targetObj)
liveObj := liveObjByFullName[fullName]
if liveObj == nil {
// If we get here, it indicates we did not find the live resource when querying using
// our app label. However, it is possible that the resource was created/modified outside
// 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, err
}
apiResource, err := kubeutil.ServerResourceForGroupVersionKind(disco, gvk)
if err != nil {
return nil, err
}
liveObj, err = kubeutil.GetLiveResource(dclient, targetObj, apiResource, namespace)
if err != nil {
return nil, err
}
}
controlledLiveObj[i] = liveObj
delete(liveObjByFullName, fullName)
}
// Move root level live resources to controlledLiveObj and add nil to targetObjs to indicate that target object is missing
for fullName := range liveObjByFullName {
liveObj := liveObjByFullName[fullName]
if !hasParent(liveObj) {
targetObjs = append(targetObjs, nil)
controlledLiveObj = append(controlledLiveObj, liveObj)
}
}
// Do the actual comparison
diffResults, err := diff.DiffArray(targetObjs, controlledLiveObj)
if err != nil {
return nil, err
}
comparisonStatus := v1alpha1.ComparisonStatusSynced
resources := make([]v1alpha1.ResourceState, len(targetObjs))
for i := 0; i < len(targetObjs); i++ {
resState := v1alpha1.ResourceState{
ChildLiveResources: make([]v1alpha1.ResourceNode, 0),
}
diffResult := diffResults.Diffs[i]
if diffResult.Modified {
// Set resource state to 'OutOfSync' since target and corresponding live resource are different
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
resState.Status = v1alpha1.ComparisonStatusSynced
}
if targetObjs[i] == nil {
resState.TargetState = "null"
// Set resource state to 'OutOfSync' since target resource is missing and live resource is unexpected
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
targetObjBytes, err := json.Marshal(targetObjs[i].Object)
if err != nil {
return nil, err
}
resState.TargetState = string(targetObjBytes)
}
if controlledLiveObj[i] == nil {
resState.LiveState = "null"
// Set resource state to 'OutOfSync' since target resource present but corresponding live resource is missing
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
liveObjBytes, err := json.Marshal(controlledLiveObj[i].Object)
if err != nil {
return nil, err
}
resState.LiveState = string(liveObjBytes)
}
resources[i] = resState
}
for i, resource := range resources {
liveResource := controlledLiveObj[i]
if liveResource != nil {
childResources, err := getChildren(liveResource, liveObjByFullName)
if err != nil {
return nil, err
}
resource.ChildLiveResources = childResources
resources[i] = resource
}
}
compResult := v1alpha1.ComparisonResult{
ComparedTo: app.Spec.Source,
ComparedAt: metav1.Time{Time: time.Now().UTC()},
Server: clst.Server,
Namespace: namespace,
Resources: resources,
Status: comparisonStatus,
}
return &compResult, nil
}
func hasParent(obj *unstructured.Unstructured) bool {
// TODO: remove special case after Service and Endpoint get explicit relationship ( https://github.com/kubernetes/kubernetes/issues/28483 )
return obj.GetKind() == kubeutil.EndpointsKind || metav1.GetControllerOf(obj) != nil
}
func isControlledBy(obj *unstructured.Unstructured, parent *unstructured.Unstructured) bool {
// TODO: remove special case after Service and Endpoint get explicit relationship ( https://github.com/kubernetes/kubernetes/issues/28483 )
if obj.GetKind() == kubeutil.EndpointsKind && parent.GetKind() == kubeutil.ServiceKind {
return obj.GetName() == parent.GetName()
}
return metav1.IsControlledBy(obj, parent)
}
func getChildren(parent *unstructured.Unstructured, liveObjByFullName map[string]*unstructured.Unstructured) ([]v1alpha1.ResourceNode, error) {
children := make([]v1alpha1.ResourceNode, 0)
for fullName, obj := range liveObjByFullName {
if isControlledBy(obj, parent) {
delete(liveObjByFullName, fullName)
childResource := v1alpha1.ResourceNode{}
json, err := json.Marshal(obj)
if err != nil {
return nil, err
}
childResource.State = string(json)
childResourceChildren, err := getChildren(obj, liveObjByFullName)
if err != nil {
return nil, err
}
childResource.Children = childResourceChildren
children = append(children, childResource)
}
}
return children, nil
}
func getResourceFullName(obj *unstructured.Unstructured) string {
return fmt.Sprintf("%s:%s", obj.GetKind(), obj.GetName())
}
// NewKsonnetAppComparator creates new instance of Ksonnet app comparator
func NewKsonnetAppComparator(clusterService cluster.ClusterServiceServer) AppComparator {
return &KsonnetAppComparator{
clusterService: clusterService,
}
}

View File

@@ -1,387 +0,0 @@
package controller
import (
"context"
"encoding/json"
"fmt"
"time"
"sync"
"github.com/argoproj/argo-cd/common"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/server/cluster"
apireposerver "github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/util"
argoutil "github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/kube"
log "github.com/sirupsen/logrus"
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/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
const (
watchResourcesRetryTimeout = 10 * time.Second
)
// ApplicationController is the controller for application resources.
type ApplicationController struct {
namespace string
repoClientset reposerver.Clientset
kubeClientset kubernetes.Interface
applicationClientset appclientset.Interface
appQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
appComparator AppComparator
statusRefreshTimeout time.Duration
apiRepoService apireposerver.RepositoryServiceServer
apiClusterService *cluster.Server
forceRefreshApps map[string]bool
forceRefreshAppsMutex *sync.Mutex
}
type ApplicationControllerConfig struct {
InstanceID string
Namespace string
}
// NewApplicationController creates new instance of ApplicationController.
func NewApplicationController(
namespace string,
kubeClientset kubernetes.Interface,
applicationClientset appclientset.Interface,
repoClientset reposerver.Clientset,
apiRepoService apireposerver.RepositoryServiceServer,
apiClusterService *cluster.Server,
appComparator AppComparator,
appResyncPeriod time.Duration,
config *ApplicationControllerConfig,
) *ApplicationController {
appQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
return &ApplicationController{
namespace: namespace,
kubeClientset: kubeClientset,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appQueue: appQueue,
apiRepoService: apiRepoService,
apiClusterService: apiClusterService,
appComparator: appComparator,
appInformer: newApplicationInformer(applicationClientset, appQueue, appResyncPeriod, config),
statusRefreshTimeout: appResyncPeriod,
forceRefreshApps: make(map[string]bool),
forceRefreshAppsMutex: &sync.Mutex{},
}
}
// Run starts the Application CRD controller.
func (ctrl *ApplicationController) Run(ctx context.Context, appWorkers int) {
defer runtime.HandleCrash()
defer ctrl.appQueue.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
}
for i := 0; i < appWorkers; i++ {
go wait.Until(ctrl.runWorker, time.Second, ctx.Done())
}
<-ctx.Done()
}
func (ctrl *ApplicationController) forceAppRefresh(appName string) {
ctrl.forceRefreshAppsMutex.Lock()
defer ctrl.forceRefreshAppsMutex.Unlock()
ctrl.forceRefreshApps[appName] = true
}
func (ctrl *ApplicationController) isRefreshForced(appName string) bool {
ctrl.forceRefreshAppsMutex.Lock()
defer ctrl.forceRefreshAppsMutex.Unlock()
_, ok := ctrl.forceRefreshApps[appName]
if ok {
delete(ctrl.forceRefreshApps, appName)
}
return ok
}
// 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)
if err != nil {
return err
}
for event := range ch {
eventObj := event.Object.(*unstructured.Unstructured)
objLabels := eventObj.GetLabels()
if objLabels == nil {
objLabels = make(map[string]string)
}
if appName, ok := objLabels[common.LabelApplicationName]; ok {
ctrl.forceAppRefresh(appName)
ctrl.appQueue.Add(ctrl.namespace + "/" + appName)
}
}
return fmt.Errorf("resource updates channel has closed")
}, fmt.Sprintf("watch app resources on %s", config.Host), ctx, watchResourcesRetryTimeout)
}
// 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)
retryUntilSucceed(func() error {
return ctrl.apiClusterService.WatchClusters(context.Background(), func(event *cluster.ClusterEvent) {
cancel, ok := watchingClusters[event.Cluster.Server]
if event.Type == watch.Deleted && ok {
cancel()
delete(watchingClusters, event.Cluster.Server)
} else if event.Type != watch.Deleted && !ok {
ctx, cancel := context.WithCancel(context.Background())
watchingClusters[event.Cluster.Server] = cancel
go ctrl.watchClusterResources(ctx, *event.Cluster)
}
})
}, "watch clusters", context.Background(), watchResourcesRetryTimeout)
<-context.Background().Done()
}
// retryUntilSucceed keep retrying given action with specified timeout until action succeed or specified context is done.
func retryUntilSucceed(action func() error, desc string, ctx context.Context, timeout time.Duration) {
ctxCompleted := false
go func() {
select {
case <-ctx.Done():
ctxCompleted = true
}
}()
for {
err := action()
if err == nil {
return
}
if err != nil {
if ctxCompleted {
log.Infof("Stop retrying %s", desc)
return
} else {
log.Warnf("Failed to %s: %v, retrying in %v", desc, err, timeout)
time.Sleep(timeout)
}
}
}
}
func (ctrl *ApplicationController) processNextItem() bool {
appKey, shutdown := ctrl.appQueue.Get()
if shutdown {
return false
}
defer ctrl.appQueue.Done(appKey)
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(appKey.(string))
if err != nil {
log.Errorf("Failed to get application '%s' from informer index: %+v", appKey, err)
return true
}
if !exists {
// This happens after app was deleted, but the work queue still had an entry for it.
return true
}
app, ok := obj.(*appv1.Application)
if !ok {
log.Warnf("Key '%s' in index is not an application", appKey)
return true
}
isForceRefreshed := ctrl.isRefreshForced(app.Name)
if isForceRefreshed || app.NeedRefreshAppStatus(ctrl.statusRefreshTimeout) {
log.Infof("Refreshing application '%s' status (force refreshed: %v)", app.Name, isForceRefreshed)
status, err := ctrl.tryRefreshAppStatus(app.DeepCopy())
if err != nil {
status = app.Status.DeepCopy()
status.ComparisonResult = appv1.ComparisonResult{
Status: appv1.ComparisonStatusError,
Error: fmt.Sprintf("Failed to get application status for application '%s': %v", app.Name, err),
ComparedTo: app.Spec.Source,
ComparedAt: metav1.Time{Time: time.Now().UTC()},
}
}
ctrl.updateAppStatus(app.Name, app.Namespace, status)
}
return true
}
func (ctrl *ApplicationController) tryRefreshAppStatus(app *appv1.Application) (*appv1.ApplicationStatus, error) {
conn, client, err := ctrl.repoClientset.NewRepositoryClient()
if err != nil {
return nil, err
}
defer util.Close(conn)
repo, err := ctrl.apiRepoService.Get(context.Background(), &apireposerver.RepoQuery{Repo: app.Spec.Source.RepoURL})
if err != nil {
// If we couldn't retrieve from the repo service, assume public repositories
repo = &appv1.Repository{
Repo: app.Spec.Source.RepoURL,
Username: "",
Password: "",
}
}
overrides := make([]*appv1.ComponentParameter, len(app.Spec.Source.ComponentParameterOverrides))
if app.Spec.Source.ComponentParameterOverrides != nil {
for i := range app.Spec.Source.ComponentParameterOverrides {
item := app.Spec.Source.ComponentParameterOverrides[i]
overrides[i] = &item
}
}
revision := app.Spec.Source.TargetRevision
manifestInfo, err := client.GenerateManifest(context.Background(), &repository.ManifestRequest{
Repo: repo,
Revision: revision,
Path: app.Spec.Source.Path,
Environment: app.Spec.Source.Environment,
AppLabel: app.Name,
ComponentParameterOverrides: overrides,
})
if err != nil {
log.Errorf("Failed to load application manifest %v", err)
return nil, err
}
targetObjs := make([]*unstructured.Unstructured, len(manifestInfo.Manifests))
for i, manifestStr := range manifestInfo.Manifests {
var obj unstructured.Unstructured
if err := json.Unmarshal([]byte(manifestStr), &obj); err != nil {
if err != nil {
return nil, err
}
}
targetObjs[i] = &obj
}
server, namespace := argoutil.ResolveServerNamespace(app.Spec.Destination, manifestInfo)
comparisonResult, err := ctrl.appComparator.CompareAppState(server, namespace, targetObjs, app)
if err != nil {
return nil, err
}
log.Infof("App %s comparison result: prev: %s. current: %s", app.Name, app.Status.ComparisonResult.Status, comparisonResult.Status)
newStatus := app.Status
newStatus.ComparisonResult = *comparisonResult
paramsReq := repository.EnvParamsRequest{
Repo: repo,
Revision: revision,
Path: app.Spec.Source.Path,
Environment: app.Spec.Source.Environment,
}
params, err := client.GetEnvParams(context.Background(), &paramsReq)
if err != nil {
return nil, err
}
newStatus.Parameters = make([]appv1.ComponentParameter, len(params.Params))
for i := range params.Params {
newStatus.Parameters[i] = *params.Params[i]
}
return &newStatus, nil
}
func (ctrl *ApplicationController) runWorker() {
for ctrl.processNextItem() {
}
}
func (ctrl *ApplicationController) updateAppStatus(appName string, namespace string, status *appv1.ApplicationStatus) {
appKey := fmt.Sprintf("%s/%s", namespace, appName)
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(appKey)
if err != nil {
log.Warnf("Failed to get application '%s' from informer index: %+v", appKey, err)
} else {
if exists {
app := obj.(*appv1.Application).DeepCopy()
app.Status = *status
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(namespace)
_, err := appClient.Update(app)
if err != nil {
log.Warnf("Error updating application: %v", err)
} else {
log.Info("Application update successful")
}
}
}
}
func newApplicationInformer(
appClientset appclientset.Interface, appQueue workqueue.RateLimitingInterface, appResyncPeriod time.Duration, config *ApplicationControllerConfig) cache.SharedIndexInformer {
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)
}
options.FieldSelector = fields.Everything().String()
labelSelector := labels.NewSelector().Add(*instanceIDReq)
options.LabelSelector = labelSelector.String()
},
)
informer := appInformerFactory.Argoproj().V1alpha1().Applications().Informer()
informer.AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
appQueue.Add(key)
}
},
UpdateFunc: func(old, new interface{}) {
key, err := cache.MetaNamespaceKeyFunc(new)
if err == nil {
appQueue.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)
}
},
},
)
return informer
}

View File

@@ -1,33 +0,0 @@
// Code generated by mockery v1.0.0
package mocks
import mock "github.com/stretchr/testify/mock"
import v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
// AppComparator is an autogenerated mock type for the AppComparator type
type AppComparator struct {
mock.Mock
}
// CompareAppState provides a mock function with given fields: appRepoPath, app
func (_m *AppComparator) CompareAppState(appRepoPath string, app *v1alpha1.Application) (*v1alpha1.ComparisonResult, error) {
ret := _m.Called(appRepoPath, app)
var r0 *v1alpha1.ComparisonResult
if rf, ok := ret.Get(0).(func(string, *v1alpha1.Application) *v1alpha1.ComparisonResult); ok {
r0 = rf(appRepoPath, app)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ComparisonResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *v1alpha1.Application) error); ok {
r1 = rf(appRepoPath, app)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@@ -0,0 +1,205 @@
package controller
import (
"context"
"encoding/json"
"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"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
type SecretController struct {
kubeClient kubernetes.Interface
secretQueue workqueue.RateLimitingInterface
secretInformer cache.SharedIndexInformer
repoClientset reposerver.Clientset
namespace string
}
func (ctrl *SecretController) Run(ctx context.Context) {
go ctrl.secretInformer.Run(ctx.Done())
if !cache.WaitForCacheSync(ctx.Done(), ctrl.secretInformer.HasSynced) {
log.Error("Timed out waiting for caches to sync")
return
}
go wait.Until(func() {
for ctrl.processSecret() {
}
}, time.Second, ctx.Done())
}
func (ctrl *SecretController) processSecret() (processNext bool) {
secretKey, shutdown := ctrl.secretQueue.Get()
if shutdown {
processNext = false
return
} else {
processNext = true
}
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
ctrl.secretQueue.Done(secretKey)
}()
obj, exists, err := ctrl.secretInformer.GetIndexer().GetByKey(secretKey.(string))
if err != nil {
log.Errorf("Failed to get secret '%s' from informer index: %+v", secretKey, err)
return
}
if !exists {
// This happens after secret was deleted, but the work queue still had an entry for it.
return
}
secret, ok := obj.(*corev1.Secret)
if !ok {
log.Warnf("Key '%s' in index is not an secret", secretKey)
return
}
if secret.Labels[common.LabelKeySecretType] == common.SecretTypeCluster {
cluster := db.SecretToCluster(secret)
ctrl.updateState(secret, ctrl.getClusterState(cluster))
} else if secret.Labels[common.LabelKeySecretType] == common.SecretTypeRepository {
repo := db.SecretToRepo(secret)
ctrl.updateState(secret, ctrl.getRepoConnectionState(repo))
}
return
}
func (ctrl *SecretController) getRepoConnectionState(repo *v1alpha1.Repository) v1alpha1.ConnectionState {
state := v1alpha1.ConnectionState{
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"})
if err == nil {
state.Status = v1alpha1.ConnectionStatusSuccessful
} else {
state.Status = v1alpha1.ConnectionStatusFailed
state.Message = err.Error()
}
return state
}
func (ctrl *SecretController) getClusterState(cluster *v1alpha1.Cluster) v1alpha1.ConnectionState {
state := v1alpha1.ConnectionState{
ModifiedAt: cluster.ConnectionState.ModifiedAt,
Status: v1alpha1.ConnectionStatusUnknown,
}
kubeClientset, err := kubernetes.NewForConfig(cluster.RESTConfig())
if err == nil {
_, err = kubeClientset.Discovery().ServerVersion()
}
if err == nil {
state.Status = v1alpha1.ConnectionStatusSuccessful
} else {
state.Status = v1alpha1.ConnectionStatusFailed
state.Message = err.Error()
}
return state
}
func (ctrl *SecretController) updateState(secret *corev1.Secret, state v1alpha1.ConnectionState) {
annotationsPatch := make(map[string]string)
for key, value := range db.AnnotationsFromConnectionState(&state) {
if secret.Annotations[key] != value {
annotationsPatch[key] = value
}
}
if len(annotationsPatch) > 0 {
annotationsPatch[common.AnnotationConnectionModifiedAt] = metav1.Now().Format(time.RFC3339)
patchData, err := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": annotationsPatch,
},
})
if err != nil {
log.Warnf("Unable to prepare secret state annotation patch: %v", err)
} else {
_, err := ctrl.kubeClient.CoreV1().Secrets(secret.Namespace).Patch(secret.Name, types.MergePatchType, patchData)
if err != nil {
log.Warnf("Unable to patch secret state annotation: %v", err)
}
}
}
}
func newSecretInformer(client kubernetes.Interface, resyncPeriod time.Duration, namespace string, secretQueue workqueue.RateLimitingInterface) cache.SharedIndexInformer {
informerFactory := informers.NewFilteredSharedInformerFactory(
client,
resyncPeriod,
namespace,
func(options *metav1.ListOptions) {
var req *labels.Requirement
req, err := labels.NewRequirement(common.LabelKeySecretType, selection.In, []string{common.SecretTypeCluster, common.SecretTypeRepository})
if err != nil {
panic(err)
}
options.FieldSelector = fields.Everything().String()
labelSelector := labels.NewSelector().Add(*req)
options.LabelSelector = labelSelector.String()
},
)
informer := informerFactory.Core().V1().Secrets().Informer()
informer.AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
secretQueue.Add(key)
}
},
UpdateFunc: func(old, new interface{}) {
key, err := cache.MetaNamespaceKeyFunc(new)
if err == nil {
secretQueue.Add(key)
}
},
},
)
return informer
}
func NewSecretController(kubeClient kubernetes.Interface, repoClientset reposerver.Clientset, resyncPeriod time.Duration, namespace string) *SecretController {
secretQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
return &SecretController{
kubeClient: kubeClient,
secretQueue: secretQueue,
secretInformer: newSecretInformer(kubeClient, resyncPeriod, namespace, secretQueue),
namespace: namespace,
repoClientset: repoClientset,
}
}

420
controller/state.go Normal file
View File

@@ -0,0 +1,420 @@
package controller
import (
"context"
"encoding/json"
"fmt"
"time"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"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"
"github.com/argoproj/argo-cd/util/diff"
kubeutil "github.com/argoproj/argo-cd/util/kube"
)
const (
maxHistoryCnt = 5
)
// AppStateManager defines methods which allow to compare application spec and actual application state.
type AppStateManager interface {
CompareAppState(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) (
*v1alpha1.ComparisonResult, *repository.ManifestResponse, []v1alpha1.ApplicationCondition, error)
SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
}
// ksonnetAppStateManager allows to compare application using KSonnet CLI
type ksonnetAppStateManager struct {
db db.ArgoDB
appclientset appclientset.Interface
repoClientset reposerver.Clientset
namespace string
}
// groupLiveObjects deduplicate list of kubernetes resources and choose correct version of resource: if resource has corresponding expected application resource then method pick
// kubernetes resource with matching version, otherwise chooses single kubernetes resource with any version
func groupLiveObjects(liveObjs []*unstructured.Unstructured, targetObjs []*unstructured.Unstructured) map[string]*unstructured.Unstructured {
targetByFullName := make(map[string]*unstructured.Unstructured)
for _, obj := range targetObjs {
targetByFullName[getResourceFullName(obj)] = obj
}
liveListByFullName := make(map[string][]*unstructured.Unstructured)
for _, obj := range liveObjs {
list := liveListByFullName[getResourceFullName(obj)]
if list == nil {
list = make([]*unstructured.Unstructured, 0)
}
list = append(list, obj)
liveListByFullName[getResourceFullName(obj)] = list
}
liveByFullName := make(map[string]*unstructured.Unstructured)
for fullName, list := range liveListByFullName {
targetObj := targetByFullName[fullName]
var liveObj *unstructured.Unstructured
if targetObj != nil {
for i := range list {
if list[i].GetAPIVersion() == targetObj.GetAPIVersion() {
liveObj = list[i]
break
}
}
} else {
liveObj = list[0]
}
if liveObj != nil {
liveByFullName[getResourceFullName(liveObj)] = liveObj
}
}
return liveByFullName
}
func (s *ksonnetAppStateManager) 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 {
return nil, nil, err
}
defer util.Close(conn)
if revision == "" {
revision = app.Spec.Source.TargetRevision
}
// Decide what overrides to compare with.
var mfReqOverrides []*v1alpha1.ComponentParameter
if overrides != nil {
// If overrides is supplied, use that
mfReqOverrides = make([]*v1alpha1.ComponentParameter, len(overrides))
for i := range overrides {
item := overrides[i]
mfReqOverrides[i] = &item
}
} else {
// Otherwise, use the overrides in the app spec
mfReqOverrides = make([]*v1alpha1.ComponentParameter, len(app.Spec.Source.ComponentParameterOverrides))
for i := range app.Spec.Source.ComponentParameterOverrides {
item := app.Spec.Source.ComponentParameterOverrides[i]
mfReqOverrides[i] = &item
}
}
manifestInfo, err := repoClient.GenerateManifest(context.Background(), &repository.ManifestRequest{
Repo: repo,
Environment: app.Spec.Source.Environment,
Path: app.Spec.Source.Path,
Revision: revision,
ComponentParameterOverrides: mfReqOverrides,
AppLabel: app.Name,
})
if err != nil {
return nil, nil, err
}
targetObjs := make([]*unstructured.Unstructured, 0)
for _, manifest := range manifestInfo.Manifests {
obj, err := v1alpha1.UnmarshalToUnstructured(manifest)
if err != nil {
return nil, nil, err
}
if isHook(obj) {
continue
}
targetObjs = append(targetObjs, obj)
}
return targetObjs, manifestInfo, nil
}
func (s *ksonnetAppStateManager) 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
clst, err := s.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err != nil {
return nil, nil, err
}
restConfig := clst.RESTConfig()
// Retrieve the live versions of the objects. exclude any hook objects
labeledObjs, err := kubeutil.GetResourcesWithLabel(restConfig, app.Spec.Destination.Namespace, common.LabelApplicationName, app.Name)
if err != nil {
return nil, nil, err
}
liveObjs := make([]*unstructured.Unstructured, 0)
for _, obj := range labeledObjs {
if isHook(obj) {
continue
}
liveObjs = append(liveObjs, obj)
}
liveObjByFullName := groupLiveObjects(liveObjs, targetObjs)
controlledLiveObj := make([]*unstructured.Unstructured, len(targetObjs))
// Move live resources which have corresponding target object to controlledLiveObj
dynClientPool := dynamic.NewDynamicClientPool(restConfig)
disco, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
return nil, nil, err
}
for i, targetObj := range targetObjs {
fullName := getResourceFullName(targetObj)
liveObj := liveObjByFullName[fullName]
if liveObj == nil && targetObj.GetName() != "" {
// If we get here, it indicates we did not find the live resource when querying using
// our app label. However, it is possible that the resource was created/modified outside
// 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
}
}
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) (
*v1alpha1.ComparisonResult, *repository.ManifestResponse, []v1alpha1.ApplicationCondition, error) {
failedToLoadObjs := false
conditions := make([]v1alpha1.ApplicationCondition, 0)
targetObjs, manifestInfo, err := s.getTargetObjs(app, revision, overrides)
if err != nil {
targetObjs = make([]*unstructured.Unstructured, 0)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
failedToLoadObjs = true
}
controlledLiveObj, liveObjByFullName, err := s.getLiveObjs(app, targetObjs)
if err != nil {
controlledLiveObj = make([]*unstructured.Unstructured, len(targetObjs))
liveObjByFullName = make(map[string]*unstructured.Unstructured)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
failedToLoadObjs = true
}
for _, liveObj := range controlledLiveObj {
if liveObj != nil && liveObj.GetLabels() != nil {
if appLabelVal, ok := liveObj.GetLabels()[common.LabelApplicationName]; ok && appLabelVal != "" && appLabelVal != app.Name {
conditions = append(conditions, v1alpha1.ApplicationCondition{
Type: v1alpha1.ApplicationConditionSharedResourceWarning,
Message: fmt.Sprintf("Resource %s/%s is controller by applications '%s' and '%s'", liveObj.GetKind(), liveObj.GetName(), app.Name, appLabelVal),
})
}
}
}
// Move root level live resources to controlledLiveObj and add nil to targetObjs to indicate that target object is missing
for fullName := range liveObjByFullName {
liveObj := liveObjByFullName[fullName]
if !hasParent(liveObj) {
targetObjs = append(targetObjs, nil)
controlledLiveObj = append(controlledLiveObj, liveObj)
}
}
log.Infof("Comparing app %s state in cluster %s (namespace: %s)", app.ObjectMeta.Name, app.Spec.Destination.Server, app.Spec.Destination.Namespace)
// Do the actual comparison
diffResults, err := diff.DiffArray(targetObjs, controlledLiveObj)
if err != nil {
return nil, nil, nil, err
}
comparisonStatus := v1alpha1.ComparisonStatusSynced
resources := make([]v1alpha1.ResourceState, len(targetObjs))
for i := 0; i < len(targetObjs); i++ {
resState := v1alpha1.ResourceState{
ChildLiveResources: make([]v1alpha1.ResourceNode, 0),
}
diffResult := diffResults.Diffs[i]
if diffResult.Modified {
// Set resource state to 'OutOfSync' since target and corresponding live resource are different
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
resState.Status = v1alpha1.ComparisonStatusSynced
}
if targetObjs[i] == nil {
resState.TargetState = "null"
// Set resource state to 'OutOfSync' since target resource is missing and live resource is unexpected
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
targetObjBytes, err := json.Marshal(targetObjs[i].Object)
if err != nil {
return nil, nil, nil, err
}
resState.TargetState = string(targetObjBytes)
}
if controlledLiveObj[i] == nil {
resState.LiveState = "null"
// Set resource state to 'OutOfSync' since target resource present but corresponding live resource is missing
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
liveObjBytes, err := json.Marshal(controlledLiveObj[i].Object)
if err != nil {
return nil, nil, nil, err
}
resState.LiveState = string(liveObjBytes)
}
resources[i] = resState
}
for i, resource := range resources {
liveResource := controlledLiveObj[i]
if liveResource != nil {
childResources, err := getChildren(liveResource, liveObjByFullName)
if err != nil {
return nil, nil, nil, err
}
resource.ChildLiveResources = childResources
resources[i] = resource
}
}
if failedToLoadObjs {
comparisonStatus = v1alpha1.ComparisonStatusUnknown
}
compResult := v1alpha1.ComparisonResult{
ComparedTo: app.Spec.Source,
ComparedAt: metav1.Time{Time: time.Now().UTC()},
Resources: resources,
Status: comparisonStatus,
}
return &compResult, manifestInfo, conditions, nil
}
func hasParent(obj *unstructured.Unstructured) bool {
// TODO: remove special case after Service and Endpoint get explicit relationship ( https://github.com/kubernetes/kubernetes/issues/28483 )
return obj.GetKind() == kubeutil.EndpointsKind || metav1.GetControllerOf(obj) != nil
}
func isControlledBy(obj *unstructured.Unstructured, parent *unstructured.Unstructured) bool {
// TODO: remove special case after Service and Endpoint get explicit relationship ( https://github.com/kubernetes/kubernetes/issues/28483 )
if obj.GetKind() == kubeutil.EndpointsKind && parent.GetKind() == kubeutil.ServiceKind {
return obj.GetName() == parent.GetName()
}
return metav1.IsControlledBy(obj, parent)
}
func getChildren(parent *unstructured.Unstructured, liveObjByFullName map[string]*unstructured.Unstructured) ([]v1alpha1.ResourceNode, error) {
children := make([]v1alpha1.ResourceNode, 0)
for fullName, obj := range liveObjByFullName {
if isControlledBy(obj, parent) {
delete(liveObjByFullName, fullName)
childResource := v1alpha1.ResourceNode{}
json, err := json.Marshal(obj)
if err != nil {
return nil, err
}
childResource.State = string(json)
childResourceChildren, err := getChildren(obj, liveObjByFullName)
if err != nil {
return nil, err
}
childResource.Children = childResourceChildren
children = append(children, childResource)
}
}
return children, nil
}
func getResourceFullName(obj *unstructured.Unstructured) string {
return fmt.Sprintf("%s:%s", obj.GetKind(), obj.GetName())
}
func (s *ksonnetAppStateManager) 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
repo = &v1alpha1.Repository{Repo: repoURL}
}
return repo
}
func (s *ksonnetAppStateManager) persistDeploymentInfo(
app *v1alpha1.Application, revision string, envParams []*v1alpha1.ComponentParameter, overrides *[]v1alpha1.ComponentParameter) error {
params := make([]v1alpha1.ComponentParameter, len(envParams))
for i := range envParams {
param := *envParams[i]
params[i] = param
}
var nextID int64 = 0
if len(app.Status.History) > 0 {
nextID = app.Status.History[len(app.Status.History)-1].ID + 1
}
history := append(app.Status.History, v1alpha1.DeploymentInfo{
ComponentParameterOverrides: app.Spec.Source.ComponentParameterOverrides,
Revision: revision,
Params: params,
DeployedAt: metav1.NewTime(time.Now().UTC()),
ID: nextID,
})
if len(history) > maxHistoryCnt {
history = history[1 : maxHistoryCnt+1]
}
patch, err := json.Marshal(map[string]map[string][]v1alpha1.DeploymentInfo{
"status": {
"history": history,
},
})
if err != nil {
return err
}
_, err = s.appclientset.ArgoprojV1alpha1().Applications(s.namespace).Patch(app.Name, types.MergePatchType, patch)
return err
}
// NewAppStateManager creates new instance of Ksonnet app comparator
func NewAppStateManager(
db db.ArgoDB,
appclientset appclientset.Interface,
repoClientset reposerver.Clientset,
namespace string,
) AppStateManager {
return &ksonnetAppStateManager{
db: db,
appclientset: appclientset,
repoClientset: repoClientset,
namespace: namespace,
}
}

841
controller/sync.go Normal file
View File

@@ -0,0 +1,841 @@
package controller
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"sync"
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
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/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/apis/batch"
"github.com/argoproj/argo-cd/common"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/kube"
)
type syncContext struct {
appName string
comparison *appv1.ComparisonResult
config *rest.Config
dynClientPool dynamic.ClientPool
disco *discovery.DiscoveryClient
namespace string
syncOp *appv1.SyncOperation
syncRes *appv1.SyncOperationResult
opState *appv1.OperationState
manifestInfo *repository.ManifestResponse
log *log.Entry
// lock to protect concurrent updates of the result list
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).
// 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
// revision that we initially started with.
var revision string
var syncOp appv1.SyncOperation
var syncRes *appv1.SyncOperationResult
var overrides []appv1.ComponentParameter
if state.Operation.Sync != nil {
syncOp = *state.Operation.Sync
if state.SyncResult != nil {
syncRes = state.SyncResult
revision = state.SyncResult.Revision
} else {
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"
return
}
if revision == "" {
// if we get here, it means we did not remember a commit SHA which we should be syncing to.
// This typically indicates we are just about to begin a brand new sync/rollback operation.
// 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
state.Message = err.Error()
return
}
errConditions := make([]appv1.ApplicationCondition, 0)
for i := range conditions {
if conditions[i].IsError() {
errConditions = append(errConditions, conditions[i])
}
}
if len(errConditions) > 0 {
state.Phase = appv1.OperationError
state.Message = argo.FormatAppConditions(errConditions)
return
}
// We now have a concrete commit SHA. Set this in the sync result revision so that we remember
// what we should be syncing to when resuming operations.
syncRes.Revision = manifestInfo.Revision
clst, err := s.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err != nil {
state.Phase = appv1.OperationError
state.Message = err.Error()
return
}
restConfig := clst.RESTConfig()
dynClientPool := dynamic.NewDynamicClientPool(restConfig)
disco, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
state.Phase = appv1.OperationError
state.Message = fmt.Sprintf("Failed to initialize dynamic client: %v", err)
return
}
syncCtx := syncContext{
appName: app.Name,
comparison: comparison,
config: restConfig,
dynClientPool: dynClientPool,
disco: disco,
namespace: app.Spec.Destination.Namespace,
syncOp: &syncOp,
syncRes: syncRes,
opState: state,
manifestInfo: manifestInfo,
log: log.WithFields(log.Fields{"application": app.Name}),
}
if state.Phase == appv1.OperationTerminating {
syncCtx.terminate()
} else {
syncCtx.sync()
}
if !syncOp.DryRun && syncCtx.opState.Phase.Successful() {
err := s.persistDeploymentInfo(app, manifestInfo.Revision, manifestInfo.Params, nil)
if err != nil {
state.Phase = appv1.OperationError
state.Message = fmt.Sprintf("failed to record sync to history: %v", err)
}
}
}
// syncTask holds the live and target object. At least one should be non-nil. A targetObj of nil
// indicates the live object needs to be pruned. A liveObj of nil indicates the object has yet to
// be deployed
type syncTask struct {
liveObj *unstructured.Unstructured
targetObj *unstructured.Unstructured
}
// sync has performs the actual apply or hook based sync
func (sc *syncContext) sync() {
syncTasks, successful := sc.generateSyncTasks()
if !successful {
return
}
// Perform a `kubectl apply --dry-run` against all the manifests. This will detect most (but
// not all) validation issues with the user's manifests (e.g. will detect syntax issues, but
// will not not detect if they are mutating immutable fields). If anything fails, we will refuse
// to perform the sync.
if !sc.startedPreSyncPhase() {
// Optimization: we only wish to do this once per operation, performing additional dry-runs
// is harmless, but redundant. The indicator we use to detect if we have already performed
// the dry-run for this operation, is if the resource or hook list is empty.
if !sc.doApplySync(syncTasks, true, false, sc.syncOp.DryRun) {
sc.setOperationPhase(appv1.OperationFailed, "one or more objects failed to apply (dry run)")
return
}
if sc.syncOp.DryRun {
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced (dry run)")
return
}
}
// All objects passed a `kubectl apply --dry-run`, so we are now ready to actually perform the sync.
if sc.syncOp.SyncStrategy == nil {
// default sync strategy to hook if no strategy
sc.syncOp.SyncStrategy = &appv1.SyncStrategy{Hook: &appv1.SyncStrategyHook{}}
}
if sc.syncOp.SyncStrategy.Apply != nil {
if !sc.startedSyncPhase() {
if !sc.doApplySync(syncTasks, false, sc.syncOp.SyncStrategy.Apply.Force, true) {
sc.setOperationPhase(appv1.OperationFailed, "one or more objects failed to apply")
return
}
// If apply was successful, return here and force an app refresh. This is so the app
// will become requeued into the workqueue, to force a new sync/health assessment before
// marking the operation as completed
return
}
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced")
} else if sc.syncOp.SyncStrategy.Hook != nil {
hooks, err := sc.getHooks()
if err != nil {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("failed to generate hooks resources: %v", err))
return
}
sc.doHookSync(syncTasks, hooks)
} else {
sc.setOperationPhase(appv1.OperationFailed, "Unknown sync strategy")
return
}
}
func (sc *syncContext) forceAppRefresh() {
sc.comparison.ComparedAt = metav1.Time{}
}
// generateSyncTasks() generates the list of sync tasks we will be performing during this sync.
func (sc *syncContext) generateSyncTasks() ([]syncTask, bool) {
syncTasks := make([]syncTask, 0)
for _, resourceState := range sc.comparison.Resources {
liveObj, err := resourceState.LiveObject()
if err != nil {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("Failed to unmarshal live object: %v", err))
return nil, false
}
targetObj, err := resourceState.TargetObject()
if err != nil {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("Failed to unmarshal target object: %v", err))
return nil, false
}
syncTask := syncTask{
liveObj: liveObj,
targetObj: targetObj,
}
syncTasks = append(syncTasks, syncTask)
}
return syncTasks, true
}
// startedPreSyncPhase detects if we already started the PreSync stage of a sync operation.
// This is equal to if we have anything in our resource or hook list
func (sc *syncContext) startedPreSyncPhase() bool {
if len(sc.syncRes.Resources) > 0 {
return true
}
if len(sc.syncRes.Hooks) > 0 {
return true
}
return false
}
// startedSyncPhase detects if we have already started the Sync stage of a sync operation.
// This is equal to if the resource list is non-empty, or we we see Sync/PostSync hooks
func (sc *syncContext) startedSyncPhase() bool {
if len(sc.syncRes.Resources) > 0 {
return true
}
for _, hookStatus := range sc.syncRes.Hooks {
if hookStatus.Type == appv1.HookTypeSync || hookStatus.Type == appv1.HookTypePostSync {
return true
}
}
return false
}
// startedPostSyncPhase detects if we have already started the PostSync stage. This is equal to if
// we see any PostSync hooks
func (sc *syncContext) startedPostSyncPhase() bool {
for _, hookStatus := range sc.syncRes.Hooks {
if hookStatus.Type == appv1.HookTypePostSync {
return true
}
}
return false
}
func (sc *syncContext) setOperationPhase(phase appv1.OperationPhase, message string) {
if sc.opState.Phase != phase || sc.opState.Message != message {
sc.log.Infof("Updating operation state. phase: %s -> %s, message: '%s' -> '%s'", sc.opState.Phase, phase, sc.opState.Message, message)
}
sc.opState.Phase = phase
sc.opState.Message = message
}
// applyObject performs a `kubectl apply` of a single resource
func (sc *syncContext) applyObject(targetObj *unstructured.Unstructured, dryRun bool, force bool) appv1.ResourceDetails {
resDetails := appv1.ResourceDetails{
Name: targetObj.GetName(),
Kind: targetObj.GetKind(),
Namespace: sc.namespace,
}
message, err := kube.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
}
// pruneObject deletes the object if both prune is true and dryRun is false. Otherwise appropriate message
func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dryRun bool) appv1.ResourceDetails {
resDetails := appv1.ResourceDetails{
Name: liveObj.GetName(),
Kind: liveObj.GetKind(),
Namespace: liveObj.GetNamespace(),
}
if prune {
if dryRun {
resDetails.Message = "pruned (dry run)"
resDetails.Status = appv1.ResourceDetailsSyncedAndPruned
} else {
err := kube.DeleteResource(sc.config, liveObj, sc.namespace)
if err != nil {
resDetails.Message = err.Error()
resDetails.Status = appv1.ResourceDetailsSyncFailed
} else {
resDetails.Message = "pruned"
resDetails.Status = appv1.ResourceDetailsSyncedAndPruned
}
}
} else {
resDetails.Message = "ignored (requires pruning)"
resDetails.Status = appv1.ResourceDetailsPruningRequired
}
return resDetails
}
// 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 wg sync.WaitGroup
for _, task := range syncTasks {
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)
}
if !resDetails.Status.Successful() {
syncSuccessful = false
}
if update || !resDetails.Status.Successful() {
sc.setResourceDetails(&resDetails)
}
}(task)
}
wg.Wait()
return syncSuccessful
}
// doHookSync initiates (or continues) a hook-based sync. This method will be invoked when there may
// already be in-flight (potentially incomplete) jobs/workflows, and should be idempotent.
func (sc *syncContext) doHookSync(syncTasks []syncTask, hooks []*unstructured.Unstructured) {
// 1. Run PreSync hooks
if !sc.runHooks(hooks, appv1.HookTypePreSync) {
return
}
// 2. Run Sync hooks (e.g. blue-green sync workflow)
// Before performing Sync hooks, apply any normal manifests which aren't annotated with a hook.
// We only want to do this once per operation.
shouldContinue := true
if !sc.startedSyncPhase() {
if !sc.syncNonHookTasks(syncTasks) {
sc.setOperationPhase(appv1.OperationFailed, "one or more objects failed to apply")
return
}
shouldContinue = false
}
if !sc.runHooks(hooks, appv1.HookTypeSync) {
shouldContinue = false
}
if !shouldContinue {
return
}
// 3. Run PostSync hooks
// Before running PostSync hooks, we want to make rollout is complete (app is healthy). If we
// 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)
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))
return
}
if healthState.Status != appv1.HealthStatusHealthy {
sc.setOperationPhase(appv1.OperationRunning, fmt.Sprintf("waiting for %s state to run %s hooks (current health: %s)", appv1.HealthStatusHealthy, appv1.HookTypePostSync, healthState.Status))
return
}
}
if !sc.runHooks(hooks, appv1.HookTypePostSync) {
return
}
// if we get here, all hooks successfully completed
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced")
}
// getHooks returns all hooks, or ones of the specific type(s)
func (sc *syncContext) getHooks(hookTypes ...appv1.HookType) ([]*unstructured.Unstructured, error) {
var hooks []*unstructured.Unstructured
for _, manifest := range sc.manifestInfo.Manifests {
var hook unstructured.Unstructured
err := json.Unmarshal([]byte(manifest), &hook)
if err != nil {
return nil, err
}
if !isHook(&hook) {
continue
}
if len(hookTypes) > 0 {
match := false
for _, desiredType := range hookTypes {
if isHookType(&hook, desiredType) {
match = true
break
}
}
if !match {
continue
}
}
hooks = append(hooks, &hook)
}
return hooks, nil
}
// runHooks iterates & filters the target manifests for resources of the specified hook type, then
// creates the resource. Updates the sc.opRes.hooks with the current status. Returns whether or not
// we should continue to the next hook phase.
func (sc *syncContext) runHooks(hooks []*unstructured.Unstructured, hookType appv1.HookType) bool {
shouldContinue := true
for _, hook := range hooks {
if hookType == appv1.HookTypeSync && isHookType(hook, appv1.HookTypeSkip) {
// If we get here, we are invoking all sync hooks and reached a resource that is
// annotated with the Skip hook. This will update the resource details to indicate it
// was skipped due to annotation
sc.setResourceDetails(&appv1.ResourceDetails{
Name: hook.GetName(),
Kind: hook.GetKind(),
Namespace: sc.namespace,
Message: "Skipped",
})
continue
}
if !isHookType(hook, hookType) {
continue
}
updated, err := sc.runHook(hook, hookType)
if err != nil {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("%s hook error: %v", hookType, err))
return false
}
if updated {
// If the result of running a hook, caused us to modify hook resource state, we should
// not proceed to the next hook phase. This is because before proceeding to the next
// phase, we want a full health assessment to happen. By returning early, we allow
// the application to get requeued into the controller workqueue, and on the next
// process iteration, a new CompareAppState() will be performed to get the most
// up-to-date live state. This enables us to accurately wait for an application to
// become Healthy before proceeding to run PostSync tasks.
shouldContinue = false
}
}
if !shouldContinue {
sc.log.Infof("Stopping after %s phase due to modifications to hook resource state", hookType)
return false
}
completed, successful := areHooksCompletedSuccessful(hookType, sc.syncRes.Hooks)
if !completed {
return false
}
if !successful {
sc.setOperationPhase(appv1.OperationFailed, fmt.Sprintf("%s hook failed", hookType))
return false
}
return true
}
// runHook runs the supplied hook and updates the hook status. Returns true if the result of
// invoking this method resulted in changes to any hook status
func (sc *syncContext) runHook(hook *unstructured.Unstructured, hookType appv1.HookType) (bool, error) {
// Hook resources names are deterministic, whether they are defined by the user (metadata.name),
// or formulated at the time of the operation (metadata.generateName). If user specifies
// metadata.generateName, then we will generate a formulated metadata.name before submission.
if hook.GetName() == "" {
postfix := strings.ToLower(fmt.Sprintf("%s-%s-%d", sc.syncRes.Revision[0:7], hookType, sc.opState.StartedAt.UTC().Unix()))
generatedName := hook.GetGenerateName()
hook = hook.DeepCopy()
hook.SetName(fmt.Sprintf("%s%s", generatedName, postfix))
}
// Check our hook statuses to see if we already completed this hook.
// If so, this method is a noop
prevStatus := sc.getHookStatus(hook, hookType)
if prevStatus != nil && prevStatus.Status.Completed() {
return false, nil
}
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)
var liveObj *unstructured.Unstructured
existing, err := resIf.Get(hook.GetName(), metav1.GetOptions{})
if err != nil {
if !apierr.IsNotFound(err) {
return false, fmt.Errorf("Failed to get status of %s hook %s '%s': %v", hookType, gvk, hook.GetName(), err)
}
hook = hook.DeepCopy()
err = kube.SetLabel(hook, common.LabelApplicationName, sc.appName)
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)
if err != nil {
return false, fmt.Errorf("Failed to create %s hook %s '%s': %v", hookType, gvk, hook.GetName(), err)
}
created, err := resIf.Get(hook.GetName(), metav1.GetOptions{})
if err != nil {
return true, fmt.Errorf("Failed to get status of %s hook %s '%s': %v", hookType, gvk, hook.GetName(), err)
}
sc.log.Infof("%s hook %s '%s' created", hookType, gvk, created.GetName())
sc.setOperationPhase(appv1.OperationRunning, fmt.Sprintf("running %s hooks", hookType))
liveObj = created
} else {
liveObj = existing
}
hookStatus := newHookStatus(liveObj, hookType)
if hookStatus.Status.Completed() {
if enforceDeletePolicy(hook, hookStatus.Status) {
err = sc.deleteHook(hook.GetName(), hook.GetKind(), hook.GetAPIVersion())
if err != nil {
hookStatus.Status = appv1.OperationFailed
hookStatus.Message = fmt.Sprintf("failed to delete %s hook: %v", hookStatus.Status, err)
}
}
}
return sc.updateHookStatus(hookStatus), nil
}
// enforceDeletePolicy examines the hook deletion policy of a object and deletes it based on the status
func enforceDeletePolicy(hook *unstructured.Unstructured, phase appv1.OperationPhase) bool {
annotations := hook.GetAnnotations()
if annotations == nil {
return false
}
deletePolicies := strings.Split(annotations[common.AnnotationHookDeletePolicy], ",")
for _, dp := range deletePolicies {
policy := appv1.HookDeletePolicy(strings.TrimSpace(dp))
if policy == appv1.HookDeletePolicyHookSucceeded && phase == appv1.OperationSucceeded {
return true
}
if policy == appv1.HookDeletePolicyHookFailed && phase == appv1.OperationFailed {
return true
}
}
return false
}
// isHookType tells whether or not the supplied object is a hook of the specified type
func isHookType(hook *unstructured.Unstructured, hookType appv1.HookType) bool {
annotations := hook.GetAnnotations()
if annotations == nil {
return false
}
resHookTypes := strings.Split(annotations[common.AnnotationHook], ",")
for _, ht := range resHookTypes {
if string(hookType) == strings.TrimSpace(ht) {
return true
}
}
return false
}
// isHook tells whether or not the supplied object is a application lifecycle hook, or a normal,
// synced application resource
func isHook(obj *unstructured.Unstructured) bool {
annotations := obj.GetAnnotations()
if annotations == nil {
return false
}
resHookTypes := strings.Split(annotations[common.AnnotationHook], ",")
for _, hookType := range resHookTypes {
hookType = strings.TrimSpace(hookType)
switch appv1.HookType(hookType) {
case appv1.HookTypePreSync, appv1.HookTypeSync, appv1.HookTypePostSync:
return true
}
}
return false
}
// syncNonHookTasks syncs or prunes the objects that are not handled by hooks using an apply sync.
// returns true if the sync was successful
func (sc *syncContext) syncNonHookTasks(syncTasks []syncTask) bool {
var nonHookTasks []syncTask
for _, task := range syncTasks {
if task.targetObj == nil {
nonHookTasks = append(nonHookTasks, task)
} else {
annotations := task.targetObj.GetAnnotations()
if annotations != nil && annotations[common.AnnotationHook] != "" {
// we are doing a hook sync and this resource is annotated with a hook annotation
continue
}
// if we get here, this resource does not have any hook annotation so we
// should perform an `kubectl apply`
nonHookTasks = append(nonHookTasks, task)
}
}
return sc.doApplySync(nonHookTasks, false, sc.syncOp.SyncStrategy.Hook.Force, true)
}
// setResourceDetails sets a resource details in the SyncResult.Resources list
func (sc *syncContext) setResourceDetails(details *appv1.ResourceDetails) {
sc.lock.Lock()
defer sc.lock.Unlock()
for i, res := range sc.syncRes.Resources {
if res.Kind == details.Kind && res.Name == details.Name {
// update existing value
if res.Status != details.Status {
sc.log.Infof("updated resource %s/%s status: %s -> %s", res.Kind, res.Name, res.Status, details.Status)
}
if res.Message != details.Message {
sc.log.Infof("updated resource %s/%s message: %s -> %s", res.Kind, res.Name, res.Message, details.Message)
}
sc.syncRes.Resources[i] = details
return
}
}
sc.log.Infof("added resource %s/%s status: %s, message: %s", details.Kind, details.Name, details.Status, details.Message)
sc.syncRes.Resources = append(sc.syncRes.Resources, details)
}
func (sc *syncContext) getHookStatus(hookObj *unstructured.Unstructured, hookType appv1.HookType) *appv1.HookStatus {
for _, hr := range sc.syncRes.Hooks {
if hr.Name == hookObj.GetName() && hr.Kind == hookObj.GetKind() && hr.Type == hookType {
return hr
}
}
return nil
}
func newHookStatus(hook *unstructured.Unstructured, hookType appv1.HookType) appv1.HookStatus {
hookStatus := appv1.HookStatus{
Name: hook.GetName(),
Kind: hook.GetKind(),
APIVersion: hook.GetAPIVersion(),
Type: hookType,
}
switch hookStatus.Kind {
case "Job":
var job batch.Job
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &job)
if err != nil {
hookStatus.Status = appv1.OperationError
hookStatus.Message = err.Error()
} else {
failed := false
var failMsg string
complete := false
var message string
for _, condition := range job.Status.Conditions {
switch condition.Type {
case batch.JobFailed:
failed = true
complete = true
failMsg = condition.Message
case batch.JobComplete:
complete = true
message = condition.Message
}
}
if !complete {
hookStatus.Status = appv1.OperationRunning
hookStatus.Message = message
} else if failed {
hookStatus.Status = appv1.OperationFailed
hookStatus.Message = failMsg
} else {
hookStatus.Status = appv1.OperationSucceeded
hookStatus.Message = message
}
}
case "Workflow":
var wf wfv1.Workflow
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &wf)
if err != nil {
hookStatus.Status = appv1.OperationError
hookStatus.Message = err.Error()
} else {
switch wf.Status.Phase {
case wfv1.NodeRunning:
hookStatus.Status = appv1.OperationRunning
case wfv1.NodeSucceeded:
hookStatus.Status = appv1.OperationSucceeded
case wfv1.NodeFailed:
hookStatus.Status = appv1.OperationFailed
case wfv1.NodeError:
hookStatus.Status = appv1.OperationError
}
hookStatus.Message = wf.Status.Message
}
default:
hookStatus.Status = appv1.OperationSucceeded
hookStatus.Message = fmt.Sprintf("%s created", hook.GetName())
}
return hookStatus
}
// updateHookStatus updates the status of a hook. Returns true if the hook was modified
func (sc *syncContext) updateHookStatus(hookStatus appv1.HookStatus) bool {
sc.lock.Lock()
defer sc.lock.Unlock()
for i, prev := range sc.syncRes.Hooks {
if prev.Name == hookStatus.Name && prev.Kind == hookStatus.Kind && prev.Type == hookStatus.Type {
if reflect.DeepEqual(prev, hookStatus) {
return false
}
if prev.Status != hookStatus.Status {
sc.log.Infof("Hook %s %s/%s status: %s -> %s", hookStatus.Type, prev.Kind, prev.Name, prev.Status, hookStatus.Status)
}
if prev.Message != hookStatus.Message {
sc.log.Infof("Hook %s %s/%s message: '%s' -> '%s'", hookStatus.Type, prev.Kind, prev.Name, prev.Message, hookStatus.Message)
}
sc.syncRes.Hooks[i] = &hookStatus
return true
}
}
sc.syncRes.Hooks = append(sc.syncRes.Hooks, &hookStatus)
sc.log.Infof("Set new hook %s %s/%s. status: %s, message: %s", hookStatus.Type, hookStatus.Kind, hookStatus.Name, hookStatus.Status, hookStatus.Message)
return true
}
// areHooksCompletedSuccessful checks if all the hooks of the specified type are completed and successful
func areHooksCompletedSuccessful(hookType appv1.HookType, hookStatuses []*appv1.HookStatus) (bool, bool) {
isSuccessful := true
for _, hookStatus := range hookStatuses {
if hookStatus.Type != hookType {
continue
}
if !hookStatus.Status.Completed() {
return false, false
}
if !hookStatus.Status.Successful() {
isSuccessful = false
}
}
return true, isSuccessful
}
// terminate looks for any running jobs/workflow hooks and deletes the resource
func (sc *syncContext) terminate() {
terminateSuccessful := true
for _, hookStatus := range sc.syncRes.Hooks {
if hookStatus.Status.Completed() {
continue
}
switch hookStatus.Kind {
case "Job", "Workflow":
hookStatus.Status = appv1.OperationFailed
err := sc.deleteHook(hookStatus.Name, hookStatus.Kind, hookStatus.APIVersion)
if err != nil {
hookStatus.Message = fmt.Sprintf("Failed to delete %s hook %s/%s: %v", hookStatus.Type, hookStatus.Kind, hookStatus.Name, err)
terminateSuccessful = false
} else {
hookStatus.Message = fmt.Sprintf("Deleted %s hook %s/%s", hookStatus.Type, hookStatus.Kind, hookStatus.Name)
}
sc.updateHookStatus(*hookStatus)
}
}
if terminateSuccessful {
sc.setOperationPhase(appv1.OperationFailed, "Operation terminated")
} else {
sc.setOperationPhase(appv1.OperationError, "Operation termination had errors")
}
}
func (sc *syncContext) deleteHook(name, kind, apiVersion string) error {
groupVersion := strings.Split(apiVersion, "/")
if len(groupVersion) != 2 {
return fmt.Errorf("Failed to terminate app. Unrecognized group/version: %s", apiVersion)
}
gvk := schema.GroupVersionKind{
Group: groupVersion[0],
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)
return resIf.Delete(name, &metav1.DeleteOptions{})
}

26
controller/sync_test.go Normal file
View File

@@ -0,0 +1,26 @@
package controller
import (
"testing"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/rest"
)
func newTestSyncCtx() *syncContext {
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"}),
}
}
func TestRunWorkflows(t *testing.T) {
// syncCtx := newTestSyncCtx()
// syncCtx.doWorkflowSync(nil, nil)
}

14
docs/README.md Normal file
View File

@@ -0,0 +1,14 @@
# ArgoCD Documentation
## [Getting Started](getting_started.md)
## Concepts
* [Architecture](architecture.md)
* [Tracking Strategies](tracking_strategies.md)
## Features
* [Resource Health](health.md)
* [Resource Hooks](resource_hooks.md)
* [Single Sign On](sso.md)
* [Webhooks](webhook.md)
* [RBAC](rbac.md)

View File

@@ -9,9 +9,10 @@
The API server is a gRPC/REST server which exposes the API consumed by the Web UI, CLI, and CI/CD
systems. It has the following responsibilities:
* application management and status reporting
* invoking of application actions (e.g. manual sync, user-defined actions)
* invoking of application operations (e.g. sync, rollback, user-defined actions)
* repository and cluster credential management (stored as K8s secrets)
* authentication and RBAC enforcement, with eventual integration with external identity providers
* authentication and auth delegation to external identity providers
* RBAC enforcement
* listener/forwarder for git webhook events
### Repository Server

BIN
docs/argocd-ui.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/assets/create_app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/assets/select_app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/assets/select_env.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/assets/select_repo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -1,81 +1,173 @@
# Argo CD Getting Started
# ArgoCD Getting Started
An example Ksonnet guestbook application is provided to demonstrates how Argo CD works.
An example Ksonnet guestbook application is provided to demonstrates how ArgoCD works.
## Requirements
* Installed [minikube](https://github.com/kubernetes/minikube#installation)
* Installed the [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line tool
* Installed [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line tool
* Have a [kubeconfig](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) file (default location is `~/.kube/config`).
## 1. Download Argo CD
Download the latest Argo CD version
## 1. Install ArgoCD
```
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.3.1/argocd-darwin-amd64
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/master/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
```
$ kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
```
## 2. Download ArgoCD CLI
Download the latest ArgoCD version:
```
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.6.0/argocd-darwin-amd64
chmod +x /usr/local/bin/argocd
```
## 3. Open access to ArgoCD API server
## 2. Install Argo CD
```
argocd install
```
This will create a new namespace, `argocd`, where Argo CD services and application resources will live.
## 3. Open access to Argo CD API server
By default, the Argo CD API server is not exposed with an external IP. To expose the API server,
change service type to `LoadBalancer`:
By default, the ArgoCD API server is not exposed with an external IP. To expose the API server,
change the service type to `LoadBalancer`:
```
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
```
# 4. Login to the server from the CLI
## 4. Login to the server from the CLI
Login with using 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:
```
argocd login $(minikube service argocd-server -n argocd --url | cut -d'/' -f 3)
kubectl get pods -n argocd -l app=argocd-server -o name | cut -d'/' -f 2
```
Now, the Argo CD cli is configured to talk to API server and you can deploy your first application.
## 5. Connect and deploy the Guestbook application
1. Register the minikube cluster to Argo CD:
Using the above password, login to ArgoCD's external IP:
On Minikube:
```
argocd cluster add minikube
argocd login $(minikube service argocd-server -n argocd --url | cut -d'/' -f 3) --name minikube
```
The `argocd cluster add CONTEXT` command installs an `argocd-manager` ServiceAccount and ClusterRole into
the cluster associated with the supplied kubectl context. Argo CD then uses the associated service account
token to perform its required management tasks (i.e. deploy/monitoring).
2. Add the guestbook application and github repository containing the Guestbook application
Other clusters:
```
argocd app create --name guestbook --repo https://github.com/argoproj/argo-cd.git --path examples/guestbook --env minikube --dest-server https://$(minikube ip):8443
kubectl get svc argocd-server
argocd login <EXTERNAL-IP>
```
Once the application is added, you can now see its status:
After logging in, change the password using the command:
```
argocd account update-password
```
## 5. Register a cluster to deploy apps to
We will now register a cluster to deploy applications to. First list all clusters contexts in your
kubconfig:
```
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
```
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
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
### 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
```
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.
![connect repo](assets/connect_repo.png)
After connecting a git repository, select the guestbook application for creation:
![select repo](assets/select_repo.png)
![select app](assets/select_app.png)
![select env](assets/select_env.png)
![create app](assets/create_app.png)
### Creating apps via CLI
Applications can be also be created using the ArgoCD CLI:
```
argocd app list
argocd app get guestbook
argocd app create --name guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
```
## 7. Sync (deploy) the application
Once the guestbook application is created, you can now view its status:
From UI:
![create app](assets/guestbook-app.png)
From CLI:
```
$ argocd app get guestbook-default
Name: guestbook-default
Server: https://kubernetes.default.svc
Namespace: default
URL: https://192.168.64.36:31880/applications/argocd/guestbook-default
Environment: default
Repo: https://github.com/argoproj/argocd-example-apps.git
Path: guestbook
Target: HEAD
KIND NAME STATUS HEALTH
Service guestbook-ui OutOfSync
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:
```
argocd app sync guestbook
$ argocd app sync guestbook-default
Application: guestbook-default
Operation: Sync
Phase: Succeeded
Message: successfully synced
KIND NAME MESSAGE
Service guestbook-ui service "guestbook-ui" created
Deployment guestbook-ui deployment.apps "guestbook-ui" created
```
[![asciicast](https://asciinema.org/a/uYnbFMy5WI2rc9S49oEAyGLb0.png)](https://asciinema.org/a/uYnbFMy5WI2rc9S49oEAyGLb0)
This command retrieves the manifests from the ksonnet app in the 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:
Argo CD also allows to view and manager applications using web UI. Get the web UI URL by running:
![view app](assets/guestbook-tree.png)
```
minikube service argocd-server -n argocd --url
```
![argo cd ui](argocd-ui.png)
## 8. Next Steps
ArgoCD supports additional features such as SSO, WebHooks, RBAC. See the following guides on setting
these up:
* [Configuring SSO](sso.md)
* [Configuring RBAC](rbac.md)
* [Configuring WebHooks](webhook.md)

18
docs/health.md Normal file
View File

@@ -0,0 +1,18 @@
# Resource Health
## Overview
ArgoCD provides built-in health assessment for several standard Kubernetes types, which is then
surfaced to the overall Application health status as a whole. The following checks are made for
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.
### Service
* If service type is of type `LoadBalancer`, the `status.loadBalancer.ingress` list is non-empty,
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`.

99
docs/rbac.md Normal file
View File

@@ -0,0 +1,99 @@
# RBAC
## 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.
## 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.
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.
*ConfigMap `argocd-rbac-cm` example:*
```yaml
apiVersion: v1
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, *
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 source ksonnet application
should be defined in `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`
and `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
```

61
docs/resource_hooks.md Normal file
View File

@@ -0,0 +1,61 @@
# Resource Hooks
## Overview
Hooks are ways to interject custom logic before, during, and after a Sync operation. Some use cases
for hooks are:
* Using a `PreSync` hook to perform a database schema migration before deploying a new version of the app.
* Using a `Sync` hook to orchestrate a complex deployment requiring more sophistication than the
kubernetes rolling update strategy (e.g. a blue/green deployment).
* Using a `PostSync` hook to run integration and health checks after a deployment.
## Usage
Hooks are simply kubernetes manifests annotated with the `argocd.argoproj.io/hook` annotation. To
make use of hooks, simply add the annotation to any resource:
```yaml
apiVersion: batch/v1
kind: Job
metadata:
generateName: schema-migrate-
annotations:
argocd.argoproj.io/hook: PreSync
```
During a Sync operation, ArgoCD will create the resource during the appropriate stage of the
deployment. Hooks can be any type of Kuberentes resource kind, but tend to be most useful as
[Kubernetes Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/)
or [Argo Workflows](https://github.com/argoproj/argo). Multiple hooks can be specified as a comma
separated list.
## Available Hooks
The following hooks are defined:
| Hook | Description |
|------|-------------|
| `PreSync` | Executes prior to the apply of the manifests. |
| `Sync` | Executes after all `PreSync` hooks completed and were successful. Occurs in conjuction with the apply of the manifests. |
| `Skip` | Indicates to ArgoCD to skip the apply of the manifest. This is typically used in conjunction with a `Sync` hook which is presumably handling the deployment in an alternate way (e.g. blue-green deployment) |
| `PostSync` | Executes after all `Sync` hooks completed and were successful, a succcessful apply, and all resources in a `Healthy` state. |
## Hook Deletion Policies
Hooks can be deleted in an automatic fashion using the annotation: `argocd.argoproj.io/hook-delete-policy`.
```yaml
apiVersion: batch/v1
kind: Job
metadata:
generateName: integration-test-
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: OnSuccess
```
The following policies define when the hook will be deleted.
| Policy | Description |
|--------|-------------|
| `OnSuccess` | The hook resource is deleted after the hook succeeded (e.g. Job/Workflow completed successfully). |
| `OnFailure` | The hook resource is deleted after the hook failed. |

87
docs/sso.md Normal file
View File

@@ -0,0 +1,87 @@
# SSO Configuration
## Overview
ArgoCD embeds and bundles [Dex](https://github.com/coreos/dex) as part of its installation, for the
purpose of delegating authentication to an external identity provider. Multiple types of identity
providers are supported (OIDC, SAML, LDAP, GitHub, etc...). SSO configuration of ArgoCD requires
editing the `argocd-cm` ConfigMap with
[Dex connector](https://github.com/coreos/dex/tree/master/Documentation/connectors) settings.
This document describes how to configure ArgoCD SSO using GitHub (OAuth2) as an example, but the
steps should be similar for other identity providers.
### 1. Register the application in the identity provider
In GitHub, register a new application. The callback address should be the `/api/dex/callback`
endpoint of your ArgoCD URL (e.g. https://argocd.example.com/api/dex/callback).
![Register OAuth App](assets/register-app.png "Register OAuth App")
After registering the app, you will receive an OAuth2 client ID and secret. These values will be
inputted into the ArgoCD configmap.
![OAuth2 Client Config](assets/oauth2-config.png "OAuth2 Client Config")
### 2. Configure ArgoCD for SSO
Edit the argocd-cm configmap:
```
kubectl edit configmap argocd-cm
```
* In the `url` key, input the base URL of ArgoCD. In this example, it is https://argocd.example.com
* In the `dex.config` key, add the `github` connector to the `connectors` sub field. See Dex's
[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
`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.
```
data:
url: https://argocd.example.com
dex.config: |
connectors:
# GitHub example
- type: github
id: github
name: GitHub
config:
clientID: aabbccddeeff00112233
clientSecret: $dex.github.clientSecret
orgs:
- name: your-github-org
# GitHub enterprise example
- type: github
id: acme-github
name: Acme GitHub
config:
hostName: github.acme.com
clientID: abcdefghijklmnopqrst
clientSecret: $dex.acme.clientSecret
orgs:
- name: your-github-org
# OIDC example (e.g. Okta)
- type: oidc
id: okta
name: Okta
config:
issuer: https://dev-123456.oktapreview.com
clientID: aaaabbbbccccddddeee
clientSecret: $dex.okta.clientSecret
```
After saving, the changes should take affect automatically.
NOTES:
* Any values which start with '$' will look to a key in argocd-secret of the same name (minus the $),
to obtain the actual value. This allows you to store the `clientSecret` as a kubernetes secret.
* There is no need to set `redirectURI` in the `connectors.config` as shown in the dex documentation.
ArgoCD will automatically use the correct `redirectURI` for any OAuth2 connectors, to match the
correct external callback URL (e.g. https://argocd.example.com/api/dex/callback)

View File

@@ -2,10 +2,6 @@
An ArgoCD application spec provides several different ways of track kubernetes resource manifests in git. This document describes the different techniques and the means of deploying those manifests to the target environment.
## Auto-Sync
In all tracking strategies described below, the application has the option to sync automatically. If auto-sync 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.
## 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.
@@ -23,12 +19,13 @@ To redeploy an application, the user uses git to change the meaning of a tag by
If a git commit SHA is specified, the application is effectively pinned to the manifests defined at the specified commit. This is the most restrictive of the techniques and is typically used to control production environments.
Since commit SHAs cannot change meaning, the only way to change the live state of an application which is pinned to a commit, is by updating the tracking revision in the application to a different commit containing the new manifests.
Note that parameter overrides can still be made against a application which is pinned to a revision.
## Parameter Overrides
ArgoCD provides means to override the parameters of a ksonnet app. This gives some extra flexibility in having *some* parts of the k8s manifests determined dynamically. It also serves as an alternative way of redeploying an application by changing application parameters via ArgoCD, instead of making the changes to the manifests in git.
The following is an example of where this would be useful: A team maintains a "dev" environment, which needs to be continually updated with the latest version of their guestbook application after every build in the tip of master. To solve this, the ksonnet application would expose an parameter named `image`, whose value used in the `dev` environment contains a placeholder value (e.g. `example/guestbook:replaceme`) intended to be set externally (outside of git) such as by build systems. As part of the build pipeline, the parameter value of the `image` would be continually updated to the freshly built image (e.g. `example/guestbook:abcd123`). A sync operation would result in the application being redeployed with the new image.
The following is an example of where this would be useful: A team maintains a "dev" environment, which needs to be continually updated with the latest version of their guestbook application after every build in the tip of master. To address this use case, the ksonnet application should expose an parameter named `image`, whose value used in the `dev` environment contains a placeholder value (e.g. `example/guestbook:replaceme`), intended to be set externally (outside of git) such as a build systems. As part of the build pipeline, the parameter value of the `image` would be continually updated to the freshly built image (e.g. `example/guestbook:abcd123`). A sync operation would result in the application being redeployed with the new image.
ArgoCD provides these operations conveniently via the CLI, or alternatively via the gRPC/REST API.
```
@@ -38,3 +35,7 @@ $ argocd app sync guestbook
Note that in all tracking strategies, any parameter overrides set in the application instance will be honored.
## [Auto-Sync](https://github.com/argoproj/argo-cd/issues/79) (Not Yet Implemented)
In all tracking strategies, the application will have the option to sync automatically. If auto-sync 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.

63
docs/webhook.md Normal file
View File

@@ -0,0 +1,63 @@
# Git Webhook Configuration
## Overview
ArgoCD will poll git repositories every three minutes for changes to the manifests. To eliminate
this delay from polling, the API server can be configured to receive webhook events. ArgoCD supports
git webhook notifications from GitHub, GitLab, and BitBucket. The following explains how to configure
a git webhook for GitHub, but the same process should be applicable to other providers.
### 1. Create the webhook in the git provider
In your git provider, navigate to the settings page where webhooks can be configured. 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). Input an arbitrary value in the secret. The same
value will be used when configuring the webhook in step 2.
![Add Webhook](assets/webhook-config.png "Add Webhook")
### 2. Configure ArgoCD with the webhook secret
In the `argocd-secret` kubernetes secret, configure one of the following keys with the git provider
webhook secret configured in step 1.
| Provider | K8s Secret Key |
|---------- | ------------------------ |
| GitHub | `github.webhook.secret` |
| GitLab | `gitlab.webhook.secret` |
| BitBucket | `bitbucket.webhook.uuid` |
Edit the ArgoCD kubernetes secret:
```
kubectl edit secret argocd-secret
```
TIP: for ease of entering secrets, kubernetes supports inputting secrets in the `stringData` field,
which saves you the trouble of base64 encoding the values and copying it to the `data` field.
Simply copy the shared webhook secret created in step 1, to the corresponding
GitHub/GitLab/BitBucket key under the `stringData` field:
```
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
namespace: argocd
type: Opaque
data:
...
stringData:
# github webhook secret
github.webhook.secret: shhhh! it's a github secret
# gitlab webhook secret
gitlab.webhook.secret: shhhh! it's a gitlab secret
# bitbucket webhook secret
bitbucket.webhook.uuid: your-bitbucket-uuid
```
After saving, the changes should take affect automatically.

View File

@@ -23,7 +23,7 @@ local appDeployment = deployment
params.replicas,
container
.new(params.name, params.image)
.withPorts(containerPort.new(targetPort)),
labels);
.withPorts(containerPort.new(targetPort)) + if params.command != null then { command: [ params.command ] } else {},
labels).withProgressDeadlineSeconds(5);
k.core.v1.list.new([appService, appDeployment])

View File

@@ -12,7 +12,8 @@
name: "guestbook-ui",
replicas: 1,
servicePort: 80,
type: "LoadBalancer",
type: "ClusterIP",
command: null,
},
},
}

View File

@@ -1,7 +1,7 @@
{
"Vendor": true,
"DisableAll": true,
"Deadline": "3m",
"Deadline": "8m",
"Enable": [
"vet",
"gofmt",

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#! /usr/bin/env bash
# This script auto-generates protobuf related files. It is intended to be run manually when either
# API types are added/modified, or server gRPC calls are added. The generated files should then
@@ -55,6 +55,8 @@ GOPROTOBINARY=gogofast
# protoc-gen-grpc-gateway is used to build <service>.pb.gw.go files from from .proto files
go build -i -o dist/protoc-gen-grpc-gateway ./vendor/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
# protoc-gen-swagger is used to build swagger.json
go build -i -o dist/protoc-gen-swagger ./vendor/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
# Generate server/<service>/(<service>.pb.go|<service>.pb.gw.go)
PROTO_FILES=$(find $PROJECT_ROOT \( -name "*.proto" -and -path '*/server/*' -or -path '*/reposerver/*' -and -name "*.proto" \))
@@ -77,5 +79,44 @@ for i in ${PROTO_FILES}; do
-I${GOGO_PROTOBUF_PATH} \
--${GOPROTOBINARY}_out=plugins=grpc:$GOPATH/src \
--grpc-gateway_out=logtostderr=true:$GOPATH/src \
--swagger_out=logtostderr=true:. \
$i
done
# collect_swagger gathers swagger files into a subdirectory
collect_swagger() {
SWAGGER_ROOT="$1"
EXPECTED_COLLISIONS="$2"
SWAGGER_OUT="${SWAGGER_ROOT}/swagger.json"
PRIMARY_SWAGGER=`mktemp`
COMBINED_SWAGGER=`mktemp`
cat <<EOF > "${PRIMARY_SWAGGER}"
{
"swagger": "2.0",
"info": {
"title": "Consolidate Services",
"description": "Description of all APIs",
"version": "version not set"
},
"paths": {}
}
EOF
/bin/rm -f "${SWAGGER_OUT}"
/usr/bin/find "${SWAGGER_ROOT}" -name '*.swagger.json' -exec /usr/local/bin/swagger mixin -c "${EXPECTED_COLLISIONS}" "${PRIMARY_SWAGGER}" '{}' \+ > "${COMBINED_SWAGGER}"
/usr/local/bin/jq -r 'del(.definitions[].properties[]? | select(."$ref"!=null and .description!=null).description) | del(.definitions[].properties[]? | select(."$ref"!=null and .title!=null).title)' "${COMBINED_SWAGGER}" > "${SWAGGER_OUT}"
/bin/rm "${PRIMARY_SWAGGER}" "${COMBINED_SWAGGER}"
}
# clean up generated swagger files (should come after collect_swagger)
clean_swagger() {
SWAGGER_ROOT="$1"
/usr/bin/find "${SWAGGER_ROOT}" -name '*.swagger.json' -delete
}
collect_swagger server 15
clean_swagger server
clean_swagger reposerver

View File

@@ -0,0 +1,168 @@
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,360 +0,0 @@
package install
import (
"fmt"
"strings"
"syscall"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/util/config"
"github.com/argoproj/argo-cd/util/diff"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/password"
"github.com/argoproj/argo-cd/util/session"
tlsutil "github.com/argoproj/argo-cd/util/tls"
"github.com/ghodss/yaml"
"github.com/gobuffalo/packr"
log "github.com/sirupsen/logrus"
"github.com/yudai/gojsondiff/formatter"
"golang.org/x/crypto/ssh/terminal"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apiv1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
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/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
var (
// These values will be overridden by the link flags during build
// (e.g. imageTag will use the official release tag on tagged builds)
imageNamespace = "argoproj"
imageTag = "latest"
// Default namespace and image names which `argocd install` uses during install
DefaultInstallNamespace = "argocd"
DefaultControllerImage = imageNamespace + "/argocd-application-controller:" + imageTag
DefaultUIImage = imageNamespace + "/argocd-ui:" + imageTag
DefaultServerImage = imageNamespace + "/argocd-server:" + imageTag
DefaultRepoServerImage = imageNamespace + "/argocd-repo-server:" + imageTag
)
// InstallOptions stores a collection of installation settings.
type InstallOptions struct {
DryRun bool
Upgrade bool
ConfigSuperuser bool
CreateSignature bool
ConfigMap string
Namespace string
ControllerImage string
UIImage string
ServerImage string
RepoServerImage string
ImagePullPolicy string
}
type Installer struct {
InstallOptions
box packr.Box
config *rest.Config
dynClientPool dynamic.ClientPool
disco discovery.DiscoveryInterface
}
func NewInstaller(config *rest.Config, opts InstallOptions) (*Installer, error) {
shallowCopy := *config
inst := Installer{
InstallOptions: opts,
box: packr.NewBox("./manifests"),
config: &shallowCopy,
}
if opts.Namespace == "" {
inst.Namespace = DefaultInstallNamespace
}
var err error
inst.dynClientPool = dynamic.NewDynamicClientPool(inst.config)
inst.disco, err = discovery.NewDiscoveryClientForConfig(inst.config)
if err != nil {
return nil, err
}
return &inst, nil
}
func (i *Installer) Install() {
i.InstallNamespace()
i.InstallApplicationCRD()
i.InstallSettings()
i.InstallApplicationController()
i.InstallArgoCDServer()
i.InstallArgoCDRepoServer()
}
func (i *Installer) Uninstall() {
manifests := i.box.List()
for _, manifestPath := range manifests {
if strings.HasSuffix(manifestPath, ".yaml") || strings.HasSuffix(manifestPath, ".yml") {
var obj unstructured.Unstructured
i.unmarshalManifest(manifestPath, &obj)
if obj.GetKind() == "Namespace" {
// Don't delete namespaces
continue
}
i.MustUninstallResource(&obj)
}
}
}
func (i *Installer) InstallNamespace() {
if i.Namespace != DefaultInstallNamespace {
// don't create namespace if a different one was supplied
return
}
var namespace apiv1.Namespace
i.unmarshalManifest("00_namespace.yaml", &namespace)
namespace.ObjectMeta.Name = i.Namespace
i.MustInstallResource(kube.MustToUnstructured(&namespace))
}
func (i *Installer) InstallApplicationCRD() {
var applicationCRD apiextensionsv1beta1.CustomResourceDefinition
i.unmarshalManifest("01_application-crd.yaml", &applicationCRD)
i.MustInstallResource(kube.MustToUnstructured(&applicationCRD))
}
func (i *Installer) InstallSettings() {
kubeclientset, err := kubernetes.NewForConfig(i.config)
errors.CheckError(err)
configManager := config.NewConfigManager(kubeclientset, i.Namespace)
_, err = configManager.GetSettings()
if err == nil {
log.Infof("Settings already exists. Skipping creation")
return
}
if !apierr.IsNotFound(err) {
log.Fatal(err)
}
// configmap/secret not yet created
var newSettings config.ArgoCDSettings
// set JWT signature
signature, err := session.MakeSignature(32)
errors.CheckError(err)
newSettings.ServerSignature = signature
// generate admin password
passwordRaw := readAndConfirmPassword()
hashedPassword, err := password.HashPassword(passwordRaw)
errors.CheckError(err)
newSettings.LocalUsers = map[string]string{
common.ArgoCDAdminUsername: hashedPassword,
}
// generate TLS cert
hosts := []string{
"localhost",
"argocd-server",
fmt.Sprintf("argocd-server.%s", i.Namespace),
fmt.Sprintf("argocd-server.%s.svc", i.Namespace),
fmt.Sprintf("argocd-server.%s.svc.cluster.local", i.Namespace),
}
certOpts := tlsutil.CertOptions{
Hosts: hosts,
Organization: "Argo CD",
IsCA: true,
}
cert, err := tlsutil.GenerateX509KeyPair(certOpts)
errors.CheckError(err)
newSettings.Certificate = cert
err = configManager.SaveSettings(&newSettings)
errors.CheckError(err)
}
func readAndConfirmPassword() string {
for {
fmt.Print("*** Enter an admin password: ")
password, err := terminal.ReadPassword(syscall.Stdin)
errors.CheckError(err)
fmt.Print("\n")
fmt.Print("*** Confirm the admin password: ")
confirmPassword, err := terminal.ReadPassword(syscall.Stdin)
errors.CheckError(err)
fmt.Print("\n")
if string(password) == string(confirmPassword) {
return string(password)
}
log.Error("Passwords do not match")
}
}
func (i *Installer) InstallApplicationController() {
var applicationControllerServiceAccount apiv1.ServiceAccount
var applicationControllerRole rbacv1.Role
var applicationControllerRoleBinding rbacv1.RoleBinding
var applicationControllerDeployment appsv1beta2.Deployment
i.unmarshalManifest("03a_application-controller-sa.yaml", &applicationControllerServiceAccount)
i.unmarshalManifest("03b_application-controller-role.yaml", &applicationControllerRole)
i.unmarshalManifest("03c_application-controller-rolebinding.yaml", &applicationControllerRoleBinding)
i.unmarshalManifest("03d_application-controller-deployment.yaml", &applicationControllerDeployment)
applicationControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.ControllerImage
applicationControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerServiceAccount))
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerRole))
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerRoleBinding))
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerDeployment))
}
func (i *Installer) InstallArgoCDServer() {
var argoCDServerServiceAccount apiv1.ServiceAccount
var argoCDServerControllerRole rbacv1.Role
var argoCDServerControllerRoleBinding rbacv1.RoleBinding
var argoCDServerControllerDeployment appsv1beta2.Deployment
var argoCDServerService apiv1.Service
i.unmarshalManifest("04a_argocd-server-sa.yaml", &argoCDServerServiceAccount)
i.unmarshalManifest("04b_argocd-server-role.yaml", &argoCDServerControllerRole)
i.unmarshalManifest("04c_argocd-server-rolebinding.yaml", &argoCDServerControllerRoleBinding)
i.unmarshalManifest("04d_argocd-server-deployment.yaml", &argoCDServerControllerDeployment)
i.unmarshalManifest("04e_argocd-server-service.yaml", &argoCDServerService)
argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[0].Image = i.UIImage
argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.ServerImage
argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerServiceAccount))
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerRole))
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerRoleBinding))
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerDeployment))
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerService))
}
func (i *Installer) InstallArgoCDRepoServer() {
var argoCDRepoServerControllerDeployment appsv1beta2.Deployment
var argoCDRepoServerService apiv1.Service
i.unmarshalManifest("05a_argocd-repo-server-deployment.yaml", &argoCDRepoServerControllerDeployment)
i.unmarshalManifest("05b_argocd-repo-server-service.yaml", &argoCDRepoServerService)
argoCDRepoServerControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.RepoServerImage
argoCDRepoServerControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
i.MustInstallResource(kube.MustToUnstructured(&argoCDRepoServerControllerDeployment))
i.MustInstallResource(kube.MustToUnstructured(&argoCDRepoServerService))
}
func (i *Installer) unmarshalManifest(fileName string, obj interface{}) {
yamlBytes, err := i.box.MustBytes(fileName)
errors.CheckError(err)
err = yaml.Unmarshal(yamlBytes, obj)
errors.CheckError(err)
}
func (i *Installer) MustInstallResource(obj *unstructured.Unstructured) *unstructured.Unstructured {
obj, err := i.InstallResource(obj)
errors.CheckError(err)
return obj
}
func isNamespaced(obj *unstructured.Unstructured) bool {
switch obj.GetKind() {
case "Namespace", "ClusterRole", "ClusterRoleBinding", "CustomResourceDefinition":
return false
}
return true
}
// InstallResource creates or updates a resource. If installed resource is up-to-date, does nothing
func (i *Installer) InstallResource(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
if isNamespaced(obj) {
obj.SetNamespace(i.Namespace)
}
// remove 'creationTimestamp' and 'status' fields from object so that the diff will not be modified
obj.SetCreationTimestamp(metav1.Time{})
delete(obj.Object, "status")
if i.DryRun {
printYAML(obj)
return nil, nil
}
gvk := obj.GroupVersionKind()
dclient, err := i.dynClientPool.ClientForGroupVersionKind(gvk)
if err != nil {
return nil, err
}
apiResource, err := kube.ServerResourceForGroupVersionKind(i.disco, gvk)
if err != nil {
return nil, err
}
reIf := dclient.Resource(apiResource, i.Namespace)
liveObj, err := reIf.Create(obj)
if err == nil {
fmt.Printf("%s '%s' created\n", liveObj.GetKind(), liveObj.GetName())
return liveObj, nil
}
if !apierr.IsAlreadyExists(err) {
return nil, err
}
liveObj, err = reIf.Get(obj.GetName(), metav1.GetOptions{})
if err != nil {
return nil, err
}
diffRes := diff.Diff(obj, liveObj)
if !diffRes.Modified {
fmt.Printf("%s '%s' up-to-date\n", liveObj.GetKind(), liveObj.GetName())
return liveObj, nil
}
if !i.Upgrade {
log.Println(diffRes.ASCIIFormat(obj, formatter.AsciiFormatterConfig{}))
return nil, fmt.Errorf("%s '%s' already exists. Rerun with --upgrade to update", obj.GetKind(), obj.GetName())
}
liveObj, err = reIf.Update(obj)
if err != nil {
return nil, err
}
fmt.Printf("%s '%s' updated\n", liveObj.GetKind(), liveObj.GetName())
return liveObj, nil
}
func (i *Installer) MustUninstallResource(obj *unstructured.Unstructured) {
err := i.UninstallResource(obj)
errors.CheckError(err)
}
// UninstallResource deletes a resource from the cluster
func (i *Installer) UninstallResource(obj *unstructured.Unstructured) error {
if isNamespaced(obj) {
obj.SetNamespace(i.Namespace)
}
gvk := obj.GroupVersionKind()
dclient, err := i.dynClientPool.ClientForGroupVersionKind(gvk)
if err != nil {
return err
}
apiResource, err := kube.ServerResourceForGroupVersionKind(i.disco, gvk)
if err != nil {
return err
}
reIf := dclient.Resource(apiResource, i.Namespace)
deletePolicy := metav1.DeletePropagationForeground
err = reIf.Delete(obj.GetName(), &metav1.DeleteOptions{PropagationPolicy: &deletePolicy})
if err != nil {
if apierr.IsNotFound(err) {
fmt.Printf("%s '%s' not found\n", obj.GetKind(), obj.GetName())
return nil
}
return err
}
fmt.Printf("%s '%s' deleted\n", obj.GetKind(), obj.GetName())
return nil
}
func printYAML(obj interface{}) {
objBytes, err := yaml.Marshal(obj)
if err != nil {
log.Fatalf("Failed to marshal %v", obj)
}
fmt.Printf("---\n%s\n", string(objBytes))
}

View File

@@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: argocd

View File

@@ -1,7 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
# TODO: future argocd tuning keys go here (e.g. resync period)
data: {}

View File

@@ -1,13 +0,0 @@
# NOTE: the values in this secret are provided as working manifest example and are not the values
# used during an install. New values will be generated as part of `argocd install`
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
namespace: argocd
type: Opaque
data:
# bcrypt hash of 'password'
admin.password: JDJhJDEwJGVYYkZmOEt3NUMzTDJVbE9FRDNqUU9QMC5reVNBamVLUXY0N3NqaFFpWlZwTkkyU2dMTzd1
# random server signature key for session validation
server.secretkey: aEDvv73vv70F77+9CRBSNu+/vTYQ77+9EUFh77+9LzFyJ++/vXfLsO+/vWRbeu+/ve+/vQ==

View File

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

View File

@@ -0,0 +1,15 @@
---
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

View File

@@ -0,0 +1,14 @@
---
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

@@ -0,0 +1,24 @@
---
# 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

@@ -0,0 +1,26 @@
---
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,5 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: application-controller
namespace: argocd

View File

@@ -1,8 +1,8 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: application-controller-role
namespace: argocd
rules:
- apiGroups:
- ""
@@ -11,10 +11,14 @@ rules:
verbs:
- get
- watch
- list
- patch
- update
- apiGroups:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get

View File

@@ -1,8 +1,8 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: application-controller-role-binding
namespace: argocd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role

View File

@@ -1,8 +1,8 @@
apiVersion: apps/v1beta2
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-controller
namespace: argocd
spec:
selector:
matchLabels:

View File

@@ -1,5 +1,5 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-server
namespace: argocd

View File

@@ -1,34 +1,13 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argocd-server-role
namespace: argocd
rules:
- apiGroups:
- ""
resources:
- pods
- pods/exec
- pods/log
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
@@ -42,6 +21,7 @@ rules:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get

View File

@@ -1,8 +1,8 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argocd-server-role-binding
namespace: argocd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role

View File

@@ -1,8 +1,8 @@
apiVersion: apps/v1beta2
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-server
namespace: argocd
spec:
selector:
matchLabels:
@@ -14,16 +14,28 @@ spec:
spec:
serviceAccountName: argocd-server
initContainers:
- command: [cp, -r, /app, /shared]
- name: copyutil
image: argoproj/argocd-server:latest
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
- name: ui
image: argoproj/argocd-ui:latest
name: argocd-server-ui
command: [cp, -r, /app, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
containers:
- command: [/argocd-server, --staticassets, /shared/app, --repo-server, 'argocd-repo-server:8081']
- name: argocd-server
image: argoproj/argocd-server:latest
name: argocd-server
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

View File

@@ -1,8 +1,8 @@
---
apiVersion: v1
kind: Service
metadata:
name: argocd-server
namespace: argocd
spec:
ports:
- name: http

View File

@@ -1,8 +1,8 @@
apiVersion: apps/v1beta2
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-repo-server
namespace: argocd
spec:
selector:
matchLabels:
@@ -13,8 +13,8 @@ spec:
app: argocd-repo-server
spec:
containers:
- command: [/argocd-repo-server]
- name: argocd-repo-server
image: argoproj/argocd-repo-server:latest
name: argocd-repo-server
command: [/argocd-repo-server]
ports:
- containerPort: 8081

View File

@@ -1,8 +1,8 @@
---
apiVersion: v1
kind: Service
metadata:
name: argocd-repo-server
namespace: argocd
spec:
ports:
- port: 8081

300
manifests/install.yaml Normal file
View File

@@ -0,0 +1,300 @@
# 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: 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:
---
# 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
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: application-controller
---
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
---
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.6.2
name: application-controller
serviceAccountName: application-controller
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-server
---
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
---
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: apps/v1
kind: Deployment
metadata:
name: argocd-server
spec:
selector:
matchLabels:
app: argocd-server
template:
metadata:
labels:
app: argocd-server
spec:
serviceAccountName: argocd-server
initContainers:
- name: copyutil
image: argoproj/argocd-server:v0.6.2
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
- name: ui
image: argoproj/argocd-ui:v0.6.2
command: [cp, -r, /app, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
containers:
- name: argocd-server
image: argoproj/argocd-server:v0.6.2
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
---
apiVersion: v1
kind: Service
metadata:
name: argocd-server
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
- name: https
protocol: TCP
port: 443
targetPort: 8080
selector:
app: argocd-server
---
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:
containers:
- name: argocd-repo-server
image: argoproj/argocd-repo-server:v0.6.2
command: [/argocd-repo-server]
ports:
- containerPort: 8081
---
apiVersion: v1
kind: Service
metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
targetPort: 8081
selector:
app: argocd-repo-server

View File

@@ -11,10 +11,13 @@ import (
"os"
"strings"
"github.com/argoproj/argo-cd/server/account"
"github.com/argoproj/argo-cd/server/application"
"github.com/argoproj/argo-cd/server/cluster"
"github.com/argoproj/argo-cd/server/project"
"github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/server/session"
"github.com/argoproj/argo-cd/server/settings"
"github.com/argoproj/argo-cd/server/version"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/localconfig"
@@ -24,14 +27,16 @@ import (
)
const (
MetaDataTokenKey = "token"
// EnvArgoCDServer is the environment variable to look for an ArgoCD server address
EnvArgoCDServer = "ARGOCD_SERVER"
// EnvArgoCDAuthToken is the environment variable to look for an ArgoCD auth token
EnvArgoCDAuthToken = "ARGOCD_AUTH_TOKEN"
)
// ServerClient defines an interface for interaction with an Argo CD server.
type ServerClient interface {
// Client defines an interface for interaction with an Argo CD server.
type Client interface {
ClientOptions() ClientOptions
NewConn() (*grpc.ClientConn, error)
NewRepoClient() (*grpc.ClientConn, repository.RepositoryServiceClient, error)
NewRepoClientOrDie() (*grpc.ClientConn, repository.RepositoryServiceClient)
@@ -41,8 +46,14 @@ type ServerClient interface {
NewApplicationClientOrDie() (*grpc.ClientConn, application.ApplicationServiceClient)
NewSessionClient() (*grpc.ClientConn, session.SessionServiceClient, error)
NewSessionClientOrDie() (*grpc.ClientConn, session.SessionServiceClient)
NewSettingsClient() (*grpc.ClientConn, settings.SettingsServiceClient, error)
NewSettingsClientOrDie() (*grpc.ClientConn, settings.SettingsServiceClient)
NewVersionClient() (*grpc.ClientConn, version.VersionServiceClient, error)
NewVersionClientOrDie() (*grpc.ClientConn, version.VersionServiceClient)
NewProjectClient() (*grpc.ClientConn, project.ProjectServiceClient, error)
NewProjectClientOrDie() (*grpc.ClientConn, project.ProjectServiceClient)
NewAccountClient() (*grpc.ClientConn, account.AccountServiceClient, error)
NewAccountClientOrDie() (*grpc.ClientConn, account.AccountServiceClient)
}
// ClientOptions hold address, security, and other settings for the API client.
@@ -65,7 +76,7 @@ type client struct {
}
// NewClient creates a new API client from a set of config options.
func NewClient(opts *ClientOptions) (ServerClient, error) {
func NewClient(opts *ClientOptions) (Client, error) {
var c client
localCfg, err := localconfig.ReadLocalConfig(opts.ConfigPath)
if err != nil {
@@ -130,7 +141,7 @@ func NewClient(opts *ClientOptions) (ServerClient, error) {
}
// NewClientOrDie creates a new API client from a set of config options, or fails fatally if the new client creation fails.
func NewClientOrDie(opts *ClientOptions) ServerClient {
func NewClientOrDie(opts *ClientOptions) Client {
client, err := NewClient(opts)
if err != nil {
log.Fatal(err)
@@ -138,7 +149,8 @@ func NewClientOrDie(opts *ClientOptions) ServerClient {
return client
}
// JwtCredentials holds a token for authentication.
// JwtCredentials implements the gRPC credentials.Credentials interface which we is used to do
// grpc.WithPerRPCCredentials(), for authentication
type jwtCredentials struct {
Token string
}
@@ -149,7 +161,8 @@ func (c jwtCredentials) RequireTransportSecurity() bool {
func (c jwtCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
return map[string]string{
"tokens": c.Token,
MetaDataTokenKey: c.Token,
"tokens": c.Token, // legacy key. delete eventually
}, nil
}
@@ -175,6 +188,15 @@ func (c *client) NewConn() (*grpc.ClientConn, error) {
return grpc_util.BlockingDial(context.Background(), "tcp", c.ServerAddr, creds, grpc.WithPerRPCCredentials(endpointCredentials))
}
func (c *client) ClientOptions() ClientOptions {
return ClientOptions{
ServerAddr: c.ServerAddr,
PlainText: c.PlainText,
Insecure: c.Insecure,
AuthToken: c.AuthToken,
}
}
func (c *client) NewRepoClient() (*grpc.ClientConn, repository.RepositoryServiceClient, error) {
conn, err := c.NewConn()
if err != nil {
@@ -243,6 +265,23 @@ func (c *client) NewSessionClientOrDie() (*grpc.ClientConn, session.SessionServi
return conn, sessionIf
}
func (c *client) NewSettingsClient() (*grpc.ClientConn, settings.SettingsServiceClient, error) {
conn, err := c.NewConn()
if err != nil {
return nil, nil, err
}
setIf := settings.NewSettingsServiceClient(conn)
return conn, setIf, nil
}
func (c *client) NewSettingsClientOrDie() (*grpc.ClientConn, settings.SettingsServiceClient) {
conn, setIf, err := c.NewSettingsClient()
if err != nil {
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
}
return conn, setIf
}
func (c *client) NewVersionClient() (*grpc.ClientConn, version.VersionServiceClient, error) {
conn, err := c.NewConn()
if err != nil {
@@ -259,3 +298,37 @@ func (c *client) NewVersionClientOrDie() (*grpc.ClientConn, version.VersionServi
}
return conn, versionIf
}
func (c *client) NewProjectClient() (*grpc.ClientConn, project.ProjectServiceClient, error) {
conn, err := c.NewConn()
if err != nil {
return nil, nil, err
}
projIf := project.NewProjectServiceClient(conn)
return conn, projIf, nil
}
func (c *client) NewProjectClientOrDie() (*grpc.ClientConn, project.ProjectServiceClient) {
conn, projIf, err := c.NewProjectClient()
if err != nil {
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
}
return conn, projIf
}
func (c *client) NewAccountClient() (*grpc.ClientConn, account.AccountServiceClient, error) {
conn, err := c.NewConn()
if err != nil {
return nil, nil, err
}
usrIf := account.NewAccountServiceClient(conn)
return conn, usrIf, nil
}
func (c *client) NewAccountClientOrDie() (*grpc.ClientConn, account.AccountServiceClient) {
conn, usrIf, err := c.NewAccountClient()
if err != nil {
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
}
return conn, usrIf
}

View File

@@ -11,10 +11,10 @@ const (
ApplicationShortName string = "app"
ApplicationFullName string = ApplicationPlural + "." + Group
// Cluster constants
ClusterKind string = "Cluster"
ClusterSingular string = "cluster"
ClusterPlural string = "clusters"
ClusterShortName string = "cluster"
ClusterFullName string = ClusterPlural + "." + Group
// AppProject constants
AppProjectKind string = "AppProject"
AppProjectSingular string = "appproject"
AppProjectPlural string = "appprojects"
AppProjectShortName string = "appproject"
AppProjectFullName string = AppProjectPlural + "." + Group
)

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,36 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1alpha1";
// AppProject is a definition of AppProject resource.
// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
message AppProject {
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
optional AppProjectSpec spec = 2;
}
// AppProjectList is list of AppProject resources
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
message AppProjectList {
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
repeated AppProject items = 2;
}
// AppProjectSpec represents
message AppProjectSpec {
// SourceRepos contains list of git repository URLs which can be used for deployment
repeated string sources = 1;
// Destinations contains list of destinations available for deployment
repeated ApplicationDestination destinations = 2;
// Description contains optional project description
optional string description = 3;
}
// Application is a definition of Application resource.
// +genclient
// +genclient:noStatus
@@ -23,6 +53,17 @@ message Application {
optional ApplicationSpec spec = 2;
optional ApplicationStatus status = 3;
optional Operation operation = 4;
}
// ApplicationCondition contains details about current application condition
message ApplicationCondition {
// Type is an application condition type
optional string type = 1;
// Message contains human-readable message indicating details about condition
optional string message = 2;
}
// ApplicationDestination contains deployment destination information
@@ -67,20 +108,25 @@ message ApplicationSpec {
optional ApplicationSource source = 1;
// Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml
// This field is optional. If omitted, uses the server and namespace defined in the environment
optional ApplicationDestination destination = 2;
// SyncPolicy dictates whether we auto-sync based on the delta between the tracked branch and live state
optional string syncPolicy = 3;
// Project is a application project name. Empty name means that application belongs to 'default' project.
optional string project = 3;
}
// ApplicationStatus contains information about application status in target environment.
message ApplicationStatus {
optional ComparisonResult comparisonResult = 1;
repeated DeploymentInfo recentDeployment = 2;
repeated DeploymentInfo history = 2;
repeated ComponentParameter parameters = 3;
optional HealthStatus health = 4;
optional OperationState operationState = 5;
repeated ApplicationCondition conditions = 6;
}
// ApplicationWatchEvent contains information about application change.
@@ -105,6 +151,9 @@ message Cluster {
// Config holds cluster information for connecting to a cluster
optional ClusterConfig config = 3;
// ConnectionState contains information about cluster connection state
optional ConnectionState connectionState = 4;
}
// ClusterConfig is the configuration attributes. This structure is subset of the go-client
@@ -137,15 +186,9 @@ message ComparisonResult {
optional ApplicationSource comparedTo = 2;
optional string server = 3;
optional string namespace = 4;
optional string status = 5;
repeated ResourceState resources = 6;
optional string error = 7;
}
// ComponentParameter contains information about component parameter value
@@ -157,6 +200,15 @@ message ComponentParameter {
optional string value = 3;
}
// ConnectionState contains information about remote resource connection state
message ConnectionState {
optional string status = 1;
optional string message = 2;
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time attemptedAt = 3;
}
// DeploymentInfo contains information relevant to an application deployment
message DeploymentInfo {
repeated ComponentParameter params = 1;
@@ -170,6 +222,64 @@ message DeploymentInfo {
optional int64 id = 5;
}
message HealthStatus {
optional string status = 1;
optional string statusDetails = 2;
}
// HookStatus contains status about a hook invocation
message HookStatus {
// Name is the resource name
optional string name = 1;
// Kind is the resource kind
optional string kind = 2;
// APIVersion is the resource API version
optional string apiVersion = 3;
// Type is the type of hook (e.g. PreSync, Sync, PostSync, Skip)
optional string type = 4;
// Status a simple, high-level summary of where the resource is in its lifecycle
optional string status = 5;
// A human readable message indicating details about why the resource is in this condition.
optional string message = 6;
}
// 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.
message OperationState {
// Operation is the original requested operation
optional Operation operation = 1;
// Phase is the current phase of the operation
optional string phase = 2;
// Message hold any pertinent messages when attempting to perform operation (typically errors).
optional string message = 3;
// 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;
// FinishedAt contains time of operation completion
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time finishedAt = 7;
}
// Repository is a Git repository holding application configurations
message Repository {
optional string repo = 1;
@@ -179,6 +289,8 @@ message Repository {
optional string password = 3;
optional string sshPrivateKey = 4;
optional ConnectionState connectionState = 5;
}
// RepositoryList is a collection of Repositories.
@@ -188,6 +300,18 @@ message RepositoryList {
repeated Repository items = 2;
}
message ResourceDetails {
optional string name = 1;
optional string kind = 2;
optional string namespace = 3;
optional string message = 4;
optional string status = 5;
}
// ResourceNode contains information about live resource and its children
message ResourceNode {
optional string state = 1;
@@ -204,6 +328,67 @@ message ResourceState {
optional string status = 3;
repeated ResourceNode childLiveResources = 4;
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
optional string revision = 1;
// Prune deletes resources that are no longer tracked in git
optional bool prune = 2;
// DryRun will perform a `kubectl apply --dry-run` without actually performing the sync
optional bool dryRun = 3;
// SyncStrategy describes how to perform the sync
optional SyncStrategy syncStrategy = 4;
}
// SyncOperationResult represent result of sync operation
message SyncOperationResult {
// Resources holds the sync result of each individual resource
repeated ResourceDetails resources = 1;
// Revision holds the git commit SHA of the sync
optional string revision = 2;
// Hooks contains list of hook resource statuses associated with this operation
repeated HookStatus hooks = 3;
}
// SyncStrategy indicates the
message SyncStrategy {
// Apply wil perform a `kubectl apply` to perform the sync. This is the default strategy
optional SyncStrategyApply apply = 1;
// Hook will submit any referenced resources to perform the sync
optional SyncStrategyHook hook = 2;
}
// SyncStrategyApply uses `kubectl apply` to perform the apply
message SyncStrategyApply {
// Force indicates whether or not to supply the --force flag to `kubectl apply`.
// The --force flag deletes and re-create the resource, when PATCH encounters conflict and has
// retried for 5 times.
optional bool force = 1;
}
// SyncStrategyHook will perform a sync using hooks annotations.
// If no hook annotation is specified falls back to `kubectl apply`.
message SyncStrategyHook {
// Embed SyncStrategyApply type to inherit any `apply` options
optional SyncStrategyApply syncStrategyApply = 1;
}
// TLSClientConfig contains settings to enable transport layer security

View File

@@ -11,7 +11,9 @@ type objectMeta struct {
}
func (a *Application) GetMetadata() *objectMeta {
return &objectMeta{
Name: &a.Name,
var om objectMeta
if a != nil {
om.Name = &a.Name
}
return &om
}

View File

@@ -12,6 +12,7 @@ var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: application.Group, Version: "v1alpha1"}
ApplicationSchemaGroupVersionKind = schema.GroupVersionKind{Group: application.Group, Version: "v1alpha1", Kind: application.ApplicationKind}
AppProjectSchemaGroupVersionKind = schema.GroupVersionKind{Group: application.Group, Version: "v1alpha1", Kind: application.AppProjectKind}
)
// Resource takes an unqualified resource and returns a Group-qualified GroupResource.
@@ -29,6 +30,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Application{},
&ApplicationList{},
&AppProject{},
&AppProjectList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@@ -2,15 +2,172 @@ package v1alpha1
import (
"encoding/json"
"time"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/util/git"
)
// SyncOperation contains sync operation details.
type SyncOperation struct {
// Revision is the git revision in which to sync the application to
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"`
// DryRun will perform a `kubectl apply --dry-run` without actually performing the sync
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"`
}
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"`
}
// 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"`
}
type OperationPhase string
const (
OperationRunning OperationPhase = "Running"
OperationTerminating OperationPhase = "Terminating"
OperationFailed OperationPhase = "Failed"
OperationError OperationPhase = "Error"
OperationSucceeded OperationPhase = "Succeeded"
)
func (os OperationPhase) Completed() bool {
switch os {
case OperationFailed, OperationError, OperationSucceeded:
return true
}
return false
}
func (os OperationPhase) Successful() bool {
return os == OperationSucceeded
}
// OperationState contains information about state of currently performing operation on application.
type OperationState struct {
// Operation is the original requested operation
Operation Operation `json:"operation" protobuf:"bytes,1,opt,name=operation"`
// Phase is the current phase of the operation
Phase OperationPhase `json:"phase" protobuf:"bytes,2,opt,name=phase"`
// Message hold any pertinent messages when attempting to perform operation (typically errors).
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
type SyncStrategy struct {
// Apply wil perform a `kubectl apply` to perform the sync. This is the default strategy
Apply *SyncStrategyApply `json:"apply,omitempty" protobuf:"bytes,1,opt,name=apply"`
// Hook will submit any referenced resources to perform the sync
Hook *SyncStrategyHook `json:"hook,omitempty" protobuf:"bytes,2,opt,name=hook"`
}
// SyncStrategyApply uses `kubectl apply` to perform the apply
type SyncStrategyApply struct {
// Force indicates whether or not to supply the --force flag to `kubectl apply`.
// The --force flag deletes and re-create the resource, when PATCH encounters conflict and has
// retried for 5 times.
Force bool `json:"force,omitempty" protobuf:"bytes,1,opt,name=force"`
}
// SyncStrategyHook will perform a sync using hooks annotations.
// If no hook annotation is specified falls back to `kubectl apply`.
type SyncStrategyHook struct {
// Embed SyncStrategyApply type to inherit any `apply` options
SyncStrategyApply `protobuf:"bytes,1,opt,name=syncStrategyApply"`
}
type HookType string
const (
HookTypePreSync HookType = "PreSync"
HookTypeSync HookType = "Sync"
HookTypePostSync HookType = "PostSync"
HookTypeSkip HookType = "Skip"
// NOTE: we may consider adding SyncFail hook. With a SyncFail hook, finalizer-like logic could
// be implemented by specifying both PostSync,SyncFail in the hook annotation:
// (e.g.: argocd.argoproj.io/hook: PostSync,SyncFail)
//HookTypeSyncFail HookType = "SyncFail"
)
type HookDeletePolicy string
const (
HookDeletePolicyHookSucceeded HookDeletePolicy = "HookSucceeded"
HookDeletePolicyHookFailed HookDeletePolicy = "HookFailed"
)
// HookStatus contains status about a hook invocation
type HookStatus struct {
// Name is the resource name
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// Kind is the resource kind
Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"`
// APIVersion is the resource API version
APIVersion string `json:"apiVersion" protobuf:"bytes,3,opt,name=apiVersion"`
// Type is the type of hook (e.g. PreSync, Sync, PostSync, Skip)
Type HookType `json:"type" protobuf:"bytes,4,opt,name=type"`
// Status a simple, high-level summary of where the resource is in its lifecycle
Status OperationPhase `json:"status" protobuf:"bytes,5,opt,name=status"`
// A human readable message indicating details about why the resource is in this condition.
Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
}
// SyncOperationResult represent result of sync operation
type SyncOperationResult struct {
// Resources holds the sync result of each individual resource
Resources []*ResourceDetails `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"`
// Revision holds the git commit SHA of the sync
Revision string `json:"revision" protobuf:"bytes,2,opt,name=revision"`
// Hooks contains list of hook resource statuses associated with this operation
Hooks []*HookStatus `json:"hooks,omitempty" protobuf:"bytes,3,opt,name=hooks"`
}
type ResourceSyncStatus string
const (
ResourceDetailsSynced ResourceSyncStatus = "Synced"
ResourceDetailsSyncFailed ResourceSyncStatus = "SyncFailed"
ResourceDetailsSyncedAndPruned ResourceSyncStatus = "SyncedAndPruned"
ResourceDetailsPruningRequired ResourceSyncStatus = "PruningRequired"
)
func (s ResourceSyncStatus) Successful() bool {
return s != ResourceDetailsSyncFailed
}
type ResourceDetails struct {
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"`
Namespace string `json:"namespace" protobuf:"bytes,3,opt,name=namespace"`
Message string `json:"message,omitempty" protobuf:"bytes,4,opt,name=message"`
Status ResourceSyncStatus `json:"status,omitempty" protobuf:"bytes,5,opt,name=status"`
}
// DeploymentInfo contains information relevant to an application deployment
type DeploymentInfo struct {
Params []ComponentParameter `json:"params" protobuf:"bytes,1,name=params"`
@@ -29,6 +186,7 @@ type Application struct {
metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
Spec ApplicationSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
Status ApplicationStatus `json:"status" protobuf:"bytes,3,opt,name=status"`
Operation *Operation `json:"operation,omitempty" protobuf:"bytes,4,opt,name=operation"`
}
// ApplicationWatchEvent contains information about application change.
@@ -56,10 +214,9 @@ type ApplicationSpec struct {
// Source is a reference to the location ksonnet application definition
Source ApplicationSource `json:"source" protobuf:"bytes,1,opt,name=source"`
// Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml
// This field is optional. If omitted, uses the server and namespace defined in the environment
Destination *ApplicationDestination `json:"destination,omitempty" protobuf:"bytes,2,opt,name=destination"`
// SyncPolicy dictates whether we auto-sync based on the delta between the tracked branch and live state
SyncPolicy string `json:"syncPolicy,omitempty" protobuf:"bytes,3,opt,name=syncPolicy"`
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"`
}
// ComponentParameter contains information about component parameter value
@@ -97,30 +254,71 @@ type ComparisonStatus string
// Possible comparison results
const (
ComparisonStatusUnknown ComparisonStatus = ""
ComparisonStatusError ComparisonStatus = "Error"
ComparisonStatusUnknown ComparisonStatus = "Unknown"
ComparisonStatusSynced ComparisonStatus = "Synced"
ComparisonStatusOutOfSync ComparisonStatus = "OutOfSync"
)
// ApplicationStatus contains information about application status in target environment.
type ApplicationStatus struct {
ComparisonResult ComparisonResult `json:"comparisonResult" protobuf:"bytes,1,opt,name=comparisonResult"`
RecentDeployments []DeploymentInfo `json:"recentDeployments" protobuf:"bytes,2,opt,name=recentDeployment"`
Parameters []ComponentParameter `json:"parameters,omitempty" protobuf:"bytes,3,opt,name=parameters"`
ComparisonResult ComparisonResult `json:"comparisonResult" protobuf:"bytes,1,opt,name=comparisonResult"`
History []DeploymentInfo `json:"history" protobuf:"bytes,2,opt,name=history"`
Parameters []ComponentParameter `json:"parameters,omitempty" protobuf:"bytes,3,opt,name=parameters"`
Health HealthStatus `json:"health,omitempty" protobuf:"bytes,4,opt,name=health"`
OperationState *OperationState `json:"operationState,omitempty" protobuf:"bytes,5,opt,name=operationState"`
Conditions []ApplicationCondition `json:"conditions,omitempty" protobuf:"bytes,6,opt,name=conditions"`
}
// ApplicationConditionType represents type of application condition. Type name has following convention:
// prefix "Error" means error condition
// prefix "Warning" means warning condition
// prefix "Info" means informational condition
type ApplicationConditionType = string
const (
// ApplicationConditionDeletionError indicates that controller failed to delete application
ApplicationConditionDeletionError = "DeletionError"
// ApplicationConditionInvalidSpecError indicates that application source is invalid
ApplicationConditionInvalidSpecError = "InvalidSpecError"
// ApplicationComparisonError indicates controller failed to compare application state
ApplicationConditionComparisonError = "ComparisonError"
// ApplicationConditionUnknownError indicates an unknown controller error
ApplicationConditionUnknownError = "UnknownError"
// ApplicationConditionSharedResourceWarning indicates that controller detected resources which belongs to more than one application
ApplicationConditionSharedResourceWarning = "SharedResourceWarning"
)
// ApplicationCondition contains details about current application condition
type ApplicationCondition struct {
// Type is an application condition type
Type ApplicationConditionType `json:"type" protobuf:"bytes,1,opt,name=type"`
// Message contains human-readable message indicating details about condition
Message string `json:"message" protobuf:"bytes,2,opt,name=message"`
}
// ComparisonResult is a comparison result of application spec and deployed application.
type ComparisonResult struct {
ComparedAt metav1.Time `json:"comparedAt" protobuf:"bytes,1,opt,name=comparedAt"`
ComparedTo ApplicationSource `json:"comparedTo" protobuf:"bytes,2,opt,name=comparedTo"`
Server string `json:"server" protobuf:"bytes,3,opt,name=server"`
Namespace string `json:"namespace" protobuf:"bytes,4,opt,name=namespace"`
Status ComparisonStatus `json:"status" protobuf:"bytes,5,opt,name=status,casttype=ComparisonStatus"`
Resources []ResourceState `json:"resources" protobuf:"bytes,6,opt,name=resources"`
Error string `json:"error,omitempty" protobuf:"bytes,7,opt,name=error"`
}
type HealthStatus struct {
Status HealthStatusCode `json:"status,omitempty" protobuf:"bytes,1,opt,name=status"`
StatusDetails string `json:"statusDetails,omitempty" protobuf:"bytes,2,opt,name=statusDetails"`
}
type HealthStatusCode = string
const (
HealthStatusUnknown = "Unknown"
HealthStatusProgressing = "Progressing"
HealthStatusHealthy = "Healthy"
HealthStatusDegraded = "Degraded"
HealthStatusMissing = "Missing"
)
// ResourceNode contains information about live resource and its children
type ResourceNode struct {
State string `json:"state,omitempty" protobuf:"bytes,1,opt,name=state"`
@@ -133,6 +331,23 @@ type ResourceState struct {
LiveState string `json:"liveState,omitempty" protobuf:"bytes,2,opt,name=liveState"`
Status ComparisonStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
ChildLiveResources []ResourceNode `json:"childLiveResources,omitempty" protobuf:"bytes,4,opt,name=childLiveResources"`
Health HealthStatus `json:"health,omitempty" protobuf:"bytes,5,opt,name=health"`
}
// ConnectionStatus represents connection status
type ConnectionStatus = string
const (
ConnectionStatusUnknown = "Unknown"
ConnectionStatusSuccessful = "Successful"
ConnectionStatusFailed = "Failed"
)
// ConnectionState contains information about remote resource connection state
type ConnectionState struct {
Status ConnectionStatus `json:"status" protobuf:"bytes,1,opt,name=status"`
Message string `json:"message" protobuf:"bytes,2,opt,name=message"`
ModifiedAt *metav1.Time `json:"attemptedAt" protobuf:"bytes,3,opt,name=attemptedAt"`
}
// Cluster is the definition of a cluster resource
@@ -145,6 +360,9 @@ type Cluster struct {
// Config holds cluster information for connecting to a cluster
Config ClusterConfig `json:"config" protobuf:"bytes,3,opt,name=config"`
// ConnectionState contains information about cluster connection state
ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,4,opt,name=connectionState"`
}
// ClusterList is a collection of Clusters.
@@ -191,10 +409,11 @@ type TLSClientConfig struct {
// Repository is a Git repository holding application configurations
type Repository struct {
Repo string `json:"repo" protobuf:"bytes,1,opt,name=repo"`
Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
Repo string `json:"repo" protobuf:"bytes,1,opt,name=repo"`
Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,5,opt,name=connectionState"`
}
// RepositoryList is a collection of Repositories.
@@ -203,11 +422,87 @@ type RepositoryList struct {
Items []Repository `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// NeedRefreshAppStatus answers if application status needs to be refreshed. Returns true if application never been compared, has changed or comparison result has expired.
func (app *Application) NeedRefreshAppStatus(statusRefreshTimeout time.Duration) bool {
return app.Status.ComparisonResult.Status == ComparisonStatusUnknown ||
!app.Spec.Source.Equals(app.Status.ComparisonResult.ComparedTo) ||
app.Status.ComparisonResult.ComparedAt.Add(statusRefreshTimeout).Before(time.Now())
// AppProjectList is list of AppProject resources
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type AppProjectList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
Items []AppProject `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// AppProject is a definition of AppProject resource.
// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type AppProject struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
Spec AppProjectSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
}
// AppProjectSpec represents
type AppProjectSpec struct {
// SourceRepos contains list of git repository URLs which can be used for deployment
SourceRepos []string `json:"sources" protobuf:"bytes,1,name=destination"`
// Destinations contains list of destinations available for deployment
Destinations []ApplicationDestination `json:"destinations" protobuf:"bytes,2,name=destination"`
// Description contains optional project description
Description string `json:"description,omitempty" protobuf:"bytes,3,opt,name=description"`
}
func GetDefaultProject(namespace string) AppProject {
return AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: common.DefaultAppProjectName,
Namespace: namespace,
},
}
}
func (app *Application) getFinalizerIndex(name string) int {
for i, finalizer := range app.Finalizers {
if finalizer == name {
return i
}
}
return -1
}
// CascadedDeletion indicates if resources finalizer is set and controller should delete app resources before deleting app
func (app *Application) CascadedDeletion() bool {
return app.getFinalizerIndex(common.ResourcesFinalizerName) > -1
}
// SetCascadedDeletion sets or remove resources finalizer
func (app *Application) SetCascadedDeletion(prune bool) {
index := app.getFinalizerIndex(common.ResourcesFinalizerName)
if prune != (index > -1) {
if index > -1 {
app.Finalizers[index] = app.Finalizers[len(app.Finalizers)-1]
app.Finalizers = app.Finalizers[:len(app.Finalizers)-1]
} else {
app.Finalizers = append(app.Finalizers, common.ResourcesFinalizerName)
}
}
}
// GetErrorConditions returns list of application error conditions
func (status *ApplicationStatus) GetErrorConditions() []ApplicationCondition {
result := make([]ApplicationCondition, 0)
for i := range status.Conditions {
condition := status.Conditions[i]
if condition.IsError() {
result = append(result, condition)
}
}
return result
}
// IsError returns true if condition is error condition
func (condition *ApplicationCondition) IsError() bool {
return strings.HasSuffix(condition.Type, "Error")
}
// Equals compares two instances of ApplicationSource and return true if instances are equal.
@@ -218,6 +513,46 @@ func (source ApplicationSource) Equals(other ApplicationSource) bool {
source.Environment == other.Environment
}
func (spec ApplicationSpec) BelongsToDefaultProject() bool {
return spec.GetProject() == common.DefaultAppProjectName
}
func (spec ApplicationSpec) GetProject() string {
if spec.Project == "" {
return common.DefaultAppProjectName
}
return spec.Project
}
func (proj AppProject) IsDefault() bool {
return proj.Name == "" || proj.Name == common.DefaultAppProjectName
}
func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
if proj.IsDefault() {
return true
}
normalizedURL := git.NormalizeGitURL(src.RepoURL)
for _, repoURL := range proj.Spec.SourceRepos {
if git.NormalizeGitURL(repoURL) == normalizedURL {
return true
}
}
return false
}
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
}
}
return false
}
// RESTConfig returns a go-client REST config from cluster
func (c *Cluster) RESTConfig() *rest.Config {
return &rest.Config{
@@ -239,7 +574,7 @@ func (c *Cluster) RESTConfig() *rest.Config {
func (cr *ComparisonResult) TargetObjects() ([]*unstructured.Unstructured, error) {
objs := make([]*unstructured.Unstructured, len(cr.Resources))
for i, resState := range cr.Resources {
obj, err := UnmarshalToUnstructured(resState.TargetState)
obj, err := resState.TargetObject()
if err != nil {
return nil, err
}
@@ -252,7 +587,7 @@ func (cr *ComparisonResult) TargetObjects() ([]*unstructured.Unstructured, error
func (cr *ComparisonResult) LiveObjects() ([]*unstructured.Unstructured, error) {
objs := make([]*unstructured.Unstructured, len(cr.Resources))
for i, resState := range cr.Resources {
obj, err := UnmarshalToUnstructured(resState.LiveState)
obj, err := resState.LiveObject()
if err != nil {
return nil, err
}
@@ -272,3 +607,11 @@ func UnmarshalToUnstructured(resource string) (*unstructured.Unstructured, error
}
return &obj, nil
}
func (r ResourceState) LiveObject() (*unstructured.Unstructured, error) {
return UnmarshalToUnstructured(r.LiveState)
}
func (r ResourceState) TargetObject() (*unstructured.Unstructured, error) {
return UnmarshalToUnstructured(r.TargetState)
}

View File

@@ -5,9 +5,96 @@
package v1alpha1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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 *AppProject) DeepCopyInto(out *AppProject) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProject.
func (in *AppProject) DeepCopy() *AppProject {
if in == nil {
return nil
}
out := new(AppProject)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AppProject) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppProjectList) DeepCopyInto(out *AppProjectList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]AppProject, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProjectList.
func (in *AppProjectList) DeepCopy() *AppProjectList {
if in == nil {
return nil
}
out := new(AppProjectList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AppProjectList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppProjectSpec) DeepCopyInto(out *AppProjectSpec) {
*out = *in
if in.SourceRepos != nil {
in, out := &in.SourceRepos, &out.SourceRepos
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Destinations != nil {
in, out := &in.Destinations, &out.Destinations
*out = make([]ApplicationDestination, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProjectSpec.
func (in *AppProjectSpec) DeepCopy() *AppProjectSpec {
if in == nil {
return nil
}
out := new(AppProjectSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Application) DeepCopyInto(out *Application) {
*out = *in
@@ -15,6 +102,15 @@ func (in *Application) DeepCopyInto(out *Application) {
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
if in.Operation != nil {
in, out := &in.Operation, &out.Operation
if *in == nil {
*out = nil
} else {
*out = new(Operation)
(*in).DeepCopyInto(*out)
}
}
return
}
@@ -36,6 +132,22 @@ func (in *Application) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationCondition) DeepCopyInto(out *ApplicationCondition) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCondition.
func (in *ApplicationCondition) DeepCopy() *ApplicationCondition {
if in == nil {
return nil
}
out := new(ApplicationCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDestination) DeepCopyInto(out *ApplicationDestination) {
*out = *in
@@ -110,15 +222,7 @@ func (in *ApplicationSource) DeepCopy() *ApplicationSource {
func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
*out = *in
in.Source.DeepCopyInto(&out.Source)
if in.Destination != nil {
in, out := &in.Destination, &out.Destination
if *in == nil {
*out = nil
} else {
*out = new(ApplicationDestination)
**out = **in
}
}
out.Destination = in.Destination
return
}
@@ -136,8 +240,8 @@ func (in *ApplicationSpec) DeepCopy() *ApplicationSpec {
func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
*out = *in
in.ComparisonResult.DeepCopyInto(&out.ComparisonResult)
if in.RecentDeployments != nil {
in, out := &in.RecentDeployments, &out.RecentDeployments
if in.History != nil {
in, out := &in.History, &out.History
*out = make([]DeploymentInfo, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
@@ -148,6 +252,21 @@ func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
*out = make([]ComponentParameter, len(*in))
copy(*out, *in)
}
out.Health = in.Health
if in.OperationState != nil {
in, out := &in.OperationState, &out.OperationState
if *in == nil {
*out = nil
} else {
*out = new(OperationState)
(*in).DeepCopyInto(*out)
}
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]ApplicationCondition, len(*in))
copy(*out, *in)
}
return
}
@@ -182,6 +301,7 @@ func (in *ApplicationWatchEvent) DeepCopy() *ApplicationWatchEvent {
func (in *Cluster) DeepCopyInto(out *Cluster) {
*out = *in
in.Config.DeepCopyInto(&out.Config)
in.ConnectionState.DeepCopyInto(&out.ConnectionState)
return
}
@@ -277,6 +397,31 @@ func (in *ComponentParameter) DeepCopy() *ComponentParameter {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConnectionState) DeepCopyInto(out *ConnectionState) {
*out = *in
if in.ModifiedAt != nil {
in, out := &in.ModifiedAt, &out.ModifiedAt
if *in == nil {
*out = nil
} else {
*out = new(v1.Time)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionState.
func (in *ConnectionState) DeepCopy() *ConnectionState {
if in == nil {
return nil
}
out := new(ConnectionState)
in.DeepCopyInto(out)
return out
}
// 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
@@ -304,9 +449,121 @@ func (in *DeploymentInfo) DeepCopy() *DeploymentInfo {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HealthStatus) DeepCopyInto(out *HealthStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthStatus.
func (in *HealthStatus) DeepCopy() *HealthStatus {
if in == nil {
return nil
}
out := new(HealthStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HookStatus) DeepCopyInto(out *HookStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HookStatus.
func (in *HookStatus) DeepCopy() *HookStatus {
if in == nil {
return nil
}
out := new(HookStatus)
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
if in.Sync != nil {
in, out := &in.Sync, &out.Sync
if *in == nil {
*out = nil
} else {
*out = new(SyncOperation)
(*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
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Operation.
func (in *Operation) DeepCopy() *Operation {
if in == nil {
return nil
}
out := new(Operation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OperationState) DeepCopyInto(out *OperationState) {
*out = *in
in.Operation.DeepCopyInto(&out.Operation)
if in.SyncResult != nil {
in, out := &in.SyncResult, &out.SyncResult
if *in == nil {
*out = nil
} else {
*out = new(SyncOperationResult)
(*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
if *in == nil {
*out = nil
} else {
*out = new(v1.Time)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperationState.
func (in *OperationState) DeepCopy() *OperationState {
if in == nil {
return nil
}
out := new(OperationState)
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
in.ConnectionState.DeepCopyInto(&out.ConnectionState)
return
}
@@ -327,7 +584,9 @@ func (in *RepositoryList) DeepCopyInto(out *RepositoryList) {
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Repository, len(*in))
copy(*out, *in)
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
@@ -342,6 +601,22 @@ func (in *RepositoryList) DeepCopy() *RepositoryList {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceDetails) DeepCopyInto(out *ResourceDetails) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceDetails.
func (in *ResourceDetails) DeepCopy() *ResourceDetails {
if in == nil {
return nil
}
out := new(ResourceDetails)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceNode) DeepCopyInto(out *ResourceNode) {
*out = *in
@@ -375,6 +650,7 @@ func (in *ResourceState) DeepCopyInto(out *ResourceState) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Health = in.Health
return
}
@@ -388,6 +664,154 @@ 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
if in.SyncStrategy != nil {
in, out := &in.SyncStrategy, &out.SyncStrategy
if *in == nil {
*out = nil
} else {
*out = new(SyncStrategy)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncOperation.
func (in *SyncOperation) DeepCopy() *SyncOperation {
if in == nil {
return nil
}
out := new(SyncOperation)
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
if in.Resources != nil {
in, out := &in.Resources, &out.Resources
*out = make([]*ResourceDetails, len(*in))
for i := range *in {
if (*in)[i] == nil {
(*out)[i] = nil
} else {
(*out)[i] = new(ResourceDetails)
(*in)[i].DeepCopyInto((*out)[i])
}
}
}
if in.Hooks != nil {
in, out := &in.Hooks, &out.Hooks
*out = make([]*HookStatus, len(*in))
for i := range *in {
if (*in)[i] == nil {
(*out)[i] = nil
} else {
(*out)[i] = new(HookStatus)
(*in)[i].DeepCopyInto((*out)[i])
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncOperationResult.
func (in *SyncOperationResult) DeepCopy() *SyncOperationResult {
if in == nil {
return nil
}
out := new(SyncOperationResult)
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
if in.Apply != nil {
in, out := &in.Apply, &out.Apply
if *in == nil {
*out = nil
} else {
*out = new(SyncStrategyApply)
**out = **in
}
}
if in.Hook != nil {
in, out := &in.Hook, &out.Hook
if *in == nil {
*out = nil
} else {
*out = new(SyncStrategyHook)
**out = **in
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncStrategy.
func (in *SyncStrategy) DeepCopy() *SyncStrategy {
if in == nil {
return nil
}
out := new(SyncStrategy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncStrategyApply) DeepCopyInto(out *SyncStrategyApply) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncStrategyApply.
func (in *SyncStrategyApply) DeepCopy() *SyncStrategyApply {
if in == nil {
return nil
}
out := new(SyncStrategyApply)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncStrategyHook) DeepCopyInto(out *SyncStrategyHook) {
*out = *in
out.SyncStrategyApply = in.SyncStrategyApply
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncStrategyHook.
func (in *SyncStrategyHook) DeepCopy() *SyncStrategyHook {
if in == nil {
return nil
}
out := new(SyncStrategyHook)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSClientConfig) DeepCopyInto(out *TLSClientConfig) {
*out = *in

View File

@@ -25,7 +25,15 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset {
fakePtr := testing.Fake{}
fakePtr.AddReactor("*", "*", testing.ObjectReaction(o))
fakePtr.AddWatchReactor("*", testing.DefaultWatchReactor(watch.NewFake(), nil))
fakePtr.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
gvr := action.GetResource()
ns := action.GetNamespace()
watch, err := o.Watch(gvr, ns)
if err != nil {
return false, nil, err
}
return true, watch, nil
})
return &Clientset{fakePtr, &fakediscovery.FakeDiscovery{Fake: &fakePtr}}
}

View File

@@ -22,7 +22,7 @@ func init() {
//
// import (
// "k8s.io/client-go/kubernetes"
// clientsetscheme "k8s.io/client-go/kuberentes/scheme"
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
// )
//
@@ -33,5 +33,4 @@ func init() {
// correctly.
func AddToScheme(scheme *runtime.Scheme) {
argoprojv1alpha1.AddToScheme(scheme)
}

View File

@@ -22,7 +22,7 @@ func init() {
//
// import (
// "k8s.io/client-go/kubernetes"
// clientsetscheme "k8s.io/client-go/kuberentes/scheme"
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
// )
//
@@ -33,5 +33,4 @@ func init() {
// correctly.
func AddToScheme(scheme *runtime.Scheme) {
argoprojv1alpha1.AddToScheme(scheme)
}

View File

@@ -9,6 +9,7 @@ import (
type ArgoprojV1alpha1Interface interface {
RESTClient() rest.Interface
AppProjectsGetter
ApplicationsGetter
}
@@ -17,6 +18,10 @@ type ArgoprojV1alpha1Client struct {
restClient rest.Interface
}
func (c *ArgoprojV1alpha1Client) AppProjects(namespace string) AppProjectInterface {
return newAppProjects(c, namespace)
}
func (c *ArgoprojV1alpha1Client) Applications(namespace string) ApplicationInterface {
return newApplications(c, namespace)
}

View File

@@ -0,0 +1,139 @@
package v1alpha1
import (
v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
scheme "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// AppProjectsGetter has a method to return a AppProjectInterface.
// A group's client should implement this interface.
type AppProjectsGetter interface {
AppProjects(namespace string) AppProjectInterface
}
// AppProjectInterface has methods to work with AppProject resources.
type AppProjectInterface interface {
Create(*v1alpha1.AppProject) (*v1alpha1.AppProject, error)
Update(*v1alpha1.AppProject) (*v1alpha1.AppProject, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha1.AppProject, error)
List(opts v1.ListOptions) (*v1alpha1.AppProjectList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.AppProject, err error)
AppProjectExpansion
}
// appProjects implements AppProjectInterface
type appProjects struct {
client rest.Interface
ns string
}
// newAppProjects returns a AppProjects
func newAppProjects(c *ArgoprojV1alpha1Client, namespace string) *appProjects {
return &appProjects{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the appProject, and returns the corresponding appProject object, and an error if there is any.
func (c *appProjects) Get(name string, options v1.GetOptions) (result *v1alpha1.AppProject, err error) {
result = &v1alpha1.AppProject{}
err = c.client.Get().
Namespace(c.ns).
Resource("appprojects").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of AppProjects that match those selectors.
func (c *appProjects) List(opts v1.ListOptions) (result *v1alpha1.AppProjectList, err error) {
result = &v1alpha1.AppProjectList{}
err = c.client.Get().
Namespace(c.ns).
Resource("appprojects").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested appProjects.
func (c *appProjects) Watch(opts v1.ListOptions) (watch.Interface, error) {
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("appprojects").
VersionedParams(&opts, scheme.ParameterCodec).
Watch()
}
// Create takes the representation of a appProject and creates it. Returns the server's representation of the appProject, and an error, if there is any.
func (c *appProjects) Create(appProject *v1alpha1.AppProject) (result *v1alpha1.AppProject, err error) {
result = &v1alpha1.AppProject{}
err = c.client.Post().
Namespace(c.ns).
Resource("appprojects").
Body(appProject).
Do().
Into(result)
return
}
// Update takes the representation of a appProject and updates it. Returns the server's representation of the appProject, and an error, if there is any.
func (c *appProjects) Update(appProject *v1alpha1.AppProject) (result *v1alpha1.AppProject, err error) {
result = &v1alpha1.AppProject{}
err = c.client.Put().
Namespace(c.ns).
Resource("appprojects").
Name(appProject.Name).
Body(appProject).
Do().
Into(result)
return
}
// Delete takes name of the appProject and deletes it. Returns an error if one occurs.
func (c *appProjects) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("appprojects").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *appProjects) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("appprojects").
VersionedParams(&listOptions, scheme.ParameterCodec).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched appProject.
func (c *appProjects) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.AppProject, err error) {
result = &v1alpha1.AppProject{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("appprojects").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@@ -10,6 +10,10 @@ type FakeArgoprojV1alpha1 struct {
*testing.Fake
}
func (c *FakeArgoprojV1alpha1) AppProjects(namespace string) v1alpha1.AppProjectInterface {
return &FakeAppProjects{c, namespace}
}
func (c *FakeArgoprojV1alpha1) Applications(namespace string) v1alpha1.ApplicationInterface {
return &FakeApplications{c, namespace}
}

View File

@@ -0,0 +1,110 @@
package fake
import (
v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeAppProjects implements AppProjectInterface
type FakeAppProjects struct {
Fake *FakeArgoprojV1alpha1
ns string
}
var appprojectsResource = schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "appprojects"}
var appprojectsKind = schema.GroupVersionKind{Group: "argoproj.io", Version: "v1alpha1", Kind: "AppProject"}
// Get takes name of the appProject, and returns the corresponding appProject object, and an error if there is any.
func (c *FakeAppProjects) Get(name string, options v1.GetOptions) (result *v1alpha1.AppProject, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(appprojectsResource, c.ns, name), &v1alpha1.AppProject{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.AppProject), err
}
// List takes label and field selectors, and returns the list of AppProjects that match those selectors.
func (c *FakeAppProjects) List(opts v1.ListOptions) (result *v1alpha1.AppProjectList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(appprojectsResource, appprojectsKind, c.ns, opts), &v1alpha1.AppProjectList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.AppProjectList{}
for _, item := range obj.(*v1alpha1.AppProjectList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested appProjects.
func (c *FakeAppProjects) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(appprojectsResource, c.ns, opts))
}
// Create takes the representation of a appProject and creates it. Returns the server's representation of the appProject, and an error, if there is any.
func (c *FakeAppProjects) Create(appProject *v1alpha1.AppProject) (result *v1alpha1.AppProject, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(appprojectsResource, c.ns, appProject), &v1alpha1.AppProject{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.AppProject), err
}
// Update takes the representation of a appProject and updates it. Returns the server's representation of the appProject, and an error, if there is any.
func (c *FakeAppProjects) Update(appProject *v1alpha1.AppProject) (result *v1alpha1.AppProject, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(appprojectsResource, c.ns, appProject), &v1alpha1.AppProject{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.AppProject), err
}
// Delete takes name of the appProject and deletes it. Returns an error if one occurs.
func (c *FakeAppProjects) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(appprojectsResource, c.ns, name), &v1alpha1.AppProject{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeAppProjects) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(appprojectsResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha1.AppProjectList{})
return err
}
// Patch applies the patch and returns the patched appProject.
func (c *FakeAppProjects) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.AppProject, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(appprojectsResource, c.ns, name, data, subresources...), &v1alpha1.AppProject{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.AppProject), err
}

View File

@@ -1,3 +1,5 @@
package v1alpha1
type AppProjectExpansion interface{}
type ApplicationExpansion interface{}

View File

@@ -1,5 +1,3 @@
// This file was automatically generated by informer-gen
package argoproj
import (

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