Compare commits

...

870 Commits

Author SHA1 Message Date
Alex Collins
3aaca0bbe8 Sets app status to unknown if there is an error. Closes #2577 (#2578) 2019-10-29 11:45:42 -07:00
Alexander Matyushentsev
85f62dff9e Update manifests to v1.2.5 2019-10-28 17:00:52 -07:00
Alexander Matyushentsev
97f08aaf4c Issue #2339 - Don't update 'status.reconciledAt' unless compared with latest git version (#2581) 2019-10-28 16:59:37 -07:00
Alex Collins
ad7a69a798 Adds flags for codegen and lint 2019-10-23 12:50:11 -07:00
Alex Collins
fc588538bc Increase test timeout to 10s as per master 2019-10-23 10:41:18 -07:00
Alexander Matyushentsev
675ae6a991 Issue #2185 - Manual sync don't trigger hooks (#2477) 2019-10-23 10:33:05 -07:00
Alex Collins
6cc0fd8b8c Increases lint deadline to 4m 2019-10-23 09:33:44 -07:00
Alexander Matyushentsev
26e1cf0438 Issue #2339 - Controller should compare with latest git revision if app has changed (#2543) 2019-10-22 15:28:23 -07:00
Alexander Matyushentsev
30d3fcb84a Unknown child app should not affect app health (#2544) 2019-10-22 15:28:17 -07:00
Alex Collins
3a3a7f5044 Update manifests to v1.2.4 2019-10-22 10:50:42 -07:00
Simon Behar
6c34dd828f Redact secrets in dex logs (#2538)
* Done

* Pre-commit

* Added test

* Pre-commit

* Goimports
2019-10-22 10:46:25 -07:00
Fred Dubois
de7003f530 Make argo-cd docker images openshift friendly (#2362)
In openshift clusters, the user id of your container can be arbitrary,
so you need to make the running images compatible with this behavior.

The problematic application for argo-cd was the repo server. When trying
to clone the repos it was getting the error "No user exists for uid
100083000" (100083000 was the random user id being injected by
openshift in my case). This was because the user 100083000 wasn't in the
/etc/passwd file.

The changes in this commit add a uid_entrypoint.sh script that, when the
container starts, modifies the /etc/passwd file to add an entry with the
current UID _only_ if the current UID isn't there.

References:
* Problematic behavior of ssh when user id isn't in the /etc/passwd file:
  https://unix.stackexchange.com/questions/524268/running-git-or-ssh-client-in-docker-as-user-no-user-exists-for-uid
* OpenShift guidelines on how to make your docker image runnable by
  arbitrary user ids:
  https://access.redhat.com/documentation/en-us/openshift_container_platform/3.11/html/creating_images/creating-images-guidelines#use-uid
2019-10-01 12:44:47 -07:00
Alexander Matyushentsev
8981903603 Update manifests to v1.2.3 2019-10-01 11:56:35 -07:00
Alexander Matyushentsev
509c567a40 Stop loggin /repository.RepositoryService/ValidateAccess parameters (#2387) 2019-10-01 11:55:00 -07:00
Alexander Matyushentsev
c722a71820 Add dest-server and dest-namespace field to reconciliation logs (#2388) 2019-10-01 11:54:55 -07:00
Alexander Matyushentsev
1aaf76f230 Update manifests to v1.2.2 2019-09-24 07:53:42 -07:00
Gregor Krmelj
71ad60e89f Add cache-control HTTP header to badge response (#2328)
Since we serve the badge as an image using HTTP GET, cache systems
(incl. GitHub's CDN - Fastly) like to cache the image thus the
badge becomes stale rendering it useless. Adding the appropriate
Cache-Control HTTP header we direct cache systems and web browsers
not to cache the contents of the response.
2019-09-24 07:45:06 -07:00
Gustav Paul
9c46f77bb1 util/localconfig: prefer HOME env var over os/user (#2326)
* util/localconfig: prefer HOME env var over os/user

The os/user package requires that the current user be in /etc/passwd.
That complicates executing the argocd command in a docker container
when the UID:GID of the executing user is overridden.

This is often done in order to have files generated inside a docker
container have their ownership set to match the uid/gid of the host
user.

For example,

```sh
docker run -ti -u "$(id -u "${USER}"):$(id -g "${USER}")" argocd:latest ...
```

* Makefile: use pinned dev image dependencies to run make lint
2019-09-24 07:21:52 -07:00
Alexander Matyushentsev
b6256aae9f Codegen (#2343) 2019-09-23 14:22:49 -07:00
Alexander Matyushentsev
f802190a24 Issue #2339 - Make sure controller uses latest git version if app reconciliation result expired (#2346) 2019-09-23 14:22:36 -07:00
Alexander Matyushentsev
e07c1edece Don't fix imports in auto-generated files (#2342) 2019-09-23 14:22:31 -07:00
Alex Collins
ed15da5271 Adds support for Github Enterprise URLs (#2344) 2019-09-23 13:24:52 -07:00
Jesse Suen
d34dbeac0d Add restart action to Deployment/StatefulSet/DaemonSet (#2300) 2019-09-13 02:57:25 -07:00
Alexander Matyushentsev
c4d3a54126 Merge branch 'release-1.2' of github.com:argoproj/argo-cd into release-1.2 2019-09-12 10:26:23 -07:00
Alex Collins
622671ece4 removed e2e tests that do not work and I should not have merged 2019-09-12 10:08:49 -07:00
Alex Collins
cf6a7abd30 ported FailOnErr from master 2019-09-12 09:56:43 -07:00
Alexander Matyushentsev
a6a394ba93 Update manifests to v1.2.1 2019-09-12 09:56:15 -07:00
Alex Collins
d315814020 Fixes issue diffing secrets (#2271)
# Conflicts:
#	test/e2e/app_management_test.go
2019-09-12 09:55:15 -07:00
Seiya Muramatsu
d46872d7e8 Add --self-heal flag to argocd cli (#2296) 2019-09-12 09:45:14 -07:00
Alexander Matyushentsev
ba7f0fcb47 Issue #2290 - Fix nil pointer dereference in application controller (#2291) 2019-09-12 09:44:59 -07:00
Alexander Matyushentsev
5fcfc22298 Issue #2022 - Support limiting number of concurrent kubectl fork/execs (#2264) 2019-09-12 09:43:35 -07:00
Mitz Amano
9e486dfad4 Fix degraded proxy support for http(s) git repository (#2243) (#2249) 2019-09-05 15:38:45 -07:00
Alexander Matyushentsev
674978cd58 Update manifests to v1.2.0 2019-09-04 13:51:54 -07:00
Alexander Matyushentsev
020d284a00 Add missing pending method 2019-09-04 11:49:13 -07:00
Alex Collins
09b874613d If there is only one wave and no pre/post hooks, we should be synced.… (#2217) 2019-08-27 08:57:42 -07:00
Alex Collins
44cb2ce51a codegen 2019-08-26 13:56:25 -07:00
Alex Collins
6eaed1e64e Fix for displaying hooks in app diff view. Fixes #2215 (#2218)
* Duct tape fix for displaying hooks in app diff view. Fixes #2215

* ""operationId": "ListMixin7"," to swagger.json

* "for _, item := range items {" to app.go
2019-08-26 13:51:18 -07:00
Alex Collins
2a63b44af0 Redact secrets using "+" rather than "*" as this is base 64 compatiba… (#2119) 2019-08-23 13:46:34 -07:00
Alexander Matyushentsev
5571cf1333 Update codegen 2019-08-21 08:20:45 -07:00
Alexander Matyushentsev
ed0add3087 Update manifests to v1.2.0-rc2 2019-08-20 15:45:24 -07:00
Alex Collins
d27849cdc4 Adds a floating action button with help and chat links to every page.… (#2125) 2019-08-20 10:35:31 -07:00
Alexander Matyushentsev
3a3f490abf Issue #2174 - Fix git repo url parsing on application list view (#2175) 2019-08-20 09:30:33 -07:00
Alexander Matyushentsev
2dc95fffb7 Issue #2146 - Fix nil pointer dereference error during app reconciliation (#2170) 2019-08-20 08:56:43 -07:00
Alexander Matyushentsev
9cf978c168 Temporary disable Git LFS test to unblock release (#2172) 2019-08-20 08:20:10 -07:00
Alex Collins
3891b29d82 Determine the manifest version from the VERSION file when on release branch (#2166) 2019-08-20 08:20:06 -07:00
Alexander Matyushentsev
ed916702d6 Issue #2114 - Fix history api fallback implementation to support app names with dots (#2168) 2019-08-20 08:20:02 -07:00
Alex Collins
c4eba32f0e Enhances cookie warning with actual length to help users fix their co… (#2134) 2019-08-16 14:08:03 -07:00
Alex Collins
046a62420e Fixes some code issues related to Kustomize build options. See #2146 (#2151) 2019-08-16 12:56:06 -07:00
Simon Behar
1b393bc473 Added 'SyncFail' to possible HookTypes in UI (#2153) 2019-08-14 14:29:48 -07:00
jannfis
d8c38bb45b Fix and enhance end-to-end testing for SSH repositories (#2101)
* Fix and enhance end-to-end testing for SSH repositories
2019-08-14 10:42:23 -07:00
Alex Collins
249ce9317f Adds checks around valid paths for apps (#2133) 2019-08-09 14:26:31 -07:00
Alex Collins
a094d5abb8 Minor CLI bug fixes (#2132) 2019-08-09 13:27:02 -07:00
Alexander Matyushentsev
3f31224a6e Issue #2060 - Enpoint incorrectly considered top level managed resource (#2129) 2019-08-09 11:52:55 -07:00
jannfis
649b1b7b75 Allow adding certs for hostnames ending on a dot (fixes #2116) (#2120) 2019-08-08 17:14:33 -07:00
Alexander Matyushentsev
2c691a874b Update manifests to v1.2.0-rc1 2019-08-06 10:40:47 -07:00
Alex Collins
e3e4ae5d2e Do not panic if the type is not api.Status (an error scenario). Closes #2105 (#2106) 2019-08-06 00:24:22 -07:00
Alexander Matyushentsev
c47cc25690 Run dockerized manifests generation during release (#2107) 2019-08-06 00:24:03 -07:00
Alexander Matyushentsev
c2ee30f49f Issue #2073 - Fix status column width on resources list table (#2104) 2019-08-05 16:34:51 -07:00
jannfis
ea8b7c6ac9 Update private repository documentation to reflect latest changes (#2102) 2019-08-05 15:22:30 -07:00
Alexander Matyushentsev
9bc5f26b41 Fix flaky TestDeletingAppStuckInSync test (#2100) 2019-08-05 10:12:03 -07:00
jannfis
1e81f2a163 Export TLS and SSH known hosts settings and TLS client cert secrets in argocd-util (#2098) 2019-08-05 09:37:15 -07:00
jannfis
f6240f0dea Correctly set required env vars for running E2E tests locally (#2097) 2019-08-04 11:35:00 -07:00
Alexander Matyushentsev
52e7a839d6 Support Jsonnet TLA code and string args (#2096) 2019-08-02 16:58:09 -07:00
Alexander Matyushentsev
31ec4cf615 Issue #2060 - Make sure endpoint is shown as a child of service (#2080) 2019-08-02 16:57:54 -07:00
Simon Behar
3a9034e68f Added Kustomize build options to settings/argocd-cm (#1817)
Added Kustomize build options to settings/argocd-cm (#1817)
2019-08-02 16:57:33 -07:00
Alex Collins
583be34815 Fixes TestDeletingAppStuckInSync. Closes #2087 (#2093) 2019-08-02 15:06:59 -07:00
Alex Collins
bc77e8ce12 Fixes TestArgoCDWaitEnsureAppIsNotCrashing. Closes #2088 (#2092) 2019-08-02 15:06:45 -07:00
Alex Collins
5f2dc0e9e0 Skip TestAppOfApps. Closes #2089 (#2091) 2019-08-02 15:06:35 -07:00
Alex Collins
b49a0ce7d5 Remove support for Kustomize 1. Closes #1573 (#2077) 2019-08-02 13:13:27 -07:00
JB Volta
5d5c8168d4 Update metrics doc (#2079) 2019-08-02 13:07:24 -07:00
Alexander Matyushentsev
5e0f3fc81c Update releasing docs (#2084) 2019-08-02 11:48:13 -07:00
Alexander Matyushentsev
daad4306bd Update VERSION file (#2083) 2019-08-02 11:47:27 -07:00
Alexander Matyushentsev
cdfa8c1561 Issue #2081 - Fix empty manifest tab error (#2082) 2019-08-02 11:46:22 -07:00
Alex Collins
4e15036cad Adds support for argocd app set for Kustomize. Closes #1843 (#2055) 2019-08-02 11:39:24 -07:00
Alex Collins
2780f81fbb Allow users to create tokens for projects where they have any role. C… (#2066) 2019-08-01 17:03:07 -07:00
Alexander Matyushentsev
2114189bef Issue #2074 - Add UI to manage sync options (#2076) 2019-08-01 16:26:27 -07:00
Alexander Matyushentsev
588d30268b Upgrade kustomize to v3.1.0 (#2072) 2019-08-01 11:27:08 -07:00
jannfis
399b33df48 Change field names to camelCase in RepositoryCertificate of cert API (#2071) 2019-08-01 10:09:15 -07:00
jannfis
2742ead047 Bugfix: Ensure we have a valid hostname when adding certificates (#2064) 2019-08-01 08:29:04 -07:00
Alexander Matyushentsev
dacdeb6025 Improve wrapped text rendering on application list and application sync status pages (#2067) 2019-07-31 18:39:41 -07:00
jannfis
29b72ca695 Add first-class support for connecting repositories via UI (#2043)
* Add first-class support for connecting repositories via UI

* Fix height of TextArea and title of add HTTPS repo dialogue
2019-07-31 17:38:34 -07:00
Alexander Matyushentsev
355e949a87 Make sure applications filter is expanded on xlarge and xxlarge screens (#2065) 2019-07-31 14:26:07 -07:00
Alex Collins
47804103d9 Moves talks into one place (#2063) 2019-07-31 11:22:08 -07:00
jannfis
df6a66e580 Update user documentation on private repositories feature (#2062) 2019-07-31 09:30:18 -07:00
jannfis
4257031c3f Move repo certificate info generation to server side (#2044) 2019-07-31 09:29:26 -07:00
Alexander Matyushentsev
8da326926b Make sure applications page has 4 tiles on very large screen (#2059) 2019-07-30 17:14:15 -07:00
Alex Collins
935ffc167e Bump Circle CI cache key versions. (#2058) 2019-07-30 16:34:32 -07:00
Alexander Matyushentsev
5d27eaab9b Install missing go tools in CI job (#2054) 2019-07-30 13:53:30 -07:00
Alexander Matyushentsev
4a00634ac1 Improve refresh button on applications tiles view (#2053) 2019-07-30 13:50:45 -07:00
Alexander Matyushentsev
d6b97c93f5 Add v1.x changelog (#2051) 2019-07-30 13:48:48 -07:00
jannfis
c51645be35 Build UI into argocd Docker image (#2052) 2019-07-30 13:07:20 -07:00
Alexander Matyushentsev
a24184bb7a Issue #2049 - 'argocd app wait' should print correct sync status (#2050) 2019-07-30 10:30:51 -07:00
Alex Collins
3f34667dc7 Word-wraps app info in the table and list views. Closes #2004 (#2026) 2019-07-29 16:14:36 -07:00
Alex Collins
4ec2ed3fe6 Adds a refresh button to list and tile view. Closes #1606 (#2027) 2019-07-29 15:32:16 -07:00
jannfis
488bdcf0e3 Build argocd-repo-server and argocd-util with packr (#2041) 2019-07-29 10:21:19 -07:00
Alexander Matyushentsev
2cac22bc55 Delete obsolete comment (#2042) 2019-07-29 10:17:49 -07:00
jannfis
351e964e4e Allow codegen to run as non-root user in the Docker container (#2032) 2019-07-26 10:46:45 -07:00
Alex Collins
5e4f327edd Shows correct revision metadata for. Closes #2028 (#2029) 2019-07-26 08:50:37 -07:00
Alex Collins
47e5285544 Removes width limitation and adjusts column widths for resource list … (#2025) 2019-07-26 08:50:11 -07:00
Alex Collins
3445f448ac Run yarn build on CI. (#2024) 2019-07-26 08:49:44 -07:00
Ed Lee
3cf00496b3 Update OWNERS (#2030) 2019-07-25 16:37:58 -07:00
Alexander Matyushentsev
81719d3150 Issue #1780 - Project source/destination removal should consider wildcards (#2023) 2019-07-25 15:31:18 -07:00
Alex Collins
d9bdc3b125 Word wraps error message. Closes #1942 (#2011) 2019-07-25 13:22:46 -07:00
Alex Collins
0fb8680da6 Adds CLI support for adding and removing groups from project roles. C… (#2013) 2019-07-25 11:05:06 -07:00
Alex Collins
2e523bfb93 Update yarn.lock (#2012) 2019-07-25 09:29:35 -07:00
Lev Aminov
0ef5061e11 Allow Apply Only on sync (#2015) 2019-07-25 09:13:16 -07:00
Ali
ab8b435de3 Fixed date and version number of 1.1 release in changelog (#2014) 2019-07-25 09:12:27 -07:00
Alexander Matyushentsev
e27568fa8c Issue #1736 - Auto-sync should support self-healing option (#1990) 2019-07-24 19:26:09 -07:00
Alexander Matyushentsev
41650ed043 Issue #2000 - Repo whitelisting in UI does not support wildcards (#2010) 2019-07-24 17:26:35 -07:00
jannfis
5953080c96 Add support for connecting repositories using TLS client certs (fixes #1945) (#1960) 2019-07-24 17:25:27 -07:00
Alexander Matyushentsev
754e4754eb Issue #2007 - UI should remember most recent selected tab on resource info panel (#2009) 2019-07-24 16:08:19 -07:00
Alexander Matyushentsev
8d0744cdb0 Regenerate lock file (#2008) 2019-07-24 16:04:11 -07:00
Alex Collins
da29f43a2b Adds link to the project from the app summary page. Closes #1911 (#1998) 2019-07-24 15:50:31 -07:00
Alex Collins
7dc19630fe Documents the instance label. Closes #1482 (#1996) 2019-07-24 15:08:24 -07:00
Alex Collins
1b2bdb1e09 Update config-management-plugins.md (#1997) 2019-07-24 15:07:48 -07:00
Alexander Matyushentsev
1b3ec9d578 Issue #1989 - Fix creating app resources graph using network connections on app details page (#2001) 2019-07-24 15:07:06 -07:00
Alexander Matyushentsev
282de00d80 Fix invalid RBAC configuration example (add missing quotes) (#2003) 2019-07-24 15:06:17 -07:00
Alexander Matyushentsev
017f9c06fb Issue #1940 - Wait for CRD creation during sync process (#1999) 2019-07-24 15:00:40 -07:00
Alexander Matyushentsev
be5ea7e219 Add 1.1 changelog (#1994) 2019-07-24 10:52:02 -07:00
jannfis
33bc349280 Add UI functionality for certificate management (#1987)
Add UI functionality for certificate management (#1987)
2019-07-24 10:04:42 -07:00
Rayyis
d1d783f24d Added a button to select out of sync items in the sync panel (#1993) 2019-07-24 09:12:35 -07:00
Dai Kurosawa
8bedda2314 Fix typo. (#1991) 2019-07-24 09:10:44 -07:00
Alexander Matyushentsev
8c8b552485 Issue #1984 - Support 'override' action in UI/API (#1985) 2019-07-23 14:16:36 -07:00
Alexander Matyushentsev
6956dcb6dc Move kustomization files back to kustomize 2.0.3 (#1972)
* Move kustomization files back to kustomize 2.0.3

* Dockerize codegen
2019-07-23 11:24:59 -07:00
Alexander Matyushentsev
57e2c5d7e7 Issue #1982 - Fix argocd app wait message (#1983) 2019-07-23 11:14:48 -07:00
jannfis
d58339561c Add ability to specify insecure and LFS modes when adding repos via UI (#1979) 2019-07-23 09:24:40 -07:00
Alexander Matyushentsev
a6cf2c5145 Update sample grafana dashboard with new metrics (#1976) 2019-07-23 08:58:30 -07:00
jannfis
1cd016d7eb Mention git-lfs as a build pre-requisite (#1978) 2019-07-23 02:32:14 -07:00
Alex Collins
c11c2a617f Only run Git LFS tests on CI. (#1975) 2019-07-22 16:51:29 -07:00
Alex Collins
6031d7f830 Do not ignore Argo hooks when there is a Helm hook. Closes #1952 (#1973) 2019-07-22 14:52:38 -07:00
dependabot[bot]
604ac4f0e7 Bump jquery from 3.3.1 to 3.4.1 in /ui (#1970) 2019-07-22 13:49:09 -07:00
dependabot[bot]
2438c0de91 Bump lodash-es from 4.17.10 to 4.17.15 in /ui (#1969) 2019-07-22 13:48:57 -07:00
dependabot[bot]
abeb301c2b Bump fstream from 1.0.11 to 1.0.12 in /ui (#1968) 2019-07-22 13:48:47 -07:00
dependabot[bot]
db5f795be8 Bump lodash.mergewith from 4.6.1 to 4.6.2 in /ui (#1967) 2019-07-22 13:48:38 -07:00
dependabot[bot]
fe86b57a2c Bump js-yaml from 3.12.0 to 3.13.1 in /ui (#1966) 2019-07-22 13:48:30 -07:00
Devon Mizelle
4dc959f3e5 Check that TLS is enabled when registering DEX Handlers (#1963)
This commit makes it so that `registerDexHandlers` in `server/server.go`
only attempts to modify `a.TLSConfig` if TLS is enabled.

Without this, deployments of ArgoCD that don't have a certificate
enabled (in the case where a LB/Ingress Controller is handling SSL
connections as a reverse proxy) end up having a nil pointer reference
panic on start.
2019-07-20 21:53:22 -07:00
stgarf
a657ceb59d Update CONTRIBUTING.md (#1964)
Add small snippet on updating dependencies before building to avoid 
failures.
2019-07-20 21:02:40 -07:00
Alexander Matyushentsev
26df57e5c7 Fix argocd app sync/get cli (#1959) 2019-07-19 14:31:07 -07:00
Jesse Suen
09dd89a468 Do not allow app-of-app child app's Missing status to affect parent (#1954) 2019-07-19 13:27:44 -07:00
Alexander Matyushentsev
ce861fe366 Issue #1935 - argocd app sync hangs when cluster is not configured #1935 (#1962) 2019-07-19 13:04:01 -07:00
Alexander Matyushentsev
3f4bacdbba Revision metadata api fails if specified revision is ambiguous (#1958) 2019-07-19 12:31:13 -07:00
Alexander Matyushentsev
cd3ff90e0d Remove unnecessary details from sync errors (#1951) 2019-07-18 17:28:28 -07:00
jannfis
8f3a6047b2 Add support for Git LFS enabled repositories (fixes #1853) (#1941)
* Add support for LFS enabled repositories
2019-07-18 12:49:49 -07:00
Alexander Matyushentsev
6049a49114 Change git prometheus counter name (#1949) 2019-07-17 16:04:54 -07:00
Devon Mizelle
43721515d8 Bump Kustomize v2.1.0 to v3.0.2 (#1948)
* Bump Kustomize v2.1.0 to v3.0.0
2019-07-17 14:39:21 -07:00
Alexander Matyushentsev
5c07473e60 E2E test should fail in action fails unless otherwise configured (#1946) 2019-07-17 14:38:48 -07:00
Liviu Costea
1524aed25e Add Mambu - The SaaS Banking Engine to the list of users (#1947) 2019-07-17 10:46:13 -07:00
Saradhi Sreegiriraju
a182e0c306 Update README.md (#1943) 2019-07-15 15:49:46 -07:00
Alexander Matyushentsev
a9fc89e8a5 Issue #1919 - Eliminate unnecessary git interactions for top-level resource changes (#1929)
* Issue #1919 - Eliminate unnecessary git interactions for top-level resource changes

* Apply reviewer notes
2019-07-15 13:34:26 -07:00
Alexander Matyushentsev
8ea7b4ac3b Add link to IBM blog about Argo CD (#1939) 2019-07-15 12:50:47 -07:00
Saradhi Sreegiriraju
d50fbccaf1 Update README.md (#1937)
Created a community blogs and presentations section
2019-07-15 11:19:58 -07:00
Alexander Matyushentsev
d3c850b8e7 Issues #1513 - Make sure insecure flag works for remote Kustomize bases (#1934)
* Make sure insecure flag works for remote Kustomize bases
2019-07-12 17:17:23 -07:00
jannfis
958239fffb Update user and operator documentation for change introduced in #1807 (#1933) 2019-07-12 14:04:44 -07:00
jannfis
22dfc7e066 Adress some change requests remaining from #1807 (#1931) 2019-07-12 10:19:51 -07:00
Alex Collins
9a76a06f39 Introduces compact diff view. Closes #1831 (#1913) 2019-07-12 09:30:02 -07:00
Alexander Matyushentsev
a607731d86 Make sure refresh icon is not hidden under status panel (#1928) 2019-07-12 09:19:43 -07:00
Alexander Matyushentsev
ba731ee507 Regenerate Gopkg.lock (#1925) 2019-07-11 16:31:18 -07:00
Alexander Matyushentsev
e93b0a0a8d Disable codecov/patch (#1926) 2019-07-11 16:31:07 -07:00
Alexander Matyushentsev
377fa5532a Issue #1915 - Deployment history metadata should be loaded only if deployment history panel is opened (#1916) 2019-07-11 16:01:21 -07:00
jannfis
9cf744f435 Simplify server certificate and known hosts management (#1807) 2019-07-11 16:00:47 -07:00
Masayuki Ishii
641e344c7f Issue #1841 Make the health check for APIService a built in (#1921) 2019-07-11 11:12:13 -07:00
Alexander Matyushentsev
722aefd1c9 Issue #897 - Secret data not redacted in last-applied-configuration (#1920) 2019-07-11 09:40:48 -07:00
Alexander Matyushentsev
f40ffdf81e Issue #1912 - Add Prometheus metrics for git repo interactions (#1914) 2019-07-10 17:13:58 -07:00
Alexander Matyushentsev
647275cf46 Issue #1917 - App details page incorrect uses 'requires pruning' icon for out-of-sync resources (#1918) 2019-07-10 16:56:52 -07:00
Alex Collins
933426a5e6 Update config-management-plugins.md (#1908) 2019-07-10 14:46:05 -07:00
Alexander Matyushentsev
7162100415 Issue #1909 - App controller should log additional information during app syncing (#1910) 2019-07-10 13:29:19 -07:00
Alexander Matyushentsev
85b6defbc8 Upgrade argo ui version to pull dropdown fix (#1906) 2019-07-10 11:45:46 -07:00
Simon Behar
a03f257205 Sync Docs (#1907) 2019-07-10 11:43:53 -07:00
Alex Collins
2a8807161a Attempts to make CI builds more reliable by reducing lint memory usage. (#1905) 2019-07-10 11:43:11 -07:00
Alex Collins
51e340cff6 Improved the usability of an app-of-apps by adding a link to the chil… (#1900) 2019-07-09 16:47:36 -07:00
Alexander Matyushentsev
03aade0267 Upgrade argo ui version to pull dropdown fix (#1899) 2019-07-09 14:15:13 -07:00
Jake Utley
afdfabd71b Allow Helm parameters to force ambiguously-typed values to be strings (#1889)
* Allow Helm parameters to force ambiguously-typed values to be strings

* Fix protobud issue for ForceString field

* Ran pre-commit checks

* Revert "Ran pre-commit checks"

This reverts commit b384d16e46.

* Ran pre-commit checks

* Revert "Ran pre-commit checks"

This reverts commit dc9e31567b.

* Ran pre-commit checks
2019-07-09 12:20:36 -07:00
Alex Collins
b2131e4a06 Attempts to fix flaky TestCustomToolWithEnv (#1876) (#1893) 2019-07-08 17:24:59 -07:00
Alex Collins
a786caa73d Log more error information. See #1887 (#1891) 2019-07-08 17:24:36 -07:00
Alex Collins
43d2848c12 Fixes bug where repo was not displayed in UI. Closes #1883 (#1892) 2019-07-08 14:07:20 -07:00
Alex Collins
a123f0bd37 Attempts to fix flaky TestCustomToolWithEnv (#1876) 2019-07-03 14:39:37 -07:00
Alex Collins
11afe8d723 Marks TestDeletingAppStuckInSync as flaky (#1880) 2019-07-03 14:37:54 -07:00
Alex Collins
8665997d89 Fixes garbage in e2e logging on CI (#1879) 2019-07-03 14:37:41 -07:00
Alex Collins
4296a87e6b Enables unparam linter and fixes linting issues (#1872) 2019-07-03 14:17:58 -07:00
Alexander Matyushentsev
37b72caf37 Issue #1874 - validate app spec before verifying app permissions (#1875) 2019-07-03 13:09:54 -07:00
Alex Collins
37ee096322 Redacts Helm username and password. Closes #1868 (#1871) 2019-07-03 13:02:25 -07:00
Alexander Matyushentsev
a2c9ed21ff Issue #1867 - Fix JS error on project role edit panel (#1869) 2019-07-03 10:36:32 -07:00
Alexander Matyushentsev
7d86b51246 Issue #1620 - Support anonymous argocd access (#1864) 2019-07-02 19:10:53 -07:00
Alex Collins
b387a3a1e1 Adds support for environment variables to custom plugins (#1860) 2019-07-02 15:59:55 -07:00
Alexander Matyushentsev
5f798583db Fix JS UI crash if user is not authenticated (#1863) 2019-07-02 14:36:07 -07:00
Alexander Matyushentsev
9f8693a6a1 Issue #1621 - Proper handling of an excluded resource in an application (#1862) 2019-07-02 13:56:25 -07:00
Alexander Matyushentsev
5ee346c266 Issue 1858 - Support 'application/merge-patch+json' in 'argocd app patch' (#1859) 2019-07-02 13:41:25 -07:00
Alexander Matyushentsev
f79b49e706 Issue #1159 - Different icon for resources which require pruning (#1854) 2019-07-02 09:43:29 -07:00
Alexander Matyushentsev
24f68835d3 Issue #1855 - Fix jumping app status panel (#1856) 2019-07-02 09:28:49 -07:00
Alex Collins
92d3173077 Attempt to fix flaky tests (#1849) 2019-07-02 09:23:50 -07:00
Alexander Matyushentsev
f555da6536 Issue #738 - Allow configuring google analytics tracking (#1848) 2019-07-01 16:57:40 -07:00
Alexander Matyushentsev
71cf80b44b Issue #1614 - Stop repeating logs on stopped container (#1850) 2019-07-01 16:47:51 -07:00
Alex Collins
5be6db4689 Adds cluster name to UI. Closes #1353 (#1814) 2019-07-01 15:41:48 -07:00
Alexander Matyushentsev
c26c07d938 Improve status badge feature (#1844)
* Improve status badge feature

* Add status badge tests
2019-07-01 12:10:36 -07:00
Alexander Matyushentsev
46204e4a44 Upgrade argo-ui version to fix dropdown position calculation (#1847) 2019-07-01 10:08:12 -07:00
Simon Behar
fb5e85d7fa Added SyncFail hooks (#1795) 2019-06-28 15:50:02 -07:00
Alex Collins
4f9e81f6c4 Removes logging that appears when using the CLI (#1842) 2019-06-28 13:19:27 -07:00
Jean-Philippe Evrard
809f742249 Fix docs typos (#1838) 2019-06-28 10:54:05 -07:00
Alexander Matyushentsev
b0aa80f26f Enable monaco editor web service to improve editor performance (#1837) 2019-06-28 10:46:51 -07:00
naynasiddharth
c76ee06cd8 Badgedocs (#1840) 2019-06-28 10:45:46 -07:00
Alex Collins
f0d3a042a3 Adds a timeout to all external commands. Closes #1821 (#1823) 2019-06-28 10:44:01 -07:00
Alexander Matyushentsev
bbad449ac6 Fields status.reconciledAt and status.observedAt must be pointers (#1836) 2019-06-27 17:55:06 -07:00
Alex Collins
640c139fdc Updates CI cache go-v16 (#1834) 2019-06-27 16:19:51 -07:00
Alexander Matyushentsev
0f40a0b843 Explicitly specify version of every dev tool (#1835) 2019-06-27 15:55:43 -07:00
Alexander Matyushentsev
1e502fb5c5 Generate CRD schema using github.com/kubernetes-sigs/controller-tools (#1773) 2019-06-27 14:44:49 -07:00
Alexander Matyushentsev
825a815dd0 Issue #1820 - Make sure api server to repo server grpc calls have timeout (#1832) 2019-06-27 14:21:43 -07:00
Alexander Matyushentsev
00134d2faf Issue #1676 - Move remove Repositories, RepositoryCredentials, HelmCredentials settings from ArgoCDSettings structure (#1829)
* Issue #1676 - Move remove Repositories, RepositoryCredentials, HelmCredentials settings from ArgoCDSettings structure

* Apply reviewer notes
2019-06-27 12:04:47 -07:00
Alexander Matyushentsev
b53a3db971 Increase codecov threshold to 1% (#1830) 2019-06-27 11:37:22 -07:00
Alex Collins
8d2ab47494 Update declarative-setup.md (#1825) 2019-06-27 11:35:17 -07:00
Jesse Suen
6c5ccca4cd Running application actions should require override privileges not get (#1828) 2019-06-27 11:01:48 -07:00
naynasiddharth
3815570294 App status badge (#1818) 2019-06-26 17:08:55 -07:00
Alex Collins
c46e3f979e Fixes TestUserAgent (#1822) 2019-06-26 07:42:27 -07:00
naynasiddharth
d55ac4fe92 App status badge (#1812) 2019-06-25 14:30:29 -07:00
Jesse Suen
7950a6e0e5 Update k8s libraries to v1.14 (#1806) 2019-06-25 02:52:58 -07:00
Jesse Suen
9ff0cbf6ff Parameterize Argo UI base image (#1813) 2019-06-24 17:18:50 -07:00
John Marcou
60fedf0985 Implement Bitbucket Server and Gogs webhook providers (#1808) 2019-06-24 16:40:44 -07:00
Alex Collins
469455ef00 Fix formatting on helm docs (#1810) 2019-06-24 16:03:42 -07:00
Edwin Jacques
94116d17d8 bash autocompletion for argocd (#1803) 2019-06-24 13:30:23 -07:00
Alex Collins
e5f9f9df14 Display the revision of resources in the labels. Closes #1367 (#1802) 2019-06-21 17:48:21 -07:00
Alexander Matyushentsev
933877a179 Issue #1676 - Move remove AppInstanceLabelKey, ConfigManagementPlugins, ResourceOverrides, ResourceExclusions, ResourceInclusions settings from ArgoCDSettings structure (#1799)
* Issue #1676 - Move remove AppInstanceLabelKey, ConfigManagementPlugins, ResourceOverrides, ResourceExclusions, ResourceInclusions settings from ArgoCDSettings structure

* Add missing tests
2019-06-21 15:59:05 -07:00
Alex Collins
f8a4d662f2 Adds more commit data. Closes #1219 (#1762) 2019-06-21 15:51:48 -07:00
Jesse Suen
059275a37f Server side rotation of cluster bearer tokens (#1744) 2019-06-21 13:34:56 -07:00
Alex Collins
261c6885f8 Fixes TestKustomize2AppSource. Closes #1800 (#1801) 2019-06-21 12:49:42 -07:00
Jesse Suen
8a6c18f3c9 Move remarshaling to happen only during comparison, instead of manifest generation (#1788) 2019-06-21 00:42:22 -07:00
Alex Collins
d2647e85a9 Fixes a bug where cluster objs could leave app is running op state. C… (#1796) 2019-06-20 21:26:19 -07:00
Alexander Matyushentsev
024dee09f5 Add health check to the controller deployment (#1785) 2019-06-19 12:00:01 -07:00
stgarf
1b55b1f1d4 Make the --grpc-web flag more obvious (#1703) 2019-06-19 11:31:39 -07:00
Liviu Costea
a223cbb96d Support top level arguments for jsonnet on app create cli (#1769) 2019-06-18 22:40:03 -07:00
Alexander Matyushentsev
882584202c Make status fields as optional fields (#1779) 2019-06-18 15:59:15 -07:00
dthomson25
2dfa59ac3a Use correct healthcheck for Rollout with empty steps list (#1776) 2019-06-18 14:09:39 -07:00
Alex Collins
d9f087fe6c Adds a check for whether or not the application path exists and return a clear error message (#1772) 2019-06-18 14:08:29 -07:00
Alex Collins
4660456ad9 Adds Validate=false that disables validation when applying resources.… (#1750) 2019-06-18 14:07:26 -07:00
Alex Collins
b2066afee4 Updates docs for resetting password. Closes #1775 (#1777) 2019-06-18 13:30:45 -07:00
Simon Behar
d6ff62e19c Added local sync to docs (#1771) 2019-06-18 10:03:19 -07:00
Alex Collins
620b3faa8f Displays targetRevision in app dashboards. Closes #1239 (#1767) 2019-06-18 10:02:45 -07:00
Alex Collins
65783410b6 Improve sync result messages. Closes #1486 (#1768) 2019-06-18 09:59:42 -07:00
Alexander Matyushentsev
fac9d6d38d Sync status button should be hidden if there is no sync operation (#1770) 2019-06-18 09:57:53 -07:00
Simon Behar
120502097a Added local path syncing (#1578) 2019-06-17 19:09:43 -07:00
Alexander Matyushentsev
d8272908e9 UI should allow editing repo URL (#1763) 2019-06-17 14:38:55 -07:00
Jesse Suen
d3268f11f9 Pin k8s.io/kube-openapi to a stable version (#1765) 2019-06-17 14:27:27 -07:00
Alex Collins
9457c65feb Attempts to make CI builds more robust by increasing the expect timeout (#1759) 2019-06-17 13:04:23 -07:00
Alex Collins
75dc92ff5f Adds "namespace: argocd" to examples (#1758) 2019-06-17 10:54:49 -07:00
Alex Collins
030acb0f56 Fix bug in release (#1748) 2019-06-17 10:45:32 -07:00
Andreas Sommer
784f73c0fe Add example for overriding Helm values and release name (#1756) 2019-06-17 09:43:32 -07:00
jannfis
7bc8bc9846 Allow setting timeout for golangci-lint in build process from external (#1752) 2019-06-17 09:39:57 -07:00
jannfis
6e6fef8d2f Make E2E tests more configurable locally (#1753) 2019-06-17 09:37:24 -07:00
Jaret
83db6a68f3 Update Readme.md adds Saildrone org (#1757) 2019-06-17 09:19:57 -07:00
Alexander Matyushentsev
569aec60a3 Increase stale bot inactivity period (#1754) 2019-06-17 09:17:54 -07:00
John Marcou
4d008602cd Add ANSTO - Australian Synchrotron to list of users (#1755) 2019-06-16 20:33:59 -07:00
Alexander Matyushentsev
ff00be9a7c Explicitly specify user root during argocd image build (#1749) 2019-06-14 15:50:43 -07:00
Alex Collins
4927943595 codegen (#1747) 2019-06-14 14:08:00 -07:00
Alex Collins
fbf2e9e128 Adds support for SSH keys with Kustomize remote bases WIP (#1733) 2019-06-14 09:34:27 -07:00
Simon Behar
770832bcb9 Added --async flag to argocd app sync (#1738) 2019-06-14 09:32:10 -07:00
Jesse Suen
40ca1e731d Cluster registration was unintentionally persisting client-cert auth credentials (#1742)
Remove unused CreateClusterFromKubeConfig server method
2019-06-14 03:45:57 -07:00
Alexander Matyushentsev
87ac100e77 Support parameterizing argocd base image (#1741) 2019-06-13 23:20:15 -07:00
Aditya Gupta
05097f3307 Issue #1677 - Allow users to define app specific urls to expose in the UI (#1714)
* Issue argoproj#1677 - allow users to define application-specific URLs, email addresses, and other info
2019-06-13 10:52:37 -07:00
Spencer Gilbert
19f0af6169 Add Optoro to list of users (#1737) 2019-06-13 09:59:30 -07:00
Mats Iremark
bb53a8edff Adding Volvo Cars as officially using ArgoCD (#1735) 2019-06-12 11:37:05 -07:00
Alex Collins
b7f1639016 No longer waits for healthy before completing sync op. Closes #1715 (#1727) 2019-06-11 15:47:19 -07:00
Aditya Gupta
e57fa0c32e Issue #1375 - Error view instead of blank page in UI (#1726) 2019-06-11 15:31:51 -07:00
Simon Behar
8729c093c8 Helm parameter fix (#1732) 2019-06-11 15:06:15 -07:00
Simon Behar
bbe800dbac Fix key generation loop when running server on insecure mode (#1723) 2019-06-11 12:40:17 -07:00
Simon Behar
1d9cd061b1 Fixes non-escaped comma bug on Helm command arguments (#1720) 2019-06-10 15:41:34 -07:00
Alex Collins
65cceaf224 Order users alphabetically (#1721) 2019-06-10 15:40:31 -07:00
Alexander Matyushentsev
88231bc93b Issue #1533 - Add e2e tests for self-referenced app edge case (#1724) 2019-06-10 14:30:13 -07:00
Alexander Matyushentsev
611323b5ce Add ui/node_modules to docker ignore (#1725) 2019-06-10 14:28:24 -07:00
Aditya Gupta
4dc102af3f Issue #1693 - Project Editor: Whitelisted Cluster Resources doesn't strip whitespace (#1722) 2019-06-10 11:53:54 -07:00
Alexander Matyushentsev
3f14f75e51 Issue #1711 - Upgrade argo ui version to get dropdown fix (#1717) 2019-06-07 18:29:51 -07:00
Alex Collins
3256e6c29e Forward git credentials to config management plugins. Closes #1628 (#1716) 2019-06-07 18:28:38 -07:00
Alex Collins
604954561a Adds documentation around repo connections (#1709) 2019-06-07 16:21:28 -07:00
Alexander Matyushentsev
9f60933a6e Issue #1701 - UI will crash when create application without destination namespace (#1713) 2019-06-07 16:21:23 -07:00
Isaac Gaskin
893f142345 Adding Telsa to list of users (#1712) 2019-06-07 14:22:16 -07:00
dthomson25
03b7d24216 Account for missing fields in Rollout HealthStatus (#1699) 2019-06-07 11:41:52 -07:00
Simon Behar
4860f2ce21 Added logout ability (argocd logout) (#1582) 2019-06-07 11:41:47 -07:00
Alex Collins
00889551e7 Adds Prune=false and IgnoreExtraneous options (#1680) 2019-06-07 08:46:11 -07:00
Alexander Matyushentsev
bdabd5b75c Restore reposerver in Procfile (#1708) 2019-06-06 16:40:50 -07:00
Alex Collins
ac51f66829 Name e2e apps after the test they run for, rather than random ID. (#1698) 2019-06-06 15:50:10 -07:00
Alex Collins
bc7bbb9dbc Improve Circle CI builds (#1691) 2019-06-06 14:45:52 -07:00
Alex Collins
fcf9f82da0 Updates generated code (#1707) 2019-06-06 14:45:30 -07:00
Liviu Costea
8275200c82 Support to override helm release name (#1682) 2019-06-06 11:51:31 -07:00
Paul Brit
85ff669b66 Add Mirantis as an official user (#1702) 2019-06-06 10:09:31 -07:00
dthomson25
b16c485a2a Handle nil obj when processing custom actions (#1700) 2019-06-05 18:04:22 -07:00
Alex Collins
23ad098aa9 Documents HA/DR (#1690) 2019-06-05 14:23:30 -07:00
Alexander Matyushentsev
0f2fe76027 Move generated api code to pkg package (#1696) 2019-06-05 12:58:11 -07:00
narg95
a5d957ec06 Bump base version to 1.0.1 for cluster-install (#1695) 2019-06-05 09:13:31 -07:00
Sebastian Vaisov
251cbfa99e Adds custom port repo note (#1694) 2019-06-05 09:13:07 -07:00
Alex Collins
243378b035 Sync wave (#1634) 2019-06-04 18:17:41 -07:00
Alex Collins
0dd80f9d8e Tidy up #1684 (#1689) 2019-06-04 17:08:07 -07:00
Alex Collins
f380deaf86 Update SUPPORT.md (#1681) 2019-06-04 17:07:19 -07:00
Alex Collins
bb5b78e94e Merge pull request #1684 from twz123/kustomize-commonlabels 2019-06-04 16:16:01 -07:00
Alex Collins
a234894d01 Merge pull request #1688 from argoproj/merge-ui
Merge UI
2019-06-04 14:55:53 -07:00
Alex Collins
4c1cbbcdfc Merge remote-tracking branch 'ui/master' into merge-ui 2019-06-04 14:50:51 -07:00
Alex Collins
10cf1482ab Moves UI code into ui/ (#129) 2019-06-04 14:47:28 -07:00
Steve Christensen
89afb5cac2 add tZERO to organizations using Argo CD list (#1686) 2019-06-04 14:40:03 -07:00
Marcin Jasion
34f0f286d6 Added Codility to ArgoCD users (#1679) 2019-06-04 14:18:18 -07:00
Tom Wieczorek
b645589ed5 Add support for adding Kustomize commonLabels in Applications 2019-06-04 16:54:56 +02:00
Alex Collins
d09388bc97 codegen (#1674) 2019-06-03 12:00:16 -07:00
jannfis
64a1ea9e81 Add ability to specify system namespace during cluster add operation (#1661) 2019-06-03 09:50:46 -07:00
Alex Collins
0fd10be9de Adds "Sync Status" button" (#127) 2019-05-31 17:52:05 -07:00
Alexander Matyushentsev
c214ed9546 Issue #1668 - Replicasets ordering is not stable on app tree view (#1669) 2019-05-31 17:42:20 -07:00
Alexander Matyushentsev
8a7c870f1c Fix broken e2e tests (#1667) 2019-05-31 13:13:35 -07:00
Alex Collins
556f12fd59 Adds docs about app deletion (#1664) 2019-05-30 14:38:52 -07:00
Alexander Matyushentsev
ecdf94232f Issue #1665 - Stuck processor on App Controller after deleting application with incomplete operation (#1666) 2019-05-30 12:39:54 -07:00
Alex Collins
c2b6e0f34a Update releasing.md (#1657) 2019-05-29 14:53:00 -07:00
Alexander Matyushentsev
32bfad21f8 Issue #1662 - Role edit page fails with JS error (#126) 2019-05-29 11:53:14 -07:00
Alex Collins
2777910d1f Terminates op before delete (#1658) 2019-05-29 10:55:27 -07:00
Alexander Matyushentsev
a49314be07 Issue #1609 - Improve Kustomize 2 parameters UI (#125)
* Issue #1609 - Improve Kustomize 2 parameters UI

* Add unit tests for kustomize image parsing
2019-05-28 15:30:57 -07:00
jannfis
9f9a076433 Make listener and metrics ports configurable (#1647) 2019-05-28 11:41:02 -07:00
Alex Collins
4c41f82d18 Build ArgoCD on CircleCI (#1635) 2019-05-28 11:39:13 -07:00
Alex Collins
18b62f9bbe Updated templates (#1654) 2019-05-28 10:18:04 -07:00
Appréderisse Benjamin
b9700b760f Update README.md (#1650) 2019-05-28 08:35:33 -07:00
samcgardner
894b150ac9 Add END. to adopters in README.md (#1643) 2019-05-24 05:25:29 +02:00
Alex Collins
5515b8ce9d Public git creds (#1633) 2019-05-22 17:21:56 -07:00
jannfis
24006300e5 Make build options in Makefile settable from environment (#1619) 2019-05-22 15:59:30 -07:00
Alex Collins
8cd7d590e0 Codegen (#1632) 2019-05-21 14:37:20 -07:00
Alexander Matyushentsev
38b5b242b3 Update v1.0.0 change log (#1618) 2019-05-16 15:08:22 -07:00
Alex Collins
9f330348ec Fixes e2e tests. Closes #1616. (#1617) 2019-05-16 11:05:35 -07:00
Alexander Matyushentsev
303737c0b0 Issue #1471 - Support configuring requested OIDC provider scopes and enforced RBAC scopes (#1585)
* Issue #1471 - Support configuring requested OIDC provider scopes and enforced RBAC scopes

* Apply reviewer notes
2019-05-16 07:34:20 -07:00
Alex Collins
71a8eb1697 E2e test infra (#1600) 2019-05-15 09:24:35 -07:00
Alexander Matyushentsev
71f3351d2b Issue #1352 - Dedupe live resourced by UID instead of group/kind/namespace/name (#123) 2019-05-13 15:56:46 -07:00
Alexander Matyushentsev
b93143381f Issue #1352 - Dedupe live resourced by UID instead of group/kind/namespace/name (#1575) 2019-05-13 15:56:40 -07:00
Jesse Suen
5ed0b1a6bf Supply resourceVersion to watch request to prevent reading of stale cache (#1612) 2019-05-13 14:58:22 -07:00
Alexander Matyushentsev
847b7f5e11 Issue #1533 - Prevent reconciliation loop for self-managed apps (#1608) 2019-05-13 11:17:32 -07:00
Alex Collins
7568b099ee Updates codegen (#1601) 2019-05-09 13:40:41 -07:00
Alexander Matyushentsev
7fdd865d5c Issue #1586 - Ignore patch errors during diffing normalization (#1599) 2019-05-09 09:26:02 -07:00
Alex Collins
d0d4d593cf Updates issue template and Makefile (#1598) 2019-05-09 09:05:42 -07:00
Alexander Matyushentsev
20810e98f2 Issue #1596 - SSH URLs support is partially broken (#1597) 2019-05-09 08:53:11 -07:00
Alexander Matyushentsev
97ab061ab5 Issue #1592 - Fix UI Crash is app never been reconciled 2019-05-09 08:45:14 -07:00
Alexander Matyushentsev
edf8a0ede2 Issue #1592 - Fix UI Crash is app never been reconciled 2019-05-09 07:46:50 -07:00
Alex Collins
1c6bb4386f Documents Kustomize. Closes #1566 (#1572) 2019-05-08 09:37:45 -07:00
Alexander Matyushentsev
ee00a0e049 Issue #1552 - Improve rendering app image information (#124) 2019-05-08 09:00:49 -07:00
Alexander Matyushentsev
e85eb01831 Issue #1552 - Improve rendering app image information (#1584) 2019-05-08 09:00:45 -07:00
Alexander Matyushentsev
e6697274f4 Fix ingress browsable url formatting if port is not string (#1576) 2019-05-07 16:07:34 -07:00
Alexander Matyushentsev
b3ade6159e Issue #1579 - Impossible to sync to HEAD from UI if auto-sync is enabled (#1580) 2019-05-07 14:54:51 -07:00
jpresky
6c0e21780c add commonbond to users of argocd (#1577) 2019-05-07 07:34:38 -07:00
Alexander Matyushentsev
0b945ef616 Issue #1570 - Application controller is unable to delete self-referenced app (#1574) 2019-05-06 12:49:29 -07:00
Alexander Matyushentsev
41cad56991 Issue #1546 - Add liveness probe to repo server/api servers (#1560) 2019-05-04 10:51:26 -07:00
Sahil Ahuja
f8283a1014 Add GMETRI to organizations using ArgoCD (#1564) 2019-05-04 10:50:16 -07:00
Alexander Matyushentsev
da29c05662 Issue #1563 - Network view crashes if any filter is set (#122) 2019-05-03 15:05:37 -07:00
Alexander Matyushentsev
5bf834e14e Fix broken applications chart icon (#121) 2019-05-02 17:55:49 -07:00
Alexander Matyushentsev
5c353a12f2 ISsue #1557 - Controller incorrectly report health state of self managed application (#1558) 2019-05-02 14:38:37 -07:00
Alexander Matyushentsev
5ec5301680 Issue #1540 - Fix kustomize manifest generation crash is manifest has image without version (#1559) 2019-05-02 13:34:54 -07:00
Paul Brit
d06303c432 Fix hardcoded 'git' user in util/git.NewClient (#1556)
Closes #1555
2019-05-02 10:01:20 -07:00
dthomson25
f268f82780 Improve Rollout health.lua (#1554) 2019-05-01 15:53:27 -07:00
Alexander Matyushentsev
8ea785892f Fix invalid URL for ingress without hostname (#1553) 2019-05-01 15:38:24 -07:00
Alexander Matyushentsev
5f81dc0d51 Issue #1550 - Support ':' character in resource name (#120) 2019-05-01 12:08:56 -07:00
Alex Collins
6ca654294c Updates manifests. Closes #1520 (#1549) 2019-05-01 11:48:27 -07:00
Alexander Matyushentsev
96d0beeaaf Issue #1533 - Prevent reconciliation loop for self-managed apps (#1547) 2019-05-01 09:42:45 -07:00
Alexander Matyushentsev
3f913c0c3f Rollout health checks/actions should support v0.2 and v0.2+ versions (#1543) 2019-04-30 13:17:06 -07:00
Alex Collins
31a8e07cec Adds missing section to docs (#1537) 2019-04-30 11:35:40 -07:00
Alex Collins
fc6df01b8e Fixes bug in normalizer (#1542) 2019-04-30 11:32:20 -07:00
Omer Kahani
bcefc34287 Add kustomize (#1541) 2019-04-30 08:00:15 -07:00
tom-256
e6fe4f0e05 fix typo in best practices (#1538) 2019-04-30 07:57:59 -07:00
Alexander Matyushentsev
e20e693d70 Issue 1476 - Avoid validating repository in application controller (#1535) 2019-04-29 15:04:25 -07:00
Alexander Matyushentsev
686fab7fec Issue #1414 - Load target resource using K8S if conversion fails (#1527) 2019-04-29 12:42:59 -07:00
Alex Collins
1ee6e1c7fa Documents cluster bootstrapping. Close #1481 (#1530) 2019-04-29 11:35:57 -07:00
Alex Collins
444b65ecac Update CONTRIBUTING.md (#1534) 2019-04-29 11:35:43 -07:00
Alexander Matyushentsev
a12124512e Fix flaky TestGetIngressInfo unit test (#1529) 2019-04-25 16:53:43 -07:00
Alexander Matyushentsev
e5e1308852 Issue #1476 - Add repo server grpc call timeout (#1528) 2019-04-25 16:53:05 -07:00
Alex Collins
7beae2beac Adds support for configuring repo creds at a domain/org level. Closes… (#1496) 2019-04-25 15:22:49 -07:00
Alex Collins
d9345c99e3 Fix e2e (#1526) 2019-04-25 14:53:25 -07:00
Simon Behar
d222b935e6 Whitelisting of resources (#1509)
* Added whitelisting of resources
2019-04-25 14:48:22 -07:00
Alexander Matyushentsev
8577114e2e Ingress resource might get invalid ExternalURL (#1522) (#1523) 2019-04-24 13:39:59 -07:00
Alex Collins
e3a120b1d8 codegen (#1521) 2019-04-24 13:39:07 -07:00
Alex Collins
00c12d9a25 Updated CHANGELOG.md (#1518) 2019-04-24 10:56:20 -07:00
Simon Behar
33353417df Added ability to sync specific labels from the command line (#1501)
* Finished initial implementation

* Added tests and fix a few bugs
2019-04-24 10:46:05 -07:00
Alexander Matyushentsev
b667cef4a8 Add Network View description to changelog (#1519) 2019-04-24 10:44:22 -07:00
Alexander Matyushentsev
3b71bd05a4 Issue #1411 - Document private repository configuration (#1515) 2019-04-24 10:26:07 -07:00
Alexander Matyushentsev
e75a7a5dea Update min client version and cache version to 1.0.0 (#1517) 2019-04-24 10:15:02 -07:00
Alexander Matyushentsev
5134ca37a7 Issue #1499 - Render application browsable URLs (#119) 2019-04-23 10:34:57 -07:00
Alexander Matyushentsev
60273ba84f Issue #1499 - Use ingress host information to populate application external URL (#1511) 2019-04-23 10:34:53 -07:00
Alexander Matyushentsev
ae23af7061 Issue #1507 - Selective sync is broken in UI (#118) 2019-04-22 15:33:06 -07:00
Alexander Matyushentsev
c33604f2ef v0.12.2 Change log (#1508) 2019-04-22 15:26:55 -07:00
Alexander Matyushentsev
9686a2f16b Issue #1502 - UI fails to load custom actions is resource is not deployed (#117) 2019-04-22 15:24:41 -07:00
Alex Collins
eea804b3f6 Allow empty. Close #1504 (#1506) 2019-04-22 14:07:12 -07:00
Alexander Matyushentsev
8f658108f2 Issue #1503 - Events tab title is not right if resources has no errors (#116) 2019-04-22 13:43:14 -07:00
Alex Collins
25edf8ac3f Update CHANGELOG.md (#1500) 2019-04-22 13:28:29 -07:00
Alexander Matyushentsev
3db5c36e60 Issue #1505 - Fix broken node resource panel (#115) 2019-04-22 11:49:17 -07:00
Omer Kahani
3ed6dc91dd Add riskified to organizations using ArgoCD (#1497) 2019-04-21 07:25:26 -07:00
Alex Collins
e803969442 Adds event count. Closes argoproj/argo-cd#1477 (#113) 2019-04-19 16:00:16 -07:00
Alexander Matyushentsev
5be580c105 Issue #86 - Support triggering resources custom actions (#114) 2019-04-19 14:34:09 -07:00
Alex Collins
13e5348177 Updates CHANGELOG for v1.0.0 (#1469) 2019-04-19 11:44:42 -07:00
Alexander Matyushentsev
90e44c092a Issue #86 - Custom actions bug fixing (#1494) 2019-04-19 10:27:12 -07:00
Simon Behar
8027882c1c Added --resource flag to argocd app wait (#1453) 2019-04-19 09:59:06 -07:00
Alexander Matyushentsev
ad9ed33f8d Fix flaky e2e test. Again (#1489) 2019-04-19 09:05:42 -07:00
Alex Collins
ddf5f0cf46 Introduces new RBAC permissions that are required for changing cluste… (#1440) 2019-04-19 08:54:30 -07:00
Alexander Matyushentsev
76811a992e Change loggin level in util function to Debug (#1488) 2019-04-18 11:58:30 -07:00
Alexander Matyushentsev
2eac7bf457 Issue #1476 - Fix racing condition in controller cache (#1485) 2019-04-18 08:12:18 -07:00
Alex Collins
53cbcd362d Adds a faster way to run e2e locally (#1475) 2019-04-17 10:53:37 -07:00
Alexander Matyushentsev
11c878b847 Change version to 1.0.0 (#1473) 2019-04-17 08:35:07 -07:00
Alexander Matyushentsev
25d5333894 Fix flaky e2e test (#1474) 2019-04-17 08:21:18 -07:00
Alexander Matyushentsev
a0ae6dd32f Fix JS error caused by change of unmounted React component 2019-04-16 15:28:15 -07:00
Alexander Matyushentsev
1bbd8f038b Issue #1386 - Improve notifications rendering (#112) 2019-04-16 15:27:07 -07:00
Alexander Matyushentsev
e7bde586d8 Network view external nodes (#109)
* Add load balancer nodes to network view

* Color traffic on network view
2019-04-16 15:07:24 -07:00
dthomson25
4541ca664a Initial Custom Actions Implementation (#1369) 2019-04-16 14:50:44 -07:00
Alexander Matyushentsev
97422b4148 Improve e2e tests for app with secrets (#1466) 2019-04-16 13:04:54 -07:00
Alex Collins
4df07a278d Adds label to Github issue templates (#1468) 2019-04-16 11:54:17 -07:00
Alexander Matyushentsev
efa418c58b Document steps to troubleshot cluster configuration (#1467) 2019-04-16 11:41:44 -07:00
Alexander Matyushentsev
5540c9b9aa Issue #1326 - Rollback UI is not showing correct ksonnet parameters in preview (#111) 2019-04-16 08:57:31 -07:00
Alexander Matyushentsev
be40dbc8cc Issue #1326 - Rollback UI is not showing correct ksonnet parameters in preview (#1464) 2019-04-16 08:52:48 -07:00
dthomson25
0bd7023b66 Add link to e2e testing on contributing guide (#1456) 2019-04-15 13:50:46 -07:00
Marc
db82456dde don't compare secrets, since argo-cd doesn't have access to their data (#1459) 2019-04-15 13:46:03 -07:00
Alex Collins
a51441546c more-information-needed (#1463) 2019-04-15 13:39:13 -07:00
Alex Collins
0bd323140d Docs (#1441) 2019-04-15 13:39:04 -07:00
Ryan Fernandes
ad22949925 grammar change. added an 'if' (#1465) 2019-04-15 13:19:11 -07:00
Alex Collins
0726ee8995 Fixes goroutine leak. Closes #1381 (#1457) 2019-04-15 10:50:05 -07:00
Alexander Matyushentsev
c120004084 Fix e2e test flakyness (#1462) 2019-04-15 09:55:30 -07:00
Alexander Matyushentsev
02c81851a8 Improve application list page UI (#110) 2019-04-12 15:45:30 -07:00
Alexander Matyushentsev
e15b97ee08 Document how to use helm without internet access (#1448) 2019-04-12 15:22:38 -07:00
Alexander Matyushentsev
bbc7d39928 Regenerate manifests (#1454) 2019-04-12 14:38:08 -07:00
Alexander Matyushentsev
b53c34c3f7 Generate random name for grpc proxy unix socket file instead of time stamp (#1455) 2019-04-12 14:25:01 -07:00
Alex Collins
d2928d5b31 Shows the health of the application. Closes #1433 (#1434) 2019-04-12 11:52:37 -07:00
Karsten Siemer
7e76d6de33 Overlay selector of argocd-redis-ha service (#1436)
* The selector of the argocd-redis-ha service wasn't being overlayed and the service never got to have endpoints

* Generated install.yaml and namespace-install.yaml using make manifests
2019-04-12 09:17:01 -07:00
Alexander Matyushentsev
3eac376a41 Revert "Redis mastergroup name should be resolvable and argocd-redis-ha is (#1450)" (#1452)
This reverts commit 7084e3af5c.
2019-04-12 07:44:45 -07:00
Karsten Siemer
7084e3af5c Redis mastergroup name should be resolvable and argocd-redis-ha is (#1450)
the mastergroup name of redis was set as argocd since this is not
resolvable because no service has this name, this should be
renamed to the service which selects all redis pods
2019-04-12 07:26:52 -07:00
Alexander Matyushentsev
ac3d12c746 Issue #1446 - Delete helm temp directories (#1449) 2019-04-12 05:24:38 -07:00
Jonah Back
41a3352516 Fix github reference to use mainline instead of fork (#1445) 2019-04-11 18:32:12 -07:00
Alex Collins
01dad77d44 Updates icons (#108) 2019-04-11 17:50:01 -07:00
Alexander Matyushentsev
018ce4e9f0 Fix JS error while rendering resource without health info 2019-04-11 16:06:56 -07:00
Alexander Matyushentsev
311ff8caed Issue #1389 - Fix null pointer exception in secret normalization function (#1443) 2019-04-11 11:46:42 -07:00
Alexander Matyushentsev
197bbda02e Issue #1425 - Argo CD should not delete CRDs (#1428) 2019-04-11 09:07:14 -07:00
Alexander Matyushentsev
56cd8fcc95 Fix invalid ignoreDifferences config example (#1437) 2019-04-11 07:53:56 -07:00
Alex Collins
3c4b42de75 Displays resources that are being deleted as "Progressing". Closes #1410 (#1426) 2019-04-11 07:47:59 -07:00
Arnar
0e89b744ec Query-ing basehref to redirect to the right URI on auth expiration (#107) 2019-04-10 14:16:24 -07:00
Alexander Matyushentsev
6aa12887b3 Fix linter error 2019-04-10 12:57:46 -07:00
Alexander Matyushentsev
e5d6e9a21a Fix broken icons on project details page 2019-04-10 08:16:41 -07:00
Le Van Nghia
e4b8a9d895 Added CyberAgent and OpenSaaS Studio to organizations using ArgoCD (#1427) 2019-04-10 07:59:24 -07:00
Alex Collins
76d25d3795 Perform health assessments on all resource nodes in the tree. Closes #1382 (#1422) 2019-04-09 18:15:24 -07:00
Alex Collins
97a59ca753 Enables Probot stale and no-respones plugins. Closes #1418 (#1419) 2019-04-09 17:35:44 -07:00
Alex Collins
544bd47e94 Nils health if the resource does not provide it. Closes #1383 (#1408) 2019-04-09 15:05:14 -07:00
Alexander Matyushentsev
56916a0321 Add v0.12.1 release notes (#1423) 2019-04-09 14:57:18 -07:00
Michael Goodness
eff83a45cd Add Ticketmaster to "Who uses" section of README (#1424)
Signed-off-by: Michael Goodness <mike.goodness@ticketmaster.com>
2019-04-09 14:42:04 -07:00
Alexander Matyushentsev
3f9d361d4f Issue #357 - Render external traffic node on network view (#105) 2019-04-09 14:09:57 -07:00
Alex Collins
0565dd3df1 Allows health to be null in the UI (#104) 2019-04-09 11:22:13 -07:00
Alex Collins
c8e8c2dc32 Updates in-product help (#103) 2019-04-09 11:01:28 -07:00
Alex Collins
9df1e27191 Fixes doc bugs. Closes #1395 (#1403) 2019-04-09 11:01:04 -07:00
Alexander Matyushentsev
abe25f62d0 Run 'go fmt' for application.go and server.go (#1417) 2019-04-09 09:43:53 -07:00
dthomson25
ad5d26f08a Add patch audit (#1416)
* Add auditing to patching commands

* Omit Patch Resource logs to prevent secret leaks
2019-04-09 08:57:22 -07:00
Alex Collins
9c5c420483 FontAwesome 5 fixes (#106) 2019-04-09 08:34:15 -07:00
Alexander Matyushentsev
dea731a6b2 Add networking test app (#1409) 2019-04-08 16:29:08 -07:00
Isaac Gaskin
1d19447e8e issue #1202: docs(help examples): adding template and first examples for the app command (#1398)
shameless ripoff of kubectl example templating
2019-04-08 15:47:02 -07:00
Alexander Matyushentsev
ac938c8738 Issue #1406 - Don't try deleting application resource if it already have (#1407) 2019-04-08 15:08:48 -07:00
Alex Collins
88a1c2a593 Pod health (#1365) 2019-04-08 14:49:57 -07:00
Petr Jediný
1e8db87320 Add KompiTech GmbH to organizations using Argo CD (#1402) 2019-04-08 12:45:40 -07:00
Alexander Matyushentsev
7382ebce27 Issue #1404 - App controller unnecessary set namespace to cluster level resources (#1405) 2019-04-08 12:02:06 -07:00
Alexander Matyushentsev
911425c1c1 Move applicatoin Refresh button to action buttons on Application Details page 2019-04-08 09:58:35 -07:00
Alex Collins
9988b3d8e6 Mkdocs2 (#1393) 2019-04-08 09:20:36 -07:00
Alexander Matyushentsev
3d0f85c188 Issue #1217 - Improve form input usability 2019-04-08 09:01:18 -07:00
Jesse Suen
6b69449175 Add OpenAPI validation in CRD schema (#1256) 2019-04-06 17:18:00 -07:00
dthomson25
85a5fb5a41 Allow wait to return on health or suspended (#1392) 2019-04-06 10:31:07 -06:00
Alex Collins
f5bc901dd7 Create docs website (#1387) Closes #1390 2019-04-05 15:12:27 -07:00
Alexander Matyushentsev
ba43a01669 Issue #1354 - [UI] default view should resource view instead of diff view 2019-04-05 15:05:40 -07:00
Alexander Matyushentsev
f5833da4cd Issue #1368 - [UI] applications view blows up when user does not have permissions 2019-04-05 15:05:02 -07:00
Alexander Matyushentsev
67882a9dff Fix broken icons on Help page 2019-04-05 13:11:59 -07:00
Alexander Matyushentsev
c03bd896d8 Issue #1357 - Dropdown menu should not have sync item for unmanaged resources 2019-04-05 11:25:27 -07:00
Alexander Matyushentsev
8ee3c93c84 Upgrade font-awesome to v5 2019-04-05 10:06:26 -07:00
Alex Collins
4ac062d09e Removes componentParameterOverrides. Closes #1372 (#1378) 2019-04-05 08:26:37 -07:00
Alexander Matyushentsev
159a30fdc7 Support tab deep linking on app details page (#102) 2019-04-05 08:26:35 -07:00
Marcin Jasion
a15ca7259c Fix project.yaml link in README.md (#1384) 2019-04-05 07:34:52 -07:00
brushmate
d4ee7972ca Add Yieldlab to organzations using Argo CD (#1385) 2019-04-05 07:34:09 -07:00
Alexander Matyushentsev
56ca350ed2 Support obsolete extensions in UI 2019-04-04 18:04:44 -07:00
Alexander Matyushentsev
86f6b657e2 Issue #1374 - Add k8s objects circular dependency protection to getApp method (#1379) 2019-04-04 17:52:30 -07:00
Alexander Matyushentsev
ac7906fdea Issue #1366 - Fix null pointer dereference error in 'argocd app wait' (#1380) 2019-04-04 17:49:34 -07:00
Alexander Matyushentsev
781a9ab627 Regenerate yarn.lock file 2019-04-04 17:38:06 -07:00
Alex Collins
d0ecaed401 Ui enhancements (#100) 2019-04-04 11:27:07 -07:00
dthomson25
4d494f3a1b Magically increase the code coverage!!! (#1370) 2019-04-04 10:06:10 -06:00
Alexander Matyushentsev
57ff5b25e4 Issue #1012 - kubectl v1.13 fails to convert extensions/NetworkPolicy (#1360) 2019-04-04 08:30:35 -07:00
Jesse Suen
28fa4a7571 MAGA: Make ArgoCD Golang Again! (#1279) 2019-04-04 02:35:13 -07:00
Alex Collins
723228598e Adds images to resource tree (#1351) 2019-04-03 15:11:48 -07:00
Alexander Matyushentsev
f28d11bf90 Issue #908 - Surface Service/Ingress external IPs, hostname to application (#99) 2019-04-03 12:04:18 -07:00
Arnar
7091585dbe Changing SSO login URL to be a relative link so it's affected by basehref (#101) 2019-04-03 12:03:34 -07:00
Alexander Matyushentsev
790cdd1d45 Add 'Who uses Argo CD?' section (#1361) 2019-04-02 22:27:54 -07:00
Tom Wieczorek
81e21a551d Add mapping to new canonical Ingress API group (#1348)
Since Kubernetes 1.14, Ingress resources are only available via networking.k8s.io/v1beta1.
2019-04-02 21:25:09 -07:00
dthomson25
7cf3f6cd19 Fix Failing Linter (#1350) 2019-04-02 17:39:04 -06:00
Alexander Matyushentsev
506d95da10 Issue #1294 - CLI diff should take into account resource customizations (#1337)
* Issue #1294 - CLI diff should take into account resource customizations

* Apply reviewer notes: add comments to type definition and e2e test
2019-04-02 13:59:55 -07:00
Alexander Matyushentsev
36b4683e84 Issue #908 - Surface Service/Ingress external IPs, hostname to application (#1347) 2019-04-02 08:48:34 -07:00
Noah Kantrowitz
2becacd48d Copy-paste error: clusterResourceWhitelist -> namespaceResourceBlacklist (#1343)
Same fix as #1312 but in another file.
2019-04-01 09:17:09 -07:00
Alex Collins
ae41425c77 gotestsum (#1341) 2019-03-30 22:14:35 -07:00
Alexander Matyushentsev
59837cb513 Issue #1218 - Allow using any name for secrets which store cluster credentials (#1336) 2019-03-29 22:09:36 -07:00
Alexander Matyushentsev
66e5d51329 Issue #733 - 'argocd app wait' should fail sooner if app transitioned to (#1339)
Issue #733 - 'argocd app wait' should fail sooner if app transitioned to Degraded state
2019-03-29 21:00:50 -07:00
Alexander Matyushentsev
8d55e72dfa Issue #357 - Implement networking view (#98) 2019-03-29 20:59:50 -07:00
Alexander Matyushentsev
15dfa79708 Issue #357 - Expose application nodes networking information (#1333) 2019-03-29 20:59:25 -07:00
Alexander Matyushentsev
896d46525e Don't run lint after running codegen (#1338) 2019-03-29 13:27:22 -07:00
Daniel van den Berg
a8b70b411c Declarative setup doc update (#1334)
This change updates the documentation around declarative setups. The
docs did not explicitly distinguish between adding an HTTPS repository
or an SSH repository, and this PR clarifies that.
2019-03-29 08:13:32 -07:00
Alex Collins
cd25c4b3c9 Enables default lint checks, fixes lint and bugs (#1330) 2019-03-28 13:37:53 -07:00
Alex Collins
b28d8361f5 Adds "make build" target, and running lint,build,test (#1331) 2019-03-28 11:20:51 -07:00
Jesse Suen
b40ba175a3 Update argocd-util import/export to support proper backup and restore (#1328) 2019-03-27 17:05:59 -07:00
Alex Collins
cd87a1436b Support overriding image name/tag in for Kustomize 2 apps (#97) 2019-03-27 12:54:30 -07:00
Alex Collins
dfa91d87cf Adds support for kustomize edit set image. Closes #1275 (#1324) 2019-03-27 12:54:23 -07:00
Alex Collins
102c24cc29 Fixs deps (#1325) 2019-03-26 15:35:32 -07:00
Alex Collins
7d3b6cc8e0 Force color logging locally (#1316) 2019-03-26 13:59:03 -07:00
Alexander Matyushentsev
9ef7064cc4 Use paused field in rollout health check (#1321) 2019-03-26 11:07:06 -07:00
Alexander Matyushentsev
56f0ff204e Issue #1319 - Fix invalid group filtering in 'patch-resource' command (#1320) 2019-03-26 08:01:35 -07:00
Alexander Matyushentsev
af896533df Issue #1135 - Run e2e tests in throw-away kubernetes cluster (#1318)
* Issue #1135 - Run e2e tests in throw-away kubernetes cluster
2019-03-24 07:35:57 -07:00
Jesse Suen
aa099f3fc0 Update CHANGELOG.md for v0.12 release (#1317) 2019-03-22 21:02:20 -07:00
Alexander Matyushentsev
27b23f6a00 Issue #1310 - application table view needs to be sorted 2019-03-22 13:59:09 -07:00
Jesse Suen
e07a877e73 Use Recreate deployment strategy for controller (#1315) 2019-03-22 11:50:15 -07:00
Jesse Suen
1f675f4bb9 Fix goroutine leak in RetryUntilSucceed (#1314) 2019-03-22 11:50:00 -07:00
Jesse Suen
e482d74d19 Support a separate OAuth2 CLI clientID different from server (#1307) 2019-03-22 03:23:51 -07:00
Tom Wieczorek
50bff3e540 Copy-paste error: clusterResourceWhitelist -> namespaceResourceBlacklist (#1312) 2019-03-22 02:35:39 -07:00
Andre Krueger
0d7c42ba54 Honor os environment variables for helm commands (#1306) 2019-03-21 16:51:04 -07:00
Alexander Matyushentsev
ec7cbf8e15 Issue #1308 - argo diff --local fails if live object does not exist (#1309) 2019-03-21 15:32:44 -07:00
Alexander Matyushentsev
d60fb2b449 Unavailable cache should not prevent reconciling/syncing application (#1303) 2019-03-20 14:02:54 -07:00
Jesse Suen
dc989dbebc Update redis-ha chart to resolve redis failover issues (#1301) 2019-03-20 12:06:18 -07:00
Marc
09164cae6c only print to stdout, if there is a diff + exit code (#1288) 2019-03-19 18:58:52 -07:00
Alexander Matyushentsev
80f0f779db Fix sample dashboard link in metrics doc (#1299) 2019-03-19 14:26:26 -07:00
Alexander Matyushentsev
c605e892b6 Issue #1258 - Disable CGO_ENABLED for server/controller binaries (#1286) 2019-03-19 14:25:19 -07:00
Alexander Matyushentsev
80fe3e1877 Controller don't stop running watches on cluster resync (#1298) 2019-03-19 13:25:01 -07:00
Jesse Suen
8f7a7ef6a4 Update dashboard to have controller/repo-server stats. Collapsible rows (#1295) 2019-03-19 10:41:51 -07:00
hartman17
2aad4d0ab5 Sample Grafana dashboard (#1277) 2019-03-19 01:12:21 -07:00
Alexander Matyushentsev
df7b0c6682 Issue #1290 - Fix concurrent read/write error in state cache (#1293) 2019-03-18 23:38:10 -07:00
Jesse Suen
ea1519de82 Fix a goroutine leak in api-server application.PodLogs and application.Watch (#1292) 2019-03-18 21:50:11 -07:00
Alexander Matyushentsev
b60067af97 Issue #1287 - Fix local diff of non-namespaced resources. Also handle duplicates in local diff (#1289) 2019-03-18 21:19:08 -07:00
Alexander Matyushentsev
3540859074 Use application/strategic-patch+json patch to update resources 2019-03-18 15:07:08 -07:00
Jesse Suen
22ddd53ea5 Fix isssue where argocd app set -p required repo privileges. (#1280)
Grant patch privileges to argocd-server
2019-03-18 14:39:32 -07:00
Alexander Matyushentsev
1b41aba841 Issue #1282 - Prevent filering out application node on Applicatoin details page 2019-03-18 14:27:27 -07:00
Alexander Matyushentsev
cafe24da86 Issue #1070 - Handle duplicated resource definitions (#1284) 2019-03-18 13:21:03 -07:00
Yann Soubeyrand
c33acf749c Fix documentation on diffing customization (#1285) 2019-03-18 13:20:44 -07:00
Jesse Suen
dab3b688f0 Add golang prometheus metrics to controller and repo-server (#1281) 2019-03-18 11:32:20 -07:00
dthomson25
a34d2c750b Add note about Kustomize1 (#1263) 2019-03-17 22:25:20 -07:00
Jesse Suen
5210c678b9 Git cloning via SSH was not verifying host public key (#1276) 2019-03-15 14:29:10 -07:00
Alexander Matyushentsev
baf157901c Rename Application observedAt to reconciledAt and use observedAt to notify about partial app refresh (#1270) 2019-03-14 16:42:36 -07:00
Alexander Matyushentsev
e457dd6f6c Bug fix: set 'Version' field while saving application resources tree (#1268) 2019-03-14 15:52:50 -07:00
Alexander Matyushentsev
f787828712 Chunk file name should include content hash 2019-03-14 15:08:40 -07:00
Alexander Matyushentsev
2724aeef32 Avoid doing full reconciliation unless application 'managed' resource has changed (#1267) 2019-03-14 14:54:34 -07:00
Jesse Suen
1d3ec93ec7 Support kustomize apps with remote bases in private repos in the same host (#1264) 2019-03-14 14:25:05 -07:00
Alexander Matyushentsev
471dac48be Issue #1261 - UI loads helm parameters without taking into account selected values files 2019-03-12 11:46:20 -07:00
Omer Kahani
fea3899f26 Fix project.yaml link location (#1257) 2019-03-12 10:38:22 -07:00
Alex Collins
f016acdade Enable debug logging for local development (#1260)
* Enable debug logging for local development

* Update Procfile
2019-03-12 10:31:51 -07:00
Alex Collins
0c4d5009a2 Tweak lint (#1259) 2019-03-12 10:31:35 -07:00
Alexander Matyushentsev
815ba879e6 Issue #1252 - Application controller incorrectly build application objects tree (#1253) 2019-03-11 11:31:46 -07:00
Alexander Matyushentsev
3df86a7918 Issue #1247 - Fix CRD creation/deletion handling (#1249) 2019-03-11 08:50:00 -07:00
Alexander Matyushentsev
2675367400 Live manifest state tab should always load latest manifest from target cluster 2019-03-11 00:06:56 -07:00
Alex Collins
5e7b48c9a2 Migrates from gometalinter to golangci-lint. Closes #1225 (#1226) 2019-03-08 16:22:04 -08:00
Jesse Suen
0f248e9149 Replace git fetch implementation with git CLI (from go-git) (#1244) 2019-03-08 14:08:02 -08:00
Alexander Matyushentsev
461d8c980f Fix nil pointer dereference in CompareAppState (#1234) (#1240) 2019-03-07 19:24:47 -08:00
Alexander Matyushentsev
f120c1dedb Fix autocomlete dropdown scrolling 2019-03-07 16:37:44 -08:00
Alexander Matyushentsev
a54dc192d7 Issue #1058 - Allows you to set sync-policy when you create an app 2019-03-07 12:07:06 -08:00
Alexander Matyushentsev
0850db530f Issue #1236 - project field in 'create application' dialog is confusing 2019-03-07 11:26:15 -08:00
Alexander Matyushentsev
0a1a579714 Enable autocomplete suggestions filtering only on application list page 2019-03-06 15:22:26 -08:00
Alexander Matyushentsev
9a7fecef06 Issue #1231 - Deprecated resource kinds from 'extensions' groups are not reconciled correctly (#1232) 2019-03-06 01:42:26 -08:00
Alexander Matyushentsev
39c63371bf Update link to config management plugins in custom_tools.md (#1228) 2019-03-06 01:16:19 -08:00
Jesse Suen
80b0e1138c Update documentation for v0.12.0 (#1227)
* Sort kustomize params in GetAppDetails
2019-03-06 00:09:01 -08:00
Alexander Matyushentsev
3acc0b3af2 Issue #1229 - App creation failed for public repository (#1230) 2019-03-06 00:02:27 -08:00
Alexander Matyushentsev
af3a766304 Fix rendering revision history parameters 2019-03-05 15:57:22 -08:00
Alexander Matyushentsev
d7b1ffd014 Issue #1141 - Deprecate ComponentParameterOverrides in favor of source specific config 2019-03-05 15:24:58 -08:00
Alexander Matyushentsev
0a6028e116 Issue #1122 - Autosuggest should expand to the top is there is not enough space to expand bottom 2019-03-04 11:33:36 -08:00
Alexander Matyushentsev
61173d7e70 Recalculate autocomplete menu position on scroll 2019-03-04 09:21:05 -08:00
Alexander Matyushentsev
3ae30c9028 Pre-populate sample app values when user create the first app 2019-03-04 09:20:20 -08:00
Alexander Matyushentsev
fa62cdf127 Fix showing edit button on resource yaml editor panel 2019-03-03 23:34:30 -08:00
Alexander Matyushentsev
d715ac9e53 Issue #1176 - UI should support raw YAML editor when creating/updating an app 2019-03-02 21:59:22 -08:00
Alex Collins
8fa0d9c4fc Corrects lint error 2019-02-27 16:34:42 -08:00
dthomson25
5d2304b18f Add support for suspended status (#94) 2019-02-27 14:36:33 -08:00
Alexander Matyushentsev
a886a58421 Issue #1176 - support editing raw application spec YAML 2019-02-27 14:08:23 -08:00
Alex Collins
2f5549e0c8 Allows you to set sync-policy when you create an app. Closes #1058 (#93) 2019-02-27 13:03:42 -08:00
Alexander Matyushentsev
915514e37b Support editing resource manifests on application details page 2019-02-26 22:23:33 -08:00
Alexander Matyushentsev
e776d64b6f Fix 'details' menu item on application details page 2019-02-26 13:15:14 -08:00
Alexander Matyushentsev
d3c41395bc Fix double bottom border in form fields 2019-02-26 11:58:18 -08:00
Alexander Matyushentsev
7cc55c078f Fix React warning which happens on App list page after app creation 2019-02-26 11:51:35 -08:00
Alexander Matyushentsev
a1edbb5972 Issue #1086 - Switch to text based YAML diff instead of json diff 2019-02-26 11:29:47 -08:00
Alexander Matyushentsev
6fe6a603d7 Upgrade react; use argo-ui from git instead of npm 2019-02-26 08:05:34 -08:00
Alexander Matyushentsev
2a9a9884cf Issue #1152 - Render cluster name in application wizard 2019-02-25 15:03:57 -08:00
Alexander Matyushentsev
a5fedca016 Fix application node selection 2019-02-22 08:56:01 -08:00
Alexander Matyushentsev
98caad1ff7 Issue #1160 - Deleting an application child resource from a parent application deletes the parent 2019-02-22 08:45:31 -08:00
Alexander Matyushentsev
b38485e169 Allow user configure table page size 2019-02-21 15:48:25 -08:00
Alexander Matyushentsev
b92e0a6d0f UI fails to update application which has spec errors and not reconciled by app controller 2019-02-20 12:47:17 -08:00
Alexander Matyushentsev
8b366ed5c2 Application details page fails if application have been reconciled by app controller 2019-02-20 12:39:39 -08:00
Alexander Matyushentsev
7b1bf35b8c Don't show directory app parameters for kustomize apps (#92) 2019-02-15 09:13:54 -08:00
Alex Collins
c631589306 Directory recurse (#90)
* Adds support for viewing and editing directory recurse

* Adds support for creating apps using directory recurse

* Adds details of pre-commit checks

* Removed redundant file

* Removed redundant file

* Removes unused field

* Renames CheckboxInputField to CheckboxField

* Renames directory to just "checkbox"

* Updates to use CheckboxField
2019-02-11 13:58:21 -08:00
Alexander Matyushentsev
138233e97d Issue #929 - Add indicator to app resources tree if resources are filtered 2019-02-11 09:40:08 -08:00
Alexander Matyushentsev
f09e213202 Issue #1101 - Add menu to resource list table (#91) 2019-02-11 08:42:13 -08:00
Alexander Matyushentsev
960a51853e Fix broken prod build 2019-02-11 07:32:26 -08:00
Alexander Matyushentsev
906ac8f987 Add list view to application details page 2019-02-08 15:10:37 -08:00
Alexander Matyushentsev
6bd8dea088 Handle invalid/obsolete applications list filters in user preferences 2019-02-07 14:02:24 -08:00
Alexander Matyushentsev
9f1a1f0f5e Add summary view to applications list page 2019-02-07 13:13:41 -08:00
Alexander Matyushentsev
c4952fe81e Add 'x' to search bar on applications list page 2019-02-07 10:05:00 -08:00
Alexander Matyushentsev
abad42fcd5 Add search functionality to applications list page 2019-02-07 09:54:29 -08:00
Alexander Matyushentsev
fc87fa0630 Fix cluster filtering 2019-02-06 23:19:45 -08:00
Alexander Matyushentsev
59c5c6276d Delete tags-editor component 2019-02-06 22:39:01 -08:00
Alexander Matyushentsev
9d81e923b9 Impement cluster/networks filtering on Applications list page 2019-02-06 22:15:40 -08:00
Alexander Matyushentsev
4d402c1223 Issue #1055 - Render sync/health status filter checkboxes even if there are not apps in that status 2019-02-05 13:44:46 -08:00
Alexander Matyushentsev
ce18509697 Issue #279 - improve empty state design 2019-02-05 08:12:59 -08:00
Alexander Matyushentsev
e9990767fa Minor applications view improvements 2019-02-04 22:35:40 -08:00
Alexander Matyushentsev
cf4896bb3a Issue #1061 - Implement table view mode on applications list page 2019-02-04 18:49:01 -08:00
Alexander Matyushentsev
adcac7f7b4 Issue #1055 - Implement applications list view filtering 2019-02-04 17:32:36 -08:00
Alexander Matyushentsev
4bcef1bc67 Issue #1061 - Fix JS crash during app creation 2019-01-24 22:51:15 -08:00
Alexander Matyushentsev
b1b5ce211e Update project LICENSE 2019-01-22 08:44:47 -08:00
Alexander Matyushentsev
150c69bb1d Correctly handle empty response from repository/<repo>/apps API 2019-01-17 16:12:26 -08:00
Alexander Matyushentsev
6006254716 Issue #1036 - Fix rendering resources state without status 2019-01-17 11:37:04 -08:00
Alexander Matyushentsev
2658cdfa5d Issue #1032 - fix JS error during editing helm app without value files 2019-01-17 09:02:03 -08:00
Alexander Matyushentsev
eb79239e6e Issue #1028 - Resource details 'blink' when resource changes 2019-01-16 09:58:55 -08:00
Alexander Matyushentsev
5c0c5a8446 Issue #1027 - UI should render page title to simplify navigation 2019-01-16 09:53:27 -08:00
Alexander Matyushentsev
fbc2021ed8 Add metadata.creationTimestamp to list of requested app fields 2019-01-09 14:10:43 -08:00
Jesse Suen
564413df01 Add descriptions for project fields and slight improvements to UI/validation 2019-01-08 02:57:02 -08:00
Alexander Matyushentsev
5f32cae938 Issue #966 - UI error with helm charts parameters 2019-01-03 10:39:29 -08:00
Alexander Matyushentsev
198e4fe520 Issue #969 - Fix rendering number of application parameter overrides 2019-01-03 09:37:03 -08:00
Alexander Matyushentsev
3c2febf8b4 Update argo slack URL 2018-12-27 15:58:08 -08:00
Alexander Matyushentsev
0d4c10bd45 Disable save button while saving application changes 2018-12-26 13:32:47 -08:00
Alexander Matyushentsev
d4e4d7e4b4 Issue #952 - Add helm file if user selected file name from autocompletion dropdown 2018-12-26 10:49:09 -08:00
Alexander Matyushentsev
943bf8c69c Show operation in progress even if controller is down 2018-12-19 16:20:27 -08:00
Alexander Matyushentsev
be732210a4 Fix broken filter after updating app 2018-12-19 14:51:55 -08:00
Alexander Matyushentsev
d60ef39f82 Issue #939 - Fix nil dereference error in Diff function 2018-12-18 10:02:22 -08:00
Alexander Matyushentsev
23121b3528 Issue 914 - Add application force refresh button (#88) 2018-12-17 18:23:55 -08:00
Alexander Matyushentsev
135dce436e Issue 906 - Support setting different base href in UI (#87) 2018-12-14 14:01:02 -08:00
Alexander Matyushentsev
f38a3ac6cd Fix filtering hooks 2018-12-14 10:16:21 -08:00
Alexander Matyushentsev
a1382e107f Fix build issue 2018-12-13 10:02:02 -08:00
Alexander Matyushentsev
3367c879bd Issue #912 - Make ResourceNode 'tags' into a more generic 'info' struct (#86)
* Issue #912 - Make ResourceNode 'tags' into a more generic 'info' struct
2018-12-12 13:17:23 -08:00
Alexander Matyushentsev
ba8005740a Fix JS error after force app refresh 2018-12-11 13:26:18 -08:00
Alexander Matyushentsev
0d225965ff Issue #909 - add sync and health filters 2018-12-11 12:45:22 -08:00
Alexander Matyushentsev
c22aff33ce Issue #417 - Add force delete option for deleting resources 2018-12-07 11:02:40 -08:00
Jesse Suen
eb73d5c372 Tweak width of error column 2018-12-07 01:45:13 -08:00
Jesse Suen
2bc9995b61 Add sync and health details to app header (#85) 2018-12-06 21:59:26 -08:00
Alexander Matyushentsev
7188823ade Issue #770 - Helm value files on App details page (#84) 2018-12-06 15:33:27 -08:00
Alexander Matyushentsev
10f4a22192 Issue #741 - Trim repo URL in app creation wizard 2018-12-06 10:45:14 -08:00
Alexander Matyushentsev
07111fa952 Issue #732 - Cmd+Click should open app in new tab 2018-12-06 08:52:12 -08:00
Alexander Matyushentsev
adf522454e Issue #821 - Login button when external OIDC provider is configured 2018-12-05 12:00:24 -08:00
Alexander Matyushentsev
d7b89f5a7c Remove parameters field from ApplicationStatus (#83) 2018-12-04 22:29:14 -08:00
Alexander Matyushentsev
39f8662beb Gracefully handle application deletion in UI 2018-12-04 15:30:45 -08:00
Alexander Matyushentsev
bf157fd794 Fix editing parameters with multiple '.' character in name 2018-12-04 13:47:43 -08:00
Alexander Matyushentsev
4c62c19230 Upgrade argo-ui version 2018-12-04 11:13:33 -08:00
Alexander Matyushentsev
6f0f9ec1ba Animate application resource changes 2018-12-04 10:35:51 -08:00
Jesse Suen
3c8da80fa4 Accommodate rework of application status datastructure 2018-12-04 10:03:50 -08:00
Jesse Suen
a9f18abb41 Proper support for resource lifecycle hooks
Add ability to perform a dry-run sync
Refactor models to use renamed types
2018-12-04 10:03:50 -08:00
Alexander Matyushentsev
4a1590c0bd Fix null pointer exception during app events loading 2018-12-03 15:33:35 -08:00
Alexander Matyushentsev
2b89f6fb71 Issue #858 - Support loading resource events for multi-network apps (#81) 2018-12-03 14:54:06 -08:00
Alexander Matyushentsev
e5fd75cdd2 Issue #740 - Render synced to revision 2018-12-03 14:18:12 -08:00
Alexander Matyushentsev
59eb3ab749 Issue #822 - No error indication when insufficient permissions to create tokens 2018-12-03 13:52:46 -08:00
Jesse Suen
d67fd59f65 Remove ability to set helm release name (#80) 2018-11-30 23:10:17 -08:00
Alexander Matyushentsev
6a90de738c Switch to never DataLoader version 2018-11-30 22:43:46 -08:00
Alexander Matyushentsev
cf757831b6 Issue #853 - pod logs does not work in multi namespaced apps (#79) 2018-11-30 15:40:18 -08:00
Jesse Suen
c48b9f8edd Rename 'controlled resources' to 'managed resources' (#78)
Rename 'resources tree' to 'resource tree'
2018-11-30 10:38:12 -08:00
Alexander Matyushentsev
dfb3451000 Fix missing app comparison, health status icons 2018-11-30 10:09:25 -08:00
Jesse Suen
419a40beac Support project whitelists/blacklists rendering and editing (#77)
Support adding/removing of project role groups
Remove obsolete rollback result from models
Support new style of structured application sources
2018-11-29 13:13:13 -08:00
Alexander Matyushentsev
15032dd3b9 Fix null pointer exception on resource details panel 2018-11-28 16:29:27 -08:00
Alexander Matyushentsev
649152c97a Use /<app>/resource-tree and /<app>/controlled-resources apis 2018-11-28 13:38:19 -08:00
Jesse Suen
c387dca4fb Present a 'deletion' operation while application is deleting (#76) 2018-11-27 14:39:56 -08:00
Jesse Suen
d9b0e6b234 Update link to download argocd CLI directly from API server (#75) 2018-11-27 14:39:38 -08:00
Jesse Suen
8372d751fd Handle case where jwtTokens is omitted from the payload (#74) 2018-11-27 14:39:26 -08:00
Alexander Matyushentsev
0a752fb61f Issue #621 - Load resources from API (#73) 2018-11-16 17:10:48 -08:00
Alexander Matyushentsev
16be7e708f Issue #768 - Fix application wizard crash (#72) 2018-11-13 15:15:17 -08:00
Chris Garland
09e4c32832 Allow 'syncApplication' action to reference target revision rather then hard-coding to 'HEAD' (#69) 2018-11-01 13:15:43 -07:00
dthomson25
7298289f3a Show operation without status.operationStatus existing (#70) 2018-11-01 11:27:11 -07:00
Alexander Matyushentsev
136cf5be52 Issue #697 - Use /v1/applications field selection feature (#68) 2018-10-30 10:26:47 -07:00
dthomson25
d5023bc195 Support adding name prefix for helm and kustomize (#67) 2018-10-30 09:59:32 -07:00
Alexander Matyushentsev
e94a551ec2 Show confirmation message only if sync is successful (#66) 2018-10-25 13:04:16 -07:00
Alexander Matyushentsev
127cf77db4 Fix sso relogin redirect 2018-10-22 23:37:04 -07:00
Alexander Matyushentsev
4c2d4d11ef Issue #707 - Application details page don't allow editing parameter if parameter name has '.' (#65) 2018-10-18 20:13:38 -07:00
Alexander Matyushentsev
89690b1e97 Issue #508 - Support fine grained sync in UI (#64) 2018-10-18 20:13:06 -07:00
Alexander Matyushentsev
ddbb39bb22 Issue #693 - Input type text instead of password on Connect repo panel (#63) 2018-10-17 10:00:31 -07:00
Alexander Matyushentsev
a775f48cf0 Issue #655 - Generate role token click resets policy changes (#62) 2018-10-16 17:22:33 -07:00
Alexander Matyushentsev
8e10610173 Issue #685 - Better update conflict error handing during app editing in UI (#61) 2018-10-16 17:16:41 -07:00
Alexander Matyushentsev
9356994d6a Issue #681 - Display init container logs (#60) 2018-10-10 17:53:08 -04:00
Alexander Matyushentsev
36b8abe601 Issue #683 - Resource nodes are 'jumping' on app details page (#59) 2018-10-10 17:51:50 -04:00
Alexander Matyushentsev
c570186a6f Issue 348 - Redirect to /auth/login instead of /login when SSO token expires (#58) 2018-10-10 12:18:00 -04:00
Alexander Matyushentsev
db686b67ec Issue #669 - Sync always suggest using latest revision instead of target (#57) 2018-10-04 14:20:15 -04:00
Alexander Matyushentsev
40e04ab639 Issue #624 - Support ability to use a helm values files from a URL (#56) 2018-10-04 13:18:41 -04:00
Alexander Matyushentsev
1891d7cde7 Support public not-connected repo in app creation UI (#55) 2018-10-04 12:46:54 -04:00
Alexander Matyushentsev
98d224a5ec Move form-form components to argo-ui; Use autocomplete component (#54) 2018-10-02 12:54:17 -04:00
dthomson25
f62bd58fae Limit source and destination options to permissions in project (#53) 2018-10-02 11:03:36 -04:00
dthomson25
1ff4548a2c Load params dyanamically for rollback (#52) 2018-09-28 15:45:34 -07:00
dthomson25
ba36b3f63b Insert whitespaces after commas in policies (#51) 2018-09-27 11:04:19 -07:00
Alexander Matyushentsev
f353236c8a Move DataLoader and NotificationError components to argo-ui libarary (#50) 2018-09-27 13:03:35 -04:00
dthomson25
1bcc4d3991 Change textarea for policies to interactive UI (#48) 2018-09-24 09:42:15 -07:00
Alexander Matyushentsev
b06ae9ea47 Issue #615 - Ability to modify application from UI (#49)
* Issue #615 - Ability to modify application from UI
2018-09-20 16:43:57 -07:00
dthomson25
7fd326eb21 Add create and delete JWT tokens functionality (#45) 2018-09-18 23:54:46 -07:00
Alexander Matyushentsev
2a8fccc6cd Fix JS error in project edit UI (#47) 2018-09-18 09:51:41 -07:00
Alexander Matyushentsev
1295a89911 Issue #566 - indicate when operation is in progress or has failed (#46) 2018-09-17 15:07:58 -07:00
dthomson25
3e2f205045 Implement project role functionality (#43) 2018-09-14 09:51:20 -07:00
Alexander Matyushentsev
eff5421ce4 Issue #601 - Fix NPE in getResourceLabels function (#44) 2018-09-13 15:13:10 -07:00
Alexander Matyushentsev
857ac806ae Issue #573 - Projects filter does not work when application got changed (#42) 2018-09-10 17:13:23 -07:00
Alexander Matyushentsev
0f7ae16eb6 Issue #562 - App creation wizard should allow specifying source revision (#41) 2018-09-10 16:49:54 -07:00
Alexander Matyushentsev
c31a756517 Issue #396 - provide a YAML view of resources (#40) 2018-09-10 14:41:19 -07:00
Andrew Merenbach
e09453d6e4 Merge pull request #39 from merenbach/hide-no-override-label
Only label overrides > 0
2018-09-07 16:44:58 -07:00
Andrew Merenbach
9177011abd Only label overrides > 0 2018-09-07 16:11:46 -07:00
Andrew Merenbach
9e45d5c8db Merge pull request #38 from merenbach/503-indicate-overrides
Label apps with overrides
2018-09-07 11:40:29 -07:00
Andrew Merenbach
fca687f5fb Satisfy linter, thanks @alexmt 2018-09-07 11:35:44 -07:00
Andrew Merenbach
1223955cba Add count of component parameter overrides 2018-09-07 10:00:57 -07:00
Andrew Merenbach
636c896b90 Use switch statement instead of if-else 2018-09-07 09:50:32 -07:00
Andrew Merenbach
2fa93fd694 Merge pull request #37 from merenbach/539-indicate-notready-pods
Show number of ready containers
2018-09-07 08:33:12 -07:00
Andrew Merenbach
7893e6461b Use index signature instead of map, thanks @alexmt 2018-09-06 16:42:59 -07:00
Andrew Merenbach
1bad5b3179 Fix linter errors, thanks @alexmt 2018-09-06 16:41:50 -07:00
Andrew Merenbach
a0330d439c Rm spurious newline 2018-09-06 16:11:12 -07:00
Andrew Merenbach
3d831c1db7 Simplify filter even more 2018-09-06 16:08:33 -07:00
Andrew Merenbach
613e294f15 Simplify filter 2018-09-06 16:07:55 -07:00
Andrew Merenbach
013d37f23a Refactor code, thanks @alexmt 2018-09-06 16:05:55 -07:00
Andrew Merenbach
a38f293246 Add typing to new function, thanks @alexmt 2018-09-06 15:30:25 -07:00
Andrew Merenbach
f9c39fbc3b Show number of ready containers 2018-09-06 15:16:42 -07:00
Alexander Matyushentsev
a85ff52115 Issue 499 - Support helm values files in App creation wizard (#35) 2018-09-06 00:37:41 +03:00
Alexander Matyushentsev
c359a24017 Issue 457 - Improve resource diff rendering (#36) 2018-09-06 00:37:27 +03:00
Jesse Suen
7816430fd7 Project deletion was not waiting for confirmation before deletion (#34) 2018-09-01 00:08:22 -07:00
Jesse Suen
3e4ed83112 Add ability edit projects with * sources and destinations (#33) 2018-09-01 00:08:10 -07:00
Jesse Suen
617d7be300 UI support for deleting an application resource using the new endpoint (#32) 2018-08-15 12:55:31 -07:00
Jesse Suen
108dbb8efd App create wizard support for kustomize apps (#31) 2018-08-15 12:55:20 -07:00
Alexander Matyushentsev
40fdda3f5a Issue #458 - Render events on project details page (#30) 2018-08-10 03:01:42 +03:00
Alexander Matyushentsev
c451919511 Issue #458 - Project management UI (#29) 2018-08-09 23:11:55 +03:00
Alexander Matyushentsev
4f6b686ed7 Upgrade argo-ui version 2018-08-08 14:41:17 -07:00
Alexander Matyushentsev
455993b164 Issue #458 - add projects list page (#28) 2018-08-04 21:34:55 +03:00
Alexander Matyushentsev
ec47a07195 Fix npe error in app wizard 2018-08-03 11:45:31 -07:00
Alexander Matyushentsev
9cbfc37774 Merge pull request #27 from alexmt/459-app-wizard-improvement
Issue #459 - Improve application creation wizard
2018-08-03 21:33:24 +03:00
Alexander Matyushentsev
1928548346 Issue #459 - Improve application creation wizard 2018-08-03 11:30:08 -07:00
Alexander Matyushentsev
bc90faa69f Merge pull request #26 from alexmt/474-list-apps
Issue #474 - Load app details on the fly
2018-08-03 20:10:58 +03:00
Alexander Matyushentsev
29563434df Issue #474 - Load app details on the fly 2018-08-03 09:45:25 -07:00
Alexander Matyushentsev
eca1789ad1 Merge pull request #25 from alexmt/446-loading-error-notification
Issue #446 - Improve data loading errors notification
2018-08-03 00:51:00 +03:00
Alexander Matyushentsev
7c60ff0201 Issue #446 - Improve data loading errors notification 2018-08-02 14:07:49 -07:00
Alexander Matyushentsev
a930b4fdca Fix linter warning 2018-08-02 08:41:39 -07:00
Alexander Matyushentsev
9c6125deef Merge pull request #24 from alexmt/463-empty-component
Issue #463 - Support parameters with empty component name
2018-08-01 08:07:01 +03:00
Alexander Matyushentsev
26d390e2bd Issue #463 - Support parameters with empty component name 2018-07-31 22:06:22 -07:00
Andrew Merenbach
363ca3febb Merge pull request #23 from merenbach/fix-application-card
Update colors for application cards
2018-07-31 17:12:57 -07:00
Andrew Merenbach
6c648ef0d8 Update colors for application cards 2018-07-31 16:18:59 -07:00
Alexander Matyushentsev
3d9943c7b3 Fix wizard back navigration if drop-in directory/helm selected 2018-07-27 13:34:37 -07:00
Alexander Matyushentsev
a48b1bcbae Merge pull request #22 from alexmt/443-helm-app
Issue #443 - UI changes for selecting Helm and manifest app directories
2018-07-25 21:08:39 +03:00
Alexander Matyushentsev
1483ee4c8c Issue #443 - UI changes for selecting Helm and manifest app directories 2018-07-25 11:04:18 -07:00
Alexander Matyushentsev
ab505fddcd Merge pull request #21 from alexmt/442-app-project
Issue 442 - UI does not allow to select project
2018-07-25 19:49:02 +03:00
Alexander Matyushentsev
62158a0c06 Issue 442 - UI does not allow to select project 2018-07-25 09:40:10 -07:00
Alexander Matyushentsev
83d0c4b084 Merge pull request #20 from alexmt/340-app-events-ui
Issue #340 - render application events
2018-07-24 20:13:32 +03:00
Alexander Matyushentsev
95b237bdc5 Issue #340 - render application events 2018-07-23 09:02:15 -07:00
Alexander Matyushentsev
2e1db8f69b Merge pull request #19 from alexmt/351-sso-error-message
Issue #351 - render sso error message
2018-07-18 00:48:54 +03:00
Alexander Matyushentsev
66a182e743 Issue #351 - render sso error message 2018-07-17 14:16:29 -07:00
Alexander Matyushentsev
28580b09c3 Merge pull request #18 from alexmt/406-terminate-button
Issue #406 - add button to terminate a operation
2018-07-16 20:02:38 +03:00
Alexander Matyushentsev
72bcad4810 Explicitly define function return type 2018-07-16 10:00:49 -07:00
Alexander Matyushentsev
6862fe3551 Issue #406 - add button to terminate a operation 2018-07-16 09:41:33 -07:00
Alexander Matyushentsev
71b02e3bcd Merge pull request #17 from alexmt/402-deployment-override-history
Issue #402 - App deployment history don't display parameter overrides
2018-07-14 01:23:50 +03:00
Alexander Matyushentsev
af88064c2a Merge pull request #16 from alexmt/400-swagger-link
Issue #400 - Provide a link to swagger UI
2018-07-14 01:17:20 +03:00
Alexander Matyushentsev
658a16fb78 Issue #402 - App deployment history don't display parameter overrides 2018-07-13 15:16:40 -07:00
Alexander Matyushentsev
bc2c2a5189 Issue #400 - Provide a link to swagger UI 2018-07-13 14:55:27 -07:00
Alexander Matyushentsev
7ea4d5a957 Merge pull request #15 from alexmt/bug-fixes
UI Bug fixes
2018-07-13 02:02:31 +03:00
Alexander Matyushentsev
1db0fbdedc Render sync hooks and operation status message 2018-07-12 15:38:56 -07:00
Alexander Matyushentsev
3a25697349 Add revision to app summary panel 2018-07-12 15:38:29 -07:00
Alexander Matyushentsev
4c80d6bc34 Set cascade to true in app delete popup 2018-07-12 15:37:56 -07:00
Alexander Matyushentsev
49f342ad43 Merge pull request #14 from alexmt/bug-fixes
Various UI bug fixes
2018-07-13 00:23:28 +03:00
Alexander Matyushentsev
e849321f62 Fix connect repo URL in app creation wizard 2018-07-12 13:55:31 -07:00
Alexander Matyushentsev
6ded5c5cfe Fix health/comparsion status icons 2018-07-12 13:53:23 -07:00
Alexander Matyushentsev
191f737d5f Remove unnecessary margins on application details page 2018-07-12 08:34:38 -07:00
Alexander Matyushentsev
4a03d1120f Merge pull request #13 from alexmt/290-cluster-list-page
Issue #290 - Cluster list page
2018-07-12 02:47:43 +03:00
Alexander Matyushentsev
5bbc94188c Issue #290 - Cluster list page 2018-07-11 16:32:50 -07:00
Andrew Merenbach
bf9f634613 Merge pull request #12 from merenbach/update-status-on-sync
Refactor app sync and delete
2018-07-11 13:57:12 -07:00
Andrew Merenbach
73452f7b10 Rm spurious app update, thanks @alexmt 2018-07-11 12:57:18 -07:00
Andrew Merenbach
eb92001626 Fix await/then redundancy, thanks @alexmt 2018-07-11 12:53:14 -07:00
Andrew Merenbach
5b5fadce77 Use promises instead of success callback 2018-07-11 11:51:22 -07:00
Alexander Matyushentsev
87f706aa1e Fix linter warning 2018-07-11 11:35:36 -07:00
Alexander Matyushentsev
4b36f0e211 Fix javascript exception caused by missing check 2018-07-11 11:34:45 -07:00
Andrew Merenbach
b575f45c11 Merge pull request #11 from merenbach/label-terminating-pods
Label terminating pods
2018-07-11 11:01:39 -07:00
Alexander Matyushentsev
3900d11454 Merge pull request #9 from alexmt/277-condition-types
Issue #277 - support error/warning/info condition types
2018-07-11 20:58:41 +03:00
Alexander Matyushentsev
3434f5e601 Merge pull request #10 from alexmt/341-app-refresh-btn
Issue #341 - add refresh button in app view
2018-07-11 20:58:12 +03:00
Andrew Merenbach
8381581821 Check metadata for deletionTimestamp 2018-07-11 10:55:14 -07:00
Andrew Merenbach
e7ef4dbc4f Update package dependencies 2018-07-11 10:55:14 -07:00
Alexander Matyushentsev
42778b5a91 Issue #341 - add refresh button in app view 2018-07-11 10:55:12 -07:00
Alexander Matyushentsev
ac89d49bea Issue #277 - support error/warning/info condition types 2018-07-11 10:43:27 -07:00
Alexander Matyushentsev
9e43ed4293 Merge pull request #8 from alexmt/337-remember-filtering
Issue #337 - remember my resource filtering preferences
2018-07-11 19:52:55 +03:00
Alexander Matyushentsev
d37b09b6bc Fix linter errors 2018-07-11 09:31:05 -07:00
Andrew Merenbach
5e60a65fc6 Merge pull request #5 from merenbach/support-force-delete
Support cascading delete
2018-07-10 16:42:00 -07:00
Andrew Merenbach
92125c51b6 Behold the glory of an anonymous component 2018-07-10 16:07:47 -07:00
Alexander Matyushentsev
4301fc6b58 Issue #337 - remember my resource filtering preferences 2018-07-10 15:16:23 -07:00
Andrew Merenbach
da1223aa57 Get working, but checkbox UI does not update 2018-07-10 15:09:09 -07:00
Alexander Matyushentsev
4330130017 Merge pull request #7 from alexmt/306-allow-redeploy-latest
Issue #306 - UI should allow redeploying most recent successful deployment from history
2018-07-11 00:52:23 +03:00
Andrew Merenbach
db8528c037 Another step 2018-07-10 14:51:05 -07:00
Andrew Merenbach
5cad0db347 Take initial steps toward checkbox for cascade 2018-07-10 14:51:05 -07:00
Andrew Merenbach
658f72fe84 Clean up query construction, thanks @alexmt 2018-07-10 14:51:05 -07:00
Andrew Merenbach
ee375a0224 Use proper backend var name for cascade 2018-07-10 14:51:05 -07:00
Andrew Merenbach
e3a912a46f Pass app context to show prompts in shared code 2018-07-10 14:51:05 -07:00
Andrew Merenbach
b94f3895db Support cascading now 2018-07-10 14:51:05 -07:00
Andrew Merenbach
4404df3903 Fix popup 2018-07-10 14:51:05 -07:00
Andrew Merenbach
a502d5215a Rm unnecessary semicolon, thanks linter 2018-07-10 14:51:05 -07:00
Andrew Merenbach
b64143d314 Factor out common app delete functionality 2018-07-10 14:51:05 -07:00
Andrew Merenbach
0148112676 Tweak error message 2018-07-10 14:51:05 -07:00
Alexander Matyushentsev
ceb838d559 Issue #306 - UI should allow redeploying most recent successful deployment from history 2018-07-10 12:35:18 -07:00
Alexander Matyushentsev
a30aff9454 Issue #352 - resource names are almost always truncated 2018-07-10 11:45:50 -07:00
Alexander Matyushentsev
47c756b243 Upgrade argo-ui dependency 2018-07-10 11:11:13 -07:00
Alexander Matyushentsev
afe84768a5 Remove unnecessary left side border on app status panel 2018-07-10 11:10:38 -07:00
Andrew Merenbach
6da644b669 Merge pull request #4 from merenbach/show-application-conditions
Place conditions into app details page
2018-07-09 15:40:00 -07:00
Andrew Merenbach
a02941cb99 Satisfy linter 2018-07-09 15:37:53 -07:00
Andrew Merenbach
4c8f02e35d Use table instead of white boxes 2018-07-09 13:40:43 -07:00
Andrew Merenbach
a7d2fddd07 Break out condition table into columns 2018-07-09 11:32:15 -07:00
Andrew Merenbach
01a3ce70cb Rm debugging test conditions 2018-07-09 10:52:28 -07:00
Andrew Merenbach
92adcf107c Tweak warning display, thanks @alexmt 2018-07-09 10:48:44 -07:00
Andrew Merenbach
cbf7b70a8d Add missing key, rm unneeded code, thanks @alexmt 2018-07-09 10:17:44 -07:00
Andrew Merenbach
579c230969 Add slideout conditions panel 2018-07-05 13:55:48 -07:00
Andrew Merenbach
561843d006 Display warning count at top of panel 2018-07-05 13:55:47 -07:00
Andrew Merenbach
f83ae97fbd Place conditions into app details page 2018-07-05 13:55:47 -07:00
Andrew Merenbach
35afec5884 Merge pull request #6 from merenbach/fix-events-truncation
Don't truncate columns in events table
2018-07-05 13:54:06 -07:00
Andrew Merenbach
ae41dba29f Don't truncate columns in events table 2018-07-05 12:24:44 -07:00
Andrew Merenbach
ebf808b0f9 Merge pull request #3 from merenbach/rm-app-url-namespace
Rm app url namespace
2018-06-28 13:58:03 -07:00
Andrew Merenbach
8a284f1726 Rm namespace from routing 2018-06-28 13:39:39 -07:00
Andrew Merenbach
530320ca6e Remove app URL namespace 2018-06-28 11:48:35 -07:00
Alexander Matyushentsev
e71bdcfdd6 Render project on app details page; implement filtering on app list page 2018-06-25 23:29:17 -07:00
Alexander Matyushentsev
fc49ca3438 Support option for app sync operation on app details page #289 2018-06-14 14:04:24 -07:00
Alexander Matyushentsev
a688d38165 Issue #231 - Display pod status on application details page 2018-06-14 12:40:36 -07:00
Alexander Matyushentsev
a3379dceec Issue #286 - Resource events tab on application details page 2018-06-14 10:29:51 -07:00
Alexander Matyushentsev
f5ad24f352 Restore missing loading indicator on application list page 2018-06-13 14:10:49 -07:00
Alexander Matyushentsev
ae8834a6f2 Fix NPE error 2018-06-12 15:38:32 -07:00
Alexander Matyushentsev
ccd6863ad4 Improve error messages 2018-06-12 11:15:59 -07:00
Alexander Matyushentsev
57ad86a222 Add health status details message 2018-06-12 09:25:50 -07:00
Alexander Matyushentsev
d8d32ec1f5 Visalize ksonnet app loading state on app creation wizard 2018-06-12 09:22:09 -07:00
Alexander Matyushentsev
56d06482fe Bug fixing: don't reset filter on app details page; add health/sync status icons to application list page 2018-06-11 11:23:18 -07:00
Andrew Merenbach
309f44a079 Merge pull request #2 from merenbach/app-list-redesign
Application list redesign
2018-06-07 10:27:02 -07:00
Andrew Merenbach
9401f94b78 Rename vars for clarity 2018-06-07 10:17:44 -07:00
Andrew Merenbach
fab12da4e7 Satisfy linter 2018-06-07 10:16:44 -07:00
Alexander Matyushentsev
4bd49b0bf6 Move logout button to right top corner 2018-06-07 10:16:32 -07:00
Alexander Matyushentsev
a972f76224 Reset app creation wizard state 2018-06-07 10:04:03 -07:00
Andrew Merenbach
f28cd3f709 Handle date subtraction now 2018-06-07 09:51:26 -07:00
Andrew Merenbach
c87d6ec182 Add app delete code 2018-06-06 17:20:50 -07:00
Andrew Merenbach
d4e781d48f Actually sync when requested 2018-06-06 17:17:19 -07:00
Andrew Merenbach
6e5efa1e09 Update spacing, sizing, casing 2018-06-06 17:03:14 -07:00
Andrew Merenbach
326489ff60 Add dropdown 2018-06-06 10:13:45 -07:00
Andrew Merenbach
62a7c160ab Fix indentation 2018-06-06 10:11:21 -07:00
Andrew Merenbach
13937ac7f9 Get 2-up layout 2018-06-06 09:24:02 -07:00
Andrew Merenbach
9128daf883 Follow BEM conventions for class names, thanks @alexmt 2018-06-06 09:24:02 -07:00
Andrew Merenbach
6caa019231 Use proper color names, thanks @alexmt 2018-06-06 09:24:02 -07:00
Andrew Merenbach
4055960757 Get border colors correct 2018-06-06 09:24:02 -07:00
Andrew Merenbach
88fa8bb8b2 Flesh out initial cell design a bit 2018-06-06 09:24:01 -07:00
Andrew Merenbach
269fcbb091 Start redesigning app list 2018-06-06 09:23:34 -07:00
Alexander Matyushentsev
a97ac8fadf Merge branch 'master' of github.com:argoproj/argo-cd-ui 2018-06-05 15:10:15 -07:00
Alexander Matyushentsev
93cbef4aeb Implement Application creation wizard 2018-06-05 15:09:44 -07:00
Alexander Matyushentsev
929f30c58b Issue #241 - Repositories list page 2018-06-04 11:57:28 -07:00
Andrew Merenbach
e59f5b1ba4 Merge pull request #1 from merenbach/update-bootstrap
Update upath plus getting started instructions in README
2018-05-30 08:51:31 -07:00
Andrew Merenbach
20c8b0cec9 Update upath to version 1.1.0 to resolve Node 10 incompatibility 2018-05-30 08:28:12 -07:00
Andrew Merenbach
9c0dc4e865 Fix README, thanks @alexmt 2018-05-30 08:27:55 -07:00
Andrew Merenbach
28e68a2a3c Clean up instructions a little more 2018-05-29 17:24:28 -07:00
Andrew Merenbach
a39d3f28e2 Update getting started instructions in README 2018-05-29 17:21:00 -07:00
Alexander Matyushentsev
2ba7eb83d2 Remove last redux dependency 2018-05-29 13:50:00 -07:00
Alexander Matyushentsev
d8129ba59f Move notification manager and popup manager to argo-ui 2018-05-29 13:43:37 -07:00
Alexander Matyushentsev
360c7e051e Fix default resource kind filtering bug 2018-05-24 17:03:24 -07:00
Alexander Matyushentsev
9a3425cfcd Issue #232 - Resource filtering on Application Details page 2018-05-24 16:00:39 -07:00
Alexander Matyushentsev
f5b0af521c Issue #235 - Allow viewing pod side car container logs 2018-05-24 12:47:34 -07:00
Alexander Matyushentsev
658126b7bc Issue #230 - Display operation state on application details page 2018-05-24 10:35:29 -07:00
Alexander Matyushentsev
196d168b65 Add confirmation message before starting application rollback 2018-05-23 11:53:32 -07:00
Alexander Matyushentsev
d71927a006 Extract notification manager implementation into separate class 2018-05-23 11:50:30 -07:00
Alexander Matyushentsev
8e8017531a Show confirmation message prior deleting application/pod 2018-05-23 11:39:40 -07:00
Alexander Matyushentsev
8be2660994 Render resource manifest on app details page 2018-05-23 10:05:04 -07:00
Alexander Matyushentsev
a34bae8905 Remove redux from app 2018-05-23 09:26:55 -07:00
Alexander Matyushentsev
01aaae9774 Remove redux usage from login page code 2018-05-23 09:08:35 -07:00
Alexander Matyushentsev
9e7a02e2b2 Remove redux usage from app list page code 2018-05-23 08:46:57 -07:00
Alexander Matyushentsev
470d4f1dec Remove redux usage from app details page code 2018-05-23 08:17:06 -07:00
Alexander Matyushentsev
65c2c6bb78 Remove redux-form usage 2018-05-22 13:47:06 -07:00
Alexander Matyushentsev
62b68a8892 Issue #184 - Allow downloading of argocd binaries directly from API server 2018-05-17 14:04:43 -07:00
Alexander Matyushentsev
c4c9ee4427 Rename recent deployments to history 2018-05-17 08:08:04 -07:00
Alexander Matyushentsev
44790ad1e1 Implement application status ui 2018-05-15 11:37:44 -07:00
Alexander Matyushentsev
9e3727a037 Issue #189 - switch to Spec.Destination.Server/Namespace fields 2018-05-14 10:53:09 -07:00
Alexander Matyushentsev
6721909257 Issue #118 - Provide return URL during sso authentication 2018-05-14 10:47:03 -07:00
Alexander Matyushentsev
26ffea9bed Issue #191 - ArgoCD UI s/rollback/history/ and s/deploy/sync/ 2018-05-14 10:15:02 -07:00
Alexander Matyushentsev
d8fb318253 Render health status icon on application details page 2018-05-14 10:11:46 -07:00
Alexander Matyushentsev
bb5dde23b8 SSO Login Button 2018-05-04 09:15:53 -07:00
Alexander Matyushentsev
94b2b0c208 Add env variable 2018-04-24 15:49:21 -07:00
Alexander Matyushentsev
e7a9f311c7 Update rollback UI 2018-04-24 13:37:52 -07:00
Alexander Matyushentsev
8156680b70 Allow specifying cluster and namespace during app creation 2018-04-20 15:51:30 -07:00
Alexander Matyushentsev
4d74e57bb6 Implement application login/logout 2018-04-19 15:48:11 -07:00
Alexander Matyushentsev
8ba3bf1e5f Merge branch 'master' of github.com:argoproj/argo-cd-ui 2018-04-18 14:29:01 -07:00
Alexander Matyushentsev
e16b3a25b3 Implement simple application creation/deletion form 2018-04-18 14:28:34 -07:00
Jesse Suen
972d5ff493 Add README.md 2018-04-17 16:49:02 -07:00
Alexander Matyushentsev
059f4e0748 Render additional details for pods and servies on application details page 2018-04-11 13:08:24 -07:00
Alexander Matyushentsev
9ae501c7ca Implement delete pod action 2018-04-09 10:46:12 -07:00
Alexander Matyushentsev
dffac4069d Implement logs rendering on application details page 2018-04-06 13:21:55 -07:00
Alexander Matyushentsev
5527b3a852 Improve application resources tree component: add resource kind labels, improve app icon 2018-04-06 09:44:41 -07:00
Alexander Matyushentsev
537e28a0ce Application details page should render resources tree 2018-04-06 09:14:03 -07:00
Alexander Matyushentsev
5382968864 Implement rollback UI draft 2018-03-29 09:41:59 -07:00
Alexander Matyushentsev
994474aead Render application deployment parameters 2018-03-26 15:34:55 -07:00
Alexander Matyushentsev
f38c1b3106 Implement ability to deploy any revision using web ui 2018-03-08 11:20:15 -08:00
Alexander Matyushentsev
ff3b5cc3c4 Add cluster URL and fix app namespace rendering on app list and app details pages 2018-03-01 15:13:11 -08:00
Alexander Matyushentsev
c9242b84f8 Change application details page title 2018-03-01 14:36:32 -08:00
Alexander Matyushentsev
94c8ff5e1b Implement deploy action on application details page 2018-03-01 11:20:27 -08:00
Alexander Matyushentsev
e2e5a7715c Fix status name typo 2018-03-01 08:37:13 -08:00
Alexander Matyushentsev
7fc6628934 Use resources field to render application resources status 2018-03-01 08:32:57 -08:00
Alexander Matyushentsev
f834803946 Add dockerfile and production build script 2018-02-28 20:47:26 -08:00
Alexander Matyushentsev
172aa7e47c Close stream connection when user navigate away from applications list page 2018-02-28 11:27:58 -08:00
Alexander Matyushentsev
6ea5b671e7 Implement applications list and application details page live update 2018-02-28 10:38:02 -08:00
Alexander Matyushentsev
80f373bc59 Implement components rendering on application details page 2018-02-28 09:10:12 -08:00
Alexander Matyushentsev
7de1908f48 Add lint command 2018-02-27 21:45:36 -08:00
Alexander Matyushentsev
d8ff73b702 Implement application details page 2018-02-27 14:14:59 -08:00
Alexander Matyushentsev
a0880c58a9 Implement applications list page 2018-02-27 13:32:13 -08:00
Alexander Matyushentsev
eab17ce9fb Initial commit 2018-02-26 19:23:14 -08:00
751 changed files with 77483 additions and 283819 deletions

View File

@@ -1,97 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: argo-cd-ci-
spec:
entrypoint: argo-cd-ci
arguments:
parameters:
- name: revision
value: master
- name: repo
value: https://github.com/argoproj/argo-cd.git
templates:
- name: argo-cd-ci
steps:
- - name: build
template: ci-dind
arguments:
parameters:
- name: cmd
value: make image
- name: test
template: ci-builder
arguments:
parameters:
- name: cmd
value: "dep ensure && make lint test && bash <(curl -s https://codecov.io/bash) -f coverage.out"
- name: test-e2e
template: ci-builder
arguments:
parameters:
- name: cmd
value: "dep ensure && make test-e2e"
- name: ci-builder
inputs:
parameters:
- name: cmd
artifacts:
- name: code
path: /go/src/github.com/argoproj/argo-cd
git:
repo: "{{workflow.parameters.repo}}"
revision: "{{workflow.parameters.revision}}"
container:
image: argoproj/argo-cd-ci-builder:v0.12.0
imagePullPolicy: Always
command: [bash, -c]
args: ["{{inputs.parameters.cmd}}"]
workingDir: /go/src/github.com/argoproj/argo-cd
env:
- name: CODECOV_TOKEN
valueFrom:
secretKeyRef:
name: codecov-token
key: codecov-token
resources:
requests:
memory: 1024Mi
cpu: 200m
archiveLocation:
archiveLogs: true
- name: ci-dind
inputs:
parameters:
- name: cmd
artifacts:
- name: code
path: /go/src/github.com/argoproj/argo-cd
git:
repo: "{{workflow.parameters.repo}}"
revision: "{{workflow.parameters.revision}}"
container:
image: argoproj/argo-cd-ci-builder:v0.12.0
imagePullPolicy: Always
command: [sh, -c]
args: ["until docker ps; do sleep 3; done && {{inputs.parameters.cmd}}"]
workingDir: /go/src/github.com/argoproj/argo-cd
env:
- name: DOCKER_HOST
value: 127.0.0.1
- name: DOCKER_BUILDKIT
value: "1"
resources:
requests:
memory: 1024Mi
cpu: 200m
sidecars:
- name: dind
image: docker:18.09-dind
securityContext:
privileged: true
mirrorVolumeMounts: true
archiveLocation:
archiveLogs: true

327
.circleci/config.yml Normal file
View File

@@ -0,0 +1,327 @@
version: 2.1
commands:
before:
steps:
- restore_go_cache
- install_golang
- install_tools
- clean_checkout
- configure_git
- install_go_deps
- dep_ensure
configure_git:
steps:
- run:
name: Configure Git
command: |
set -x
# must be configured for tests to run
git config --global user.email you@example.com
git config --global user.name "Your Name"
echo "export PATH=/home/circleci/.go_workspace/src/github.com/argoproj/argo-cd/hack:\$PATH" | tee -a $BASH_ENV
echo "export GIT_ASKPASS=git-ask-pass.sh" | tee -a $BASH_ENV
clean_checkout:
steps:
- run:
name: Remove checked out code
command: rm -Rf /home/circleci/.go_workspace/src/github.com/argoproj/argo-cd
- checkout
dep_ensure:
steps:
- restore_cache:
keys:
- vendor-v4-{{ checksum "Gopkg.lock" }}
- run:
name: Run dep ensure
command: dep ensure -v
- save_cache:
key: vendor-v4-{{ checksum "Gopkg.lock" }}
paths:
- vendor
install_golang:
steps:
- run:
name: Install Golang v1.12.6
command: |
go get golang.org/dl/go1.12.6
[ -e /home/circleci/sdk/go1.12.6 ] || go1.12.6 download
echo "export GOPATH=/home/circleci/.go_workspace" | tee -a $BASH_ENV
echo "export PATH=/home/circleci/sdk/go1.12.6/bin:\$PATH" | tee -a $BASH_ENV
- run:
name: Golang diagnostics
command: |
env
which go
go version
go env
install_go_deps:
steps:
- run:
name: Install Go deps
command: |
set -x
go get github.com/jstemmer/go-junit-report
go get github.com/mattn/goreman
install_tools:
steps:
- run:
name: Create downloads dir
command: mkdir -p /tmp/dl
- restore_cache:
keys:
- dl-v7
- run:
name: Install Kubectl v1.14.0
command: |
set -x
[ -e /tmp/dl/kubectl ] || curl -sLf -C - -o /tmp/dl/kubectl https://storage.googleapis.com/kubernetes-release/release/v1.14.0/bin/linux/amd64/kubectl
sudo cp /tmp/dl/kubectl /usr/local/bin/kubectl
sudo chmod +x /usr/local/bin/kubectl
- run:
name: Install Kubectx v0.6.3
command: |
set -x
[ -e /tmp/dl/kubectx.zip ] || curl -sLf -C - -o /tmp/dl/kubectx.zip https://github.com/ahmetb/kubectx/archive/v0.6.3.zip
sudo unzip /tmp/dl/kubectx.zip kubectx-0.6.3/kubectx
sudo unzip /tmp/dl/kubectx.zip kubectx-0.6.3/kubens
sudo mv kubectx-0.6.3/kubectx /usr/local/bin/
sudo mv kubectx-0.6.3/kubens /usr/local/bin/
sudo chmod +x /usr/local/bin/kubectx
sudo chmod +x /usr/local/bin/kubens
- run:
name: Install Dep v0.5.3
command: |
set -x
[ -e /tmp/dl/dep ] || curl -sLf -C - -o /tmp/dl/dep https://github.com/golang/dep/releases/download/v0.5.3/dep-linux-amd64
sudo cp /tmp/dl/dep /usr/local/go/bin/dep
sudo chmod +x /usr/local/go/bin/dep
dep version
- run:
name: Install Ksonnet v0.13.1
command: |
set -x
[ -e /tmp/dl/ks.tar.gz ] || curl -sLf -C - -o /tmp/dl/ks.tar.gz https://github.com/ksonnet/ksonnet/releases/download/v0.13.1/ks_0.13.1_linux_amd64.tar.gz
tar -C /tmp -xf /tmp/dl/ks.tar.gz
sudo cp /tmp/ks_0.13.1_linux_amd64/ks /usr/local/go/bin/ks
sudo chmod +x /usr/local/go/bin/ks
ks version
- run:
name: Install Helm v2.13.1
command: |
set -x
[ -e /tmp/dl/helm.tar.gz ] || curl -sLf -C - -o /tmp/dl/helm.tar.gz https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz
tar -C /tmp/ -xf /tmp/dl/helm.tar.gz
sudo cp /tmp/linux-amd64/helm /usr/local/go/bin/helm
helm version --client
helm init --client-only
- run:
name: Install Kustomize v3.1.0
command: |
set -x
export VER=3.1.0
[ -e /tmp/dl/kustomize_${VER} ] || curl -sLf -C - -o /tmp/dl/kustomize_${VER} https://github.com/kubernetes-sigs/kustomize/releases/download/v${VER}/kustomize_${VER}_linux_amd64
sudo cp /tmp/dl/kustomize_${VER} /usr/local/go/bin/kustomize
sudo chmod +x /usr/local/go/bin/kustomize
kustomize version
- save_cache:
key: dl-v7
paths:
- /tmp/dl
save_go_cache:
steps:
- save_cache:
key: go-v17-{{ .Branch }}
paths:
- /home/circleci/.go_workspace
- /home/circleci/.cache/go-build
- /home/circleci/sdk/go1.12.6
restore_go_cache:
steps:
- restore_cache:
keys:
- go-v17-{{ .Branch }}
- go-v17-master
- go-v16-{{ .Branch }}
- go-v16-master
jobs:
build:
working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo-cd
machine:
image: circleci/classic:201808-01
steps:
- before
- run:
name: Run unit tests
command: |
set -x
mkdir -p /tmp/test-results
trap "go-junit-report </tmp/test-results/go-test.out > /tmp/test-results/go-test-report.xml" EXIT
make test | tee /tmp/test-results/go-test.out
- save_go_cache
- run:
name: Uploading code coverage
command: bash <(curl -s https://codecov.io/bash) -f coverage.out
# This takes 2m, lets background it.
background: true
- store_test_results:
path: /tmp/test-results
- run:
name: Generate code
command: make codegen
- run:
name: Lint code
# use GOGC to limit memory usage in exchange for CPU usage, https://github.com/golangci/golangci-lint#memory-usage-of-golangci-lint
# we have 8GB RAM, 2CPUs https://circleci.com/docs/2.0/executor-types/#using-machine
command: LINT_GOGC=20 LINT_CONCURRENCY=1 LINT_DEADLINE=3m0s make lint
- run:
name: Check nothing has changed
command: |
set -xo pipefail
# This makes sure you ran `make pre-commit` before you pushed.
# We exclude the Swagger resources; CircleCI doesn't generate them correctly.
# When this fails, it will, create a patch file you can apply locally to fix it.
# To troubleshoot builds: https://argoproj.github.io/argo-cd/developer-guide/ci/
git diff --exit-code -- . ':!Gopkg.lock' ':!assets/swagger.json' | tee codegen.patch
- store_artifacts:
path: codegen.patch
when: always
e2e:
working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo-cd
machine:
image: circleci/classic:201808-01
steps:
- run:
name: Install and start K3S v0.5.0
command: |
curl -sfL https://get.k3s.io | sh -
sudo chmod -R a+rw /etc/rancher/k3s
kubectl version
background: true
environment:
INSTALL_K3S_EXEC: --docker
INSTALL_K3S_VERSION: v0.5.0
- before
- run:
# do this before we build everything else in the background, as they tend to explode
name: Make CLI
command: |
set -x
make cli
# must be added to path for tests
echo export PATH="`pwd`/dist:\$PATH" | tee -a $BASH_ENV
- run:
name: Create namespace
command: |
set -x
cat /etc/rancher/k3s/k3s.yaml | sed "s/localhost/`hostname`/" | tee ~/.kube/config
echo "127.0.0.1 `hostname`" | sudo tee -a /etc/hosts
kubectl create ns argocd-e2e
kubens argocd-e2e
# install the certificates (not 100% sure we need this)
sudo cp /var/lib/rancher/k3s/server/tls/token-ca.crt /usr/local/share/ca-certificates/k3s.crt
sudo update-ca-certificates
- run:
name: Apply manifests
command: kustomize build test/manifests/base | kubectl apply -f -
- run:
name: Start Redis
command: docker run --rm --name argocd-redis -i -p 6379:6379 redis:5.0.3-alpine --save "" --appendonly no
background: true
- run:
name: Start repo server
command: go run ./cmd/argocd-repo-server/main.go --loglevel debug --redis localhost:6379
background: true
environment:
# pft. if you do not quote "true", CircleCI turns it into "1", stoopid
ARGOCD_FAKE_IN_CLUSTER: "true"
ARGOCD_SSH_DATA_PATH: "/tmp/argo-e2e/app/config/ssh"
ARGOCD_TLS_DATA_PATH: "/tmp/argo-e2e/app/config/tls"
- run:
name: Start API server
command: go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:6379 --insecure --dex-server http://localhost:5556 --repo-server localhost:8081 --staticassets ../argo-cd-ui/dist/app
background: true
environment:
ARGOCD_FAKE_IN_CLUSTER: "true"
ARGOCD_SSH_DATA_PATH: "/tmp/argo-e2e/app/config/ssh"
ARGOCD_TLS_DATA_PATH: "/tmp/argo-e2e/app/config/tls"
- run:
name: Start Test Git
command: |
test/fixture/testrepos/start-git.sh
background: true
- run:
name: Wait for API server
command: |
set -x
until curl -v http://localhost:8080/healthz; do sleep 3; done
- run:
name: Start controller
command: go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:6379 --repo-server localhost:8081 --kubeconfig ~/.kube/config
background: true
environment:
ARGOCD_FAKE_IN_CLUSTER: "true"
- run:
name: Smoke test
command: |
set -x
argocd login localhost:8080 --plaintext --username admin --password password
argocd app create guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook
argocd app sync guestbook
argocd app delete guestbook
- run:
name: Run e2e tests
command: |
set -x
mkdir -p /tmp/test-results
trap "go-junit-report </tmp/test-results/go-e2e.out > /tmp/test-results/go-e2e-report.xml" EXIT
make test-e2e | tee /tmp/test-results/go-e2e.out
environment:
ARGOCD_OPTS: "--server localhost:8080 --plaintext"
ARGOCD_E2E_EXPECT_TIMEOUT: "30"
ARGOCD_E2E_K3S: "true"
- store_test_results:
path: /tmp/test-results
ui:
# note that we checkout the code in ~/argo-cd/, but then work in ~/argo-cd/ui
working_directory: ~/argo-cd/ui
docker:
- image: node:11.15.0
steps:
- checkout:
path: ~/argo-cd/
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v3-{{ checksum "yarn.lock" }}
- run:
name: Install
command:
yarn install --frozen-lockfile --ignore-optional --non-interactive
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-v3-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- node_modules
- run:
name: Test
command: yarn test
# This does not appear to work, and I don't want to spend time on it.
- store_test_results:
path: junit.xml
- run:
name: Build
command: yarn build
- run:
name: Lint
command: yarn lint
workflows:
version: 2
workflow:
jobs:
- build
- e2e
- ui:
# this isn't strictly true, we just put in here so that we 2/4 executors rather than 3/4
requires:
- build

View File

@@ -1,6 +1,17 @@
ignore:
- "**/*.pb.go"
- "**/*.pb.gw.go"
- "**/*_test.go"
- "pkg/apis/.*"
- "pkg/client/.*"
- "test/.*"
coverage:
status:
# allow test coverage to drop by 1%, assume that it's typically due to CI problems
patch:
default:
enabled: no
if_not_found: success
project:
default:
threshold: 1

View File

@@ -10,3 +10,4 @@ dist/
cmd/**/debug
debug.test
coverage.out
ui/node_modules/

43
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,43 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
If we cannot reproduce, we cannot fix! Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version**
```shell
Paste the output from `argocd version` here.
```
**Logs**
```
Paste any relevant application logs here.
```
**Have you thought about contributing a fix yourself?**
Open Source software thrives with your contribution. It not only gives skills you might not be able to get in your day job, it also looks amazing on your resume.
If you want to get involved, check out the
[contributing guide](https://github.com/argoproj/argo-cd/blob/master/docs/CONTRIBUTING.md), then reach out to us on [Slack](https://argoproj.github.io/community/join-slack) so we can see how to get you started.

View File

@@ -0,0 +1,21 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Have you thought about contributing yourself?**
Open Source software thrives with your contribution. It not only gives skills you might not be able to get in your day job, it also looks amazing on your resume.
If you want to get involved, check out the
[contributing guide](https://github.com/argoproj/argo-cd/blob/master/docs/CONTRIBUTING.md), then reach out to us on [Slack](https://argoproj.github.io/community/join-slack) so we can see how to get you started.

1
.github/no-response.yml vendored Normal file
View File

@@ -0,0 +1 @@
# See https://github.com/probot/no-response

7
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,7 @@
<!--
Thank you for submitting your PR!
We'd love your organisation to be listed in the [README](https://github.com/argoproj/argo-cd). Don't forget to add it if you can!
To troubleshoot builds: https://argoproj.github.io/argo-cd/developer-guide/ci/
-->

4
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
# See https://github.com/probot/stale
# See https://github.com/probot/stale
exemptLabels:
- backlog

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@
.DS_Store
vendor/
dist/
site/
*.iml
# delve debug binaries
cmd/**/debug

22
.golangci.yml Normal file
View File

@@ -0,0 +1,22 @@
run:
deadline: 2m
skip-files:
- ".*\\.pb\\.go"
skip-dirs:
- pkg/client
- vendor
linter-settings:
goimports:
local-prefixes: github.com/argoproj/argo-cd
linters:
enable:
- vet
- gofmt
- goimports
- deadcode
- varcheck
- structcheck
- ineffassign
- unconvert
- misspell
- unparam

View File

@@ -1,5 +1,408 @@
# Changelog
## v1.1.2 (2019-07-30)
- 'argocd app wait' should print correct sync status (#2049)
- Check that TLS is enabled when registering DEX Handlers (#2047)
- Do not ignore Argo hooks when there is a Helm hook. (#1952)
## v1.1.1 (2019-07-25)
+ Support 'override' action in UI/API (#1984)
- Fix argocd app wait message (#1982)
## v1.1.0 (2019-07-24)
### New Features
#### Sync Waves
Sync waves feature allows executing a sync operation in a number of steps or waves. Within each synchronization phase (pre-sync, sync, post-sync) you can have one or more waves,
than allows you to ensure certain resources are healthy before subsequent resources are synced.
#### Optimized Interaction With Git
Argo CD needs to execute `git fetch` operation to access application manifests and `git ls-remote` to resolve ambiguous git revision. The `git ls-remote` is executed very frequently
and although the operation is very lightweight it adds unnecessary load on Git server and might cause performance issues. In v1.1 release, the application reconciliation process was
optimized which significantly reduced the number of Git requests. With v1.1 release, Argo CD should send 3x ~ 5x fewer Git requests.
#### User Defined Application Metadata
User-defined Application metadata enables the user to define a list of useful URLs for their specific application and expose those links on the UI
(e.g. reference tp a CI pipeline or an application-specific management tool). These links should provide helpful shortcuts that make easier to integrate Argo CD into existing
systems by making it easier to find other components inside and outside Argo CD.
### Deprecation Notice
* Kustomize v1.0 is deprecated and support will be removed in the Argo CD v1.2 release.
#### Enhancements
- Sync waves [#1544](https://github.com/argoproj/argo-cd/issues/1544)
- Adds Prune=false and IgnoreExtraneous options [#1629](https://github.com/argoproj/argo-cd/issues/1629)
- Forward Git credentials to config management plugins [#1628](https://github.com/argoproj/argo-cd/issues/1628)
- Improve Kustomize 2 parameters UI [#1609](https://github.com/argoproj/argo-cd/issues/1609)
- Adds `argocd logout` [#1210](https://github.com/argoproj/argo-cd/issues/1210)
- Make it possible to set Helm release name different from Argo CD app name. [#1066](https://github.com/argoproj/argo-cd/issues/1066)
- Add ability to specify system namespace during cluster add operation [#1661](https://github.com/argoproj/argo-cd/pull/1661)
- Make listener and metrics ports configurable [#1647](https://github.com/argoproj/argo-cd/pull/1647)
- Using SSH keys to authenticate kustomize bases from git [#827](https://github.com/argoproj/argo-cd/issues/827)
- Adds `argocd app sync APPNAME --async` [#1728](https://github.com/argoproj/argo-cd/issues/1728)
- Allow users to define app specific urls to expose in the UI [#1677](https://github.com/argoproj/argo-cd/issues/1677)
- Error view instead of blank page in UI [#1375](https://github.com/argoproj/argo-cd/issues/1375)
- Project Editor: Whitelisted Cluster Resources doesn't strip whitespace [#1693](https://github.com/argoproj/argo-cd/issues/1693)
- Eliminate unnecessary git interactions for top-level resource changes (#1919)
- Ability to rotate the bearer token used to manage external clusters (#1084)
#### Bug Fixes
- Project Editor: Whitelisted Cluster Resources doesn't strip whitespace [#1693](https://github.com/argoproj/argo-cd/issues/1693)
- \[ui small bug\] menu position outside block [#1711](https://github.com/argoproj/argo-cd/issues/1711)
- UI will crash when create application without destination namespace [#1701](https://github.com/argoproj/argo-cd/issues/1701)
- ArgoCD synchronization failed due to internal error [#1697](https://github.com/argoproj/argo-cd/issues/1697)
- Replicasets ordering is not stable on app tree view [#1668](https://github.com/argoproj/argo-cd/issues/1668)
- Stuck processor on App Controller after deleting application with incomplete operation [#1665](https://github.com/argoproj/argo-cd/issues/1665)
- Role edit page fails with JS error [#1662](https://github.com/argoproj/argo-cd/issues/1662)
- failed parsing on parameters with comma [#1660](https://github.com/argoproj/argo-cd/issues/1660)
- Handle nil obj when processing custom actions [#1700](https://github.com/argoproj/argo-cd/pull/1700)
- Account for missing fields in Rollout HealthStatus [#1699](https://github.com/argoproj/argo-cd/pull/1699)
- Sync operation unnecessary waits for a healthy state of all resources [#1715](https://github.com/argoproj/argo-cd/issues/1715)
- failed parsing on parameters with comma [#1660](https://github.com/argoproj/argo-cd/issues/1660)
- argocd app sync hangs when cluster is not configured (#1935)
- Do not allow app-of-app child app's Missing status to affect parent (#1954)
- Argo CD don't handle well k8s objects which size exceeds 1mb (#1685)
- Secret data not redacted in last-applied-configuration (#897)
- Running app actions requires only read privileges (#1827)
- UI should allow editing repo URL (#1763)
- Make status fields as optional fields (#1779)
- Use correct healthcheck for Rollout with empty steps list (#1776)
#### Other
- Add Prometheus metrics for git repo interactions (#1912)
- App controller should log additional information during app syncing (#1909)
- Make sure api server to repo server grpc calls have timeout (#1820)
- Forked tool processes should timeout (#1821)
- Add health check to the controller deployment (#1785)
#### Contributors
* [Aditya Gupta](https://github.com/AdityaGupta1)
* [Alex Collins](https://github.com/alexec)
* [Alex Matyushentsev](https://github.com/alexmt)
* [Danny Thomson](https://github.com/dthomson25)
* [jannfis](https://github.com/jannfis)
* [Jesse Suen](https://github.com/jessesuen)
* [Liviu Costea](https://github.com/lcostea)
* [narg95](https://github.com/narg95)
* [Simon Behar](https://github.com/simster7)
See also [milestone v1.1](https://github.com/argoproj/argo-cd/milestone/13)
## v1.0.0 (2019-05-16)
### New Features
#### Network View
A new way to visual application resources had been introduced to the Application Details page. The Network View visualizes connections between Ingresses, Services and Pods
based on ingress reference service, service's label selectors and labels. The new view is useful to understand the application traffic flow and troubleshot connectivity issues.
#### Custom Actions
Argo CD introduces Custom Resource Actions to allow users to provide their own Lua scripts to modify existing Kubernetes resources in their applications. These actions are exposed in the UI to allow easy, safe, and reliable changes to their resources. This functionality can be used to introduce functionality such as suspending and enabling a Kubernetes cronjob, continue a BlueGreen deployment with Argo Rollouts, or scaling a deployment.
#### UI Enhancements & Usability Enhancements
* New color palette intended to highlight unhealthily and out-of-sync resources more clearly.
* The health of more resources is displayed, so it easier to quickly zoom to unhealthy pods, replica-sets, etc.
* Resources that do not have health no longer appear to be healthy.
* Support for configuring Git repo credentials at a domain/org level
* Support for configuring requested OIDC provider scopes and enforced RBAC scopes
* Support for configuring monitored resources whitelist in addition to excluded resources
### Breaking Changes
* Remove deprecated componentParameterOverrides field #1372
### Changes since v0.12.2
#### Enhancements
* `argocd app wait` should have `--resource` flag like sync #1206
* Adds support for `kustomize edit set image`. Closes #1275 (#1324)
* Allow wait to return on health or suspended (#1392)
* Application warning when a manifest is defined twice #1070
* Create new documentation website #1390
* Default view should resource view instead of diff view #1354
* Display number of errors on resource tab #1477
* Displays resources that are being deleted as "Progressing". Closes #1410 (#1426)
* Generate random name for grpc proxy unix socket file instead of time stamp (#1455)
* Issue #357 - Expose application nodes networking information (#1333)
* Issue #1404 - App controller unnecessary set namespace to cluster level resources (#1405)
* Nils health if the resource does not provide it. Closes #1383 (#1408)
* Perform health assessments on all resource nodes in the tree. Closes #1382 (#1422)
* Remove deprecated componentParameterOverrides field #1372
* Shows the health of the application. Closes #1433 (#1434)
* Surface Service/Ingress external IPs, hostname to application #908
* Surface pod status to tree view #1358
* Support for customizable resource actions as Lua scripts #86
* UI / API Errors Truncated, Time Out #1386
* UI Enhancement Proposals Quick Wins #1274
* Update argocd-util import/export to support proper backup and restore (#1328)
* Whitelisting repos/clusters in projects should consider repo/cluster permissions #1432
* Adds support for configuring repo creds at a domain/org level. (#1332)
* Implement whitelist option analogous to `resource.exclusions` (#1490)
* Added ability to sync specific labels from the command line (#1241)
* Improve rendering app image information (#1552)
* Add liveness probe to repo server/api servers (#1546)
* Support configuring requested OIDC provider scopes and enforced RBAC scopes (#1471)
#### Bug Fixes
- Don't compare secrets in the CLI, since argo-cd doesn't have access to their data (#1459)
- Dropdown menu should not have sync item for unmanaged resources #1357
- Fixes goroutine leak. Closes #1381 (#1457)
- Improve input style #1217
- Issue #908 - Surface Service/Ingress external IPs, hostname to application (#1347)
- kustomization fields are all mandatory #1504
- Resource node details is crashing if live resource is missing $1505
- Rollback UI is not showing correct ksonnet parameters in preview #1326
- See details of applications fails with "r.nodes is undefined" #1371
- UI fails to load custom actions is resource is not deployed #1502
- Unable to create app from private repo: x509: certificate signed by unknown authority (#1171)
- Fix hardcoded 'git' user in `util/git.NewClient` (#1555)
- Application controller becomes unresponsive (#1476)
- Load target resource using K8S if conversion fails (#1414)
- Can't ignore a non-existent pointer anymore (#1586)
- Impossible to sync to HEAD from UI if auto-sync is enabled (#1579)
- Application controller is unable to delete self-referenced app (#1570)
- Prevent reconciliation loop for self-managed apps (#1533)
- Controller incorrectly report health state of self managed application (#1557)
- Fix kustomize manifest generation crash is manifest has image without version (#1540)
- Supply resourceVersion to watch request to prevent reading of stale cache (#1605)
## v0.12.2 (2019-04-22)
### Changes since v0.12.1
- Fix racing condition in controller cache (#1498)
- "bind: address already in use" after switching to gRPC-Web (#1451)
- Annoying warning while using --grpc-web flag (#1420)
- Delete helm temp directories (#1446)
- Fix null pointer exception in secret normalization function (#1389)
- Argo CD should not delete CRDs(#1425)
- UI is unable to load cluster level resource manifest (#1429)
## v0.12.1 (2019-04-09)
### Changes since v0.12.0
- [UI] applications view blows up when user does not have permissions (#1368)
- Add k8s objects circular dependency protection to getApp method (#1374)
- App controller unnecessary set namespace to cluster level resources (#1404)
- Changing SSO login URL to be a relative link so it's affected by basehref (#101) (@arnarg)
- CLI diff should take into account resource customizations (#1294)
- Don't try deleting application resource if it already has `deletionTimestamp` (#1406)
- Fix invalid group filtering in 'patch-resource' command (#1319)
- Fix null pointer dereference error in 'argocd app wait' (#1366)
- kubectl v1.13 fails to convert extensions/NetworkPolicy (#1012)
- Patch APIs are not audited (#1397)
+ 'argocd app wait' should fail sooner if app transitioned to Degraded state (#733)
+ Add mapping to new canonical Ingress API group - kubernetes 1.14 support (#1348) (@twz123)
+ Adds support for `kustomize edit set image`. (#1275)
+ Allow using any name for secrets which store cluster credentials (#1218)
+ Update argocd-util import/export to support proper backup and restore (#1048)
## v0.12.0 (2019-03-20)
### New Features
#### Improved UI
Many improvements to the UI were made, including:
* Table view when viewing applications
* Filters on applications
* Table view when viewing application resources
* YAML editor in UI
* Switch to text-based diff instead of json diff
* Ability to edit application specs
#### Custom Health Assessments (CRD Health)
Argo CD has long been able to perform health assessments on resources, however this could only
assess the health for a few native kubernetes types (deployments, statefulsets, daemonsets, etc...).
Now, Argo CD can be extended to gain understanding of any CRD health, in the form of Lua scripts.
For example, using this feature, Argo CD now understands the CertManager Certificate CRD and will
report a Degraded status when there are issues with the cert.
#### Configuration Management Plugins
Argo CD introduces Config Management Plugins to support custom configuration management tools other
than the set that Argo CD provides out-of-the-box (Helm, Kustomize, Ksonnet, Jsonnet). Using config
management plugins, Argo CD can be configured to run specified commands to render manifests. This
makes it possible for Argo CD to support other config management tools (kubecfg, kapitan, shell
scripts, etc...).
#### High Availability
Argo CD is now fully HA. A set HA of manifests are provided for users who wish to run Argo CD in
a highly available manner. NOTE: The HA installation will require at least three different nodes due
to pod anti-affinity roles in the specs.
#### Improved Application Source
* Support for Kustomize 2
* YAML/JSON/Jsonnet Directories can now be recursed
* Support for Jsonnet external variables and top-level arguments
#### Additional Prometheus Metrics
Argo CD provides the following additional prometheus metrics:
* Sync counter to track sync activity and results over time
* Application reconciliation (refresh) performance to track Argo CD performance and controller activity
* Argo CD API Server metrics for monitoring HTTP/gRPC requests
#### Fuzzy Diff Logic
Argo CD can now be configured to ignore known differences for resource types by specifying a json
pointer to the field path to ignore. This helps prevent OutOfSync conditions when a user has no
control over the manifests. Ignored differences can be configured either at an application level,
or a system level, based on a group/kind.
#### Resource Exclusions
Argo CD can now be configured to completely ignore entire classes of resources group/kinds.
Excluding high-volume resources improves performance and memory usage, and reduces load and
bandwidth to the Kubernetes API server. It also allows users to fine-tune the permissions that
Argo CD needs to a cluster by preventing Argo CD from attempting to watch resources of that
group/kind.
#### gRPC-Web Support
The argocd CLI can be now configured to communicate to the Argo CD API server using gRPC-Web
(HTTP1.1) using a new CLI flag `--grpc-web`. This resolves some compatibility issues users were
experiencing with ingresses and gRPC (HTTP2), and should enable argocd CLI to work with virtually
any load balancer, ingress controller, or API gateway.
#### CLI features
Argo CD introduces some additional CLI commands:
* `argocd app edit APPNAME` - to edit an application spec using preferred EDITOR
* `argocd proj edit PROJNAME` - to edit an project spec using preferred EDITOR
* `argocd app patch APPNAME` - to patch an application spec
* `argocd app patch-resource APPNAME` - to patch a specific resource which is part of an application
### Breaking Changes
#### Label selector changes, dex-server rename
The label selectors for deployments were been renamed to use kubernetes common labels
(`app.kuberentes.io/name=NAME` instead of `app=NAME`). Since K8s deployment label selectors are
immutable, during an upgrade from v0.11 to v0.12, the old deployments should be deleted using
`--cascade=false` which allows the new deployments to be created without introducing downtime.
Once the new deployments are ready, the older replicasets can be deleted. Use the following
instructions to upgrade from v0.11 to v0.12 without introducing downtime:
```
# delete the deployments with cascade=false. this orphan the replicasets, but leaves the pods running
kubectl delete deploy --cascade=false argocd-server argocd-repo-server argocd-application-controller
# apply the new manifests and wait for them to finish rolling out
kubectl apply <new install manifests>
kubectl rollout status deploy/argocd-application-controller
kubectl rollout status deploy/argocd-repo-server
kubectl rollout status deploy/argocd-application-controller
# delete old replicasets which are using the legacy label
kubectl delete rs -l app=argocd-server
kubectl delete rs -l app=argocd-repo-server
kubectl delete rs -l app=argocd-application-controller
# delete the legacy dex-server which was renamed
kubectl delete deploy dex-server
```
#### Deprecation of spec.source.componentParameterOverrides
For declarative application specs, the `spec.source.componentParameterOverrides` field is now
deprecated in favor of application source specific config. They are replaced with new fields
specific to their respective config management. For example, a Helm application spec using the
legacy field:
```yaml
spec:
source:
componentParameterOverrides:
- name: image.tag
value: v1.2
```
should move to:
```yaml
spec:
source:
helm:
parameters:
- name: image.tag
value: v1.2
```
Argo CD will automatically duplicate the legacy field values to the new locations (and vice versa)
as part of automatic migration. The legacy `spec.source.componentParameterOverrides` field will be
kept around for the v0.12 release (for migration purposes) and will be removed in the next Argo CD
release.
#### Removal of spec.source.environment and spec.source.valuesFiles
The `spec.source.environment` and `spec.source.valuesFiles` fields, which were deprecated in v0.11,
are now completely removed from the Application spec.
#### API/CLI compatibility
Due to API spec changes related to the deprecation of componentParameterOverrides, Argo CD v0.12
has a minimum client version of v0.12.0. Older CLI clients will be rejected.
### Changes since v0.11:
+ Improved UI
+ Custom Health Assessments (CRD Health)
+ Configuration Management Plugins
+ High Availability
+ Fuzzy Diff Logic
+ Resource Exclusions
+ gRPC-Web Support
+ CLI features
+ Additional prometheus metrics
+ Sample Grafana dashboard (#1277) (@hartman17)
+ Support for Kustomize 2
+ YAML/JSON/Jsonnet Directories can now be recursed
+ Support for Jsonnet external variables and top-level arguments
+ Optimized reconciliation performance for applications with very active resources (#1267)
+ Support a separate OAuth2 CLI clientID different from server (#1307)
+ argocd diff: only print to stdout, if there is a diff + exit code (#1288) (@marcb1)
+ Detection and handling of duplicated resource definitions (#1284)
+ Support kustomize apps with remote bases in private repos in the same host (#1264)
+ Support patching resource using REST API (#1186)
* Deprecate componentParameterOverrides in favor of source specific config (#1207)
* Support talking to Dex using local cluster address instead of public address (#1211)
* Use Recreate deployment strategy for controller (#1315)
* Honor os environment variables for helm commands (#1306) (@1337andre)
* Disable CGO_ENABLED for server/controller binaries (#1286)
* Documentation fixes and improvements (@twz123, @yann-soubeyrand, @OmerKahani, @dulltz)
- Fix CRD creation/deletion handling (#1249)
- Git cloning via SSH was not verifying host public key (#1276)
- Fixed multiple goroutine leaks in controller and api-server
- Fix isssue where `argocd app set -p` required repo privileges. (#1280)
- Fix local diff of non-namespaced resources. Also handle duplicates in local diff (#1289)
- Deprecated resource kinds from 'extensions' groups are not reconciled correctly (#1232)
- Fix issue where CLI would panic after timeout when cli did not have get permissions (#1209)
- invalidate repo cache on delete (#1182) (@narg95)
## v0.11.2 (2019-02-19)
+ Adds client retry. Fixes #959 (#1119)
- Prevent deletion hotloop (#1115)

View File

@@ -1,12 +1,19 @@
ARG BASE_IMAGE=debian:9.5-slim
####################################################################################################
# Builder image
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
# Also used as the image in CI jobs so needs all dependencies
####################################################################################################
FROM golang:1.11.4 as builder
FROM golang:1.12.6 as builder
RUN echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y \
openssh-server \
nginx \
fcgiwrap \
git \
git-lfs \
make \
wget \
gcc \
@@ -16,24 +23,11 @@ RUN apt-get update && apt-get install -y \
WORKDIR /tmp
# Install docker
ENV DOCKER_CHANNEL stable
ENV DOCKER_VERSION 18.09.1
RUN wget -O docker.tgz "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/x86_64/docker-${DOCKER_VERSION}.tgz" && \
tar --extract --file docker.tgz --strip-components 1 --directory /usr/local/bin/ && \
rm docker.tgz
# Install dep
ENV DEP_VERSION=0.5.0
RUN wget https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 -O /usr/local/bin/dep && \
chmod +x /usr/local/bin/dep
# Install gometalinter
ENV GOMETALINTER_VERSION=2.0.12
RUN curl -sLo- https://github.com/alecthomas/gometalinter/releases/download/v${GOMETALINTER_VERSION}/gometalinter-${GOMETALINTER_VERSION}-linux-amd64.tar.gz | \
tar -xzC "$GOPATH/bin" --exclude COPYING --exclude README.md --strip-components 1 -f- && \
ln -s $GOPATH/bin/gometalinter $GOPATH/bin/gometalinter.v2
# Install packr
ENV PACKR_VERSION=1.21.9
RUN wget https://github.com/gobuffalo/packr/releases/download/v${PACKR_VERSION}/packr_${PACKR_VERSION}_linux_amd64.tar.gz && \
@@ -42,8 +36,7 @@ RUN wget https://github.com/gobuffalo/packr/releases/download/v${PACKR_VERSION}/
# Install kubectl
# NOTE: keep the version synced with https://storage.googleapis.com/kubernetes-release/release/stable.txt
# Keep version at 1.12.X until https://github.com/argoproj/argo-cd/issues/1012 is resolved
ENV KUBECTL_VERSION=1.12.4
ENV KUBECTL_VERSION=1.14.0
RUN curl -L -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
chmod +x /usr/local/bin/kubectl && \
kubectl version --client
@@ -62,14 +55,7 @@ RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-li
mv /tmp/linux-amd64/helm /usr/local/bin/helm && \
helm version --client
# Install kustomize
ENV KUSTOMIZE1_VERSION=1.0.11
RUN curl -L -o /usr/local/bin/kustomize1 https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE1_VERSION}/kustomize_${KUSTOMIZE1_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/kustomize1 && \
kustomize1 version
ENV KUSTOMIZE_VERSION=2.0.2
ENV KUSTOMIZE_VERSION=3.1.0
RUN curl -L -o /usr/local/bin/kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/kustomize && \
kustomize version
@@ -79,27 +65,42 @@ ENV AWS_IAM_AUTHENTICATOR_VERSION=0.4.0-alpha.1
RUN curl -L -o /usr/local/bin/aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/${AWS_IAM_AUTHENTICATOR_VERSION}/aws-iam-authenticator_${AWS_IAM_AUTHENTICATOR_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/aws-iam-authenticator
####################################################################################################
# Argo CD Base - used as the base for both the release and dev argocd images
####################################################################################################
FROM debian:9.5-slim as argocd-base
FROM $BASE_IMAGE as argocd-base
USER root
RUN echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list
RUN groupadd -g 999 argocd && \
useradd -r -u 999 -g argocd argocd && \
mkdir -p /home/argocd && \
chown argocd:argocd /home/argocd && \
chown argocd:0 /home/argocd && \
chmod g=u /home/argocd && \
chmod g=u /etc/passwd && \
apt-get update && \
apt-get install -y git && \
apt-get install -y git git-lfs && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY hack/git-ask-pass.sh /usr/local/bin/git-ask-pass.sh
COPY --from=builder /usr/local/bin/ks /usr/local/bin/ks
COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/kubectl
COPY --from=builder /usr/local/bin/kustomize1 /usr/local/bin/kustomize1
COPY --from=builder /usr/local/bin/kustomize /usr/local/bin/kustomize
COPY --from=builder /usr/local/bin/aws-iam-authenticator /usr/local/bin/aws-iam-authenticator
# script to add current (possibly arbitrary) user to /etc/passwd at runtime
# (if it's not already there, to be openshift friendly)
COPY uid_entrypoint.sh /usr/local/bin/uid_entrypoint.sh
# support for mounting configuration from a configmap
RUN mkdir -p /app/config/ssh && \
touch /app/config/ssh/ssh_known_hosts && \
ln -s /app/config/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts
RUN mkdir -p /app/config/tls
# workaround ksonnet issue https://github.com/ksonnet/ksonnet/issues/298
ENV USER=argocd
@@ -107,11 +108,26 @@ ENV USER=argocd
USER argocd
WORKDIR /home/argocd
####################################################################################################
# Argo CD UI stage
####################################################################################################
FROM node:11.15.0 as argocd-ui
WORKDIR /src
ADD ["ui/package.json", "ui/yarn.lock", "./"]
RUN yarn install
ADD ["ui/", "."]
ARG ARGO_VERSION=latest
ENV ARGO_VERSION=$ARGO_VERSION
RUN NODE_ENV='production' yarn build
####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries
####################################################################################################
FROM golang:1.11.4 as argocd-build
FROM golang:1.12.6 as argocd-build
COPY --from=builder /usr/local/bin/dep /usr/local/bin/dep
COPY --from=builder /usr/local/bin/packr /usr/local/bin/packr
@@ -138,3 +154,5 @@ RUN make cli server controller repo-server argocd-util && \
####################################################################################################
FROM argocd-base
COPY --from=argocd-build /go/src/github.com/argoproj/argo-cd/dist/argocd* /usr/local/bin/
COPY --from=argocd-ui ./src/dist/app /shared/app

308
Gopkg.lock generated
View File

@@ -48,25 +48,6 @@
pruneopts = ""
revision = "09c41003ee1d5015b75f331e52215512e7145b8d"
[[projects]]
branch = "master"
digest = "1:a74730e052a45a3fab1d310fdef2ec17ae3d6af16228421e238320846f2aaec8"
name = "github.com/alecthomas/template"
packages = [
".",
"parse",
]
pruneopts = ""
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
[[projects]]
branch = "master"
digest = "1:8483994d21404c8a1d489f6be756e25bfccd3b45d65821f25695577791a08e68"
name = "github.com/alecthomas/units"
packages = ["."]
pruneopts = ""
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]]
branch = "master"
digest = "1:0caf9208419fa5db5a0ca7112affaa9550c54291dda8e2abac0c0e76181c959e"
@@ -81,14 +62,16 @@
[[projects]]
branch = "master"
digest = "1:e8ec0abbf32fdcc9f7eb14c0656c1d0fc2fc7ec8f60dff4b7ac080c50afd8e49"
digest = "1:4f6afcf4ebe041b3d4aa7926d09344b48d2f588e1f957526bbbe54f9cbb366a1"
name = "github.com/argoproj/pkg"
packages = [
"errors",
"exec",
"rand",
"time",
]
pruneopts = ""
revision = "88ab0e836a8e8c70bc297c5764669bd7da27afd1"
revision = "38dba6e98495680ff1f8225642b63db10a96bb06"
[[projects]]
digest = "1:d8a2bb36a048d1571bcc1aee208b61f39dc16c6c53823feffd37449dde162507"
@@ -106,6 +89,14 @@
pruneopts = ""
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
digest = "1:a6ee710e45210bafe11f2f28963571be2ac8809f9a7b675a6d2c02302a1ce1a9"
name = "github.com/bouk/monkey"
packages = ["."]
pruneopts = ""
revision = "5df1f207ff77e025801505ae4d903133a0b4353f"
version = "v1.0.0"
[[projects]]
digest = "1:e04162bd6a6d4950541bae744c968108e14913b1cebccf29f7650b573f44adb3"
name = "github.com/casbin/casbin"
@@ -360,6 +351,14 @@
revision = "5ccd90ef52e1e632236f7326478d4faa74f99438"
version = "v0.2.3"
[[projects]]
branch = "master"
digest = "1:9a06e7365c6039daf4db9bbf79650e2933a2880982cbab8106cb74a36617f40d"
name = "github.com/gogits/go-gogs-client"
packages = ["."]
pruneopts = ""
revision = "5a05380e4bc2440e0ec12f54f6f45648dbdd5e55"
[[projects]]
digest = "1:6e73003ecd35f4487a5e88270d3ca0a81bc80dc88053ac7e4dcfec5fba30d918"
name = "github.com/gogo/protobuf"
@@ -428,14 +427,6 @@
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:1e5b1e14524ed08301977b7b8e10c719ed853cbf3f24ecb66fae783a46f207a6"
name = "github.com/google/btree"
packages = ["."]
pruneopts = ""
revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306"
[[projects]]
digest = "1:14d826ee25139b4674e9768ac287a135f4e7c14e1134a5b15e4e152edfd49f41"
name = "github.com/google/go-jsonnet"
@@ -483,17 +474,6 @@
revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d"
version = "v1.4.0"
[[projects]]
branch = "master"
digest = "1:009a1928b8c096338b68b5822d838a72b4d8520715c1463614476359f3282ec8"
name = "github.com/gregjones/httpcache"
packages = [
".",
"diskcache",
]
pruneopts = ""
revision = "9cad4c3443a7200dd6400aef47183728de563a38"
[[projects]]
branch = "master"
digest = "1:9dca8c981b8aed7448d94e78bc68a76784867a38b3036d5aabc0b32d92ffd1f4"
@@ -691,22 +671,6 @@
revision = "c37440a7cf42ac63b919c752ca73a85067e05992"
version = "v0.2.0"
[[projects]]
branch = "master"
digest = "1:c24598ffeadd2762552269271b3b1510df2d83ee6696c1e543a0ff653af494bc"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
pruneopts = ""
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
[[projects]]
digest = "1:b46305723171710475f2dd37547edd57b67b9de9f2a6267cafdd98331fd6897f"
name = "github.com/peterbourgon/diskv"
packages = ["."]
pruneopts = ""
revision = "5f041e8faa004a95c88a202771f4cc3e991971e6"
version = "v2.0.1"
[[projects]]
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
name = "github.com/pkg/errors"
@@ -760,7 +724,6 @@
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"log",
"model",
]
pruneopts = ""
@@ -798,7 +761,10 @@
[[projects]]
digest = "1:01d968ff6535945510c944983eee024e81f1c949043e9bbfe5ab206ebc3588a4"
name = "github.com/sirupsen/logrus"
packages = ["."]
packages = [
".",
"hooks/test",
]
pruneopts = ""
revision = "a67f783a3814b8729bd2dac5780b5f78f8dbd64d"
version = "v1.1.0"
@@ -820,19 +786,19 @@
version = "v0.1.4"
[[projects]]
digest = "1:2208a80fc3259291e43b30f42f844d18f4218036dff510f42c653ec9890d460a"
digest = "1:9ba49264cef4386aded205f9cb5b1f2d30f983d7dc37a21c780d9db3edfac9a7"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = ""
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
version = "v0.0.1"
revision = "fe5e611709b0c57fa4a89136deaa8e1d4004d053"
[[projects]]
digest = "1:261bc565833ef4f02121450d74eb88d5ae4bd74bfe5d0e862cddb8550ec35000"
digest = "1:8e243c568f36b09031ec18dff5f7d2769dcf5ca4d624ea511c8e3197dc3d352d"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = ""
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:b1861b9a1aa0801b0b62945ed7477c1ab61a4bd03b55dfbc27f6d4f378110c8c"
@@ -992,8 +958,6 @@
packages = [
"unix",
"windows",
"windows/registry",
"windows/svc/eventlog",
]
pruneopts = ""
revision = "d0be0721c37eeb5299f245a996a483160fc36940"
@@ -1047,6 +1011,39 @@
pruneopts = ""
revision = "5e776fee60db37e560cee3fb46db699d2f095386"
[[projects]]
branch = "master"
digest = "1:e9e4b928898842a138bc345d42aae33741baa6d64f3ca69b0931f9c7a4fd0437"
name = "gonum.org/v1/gonum"
packages = [
"blas",
"blas/blas64",
"blas/cblas128",
"blas/gonum",
"floats",
"graph",
"graph/internal/linear",
"graph/internal/ordered",
"graph/internal/set",
"graph/internal/uid",
"graph/iterator",
"graph/simple",
"graph/topo",
"graph/traverse",
"internal/asm/c128",
"internal/asm/c64",
"internal/asm/f32",
"internal/asm/f64",
"internal/cmplx64",
"internal/math32",
"lapack",
"lapack/gonum",
"lapack/lapack64",
"mat",
]
pruneopts = ""
revision = "90b7154515874cee6c33cf56b29e257403a09a69"
[[projects]]
digest = "1:934fb8966f303ede63aa405e2c8d7f0a427a05ea8df335dfdc1833dd4d40756f"
name = "google.golang.org/appengine"
@@ -1116,25 +1113,18 @@
version = "v1.15.0"
[[projects]]
digest = "1:15d017551627c8bb091bde628215b2861bed128855343fdd570c62d08871f6e1"
name = "gopkg.in/alecthomas/kingpin.v2"
packages = ["."]
pruneopts = ""
revision = "947dcec5ba9c011838740e680966fd7087a71d0d"
version = "v2.2.6"
[[projects]]
digest = "1:bf7444e1e6a36e633f4f1624a67b9e4734cfb879c27ac0a2082ac16aff8462ac"
name = "gopkg.in/go-playground/webhooks.v3"
digest = "1:adf5b0ae3467c3182757ecb86fbfe819939473bb870a42789dc1a3e7729397cd"
name = "gopkg.in/go-playground/webhooks.v5"
packages = [
".",
"bitbucket",
"bitbucket-server",
"github",
"gitlab",
"gogs",
]
pruneopts = ""
revision = "5580947e3ec83427ef5f6f2392eddca8dde5d99a"
version = "v3.11.0"
revision = "175186584584a83966dc9a7b8ec6c3d3a4ce6110"
version = "v5.11.0"
[[projects]]
digest = "1:e5d1fb981765b6f7513f793a3fcaac7158408cca77f75f7311ac82cc88e9c445"
@@ -1247,16 +1237,16 @@
version = "v2.2.2"
[[projects]]
branch = "release-1.12"
digest = "1:ed04c5203ecbf6358fb6a774b0ecd40ea992d6dcc42adc1d3b7cf9eceb66b6c8"
branch = "release-1.14"
digest = "1:d8a6f1ec98713e685346a2e4b46c6ec4a1792a5535f8b0dffe3b1c08c9d69b12"
name = "k8s.io/api"
packages = [
"admission/v1beta1",
"admissionregistration/v1alpha1",
"admissionregistration/v1beta1",
"apps/v1",
"apps/v1beta1",
"apps/v1beta2",
"auditregistration/v1alpha1",
"authentication/v1",
"authentication/v1beta1",
"authorization/v1",
@@ -1268,16 +1258,21 @@
"batch/v1beta1",
"batch/v2alpha1",
"certificates/v1beta1",
"coordination/v1",
"coordination/v1beta1",
"core/v1",
"events/v1beta1",
"extensions/v1beta1",
"imagepolicy/v1alpha1",
"networking/v1",
"networking/v1beta1",
"node/v1alpha1",
"node/v1beta1",
"policy/v1beta1",
"rbac/v1",
"rbac/v1alpha1",
"rbac/v1beta1",
"scheduling/v1",
"scheduling/v1alpha1",
"scheduling/v1beta1",
"settings/v1alpha1",
@@ -1286,11 +1281,11 @@
"storage/v1beta1",
]
pruneopts = ""
revision = "475331a8afff5587f47d0470a93f79c60c573c03"
revision = "40a48860b5abbba9aa891b02b32da429b08d96a0"
[[projects]]
branch = "release-1.12"
digest = "1:39be82077450762b5e14b5268e679a14ac0e9c7d3286e2fcface437556a29e4c"
branch = "master"
digest = "1:49e0fcdcaeaf937c6c608d1da19eb80de74fe990021278d49d46e10288659be6"
name = "k8s.io/apiextensions-apiserver"
packages = [
"pkg/apis/apiextensions",
@@ -1298,14 +1293,13 @@
"pkg/client/clientset/clientset",
"pkg/client/clientset/clientset/scheme",
"pkg/client/clientset/clientset/typed/apiextensions/v1beta1",
"pkg/features",
]
pruneopts = ""
revision = "ca1024863b48cf0701229109df75ac5f0bb4907e"
revision = "7f7d2b94eca3a7a1c49840e119a8bc03c3afb1e3"
[[projects]]
branch = "release-1.12"
digest = "1:5899da40e41bcc8c1df101b72954096bba9d85b763bc17efc846062ccc111c7b"
branch = "release-1.14"
digest = "1:a802c91b189a31200cfb66744441fe62dac961ec7c5c58c47716570de7da195c"
name = "k8s.io/apimachinery"
packages = [
"pkg/api/equality",
@@ -1357,34 +1351,22 @@
"third_party/forked/golang/reflect",
]
pruneopts = ""
revision = "f71dbbc36e126f5a371b85f6cca96bc8c57db2b6"
revision = "6a84e37a896db9780c75367af8d2ed2bb944022e"
[[projects]]
branch = "master"
digest = "1:cb3ac215bfac54696f64a6e5c46524a7fc0f7a8f9b7a22cccb2e1e83ac2d013f"
name = "k8s.io/apiserver"
packages = [
"pkg/features",
"pkg/util/feature",
]
pruneopts = ""
revision = "19cf388d0a374e95329bf7d98e9bfd7da8853be0"
[[projects]]
branch = "release-9.0"
digest = "1:77bf3d9f18ec82e08ac6c4c7e2d9d1a2ef8d16b25d3ff72fcefcf9256d751573"
branch = "release-11.0"
digest = "1:794140b3ac07405646ea3d4a57e1f6155186e672aed8aa0c996779381cd92fe6"
name = "k8s.io/client-go"
packages = [
"discovery",
"discovery/fake",
"dynamic",
"dynamic/fake",
"informers/core/v1",
"informers/internalinterfaces",
"kubernetes",
"kubernetes/fake",
"kubernetes/scheme",
"kubernetes/typed/admissionregistration/v1alpha1",
"kubernetes/typed/admissionregistration/v1alpha1/fake",
"kubernetes/typed/admissionregistration/v1beta1",
"kubernetes/typed/admissionregistration/v1beta1/fake",
"kubernetes/typed/apps/v1",
@@ -1393,6 +1375,8 @@
"kubernetes/typed/apps/v1beta1/fake",
"kubernetes/typed/apps/v1beta2",
"kubernetes/typed/apps/v1beta2/fake",
"kubernetes/typed/auditregistration/v1alpha1",
"kubernetes/typed/auditregistration/v1alpha1/fake",
"kubernetes/typed/authentication/v1",
"kubernetes/typed/authentication/v1/fake",
"kubernetes/typed/authentication/v1beta1",
@@ -1415,6 +1399,8 @@
"kubernetes/typed/batch/v2alpha1/fake",
"kubernetes/typed/certificates/v1beta1",
"kubernetes/typed/certificates/v1beta1/fake",
"kubernetes/typed/coordination/v1",
"kubernetes/typed/coordination/v1/fake",
"kubernetes/typed/coordination/v1beta1",
"kubernetes/typed/coordination/v1beta1/fake",
"kubernetes/typed/core/v1",
@@ -1425,6 +1411,12 @@
"kubernetes/typed/extensions/v1beta1/fake",
"kubernetes/typed/networking/v1",
"kubernetes/typed/networking/v1/fake",
"kubernetes/typed/networking/v1beta1",
"kubernetes/typed/networking/v1beta1/fake",
"kubernetes/typed/node/v1alpha1",
"kubernetes/typed/node/v1alpha1/fake",
"kubernetes/typed/node/v1beta1",
"kubernetes/typed/node/v1beta1/fake",
"kubernetes/typed/policy/v1beta1",
"kubernetes/typed/policy/v1beta1/fake",
"kubernetes/typed/rbac/v1",
@@ -1433,6 +1425,8 @@
"kubernetes/typed/rbac/v1alpha1/fake",
"kubernetes/typed/rbac/v1beta1",
"kubernetes/typed/rbac/v1beta1/fake",
"kubernetes/typed/scheduling/v1",
"kubernetes/typed/scheduling/v1/fake",
"kubernetes/typed/scheduling/v1alpha1",
"kubernetes/typed/scheduling/v1alpha1/fake",
"kubernetes/typed/scheduling/v1beta1",
@@ -1469,23 +1463,22 @@
"tools/remotecommand",
"transport",
"transport/spdy",
"util/buffer",
"util/cert",
"util/connrotation",
"util/exec",
"util/flowcontrol",
"util/homedir",
"util/integer",
"util/jsonpath",
"util/keyutil",
"util/retry",
"util/workqueue",
]
pruneopts = ""
revision = "13596e875accbd333e0b5bd5fd9462185acd9958"
revision = "11646d1007e006f6f24995cb905c68bc62901c81"
[[projects]]
branch = "release-1.12"
digest = "1:e6fffdf0dfeb0d189a7c6d735e76e7564685d3b6513f8b19d3651191cb6b084b"
branch = "release-1.14"
digest = "1:742ce70d2c6de0f02b5331a25d4d549f55de6b214af22044455fd6e6b451cad9"
name = "k8s.io/code-generator"
packages = [
"cmd/go-to-protobuf",
@@ -1494,43 +1487,61 @@
"third_party/forked/golang/reflect",
]
pruneopts = ""
revision = "3dcf91f64f638563e5106f21f50c31fa361c918d"
revision = "50b561225d70b3eb79a1faafd3dfe7b1a62cbe73"
[[projects]]
branch = "master"
digest = "1:15710582bd5ceff07eee4726884f75f97f90366fde9307b8dd09500c75722456"
digest = "1:6a2a63e09a59caff3fd2d36d69b7b92c2fe7cf783390f0b7349fb330820f9a8e"
name = "k8s.io/gengo"
packages = [
"args",
"examples/set-gen/sets",
"generator",
"namer",
"parser",
"types",
]
pruneopts = ""
revision = "8394c995ab8fbe52216f38d0e1a37de36d820528"
revision = "e17681d19d3ac4837a019ece36c2a0ec31ffe985"
[[projects]]
digest = "1:4f5eb833037cc0ba0bf8fe9cae6be9df62c19dd1c869415275c708daa8ccfda5"
digest = "1:9eaf86f4f6fb4a8f177220d488ef1e3255d06a691cca95f14ef085d4cd1cef3c"
name = "k8s.io/klog"
packages = ["."]
pruneopts = ""
revision = "a5bc97fbc634d635061f3146511332c7e313a55a"
version = "v0.1.0"
revision = "d98d8acdac006fb39831f1b25640813fef9c314f"
version = "v0.3.3"
[[projects]]
branch = "master"
digest = "1:9a648ff9eb89673d2870c22fc011ec5db0fcff6c4e5174a650298e51be71bbf1"
name = "k8s.io/kube-openapi"
digest = "1:0d737d598e9db0a38d6ef6cba514c358b9fe7e1bc6b1128d02b2622700c75f2a"
name = "k8s.io/kube-aggregator"
packages = [
"pkg/common",
"pkg/util/proto",
"pkg/apis/apiregistration",
"pkg/apis/apiregistration/v1",
"pkg/apis/apiregistration/v1beta1",
]
pruneopts = ""
revision = "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
revision = "e80910364765199a4baebd4dec54c885fe52b680"
[[projects]]
digest = "1:6061aa42761235df375f20fa4a1aa6d1845cba3687575f3adb2ef3f3bc540af5"
digest = "1:42ea993b351fdd39b9aad3c9ebe71f2fdb5d1f8d12eed24e71c3dff1a31b2a43"
name = "k8s.io/kube-openapi"
packages = [
"cmd/openapi-gen",
"cmd/openapi-gen/args",
"pkg/common",
"pkg/generators",
"pkg/generators/rules",
"pkg/util/proto",
"pkg/util/sets",
]
pruneopts = ""
revision = "411b2483e5034420675ebcdd4a55fc76fe5e55cf"
[[projects]]
branch = "release-1.14"
digest = "1:78aa6079e011ece0d28513c7fe1bd64284fa9eb5d671760803a839ffdf0e9e38"
name = "k8s.io/kubernetes"
packages = [
"pkg/api/v1/pod",
@@ -1538,19 +1549,26 @@
"pkg/apis/autoscaling",
"pkg/apis/batch",
"pkg/apis/core",
"pkg/apis/extensions",
"pkg/apis/networking",
"pkg/apis/policy",
"pkg/features",
"pkg/kubectl/scheme",
"pkg/kubectl/util/term",
"pkg/kubelet/apis",
"pkg/util/interrupt",
"pkg/util/node",
]
pruneopts = ""
revision = "17c77c7898218073f14c8d573582e8d2313dc740"
version = "v1.12.2"
revision = "2d20b5759406ded89f8b25cf085ff4733b144ba5"
[[projects]]
branch = "master"
digest = "1:4c5d39f7ca1c940d7e74dbc62d2221e2c59b3d35c54f1fa9c77f3fd3113bbcb1"
name = "k8s.io/utils"
packages = [
"buffer",
"integer",
"pointer",
"trace",
]
pruneopts = ""
revision = "c55fbcfc754a5b2ec2fbae8fb9dcac36bdba6a12"
[[projects]]
branch = "master"
@@ -1560,6 +1578,14 @@
pruneopts = ""
revision = "97fed8db84274c421dbfffbb28ec859901556b97"
[[projects]]
digest = "1:321081b4a44256715f2b68411d8eda9a17f17ebfe6f0cc61d2cc52d11c08acfa"
name = "sigs.k8s.io/yaml"
packages = ["."]
pruneopts = ""
revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"
version = "v1.1.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
@@ -1568,8 +1594,10 @@
"github.com/TomOnTime/utfutil",
"github.com/argoproj/argo/pkg/apis/workflow/v1alpha1",
"github.com/argoproj/argo/util",
"github.com/argoproj/pkg/errors",
"github.com/argoproj/pkg/exec",
"github.com/argoproj/pkg/time",
"github.com/bouk/monkey",
"github.com/casbin/casbin",
"github.com/casbin/casbin/model",
"github.com/casbin/casbin/persist",
@@ -1580,14 +1608,17 @@
"github.com/ghodss/yaml",
"github.com/go-openapi/loads",
"github.com/go-openapi/runtime/middleware",
"github.com/go-openapi/spec",
"github.com/go-redis/cache",
"github.com/go-redis/redis",
"github.com/gobuffalo/packr",
"github.com/gobwas/glob",
"github.com/gogits/go-gogs-client",
"github.com/gogo/protobuf/gogoproto",
"github.com/gogo/protobuf/proto",
"github.com/gogo/protobuf/protoc-gen-gofast",
"github.com/gogo/protobuf/protoc-gen-gogofast",
"github.com/gogo/protobuf/sortkeys",
"github.com/golang/protobuf/proto",
"github.com/golang/protobuf/protoc-gen-go",
"github.com/golang/protobuf/ptypes/empty",
@@ -1610,8 +1641,8 @@
"github.com/pkg/errors",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/prometheus/common/log",
"github.com/sirupsen/logrus",
"github.com/sirupsen/logrus/hooks/test",
"github.com/skratchdot/open-golang/open",
"github.com/soheilhy/cmux",
"github.com/spf13/cobra",
@@ -1624,6 +1655,7 @@
"github.com/yuin/gopher-lua",
"golang.org/x/crypto/bcrypt",
"golang.org/x/crypto/ssh",
"golang.org/x/crypto/ssh/knownhosts",
"golang.org/x/crypto/ssh/terminal",
"golang.org/x/net/context",
"golang.org/x/oauth2",
@@ -1637,23 +1669,27 @@
"google.golang.org/grpc/metadata",
"google.golang.org/grpc/reflection",
"google.golang.org/grpc/status",
"gopkg.in/go-playground/webhooks.v3",
"gopkg.in/go-playground/webhooks.v3/bitbucket",
"gopkg.in/go-playground/webhooks.v3/github",
"gopkg.in/go-playground/webhooks.v3/gitlab",
"gopkg.in/go-playground/webhooks.v5/bitbucket",
"gopkg.in/go-playground/webhooks.v5/bitbucket-server",
"gopkg.in/go-playground/webhooks.v5/github",
"gopkg.in/go-playground/webhooks.v5/gitlab",
"gopkg.in/go-playground/webhooks.v5/gogs",
"gopkg.in/src-d/go-git.v4",
"gopkg.in/src-d/go-git.v4/config",
"gopkg.in/src-d/go-git.v4/plumbing",
"gopkg.in/src-d/go-git.v4/plumbing/transport",
"gopkg.in/src-d/go-git.v4/plumbing/transport/client",
"gopkg.in/src-d/go-git.v4/plumbing/transport/http",
"gopkg.in/src-d/go-git.v4/plumbing/transport/ssh",
"gopkg.in/src-d/go-git.v4/storage/memory",
"gopkg.in/src-d/go-git.v4/utils/ioutil",
"gopkg.in/yaml.v2",
"k8s.io/api/apps/v1",
"k8s.io/api/batch/v1",
"k8s.io/api/core/v1",
"k8s.io/api/extensions/v1beta1",
"k8s.io/api/rbac/v1",
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1",
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset",
"k8s.io/apimachinery/pkg/api/equality",
"k8s.io/apimachinery/pkg/api/errors",
@@ -1673,6 +1709,7 @@
"k8s.io/client-go/discovery",
"k8s.io/client-go/discovery/fake",
"k8s.io/client-go/dynamic",
"k8s.io/client-go/dynamic/fake",
"k8s.io/client-go/informers/core/v1",
"k8s.io/client-go/kubernetes",
"k8s.io/client-go/kubernetes/fake",
@@ -1687,6 +1724,11 @@
"k8s.io/client-go/util/flowcontrol",
"k8s.io/client-go/util/workqueue",
"k8s.io/code-generator/cmd/go-to-protobuf",
"k8s.io/klog",
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1",
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1",
"k8s.io/kube-openapi/cmd/openapi-gen",
"k8s.io/kube-openapi/pkg/common",
"k8s.io/kubernetes/pkg/api/v1/pod",
"k8s.io/kubernetes/pkg/apis/apps",
"k8s.io/kubernetes/pkg/apis/batch",

View File

@@ -7,6 +7,7 @@ required = [
"github.com/gogo/protobuf/protoc-gen-gofast",
"github.com/gogo/protobuf/protoc-gen-gogofast",
"k8s.io/code-generator/cmd/go-to-protobuf",
"k8s.io/kube-openapi/cmd/openapi-gen",
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway",
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger",
"golang.org/x/sync/errgroup",
@@ -34,20 +35,24 @@ required = [
name = "github.com/prometheus/client_golang"
revision = "7858729281ec582767b20e0d696b6041d995d5e0"
[[constraint]]
branch = "release-1.12"
[[override]]
branch = "release-1.14"
name = "k8s.io/api"
[[constraint]]
name = "k8s.io/apiextensions-apiserver"
branch = "release-1.12"
[[override]]
branch = "release-1.14"
name = "k8s.io/kubernetes"
[[constraint]]
branch = "release-1.12"
[[override]]
branch = "release-1.14"
name = "k8s.io/code-generator"
[[constraint]]
branch = "release-9.0"
[[override]]
branch = "release-1.14"
name = "k8s.io/apimachinery"
[[override]]
branch = "release-11.0"
name = "k8s.io/client-go"
[[constraint]]
@@ -65,3 +70,13 @@ required = [
[[constraint]]
branch = "master"
name = "github.com/yudai/gojsondiff"
[[constraint]]
name = "github.com/spf13/cobra"
revision = "fe5e611709b0c57fa4a89136deaa8e1d4004d053"
# TODO: move off of k8s.io/kube-openapi and use controller-tools for CRD spec generation
# (override argoproj/argo contraint on master)
[[override]]
revision = "411b2483e5034420675ebcdd4a55fc76fe5e55cf"
name = "k8s.io/kube-openapi"

110
Makefile
View File

@@ -1,4 +1,4 @@
PACKAGE=github.com/argoproj/argo-cd
PACKAGE=github.com/argoproj/argo-cd/common
CURRENT_DIR=$(shell pwd)
DIST_DIR=${CURRENT_DIR}/dist
CLI_NAME=argocd
@@ -10,13 +10,26 @@ GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-
GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)
PACKR_CMD=$(shell if [ "`which packr`" ]; then echo "packr"; else echo "go run vendor/github.com/gobuffalo/packr/packr/main.go"; fi)
define run-in-dev-tool
docker run --rm -it -u $(shell id -u) -e HOME=/home/user -v ${CURRENT_DIR}:/go/src/github.com/argoproj/argo-cd -w /go/src/github.com/argoproj/argo-cd argocd-dev-tools bash -c "GOPATH=/go $(1)"
endef
PATH:=$(PATH):$(PWD)/hack
# docker image publishing options
DOCKER_PUSH=false
IMAGE_TAG=latest
DOCKER_PUSH?=false
IMAGE_TAG?=
# perform static compilation
STATIC_BUILD=true
STATIC_BUILD?=true
# build development images
DEV_IMAGE=false
DEV_IMAGE?=false
# lint is memory and CPU intensive, so we can limit on CI to mitigate OOM
LINT_GOGC?=off
LINT_CONCURRENCY?=8
# Set timeout for linter
LINT_DEADLINE?=4m0s
CODEGEN=true
LINT=true
override LDFLAGS += \
-X ${PACKAGE}.version=${VERSION} \
@@ -50,12 +63,20 @@ all: cli image argocd-util
protogen:
./hack/generate-proto.sh
.PHONY: openapigen
openapigen:
./hack/update-openapi.sh
.PHONY: clientgen
clientgen:
./hack/update-codegen.sh
.PHONY: codegen-local
codegen-local: protogen clientgen openapigen manifests-local
.PHONY: codegen
codegen: protogen clientgen format-code
codegen: dev-tools-image
@if [ "$(CODGEN)" = "true" ]; then $(call run-in-dev-tool,make codegen-local) ; fi
.PHONY: cli
cli: clean-debug
@@ -71,25 +92,34 @@ release-cli: clean-debug image
.PHONY: argocd-util
argocd-util: clean-debug
# Build argocd-util as a statically linked binary, so it could run within the alpine-based dex container (argoproj/argo-cd#844)
CGO_ENABLED=0 go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-util ./cmd/argocd-util
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-util ./cmd/argocd-util
.PHONY: dev-tools-image
dev-tools-image:
docker build -t argocd-dev-tools ./hack -f ./hack/Dockerfile.dev-tools
.PHONY: manifests-local
manifests-local:
./hack/update-manifests.sh
.PHONY: manifests
manifests:
./hack/update-manifests.sh
manifests: dev-tools-image
$(call run-in-dev-tool,make manifests-local IMAGE_TAG='${IMAGE_TAG}')
# NOTE: we use packr to do the build instead of go, since we embed swagger files and policy.csv
# files into the go binary
.PHONY: server
server: clean-debug
${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd/argocd-server
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd/argocd-server
.PHONY: repo-server
repo-server:
go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd/argocd-repo-server
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd/argocd-repo-server
.PHONY: controller
controller:
${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd/argocd-application-controller
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd/argocd-application-controller
.PHONY: packr
packr:
@@ -119,26 +149,50 @@ endif
.PHONY: builder-image
builder-image:
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
.PHONY: dep-ensure
dep-ensure:
dep ensure
dep ensure -no-vendor
.PHONY: format-code
format-code:
./hack/format-code.sh
.PHONY: lint-local
lint-local: build
# golangci-lint does not do a good job of formatting imports
goimports -local github.com/argoproj/argo-cd -w `find . ! -path './vendor/*' ! -path './pkg/client/*' ! -path '*.pb.go' ! -path '*.gw.go' -type f -name '*.go'`
GOGC=$(LINT_GOGC) golangci-lint run --fix --verbose --concurrency $(LINT_CONCURRENCY) --deadline $(LINT_DEADLINE)
.PHONY: lint
lint:
gometalinter.v2 --config gometalinter.json ./...
lint: dev-tools-image
@if [ "$(LINT)" = "true" ]; then $(call run-in-dev-tool,make lint-local LINT_CONCURRENCY=$(LINT_CONCURRENCY) LINT_DEADLINE=$(LINT_DEADLINE) LINT_GOGC=$(LINT_GOGC)); fi
.PHONY: build
build:
go build -v `go list ./... | grep -v 'resource_customizations\|test/e2e'`
.PHONY: test
test:
go test -covermode=count -coverprofile=coverage.out `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
go test -v -covermode=count -coverprofile=coverage.out `go list ./... | grep -v "test/e2e"`
.PHONY: cover
cover:
go tool cover -html=coverage.out
.PHONY: test-e2e
test-e2e:
go test -v -failfast -timeout 20m ./test/e2e
test-e2e: cli
go test -v -timeout 10m ./test/e2e
.PHONY: start-e2e
start-e2e: cli
killall goreman || true
# check we can connect to Docker to start Redis
docker version
kubectl create ns argocd-e2e || true
kubectl config set-context --current --namespace=argocd-e2e
kustomize build test/manifests/base | kubectl apply -f -
# set paths for locally managed ssh known hosts and tls certs data
ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
goreman start
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in packr boxes
.PHONY: clean-debug
@@ -149,8 +203,16 @@ clean-debug:
clean: clean-debug
-rm -rf ${CURRENT_DIR}/dist
.PHONY: start
start:
killall goreman || true
# check we can connect to Docker to start Redis
docker version
kubens argocd
goreman start
.PHONY: pre-commit
pre-commit: dep-ensure codegen format-code test lint
pre-commit: dep-ensure codegen build lint test
.PHONY: release-precheck
release-precheck: manifests
@@ -159,4 +221,4 @@ release-precheck: manifests
@if [ "$(GIT_TAG)" != "v`cat VERSION`" ]; then echo 'VERSION does not match git tag'; exit 1; fi
.PHONY: release
release: release-precheck precheckin image release-cli
release: pre-commit release-precheck image release-cli

2
OWNERS
View File

@@ -3,6 +3,6 @@ owners:
- jessesuen
approvers:
- alexc
- alexmt
- jessesuen
- merenbach

View File

@@ -1,5 +1,7 @@
controller: go run ./cmd/argocd-application-controller/main.go --redis localhost:6379 --repo-server localhost:8081
api-server: go run ./cmd/argocd-server/main.go --redis localhost:6379 --disable-auth --insecure --dex-server http://localhost:5556 --repo-server localhost:8081 --staticassets ../argo-cd-ui/dist/app
repo-server: go run ./cmd/argocd-repo-server/main.go --loglevel debug --redis localhost:6379
dex: sh -c "go run ./cmd/argocd-util/main.go gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p 5556:5556 -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/dexidp/dex:v2.14.0 serve /dex.yaml"
redis: docker run --rm -i -p 6379:6379 redis:5.0.3-alpine --save "" --appendonly no
controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --staticassets ui/dist/app"
dex: sh -c "go run ./cmd/argocd-util/main.go gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/dexidp/dex:v2.14.0 serve /dex.yaml"
redis: docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:5.0.3-alpine --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh

116
README.md
View File

@@ -7,100 +7,50 @@
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.
![Argo CD UI](docs/argocd-ui.gif)
![Argo CD UI](docs/assets/argocd-ui.gif)
## Why Argo CD?
Application definitions, configurations, and environments should be declarative and version controlled.
Application deployment and lifecycle management should be automated, auditable, and easy to understand.
## Getting Started
### Quickstart
## Who uses Argo CD?
```bash
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```
Organizations below are **officially** using Argo CD. Please send a PR with your organization name if you are using Argo CD.
Follow our [getting started guide](docs/getting_started.md). Further [documentation](docs/)
is provided for additional features.
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
1. [Codility](https://www.codility.com/)
1. [Commonbond](https://commonbond.co/)
1. [CyberAgent](https://www.cyberagent.co.jp/en/)
1. [END.](https://www.endclothing.com/)
1. [GMETRI](https://gmetri.com/)
1. [Intuit](https://www.intuit.com/)
1. [KintoHub](https://www.kintohub.com/)
1. [KompiTech GmbH](https://www.kompitech.com/)
1. [Mambu](https://www.mambu.com/)
1. [Mirantis](https://mirantis.com/)
1. [OpenSaaS Studio](https://opensaas.studio)
1. [Optoro](https://www.optoro.com/)
1. [Riskified](https://www.riskified.com/)
1. [Saildrone](https://www.saildrone.com/)
1. [Tesla](https://tesla.com/)
1. [tZERO](https://www.tzero.com/)
1. [Ticketmaster](https://ticketmaster.com)
1. [Yieldlab](https://www.yieldlab.de/)
1. [Volvo Cars](https://www.volvocars.com/)
## How it works
## Documentation
Argo CD follows the **GitOps** pattern of using git repositories as the source of truth for defining
the desired application state. Kubernetes manifests can be specified in several ways:
* [kustomize](https://kustomize.io) applications
* [helm](https://helm.sh) charts
* [ksonnet](https://ksonnet.io) applications
* [jsonnet](https://jsonnet.org) files
* Plain directory of YAML/json manifests
* Any custom config management tool configured as a config management plugin
Argo CD automates the deployment of the desired application states in the specified target environments.
Application deployments can track updates to branches, tags, or pinned to a specific version of
manifests at a git commit. See [tracking strategies](docs/tracking_strategies.md) for additional
details about the different tracking strategies available.
For a quick 10 minute overview of Argo CD, check out the demo presented to the Sig Apps community
meeting:
[![Alt text](https://img.youtube.com/vi/aWDIQMbp1cc/0.jpg)](https://youtu.be/aWDIQMbp1cc?t=1m4s)
## Architecture
![Argo CD Architecture](docs/argocd_architecture.png)
Argo CD is implemented as a kubernetes controller which continuously monitors running applications
and compares the current, live state against the desired target state (as specified in the git repo).
A deployed application whose live state deviates from the target state is considered `OutOfSync`.
Argo CD reports & visualizes the differences, while providing facilities to automatically or
manually sync the live state back to the desired target state. Any modifications made to the desired
target state in the git repo can be automatically applied and reflected in the specified target
environments.
For additional details, see [architecture overview](docs/architecture.md).
## Features
* Automated deployment of applications to specified target environments
* Support for multiple config management/templating tools (Kustomize, Helm, Ksonnet, Jsonnet, plain-YAML)
* Ability to manage and deploy to multiple clusters
* SSO Integration (OIDC, OAuth2, LDAP, SAML 2.0, GitHub, GitLab, Microsoft, LinkedIn)
* Multi-tenancy and RBAC policies for authorization
* Rollback/Roll-anywhere to any application configuration committed in git repository
* Health status analysis of application resources
* Automated configuration drift detection and visualization
* Automated or manual syncing of applications to its desired state
* Web UI which provides real-time view of application activity
* CLI for automation and CI integration
* Webhook integration (GitHub, BitBucket, GitLab)
* Access tokens for automation
* PreSync, Sync, PostSync hooks to support complex application rollouts (e.g.blue/green & canary upgrades)
* Audit trails for application events and API calls
* Prometheus metrics
* Parameter overrides for overriding ksonnet/helm parameters in git
To learn more about Argo CD [go to the complete documentation](https://argoproj.github.io/argo-cd/).
## Community Blogs and Presentations
* GitOps with Argo CD: [Simplify and Automate Deployments Using GitOps with IBM Multicloud Manager](https://www.ibm.com/blogs/bluemix/2019/02/simplify-and-automate-deployments-using-gitops-with-ibm-multicloud-manager-3-1-2/)
* KubeCon talk: [CI/CD in Light Speed with K8s and Argo CD](https://www.youtube.com/watch?v=OdzH82VpMwI&feature=youtu.be)
* KubeCon talk: [Machine Learning as Code](https://www.youtube.com/watch?v=VXrGp5er1ZE&t=0s&index=135&list=PLj6h78yzYM2PZf9eA7bhWnIh_mK1vyOfU)
* Among other things, desribes how Kubeflow uses Argo CD to implement GitOPs for ML
* SIG Apps demo: [Argo CD - GitOps Continuous Delivery for Kubernetes](https://www.youtube.com/watch?v=aWDIQMbp1cc&feature=youtu.be&t=1m4s)
## Project Resources
* Argo GitHub: https://github.com/argoproj
* Argo Slack: [click here to join](https://argoproj.github.io/community/join-slack)
* Argo website: https://argoproj.github.io/
## Development Status
* Argo CD is actively developed and is being used in production to deploy SaaS services at Intuit
## Roadmap
### v0.12
* Support for custom K8S manifest templating engines
* Support for custom health assessments (e.g. CRD health)
* Improved prometheus metrics
* Higher availability
* UI improvements
1. [Comparison of Argo CD, Spinnaker, Jenkins X, and Tekton](https://www.inovex.de/blog/spinnaker-vs-argo-cd-vs-tekton-vs-jenkins-x/)
1. [Simplify and Automate Deployments Using GitOps with IBM Multicloud Manager 3.1.2](https://medium.com/ibm-cloud/simplify-and-automate-deployments-using-gitops-with-ibm-multicloud-manager-3-1-2-4395af317359)
1. [GitOps for Kubeflow using Argo CD](https://www.kubeflow.org/docs/use-cases/gitops-for-kubeflow/)
1. [GitOps Toolsets on Kubernetes with CircleCI and Argo CD](https://www.digitalocean.com/community/tutorials/webinar-series-gitops-tool-sets-on-kubernetes-with-circleci-and-argo-cd)
1. [Simplify and Automate Deployments Using GitOps with IBM Multicloud Manager](https://www.ibm.com/blogs/bluemix/2019/02/simplify-and-automate-deployments-using-gitops-with-ibm-multicloud-manager-3-1-2/)
1. [CI/CD in Light Speed with K8s and Argo CD](https://www.youtube.com/watch?v=OdzH82VpMwI&feature=youtu.be)
1. [Machine Learning as Code](https://www.youtube.com/watch?v=VXrGp5er1ZE&t=0s&index=135&list=PLj6h78yzYM2PZf9eA7bhWnIh_mK1vyOfU). Among other things, describes how Kubeflow uses Argo CD to implement GitOPs for ML
1. [Argo CD - GitOps Continuous Delivery for Kubernetes](https://www.youtube.com/watch?v=aWDIQMbp1cc&feature=youtu.be&t=1m4s)

View File

@@ -1 +1 @@
0.12.0
1.2.5

22
assets/badge.svg Normal file
View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="131" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="131" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path id="leftPath" fill="#555" d="M0 0h74v20H0z"/>
<path id="rightPath" fill="#4c1" d="M74 0h57v20H74z"/>
<path fill="url(#b)" d="M0 0h131v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="90">
<image x="5" y="3" width="14" height="14" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAeCAYAAADU8sWcAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAABPAAAATwFjiv3XAAACC2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjE8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlBob3RvbWV0cmljSW50ZXJwcmV0YXRpb24+MjwvdGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KD0UqkwAACpZJREFUSA11VnmQFcUZ//qY4527by92WWTXwHKqEJDDg0MQUioKSWopK4oaS9RAlMofUSPGbFlBSaViKomgQSNmIdECkUS8ophdozEJArKoKCsgIFlY2Pu9N29mero7X78FxCR2Vb8309PTv+/4fb9vCHz1II2Nm+jmzYul2bJsdlMyO7LyPGlZtYqQck1ZHCgwLZWiROeJgB7bDzpYb/7o0y/emzXv4Pts8+ZGBUC0uf/vQf57wdw3NTVRnOYFfXvj6pJcadkUmbDGRoxVSEItSYAQQrUmRBP81VoRpkFTJUMuZA/3g/28q3dn89b7u885D4348vgf8NPAxY033LJmSlDizlSOU94f6UhoHaZdC/3QCHWON2gDYBhAYyRAWgyD4QSi1815f29++vv/+CoD2Lm2nAFGl8mndzyxMMgk5/iW6xzPi/xVk+oyE8+vLNt14FR/0uYW14ox0IwUJ4ZAaUpAaaBEYByiyLbikWWNnnThvPIxu+L717auVeb81tbWsyk4C34u8I3LnlhcSCameMzykg7TwzOJGIYWQj+M+voLYcSo/hxY1E65FECUq4G4RFP0nSjMCMVAIIKUnOHkw90L6qq/vXvbh02trV8yoBh2E0NMY9GiG+58fIGXTF6epyw7JOnamZQbz/tCnDrVVzgqqdwTT0ZTdWDN0wPpMh3ZPZqHLSw98C7Y4RwtnAodsTwGAg0ppkcxrlkYJFID+Z0bn/zelsFImzQRXfScNJFiOG689dcT/FT6GzlqedVJTHRpPH6yz/PyfTlx2IrLCpvSdWrv5OXkX9fOJB/Mnaz3XzItap+yMDoyZgEE7F1SceIghryeRNwnTBkEqhRIxiLF6bCpDXNybW2v/ruxcTzbt2+zZmfCvWT+zxNhbek3cxaPlyYsXVWSSHT25rxC3o+OWK6aDn7sZ3r79ZNY28w45Esxuhw5RjmVvEQPlIwgR8bOiE7VdrPaA2+TeGEEhFbhtAEmG5pzk5WqqbWTPv7jC8sLpgxNSRWZrUeWTAgor7EY9ytTiURvzg8GskGU5xxKkc33yDcX1yc7x3ijLxF+zZgoEljgCrSiXOWIG/WGrjifHxl1V9iyeKwW1lHNo5SKmDmcYinivwhjVpkor55sQj9+/D4MO9bpLW8RN6zJzMvbPDUkHaecE368N1+IUUJfIm5hPf1w2kTedlm/LgntmqEWEQGT3ScJYUh2NIxiiWupSUFasor1VgzXvPATfv6BC3Roq0E9MLqAXKQcAe1rqi/dv+q3DwQUw6djY6tiAaG1jLEw7nAn64WhcStPqZqpA6chap8aage077GwbQcJDx8Awq3T3DHlbeijUHWwytHFuuizCTergdQ+YocxLRHVPNcESRAJxqo60nbSvExvvvWx23MCVvucDyRt5hitwDRHHG09pZiaT7MlSUdUhtQGZttYRdwA4WnFQjFnDA4LX7UdIlgMYpaouBgGMnuBKVN/ZhgBRCMVynNAHOfRm777q4UcrW3EZxcEnD/kclYlpVaFKFIZrIRtluvdls/G9InnLZ9fpLXoJjwZAzZkDJ5mjkIj8HTCKETH9oD0eoikMSDQ5cTrLrUg4QoaFlxUH5MdYy6yAFAaYBHed6ODkMPOkMcFZggZKa0SkaL/jMeDpq5D1Vez9693V/wNUg1jQRY8yLX8BcSba8AaOgyBOdqtQBz7DNj8lZCeNY9SN6aiD3bTOc89s2iliDVvKa3uGxf6doQAxYE2oBbm0HQPG5IxqKhOg9ZpSSQj+pDU+jv5t7913spV5c70GcqLJwgMGw6ZJUuBLbgHPW0D4rgQdewDvnAllN58B0D918AvzVBn3jWi/p4Hq2/s+et1URgBRlVRMGVvBtITfwyuyYPAoIRmWSIrEqjZW1Ml/qN7dtRWzptfBzXDoaeri6TicWhes8Zsg9iMOaAyU0D3fw4qdSEkrryquP77xx+HZCwG3T09HEZfCBUXTxh125H9VZ9SHp1OfZF4aEWI9MM1AhmiVCmaIwV6a2F5ge2q8s7OBB8ytPiOZduw6pFHYOS4cUUQGsNWXj4MVLYdaGU9UAQ0o2H8eHh49WpAITReKqdmGC0PsvF2JB4Gu+i5RqKg5JSi82mOnu9GYGEhwbFjSOXadJII7Pa6Ed3hwU8igAU8lU7D/ffdZ1pFEUT19YI+sR9oZgLIjo8h6usDu2oIzJpxOc4ZphKQtoqFxw6LzxIV/RMxkabSUPAJRz3A5oc50x9T7Lf3xkR0R0xGSS+IRIFQOUUUkqvGXdR5tGXnLmjbYXI0OLCJykIBvFe2ABVHgCQqgMpjkH9xE9IY7cSgGd0w+8mOVjiy68DOh+sauusiwSM8F11nlhCm39/SvP7ux4qNZVfba/0TJ183Is9opWtxEXe5E/cC+Y5bf+CilmczqaC/JigEOjjYTrwtzwB8tAn4kJFGMIEmy0B98gb4B7tAYimHncd19ObLrP25rW0/rb76pQqsoKTEkKJgoT64biCObVx35xvGGz67qYW3Nl0RWYH/ftxmo7pyfpSoKFFjEiLxhE73jXaueHXlGw+PZ68rS8pQ01QlodUjTqcAfcQo8qGjQB36EwQfbFAuF8wjZbmNtStefZ4nC0uibKIHez92AeZEUln5wocGuLGxyUbg2cVEFp5a9pFz11MH/ZCP6M0V8gmbxWZL35K2paFmUmjDgCXAMQkjqv8wgFMGBFVSCw8g1wWsrB5IBrTDAvB0eaAkhVHCeGzSQDUm3bE9/1hs/d7dBhy5aSqAaNNWN6Mvdn9+e0KGfl8hZCd6vBwqABmISKSkCgkqr/a6QMWrwLpsKaYWhfrwi1ikfWBNvQmUhXItAlznICMlcopFFNXcEA25wBw/EIms2L4O1onZs5u46abFUsILDWhAc/OKo7H+/OtuEFqaUcsmIE8CR47zEMsSuJvRQcceODVmOqR/uAHKftAC6R+9AKcmzgVxci9qexwLTIDZ34+MR6FVErXECXw7kc+3/G7j8gPG0dbWJmQnum1+zDj3U2rJ0rWzvFRynu/YskMTb5vetmwI7xyeg7R0g072ynEGwa0PwZTRDbDn0GGI1j0Ii2pCKPCyqJTl+eei9tOl5Kr1eU2seuG5ZMBv2fjUIMkQymAWC6jouQE3jdlYZa43PLnsLbcn++eYVwi6FCQktTCEmO3QAx2rhOllAl6bOwsahg2FDTMvhRklWVSUMkxcWEyHJFbUqyCW9AtRSV//K18AF4XmbOWeBTegCF78ujTXf3hm+XvTeo49G8/67Sg+A0a0OGc6CDzIlFbDL3+8GPYuuxLWP7AYysprIQx9YNjdzAglyVkD3qGvd3dsWvv0infM2qBj53zr49rZsJsNZwa2WeTTFxtP3L1oe1VCzh3AWqOM2FJJsFBwbMuCUAgQyAqGrVVJ7Zdwyz2RZS/X/GbrguJ5eNYgyhfnncH5v+DmYcft18Y1T5S7fl9jPMN+4aM6IpeQPmgxThNA5AnuJFhIeI2tHafiNqEUW1XQL+8NrfRznENP1drNuTOA5/5/KezmAZ4zaJBSdVaUvQk5PsNTeqfrMsKQOui5LGKibBAjmKgSRk/RIMlc8BiWCLbI95Dx05jybrMkfsh+xfgPf+hH0AC4OlsAAAAASUVORK5CYII="/>
<text id="leftText1" x="435" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="470"></text>
<text id="leftText2" x="435" y="140" transform="scale(.1)" textLength="470"></text>
<text id="rightText1" x="995" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="470"></text>
<text id="rightText1" x="995" y="140" transform="scale(.1)" textLength="470"></text></g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -7,6 +7,7 @@
# p, <user/group>, <resource>, <action>, <object>
p, role:readonly, applications, get, */*, allow
p, role:readonly, certificates, get, *, allow
p, role:readonly, clusters, get, *, allow
p, role:readonly, repositories, get, *, allow
p, role:readonly, projects, get, *, allow
@@ -15,6 +16,10 @@ p, role:admin, applications, create, */*, allow
p, role:admin, applications, update, */*, allow
p, role:admin, applications, delete, */*, allow
p, role:admin, applications, sync, */*, allow
p, role:admin, applications, override, */*, allow
p, role:admin, certificates, create, *, allow
p, role:admin, certificates, update, *, allow
p, role:admin, certificates, delete, *, allow
p, role:admin, clusters, create, *, allow
p, role:admin, clusters, update, *, allow
p, role:admin, clusters, delete, *, allow
1 # Built-in policy which defines two roles: role:readonly and role:admin,
7 # p, <user/group>, <resource>, <action>, <object>
8 p, role:readonly, applications, get, */*, allow
9 p, role:readonly, clusters, get, *, allow p, role:readonly, certificates, get, *, allow
10 p, role:readonly, clusters, get, *, allow
11 p, role:readonly, repositories, get, *, allow
12 p, role:readonly, projects, get, *, allow
13 p, role:admin, applications, create, */*, allow
16 p, role:admin, applications, sync, */*, allow
17 p, role:admin, clusters, create, *, allow p, role:admin, applications, override, */*, allow
18 p, role:admin, clusters, update, *, allow p, role:admin, certificates, create, *, allow
19 p, role:admin, certificates, update, *, allow
20 p, role:admin, certificates, delete, *, allow
21 p, role:admin, clusters, create, *, allow
22 p, role:admin, clusters, update, *, allow
23 p, role:admin, clusters, delete, *, allow
24 p, role:admin, repositories, create, *, allow
25 p, role:admin, repositories, update, *, allow

File diff suppressed because it is too large Load Diff

View File

@@ -16,12 +16,11 @@ import (
// load the oidc plugin (required to authenticate with OpenID Connect).
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/common"
"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/reposerver/apiclient"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/settings"
@@ -37,14 +36,18 @@ const (
func newCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
repoServerAddress string
statusProcessors int
operationProcessors int
logLevel string
glogLevel int
cacheSrc func() (*cache.Cache, error)
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
repoServerAddress string
repoServerTimeoutSeconds int
selfHealTimeoutSeconds int
statusProcessors int
operationProcessors int
logLevel string
glogLevel int
metricsPort int
kubectlParallelismLimit int64
cacheSrc func() (*cache.Cache, error)
)
var command = cobra.Command{
Use: cliName,
@@ -54,9 +57,9 @@ func newCommand() *cobra.Command {
cli.SetGLogLevel(glogLevel)
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
config.QPS = common.K8sClientConfigQPS
config.Burst = common.K8sClientConfigBurst
errors.CheckError(err)
kubeClient := kubernetes.NewForConfigOrDie(config)
appClient := appclientset.NewForConfigOrDie(config)
@@ -65,7 +68,7 @@ func newCommand() *cobra.Command {
errors.CheckError(err)
resyncDuration := time.Duration(appResyncPeriod) * time.Second
repoClientset := reposerver.NewRepoServerClientset(repoServerAddress)
repoClientset := apiclient.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -80,10 +83,13 @@ func newCommand() *cobra.Command {
appClient,
repoClientset,
cache,
resyncDuration)
resyncDuration,
time.Duration(selfHealTimeoutSeconds)*time.Second,
metricsPort,
kubectlParallelismLimit)
errors.CheckError(err)
log.Infof("Application Controller (version: %s) starting (namespace: %s)", argocd.GetVersion(), namespace)
log.Infof("Application Controller (version: %s) starting (namespace: %s)", common.GetVersion(), namespace)
stats.RegisterStackDumper()
stats.StartStatsTicker(10 * time.Minute)
stats.RegisterHeapDumper("memprofile")
@@ -98,10 +104,15 @@ 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", common.DefaultRepoServerAddr, "Repo server address.")
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", 60, "Repo server RPC call timeout seconds.")
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")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDMetrics, "Start metrics server on given port")
command.Flags().IntVar(&selfHealTimeoutSeconds, "self-heal-timeout-seconds", 5, "Specifies timeout between application self heal attempts")
command.Flags().Int64Var(&kubectlParallelismLimit, "kubectl-parallelism-limit", 0, "Number of allowed concurrent kubectl fork/execs.")
cacheSrc = cache.AddCacheFlagsToCmd(&command)
return &command
}

View File

@@ -3,19 +3,21 @@ package main
import (
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/argoproj/argo-cd/reposerver/metrics"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/stats"
"github.com/argoproj/argo-cd/util/tls"
)
@@ -23,13 +25,14 @@ import (
const (
// CLIName is the name of the CLI
cliName = "argocd-repo-server"
port = 8081
)
func newCommand() *cobra.Command {
var (
logLevel string
parallelismLimit int64
listenPort int
metricsPort int
cacheSrc func() (*cache.Cache, error)
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
)
@@ -45,17 +48,18 @@ func newCommand() *cobra.Command {
cache, err := cacheSrc()
errors.CheckError(err)
server, err := reposerver.NewServer(git.NewFactory(), cache, tlsConfigCustomizer, parallelismLimit)
metricsServer := metrics.NewMetricsServer(git.NewFactory())
server, err := reposerver.NewServer(metricsServer, cache, tlsConfigCustomizer, parallelismLimit)
errors.CheckError(err)
grpc := server.CreateGRPC()
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", listenPort))
errors.CheckError(err)
ksVers, err := ksonnet.KsonnetVersion()
errors.CheckError(err)
http.Handle("/metrics", metricsServer.GetHandler())
go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf(":%d", metricsPort), nil)) }()
log.Infof("argocd-repo-server %s serving on %s", argocd.GetVersion(), listener.Addr())
log.Infof("ksonnet version: %s", ksVers)
log.Infof("argocd-repo-server %s serving on %s", common.GetVersion(), listener.Addr())
stats.RegisterStackDumper()
stats.StartStatsTicker(10 * time.Minute)
stats.RegisterHeapDumper("memprofile")
@@ -67,6 +71,8 @@ func newCommand() *cobra.Command {
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
command.Flags().Int64Var(&parallelismLimit, "parallelismlimit", 0, "Limit on number of concurrent manifests generate requests. Any value less the 1 means no limit.")
command.Flags().IntVar(&listenPort, "port", common.DefaultPortRepoServer, "Listen on given port for incoming connections")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = cache.AddCacheFlagsToCmd(&command)
return &command

View File

@@ -11,7 +11,7 @@ import (
"github.com/argoproj/argo-cd/common"
"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/reposerver/apiclient"
"github.com/argoproj/argo-cd/server"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/cli"
@@ -22,17 +22,20 @@ import (
// NewCommand returns a new instance of an argocd command
func NewCommand() *cobra.Command {
var (
insecure bool
logLevel string
glogLevel int
clientConfig clientcmd.ClientConfig
staticAssetsDir string
baseHRef string
repoServerAddress string
dexServerAddress string
disableAuth bool
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
cacheSrc func() (*cache.Cache, error)
insecure bool
listenPort int
metricsPort int
logLevel string
glogLevel int
clientConfig clientcmd.ClientConfig
repoServerTimeoutSeconds int
staticAssetsDir string
baseHRef string
repoServerAddress string
dexServerAddress string
disableAuth bool
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
cacheSrc func() (*cache.Cache, error)
)
var command = &cobra.Command{
Use: cliName,
@@ -57,10 +60,12 @@ func NewCommand() *cobra.Command {
kubeclientset := kubernetes.NewForConfigOrDie(config)
appclientset := appclientset.NewForConfigOrDie(config)
repoclientset := reposerver.NewRepoServerClientset(repoServerAddress)
repoclientset := apiclient.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds)
argoCDOpts := server.ArgoCDServerOpts{
Insecure: insecure,
ListenPort: listenPort,
MetricsPort: metricsPort,
Namespace: namespace,
StaticAssetsDir: staticAssetsDir,
BaseHRef: baseHRef,
@@ -81,7 +86,7 @@ func NewCommand() *cobra.Command {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
argocd := server.NewServer(ctx, argoCDOpts)
argocd.Run(ctx, 8080)
argocd.Run(ctx, listenPort, metricsPort)
cancel()
}
},
@@ -97,6 +102,9 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&dexServerAddress, "dex-server", common.DefaultDexServerAddr, "Dex server address")
command.Flags().BoolVar(&disableAuth, "disable-auth", false, "Disable client authentication")
command.AddCommand(cli.NewVersionCmd(cliName))
command.Flags().IntVar(&listenPort, "port", common.DefaultPortAPIServer, "Listen on given port")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDAPIServerMetrics, "Start metrics on given port")
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", 60, "Repo server RPC call timeout seconds.")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(command)
cacheSrc = cache.AddCacheFlagsToCmd(command)
return command

View File

@@ -1,12 +1,14 @@
package main
import (
"bufio"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"regexp"
"syscall"
"github.com/ghodss/yaml"
@@ -14,13 +16,18 @@ import (
"github.com/spf13/cobra"
apiv1 "k8s.io/api/core/v1"
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/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/util"
"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"
@@ -36,9 +43,15 @@ import (
const (
// CLIName is the name of the CLI
cliName = "argocd-util"
// YamlSeparator separates sections of a YAML file
yamlSeparator = "\n---\n"
yamlSeparator = "---\n"
)
var (
configMapResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
secretResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}
applicationsResource = schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "applications"}
appprojectsResource = schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "appprojects"}
)
// NewCommand returns a new instance of an argocd command
@@ -96,7 +109,7 @@ func NewRunDexCommand() *cobra.Command {
} else {
err = ioutil.WriteFile("/tmp/dex.yaml", dexCfgBytes, 0644)
errors.CheckError(err)
log.Info(string(dexCfgBytes))
log.Info(redactor(string(dexCfgBytes)))
cmd = exec.Command("dex", "serve", "/tmp/dex.yaml")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -177,7 +190,7 @@ func NewGenDexConfigCommand() *cobra.Command {
errors.CheckError(err)
maskedDexCfgBytes, err := yaml.Marshal(dexCfg)
errors.CheckError(err)
fmt.Printf(string(maskedDexCfgBytes))
fmt.Print(string(maskedDexCfgBytes))
} else {
err = ioutil.WriteFile(out, dexCfgBytes, 0644)
errors.CheckError(err)
@@ -195,94 +208,153 @@ func NewGenDexConfigCommand() *cobra.Command {
func NewImportCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
prune bool
dryRun bool
)
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 {
Run: func(c *cobra.Command, args []string) {
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()
config.QPS = 100
config.Burst = 50
errors.CheckError(err)
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
kubeClientset := kubernetes.NewForConfigOrDie(config)
acdClients := newArgoCDClientsets(config, namespace)
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, namespace)
err = settingsMgr.SaveSettings(newSettings)
var input []byte
if in := args[0]; in == "-" {
input, err = ioutil.ReadAll(os.Stdin)
} else {
input, err = ioutil.ReadFile(in)
}
errors.CheckError(err)
db := db.NewDB(namespace, settingsMgr, kubeClientset)
var dryRunMsg string
if dryRun {
dryRunMsg = " (dry run)"
}
_, err = kubeClientset.CoreV1().ConfigMaps(namespace).Create(newRBACCM)
// pruneObjects tracks live objects and it's current resource version. any remaining
// items in this map indicates the resource should be pruned since it no longer appears
// in the backup
pruneObjects := make(map[kube.ResourceKey]string)
configMaps, err := acdClients.configMaps.List(metav1.ListOptions{})
errors.CheckError(err)
for _, cm := range configMaps.Items {
cmName := cm.GetName()
if cmName == common.ArgoCDConfigMapName || cmName == common.ArgoCDRBACConfigMapName {
pruneObjects[kube.ResourceKey{Group: "", Kind: "ConfigMap", Name: cm.GetName()}] = cm.GetResourceVersion()
}
}
secrets, err := acdClients.secrets.List(metav1.ListOptions{})
errors.CheckError(err)
for _, secret := range secrets.Items {
if isArgoCDSecret(nil, secret) {
pruneObjects[kube.ResourceKey{Group: "", Kind: "Secret", Name: secret.GetName()}] = secret.GetResourceVersion()
}
}
applications, err := acdClients.applications.List(metav1.ListOptions{})
errors.CheckError(err)
for _, app := range applications.Items {
pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "Application", Name: app.GetName()}] = app.GetResourceVersion()
}
projects, err := acdClients.projects.List(metav1.ListOptions{})
errors.CheckError(err)
for _, proj := range projects.Items {
pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "AppProject", Name: proj.GetName()}] = proj.GetResourceVersion()
}
for _, repo := range newRepos {
_, err := db.CreateRepository(context.Background(), repo)
if err != nil {
log.Warn(err)
// Create or replace existing object
objs, err := kube.SplitYAML(string(input))
errors.CheckError(err)
for _, obj := range objs {
gvk := obj.GroupVersionKind()
key := kube.ResourceKey{Group: gvk.Group, Kind: gvk.Kind, Name: obj.GetName()}
resourceVersion, exists := pruneObjects[key]
delete(pruneObjects, key)
var dynClient dynamic.ResourceInterface
switch obj.GetKind() {
case "Secret":
dynClient = acdClients.secrets
case "ConfigMap":
dynClient = acdClients.configMaps
case "AppProject":
dynClient = acdClients.projects
case "Application":
dynClient = acdClients.applications
}
if !exists {
if !dryRun {
_, err = dynClient.Create(obj, metav1.CreateOptions{})
errors.CheckError(err)
}
fmt.Printf("%s/%s %s created%s\n", gvk.Group, gvk.Kind, obj.GetName(), dryRunMsg)
} else {
if !dryRun {
obj.SetResourceVersion(resourceVersion)
_, err = dynClient.Update(obj, metav1.UpdateOptions{})
errors.CheckError(err)
}
fmt.Printf("%s/%s %s replaced%s\n", gvk.Group, gvk.Kind, obj.GetName(), dryRunMsg)
}
}
for _, cluster := range newClusters {
_, err := db.CreateCluster(context.Background(), cluster)
if err != nil {
log.Warn(err)
// Delete objects not in backup
for key := range pruneObjects {
if prune {
var dynClient dynamic.ResourceInterface
switch key.Kind {
case "Secret":
dynClient = acdClients.secrets
case "AppProject":
dynClient = acdClients.projects
case "Application":
dynClient = acdClients.applications
default:
log.Fatalf("Unexpected kind '%s' in prune list", key.Kind)
}
if !dryRun {
err = dynClient.Delete(key.Name, &metav1.DeleteOptions{})
errors.CheckError(err)
}
fmt.Printf("%s/%s %s pruned%s\n", key.Group, key.Kind, key.Name, dryRunMsg)
} else {
fmt.Printf("%s/%s %s needs pruning\n", key.Group, key.Kind, key.Name)
}
}
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)
command.Flags().BoolVar(&dryRun, "dry-run", false, "Print what will be performed")
command.Flags().BoolVar(&prune, "prune", false, "Prune secrets, applications and projects which do not appear in the backup")
return &command
}
type argoCDClientsets struct {
configMaps dynamic.ResourceInterface
secrets dynamic.ResourceInterface
applications dynamic.ResourceInterface
projects dynamic.ResourceInterface
}
func newArgoCDClientsets(config *rest.Config, namespace string) *argoCDClientsets {
dynamicIf, err := dynamic.NewForConfig(config)
errors.CheckError(err)
return &argoCDClientsets{
configMaps: dynamicIf.Resource(configMapResource).Namespace(namespace),
secrets: dynamicIf.Resource(secretResource).Namespace(namespace),
applications: dynamicIf.Resource(applicationsResource).Namespace(namespace),
projects: dynamicIf.Resource(appprojectsResource).Namespace(namespace),
}
}
// NewExportCommand defines a new command for exporting Kubernetes and Argo CD resources.
func NewExportCommand() *cobra.Command {
var (
@@ -292,75 +364,54 @@ func NewExportCommand() *cobra.Command {
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 {
Run: func(c *cobra.Command, args []string) {
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
kubeClientset := kubernetes.NewForConfigOrDie(config)
settingsMgr := settings.NewSettingsManager(context.Background(), 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, settingsMgr, kubeClientset)
clusters, err := db.ListClusters(context.Background())
errors.CheckError(err)
repoURLs, err := db.ListRepoURLs(context.Background())
errors.CheckError(err)
repos := make([]*v1alpha1.Repository, len(repoURLs))
for i := range repoURLs {
repo, err := db.GetRepository(context.Background(), repoURLs[i])
errors.CheckError(err)
repos = append(repos, repo)
}
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, clusters.Items, repos, apps.Items, rbacCM)
var writer io.Writer
if out == "-" {
fmt.Println(output)
writer = os.Stdout
} else {
err = ioutil.WriteFile(out, []byte(output), 0644)
f, err := os.Create(out)
errors.CheckError(err)
defer util.Close(f)
writer = bufio.NewWriter(f)
}
acdClients := newArgoCDClientsets(config, namespace)
acdConfigMap, err := acdClients.configMaps.Get(common.ArgoCDConfigMapName, metav1.GetOptions{})
errors.CheckError(err)
export(writer, *acdConfigMap)
acdRBACConfigMap, err := acdClients.configMaps.Get(common.ArgoCDRBACConfigMapName, metav1.GetOptions{})
errors.CheckError(err)
export(writer, *acdRBACConfigMap)
acdKnownHostsConfigMap, err := acdClients.configMaps.Get(common.ArgoCDKnownHostsConfigMapName, metav1.GetOptions{})
errors.CheckError(err)
export(writer, *acdKnownHostsConfigMap)
acdTLSCertsConfigMap, err := acdClients.configMaps.Get(common.ArgoCDTLSCertsConfigMapName, metav1.GetOptions{})
errors.CheckError(err)
export(writer, *acdTLSCertsConfigMap)
referencedSecrets := getReferencedSecrets(*acdConfigMap)
secrets, err := acdClients.secrets.List(metav1.ListOptions{})
errors.CheckError(err)
for _, secret := range secrets.Items {
if isArgoCDSecret(referencedSecrets, secret) {
export(writer, secret)
}
}
projects, err := acdClients.projects.List(metav1.ListOptions{})
errors.CheckError(err)
for _, proj := range projects.Items {
export(writer, proj)
}
applications, err := acdClients.applications.List(metav1.ListOptions{})
errors.CheckError(err)
for _, app := range applications.Items {
export(writer, app)
}
return nil
},
}
@@ -370,13 +421,115 @@ func NewExportCommand() *cobra.Command {
return &command
}
// NewClusterConfig returns a new instance of `argocd-util cluster-kubeconfig` command
// getReferencedSecrets examines the argocd-cm config for any referenced repo secrets and returns a
// map of all referenced secrets.
func getReferencedSecrets(un unstructured.Unstructured) map[string]bool {
var cm apiv1.ConfigMap
err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &cm)
errors.CheckError(err)
referencedSecrets := make(map[string]bool)
if reposRAW, ok := cm.Data["repositories"]; ok {
repoCreds := make([]settings.RepoCredentials, 0)
err := yaml.Unmarshal([]byte(reposRAW), &repoCreds)
errors.CheckError(err)
for _, cred := range repoCreds {
if cred.PasswordSecret != nil {
referencedSecrets[cred.PasswordSecret.Name] = true
}
if cred.SSHPrivateKeySecret != nil {
referencedSecrets[cred.SSHPrivateKeySecret.Name] = true
}
if cred.UsernameSecret != nil {
referencedSecrets[cred.UsernameSecret.Name] = true
}
if cred.TLSClientCertDataSecret != nil {
referencedSecrets[cred.TLSClientCertDataSecret.Name] = true
}
if cred.TLSClientCertKeySecret != nil {
referencedSecrets[cred.TLSClientCertKeySecret.Name] = true
}
}
}
if helmReposRAW, ok := cm.Data["helm.repositories"]; ok {
helmRepoCreds := make([]settings.HelmRepoCredentials, 0)
err := yaml.Unmarshal([]byte(helmReposRAW), &helmRepoCreds)
errors.CheckError(err)
for _, cred := range helmRepoCreds {
if cred.CASecret != nil {
referencedSecrets[cred.CASecret.Name] = true
}
if cred.CertSecret != nil {
referencedSecrets[cred.CertSecret.Name] = true
}
if cred.KeySecret != nil {
referencedSecrets[cred.KeySecret.Name] = true
}
if cred.UsernameSecret != nil {
referencedSecrets[cred.UsernameSecret.Name] = true
}
if cred.PasswordSecret != nil {
referencedSecrets[cred.PasswordSecret.Name] = true
}
}
}
return referencedSecrets
}
// isArgoCDSecret returns whether or not the given secret is a part of Argo CD configuration
// (e.g. argocd-secret, repo credentials, or cluster credentials)
func isArgoCDSecret(repoSecretRefs map[string]bool, un unstructured.Unstructured) bool {
secretName := un.GetName()
if secretName == common.ArgoCDSecretName {
return true
}
if repoSecretRefs != nil {
if _, ok := repoSecretRefs[secretName]; ok {
return true
}
}
if labels := un.GetLabels(); labels != nil {
if _, ok := labels[common.LabelKeySecretType]; ok {
return true
}
}
if annotations := un.GetAnnotations(); annotations != nil {
if annotations[common.AnnotationKeyManagedBy] == common.AnnotationValueManagedByArgoCD {
return true
}
}
return false
}
// export writes the unstructured object and removes extraneous cruft from output before writing
func export(w io.Writer, un unstructured.Unstructured) {
name := un.GetName()
finalizers := un.GetFinalizers()
apiVersion := un.GetAPIVersion()
kind := un.GetKind()
labels := un.GetLabels()
annotations := un.GetAnnotations()
unstructured.RemoveNestedField(un.Object, "metadata")
un.SetName(name)
un.SetFinalizers(finalizers)
un.SetAPIVersion(apiVersion)
un.SetKind(kind)
un.SetLabels(labels)
un.SetAnnotations(annotations)
data, err := yaml.Marshal(un.Object)
errors.CheckError(err)
_, err = w.Write(data)
errors.CheckError(err)
_, err = w.Write([]byte(yamlSeparator))
errors.CheckError(err)
}
// NewClusterConfig returns a new instance of `argocd-util kubeconfig` command
func NewClusterConfig() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
)
var command = &cobra.Command{
Use: "cluster-kubeconfig CLUSTER_URL OUTPUT_PATH",
Use: "kubeconfig CLUSTER_URL OUTPUT_PATH",
Short: "Generates kubeconfig for the specified cluster",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
@@ -387,12 +540,8 @@ func NewClusterConfig() *cobra.Command {
output := args[1]
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, wasSpecified, err := clientConfig.Namespace()
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
if !(wasSpecified) {
namespace = "argocd"
}
kubeclientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
@@ -406,6 +555,11 @@ func NewClusterConfig() *cobra.Command {
return command
}
func redactor(dirtyString string) string {
dirtyString = regexp.MustCompile("(clientSecret: )[^ \n]*").ReplaceAllString(dirtyString, "$1********")
return regexp.MustCompile("(secret: )[^ \n]*").ReplaceAllString(dirtyString, "$1********")
}
func main() {
if err := NewCommand().Execute(); err != nil {
fmt.Println(err)

View File

@@ -0,0 +1,73 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
var textToRedact = `
- config:
clientID: aabbccddeeff00112233
clientSecret: $dex.github.clientSecret
orgs:
- name: your-github-org
redirectURI: https://argocd.example.com/api/dex/callback
id: github
name: GitHub
type: github
grpc:
addr: 0.0.0.0:5557
issuer: https://argocd.example.com/api/dex
oauth2:
skipApprovalScreen: true
staticClients:
- id: argo-cd
name: Argo CD
redirectURIs:
- https://argocd.example.com/auth/callback
secret: Dis9M-GA11oTwZVQQWdDklPQw-sWXZkWJFyyEhMs
- id: argo-cd-cli
name: Argo CD CLI
public: true
redirectURIs:
- http://localhost
storage:
type: memory
web:
http: 0.0.0.0:5556`
var expectedRedaction = `
- config:
clientID: aabbccddeeff00112233
clientSecret: ********
orgs:
- name: your-github-org
redirectURI: https://argocd.example.com/api/dex/callback
id: github
name: GitHub
type: github
grpc:
addr: 0.0.0.0:5557
issuer: https://argocd.example.com/api/dex
oauth2:
skipApprovalScreen: true
staticClients:
- id: argo-cd
name: Argo CD
redirectURIs:
- https://argocd.example.com/auth/callback
secret: ********
- id: argo-cd-cli
name: Argo CD CLI
public: true
redirectURIs:
- http://localhost
storage:
type: memory
web:
http: 0.0.0.0:5556`
func TestSecretsRedactor(t *testing.T) {
assert.Equal(t, expectedRedaction, redactor(textToRedact))
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/server/account"
accountpkg "github.com/argoproj/argo-cd/pkg/apiclient/account"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/localconfig"
@@ -57,7 +57,7 @@ func NewAccountUpdatePasswordCommand(clientOpts *argocdclient.ClientOptions) *co
errors.CheckError(err)
}
updatePasswordRequest := account.UpdatePasswordRequest{
updatePasswordRequest := accountpkg.UpdatePasswordRequest{
NewPassword: newPassword,
CurrentPassword: currentPassword,
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
package commands
import (
"context"
"fmt"
"os"
"sort"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
applicationpkg "github.com/argoproj/argo-cd/pkg/apiclient/application"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
)
// NewApplicationResourceActionsCommand returns a new instance of an `argocd app actions` command
func NewApplicationResourceActionsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "actions",
Short: "Manage Resource actions",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
}
command.AddCommand(NewApplicationResourceActionsListCommand(clientOpts))
command.AddCommand(NewApplicationResourceActionsRunCommand(clientOpts))
return command
}
// NewApplicationResourceActionsListCommand returns a new instance of an `argocd app actions list` command
func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var namespace string
var kind string
var group string
var resourceName string
var all bool
var command = &cobra.Command{
Use: "list APPNAME",
Short: "Lists available actions on a resource",
}
command.Run = func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
ctx := context.Background()
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
errors.CheckError(err)
filteredObjects := filterResources(command, resources.Items, group, kind, namespace, resourceName, all)
availableActions := make(map[string][]argoappv1.ResourceAction)
for i := range filteredObjects {
obj := filteredObjects[i]
gvk := obj.GroupVersionKind()
availActionsForResource, err := appIf.ListResourceActions(ctx, &applicationpkg.ApplicationResourceRequest{
Name: &appName,
Namespace: obj.GetNamespace(),
ResourceName: obj.GetName(),
Group: gvk.Group,
Kind: gvk.Kind,
})
errors.CheckError(err)
availableActions[obj.GetName()] = availActionsForResource.Actions
}
var keys []string
for key := range availableActions {
keys = append(keys, key)
}
sort.Strings(keys)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "RESOURCE\tACTION\n")
fmt.Println()
for key := range availableActions {
for i := range availableActions[key] {
action := availableActions[key][i]
fmt.Fprintf(w, "%s\t%s\n", key, action.Name)
}
}
_ = w.Flush()
}
command.Flags().StringVar(&resourceName, "resource-name", "", "Name of resource")
command.Flags().StringVar(&kind, "kind", "", "Kind")
err := command.MarkFlagRequired("kind")
errors.CheckError(err)
command.Flags().StringVar(&group, "group", "", "Group")
command.Flags().StringVar(&namespace, "namespace", "", "Namespace")
command.Flags().BoolVar(&all, "all", false, "Indicates whether to list actions on multiple matching resources")
return command
}
// NewApplicationResourceActionsRunCommand returns a new instance of an `argocd app actions run` command
func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var namespace string
var kind string
var group string
var resourceName string
var all bool
var command = &cobra.Command{
Use: "run APPNAME ACTION",
Short: "Runs an available action on resource(s)",
}
command.Flags().StringVar(&resourceName, "resource-name", "", "Name of resource")
command.Flags().StringVar(&kind, "kind", "", "Kind")
err := command.MarkFlagRequired("kind")
errors.CheckError(err)
command.Flags().StringVar(&group, "group", "", "Group")
command.Flags().StringVar(&namespace, "namespace", "", "Namespace")
command.Flags().BoolVar(&all, "all", false, "Indicates whether to run the action on multiple matching resources")
command.Run = func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
actionName := args[1]
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
ctx := context.Background()
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
errors.CheckError(err)
filteredObjects := filterResources(command, resources.Items, group, kind, namespace, resourceName, all)
for i := range filteredObjects {
obj := filteredObjects[i]
gvk := obj.GroupVersionKind()
objResourceName := obj.GetName()
_, err := appIf.RunResourceAction(context.Background(), &applicationpkg.ResourceActionRunRequest{
Name: &appName,
Namespace: obj.GetNamespace(),
ResourceName: objResourceName,
Group: gvk.Group,
Kind: gvk.Kind,
Action: actionName,
})
errors.CheckError(err)
}
}
return command
}

View File

@@ -0,0 +1,25 @@
package commands
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseLabels(t *testing.T) {
validLabels := []string{"key=value", "foo=bar", "intuit=inc"}
result, err := parseLabels(validLabels)
assert.NoError(t, err)
assert.Len(t, result, 3)
invalidLabels := []string{"key=value", "too=many=equals"}
_, err = parseLabels(invalidLabels)
assert.Error(t, err)
emptyLabels := []string{}
result, err = parseLabels(emptyLabels)
assert.NoError(t, err)
assert.Len(t, result, 0)
}

290
cmd/argocd/commands/cert.go Normal file
View File

@@ -0,0 +1,290 @@
package commands
import (
"context"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
certificatepkg "github.com/argoproj/argo-cd/pkg/apiclient/certificate"
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
certutil "github.com/argoproj/argo-cd/util/cert"
"crypto/x509"
)
// NewCertCommand returns a new instance of an `argocd repo` command
func NewCertCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "cert",
Short: "Manage repository certificates and SSH known hosts entries",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
}
command.AddCommand(NewCertAddSSHCommand(clientOpts))
command.AddCommand(NewCertAddTLSCommand(clientOpts))
command.AddCommand(NewCertListCommand(clientOpts))
command.AddCommand(NewCertRemoveCommand(clientOpts))
return command
}
func NewCertAddTLSCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
fromFile string
upsert bool
)
var command = &cobra.Command{
Use: "add-tls SERVERNAME",
Short: "Add TLS certificate data for connecting to repository server SERVERNAME",
Run: func(c *cobra.Command, args []string) {
conn, certIf := argocdclient.NewClientOrDie(clientOpts).NewCertClientOrDie()
defer util.Close(conn)
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
var certificateArray []string
var err error
if fromFile != "" {
fmt.Printf("Reading TLS certificate data in PEM format from '%s'\n", fromFile)
certificateArray, err = certutil.ParseTLSCertificatesFromPath(fromFile)
} else {
fmt.Println("Enter TLS certificate data in PEM format. Press CTRL-D when finished.")
certificateArray, err = certutil.ParseTLSCertificatesFromStream(os.Stdin)
}
errors.CheckError(err)
certificateList := make([]appsv1.RepositoryCertificate, 0)
subjectMap := make(map[string]*x509.Certificate)
for _, entry := range certificateArray {
// We want to make sure to only send valid certificate data to the
// server, so we decode the certificate into X509 structure before
// further processing it.
x509cert, err := certutil.DecodePEMCertificateToX509(entry)
errors.CheckError(err)
// TODO: We need a better way to detect duplicates sent in the stream,
// maybe by using fingerprints? For now, no two certs with the same
// subject may be sent.
if subjectMap[x509cert.Subject.String()] != nil {
fmt.Printf("ERROR: Cert with subject '%s' already seen in the input stream.\n", x509cert.Subject.String())
continue
} else {
subjectMap[x509cert.Subject.String()] = x509cert
}
}
serverName := args[0]
if len(certificateArray) > 0 {
certificateList = append(certificateList, appsv1.RepositoryCertificate{
ServerName: serverName,
CertType: "https",
CertData: []byte(strings.Join(certificateArray, "\n")),
})
certificates, err := certIf.CreateCertificate(context.Background(), &certificatepkg.RepositoryCertificateCreateRequest{
Certificates: &appsv1.RepositoryCertificateList{
Items: certificateList,
},
Upsert: upsert,
})
errors.CheckError(err)
fmt.Printf("Created entry with %d PEM certificates for repository server %s\n", len(certificates.Items), serverName)
} else {
fmt.Printf("No valid certificates have been detected in the stream.\n")
}
},
}
command.Flags().StringVar(&fromFile, "from", "", "read TLS certificate data from file (default is to read from stdin)")
command.Flags().BoolVar(&upsert, "upsert", false, "Replace existing TLS certificate if certificate is different in input")
return command
}
// NewCertAddCommand returns a new instance of an `argocd cert add` command
func NewCertAddSSHCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
fromFile string
batchProcess bool
upsert bool
certificates []appsv1.RepositoryCertificate
)
var command = &cobra.Command{
Use: "add-ssh --batch",
Short: "Add SSH known host entries for repository servers",
Run: func(c *cobra.Command, args []string) {
conn, certIf := argocdclient.NewClientOrDie(clientOpts).NewCertClientOrDie()
defer util.Close(conn)
var sshKnownHostsLists []string
var err error
// --batch is a flag, but it is mandatory for now.
if batchProcess {
if fromFile != "" {
fmt.Printf("Reading SSH known hosts entries from file '%s'\n", fromFile)
sshKnownHostsLists, err = certutil.ParseSSHKnownHostsFromPath(fromFile)
} else {
fmt.Println("Enter SSH known hosts entries, one per line. Press CTRL-D when finished.")
sshKnownHostsLists, err = certutil.ParseSSHKnownHostsFromStream(os.Stdin)
}
} else {
err = fmt.Errorf("You need to specify --batch or specify --help for usage instructions")
}
errors.CheckError(err)
if len(sshKnownHostsLists) == 0 {
errors.CheckError(fmt.Errorf("No valid SSH known hosts data found."))
}
for _, knownHostsEntry := range sshKnownHostsLists {
hostname, certSubType, certData, err := certutil.TokenizeSSHKnownHostsEntry(knownHostsEntry)
errors.CheckError(err)
_, _, err = certutil.KnownHostsLineToPublicKey(knownHostsEntry)
errors.CheckError(err)
certificate := appsv1.RepositoryCertificate{
ServerName: hostname,
CertType: "ssh",
CertSubType: certSubType,
CertData: certData,
}
certificates = append(certificates, certificate)
}
certList := &appsv1.RepositoryCertificateList{Items: certificates}
response, err := certIf.CreateCertificate(context.Background(), &certificatepkg.RepositoryCertificateCreateRequest{
Certificates: certList,
Upsert: upsert,
})
errors.CheckError(err)
fmt.Printf("Successfully created %d SSH known host entries\n", len(response.Items))
},
}
command.Flags().StringVar(&fromFile, "from", "", "Read SSH known hosts data from file (default is to read from stdin)")
command.Flags().BoolVar(&batchProcess, "batch", false, "Perform batch processing by reading in SSH known hosts data (mandatory flag)")
command.Flags().BoolVar(&upsert, "upsert", false, "Replace existing SSH server public host keys if key is different in input")
return command
}
// NewCertRemoveCommand returns a new instance of an `argocd cert rm` command
func NewCertRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
certType string
certSubType string
certQuery certificatepkg.RepositoryCertificateQuery
)
var command = &cobra.Command{
Use: "rm REPOSERVER",
Short: "Remove certificate of TYPE for REPOSERVER",
Run: func(c *cobra.Command, args []string) {
if len(args) < 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, certIf := argocdclient.NewClientOrDie(clientOpts).NewCertClientOrDie()
defer util.Close(conn)
hostNamePattern := args[0]
// Prevent the user from specifying a wildcard as hostname as precaution
// measure -- the user could still use "?*" or any other pattern to
// remove all certificates, but it's less likely that it happens by
// accident.
if hostNamePattern == "*" {
err := fmt.Errorf("A single wildcard is not allowed as REPOSERVER name.")
errors.CheckError(err)
}
certQuery = certificatepkg.RepositoryCertificateQuery{
HostNamePattern: hostNamePattern,
CertType: certType,
CertSubType: certSubType,
}
removed, err := certIf.DeleteCertificate(context.Background(), &certQuery)
errors.CheckError(err)
if len(removed.Items) > 0 {
for _, cert := range removed.Items {
fmt.Printf("Removed cert for '%s' of type '%s' (subtype '%s')\n", cert.ServerName, cert.CertType, cert.CertSubType)
}
} else {
fmt.Println("No certificates were removed (none matched the given patterns)")
}
},
}
command.Flags().StringVar(&certType, "cert-type", "", "Only remove certs of given type (ssh, https)")
command.Flags().StringVar(&certSubType, "cert-sub-type", "", "Only remove certs of given sub-type (only for ssh)")
return command
}
// NewCertListCommand returns a new instance of an `argocd cert rm` command
func NewCertListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
certType string
hostNamePattern string
sortOrder string
)
var command = &cobra.Command{
Use: "list",
Short: "List configured certificates",
Run: func(c *cobra.Command, args []string) {
if certType != "" {
switch certType {
case "ssh":
case "https":
default:
fmt.Println("cert-type must be either ssh or https")
os.Exit(1)
}
}
conn, certIf := argocdclient.NewClientOrDie(clientOpts).NewCertClientOrDie()
defer util.Close(conn)
certificates, err := certIf.ListCertificates(context.Background(), &certificatepkg.RepositoryCertificateQuery{HostNamePattern: hostNamePattern, CertType: certType})
errors.CheckError(err)
printCertTable(certificates.Items, sortOrder)
},
}
command.Flags().StringVar(&sortOrder, "sort", "", "set display sort order, valid: 'hostname', 'type'")
command.Flags().StringVar(&certType, "cert-type", "", "only list certificates of given type, valid: 'ssh','https'")
command.Flags().StringVar(&hostNamePattern, "hostname-pattern", "", "only list certificates for hosts matching given glob-pattern")
return command
}
// Print table of certificate info
func printCertTable(certs []appsv1.RepositoryCertificate, sortOrder string) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "HOSTNAME\tTYPE\tSUBTYPE\tINFO\n")
if sortOrder == "hostname" || sortOrder == "" {
sort.Slice(certs, func(i, j int) bool {
return certs[i].ServerName < certs[j].ServerName
})
} else if sortOrder == "type" {
sort.Slice(certs, func(i, j int) bool {
return certs[i].CertType < certs[j].CertType
})
}
for _, c := range certs {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", c.ServerName, c.CertType, c.CertSubType, c.CertInfo)
}
_ = w.Flush()
}

View File

@@ -19,9 +19,10 @@ import (
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
clusterpkg "github.com/argoproj/argo-cd/pkg/apiclient/cluster"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/server/cluster"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/clusterauth"
)
// NewClusterCommand returns a new instance of an `argocd cluster` command
@@ -39,16 +40,18 @@ func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientc
command.AddCommand(NewClusterGetCommand(clientOpts))
command.AddCommand(NewClusterListCommand(clientOpts))
command.AddCommand(NewClusterRemoveCommand(clientOpts))
command.AddCommand(NewClusterRotateAuthCommand(clientOpts))
return command
}
// 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
awsRoleArn string
awsClusterName string
inCluster bool
upsert bool
awsRoleArn string
awsClusterName string
systemNamespace string
)
var command = &cobra.Command{
Use: "add",
@@ -85,7 +88,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
// Install RBAC resources for managing the cluster
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
managerBearerToken, err = common.InstallClusterManagerRBAC(clientset)
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, systemNamespace)
errors.CheckError(err)
}
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
@@ -94,7 +97,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
if inCluster {
clst.Server = common.KubernetesInternalAPIServerAddr
}
clstCreateReq := cluster.ClusterCreateRequest{
clstCreateReq := clusterpkg.ClusterCreateRequest{
Cluster: clst,
Upsert: upsert,
}
@@ -108,6 +111,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing cluster with the same name even if the spec differs")
command.Flags().StringVar(&awsClusterName, "aws-cluster-name", "", "AWS Cluster name if set then aws-iam-authenticator will be used to access cluster")
command.Flags().StringVar(&awsRoleArn, "aws-role-arn", "", "Optional AWS role arn. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.")
command.Flags().StringVar(&systemNamespace, "system-namespace", common.DefaultSystemNamespace, "Use different system namespace")
return command
}
@@ -154,20 +158,8 @@ func NewCluster(name string, conf *rest.Config, managerBearerToken string, awsAu
tlsClientConfig := argoappv1.TLSClientConfig{
Insecure: conf.TLSClientConfig.Insecure,
ServerName: conf.TLSClientConfig.ServerName,
CertData: conf.TLSClientConfig.CertData,
KeyData: conf.TLSClientConfig.KeyData,
CAData: conf.TLSClientConfig.CAData,
}
if len(conf.TLSClientConfig.CertData) == 0 && conf.TLSClientConfig.CertFile != "" {
data, err := ioutil.ReadFile(conf.TLSClientConfig.CertFile)
errors.CheckError(err)
tlsClientConfig.CertData = data
}
if len(conf.TLSClientConfig.KeyData) == 0 && conf.TLSClientConfig.KeyFile != "" {
data, err := ioutil.ReadFile(conf.TLSClientConfig.KeyFile)
errors.CheckError(err)
tlsClientConfig.KeyData = data
}
if len(conf.TLSClientConfig.CAData) == 0 && conf.TLSClientConfig.CAFile != "" {
data, err := ioutil.ReadFile(conf.TLSClientConfig.CAFile)
errors.CheckError(err)
@@ -188,7 +180,7 @@ func NewCluster(name string, conf *rest.Config, managerBearerToken string, awsAu
// NewClusterGetCommand returns a new instance of an `argocd cluster get` command
func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "get",
Use: "get CLUSTER",
Short: "Get cluster information",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
@@ -198,7 +190,7 @@ func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
for _, clusterName := range args {
clst, err := clusterIf.Get(context.Background(), &cluster.ClusterQuery{Server: clusterName})
clst, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Server: clusterName})
errors.CheckError(err)
yamlBytes, err := yaml.Marshal(clst)
errors.CheckError(err)
@@ -212,7 +204,7 @@ func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
// NewClusterRemoveCommand returns a new instance of an `argocd cluster list` command
func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rm",
Use: "rm CLUSTER",
Short: "Remove cluster credentials",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
@@ -227,9 +219,9 @@ func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
for _, clusterName := range args {
// TODO(jessesuen): find the right context and remove manager RBAC artifacts
// err := common.UninstallClusterManagerRBAC(clientset)
// err := clusterauth.UninstallClusterManagerRBAC(clientset)
// errors.CheckError(err)
_, err := clusterIf.Delete(context.Background(), &cluster.ClusterQuery{Server: clusterName})
_, err := clusterIf.Delete(context.Background(), &clusterpkg.ClusterQuery{Server: clusterName})
errors.CheckError(err)
}
},
@@ -237,22 +229,65 @@ func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
return command
}
// Print table of cluster information
func printClusterTable(clusters []argoappv1.Cluster) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "SERVER\tNAME\tSTATUS\tMESSAGE\n")
for _, c := range clusters {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", c.Server, c.Name, c.ConnectionState.Status, c.ConnectionState.Message)
}
_ = w.Flush()
}
// Print list of cluster servers
func printClusterServers(clusters []argoappv1.Cluster) {
for _, c := range clusters {
fmt.Println(c.Server)
}
}
// NewClusterListCommand returns a new instance of an `argocd cluster rm` command
func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
)
var command = &cobra.Command{
Use: "list",
Short: "List configured clusters",
Run: func(c *cobra.Command, args []string) {
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
clusters, err := clusterIf.List(context.Background(), &cluster.ClusterQuery{})
clusters, err := clusterIf.List(context.Background(), &clusterpkg.ClusterQuery{})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "SERVER\tNAME\tSTATUS\tMESSAGE\n")
for _, c := range clusters.Items {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", c.Server, c.Name, c.ConnectionState.Status, c.ConnectionState.Message)
if output == "server" {
printClusterServers(clusters.Items)
} else {
printClusterTable(clusters.Items)
}
_ = w.Flush()
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|server")
return command
}
// NewClusterRotateAuthCommand returns a new instance of an `argocd cluster rotate-auth` command
func NewClusterRotateAuthCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rotate-auth CLUSTER",
Short: fmt.Sprintf("%s cluster rotate-auth CLUSTER", cliName),
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
clusterQuery := clusterpkg.ClusterQuery{
Server: args[0],
}
_, err := clusterIf.RotateAuth(context.Background(), &clusterQuery)
errors.CheckError(err)
fmt.Printf("Cluster '%s' rotated auth\n", clusterQuery.Server)
},
}
return command

View File

@@ -0,0 +1,233 @@
package commands
import (
"fmt"
"io"
"log"
"os"
"github.com/spf13/cobra"
)
const (
bashCompletionFunc = `
__argocd_list_apps() {
local -a argocd_out
if argocd_out=($(argocd app list --output name 2>/dev/null)); then
COMPREPLY+=( $( compgen -W "${argocd_out[*]}" -- "$cur" ) )
fi
}
__argocd_list_app_history() {
local app=$1
local -a argocd_out
if argocd_out=($(argocd app history $app --output id 2>/dev/null)); then
COMPREPLY+=( $( compgen -W "${argocd_out[*]}" -- "$cur" ) )
fi
}
__argocd_app_rollback() {
local -a command
for comp_word in "${COMP_WORDS[@]}"; do
if [[ $comp_word =~ ^-.*$ ]]; then
continue
fi
command+=($comp_word)
done
# fourth arg is app (if present): e.g.- argocd app rollback guestbook
local app=${command[3]}
local id=${command[4]}
if [[ -z $app || $app == $cur ]]; then
__argocd_list_apps
elif [[ -z $id || $id == $cur ]]; then
__argocd_list_app_history $app
fi
}
__argocd_list_servers() {
local -a argocd_out
if argocd_out=($(argocd cluster list --output server 2>/dev/null)); then
COMPREPLY+=( $( compgen -W "${argocd_out[*]}" -- "$cur" ) )
fi
}
__argocd_list_repos() {
local -a argocd_out
if argocd_out=($(argocd repo list --output url 2>/dev/null)); then
COMPREPLY+=( $( compgen -W "${argocd_out[*]}" -- "$cur" ) )
fi
}
__argocd_list_projects() {
local -a argocd_out
if argocd_out=($(argocd proj list --output name 2>/dev/null)); then
COMPREPLY+=( $( compgen -W "${argocd_out[*]}" -- "$cur" ) )
fi
}
__argocd_list_namespaces() {
local -a argocd_out
if argocd_out=($(kubectl get namespaces --no-headers 2>/dev/null | cut -f1 -d' ' 2>/dev/null)); then
COMPREPLY+=( $( compgen -W "${argocd_out[*]}" -- "$cur" ) )
fi
}
__argocd_proj_server_namespace() {
local -a command
for comp_word in "${COMP_WORDS[@]}"; do
if [[ $comp_word =~ ^-.*$ ]]; then
continue
fi
command+=($comp_word)
done
# expect something like this: argocd proj add-destination PROJECT SERVER NAMESPACE
local project=${command[3]}
local server=${command[4]}
local namespace=${command[5]}
if [[ -z $project || $project == $cur ]]; then
__argocd_list_projects
elif [[ -z $server || $server == $cur ]]; then
__argocd_list_servers
elif [[ -z $namespace || $namespace == $cur ]]; then
__argocd_list_namespaces
fi
}
__argocd_list_project_role() {
local project="$1"
local -a argocd_out
if argocd_out=($(argocd proj role list "$project" --output=name 2>/dev/null)); then
COMPREPLY+=( $( compgen -W "${argocd_out[*]}" -- "$cur" ) )
fi
}
__argocd_proj_role(){
local -a command
for comp_word in "${COMP_WORDS[@]}"; do
if [[ $comp_word =~ ^-.*$ ]]; then
continue
fi
command+=($comp_word)
done
# expect something like this: argocd proj role add-policy PROJECT ROLE-NAME
local project=${command[4]}
local role=${command[5]}
if [[ -z $project || $project == $cur ]]; then
__argocd_list_projects
elif [[ -z $role || $role == $cur ]]; then
__argocd_list_project_role $project
fi
}
__argocd_custom_func() {
case ${last_command} in
argocd_app_delete | \
argocd_app_diff | \
argocd_app_edit | \
argocd_app_get | \
argocd_app_history | \
argocd_app_manifests | \
argocd_app_patch-resource | \
argocd_app_set | \
argocd_app_sync | \
argocd_app_terminate-op | \
argocd_app_unset | \
argocd_app_wait | \
argocd_app_create)
__argocd_list_apps
return
;;
argocd_app_rollback)
__argocd_app_rollback
return
;;
argocd_cluster_get | \
argocd_cluster_rm | \
argocd_login | \
argocd_cluster_add)
__argocd_list_servers
return
;;
argocd_repo_rm | \
argocd_repo_add)
__argocd_list_repos
return
;;
argocd_proj_add-destination | \
argocd_proj_remove-destination)
__argocd_proj_server_namespace
return
;;
argocd_proj_add-source | \
argocd_proj_remove-source | \
argocd_proj_allow-cluster-resource | \
argocd_proj_allow-namespace-resource | \
argocd_proj_deny-cluster-resource | \
argocd_proj_deny-namespace-resource | \
argocd_proj_delete | \
argocd_proj_edit | \
argocd_proj_get | \
argocd_proj_set | \
argocd_proj_role_list)
__argocd_list_projects
return
;;
argocd_proj_role_remove-policy | \
argocd_proj_role_add-policy | \
argocd_proj_role_create | \
argocd_proj_role_delete | \
argocd_proj_role_get | \
argocd_proj_role_create-token | \
argocd_proj_role_delete-token)
__argocd_proj_role
return
;;
*)
;;
esac
}
`
)
func NewCompletionCommand() *cobra.Command {
var command = &cobra.Command{
Use: "completion SHELL",
Short: "output shell completion code for the specified shell (bash or zsh)",
Long: `Write bash or zsh shell completion code to standard output.
For bash, ensure you have bash completions installed and enabled.
To access completions in your current shell, run
$ source <(argocd completion bash)
Alternatively, write it to a file and source in .bash_profile
For zsh, output to a file in a directory referenced by the $fpath shell
variable.
`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
cmd.HelpFunc()(cmd, args)
os.Exit(1)
}
shell := args[0]
rootCommand := NewCommand()
rootCommand.BashCompletionFunction = bashCompletionFunc
availableCompletions := map[string]func(io.Writer) error{
"bash": rootCommand.GenBashCompletion,
"zsh": rootCommand.GenZshCompletion,
}
completion, ok := availableCompletions[shell]
if !ok {
fmt.Printf("Invalid shell '%s'. The supported shells are bash and zsh.\n", shell)
os.Exit(1)
}
if err := completion(os.Stdout); err != nil {
log.Fatal(err)
}
},
}
return command
}

View File

@@ -8,6 +8,8 @@ import (
"strings"
"text/tabwriter"
"github.com/spf13/pflag"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -18,16 +20,36 @@ import (
// NewContextCommand returns a new instance of an `argocd ctx` command
func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var delete bool
var command = &cobra.Command{
Use: "context",
Aliases: []string{"ctx"},
Short: "Switch between contexts",
Run: func(c *cobra.Command, args []string) {
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)
deletePresentContext := false
c.Flags().Visit(func(f *pflag.Flag) {
if f.Name == "delete" {
deletePresentContext = true
}
})
if len(args) == 0 {
printArgoCDContexts(clientOpts.ConfigPath)
return
if deletePresentContext {
err := deleteContext(localCfg.CurrentContext, clientOpts.ConfigPath)
errors.CheckError(err)
return
} else {
printArgoCDContexts(clientOpts.ConfigPath)
return
}
}
ctxName := args[0]
argoCDDir, err := localconfig.DefaultConfigDir()
errors.CheckError(err)
prevCtxFile := path.Join(argoCDDir, ".prev-ctx")
@@ -37,8 +59,6 @@ func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
errors.CheckError(err)
ctxName = string(prevCtxBytes)
}
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)
if localCfg.CurrentContext == ctxName {
fmt.Printf("Already at context '%s'\n", localCfg.CurrentContext)
return
@@ -48,6 +68,7 @@ func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
}
prevCtx := localCfg.CurrentContext
localCfg.CurrentContext = ctxName
err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
errors.CheckError(err)
err = ioutil.WriteFile(prevCtxFile, []byte(prevCtx), 0644)
@@ -55,9 +76,43 @@ func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
fmt.Printf("Switched to context '%s'\n", localCfg.CurrentContext)
},
}
command.Flags().BoolVar(&delete, "delete", false, "Delete the context instead of switching to it")
return command
}
func deleteContext(context, configPath string) error {
localCfg, err := localconfig.ReadLocalConfig(configPath)
errors.CheckError(err)
if localCfg == nil {
return fmt.Errorf("Nothing to logout from")
}
serverName, ok := localCfg.RemoveContext(context)
if !ok {
return fmt.Errorf("Context %s does not exist", context)
}
_ = localCfg.RemoveUser(context)
_ = localCfg.RemoveServer(serverName)
if localCfg.IsEmpty() {
err = localconfig.DeleteLocalConfig(configPath)
errors.CheckError(err)
} else {
if localCfg.CurrentContext == context {
localCfg.CurrentContext = localCfg.Contexts[0].Name
}
err = localconfig.ValidateLocalConfig(*localCfg)
if err != nil {
return fmt.Errorf("Error in logging out")
}
err = localconfig.WriteLocalConfig(*localCfg, configPath)
errors.CheckError(err)
}
fmt.Printf("Context '%s' deleted\n", context)
return nil
}
func printArgoCDContexts(configPath string) {
localCfg, err := localconfig.ReadLocalConfig(configPath)
errors.CheckError(err)

View File

@@ -0,0 +1,60 @@
package commands
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/argoproj/argo-cd/util/localconfig"
)
const testConfig = `contexts:
- name: argocd.example.com:443
server: argocd.example.com:443
user: argocd.example.com:443
- name: localhost:8080
server: localhost:8080
user: localhost:8080
current-context: localhost:8080
servers:
- server: argocd.example.com:443
- plain-text: true
server: localhost:8080
users:
- auth-token: vErrYS3c3tReFRe$hToken
name: argocd.example.com:443
refresh-token: vErrYS3c3tReFRe$hToken
- auth-token: vErrYS3c3tReFRe$hToken
name: localhost:8080`
const testConfigFilePath = "./testdata/config"
func TestContextDelete(t *testing.T) {
// Write the test config file
err := ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
assert.NoError(t, err)
localConfig, err := localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
err = deleteContext("localhost:8080", testConfigFilePath)
assert.NoError(t, err)
localConfig, err = localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "argocd.example.com:443")
assert.NotContains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
assert.NotContains(t, localConfig.Servers, localconfig.Server{PlainText: true, Server: "localhost:8080"})
assert.NotContains(t, localConfig.Users, localconfig.User{AuthToken: "vErrYS3c3tReFRe$hToken", Name: "localhost:8080"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd.example.com:443", Server: "argocd.example.com:443", User: "argocd.example.com:443"})
// Write the file again so that no conflicts are made in git
err = ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
assert.NoError(t, err)
}

View File

@@ -8,8 +8,8 @@ import (
"strconv"
"time"
oidc "github.com/coreos/go-oidc"
jwt "github.com/dgrijalva/jwt-go"
"github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
"github.com/skratchdot/open-golang/open"
"github.com/spf13/cobra"
@@ -17,8 +17,8 @@ import (
"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"
sessionpkg "github.com/argoproj/argo-cd/pkg/apiclient/session"
settingspkg "github.com/argoproj/argo-cd/pkg/apiclient/settings"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
@@ -88,7 +88,7 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
httpClient, err := acdClient.HTTPClient()
errors.CheckError(err)
ctx = oidc.ClientContext(ctx, httpClient)
acdSet, err := setIf.Get(ctx, &settings.SettingsQuery{})
acdSet, err := setIf.Get(ctx, &settingspkg.SettingsQuery{})
errors.CheckError(err)
oauth2conf, provider, err := acdClient.OIDCConfig(ctx, acdSet)
errors.CheckError(err)
@@ -233,7 +233,7 @@ func oauth2Login(ctx context.Context, port int, oauth2conf *oauth2.Config, provi
<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)
fmt.Fprint(w, successPage)
completionChan <- ""
}
srv := &http.Server{Addr: "localhost:" + strconv.Itoa(port)}
@@ -278,7 +278,7 @@ func passwordLogin(acdClient argocdclient.Client, username, password string) str
username, password = cli.PromptCredentials(username, password)
sessConn, sessionIf := acdClient.NewSessionClientOrDie()
defer util.Close(sessConn)
sessionRequest := session.SessionCreateRequest{
sessionRequest := sessionpkg.SessionCreateRequest{
Username: username,
Password: password,
}

View File

@@ -0,0 +1,50 @@
package commands
import (
"fmt"
"os"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/util/localconfig"
)
// NewLogoutCommand returns a new instance of `argocd logout` command
func NewLogoutCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "logout CONTEXT",
Short: "Log out from Argo CD",
Long: "Log out from Argo CD",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
context := args[0]
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
errors.CheckError(err)
if localCfg == nil {
log.Fatalf("Nothing to logout from")
}
ok := localCfg.RemoveToken(context)
if !ok {
log.Fatalf("Context %s does not exist", context)
}
err = localconfig.ValidateLocalConfig(*localCfg)
if err != nil {
log.Fatalf("Error in logging out: %s", err)
}
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
errors.CheckError(err)
fmt.Printf("Logged out from '%s'\n", context)
},
}
return command
}

View File

@@ -0,0 +1,39 @@
package commands
import (
"io/ioutil"
"os"
"testing"
"github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/stretchr/testify/assert"
"github.com/argoproj/argo-cd/util/localconfig"
)
func TestLogout(t *testing.T) {
// Write the test config file
err := ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
assert.NoError(t, err)
localConfig, err := localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
command := NewLogoutCommand(&apiclient.ClientOptions{ConfigPath: testConfigFilePath})
command.Run(nil, []string{"localhost:8080"})
localConfig, err = localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.NotContains(t, localConfig.Users, localconfig.User{AuthToken: "vErrYS3c3tReFRe$hToken", Name: "localhost:8080"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd.example.com:443", Server: "argocd.example.com:443", User: "argocd.example.com:443"})
// Write the file again so that no conflicts are made in git
err = ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
assert.NoError(t, err)
}

View File

@@ -15,12 +15,12 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
projectpkg "github.com/argoproj/argo-cd/pkg/apiclient/project"
"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/cli"
"github.com/argoproj/argo-cd/util/git"
@@ -125,7 +125,7 @@ func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
_, err := projIf.Create(context.Background(), &project.ProjectCreateRequest{Project: &proj})
_, err := projIf.Create(context.Background(), &projectpkg.ProjectCreateRequest{Project: &proj})
errors.CheckError(err)
},
}
@@ -150,7 +150,7 @@ func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
visited := 0
@@ -171,7 +171,7 @@ func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
os.Exit(1)
}
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
@@ -195,7 +195,7 @@ func NewProjectAddDestinationCommand(clientOpts *argocdclient.ClientOptions) *co
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
for _, dest := range proj.Spec.Destinations {
@@ -204,7 +204,7 @@ func NewProjectAddDestinationCommand(clientOpts *argocdclient.ClientOptions) *co
}
}
proj.Spec.Destinations = append(proj.Spec.Destinations, v1alpha1.ApplicationDestination{Server: server, Namespace: namespace})
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
@@ -227,7 +227,7 @@ func NewProjectRemoveDestinationCommand(clientOpts *argocdclient.ClientOptions)
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
index := -1
@@ -241,7 +241,7 @@ func NewProjectRemoveDestinationCommand(clientOpts *argocdclient.ClientOptions)
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})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
}
},
@@ -265,7 +265,7 @@ func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
for _, item := range proj.Spec.SourceRepos {
@@ -279,7 +279,7 @@ func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
}
}
proj.Spec.SourceRepos = append(proj.Spec.SourceRepos, url)
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
@@ -299,11 +299,11 @@ func modifyProjectResourceCmd(cmdUse, cmdDesc string, clientOpts *argocdclient.C
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
if action(proj, group, kind) {
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
}
},
@@ -399,7 +399,7 @@ func NewProjectRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobr
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
index := -1
@@ -413,7 +413,7 @@ func NewProjectRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobr
fmt.Printf("Source repository '%s' does not exist in project\n", url)
} else {
proj.Spec.SourceRepos = append(proj.Spec.SourceRepos[:index], proj.Spec.SourceRepos[index+1:]...)
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
}
},
@@ -435,7 +435,7 @@ func NewProjectDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
for _, name := range args {
_, err := projIf.Delete(context.Background(), &project.ProjectQuery{Name: name})
_, err := projIf.Delete(context.Background(), &projectpkg.ProjectQuery{Name: name})
errors.CheckError(err)
}
},
@@ -443,24 +443,44 @@ func NewProjectDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
return command
}
// Print list of project names
func printProjectNames(projects []v1alpha1.AppProject) {
for _, p := range projects {
fmt.Println(p.Name)
}
}
// Print table of project info
func printProjectTable(projects []v1alpha1.AppProject) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tDESTINATIONS\tSOURCES\tCLUSTER-RESOURCE-WHITELIST\tNAMESPACE-RESOURCE-BLACKLIST\n")
for _, p := range projects {
printProjectLine(w, &p)
}
_ = w.Flush()
}
// NewProjectListCommand returns a new instance of an `argocd proj list` command
func NewProjectListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
)
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{})
projects, err := projIf.List(context.Background(), &projectpkg.ProjectQuery{})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tDESTINATIONS\tSOURCES\tCLUSTER-RESOURCE-WHITELIST\tNAMESPACE-RESOURCE-BLACKLIST\n")
for _, p := range projects.Items {
printProjectLine(w, &p)
if output == "name" {
printProjectNames(projects.Items)
} else {
printProjectTable(projects.Items)
}
_ = w.Flush()
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|name")
return command
}
@@ -513,7 +533,7 @@ func NewProjectGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
projName := args[0]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
p, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
p, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
fmt.Printf(printProjFmtStr, "Name:", p.Name)
fmt.Printf(printProjFmtStr, "Description:", p.Spec.Description)
@@ -574,7 +594,7 @@ func NewProjectEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
projName := args[0]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
projData, err := json.Marshal(proj.Spec)
errors.CheckError(err)
@@ -591,12 +611,12 @@ func NewProjectEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
if err != nil {
return err
}
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
if err != nil {
return err
}
proj.Spec = updatedSpec
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
if err != nil {
return fmt.Errorf("Failed to update project:\n%v", err)
}

View File

@@ -12,10 +12,9 @@ import (
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
projectpkg "github.com/argoproj/argo-cd/pkg/apiclient/project"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/server/project"
"github.com/argoproj/argo-cd/util"
projectutil "github.com/argoproj/argo-cd/util/project"
)
const (
@@ -40,6 +39,8 @@ func NewProjectRoleCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
roleCommand.AddCommand(NewProjectRoleDeleteTokenCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleAddPolicyCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleRemovePolicyCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleAddGroupCommand(clientOpts))
roleCommand.AddCommand(NewProjectRoleRemoveGroupCommand(clientOpts))
return roleCommand
}
@@ -61,16 +62,16 @@ func NewProjectRoleAddPolicyCommand(clientOpts *argocdclient.ClientOptions) *cob
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
role, roleIndex, err := projectutil.GetRoleByName(proj, roleName)
role, roleIndex, err := proj.GetRoleByName(roleName)
errors.CheckError(err)
policy := fmt.Sprintf(policyTemplate, proj.Name, role.Name, opts.action, proj.Name, opts.object, opts.permission)
proj.Spec.Roles[roleIndex].Policies = append(role.Policies, policy)
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
@@ -96,10 +97,10 @@ func NewProjectRoleRemovePolicyCommand(clientOpts *argocdclient.ClientOptions) *
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
role, roleIndex, err := projectutil.GetRoleByName(proj, roleName)
role, roleIndex, err := proj.GetRoleByName(roleName)
errors.CheckError(err)
policyToRemove := fmt.Sprintf(policyTemplate, proj.Name, role.Name, opts.action, proj.Name, opts.object, opts.permission)
@@ -115,7 +116,7 @@ func NewProjectRoleRemovePolicyCommand(clientOpts *argocdclient.ClientOptions) *
}
role.Policies[duplicateIndex] = role.Policies[len(role.Policies)-1]
proj.Spec.Roles[roleIndex].Policies = role.Policies[:len(role.Policies)-1]
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
},
}
@@ -141,17 +142,17 @@ func NewProjectRoleCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
_, _, err = projectutil.GetRoleByName(proj, roleName)
_, _, err = proj.GetRoleByName(roleName)
if err == nil {
fmt.Printf("Role '%s' already exists\n", roleName)
return
}
proj.Spec.Roles = append(proj.Spec.Roles, v1alpha1.ProjectRole{Name: roleName, Description: description})
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
fmt.Printf("Role '%s' created\n", roleName)
},
@@ -175,10 +176,10 @@ func NewProjectRoleDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
_, index, err := projectutil.GetRoleByName(proj, roleName)
_, index, err := proj.GetRoleByName(roleName)
if err != nil {
fmt.Printf("Role '%s' does not exist in project\n", roleName)
return
@@ -186,7 +187,7 @@ func NewProjectRoleDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
proj.Spec.Roles[index] = proj.Spec.Roles[len(proj.Spec.Roles)-1]
proj.Spec.Roles = proj.Spec.Roles[:len(proj.Spec.Roles)-1]
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
fmt.Printf("Role '%s' deleted\n", roleName)
},
@@ -213,7 +214,7 @@ func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *c
defer util.Close(conn)
duration, err := timeutil.ParseDuration(expiresIn)
errors.CheckError(err)
token, err := projIf.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projName, Role: roleName, ExpiresIn: int64(duration.Seconds())})
token, err := projIf.CreateToken(context.Background(), &projectpkg.ProjectTokenCreateRequest{Project: projName, Role: roleName, ExpiresIn: int64(duration.Seconds())})
errors.CheckError(err)
fmt.Println(token.Token)
},
@@ -241,15 +242,35 @@ func NewProjectRoleDeleteTokenCommand(clientOpts *argocdclient.ClientOptions) *c
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
_, err = projIf.DeleteToken(context.Background(), &project.ProjectTokenDeleteRequest{Project: projName, Role: roleName, Iat: issuedAt})
_, err = projIf.DeleteToken(context.Background(), &projectpkg.ProjectTokenDeleteRequest{Project: projName, Role: roleName, Iat: issuedAt})
errors.CheckError(err)
},
}
return command
}
// Print list of project role names
func printProjectRoleListName(roles []v1alpha1.ProjectRole) {
for _, role := range roles {
fmt.Println(role.Name)
}
}
// Print table of project roles
func printProjectRoleListTable(roles []v1alpha1.ProjectRole) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "ROLE-NAME\tDESCRIPTION\n")
for _, role := range roles {
fmt.Fprintf(w, "%s\t%s\n", role.Name, role.Description)
}
_ = w.Flush()
}
// NewProjectRoleListCommand returns a new instance of an `argocd proj roles list` command
func NewProjectRoleListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
)
var command = &cobra.Command{
Use: "list PROJECT",
Short: "List all the roles in a project",
@@ -262,16 +283,16 @@ func NewProjectRoleListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
project, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
project, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "ROLE-NAME\tDESCRIPTION\n")
for _, role := range project.Spec.Roles {
fmt.Fprintf(w, "%s\t%s\n", role.Name, role.Description)
if output == "name" {
printProjectRoleListName(project.Spec.Roles)
} else {
printProjectRoleListTable(project.Spec.Roles)
}
_ = w.Flush()
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|name")
return command
}
@@ -290,10 +311,10 @@ func NewProjectRoleGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
role, _, err := projectutil.GetRoleByName(proj, roleName)
role, _, err := proj.GetRoleByName(roleName)
errors.CheckError(err)
printRoleFmtStr := "%-15s%s\n"
@@ -322,24 +343,24 @@ func NewProjectRoleGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
func NewProjectRoleAddGroupCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "add-group PROJECT ROLE-NAME GROUP-CLAIM",
Short: "Add a policy to a project role",
Short: "Add a group claim to a project role",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
if len(args) != 3 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projName, roleName, groupName := args[0], args[1], args[2]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
updated, err := projectutil.AddGroupToRole(proj, roleName, groupName)
updated, err := proj.AddGroupToRole(roleName, groupName)
errors.CheckError(err)
if updated {
if !updated {
fmt.Printf("Group '%s' already present in role '%s'\n", groupName, roleName)
return
}
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
fmt.Printf("Group '%s' added to role '%s'\n", groupName, roleName)
},
@@ -360,15 +381,15 @@ func NewProjectRoleRemoveGroupCommand(clientOpts *argocdclient.ClientOptions) *c
projName, roleName, groupName := args[0], args[1], args[2]
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
defer util.Close(conn)
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
updated, err := projectutil.RemoveGroupFromRole(proj, roleName, groupName)
updated, err := proj.RemoveGroupFromRole(roleName, groupName)
errors.CheckError(err)
if !updated {
fmt.Printf("Group '%s' not present in role '%s'\n", groupName, roleName)
return
}
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
_, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj})
errors.CheckError(err)
fmt.Printf("Group '%s' removed from role '%s'\n", groupName, roleName)
},

View File

@@ -5,13 +5,13 @@ import (
"fmt"
"os"
oidc "github.com/coreos/go-oidc"
"github.com/coreos/go-oidc"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/server/settings"
settingspkg "github.com/argoproj/argo-cd/pkg/apiclient/settings"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/localconfig"
"github.com/argoproj/argo-cd/util/session"
@@ -63,7 +63,7 @@ func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comm
httpClient, err := acdClient.HTTPClient()
errors.CheckError(err)
ctx = oidc.ClientContext(ctx, httpClient)
acdSet, err := setIf.Get(ctx, &settings.SettingsQuery{})
acdSet, err := setIf.Get(ctx, &settingspkg.SettingsQuery{})
errors.CheckError(err)
oauth2conf, provider, err := acdClient.OIDCConfig(ctx, acdSet)
errors.CheckError(err)

View File

@@ -12,8 +12,8 @@ import (
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
repositorypkg "github.com/argoproj/argo-cd/pkg/apiclient/repository"
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
@@ -23,7 +23,7 @@ import (
func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "repo",
Short: "Manage git repository credentials",
Short: "Manage git repository connection parameters",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
@@ -39,44 +39,105 @@ func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
// NewRepoAddCommand returns a new instance of an `argocd repo add` command
func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
repo appsv1.Repository
upsert bool
sshPrivateKeyPath string
repo appsv1.Repository
upsert bool
sshPrivateKeyPath string
insecureIgnoreHostKey bool
insecureSkipServerVerification bool
tlsClientCertPath string
tlsClientCertKeyPath string
enableLfs bool
)
// For better readability and easier formatting
var repoAddExamples = `
Add a SSH repository using a private key for authentication, ignoring the server's host key:",
$ argocd repo add git@git.example.com --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa",
Add a HTTPS repository using username/password and TLS client certificates:",
$ argocd repo add https://git.example.com --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key",
Add a HTTPS repository using username/password without verifying the server's TLS certificate:",
$ argocd repo add https://git.example.com --username git --password secret --insecure-skip-server-verification",
`
var command = &cobra.Command{
Use: "add REPO",
Short: "Add git repository credentials",
Use: "add REPOURL",
Short: "Add git repository connection parameters",
Example: repoAddExamples,
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
// Repository URL
repo.Repo = args[0]
// Specifying ssh-private-key-path is only valid for SSH repositories
if sshPrivateKeyPath != "" {
keyData, err := ioutil.ReadFile(sshPrivateKeyPath)
if err != nil {
log.Fatal(err)
if ok, _ := git.IsSSHURL(repo.Repo); ok {
keyData, err := ioutil.ReadFile(sshPrivateKeyPath)
if err != nil {
log.Fatal(err)
}
repo.SSHPrivateKey = string(keyData)
} else {
err := fmt.Errorf("--ssh-private-key-path is only supported for SSH repositories.")
errors.CheckError(err)
}
repo.SSHPrivateKey = string(keyData)
}
// 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 git.IsSSHURL(repo.Repo) {
// If we failed using git SSH credentials, then the repo is automatically bad
log.Fatal(err)
// tls-client-cert-path and tls-client-cert-key-key-path must always be
// specified together
if (tlsClientCertPath != "" && tlsClientCertKeyPath == "") || (tlsClientCertPath == "" && tlsClientCertKeyPath != "") {
err := fmt.Errorf("--tls-client-cert-path and --tls-client-cert-key-path must be specified together")
errors.CheckError(err)
}
// Specifying tls-client-cert-path is only valid for HTTPS repositories
if tlsClientCertPath != "" {
if git.IsHTTPSURL(repo.Repo) {
tlsCertData, err := ioutil.ReadFile(tlsClientCertPath)
errors.CheckError(err)
tlsCertKey, err := ioutil.ReadFile(tlsClientCertKeyPath)
errors.CheckError(err)
repo.TLSClientCertData = string(tlsCertData)
repo.TLSClientCertKey = string(tlsCertKey)
} else {
err := fmt.Errorf("--tls-client-cert-path is only supported for HTTPS repositories")
errors.CheckError(err)
}
// 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)
}
// InsecureIgnoreHostKey is deprecated and only here for backwards compat
repo.InsecureIgnoreHostKey = insecureIgnoreHostKey
repo.Insecure = insecureSkipServerVerification
repo.EnableLFS = enableLfs
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
repoCreateReq := repository.RepoCreateRequest{
// If the user set a username, but didn't supply password via --password,
// then we prompt for it
if repo.Username != "" && repo.Password == "" {
repo.Password = cli.PromptPassword(repo.Password)
}
// We let the server check access to the repository before adding it. If
// it is a private repo, but we cannot access with with the credentials
// that were supplied, we bail out.
repoAccessReq := repositorypkg.RepoAccessQuery{
Repo: repo.Repo,
Username: repo.Username,
Password: repo.Password,
SshPrivateKey: repo.SSHPrivateKey,
TlsClientCertData: repo.TLSClientCertData,
TlsClientCertKey: repo.TLSClientCertKey,
Insecure: repo.IsInsecure(),
}
_, err := repoIf.ValidateAccess(context.Background(), &repoAccessReq)
errors.CheckError(err)
repoCreateReq := repositorypkg.RepoCreateRequest{
Repo: &repo,
Upsert: upsert,
}
@@ -88,6 +149,11 @@ 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, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().StringVar(&tlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
command.Flags().StringVar(&tlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key path (must be PEM format)")
command.Flags().BoolVar(&insecureIgnoreHostKey, "insecure-ignore-host-key", false, "disables SSH strict host key checking (deprecated, use --insecure-skip-server-validation instead)")
command.Flags().BoolVar(&insecureSkipServerVerification, "insecure-skip-server-verification", false, "disables server certificate and host key checks")
command.Flags().BoolVar(&enableLfs, "enable-lfs", false, "enable git-lfs (Large File Support) on this repository")
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
return command
}
@@ -105,7 +171,7 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
for _, repoURL := range args {
_, err := repoIf.Delete(context.Background(), &repository.RepoQuery{Repo: repoURL})
_, err := repoIf.Delete(context.Background(), &repositorypkg.RepoQuery{Repo: repoURL})
errors.CheckError(err)
}
},
@@ -113,23 +179,49 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
return command
}
// Print table of repo info
func printRepoTable(repos []appsv1.Repository) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "REPO\tINSECURE\tLFS\tUSER\tSTATUS\tMESSAGE\n")
for _, r := range repos {
var username string
if r.Username == "" {
username = "-"
} else {
username = r.Username
}
fmt.Fprintf(w, "%s\t%v\t%v\t%s\t%s\t%s\n", r.Repo, r.IsInsecure(), r.EnableLFS, username, r.ConnectionState.Status, r.ConnectionState.Message)
}
_ = w.Flush()
}
// Print list of repo urls
func printRepoUrls(repos []appsv1.Repository) {
for _, r := range repos {
fmt.Println(r.Repo)
}
}
// NewRepoListCommand returns a new instance of an `argocd repo rm` command
func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
)
var command = &cobra.Command{
Use: "list",
Short: "List configured repositories",
Run: func(c *cobra.Command, args []string) {
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
repos, err := repoIf.List(context.Background(), &repository.RepoQuery{})
repos, err := repoIf.List(context.Background(), &repositorypkg.RepoQuery{})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "REPO\tUSER\tSTATUS\tMESSAGE\n")
for _, r := range repos.Items {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", r.Repo, r.Username, r.ConnectionState.Status, r.ConnectionState.Message)
if output == "url" {
printRepoUrls(repos.Items)
} else {
printRepoTable(repos.Items)
}
_ = w.Flush()
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|url")
return command
}

View File

@@ -36,6 +36,7 @@ func NewCommand() *cobra.Command {
},
}
command.AddCommand(NewCompletionCommand())
command.AddCommand(NewVersionCmd(&clientOpts))
command.AddCommand(NewClusterCommand(&clientOpts, pathOpts))
command.AddCommand(NewApplicationCommand(&clientOpts))
@@ -45,6 +46,8 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewContextCommand(&clientOpts))
command.AddCommand(NewProjectCommand(&clientOpts))
command.AddCommand(NewAccountCommand(&clientOpts))
command.AddCommand(NewLogoutCommand(&clientOpts))
command.AddCommand(NewCertCommand(&clientOpts))
defaultLocalConfigPath, err := localconfig.DefaultLocalConfigPath()
errors.CheckError(err)

18
cmd/argocd/commands/testdata/config vendored Normal file
View File

@@ -0,0 +1,18 @@
contexts:
- name: argocd.example.com:443
server: argocd.example.com:443
user: argocd.example.com:443
- name: localhost:8080
server: localhost:8080
user: localhost:8080
current-context: localhost:8080
servers:
- server: argocd.example.com:443
- plain-text: true
server: localhost:8080
users:
- auth-token: vErrYS3c3tReFRe$hToken
name: argocd.example.com:443
refresh-token: vErrYS3c3tReFRe$hToken
- auth-token: vErrYS3c3tReFRe$hToken
name: localhost:8080

View File

@@ -7,7 +7,7 @@ import (
"github.com/golang/protobuf/ptypes/empty"
"github.com/spf13/cobra"
argocd "github.com/argoproj/argo-cd"
"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/util"
@@ -22,7 +22,7 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
Use: "version",
Short: fmt.Sprintf("Print version information"),
Run: func(cmd *cobra.Command, args []string) {
version := argocd.GetVersion()
version := common.GetVersion()
fmt.Printf("%s: %s\n", cliName, version)
if !short {
fmt.Printf(" BuildDate: %s\n", version.BuildDate)

View File

@@ -15,13 +15,36 @@ const (
ArgoCDConfigMapName = "argocd-cm"
ArgoCDSecretName = "argocd-secret"
ArgoCDRBACConfigMapName = "argocd-rbac-cm"
// Contains SSH known hosts data for connecting repositories. Will get mounted as volume to pods
ArgoCDKnownHostsConfigMapName = "argocd-ssh-known-hosts-cm"
// Contains TLS certificate data for connecting repositories. Will get mounted as volume to pods
ArgoCDTLSCertsConfigMapName = "argocd-tls-certs-cm"
)
// Default system namespace
const (
PortAPIServer = 8080
PortRepoServer = 8081
PortArgoCDMetrics = 8082
PortArgoCDAPIServerMetrics = 8083
DefaultSystemNamespace = "kube-system"
)
// Default listener ports for ArgoCD components
const (
DefaultPortAPIServer = 8080
DefaultPortRepoServer = 8081
DefaultPortArgoCDMetrics = 8082
DefaultPortArgoCDAPIServerMetrics = 8083
DefaultPortRepoServerMetrics = 8084
)
// Default paths on the pod's file system
const (
// The default base path where application config is located
DefaultPathAppConfig = "/app/config"
// The default path where TLS certificates for repositories are located
DefaultPathTLSConfig = "/app/config/tls"
// The default path where SSH known hosts are stored
DefaultPathSSHConfig = "/app/config/ssh"
// Default name for the SSH known hosts file
DefaultSSHKnownHostsName = "ssh_known_hosts"
)
// Argo CD application related constants
@@ -74,6 +97,12 @@ const (
// LabelValueSecretTypeCluster indicates a secret type of cluster
LabelValueSecretTypeCluster = "cluster"
// AnnotationCompareOptions is a comma-separated list of options for comparison
AnnotationCompareOptions = "argocd.argoproj.io/compare-options"
// AnnotationSyncOptions is a comma-separated list of options for syncing
AnnotationSyncOptions = "argocd.argoproj.io/sync-options"
// AnnotationSyncWave indicates which wave of the sync the resource or hook should be in
AnnotationSyncWave = "argocd.argoproj.io/sync-wave"
// AnnotationKeyHook contains the hook type of a resource
AnnotationKeyHook = "argocd.argoproj.io/hook"
// AnnotationKeyHookDeletePolicy is the policy of deleting a hook
@@ -102,14 +131,18 @@ const (
// EnvVarFakeInClusterConfig is an environment variable to fake an in-cluster RESTConfig using
// the current kubectl context (for development purposes)
EnvVarFakeInClusterConfig = "ARGOCD_FAKE_IN_CLUSTER"
// Overrides the location where SSH known hosts for repo access data is stored
EnvVarSSHDataPath = "ARGOCD_SSH_DATA_PATH"
// Overrides the location where TLS certificate for repo access data is stored
EnvVarTLSDataPath = "ARGOCD_TLS_DATA_PATH"
)
const (
// MinClientVersion is the minimum client version that can interface with this API server.
// When introducing breaking changes to the API or datastructures, this number should be bumped.
// The value here may be lower than the current value in VERSION
MinClientVersion = "0.12.0"
MinClientVersion = "1.0.0"
// CacheVersion is a objects version cached using util/cache/cache.go.
// Number should be bumped in case of backward incompatible change to make sure cache is invalidated after upgrade.
CacheVersion = "0.12.0"
CacheVersion = "1.0.0"
)

View File

@@ -1,4 +1,4 @@
package argocd
package common
import (
"fmt"

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"math"
"reflect"
"runtime/debug"
"strings"
@@ -11,8 +12,7 @@ import (
"time"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"golang.org/x/sync/semaphore"
v1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -28,12 +28,13 @@ import (
statecache "github.com/argoproj/argo-cd/controller/cache"
"github.com/argoproj/argo-cd/controller/metrics"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/pkg/apis/application"
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/pkg/client/informers/externalversions/application/v1alpha1"
applisters "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/reposerver/apiclient"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/argo"
argocache "github.com/argoproj/argo-cd/util/cache"
@@ -47,6 +48,21 @@ const (
updateOperationStateTimeout = 1 * time.Second
)
type CompareWith int
const (
// Compare live application state against state defined in latest git revision.
CompareWithLatest CompareWith = 2
// Compare live application state against state defined using revision of most recent comparison.
CompareWithRecent CompareWith = 1
// Skip comparison and only refresh application resources tree
ComparisonWithNothing CompareWith = 0
)
func (a CompareWith) Max(b CompareWith) CompareWith {
return CompareWith(math.Max(float64(a), float64(b)))
}
// ApplicationController is the controller for application resources.
type ApplicationController struct {
cache *argocache.Cache
@@ -63,13 +79,14 @@ type ApplicationController struct {
appStateManager AppStateManager
stateCache statecache.LiveStateCache
statusRefreshTimeout time.Duration
repoClientset reposerver.Clientset
selfHealTimeout time.Duration
repoClientset apiclient.Clientset
db db.ArgoDB
settings *settings_util.ArgoCDSettings
settingsMgr *settings_util.SettingsManager
refreshRequestedApps map[string]bool
refreshRequestedApps map[string]CompareWith
refreshRequestedAppsMutex *sync.Mutex
metricsServer *metrics.MetricsServer
kubectlSemaphore *semaphore.Weighted
}
type ApplicationControllerConfig struct {
@@ -83,15 +100,14 @@ func NewApplicationController(
settingsMgr *settings_util.SettingsManager,
kubeClientset kubernetes.Interface,
applicationClientset appclientset.Interface,
repoClientset reposerver.Clientset,
repoClientset apiclient.Clientset,
argoCache *argocache.Cache,
appResyncPeriod time.Duration,
selfHealTimeout time.Duration,
metricsPort int,
kubectlParallelismLimit int64,
) (*ApplicationController, error) {
db := db.NewDB(namespace, settingsMgr, kubeClientset)
settings, err := settingsMgr.GetSettings()
if err != nil {
return nil, err
}
kubectlCmd := kube.KubectlCmd{}
ctrl := ApplicationController{
cache: argoCache,
@@ -104,85 +120,136 @@ func NewApplicationController(
appOperationQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
db: db,
statusRefreshTimeout: appResyncPeriod,
refreshRequestedApps: make(map[string]bool),
refreshRequestedApps: make(map[string]CompareWith),
refreshRequestedAppsMutex: &sync.Mutex{},
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, "argocd-application-controller"),
settingsMgr: settingsMgr,
settings: settings,
selfHealTimeout: selfHealTimeout,
}
if kubectlParallelismLimit > 0 {
ctrl.kubectlSemaphore = semaphore.NewWeighted(kubectlParallelismLimit)
}
kubectlCmd.OnKubectlRun = ctrl.onKubectlRun
appInformer, appLister := ctrl.newApplicationInformerAndLister()
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, cache.Indexers{})
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settings, kubectlCmd, func(appName string) {
ctrl.requestAppRefresh(appName)
ctrl.appRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, appName))
metricsAddr := fmt.Sprintf("0.0.0.0:%d", metricsPort)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister, func() error {
_, err := kubeClientset.Discovery().ServerVersion()
return err
})
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd, ctrl.settings, stateCache, projInformer)
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectlCmd, ctrl.metricsServer, ctrl.handleAppUpdated)
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer)
ctrl.appInformer = appInformer
ctrl.appLister = appLister
ctrl.projInformer = projInformer
ctrl.appStateManager = appStateManager
ctrl.stateCache = stateCache
metricsAddr := fmt.Sprintf("0.0.0.0:%d", common.PortArgoCDMetrics)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, ctrl.appLister)
return &ctrl, nil
}
func (ctrl *ApplicationController) getApp(name string) (*appv1.Application, error) {
obj, exists, err := ctrl.appInformer.GetStore().GetByKey(fmt.Sprintf("%s/%s", ctrl.namespace, name))
func (ctrl *ApplicationController) onKubectlRun(command string) (util.Closer, error) {
ctrl.metricsServer.IncKubectlExec(command)
if ctrl.kubectlSemaphore != nil {
if err := ctrl.kubectlSemaphore.Acquire(context.Background(), 1); err != nil {
return nil, err
}
ctrl.metricsServer.IncKubectlExecPending(command)
}
return util.NewCloser(func() error {
if ctrl.kubectlSemaphore != nil {
ctrl.kubectlSemaphore.Release(1)
ctrl.metricsServer.DecKubectlExecPending(command)
}
return nil
}), nil
}
func isSelfReferencedApp(app *appv1.Application, ref v1.ObjectReference) bool {
gvk := ref.GroupVersionKind()
return ref.UID == app.UID &&
ref.Name == app.Name &&
ref.Namespace == app.Namespace &&
gvk.Group == application.Group &&
gvk.Kind == application.ApplicationKind
}
func (ctrl *ApplicationController) handleAppUpdated(appName string, isManagedResource bool, ref v1.ObjectReference) {
skipForceRefresh := false
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(ctrl.namespace + "/" + appName)
if app, ok := obj.(*appv1.Application); exists && err == nil && ok && isSelfReferencedApp(app, ref) {
// Don't force refresh app if related resource is application itself. This prevents infinite reconciliation loop.
skipForceRefresh = true
}
if !skipForceRefresh {
level := ComparisonWithNothing
if isManagedResource {
level = CompareWithRecent
}
ctrl.requestAppRefresh(appName, level)
}
ctrl.appRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, appName))
}
func (ctrl *ApplicationController) setAppManagedResources(a *appv1.Application, comparisonResult *comparisonResult) (*appv1.ApplicationTree, error) {
managedResources, err := ctrl.managedResources(comparisonResult)
if err != nil {
return nil, err
}
if !exists {
return nil, status.Error(codes.NotFound, fmt.Sprintf("unable to find application with name %s", name))
}
a, ok := (obj).(*appv1.Application)
if !ok {
return nil, status.Errorf(codes.Internal, fmt.Sprintf("unexpected object type in app informer"))
}
return a, nil
}
func (ctrl *ApplicationController) setAppManagedResources(a *appv1.Application, comparisonResult *comparisonResult) error {
tree, err := ctrl.resourceTree(a, comparisonResult.managedResources)
tree, err := ctrl.getResourceTree(a, managedResources)
if err != nil {
return err
}
managedResources, err := ctrl.managedResources(a, comparisonResult)
if err != nil {
return err
return nil, err
}
err = ctrl.cache.SetAppResourcesTree(a.Name, tree)
if err != nil {
return err
return nil, err
}
return ctrl.cache.SetAppManagedResources(a.Name, managedResources)
return tree, ctrl.cache.SetAppManagedResources(a.Name, managedResources)
}
func (ctrl *ApplicationController) resourceTree(a *appv1.Application, resources []managedResource) ([]*appv1.ResourceNode, error) {
items := make([]*appv1.ResourceNode, 0)
for i := range resources {
managedResource := resources[i]
node := appv1.ResourceNode{
Name: managedResource.Name,
Version: managedResource.Version,
Kind: managedResource.Kind,
Group: managedResource.Group,
Namespace: managedResource.Namespace,
func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managedResources []*appv1.ResourceDiff) (*appv1.ApplicationTree, error) {
nodes := make([]appv1.ResourceNode, 0)
for i := range managedResources {
managedResource := managedResources[i]
var live = &unstructured.Unstructured{}
err := json.Unmarshal([]byte(managedResource.LiveState), &live)
if err != nil {
return nil, err
}
if managedResource.Live != nil {
node.ResourceVersion = managedResource.Live.GetResourceVersion()
children, err := ctrl.stateCache.GetChildren(a.Spec.Destination.Server, managedResource.Live)
var target = &unstructured.Unstructured{}
err = json.Unmarshal([]byte(managedResource.TargetState), &target)
if err != nil {
return nil, err
}
if live == nil {
nodes = append(nodes, appv1.ResourceNode{
ResourceRef: appv1.ResourceRef{
Version: target.GroupVersionKind().Version,
Name: managedResource.Name,
Kind: managedResource.Kind,
Group: managedResource.Group,
Namespace: managedResource.Namespace,
},
})
} else {
err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, live, func(child appv1.ResourceNode) {
nodes = append(nodes, child)
})
if err != nil {
return nil, err
}
node.Children = children
}
items = append(items, &node)
}
return items, nil
return &appv1.ApplicationTree{Nodes: nodes}, nil
}
func (ctrl *ApplicationController) managedResources(a *appv1.Application, comparisonResult *comparisonResult) ([]*appv1.ResourceDiff, error) {
func (ctrl *ApplicationController) managedResources(comparisonResult *comparisonResult) ([]*appv1.ResourceDiff, error) {
items := make([]*appv1.ResourceDiff, len(comparisonResult.managedResources))
for i := range comparisonResult.managedResources {
res := comparisonResult.managedResources[i]
@@ -191,6 +258,7 @@ func (ctrl *ApplicationController) managedResources(a *appv1.Application, compar
Name: res.Name,
Group: res.Group,
Kind: res.Kind,
Hook: res.Hook,
}
target := res.Target
@@ -242,14 +310,13 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
go ctrl.appInformer.Run(ctx.Done())
go ctrl.projInformer.Run(ctx.Done())
go ctrl.watchSettings(ctx)
if !cache.WaitForCacheSync(ctx.Done(), ctrl.appInformer.HasSynced, ctrl.projInformer.HasSynced) {
log.Error("Timed out waiting for caches to sync")
return
}
go ctrl.stateCache.Run(ctx)
go func() { errors.CheckError(ctrl.stateCache.Run(ctx)) }()
go func() { errors.CheckError(ctrl.metricsServer.ListenAndServe()) }()
for i := 0; i < statusProcessors; i++ {
@@ -269,20 +336,20 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
<-ctx.Done()
}
func (ctrl *ApplicationController) requestAppRefresh(appName string) {
func (ctrl *ApplicationController) requestAppRefresh(appName string, compareWith CompareWith) {
ctrl.refreshRequestedAppsMutex.Lock()
defer ctrl.refreshRequestedAppsMutex.Unlock()
ctrl.refreshRequestedApps[appName] = true
ctrl.refreshRequestedApps[appName] = compareWith.Max(ctrl.refreshRequestedApps[appName])
}
func (ctrl *ApplicationController) isRefreshRequested(appName string) bool {
func (ctrl *ApplicationController) isRefreshRequested(appName string) (bool, CompareWith) {
ctrl.refreshRequestedAppsMutex.Lock()
defer ctrl.refreshRequestedAppsMutex.Unlock()
_, ok := ctrl.refreshRequestedApps[appName]
level, ok := ctrl.refreshRequestedApps[appName]
if ok {
delete(ctrl.refreshRequestedApps, appName)
}
return ok
return ok, level
}
func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext bool) {
@@ -329,6 +396,10 @@ func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext b
return
}
func shouldBeDeleted(app *appv1.Application, obj *unstructured.Unstructured) bool {
return !kube.IsCRD(obj) && !isSelfReferencedApp(app, kube.GetObjectRef(obj))
}
func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Application) error {
logCtx := log.WithField("application", app.Name)
logCtx.Infof("Deleting resources")
@@ -347,11 +418,20 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
}
objs := make([]*unstructured.Unstructured, 0)
for k := range objsMap {
objs = append(objs, objsMap[k])
if objsMap[k].GetDeletionTimestamp() == nil && shouldBeDeleted(app, objsMap[k]) {
objs = append(objs, objsMap[k])
}
}
cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err != nil {
return err
}
config := metrics.AddMetricsTransportWrapper(ctrl.metricsServer, app, cluster.RESTConfig())
err = util.RunAllAsync(len(objs), func(i int) error {
obj := objs[i]
return ctrl.stateCache.Delete(app.Spec.Destination.Server, obj)
return ctrl.kubectl.DeleteResource(config, obj.GroupVersionKind(), obj.GetName(), obj.GetNamespace(), false)
})
if err != nil {
return err
@@ -361,6 +441,11 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
if err != nil {
return err
}
for k, obj := range objsMap {
if !shouldBeDeleted(app, obj) {
delete(objsMap, k)
}
}
if len(objsMap) > 0 {
logCtx.Infof("%d objects remaining for deletion", len(objsMap))
return nil
@@ -454,6 +539,7 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
ctrl.setOperationState(app, state)
logCtx.Infof("Initialized new operation: %v", *app.Operation)
}
ctrl.appStateManager.SyncAppState(app, state)
if state.Phase == appv1.OperationRunning {
@@ -475,7 +561,13 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
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.requestAppRefresh(app.ObjectMeta.Name)
if key, err := cache.MetaNamespaceKeyFunc(app); err == nil {
// force app refresh with using CompareWithLatest comparison type and trigger app reconciliation loop
ctrl.requestAppRefresh(app.Name, CompareWithLatest)
ctrl.appRefreshQueue.Add(key)
} else {
logCtx.Warnf("Fails to requeue application: %v", err)
}
}
}
@@ -510,6 +602,10 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace)
_, err = appClient.Patch(app.Name, types.MergePatchType, patchJSON)
if err != nil {
// Stop retrying updating deleted application
if apierr.IsNotFound(err) {
return nil
}
return err
}
log.Infof("updated '%s' operation (phase: %s)", app.Name, state.Phase)
@@ -566,19 +662,47 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
log.Warnf("Key '%s' in index is not an application", appKey)
return
}
needRefresh, refreshType := ctrl.needRefreshAppStatus(origApp, ctrl.statusRefreshTimeout)
needRefresh, refreshType, comparisonLevel := ctrl.needRefreshAppStatus(origApp, ctrl.statusRefreshTimeout)
if !needRefresh {
return
}
startTime := time.Now()
defer func() {
reconcileDuration := time.Now().Sub(startTime)
reconcileDuration := time.Since(startTime)
ctrl.metricsServer.IncReconcile(origApp, reconcileDuration)
logCtx := log.WithFields(log.Fields{"application": origApp.Name, "time_ms": reconcileDuration.Seconds() * 1e3})
logCtx := log.WithFields(log.Fields{
"application": origApp.Name,
"time_ms": reconcileDuration.Seconds() * 1e3,
"level": comparisonLevel,
"dest-server": origApp.Spec.Destination.Server,
"dest-namespace": origApp.Spec.Destination.Namespace,
})
logCtx.Info("Reconciliation completed")
}()
app := origApp.DeepCopy()
logCtx := log.WithFields(log.Fields{"application": app.Name})
if comparisonLevel == ComparisonWithNothing {
if managedResources, err := ctrl.cache.GetAppManagedResources(app.Name); err != nil {
logCtx.Warnf("Failed to get cached managed resources for tree reconciliation, fallback to full reconciliation")
} else {
if tree, err := ctrl.getResourceTree(app, managedResources); err != nil {
app.Status.Conditions = []appv1.ApplicationCondition{{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()}}
} else {
app.Status.Summary = tree.GetSummary()
if err = ctrl.cache.SetAppResourcesTree(app.Name, tree); err != nil {
logCtx.Errorf("Failed to cache resources tree: %v", err)
return
}
}
now := metav1.Now()
app.Status.ObservedAt = &now
ctrl.persistAppStatus(origApp, &app.Status)
return
}
}
conditions, hasErrors := ctrl.refreshAppConditions(app)
if hasErrors {
@@ -589,58 +713,79 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
return
}
compareResult, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, refreshType == appv1.RefreshTypeHard)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
} else {
ctrl.normalizeApplication(origApp, app, compareResult.appSourceType)
conditions = append(conditions, compareResult.conditions...)
}
err = ctrl.setAppManagedResources(app, compareResult)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
var localManifests []string
if opState := app.Status.OperationState; opState != nil && opState.Operation.Sync != nil {
localManifests = opState.Operation.Sync.Manifests
}
syncErrCond := ctrl.autoSync(app, compareResult.syncStatus)
revision := app.Spec.Source.TargetRevision
if comparisonLevel == CompareWithRecent {
revision = app.Status.Sync.Revision
}
observedAt := metav1.Now()
compareResult := ctrl.appStateManager.CompareAppState(app, revision, app.Spec.Source, refreshType == appv1.RefreshTypeHard, localManifests)
ctrl.normalizeApplication(origApp, app, compareResult.appSourceType)
conditions = append(conditions, compareResult.conditions...)
tree, err := ctrl.setAppManagedResources(app, compareResult)
if err != nil {
logCtx.Errorf("Failed to cache app resources: %v", err)
} else {
app.Status.Summary = tree.GetSummary()
}
syncErrCond := ctrl.autoSync(app, compareResult.syncStatus, compareResult.resources)
if syncErrCond != nil {
conditions = append(conditions, *syncErrCond)
}
app.Status.ObservedAt = compareResult.observedAt
if app.Status.ReconciledAt == nil || comparisonLevel == CompareWithLatest {
app.Status.ReconciledAt = &observedAt
}
app.Status.ObservedAt = &observedAt
app.Status.Sync = *compareResult.syncStatus
app.Status.Health = *compareResult.healthStatus
app.Status.Resources = compareResult.resources
app.Status.Conditions = conditions
app.Status.SourceType = compareResult.appSourceType
ctrl.persistAppStatus(origApp, &app.Status)
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, appv1.RefreshType) {
// Additionally returns whether full refresh was requested or not.
// If full refresh is requested then target and live state should be reconciled, else only live state tree should be updated.
func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application, statusRefreshTimeout time.Duration) (bool, appv1.RefreshType, CompareWith) {
logCtx := log.WithFields(log.Fields{"application": app.Name})
var reason string
compareWith := CompareWithLatest
refreshType := appv1.RefreshTypeNormal
expired := app.Status.ObservedAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
expired := app.Status.ReconciledAt == nil || app.Status.ReconciledAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
if requestedType, ok := app.IsRefreshRequested(); ok {
// user requested app refresh.
refreshType = requestedType
reason = fmt.Sprintf("%s refresh requested", refreshType)
} else if ctrl.isRefreshRequested(app.Name) {
reason = fmt.Sprintf("controller refresh requested")
} else if app.Status.Sync.Status == appv1.SyncStatusCodeUnknown && expired {
reason = "comparison status unknown"
} else if expired {
reason = fmt.Sprintf("comparison expired. reconciledAt: %v, expiry: %v", app.Status.ReconciledAt, statusRefreshTimeout)
} else if !app.Spec.Source.Equals(app.Status.Sync.ComparedTo.Source) {
reason = "spec.source differs"
} else if !app.Spec.Destination.Equals(app.Status.Sync.ComparedTo.Destination) {
reason = "spec.destination differs"
} else if expired {
reason = fmt.Sprintf("comparison expired. observedAt: %v, expiry: %v", app.Status.ObservedAt, statusRefreshTimeout)
} else if requested, level := ctrl.isRefreshRequested(app.Name); requested {
compareWith = level
reason = fmt.Sprintf("controller refresh requested")
}
if reason != "" {
logCtx.Infof("Refreshing app status (%s)", reason)
return true, refreshType
logCtx.Infof("Refreshing app status (%s), level (%d)", reason, compareWith)
return true, refreshType, compareWith
}
return false, refreshType
return false, refreshType, compareWith
}
func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application) ([]appv1.ApplicationCondition, bool) {
@@ -659,7 +804,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
})
}
} else {
specConditions, err := argo.GetSpecErrors(context.Background(), &app.Spec, proj, ctrl.repoClientset, ctrl.db)
specConditions, err := argo.ValidatePermissions(context.Background(), &app.Spec, proj, ctrl.db)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionUnknownError,
@@ -672,11 +817,13 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
// 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,
appv1.ApplicationConditionSyncError: true,
appv1.ApplicationConditionInvalidSpecError: true,
appv1.ApplicationConditionUnknownError: true,
appv1.ApplicationConditionComparisonError: true,
appv1.ApplicationConditionSharedResourceWarning: true,
appv1.ApplicationConditionSyncError: true,
appv1.ApplicationConditionRepeatedResourceWarning: true,
appv1.ApplicationConditionExcludedResourceWarning: true,
}
appConditions := make([]appv1.ApplicationCondition, 0)
for i := 0; i < len(app.Status.Conditions); i++ {
@@ -756,7 +903,7 @@ func (ctrl *ApplicationController) persistAppStatus(orig *appv1.Application, new
}
// autoSync will initiate a sync operation for an application configured with automated sync
func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *appv1.SyncStatus) *appv1.ApplicationCondition {
func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *appv1.SyncStatus, resources []appv1.ResourceStatus) *appv1.ApplicationCondition {
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
return nil
}
@@ -775,28 +922,52 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
logCtx.Infof("Skipping auto-sync: application status is %s", syncStatus.Status)
return nil
}
desiredCommitSHA := syncStatus.Revision
// It is possible for manifests to remain OutOfSync even after a sync/kubectl apply (e.g.
// auto-sync with pruning disabled). We need to ensure that we do not keep Syncing an
// application in an infinite loop. To detect this, we only attempt the Sync if the revision
// and parameter overrides are different from our most recent sync operation.
if alreadyAttemptedSync(app, desiredCommitSHA) {
if app.Status.OperationState.Phase != appv1.OperationSucceeded {
logCtx.Warnf("Skipping auto-sync: failed previous sync attempt to %s", desiredCommitSHA)
message := fmt.Sprintf("Failed sync attempt to %s: %s", desiredCommitSHA, app.Status.OperationState.Message)
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: message}
}
logCtx.Infof("Skipping auto-sync: most recent sync already to %s", desiredCommitSHA)
return nil
}
alreadyAttempted, attemptPhase := alreadyAttemptedSync(app, desiredCommitSHA)
selfHeal := app.Spec.SyncPolicy.Automated.SelfHeal
op := appv1.Operation{
Sync: &appv1.SyncOperation{
Revision: desiredCommitSHA,
Prune: app.Spec.SyncPolicy.Automated.Prune,
},
}
// It is possible for manifests to remain OutOfSync even after a sync/kubectl apply (e.g.
// auto-sync with pruning disabled). We need to ensure that we do not keep Syncing an
// application in an infinite loop. To detect this, we only attempt the Sync if the revision
// and parameter overrides are different from our most recent sync operation.
if alreadyAttempted && (!selfHeal || !attemptPhase.Successful()) {
if !attemptPhase.Successful() {
logCtx.Warnf("Skipping auto-sync: failed previous sync attempt to %s", desiredCommitSHA)
message := fmt.Sprintf("Failed sync attempt to %s: %s", desiredCommitSHA, app.Status.OperationState.Message)
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: message}
}
logCtx.Infof("Skipping auto-sync: most recent sync already to %s", desiredCommitSHA)
return nil
} else if alreadyAttempted && selfHeal {
if shouldSelfHeal, retryAfter := ctrl.shouldSelfHeal(app); shouldSelfHeal {
for _, resource := range resources {
if resource.Status != appv1.SyncStatusCodeSynced {
op.Sync.Resources = append(op.Sync.Resources, appv1.SyncOperationResource{
Kind: resource.Kind,
Group: resource.Group,
Name: resource.Name,
})
}
}
} else {
logCtx.Infof("Skipping auto-sync: already attempted sync to %s with timeout %v (retrying in %v)", desiredCommitSHA, ctrl.selfHealTimeout, retryAfter)
if key, err := cache.MetaNamespaceKeyFunc(app); err == nil {
ctrl.requestAppRefresh(app.Name, CompareWithLatest)
ctrl.appRefreshQueue.AddAfter(key, retryAfter)
} else {
logCtx.Warnf("Fails to requeue application: %v", err)
}
return nil
}
}
appIf := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
_, err := argo.SetAppOperation(appIf, app.Name, &op)
if err != nil {
@@ -811,12 +982,12 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
// alreadyAttemptedSync returns whether or not the most recent sync was performed against the
// commitSHA and with the same app source config which are currently set in the app
func alreadyAttemptedSync(app *appv1.Application, commitSHA string) bool {
func alreadyAttemptedSync(app *appv1.Application, commitSHA string) (bool, appv1.OperationPhase) {
if app.Status.OperationState == nil || app.Status.OperationState.Operation.Sync == nil || app.Status.OperationState.SyncResult == nil {
return false
return false, ""
}
if app.Status.OperationState.SyncResult.Revision != commitSHA {
return false
return false, ""
}
// Ignore differences in target revision, since we already just verified commitSHAs are equal,
// and we do not want to trigger auto-sync due to things like HEAD != master
@@ -824,10 +995,21 @@ func alreadyAttemptedSync(app *appv1.Application, commitSHA string) bool {
specSource.TargetRevision = ""
syncResSource := app.Status.OperationState.SyncResult.Source.DeepCopy()
syncResSource.TargetRevision = ""
if !reflect.DeepEqual(app.Spec.Source, app.Status.OperationState.SyncResult.Source) {
return false
return reflect.DeepEqual(app.Spec.Source, app.Status.OperationState.SyncResult.Source), app.Status.OperationState.Phase
}
func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application) (bool, time.Duration) {
if app.Status.OperationState == nil {
return true, time.Duration(0)
}
return true
var retryAfter time.Duration
if app.Status.OperationState.FinishedAt == nil {
retryAfter = ctrl.selfHealTimeout
} else {
retryAfter = ctrl.selfHealTimeout - time.Since(app.Status.OperationState.FinishedAt.Time)
}
return retryAfter <= 0, retryAfter
}
func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister) {
@@ -858,7 +1040,7 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
if oldOK && newOK {
if toggledAutomatedSync(oldApp, newApp) {
log.WithField("application", newApp.Name).Info("Enabled automated sync")
ctrl.requestAppRefresh(newApp.Name)
ctrl.requestAppRefresh(newApp.Name, CompareWithLatest)
}
}
ctrl.appRefreshQueue.Add(key)
@@ -894,33 +1076,3 @@ func toggledAutomatedSync(old *appv1.Application, new *appv1.Application) bool {
// nothing changed
return false
}
func (ctrl *ApplicationController) watchSettings(ctx context.Context) {
updateCh := make(chan *settings_util.ArgoCDSettings, 1)
ctrl.settingsMgr.Subscribe(updateCh)
prevAppLabelKey := ctrl.settings.GetAppInstanceLabelKey()
prevResourceExclusions := ctrl.settings.ResourceExclusions
done := false
for !done {
select {
case newSettings := <-updateCh:
newAppLabelKey := newSettings.GetAppInstanceLabelKey()
*ctrl.settings = *newSettings
if prevAppLabelKey != newAppLabelKey {
log.Infof("label key changed: %s -> %s", prevAppLabelKey, newAppLabelKey)
ctrl.stateCache.Invalidate()
prevAppLabelKey = newAppLabelKey
}
if !reflect.DeepEqual(prevResourceExclusions, newSettings.ResourceExclusions) {
log.Infof("resource exclusions modified")
ctrl.stateCache.Invalidate()
prevResourceExclusions = newSettings.ResourceExclusions
}
case <-ctx.Done():
done = true
}
}
log.Info("shutting down settings watch")
ctrl.settingsMgr.Unsubscribe(updateCh)
close(updateCh)
}

View File

@@ -2,6 +2,7 @@ package controller
import (
"context"
"encoding/json"
"testing"
"time"
@@ -9,19 +10,22 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
corev1 "k8s.io/api/core/v1"
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/kubernetes/fake"
kubetesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
"github.com/argoproj/argo-cd/common"
mockstatecache "github.com/argoproj/argo-cd/controller/cache/mocks"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
"github.com/argoproj/argo-cd/reposerver/apiclient"
mockrepoclient "github.com/argoproj/argo-cd/reposerver/apiclient/mocks"
mockreposerver "github.com/argoproj/argo-cd/reposerver/mocks"
"github.com/argoproj/argo-cd/reposerver/repository"
mockrepoclient "github.com/argoproj/argo-cd/reposerver/repository/mocks"
"github.com/argoproj/argo-cd/test"
utilcache "github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/kube"
@@ -30,8 +34,9 @@ import (
type fakeData struct {
apps []runtime.Object
manifestResponse *repository.ManifestResponse
manifestResponse *apiclient.ManifestResponse
managedLiveObjs map[kube.ResourceKey]*unstructured.Unstructured
configMapData map[string]string
}
func newFakeController(data *fakeData) *ApplicationController {
@@ -61,8 +66,11 @@ func newFakeController(data *fakeData) *ApplicationController {
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-cm",
Namespace: test.FakeArgoCDNamespace,
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: nil,
Data: data.configMapData,
}
kubeClient := fake.NewSimpleClientset(&clust, &cm, &secret)
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClient, test.FakeArgoCDNamespace)
@@ -74,6 +82,9 @@ func newFakeController(data *fakeData) *ApplicationController {
&mockRepoClientset,
utilcache.NewCache(utilcache.NewInMemoryCache(1*time.Hour)),
time.Minute,
time.Minute,
common.DefaultPortArgoCDMetrics,
0,
)
if err != nil {
panic(err)
@@ -105,12 +116,12 @@ data:
# minikube
name: aHR0cHM6Ly9sb2NhbGhvc3Q6NjQ0Mw==
# https://localhost:6443
server: aHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3Zj
server: aHR0cHM6Ly9sb2NhbGhvc3Q6NjQ0Mw==
kind: Secret
metadata:
labels:
argocd.argoproj.io/secret-type: cluster
name: localhost-6443
name: some-secret
namespace: ` + test.FakeArgoCDNamespace + `
type: Opaque
`
@@ -119,6 +130,7 @@ var fakeApp = `
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
uid: "123"
name: my-app
namespace: ` + test.FakeArgoCDNamespace + `
spec:
@@ -171,7 +183,7 @@ func TestAutoSync(t *testing.T) {
Status: argoappv1.SyncStatusCodeOutOfSync,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond := ctrl.autoSync(app, &syncStatus)
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
@@ -190,7 +202,7 @@ func TestSkipAutoSync(t *testing.T) {
Status: argoappv1.SyncStatusCodeOutOfSync,
Revision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
}
cond := ctrl.autoSync(app, &syncStatus)
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
@@ -205,7 +217,7 @@ func TestSkipAutoSync(t *testing.T) {
Status: argoappv1.SyncStatusCodeSynced,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond := ctrl.autoSync(app, &syncStatus)
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
@@ -221,7 +233,7 @@ func TestSkipAutoSync(t *testing.T) {
Status: argoappv1.SyncStatusCodeOutOfSync,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond := ctrl.autoSync(app, &syncStatus)
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
@@ -238,7 +250,7 @@ func TestSkipAutoSync(t *testing.T) {
Status: argoappv1.SyncStatusCodeOutOfSync,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond := ctrl.autoSync(app, &syncStatus)
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
@@ -264,7 +276,7 @@ func TestSkipAutoSync(t *testing.T) {
Status: argoappv1.SyncStatusCodeOutOfSync,
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
cond := ctrl.autoSync(app, &syncStatus)
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
assert.NotNil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
@@ -300,7 +312,7 @@ func TestAutoSyncIndicateError(t *testing.T) {
Source: *app.Spec.Source.DeepCopy(),
},
}
cond := ctrl.autoSync(app, &syncStatus)
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
assert.NotNil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
@@ -343,7 +355,7 @@ func TestAutoSyncParameterOverrides(t *testing.T) {
Revision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
}
cond := ctrl.autoSync(app, &syncStatus)
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get("my-app", metav1.GetOptions{})
assert.NoError(t, err)
@@ -353,20 +365,26 @@ func TestAutoSyncParameterOverrides(t *testing.T) {
// TestFinalizeAppDeletion verifies application deletion
func TestFinalizeAppDeletion(t *testing.T) {
app := newFakeApp()
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
appObj := kube.MustToUnstructured(&app)
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}, managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(appObj): appObj,
}})
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
patched := false
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
defaultReactor := fakeAppCs.ReactionChain[0]
fakeAppCs.ReactionChain = nil
fakeAppCs.AddReactor("get", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
return defaultReactor.React(action)
})
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
patched = true
return true, nil, nil
})
err := ctrl.finalizeApplicationDeletion(app)
// TODO: use an interface to fake out the calls to GetResourcesWithLabel and DeleteResourceWithLabel
// For now just ensure we have an expected error condition
assert.Error(t, err) // Change this to assert.Nil when we stub out GetResourcesWithLabel/DeleteResourceWithLabel
assert.False(t, patched) // Change this to assert.True when we stub out GetResourcesWithLabel/DeleteResourceWithLabel
assert.NoError(t, err)
assert.True(t, patched)
}
// TestNormalizeApplication verifies we normalize an application during reconciliation
@@ -391,7 +409,7 @@ func TestNormalizeApplication(t *testing.T) {
app.Spec.Source.Kustomize = &argoappv1.ApplicationSourceKustomize{NamePrefix: "foo-"}
data := fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &repository.ManifestResponse{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
@@ -442,3 +460,173 @@ func TestNormalizeApplication(t *testing.T) {
assert.False(t, normalized)
}
}
func TestHandleAppUpdated(t *testing.T) {
app := newFakeApp()
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
app.Spec.Destination.Server = common.KubernetesInternalAPIServerAddr
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
ctrl.handleAppUpdated(app.Name, true, kube.GetObjectRef(kube.MustToUnstructured(app)))
isRequested, level := ctrl.isRefreshRequested(app.Name)
assert.False(t, isRequested)
assert.Equal(t, ComparisonWithNothing, level)
ctrl.handleAppUpdated(app.Name, true, corev1.ObjectReference{UID: "test", Kind: kube.DeploymentKind, Name: "test", Namespace: "default"})
isRequested, level = ctrl.isRefreshRequested(app.Name)
assert.True(t, isRequested)
assert.Equal(t, CompareWithRecent, level)
}
func TestSetOperationStateOnDeletedApp(t *testing.T) {
ctrl := newFakeController(&fakeData{apps: []runtime.Object{}})
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
fakeAppCs.ReactionChain = nil
patched := false
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
patched = true
return true, nil, apierr.NewNotFound(schema.GroupResource{}, "my-app")
})
ctrl.setOperationState(newFakeApp(), &argoappv1.OperationState{Phase: argoappv1.OperationSucceeded})
assert.True(t, patched)
}
func TestNeedRefreshAppStatus(t *testing.T) {
ctrl := newFakeController(&fakeData{apps: []runtime.Object{}})
app := newFakeApp()
now := metav1.Now()
app.Status.ReconciledAt = &now
app.Status.Sync = argoappv1.SyncStatus{
Status: argoappv1.SyncStatusCodeSynced,
ComparedTo: argoappv1.ComparedTo{
Source: app.Spec.Source,
Destination: app.Spec.Destination,
},
}
// no need to refresh just reconciled application
needRefresh, _, _ := ctrl.needRefreshAppStatus(app, 1*time.Hour)
assert.False(t, needRefresh)
// refresh app using the 'deepest' requested comparison level
ctrl.requestAppRefresh(app.Name, CompareWithRecent)
ctrl.requestAppRefresh(app.Name, ComparisonWithNothing)
needRefresh, refreshType, compareWith := ctrl.needRefreshAppStatus(app, 1*time.Hour)
assert.True(t, needRefresh)
assert.Equal(t, argoappv1.RefreshTypeNormal, refreshType)
assert.Equal(t, CompareWithRecent, compareWith)
// refresh application which status is not reconciled using latest commit
app.Status.Sync = argoappv1.SyncStatus{Status: argoappv1.SyncStatusCodeUnknown}
needRefresh, refreshType, compareWith = ctrl.needRefreshAppStatus(app, 1*time.Hour)
assert.True(t, needRefresh)
assert.Equal(t, argoappv1.RefreshTypeNormal, refreshType)
assert.Equal(t, CompareWithLatest, compareWith)
{
// refresh app using the 'latest' level if comparison expired
app := app.DeepCopy()
ctrl.requestAppRefresh(app.Name, CompareWithRecent)
reconciledAt := metav1.NewTime(time.Now().UTC().Add(-1 * time.Hour))
app.Status.ReconciledAt = &reconciledAt
needRefresh, refreshType, compareWith = ctrl.needRefreshAppStatus(app, 1*time.Minute)
assert.True(t, needRefresh)
assert.Equal(t, argoappv1.RefreshTypeNormal, refreshType)
assert.Equal(t, CompareWithLatest, compareWith)
}
{
app := app.DeepCopy()
// execute hard refresh if app has refresh annotation
reconciledAt := metav1.NewTime(time.Now().UTC().Add(-1 * time.Hour))
app.Status.ReconciledAt = &reconciledAt
app.Annotations = map[string]string{
common.AnnotationKeyRefresh: string(argoappv1.RefreshTypeHard),
}
needRefresh, refreshType, compareWith = ctrl.needRefreshAppStatus(app, 1*time.Hour)
assert.True(t, needRefresh)
assert.Equal(t, argoappv1.RefreshTypeHard, refreshType)
assert.Equal(t, CompareWithLatest, compareWith)
}
{
app := app.DeepCopy()
// ensure that CompareWithLatest level is used if application source has changed
ctrl.requestAppRefresh(app.Name, ComparisonWithNothing)
// sample app source change
app.Spec.Source.Helm = &argoappv1.ApplicationSourceHelm{
Parameters: []argoappv1.HelmParameter{{
Name: "foo",
Value: "bar",
}},
}
needRefresh, refreshType, compareWith = ctrl.needRefreshAppStatus(app, 1*time.Hour)
assert.True(t, needRefresh)
assert.Equal(t, argoappv1.RefreshTypeNormal, refreshType)
assert.Equal(t, CompareWithLatest, compareWith)
}
}
func TestUpdateReconciledAt(t *testing.T) {
app := newFakeApp()
reconciledAt := metav1.NewTime(time.Now().Add(-1 * time.Second))
app.Status = argoappv1.ApplicationStatus{ReconciledAt: &reconciledAt}
app.Status.Sync = argoappv1.SyncStatus{ComparedTo: argoappv1.ComparedTo{Source: app.Spec.Source, Destination: app.Spec.Destination}}
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
})
key, _ := cache.MetaNamespaceKeyFunc(app)
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
fakeAppCs.ReactionChain = nil
receivedPatch := map[string]interface{}{}
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
if patchAction, ok := action.(kubetesting.PatchAction); ok {
assert.NoError(t, json.Unmarshal(patchAction.GetPatch(), &receivedPatch))
}
return true, nil, nil
})
t.Run("UpdatedOnFullReconciliation", func(t *testing.T) {
receivedPatch = map[string]interface{}{}
ctrl.requestAppRefresh(app.Name, CompareWithLatest)
ctrl.appRefreshQueue.Add(key)
ctrl.processAppRefreshQueueItem()
_, updated, err := unstructured.NestedString(receivedPatch, "status", "reconciledAt")
assert.NoError(t, err)
assert.True(t, updated)
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
assert.NoError(t, err)
assert.True(t, updated)
})
t.Run("NotUpdatedOnPartialReconciliation", func(t *testing.T) {
receivedPatch = map[string]interface{}{}
ctrl.appRefreshQueue.Add(key)
ctrl.requestAppRefresh(app.Name, CompareWithRecent)
ctrl.processAppRefreshQueueItem()
_, updated, err := unstructured.NestedString(receivedPatch, "status", "reconciledAt")
assert.NoError(t, err)
assert.False(t, updated)
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
assert.NoError(t, err)
assert.True(t, updated)
})
}

View File

@@ -2,15 +2,17 @@ package cache
import (
"context"
"fmt"
"reflect"
"sync"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
"github.com/argoproj/argo-cd/controller/metrics"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/db"
@@ -18,20 +20,26 @@ import (
"github.com/argoproj/argo-cd/util/settings"
)
type cacheSettings struct {
ResourceOverrides map[string]appv1.ResourceOverride
AppInstanceLabelKey string
ResourcesFilter *settings.ResourcesFilter
}
type LiveStateCache interface {
IsNamespaced(server string, gvk schema.GroupVersionKind) (bool, error)
// Returns child nodes for a given k8s resource
GetChildren(server string, obj *unstructured.Unstructured) ([]appv1.ResourceNode, error)
IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error)
// Executes give callback against resource specified by the key and all its children
IterateHierarchy(server string, obj *unstructured.Unstructured, action func(child appv1.ResourceNode)) error
// Returns state of live nodes which correspond for target nodes of specified application.
GetManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error)
// Starts watching resources of each controlled cluster.
Run(ctx context.Context)
// Deletes specified resource from cluster.
Delete(server string, obj *unstructured.Unstructured) error
Run(ctx context.Context) error
// Invalidate invalidates the entire cluster state cache
Invalidate()
}
type AppUpdatedHandler = func(appName string, isManagedResource bool, ref v1.ObjectReference)
func GetTargetObjKey(a *appv1.Application, un *unstructured.Unstructured, isNamespaced bool) kube.ResourceKey {
key := kube.GetResourceKey(un)
if !isNamespaced {
@@ -43,41 +51,54 @@ func GetTargetObjKey(a *appv1.Application, un *unstructured.Unstructured, isName
return key
}
func NewLiveStateCache(db db.ArgoDB, appInformer cache.SharedIndexInformer, settings *settings.ArgoCDSettings, kubectl kube.Kubectl, onAppUpdated func(appName string)) LiveStateCache {
func NewLiveStateCache(
db db.ArgoDB,
appInformer cache.SharedIndexInformer,
settingsMgr *settings.SettingsManager,
kubectl kube.Kubectl,
metricsServer *metrics.MetricsServer,
onAppUpdated AppUpdatedHandler) LiveStateCache {
return &liveStateCache{
appInformer: appInformer,
db: db,
clusters: make(map[string]*clusterInfo),
lock: &sync.Mutex{},
onAppUpdated: onAppUpdated,
kubectl: kubectl,
settings: settings,
appInformer: appInformer,
db: db,
clusters: make(map[string]*clusterInfo),
lock: &sync.Mutex{},
onAppUpdated: onAppUpdated,
kubectl: kubectl,
settingsMgr: settingsMgr,
metricsServer: metricsServer,
cacheSettingsLock: &sync.Mutex{},
}
}
type liveStateCache struct {
db db.ArgoDB
clusters map[string]*clusterInfo
lock *sync.Mutex
appInformer cache.SharedIndexInformer
onAppUpdated func(appName string)
kubectl kube.Kubectl
settings *settings.ArgoCDSettings
db db.ArgoDB
clusters map[string]*clusterInfo
lock *sync.Mutex
appInformer cache.SharedIndexInformer
onAppUpdated AppUpdatedHandler
kubectl kube.Kubectl
settingsMgr *settings.SettingsManager
metricsServer *metrics.MetricsServer
cacheSettingsLock *sync.Mutex
cacheSettings *cacheSettings
}
func (c *liveStateCache) processEvent(event watch.EventType, obj *unstructured.Unstructured, url string) error {
info, err := c.getSyncedCluster(url)
func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
appInstanceLabelKey, err := c.settingsMgr.GetAppInstanceLabelKey()
if err != nil {
return err
return nil, err
}
return info.processEvent(event, obj)
}
func (c *liveStateCache) removeCluster(server string) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.clusters, server)
log.Infof("Dropped cluster %s cache", server)
resourcesFilter, err := c.settingsMgr.GetResourcesFilter()
if err != nil {
return nil, err
}
resourceOverrides, err := c.settingsMgr.GetResourceOverrides()
if err != nil {
return nil, err
}
return &cacheSettings{AppInstanceLabelKey: appInstanceLabelKey, ResourceOverrides: resourceOverrides, ResourcesFilter: resourcesFilter}, nil
}
func (c *liveStateCache) getCluster(server string) (*clusterInfo, error) {
@@ -90,17 +111,17 @@ func (c *liveStateCache) getCluster(server string) (*clusterInfo, error) {
return nil, err
}
info = &clusterInfo{
apis: make(map[schema.GroupKind]*gkInfo),
lock: &sync.Mutex{},
nodes: make(map[kube.ResourceKey]*node),
nsIndex: make(map[string]map[kube.ResourceKey]*node),
onAppUpdated: c.onAppUpdated,
kubectl: c.kubectl,
cluster: cluster,
syncTime: nil,
syncLock: &sync.Mutex{},
log: log.WithField("server", cluster.Server),
settings: c.settings,
apisMeta: make(map[schema.GroupKind]*apiMeta),
lock: &sync.Mutex{},
nodes: make(map[kube.ResourceKey]*node),
nsIndex: make(map[string]map[kube.ResourceKey]*node),
onAppUpdated: c.onAppUpdated,
kubectl: c.kubectl,
cluster: cluster,
syncTime: nil,
syncLock: &sync.Mutex{},
log: log.WithField("server", cluster.Server),
cacheSettingsSrc: c.getCacheSettings,
}
c.clusters[cluster.Server] = info
@@ -132,28 +153,21 @@ func (c *liveStateCache) Invalidate() {
log.Info("live state cache invalidated")
}
func (c *liveStateCache) Delete(server string, obj *unstructured.Unstructured) error {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
return err
}
return clusterInfo.delete(obj)
}
func (c *liveStateCache) IsNamespaced(server string, gvk schema.GroupVersionKind) (bool, error) {
func (c *liveStateCache) IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error) {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
return false, err
}
return clusterInfo.isNamespaced(gvk.GroupKind()), nil
return clusterInfo.isNamespaced(obj), nil
}
func (c *liveStateCache) GetChildren(server string, obj *unstructured.Unstructured) ([]appv1.ResourceNode, error) {
func (c *liveStateCache) IterateHierarchy(server string, obj *unstructured.Unstructured, action func(child appv1.ResourceNode)) error {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
return nil, err
return err
}
return clusterInfo.getChildren(obj), nil
clusterInfo.iterateHierarchy(obj, action)
return nil
}
func (c *liveStateCache) GetManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
@@ -161,7 +175,7 @@ func (c *liveStateCache) GetManagedLiveObjs(a *appv1.Application, targetObjs []*
if err != nil {
return nil, err
}
return clusterInfo.getManagedLiveObjs(a, targetObjs)
return clusterInfo.getManagedLiveObjs(a, targetObjs, c.metricsServer)
}
func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {
@@ -173,137 +187,79 @@ func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {
return false
}
func (c *liveStateCache) getCacheSettings() *cacheSettings {
c.cacheSettingsLock.Lock()
defer c.cacheSettingsLock.Unlock()
return c.cacheSettings
}
func (c *liveStateCache) watchSettings(ctx context.Context) {
updateCh := make(chan *settings.ArgoCDSettings, 1)
c.settingsMgr.Subscribe(updateCh)
done := false
for !done {
select {
case <-updateCh:
nextCacheSettings, err := c.loadCacheSettings()
if err != nil {
log.Warnf("Failed to read updated settings: %v", err)
continue
}
c.cacheSettingsLock.Lock()
needInvalidate := false
if !reflect.DeepEqual(c.cacheSettings, nextCacheSettings) {
c.cacheSettings = nextCacheSettings
needInvalidate = true
}
c.cacheSettingsLock.Unlock()
if needInvalidate {
c.Invalidate()
}
case <-ctx.Done():
done = true
}
}
log.Info("shutting down settings watch")
c.settingsMgr.Unsubscribe(updateCh)
close(updateCh)
}
// Run watches for resource changes annotated with application label on all registered clusters and schedule corresponding app refresh.
func (c *liveStateCache) Run(ctx context.Context) {
watchingClustersLock := sync.Mutex{}
watchingClusters := make(map[string]struct {
cancel context.CancelFunc
cluster *appv1.Cluster
})
func (c *liveStateCache) Run(ctx context.Context) error {
cacheSettings, err := c.loadCacheSettings()
if err != nil {
return err
}
c.cacheSettings = cacheSettings
go c.watchSettings(ctx)
util.RetryUntilSucceed(func() error {
clusterEventCallback := func(event *db.ClusterEvent) {
info, ok := watchingClusters[event.Cluster.Server]
hasApps := isClusterHasApps(c.appInformer.GetStore().List(), event.Cluster)
// cluster resources must be watched only if cluster has at least one app
if (event.Type == watch.Deleted || !hasApps) && ok {
info.cancel()
watchingClustersLock.Lock()
delete(watchingClusters, event.Cluster.Server)
watchingClustersLock.Unlock()
} else if event.Type != watch.Deleted && !ok && hasApps {
ctx, cancel := context.WithCancel(ctx)
watchingClustersLock.Lock()
watchingClusters[event.Cluster.Server] = struct {
cancel context.CancelFunc
cluster *appv1.Cluster
}{
cancel: func() {
c.removeCluster(event.Cluster.Server)
cancel()
},
cluster: event.Cluster,
c.lock.Lock()
defer c.lock.Unlock()
if cluster, ok := c.clusters[event.Cluster.Server]; ok {
if event.Type == watch.Deleted {
cluster.invalidate()
delete(c.clusters, event.Cluster.Server)
} else if event.Type == watch.Modified {
cluster.cluster = event.Cluster
cluster.invalidate()
}
watchingClustersLock.Unlock()
go c.watchClusterResources(ctx, *event.Cluster)
} else if event.Type == watch.Added && isClusterHasApps(c.appInformer.GetStore().List(), event.Cluster) {
go func() {
// warm up cache for cluster with apps
_, _ = c.getSyncedCluster(event.Cluster.Server)
}()
}
}
onAppModified := func(obj interface{}) {
if app, ok := obj.(*appv1.Application); ok {
var cluster *appv1.Cluster
info, infoOk := watchingClusters[app.Spec.Destination.Server]
if infoOk {
cluster = info.cluster
} else {
cluster, _ = c.db.GetCluster(ctx, app.Spec.Destination.Server)
}
if cluster != nil {
// trigger cluster event every time when app created/deleted to either start or stop watching resources
clusterEventCallback(&db.ClusterEvent{Cluster: cluster, Type: watch.Modified})
}
}
}
c.appInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: onAppModified,
UpdateFunc: func(oldObj, newObj interface{}) {
oldApp, oldOk := oldObj.(*appv1.Application)
newApp, newOk := newObj.(*appv1.Application)
if oldOk && newOk {
if oldApp.Spec.Destination.Server != newApp.Spec.Destination.Server {
onAppModified(oldObj)
onAppModified(newApp)
}
}
},
DeleteFunc: onAppModified,
})
return c.db.WatchClusters(ctx, clusterEventCallback)
}, "watch clusters", ctx, clusterRetryTimeout)
<-ctx.Done()
}
// watchClusterResources watches for resource changes annotated with application label on specified cluster and schedule corresponding app refresh.
func (c *liveStateCache) watchClusterResources(ctx context.Context, item appv1.Cluster) {
util.RetryUntilSucceed(func() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered from panic: %v\n", r)
}
}()
config := item.RESTConfig()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
ch, err := c.kubectl.WatchResources(ctx, config, c.settings, func(gk schema.GroupKind) (s string, e error) {
clusterInfo, err := c.getSyncedCluster(item.Server)
if err != nil {
return "", err
}
return clusterInfo.getResourceVersion(gk), nil
})
if err != nil {
return err
}
for event := range ch {
if event.WatchEvent != nil {
eventObj := event.WatchEvent.Object.(*unstructured.Unstructured)
if kube.IsCRD(eventObj) {
// restart if new CRD has been created after watch started
if event.WatchEvent.Type == watch.Added {
c.removeCluster(item.Server)
return fmt.Errorf("Restarting the watch because a new CRD %s was added", eventObj.GetName())
} else if event.WatchEvent.Type == watch.Deleted {
c.removeCluster(item.Server)
return fmt.Errorf("Restarting the watch because CRD %s was deleted", eventObj.GetName())
}
}
err = c.processEvent(event.WatchEvent.Type, eventObj, item.Server)
if err != nil {
log.Warnf("Failed to process event %s for obj %v: %v", event.WatchEvent.Type, event.WatchEvent.Object, err)
}
} else {
err = c.updateCache(item.Server, event.CacheRefresh.GVK.GroupKind(), event.CacheRefresh.ResourceVersion, event.CacheRefresh.Objects)
if err != nil {
log.Warnf("Failed to process event %s for obj %v: %v", event.WatchEvent.Type, event.WatchEvent.Object, err)
}
}
}
return fmt.Errorf("resource updates channel has closed")
}, fmt.Sprintf("watch app resources on %s", item.Server), ctx, clusterRetryTimeout)
}
func (c *liveStateCache) updateCache(server string, gk schema.GroupKind, resourceVersion string, objs []unstructured.Unstructured) error {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
return err
}
clusterInfo.updateCache(gk, resourceVersion, objs)
return nil
}

View File

@@ -1,129 +0,0 @@
package cache
import (
"context"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/kube/kubetest"
)
const (
pollInterval = 500 * time.Millisecond
)
func TestWatchClusterResourcesHandlesResourceEvents(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
events := make(chan kube.WatchEvent)
defer func() {
cancel()
close(events)
}()
pod := testPod.DeepCopy()
kubeMock := &kubetest.MockKubectlCmd{
Resources: []kube.ResourcesBatch{{
GVK: pod.GroupVersionKind(),
Objects: make([]unstructured.Unstructured, 0),
}},
Events: events,
}
server := "https://test"
clusterCache := newClusterExt(kubeMock)
cache := &liveStateCache{
clusters: map[string]*clusterInfo{server: clusterCache},
lock: &sync.Mutex{},
kubectl: kubeMock,
}
go cache.watchClusterResources(ctx, v1alpha1.Cluster{Server: server})
assert.False(t, clusterCache.synced())
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: pod, Type: watch.Added}}
err := wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
_, hasPod := clusterCache.nodes[kube.GetResourceKey(pod)]
return hasPod, nil
})
assert.Nil(t, err)
pod.SetResourceVersion("updated-resource-version")
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: pod, Type: watch.Modified}}
err = wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
updatedPodInfo, hasPod := clusterCache.nodes[kube.GetResourceKey(pod)]
return hasPod && updatedPodInfo.resourceVersion == "updated-resource-version", nil
})
assert.Nil(t, err)
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: pod, Type: watch.Deleted}}
err = wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
_, hasPod := clusterCache.nodes[kube.GetResourceKey(pod)]
return !hasPod, nil
})
assert.Nil(t, err)
}
func TestClusterCacheDroppedOnCreatedDeletedCRD(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
events := make(chan kube.WatchEvent)
defer func() {
cancel()
close(events)
}()
kubeMock := &kubetest.MockKubectlCmd{
Resources: []kube.ResourcesBatch{{
GVK: testCRD.GroupVersionKind(),
Objects: make([]unstructured.Unstructured, 0),
}},
Events: events,
}
server := "https://test"
clusterCache := newClusterExt(kubeMock)
cache := &liveStateCache{
clusters: map[string]*clusterInfo{server: clusterCache},
lock: &sync.Mutex{},
kubectl: kubeMock,
}
go cache.watchClusterResources(ctx, v1alpha1.Cluster{Server: server})
err := clusterCache.ensureSynced()
assert.Nil(t, err)
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: testCRD, Type: watch.Added}}
err = wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
cache.lock.Lock()
defer cache.lock.Unlock()
_, hasCache := cache.clusters[server]
return !hasCache, nil
})
assert.Nil(t, err)
cache.clusters[server] = clusterCache
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: testCRD, Type: watch.Deleted}}
err = wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
cache.lock.Lock()
defer cache.lock.Unlock()
_, hasCache := cache.clusters[server]
return !hasCache, nil
})
assert.Nil(t, err)
}

View File

@@ -1,13 +1,19 @@
package cache
import (
"context"
"fmt"
"runtime/debug"
"sort"
"strings"
"sync"
"time"
"k8s.io/apimachinery/pkg/types"
"github.com/argoproj/argo-cd/controller/metrics"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -16,49 +22,43 @@ import (
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/health"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/settings"
)
const (
clusterSyncTimeout = 24 * time.Hour
clusterRetryTimeout = 10 * time.Second
clusterSyncTimeout = 24 * time.Hour
clusterRetryTimeout = 10 * time.Second
watchResourcesRetryTimeout = 1 * time.Second
)
type gkInfo struct {
resource metav1.APIResource
type apiMeta struct {
namespaced bool
resourceVersion string
watchCancel context.CancelFunc
}
type clusterInfo struct {
apis map[schema.GroupKind]*gkInfo
nodes map[kube.ResourceKey]*node
nsIndex map[string]map[kube.ResourceKey]*node
lock *sync.Mutex
onAppUpdated func(appName string)
kubectl kube.Kubectl
cluster *appv1.Cluster
syncLock *sync.Mutex
syncTime *time.Time
syncError error
log *log.Entry
settings *settings.ArgoCDSettings
syncLock *sync.Mutex
syncTime *time.Time
syncError error
apisMeta map[schema.GroupKind]*apiMeta
lock *sync.Mutex
nodes map[kube.ResourceKey]*node
nsIndex map[string]map[kube.ResourceKey]*node
onAppUpdated AppUpdatedHandler
kubectl kube.Kubectl
cluster *appv1.Cluster
log *log.Entry
cacheSettingsSrc func() *cacheSettings
}
func (c *clusterInfo) getResourceVersion(gk schema.GroupKind) string {
func (c *clusterInfo) replaceResourceCache(gk schema.GroupKind, resourceVersion string, objs []unstructured.Unstructured) {
c.lock.Lock()
defer c.lock.Unlock()
info, ok := c.apis[gk]
if ok {
return info.resourceVersion
}
return ""
}
func (c *clusterInfo) updateCache(gk schema.GroupKind, resourceVersion string, objs []unstructured.Unstructured) {
c.lock.Lock()
defer c.lock.Unlock()
info, ok := c.apis[gk]
info, ok := c.apisMeta[gk]
if ok {
objByKind := make(map[kube.ResourceKey]*unstructured.Unstructured)
for i := range objs {
@@ -85,33 +85,29 @@ func (c *clusterInfo) updateCache(gk schema.GroupKind, resourceVersion string, o
}
}
func createObjInfo(un *unstructured.Unstructured, appInstanceLabel string) *node {
func (c *clusterInfo) createObjInfo(un *unstructured.Unstructured, appInstanceLabel string) *node {
ownerRefs := un.GetOwnerReferences()
// Special case for endpoint. Remove after https://github.com/kubernetes/kubernetes/issues/28483 is fixed
if un.GroupVersionKind().Group == "" && un.GetKind() == kube.EndpointsKind && len(un.GetOwnerReferences()) == 0 {
ownerRefs = append(ownerRefs, metav1.OwnerReference{
Name: un.GetName(),
Kind: kube.ServiceKind,
APIVersion: "",
APIVersion: "v1",
})
}
info := &node{
nodeInfo := &node{
resourceVersion: un.GetResourceVersion(),
ref: v1.ObjectReference{
APIVersion: un.GetAPIVersion(),
Kind: un.GetKind(),
Name: un.GetName(),
Namespace: un.GetNamespace(),
},
ownerRefs: ownerRefs,
info: getNodeInfo(un),
ref: kube.GetObjectRef(un),
ownerRefs: ownerRefs,
}
populateNodeInfo(un, nodeInfo)
appName := kube.GetAppInstanceLabel(un, appInstanceLabel)
if len(ownerRefs) == 0 && appName != "" {
info.appName = appName
info.resource = un
nodeInfo.appName = appName
nodeInfo.resource = un
}
return info
nodeInfo.health, _ = health.GetResourceHealth(un, c.cacheSettingsSrc().ResourceOverrides)
return nodeInfo
}
func (c *clusterInfo) setNode(n *node) {
@@ -136,7 +132,13 @@ func (c *clusterInfo) removeNode(key kube.ResourceKey) {
}
func (c *clusterInfo) invalidate() {
c.syncLock.Lock()
defer c.syncLock.Unlock()
c.syncTime = nil
for i := range c.apisMeta {
c.apisMeta[i].watchCancel()
}
c.apisMeta = nil
}
func (c *clusterInfo) synced() bool {
@@ -149,38 +151,158 @@ func (c *clusterInfo) synced() bool {
return time.Now().Before(c.syncTime.Add(clusterSyncTimeout))
}
func (c *clusterInfo) sync() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
}()
func (c *clusterInfo) stopWatching(gk schema.GroupKind) {
c.syncLock.Lock()
defer c.syncLock.Unlock()
if info, ok := c.apisMeta[gk]; ok {
info.watchCancel()
delete(c.apisMeta, gk)
c.replaceResourceCache(gk, "", []unstructured.Unstructured{})
log.Warnf("Stop watching %s not found on %s.", gk, c.cluster.Server)
}
}
c.log.Info("Start syncing cluster")
// startMissingWatches lists supported cluster resources and start watching for changes unless watch is already running
func (c *clusterInfo) startMissingWatches() error {
c.apis = make(map[schema.GroupKind]*gkInfo)
c.nodes = make(map[kube.ResourceKey]*node)
resources, err := c.kubectl.GetResources(c.cluster.RESTConfig(), c.settings, "")
apis, err := c.kubectl.GetAPIResources(c.cluster.RESTConfig(), c.cacheSettingsSrc().ResourcesFilter)
if err != nil {
log.Errorf("Failed to sync cluster %s: %v", c.cluster.Server, err)
return err
}
appLabelKey := c.settings.GetAppInstanceLabelKey()
for res := range resources {
if res.Error != nil {
return res.Error
for i := range apis {
api := apis[i]
if _, ok := c.apisMeta[api.GroupKind]; !ok {
ctx, cancel := context.WithCancel(context.Background())
info := &apiMeta{namespaced: api.Meta.Namespaced, watchCancel: cancel}
c.apisMeta[api.GroupKind] = info
go c.watchEvents(ctx, api, info)
}
if _, ok := c.apis[res.GVK.GroupKind()]; !ok {
c.apis[res.GVK.GroupKind()] = &gkInfo{
resourceVersion: res.ListResourceVersion,
resource: res.ResourceInfo,
}
return nil
}
func runSynced(lock *sync.Mutex, action func() error) error {
lock.Lock()
defer lock.Unlock()
return action()
}
func (c *clusterInfo) watchEvents(ctx context.Context, api kube.APIResourceInfo, info *apiMeta) {
util.RetryUntilSucceed(func() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
}()
err = runSynced(c.syncLock, func() error {
if info.resourceVersion == "" {
list, err := api.Interface.List(metav1.ListOptions{})
if err != nil {
return err
}
c.replaceResourceCache(api.GroupKind, list.GetResourceVersion(), list.Items)
}
return nil
})
if err != nil {
return err
}
w, err := api.Interface.Watch(metav1.ListOptions{ResourceVersion: info.resourceVersion})
if errors.IsNotFound(err) {
c.stopWatching(api.GroupKind)
return nil
}
err = runSynced(c.syncLock, func() error {
if errors.IsGone(err) {
info.resourceVersion = ""
log.Warnf("Resource version of %s on %s is too old.", api.GroupKind, c.cluster.Server)
}
return err
})
if err != nil {
return err
}
defer w.Stop()
for {
select {
case <-ctx.Done():
return nil
case event, ok := <-w.ResultChan():
if ok {
obj := event.Object.(*unstructured.Unstructured)
info.resourceVersion = obj.GetResourceVersion()
c.processEvent(event.Type, obj)
if kube.IsCRD(obj) {
if event.Type == watch.Deleted {
group, groupOk, groupErr := unstructured.NestedString(obj.Object, "spec", "group")
kind, kindOk, kindErr := unstructured.NestedString(obj.Object, "spec", "names", "kind")
if groupOk && groupErr == nil && kindOk && kindErr == nil {
gk := schema.GroupKind{Group: group, Kind: kind}
c.stopWatching(gk)
}
} else {
err = runSynced(c.syncLock, func() error {
return c.startMissingWatches()
})
}
}
if err != nil {
log.Warnf("Failed to start missing watch: %v", err)
}
} else {
return fmt.Errorf("Watch %s on %s has closed", api.GroupKind, c.cluster.Server)
}
}
}
for i := range res.Objects {
c.setNode(createObjInfo(&res.Objects[i], appLabelKey))
}, fmt.Sprintf("watch %s on %s", api.GroupKind, c.cluster.Server), ctx, watchResourcesRetryTimeout)
}
func (c *clusterInfo) sync() (err error) {
c.log.Info("Start syncing cluster")
for i := range c.apisMeta {
c.apisMeta[i].watchCancel()
}
c.apisMeta = make(map[schema.GroupKind]*apiMeta)
c.nodes = make(map[kube.ResourceKey]*node)
apis, err := c.kubectl.GetAPIResources(c.cluster.RESTConfig(), c.cacheSettingsSrc().ResourcesFilter)
if err != nil {
return err
}
lock := sync.Mutex{}
err = util.RunAllAsync(len(apis), func(i int) error {
api := apis[i]
list, err := api.Interface.List(metav1.ListOptions{})
if err != nil {
return err
}
lock.Lock()
for i := range list.Items {
c.setNode(c.createObjInfo(&list.Items[i], c.cacheSettingsSrc().AppInstanceLabelKey))
}
lock.Unlock()
return nil
})
if err == nil {
err = c.startMissingWatches()
}
if err != nil {
log.Errorf("Failed to sync cluster %s: %v", c.cluster.Server, err)
return err
}
c.log.Info("Cluster successfully synced")
@@ -188,9 +310,6 @@ func (c *clusterInfo) sync() (err error) {
}
func (c *clusterInfo) ensureSynced() error {
if c.synced() {
return c.syncError
}
c.syncLock.Lock()
defer c.syncLock.Unlock()
if c.synced() {
@@ -204,29 +323,47 @@ func (c *clusterInfo) ensureSynced() error {
return c.syncError
}
func (c *clusterInfo) getChildren(obj *unstructured.Unstructured) []appv1.ResourceNode {
func (c *clusterInfo) iterateHierarchy(obj *unstructured.Unstructured, action func(child appv1.ResourceNode)) {
c.lock.Lock()
defer c.lock.Unlock()
children := make([]appv1.ResourceNode, 0)
if objInfo, ok := c.nodes[kube.GetResourceKey(obj)]; ok {
nsNodes := c.nsIndex[obj.GetNamespace()]
key := kube.GetResourceKey(obj)
if objInfo, ok := c.nodes[key]; ok {
action(objInfo.asResourceNode())
nsNodes := c.nsIndex[key.Namespace]
childrenByUID := make(map[types.UID][]*node)
for _, child := range nsNodes {
if objInfo.isParentOf(child) {
children = append(children, child.childResourceNodes(nsNodes, map[kube.ResourceKey]bool{objInfo.resourceKey(): true}))
childrenByUID[child.ref.UID] = append(childrenByUID[child.ref.UID], child)
}
}
// make sure children has no duplicates
for _, children := range childrenByUID {
if len(children) > 0 {
// The object might have multiple children with the same UID (e.g. replicaset from apps and extensions group). It is ok to pick any object but we need to make sure
// we pick the same child after every refresh.
sort.Slice(children, func(i, j int) bool {
key1 := children[i].resourceKey()
key2 := children[j].resourceKey()
return strings.Compare(key1.String(), key2.String()) < 0
})
child := children[0]
action(child.asResourceNode())
child.iterateChildren(nsNodes, map[kube.ResourceKey]bool{objInfo.resourceKey(): true}, action)
}
}
} else {
action(c.createObjInfo(obj, c.cacheSettingsSrc().AppInstanceLabelKey).asResourceNode())
}
return children
}
func (c *clusterInfo) isNamespaced(gk schema.GroupKind) bool {
if api, ok := c.apis[gk]; ok && !api.resource.Namespaced {
func (c *clusterInfo) isNamespaced(obj *unstructured.Unstructured) bool {
if api, ok := c.apisMeta[kube.GetResourceKey(obj).GroupKind()]; ok && !api.namespaced {
return false
}
return true
}
func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured, metricsServer *metrics.MetricsServer) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
c.lock.Lock()
defer c.lock.Unlock()
@@ -237,12 +374,13 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
managedObjs[key] = o.resource
}
}
config := metrics.AddMetricsTransportWrapper(metricsServer, a, c.cluster.RESTConfig())
// iterate target objects and identify ones that already exist in the cluster,\
// but are simply missing our label
lock := &sync.Mutex{}
err := util.RunAllAsync(len(targetObjs), func(i int) error {
targetObj := targetObjs[i]
key := GetTargetObjKey(a, targetObj, c.isNamespaced(targetObj.GroupVersionKind().GroupKind()))
key := GetTargetObjKey(a, targetObj, c.isNamespaced(targetObj))
lock.Lock()
managedObj := managedObjs[key]
lock.Unlock()
@@ -253,22 +391,40 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
managedObj = existingObj.resource
} else {
var err error
managedObj, err = c.kubectl.GetResource(c.cluster.RESTConfig(), targetObj.GroupVersionKind(), existingObj.ref.Name, existingObj.ref.Namespace)
managedObj, err = c.kubectl.GetResource(config, targetObj.GroupVersionKind(), existingObj.ref.Name, existingObj.ref.Namespace)
if err != nil {
if errors.IsNotFound(err) {
c.checkAndInvalidateStaleCache(targetObj.GroupVersionKind(), existingObj.ref.Namespace, existingObj.ref.Name)
return nil
}
return err
}
}
} else if _, watched := c.apisMeta[key.GroupKind()]; !watched {
var err error
managedObj, err = c.kubectl.GetResource(config, targetObj.GroupVersionKind(), targetObj.GetName(), targetObj.GetNamespace())
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
}
}
if managedObj != nil {
managedObj, err := c.kubectl.ConvertToVersion(managedObj, targetObj.GroupVersionKind().Group, targetObj.GroupVersionKind().Version)
converted, err := c.kubectl.ConvertToVersion(managedObj, targetObj.GroupVersionKind().Group, targetObj.GroupVersionKind().Version)
if err != nil {
return err
// fallback to loading resource from kubernetes if conversion fails
log.Warnf("Failed to convert resource: %v", err)
managedObj, err = c.kubectl.GetResource(config, targetObj.GroupVersionKind(), managedObj.GetName(), managedObj.GetNamespace())
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
} else {
managedObj = converted
}
lock.Lock()
managedObjs[key] = managedObj
@@ -283,37 +439,10 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
return managedObjs, nil
}
func (c *clusterInfo) delete(obj *unstructured.Unstructured) error {
err := c.kubectl.DeleteResource(c.cluster.RESTConfig(), obj.GroupVersionKind(), obj.GetName(), obj.GetNamespace(), false)
if err != nil && errors.IsNotFound(err) {
// a delete request came in for an object which does not exist. it's possible that our cache
// is stale. Check and invalidate if it is
c.lock.Lock()
c.checkAndInvalidateStaleCache(obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName())
c.lock.Unlock()
return nil
}
return err
}
// checkAndInvalidateStaleCache checks if our cache is stale and invalidate it based on error
// should be called whenever we suspect our cache is stale
func (c *clusterInfo) checkAndInvalidateStaleCache(gvk schema.GroupVersionKind, namespace string, name string) {
if _, ok := c.nodes[kube.NewResourceKey(gvk.Group, gvk.Kind, namespace, name)]; ok {
if c.syncTime != nil {
c.log.Warnf("invalidated stale cache due to mismatch of %s, %s/%s", gvk, namespace, name)
c.invalidate()
}
}
}
func (c *clusterInfo) processEvent(event watch.EventType, un *unstructured.Unstructured) error {
func (c *clusterInfo) processEvent(event watch.EventType, un *unstructured.Unstructured) {
c.lock.Lock()
defer c.lock.Unlock()
key := kube.GetResourceKey(un)
if info, ok := c.apis[schema.GroupKind{Group: key.Group, Kind: key.Kind}]; ok {
info.resourceVersion = un.GetResourceVersion()
}
existingNode, exists := c.nodes[key]
if event == watch.Deleted {
if exists {
@@ -322,8 +451,6 @@ func (c *clusterInfo) processEvent(event watch.EventType, un *unstructured.Unstr
} else if event != watch.Deleted {
c.onNodeUpdated(exists, existingNode, un, key)
}
return nil
}
func (c *clusterInfo) onNodeUpdated(exists bool, existingNode *node, un *unstructured.Unstructured, key kube.ResourceKey) {
@@ -331,7 +458,7 @@ func (c *clusterInfo) onNodeUpdated(exists bool, existingNode *node, un *unstruc
if exists {
nodes = append(nodes, existingNode)
}
newObj := createObjInfo(un, c.settings.GetAppInstanceLabelKey())
newObj := c.createObjInfo(un, c.cacheSettingsSrc().AppInstanceLabelKey)
c.setNode(newObj)
nodes = append(nodes, newObj)
toNotify := make(map[string]bool)
@@ -342,18 +469,23 @@ func (c *clusterInfo) onNodeUpdated(exists bool, existingNode *node, un *unstruc
if app == "" || skipAppRequeing(key) {
continue
}
toNotify[app] = true
toNotify[app] = n.isRootAppNode() || toNotify[app]
}
}
for name := range toNotify {
c.onAppUpdated(name)
for name, isRootAppNode := range toNotify {
c.onAppUpdated(name, isRootAppNode, newObj.ref)
}
}
func (c *clusterInfo) onNodeRemoved(key kube.ResourceKey, existingNode *node) {
func (c *clusterInfo) onNodeRemoved(key kube.ResourceKey, n *node) {
appName := n.appName
if ns, ok := c.nsIndex[key.Namespace]; ok {
appName = n.getApp(ns)
}
c.removeNode(key)
if existingNode.appName != "" {
c.onAppUpdated(existingNode.appName)
if appName != "" {
c.onAppUpdated(appName, n.isRootAppNode(), n.ref)
}
}

View File

@@ -1,6 +1,7 @@
package cache
import (
"fmt"
"sort"
"strings"
"sync"
@@ -12,14 +13,16 @@ import (
corev1 "k8s.io/api/core/v1"
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/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic/fake"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/kube/kubetest"
"github.com/argoproj/argo-cd/util/settings"
)
func strToUnstructured(jsonStr string) *unstructured.Unstructured {
@@ -40,107 +43,184 @@ var (
apiVersion: v1
kind: Pod
metadata:
uid: "1"
name: helm-guestbook-pod
namespace: default
ownerReferences:
- apiVersion: extensions/v1beta1
- apiVersion: apps/v1
kind: ReplicaSet
name: helm-guestbook-rs
uid: "2"
resourceVersion: "123"`)
testRS = strToUnstructured(`
apiVersion: v1
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: ReplicaSet
metadata:
uid: "2"
name: helm-guestbook-rs
namespace: default
annotations:
deployment.kubernetes.io/revision: "2"
ownerReferences:
- apiVersion: extensions/v1beta1
- apiVersion: apps/v1beta1
kind: Deployment
name: helm-guestbook
uid: "3"
resourceVersion: "123"`)
testDeploy = strToUnstructured(`
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/instance: helm-guestbook
uid: "3"
name: helm-guestbook
namespace: default
resourceVersion: "123"`)
testCRD = strToUnstructured(`
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
testService = strToUnstructured(`
apiVersion: v1
kind: Service
metadata:
name: my-custom-resource-definition
resourceVersion: "123"`)
name: helm-guestbook
namespace: default
resourceVersion: "123"
uid: "4"
spec:
selector:
app: guestbook
type: LoadBalancer
status:
loadBalancer:
ingress:
- hostname: localhost`)
testIngress = strToUnstructured(`
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: helm-guestbook
namespace: default
uid: "4"
spec:
backend:
serviceName: not-found-service
servicePort: 443
rules:
- host: helm-guestbook.com
http:
paths:
- backend:
serviceName: helm-guestbook
servicePort: 443
path: /
- backend:
serviceName: helm-guestbook
servicePort: https
path: /
status:
loadBalancer:
ingress:
- ip: 107.178.210.11`)
)
func newCluster(objs ...*unstructured.Unstructured) *clusterInfo {
resByGVK := make(map[schema.GroupVersionKind][]unstructured.Unstructured)
runtimeObjs := make([]runtime.Object, len(objs))
for i := range objs {
resByGVK[objs[i].GroupVersionKind()] = append(resByGVK[objs[i].GroupVersionKind()], *objs[i])
runtimeObjs[i] = objs[i]
}
resources := make([]kube.ResourcesBatch, 0)
for gvk, objects := range resByGVK {
resources = append(resources, kube.ResourcesBatch{
ListResourceVersion: "1",
GVK: gvk,
Objects: objects,
})
}
return newClusterExt(kubetest.MockKubectlCmd{
Resources: resources,
})
scheme := runtime.NewScheme()
client := fake.NewSimpleDynamicClient(scheme, runtimeObjs...)
apiResources := []kube.APIResourceInfo{{
GroupKind: schema.GroupKind{Group: "", Kind: "Pod"},
Interface: client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}),
Meta: metav1.APIResource{Namespaced: true},
}, {
GroupKind: schema.GroupKind{Group: "apps", Kind: "ReplicaSet"},
Interface: client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicasets"}),
Meta: metav1.APIResource{Namespaced: true},
}, {
GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
Interface: client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}),
Meta: metav1.APIResource{Namespaced: true},
}}
return newClusterExt(&kubetest.MockKubectlCmd{APIResources: apiResources})
}
func newClusterExt(kubectl kube.Kubectl) *clusterInfo {
return &clusterInfo{
lock: &sync.Mutex{},
nodes: make(map[kube.ResourceKey]*node),
onAppUpdated: func(appName string) {},
onAppUpdated: func(appName string, fullRefresh bool, reference corev1.ObjectReference) {},
kubectl: kubectl,
nsIndex: make(map[string]map[kube.ResourceKey]*node),
cluster: &appv1.Cluster{},
syncTime: nil,
syncLock: &sync.Mutex{},
apis: make(map[schema.GroupKind]*gkInfo),
apisMeta: make(map[schema.GroupKind]*apiMeta),
log: log.WithField("cluster", "test"),
settings: &settings.ArgoCDSettings{},
cacheSettingsSrc: func() *cacheSettings {
return &cacheSettings{AppInstanceLabelKey: common.LabelKeyAppInstance}
},
}
}
func getChildren(cluster *clusterInfo, un *unstructured.Unstructured) []appv1.ResourceNode {
hierarchy := make([]appv1.ResourceNode, 0)
cluster.iterateHierarchy(un, func(child appv1.ResourceNode) {
hierarchy = append(hierarchy, child)
})
return hierarchy[1:]
}
func TestGetChildren(t *testing.T) {
cluster := newCluster(testPod, testRS, testDeploy)
err := cluster.ensureSynced()
assert.Nil(t, err)
rsChildren := cluster.getChildren(testRS)
rsChildren := getChildren(cluster, testRS)
assert.Equal(t, []appv1.ResourceNode{{
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod",
Group: "",
Version: "v1",
ResourceRef: appv1.ResourceRef{
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod",
Group: "",
Version: "v1",
UID: "1",
},
ParentRefs: []appv1.ResourceRef{{
Group: "apps",
Version: "",
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
UID: "2",
}},
Health: &appv1.HealthStatus{Status: appv1.HealthStatusUnknown},
NetworkingInfo: &appv1.ResourceNetworkingInfo{Labels: testPod.GetLabels()},
ResourceVersion: "123",
Info: []appv1.InfoItem{{Name: "Containers", Value: "0/0"}},
Children: make([]appv1.ResourceNode, 0),
ResourceVersion: "123",
}}, rsChildren)
deployChildren := cluster.getChildren(testDeploy)
deployChildren := getChildren(cluster, testDeploy)
assert.Equal(t, []appv1.ResourceNode{{
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
Group: "extensions",
Version: "v1beta1",
assert.Equal(t, append([]appv1.ResourceNode{{
ResourceRef: appv1.ResourceRef{
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
Group: "apps",
Version: "v1",
UID: "2",
},
ResourceVersion: "123",
Children: rsChildren,
Info: []appv1.InfoItem{},
}}, deployChildren)
Health: &appv1.HealthStatus{Status: appv1.HealthStatusHealthy},
Info: []appv1.InfoItem{{Name: "Revision", Value: "Rev:2"}},
ParentRefs: []appv1.ResourceRef{{Group: "apps", Version: "", Kind: "Deployment", Namespace: "default", Name: "helm-guestbook", UID: "3"}},
}}, rsChildren...), deployChildren)
}
func TestGetManagedLiveObjs(t *testing.T) {
@@ -149,7 +229,7 @@ func TestGetManagedLiveObjs(t *testing.T) {
assert.Nil(t, err)
targetDeploy := strToUnstructured(`
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: helm-guestbook
@@ -163,7 +243,7 @@ metadata:
Namespace: "default",
},
},
}, []*unstructured.Unstructured{targetDeploy})
}, []*unstructured.Unstructured{targetDeploy}, nil)
assert.Nil(t, err)
assert.Equal(t, managedObjs, map[kube.ResourceKey]*unstructured.Unstructured{
kube.NewResourceKey("apps", "Deployment", "default", "helm-guestbook"): testDeploy,
@@ -175,10 +255,9 @@ func TestChildDeletedEvent(t *testing.T) {
err := cluster.ensureSynced()
assert.Nil(t, err)
err = cluster.processEvent(watch.Deleted, testPod)
assert.Nil(t, err)
cluster.processEvent(watch.Deleted, testPod)
rsChildren := cluster.getChildren(testRS)
rsChildren := getChildren(cluster, testRS)
assert.Equal(t, []appv1.ResourceNode{}, rsChildren)
}
@@ -191,38 +270,63 @@ func TestProcessNewChildEvent(t *testing.T) {
apiVersion: v1
kind: Pod
metadata:
uid: "4"
name: helm-guestbook-pod2
namespace: default
ownerReferences:
- apiVersion: extensions/v1beta1
- apiVersion: apps/v1
kind: ReplicaSet
name: helm-guestbook-rs
uid: "2"
resourceVersion: "123"`)
err = cluster.processEvent(watch.Added, newPod)
assert.Nil(t, err)
cluster.processEvent(watch.Added, newPod)
rsChildren := cluster.getChildren(testRS)
rsChildren := getChildren(cluster, testRS)
sort.Slice(rsChildren, func(i, j int) bool {
return strings.Compare(rsChildren[i].Name, rsChildren[j].Name) < 0
})
assert.Equal(t, []appv1.ResourceNode{{
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod",
Group: "",
Version: "v1",
Info: []appv1.InfoItem{{Name: "Containers", Value: "0/0"}},
Children: make([]appv1.ResourceNode, 0),
ResourceRef: appv1.ResourceRef{
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod",
Group: "",
Version: "v1",
UID: "1",
},
Info: []appv1.InfoItem{{Name: "Containers", Value: "0/0"}},
Health: &appv1.HealthStatus{Status: appv1.HealthStatusUnknown},
NetworkingInfo: &appv1.ResourceNetworkingInfo{Labels: testPod.GetLabels()},
ParentRefs: []appv1.ResourceRef{{
Group: "apps",
Version: "",
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
UID: "2",
}},
ResourceVersion: "123",
}, {
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod2",
Group: "",
Version: "v1",
Info: []appv1.InfoItem{{Name: "Containers", Value: "0/0"}},
Children: make([]appv1.ResourceNode, 0),
ResourceRef: appv1.ResourceRef{
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod2",
Group: "",
Version: "v1",
UID: "4",
},
NetworkingInfo: &appv1.ResourceNetworkingInfo{Labels: testPod.GetLabels()},
Info: []appv1.InfoItem{{Name: "Containers", Value: "0/0"}},
Health: &appv1.HealthStatus{Status: appv1.HealthStatusUnknown},
ParentRefs: []appv1.ResourceRef{{
Group: "apps",
Version: "",
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
UID: "2",
}},
ResourceVersion: "123",
}}, rsChildren)
}
@@ -257,8 +361,7 @@ func TestUpdateResourceTags(t *testing.T) {
},
}},
}
err = cluster.processEvent(watch.Modified, mustToUnstructured(pod))
assert.Nil(t, err)
cluster.processEvent(watch.Modified, mustToUnstructured(pod))
podNode = cluster.nodes[kube.GetResourceKey(mustToUnstructured(pod))]
@@ -269,17 +372,16 @@ func TestUpdateResourceTags(t *testing.T) {
func TestUpdateAppResource(t *testing.T) {
updatesReceived := make([]string, 0)
cluster := newCluster(testPod, testRS, testDeploy)
cluster.onAppUpdated = func(appName string) {
updatesReceived = append(updatesReceived, appName)
cluster.onAppUpdated = func(appName string, fullRefresh bool, _ corev1.ObjectReference) {
updatesReceived = append(updatesReceived, fmt.Sprintf("%s: %v", appName, fullRefresh))
}
err := cluster.ensureSynced()
assert.Nil(t, err)
err = cluster.processEvent(watch.Modified, mustToUnstructured(testPod))
assert.Nil(t, err)
cluster.processEvent(watch.Modified, mustToUnstructured(testPod))
assert.Equal(t, []string{"helm-guestbook"}, updatesReceived)
assert.Contains(t, updatesReceived, "helm-guestbook: false")
}
func TestCircularReference(t *testing.T) {
@@ -294,8 +396,13 @@ func TestCircularReference(t *testing.T) {
assert.Nil(t, err)
children := cluster.getChildren(dep)
assert.Len(t, children, 1)
children := getChildren(cluster, dep)
assert.Len(t, children, 2)
node := cluster.nodes[kube.GetResourceKey(dep)]
assert.NotNil(t, node)
app := node.getApp(cluster.nodes)
assert.Equal(t, "", app)
}
func TestWatchCacheUpdated(t *testing.T) {
@@ -316,7 +423,7 @@ func TestWatchCacheUpdated(t *testing.T) {
podGroupKind := testPod.GroupVersionKind().GroupKind()
cluster.updateCache(podGroupKind, "updated-list-version", []unstructured.Unstructured{*updated, *added})
cluster.replaceResourceCache(podGroupKind, "updated-list-version", []unstructured.Unstructured{*updated, *added})
_, ok := cluster.nodes[kube.GetResourceKey(removed)]
assert.False(t, ok)
@@ -327,6 +434,22 @@ func TestWatchCacheUpdated(t *testing.T) {
_, ok = cluster.nodes[kube.GetResourceKey(added)]
assert.True(t, ok)
assert.Equal(t, cluster.getResourceVersion(podGroupKind), "updated-list-version")
}
func TestGetDuplicatedChildren(t *testing.T) {
extensionsRS := testRS.DeepCopy()
extensionsRS.SetGroupVersionKind(schema.GroupVersionKind{Group: "extensions", Kind: kube.ReplicaSetKind, Version: "v1beta1"})
cluster := newCluster(testDeploy, testRS, extensionsRS)
err := cluster.ensureSynced()
assert.Nil(t, err)
// Get children multiple times to make sure the right child is picked up every time.
for i := 0; i < 5; i++ {
children := getChildren(cluster, testDeploy)
assert.Len(t, children, 1)
assert.Equal(t, "apps", children[0].Group)
assert.Equal(t, kube.ReplicaSetKind, children[0].Kind)
assert.Equal(t, testRS.GetName(), children[0].Name)
}
}

View File

@@ -3,29 +3,157 @@ package cache
import (
"fmt"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
k8snode "k8s.io/kubernetes/pkg/util/node"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/resource"
)
func getNodeInfo(un *unstructured.Unstructured) []v1alpha1.InfoItem {
gvk := un.GroupVersionKind()
func populateNodeInfo(un *unstructured.Unstructured, node *node) {
if gvk.Kind == kube.PodKind && gvk.Group == "" {
return getPodInfo(un)
gvk := un.GroupVersionKind()
revision := resource.GetRevision(un)
if revision > 0 {
node.info = append(node.info, v1alpha1.InfoItem{Name: "Revision", Value: fmt.Sprintf("Rev:%v", revision)})
}
switch gvk.Group {
case "":
switch gvk.Kind {
case kube.PodKind:
populatePodInfo(un, node)
return
case kube.ServiceKind:
populateServiceInfo(un, node)
return
}
case "extensions":
switch gvk.Kind {
case kube.IngressKind:
populateIngressInfo(un, node)
return
}
}
return []v1alpha1.InfoItem{}
}
func getPodInfo(un *unstructured.Unstructured) []v1alpha1.InfoItem {
func getIngress(un *unstructured.Unstructured) []v1.LoadBalancerIngress {
ingress, ok, err := unstructured.NestedSlice(un.Object, "status", "loadBalancer", "ingress")
if !ok || err != nil {
return nil
}
res := make([]v1.LoadBalancerIngress, 0)
for _, item := range ingress {
if lbIngress, ok := item.(map[string]interface{}); ok {
if hostname := lbIngress["hostname"]; hostname != nil {
res = append(res, v1.LoadBalancerIngress{Hostname: fmt.Sprintf("%s", hostname)})
} else if ip := lbIngress["ip"]; ip != nil {
res = append(res, v1.LoadBalancerIngress{IP: fmt.Sprintf("%s", ip)})
}
}
}
return res
}
func populateServiceInfo(un *unstructured.Unstructured, node *node) {
targetLabels, _, _ := unstructured.NestedStringMap(un.Object, "spec", "selector")
ingress := make([]v1.LoadBalancerIngress, 0)
if serviceType, ok, err := unstructured.NestedString(un.Object, "spec", "type"); ok && err == nil && serviceType == string(v1.ServiceTypeLoadBalancer) {
ingress = getIngress(un)
}
node.networkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetLabels: targetLabels, Ingress: ingress}
}
func populateIngressInfo(un *unstructured.Unstructured, node *node) {
ingress := getIngress(un)
targetsMap := make(map[v1alpha1.ResourceRef]bool)
if backend, ok, err := unstructured.NestedMap(un.Object, "spec", "backend"); ok && err == nil {
targetsMap[v1alpha1.ResourceRef{
Group: "",
Kind: kube.ServiceKind,
Namespace: un.GetNamespace(),
Name: fmt.Sprintf("%s", backend["serviceName"]),
}] = true
}
urlsSet := make(map[string]bool)
if rules, ok, err := unstructured.NestedSlice(un.Object, "spec", "rules"); ok && err == nil {
for i := range rules {
rule, ok := rules[i].(map[string]interface{})
if !ok {
continue
}
host := rule["host"]
if host == nil || host == "" {
for i := range ingress {
host = util.FirstNonEmpty(ingress[i].Hostname, ingress[i].IP)
if host != "" {
break
}
}
}
paths, ok, err := unstructured.NestedSlice(rule, "http", "paths")
if !ok || err != nil {
continue
}
for i := range paths {
path, ok := paths[i].(map[string]interface{})
if !ok {
continue
}
if serviceName, ok, err := unstructured.NestedString(path, "backend", "serviceName"); ok && err == nil {
targetsMap[v1alpha1.ResourceRef{
Group: "",
Kind: kube.ServiceKind,
Namespace: un.GetNamespace(),
Name: serviceName,
}] = true
}
if port, ok, err := unstructured.NestedFieldNoCopy(path, "backend", "servicePort"); ok && err == nil && host != "" && host != nil {
stringPort := ""
switch typedPod := port.(type) {
case int64:
stringPort = fmt.Sprintf("%d", typedPod)
case float64:
stringPort = fmt.Sprintf("%d", int64(typedPod))
case string:
stringPort = typedPod
default:
stringPort = fmt.Sprintf("%v", port)
}
switch stringPort {
case "80", "http":
urlsSet[fmt.Sprintf("http://%s", host)] = true
case "443", "https":
urlsSet[fmt.Sprintf("https://%s", host)] = true
default:
urlsSet[fmt.Sprintf("http://%s:%s", host, stringPort)] = true
}
}
}
}
}
targets := make([]v1alpha1.ResourceRef, 0)
for target := range targetsMap {
targets = append(targets, target)
}
urls := make([]string, 0)
for url := range urlsSet {
urls = append(urls, url)
}
node.networkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, Ingress: ingress, ExternalURLs: urls}
}
func populatePodInfo(un *unstructured.Unstructured, node *node) {
pod := v1.Pod{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &pod)
if err != nil {
return []v1alpha1.InfoItem{}
return
}
restarts := 0
totalContainers := len(pod.Spec.Containers)
@@ -36,6 +164,19 @@ func getPodInfo(un *unstructured.Unstructured) []v1alpha1.InfoItem {
reason = pod.Status.Reason
}
imagesSet := make(map[string]bool)
for _, container := range pod.Spec.InitContainers {
imagesSet[container.Image] = true
}
for _, container := range pod.Spec.Containers {
imagesSet[container.Image] = true
}
node.images = nil
for image := range imagesSet {
node.images = append(node.images, image)
}
initializing := false
for i := range pod.Status.InitContainerStatuses {
container := pod.Status.InitContainerStatuses[i]
@@ -99,9 +240,9 @@ func getPodInfo(un *unstructured.Unstructured) []v1alpha1.InfoItem {
reason = "Terminating"
}
info := make([]v1alpha1.InfoItem, 0)
if reason != "" {
info = append(info, v1alpha1.InfoItem{Name: "Status Reason", Value: reason})
node.info = append(node.info, v1alpha1.InfoItem{Name: "Status Reason", Value: reason})
}
return append(info, v1alpha1.InfoItem{Name: "Containers", Value: fmt.Sprintf("%d/%d", readyContainers, totalContainers)})
node.info = append(node.info, v1alpha1.InfoItem{Name: "Containers", Value: fmt.Sprintf("%d/%d", readyContainers, totalContainers)})
node.networkingInfo = &v1alpha1.ResourceNetworkingInfo{Labels: un.GetLabels()}
}

108
controller/cache/info_test.go vendored Normal file
View File

@@ -0,0 +1,108 @@
package cache
import (
"sort"
"strings"
"testing"
v1 "k8s.io/api/core/v1"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
"github.com/stretchr/testify/assert"
)
func TestGetPodInfo(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: helm-guestbook-pod
namespace: default
ownerReferences:
- apiVersion: extensions/v1beta1
kind: ReplicaSet
name: helm-guestbook-rs
resourceVersion: "123"
labels:
app: guestbook
spec:
containers:
- image: bar`)
node := &node{}
populateNodeInfo(pod, node)
assert.Equal(t, []v1alpha1.InfoItem{{Name: "Containers", Value: "0/1"}}, node.info)
assert.Equal(t, []string{"bar"}, node.images)
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{Labels: map[string]string{"app": "guestbook"}}, node.networkingInfo)
}
func TestGetServiceInfo(t *testing.T) {
node := &node{}
populateNodeInfo(testService, node)
assert.Equal(t, 0, len(node.info))
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
TargetLabels: map[string]string{"app": "guestbook"},
Ingress: []v1.LoadBalancerIngress{{Hostname: "localhost"}},
}, node.networkingInfo)
}
func TestGetIngressInfo(t *testing.T) {
node := &node{}
populateNodeInfo(testIngress, node)
assert.Equal(t, 0, len(node.info))
sort.Slice(node.networkingInfo.TargetRefs, func(i, j int) bool {
return strings.Compare(node.networkingInfo.TargetRefs[j].Name, node.networkingInfo.TargetRefs[i].Name) < 0
})
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
TargetRefs: []v1alpha1.ResourceRef{{
Namespace: "default",
Group: "",
Kind: kube.ServiceKind,
Name: "not-found-service",
}, {
Namespace: "default",
Group: "",
Kind: kube.ServiceKind,
Name: "helm-guestbook",
}},
ExternalURLs: []string{"https://helm-guestbook.com"},
}, node.networkingInfo)
}
func TestGetIngressInfoNoHost(t *testing.T) {
ingress := strToUnstructured(`
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: helm-guestbook
namespace: default
spec:
rules:
- http:
paths:
- backend:
serviceName: helm-guestbook
servicePort: 443
path: /
status:
loadBalancer:
ingress:
- ip: 107.178.210.11`)
node := &node{}
populateNodeInfo(ingress, node)
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
TargetRefs: []v1alpha1.ResourceRef{{
Namespace: "default",
Group: "",
Kind: kube.ServiceKind,
Name: "helm-guestbook",
}},
ExternalURLs: []string{"https://107.178.210.11"},
}, node.networkingInfo)
}

View File

@@ -2,57 +2,17 @@
package mocks
import (
"context"
)
import "github.com/argoproj/argo-cd/util/kube"
import "github.com/stretchr/testify/mock"
import "k8s.io/apimachinery/pkg/runtime/schema"
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
import "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
import context "context"
import kube "github.com/argoproj/argo-cd/util/kube"
import mock "github.com/stretchr/testify/mock"
import unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
import v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
// LiveStateCache is an autogenerated mock type for the LiveStateCache type
type LiveStateCache struct {
mock.Mock
}
// Delete provides a mock function with given fields: server, obj
func (_m *LiveStateCache) Delete(server string, obj *unstructured.Unstructured) error {
ret := _m.Called(server, obj)
var r0 error
if rf, ok := ret.Get(0).(func(string, *unstructured.Unstructured) error); ok {
r0 = rf(server, obj)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetChildren provides a mock function with given fields: server, obj
func (_m *LiveStateCache) GetChildren(server string, obj *unstructured.Unstructured) ([]v1alpha1.ResourceNode, error) {
ret := _m.Called(server, obj)
var r0 []v1alpha1.ResourceNode
if rf, ok := ret.Get(0).(func(string, *unstructured.Unstructured) []v1alpha1.ResourceNode); ok {
r0 = rf(server, obj)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]v1alpha1.ResourceNode)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *unstructured.Unstructured) error); ok {
r1 = rf(server, obj)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetManagedLiveObjs provides a mock function with given fields: a, targetObjs
func (_m *LiveStateCache) GetManagedLiveObjs(a *v1alpha1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
ret := _m.Called(a, targetObjs)
@@ -81,20 +41,20 @@ func (_m *LiveStateCache) Invalidate() {
_m.Called()
}
// IsNamespaced provides a mock function with given fields: server, gvk
func (_m *LiveStateCache) IsNamespaced(server string, gvk schema.GroupVersionKind) (bool, error) {
ret := _m.Called(server, gvk)
// IsNamespaced provides a mock function with given fields: server, obj
func (_m *LiveStateCache) IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error) {
ret := _m.Called(server, obj)
var r0 bool
if rf, ok := ret.Get(0).(func(string, schema.GroupVersionKind) bool); ok {
r0 = rf(server, gvk)
if rf, ok := ret.Get(0).(func(string, *unstructured.Unstructured) bool); ok {
r0 = rf(server, obj)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, schema.GroupVersionKind) error); ok {
r1 = rf(server, gvk)
if rf, ok := ret.Get(1).(func(string, *unstructured.Unstructured) error); ok {
r1 = rf(server, obj)
} else {
r1 = ret.Error(1)
}
@@ -102,7 +62,30 @@ func (_m *LiveStateCache) IsNamespaced(server string, gvk schema.GroupVersionKin
return r0, r1
}
// Run provides a mock function with given fields: ctx
func (_m *LiveStateCache) Run(ctx context.Context) {
_m.Called(ctx)
// IterateHierarchy provides a mock function with given fields: server, obj, action
func (_m *LiveStateCache) IterateHierarchy(server string, obj *unstructured.Unstructured, action func(v1alpha1.ResourceNode)) error {
ret := _m.Called(server, obj, action)
var r0 error
if rf, ok := ret.Get(0).(func(string, *unstructured.Unstructured, func(v1alpha1.ResourceNode)) error); ok {
r0 = rf(server, obj, action)
} else {
r0 = ret.Error(0)
}
return r0
}
// Run provides a mock function with given fields: ctx
func (_m *LiveStateCache) Run(ctx context.Context) error {
ret := _m.Called(ctx)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -6,7 +6,7 @@ import (
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -18,7 +18,16 @@ type node struct {
ownerRefs []metav1.OwnerReference
info []appv1.InfoItem
appName string
resource *unstructured.Unstructured
// available only for root application nodes
resource *unstructured.Unstructured
// networkingInfo are available only for known types involved into networking: Ingress, Service, Pod
networkingInfo *appv1.ResourceNetworkingInfo
images []string
health *appv1.HealthStatus
}
func (n *node) isRootAppNode() bool {
return n.appName != "" && len(n.ownerRefs) == 0
}
func (n *node) resourceKey() kube.ResourceKey {
@@ -26,9 +35,16 @@ func (n *node) resourceKey() kube.ResourceKey {
}
func (n *node) isParentOf(child *node) bool {
ownerGvk := n.ref.GroupVersionKind()
for _, ownerRef := range child.ownerRefs {
if kube.NewResourceKey(ownerGvk.Group, ownerRef.Kind, n.ref.Namespace, ownerRef.Name) == n.resourceKey() {
for i, ownerRef := range child.ownerRefs {
// backfill UID of inferred owner child references
if ownerRef.UID == "" && n.ref.Kind == ownerRef.Kind && n.ref.APIVersion == ownerRef.APIVersion && n.ref.Name == ownerRef.Name {
ownerRef.UID = n.ref.UID
child.ownerRefs[i] = ownerRef
return true
}
if n.ref.UID == ownerRef.UID {
return true
}
}
@@ -45,13 +61,24 @@ func ownerRefGV(ownerRef metav1.OwnerReference) schema.GroupVersion {
}
func (n *node) getApp(ns map[kube.ResourceKey]*node) string {
return n.getAppRecursive(ns, map[kube.ResourceKey]bool{})
}
func (n *node) getAppRecursive(ns map[kube.ResourceKey]*node, visited map[kube.ResourceKey]bool) string {
if !visited[n.resourceKey()] {
visited[n.resourceKey()] = true
} else {
log.Warnf("Circular dependency detected: %v.", visited)
return n.appName
}
if n.appName != "" {
return n.appName
}
for _, ownerRef := range n.ownerRefs {
gv := ownerRefGV(ownerRef)
if parent, ok := ns[kube.NewResourceKey(gv.Group, ownerRef.Kind, n.ref.Namespace, ownerRef.Name)]; ok {
app := parent.getApp(ns)
app := parent.getAppRecursive(ns, visited)
if app != "" {
return app
}
@@ -71,30 +98,45 @@ func newResourceKeySet(set map[kube.ResourceKey]bool, keys ...kube.ResourceKey)
return newSet
}
func (n *node) childResourceNodes(ns map[kube.ResourceKey]*node, parents map[kube.ResourceKey]bool) appv1.ResourceNode {
children := make([]appv1.ResourceNode, 0)
for childKey := range ns {
func (n *node) asResourceNode() appv1.ResourceNode {
gv, err := schema.ParseGroupVersion(n.ref.APIVersion)
if err != nil {
gv = schema.GroupVersion{}
}
parentRefs := make([]appv1.ResourceRef, len(n.ownerRefs))
for _, ownerRef := range n.ownerRefs {
ownerGvk := schema.FromAPIVersionAndKind(ownerRef.APIVersion, ownerRef.Kind)
ownerKey := kube.NewResourceKey(ownerGvk.Group, ownerRef.Kind, n.ref.Namespace, ownerRef.Name)
parentRefs[0] = appv1.ResourceRef{Name: ownerRef.Name, Kind: ownerKey.Kind, Namespace: n.ref.Namespace, Group: ownerKey.Group, UID: string(ownerRef.UID)}
}
return appv1.ResourceNode{
ResourceRef: appv1.ResourceRef{
UID: string(n.ref.UID),
Name: n.ref.Name,
Group: gv.Group,
Version: gv.Version,
Kind: n.ref.Kind,
Namespace: n.ref.Namespace,
},
ParentRefs: parentRefs,
Info: n.info,
ResourceVersion: n.resourceVersion,
NetworkingInfo: n.networkingInfo,
Images: n.images,
Health: n.health,
}
}
func (n *node) iterateChildren(ns map[kube.ResourceKey]*node, parents map[kube.ResourceKey]bool, action func(child appv1.ResourceNode)) {
for childKey, child := range ns {
if n.isParentOf(ns[childKey]) {
if parents[childKey] {
key := n.resourceKey()
log.Warnf("Circular dependency detected. %s is child and parent of %s", childKey.String(), key.String())
} else {
children = append(children, ns[childKey].childResourceNodes(ns, newResourceKeySet(parents, n.resourceKey())))
action(child.asResourceNode())
child.iterateChildren(ns, newResourceKeySet(parents, n.resourceKey()), action)
}
}
}
gv, err := schema.ParseGroupVersion(n.ref.APIVersion)
if err != nil {
gv = schema.GroupVersion{}
}
return appv1.ResourceNode{
Name: n.ref.Name,
Group: gv.Group,
Version: gv.Version,
Kind: n.ref.Kind,
Namespace: n.ref.Namespace,
Info: n.info,
Children: children,
ResourceVersion: n.resourceVersion,
}
}

56
controller/cache/node_test.go vendored Normal file
View File

@@ -0,0 +1,56 @@
package cache
import (
"testing"
"github.com/argoproj/argo-cd/common"
"github.com/stretchr/testify/assert"
)
var c = &clusterInfo{cacheSettingsSrc: func() *cacheSettings {
return &cacheSettings{AppInstanceLabelKey: common.LabelKeyAppInstance}
}}
func TestIsParentOf(t *testing.T) {
child := c.createObjInfo(testPod, "")
parent := c.createObjInfo(testRS, "")
grandParent := c.createObjInfo(testDeploy, "")
assert.True(t, parent.isParentOf(child))
assert.False(t, grandParent.isParentOf(child))
}
func TestIsParentOfSameKindDifferentGroupAndUID(t *testing.T) {
rs := testRS.DeepCopy()
rs.SetAPIVersion("somecrd.io/v1")
rs.SetUID("123")
child := c.createObjInfo(testPod, "")
invalidParent := c.createObjInfo(rs, "")
assert.False(t, invalidParent.isParentOf(child))
}
func TestIsServiceParentOfEndPointWithTheSameName(t *testing.T) {
nonMatchingNameEndPoint := c.createObjInfo(strToUnstructured(`
apiVersion: v1
kind: Endpoints
metadata:
name: not-matching-name
namespace: default
`), "")
matchingNameEndPoint := c.createObjInfo(strToUnstructured(`
apiVersion: v1
kind: Endpoints
metadata:
name: helm-guestbook
namespace: default
`), "")
parent := c.createObjInfo(testService, "")
assert.True(t, parent.isParentOf(matchingNameEndPoint))
assert.Equal(t, parent.ref.UID, matchingNameEndPoint.ownerRefs[0].UID)
assert.False(t, parent.isParentOf(nonMatchingNameEndPoint))
}

View File

@@ -2,6 +2,7 @@ package metrics
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
@@ -12,12 +13,16 @@ import (
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
applister "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/healthz"
)
type MetricsServer struct {
*http.Server
syncCounter *prometheus.CounterVec
reconcileHistogram *prometheus.HistogramVec
syncCounter *prometheus.CounterVec
k8sRequestCounter *prometheus.CounterVec
kubectlExecCounter *prometheus.CounterVec
kubectlExecPendingGauge *prometheus.GaugeVec
reconcileHistogram *prometheus.HistogramVec
}
const (
@@ -57,10 +62,13 @@ var (
)
// NewMetricsServer returns a new prometheus server which collects application metrics
func NewMetricsServer(addr string, appLister applister.ApplicationLister) *MetricsServer {
func NewMetricsServer(addr string, appLister applister.ApplicationLister, healthCheck func() error) *MetricsServer {
mux := http.NewServeMux()
appRegistry := NewAppRegistry(appLister)
appRegistry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
appRegistry.MustRegister(prometheus.NewGoCollector())
mux.Handle(MetricsPath, promhttp.HandlerFor(appRegistry, promhttp.HandlerOpts{}))
healthz.ServeHealthCheck(mux, healthCheck)
syncCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
@@ -70,6 +78,24 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister) *Metri
append(descAppDefaultLabels, "phase"),
)
appRegistry.MustRegister(syncCounter)
kubectlExecCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "argocd_kubectl_exec_total",
Help: "Number of kubectl executions",
}, []string{"command"})
appRegistry.MustRegister(kubectlExecCounter)
kubectlExecPendingGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "argocd_kubectl_exec_pending",
Help: "Number of pending kubectl executions",
}, []string{"command"})
appRegistry.MustRegister(kubectlExecPendingGauge)
k8sRequestCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "argocd_app_k8s_request_total",
Help: "Number of kubernetes requests executed during application reconciliation.",
},
append(descAppDefaultLabels, "response_code"),
)
appRegistry.MustRegister(k8sRequestCounter)
reconcileHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
@@ -88,8 +114,11 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister) *Metri
Addr: addr,
Handler: mux,
},
syncCounter: syncCounter,
reconcileHistogram: reconcileHistogram,
syncCounter: syncCounter,
k8sRequestCounter: k8sRequestCounter,
reconcileHistogram: reconcileHistogram,
kubectlExecCounter: kubectlExecCounter,
kubectlExecPendingGauge: kubectlExecPendingGauge,
}
}
@@ -101,11 +130,28 @@ func (m *MetricsServer) IncSync(app *argoappv1.Application, state *argoappv1.Ope
m.syncCounter.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject(), string(state.Phase)).Inc()
}
// IncKubernetesRequest increments the kubernetes requests counter for an application
func (m *MetricsServer) IncKubernetesRequest(app *argoappv1.Application, statusCode int) {
m.k8sRequestCounter.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject(), strconv.Itoa(statusCode)).Inc()
}
// IncReconcile increments the reconcile counter for an application
func (m *MetricsServer) IncReconcile(app *argoappv1.Application, duration time.Duration) {
m.reconcileHistogram.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject()).Observe(duration.Seconds())
}
func (m *MetricsServer) IncKubectlExec(command string) {
m.kubectlExecCounter.WithLabelValues(command).Inc()
}
func (m *MetricsServer) IncKubectlExecPending(command string) {
m.kubectlExecPendingGauge.WithLabelValues(command).Inc()
}
func (m *MetricsServer) DecKubectlExecPending(command string) {
m.kubectlExecPendingGauge.WithLabelValues(command).Dec()
}
type appCollector struct {
store applister.ApplicationLister
}

View File

@@ -104,6 +104,10 @@ argocd_app_sync_status{name="my-app",namespace="argocd",project="default",sync_s
argocd_app_sync_status{name="my-app",namespace="argocd",project="default",sync_status="Unknown"} 0
`
var noOpHealthCheck = func() error {
return nil
}
func newFakeApp(fakeApp string) *argoappv1.Application {
var app argoappv1.Application
err := yaml.Unmarshal([]byte(fakeApp), &app)
@@ -133,7 +137,7 @@ func newFakeLister(fakeApp ...string) (context.CancelFunc, applister.Application
func testApp(t *testing.T, fakeApp string, expectedResponse string) {
cancel, appLister := newFakeLister(fakeApp)
defer cancel()
metricsServ := NewMetricsServer("localhost:8082", appLister)
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
req, err := http.NewRequest("GET", "/metrics", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
@@ -176,7 +180,7 @@ argocd_app_sync_total{name="my-app",namespace="argocd",phase="Succeeded",project
func TestMetricsSyncCounter(t *testing.T) {
cancel, appLister := newFakeLister()
defer cancel()
metricsServ := NewMetricsServer("localhost:8082", appLister)
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
fakeApp := newFakeApp(fakeApp)
metricsServ.IncSync(fakeApp, &argoappv1.OperationState{Phase: argoappv1.OperationRunning})
@@ -217,10 +221,10 @@ argocd_app_reconcile_count{name="my-app",namespace="argocd",project="important-p
func TestReconcileMetrics(t *testing.T) {
cancel, appLister := newFakeLister()
defer cancel()
metricsServ := NewMetricsServer("localhost:8082", appLister)
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
fakeApp := newFakeApp(fakeApp)
metricsServ.IncReconcile(fakeApp, time.Duration(5*time.Second))
metricsServ.IncReconcile(fakeApp, 5*time.Second)
req, err := http.NewRequest("GET", "/metrics", nil)
assert.NoError(t, err)

View File

@@ -0,0 +1,37 @@
package metrics
import (
"net/http"
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
type metricsRoundTripper struct {
roundTripper http.RoundTripper
app *v1alpha1.Application
metricsServer *MetricsServer
}
func (mrt *metricsRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
resp, err := mrt.roundTripper.RoundTrip(r)
statusCode := 0
if resp != nil {
statusCode = resp.StatusCode
}
mrt.metricsServer.IncKubernetesRequest(mrt.app, statusCode)
return resp, err
}
// AddMetricsTransportWrapper adds a transport wrapper which increments 'argocd_app_k8s_request_total' counter on each kubernetes request
func AddMetricsTransportWrapper(server *MetricsServer, app *v1alpha1.Application, config *rest.Config) *rest.Config {
wrap := config.WrapTransport
config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
if wrap != nil {
rt = wrap(rt)
}
return &metricsRoundTripper{roundTripper: rt, metricsServer: server, app: app}
}
return config
}

View File

@@ -14,11 +14,11 @@ import (
"github.com/argoproj/argo-cd/common"
statecache "github.com/argoproj/argo-cd/controller/cache"
"github.com/argoproj/argo-cd/controller/metrics"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appv1 "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/reposerver/apiclient"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/db"
@@ -26,6 +26,7 @@ import (
"github.com/argoproj/argo-cd/util/health"
hookutil "github.com/argoproj/argo-cd/util/hook"
kubeutil "github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/resource"
"github.com/argoproj/argo-cd/util/settings"
)
@@ -49,14 +50,17 @@ func GetLiveObjs(res []managedResource) []*unstructured.Unstructured {
return objs
}
type ResourceInfoProvider interface {
IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error)
}
// AppStateManager defines methods which allow to compare application spec and actual application state.
type AppStateManager interface {
CompareAppState(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, noCache bool) (*comparisonResult, error)
CompareAppState(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, noCache bool, localObjects []string) *comparisonResult
SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
}
type comparisonResult struct {
observedAt metav1.Time
syncStatus *v1alpha1.SyncStatus
healthStatus *v1alpha1.HealthStatus
resources []v1alpha1.ResourceStatus
@@ -69,22 +73,26 @@ type comparisonResult struct {
// appStateManager allows to compare applications to git
type appStateManager struct {
metricsServer *metrics.MetricsServer
db db.ArgoDB
settings *settings.ArgoCDSettings
settingsMgr *settings.SettingsManager
appclientset appclientset.Interface
projInformer cache.SharedIndexInformer
kubectl kubeutil.Kubectl
repoClientset reposerver.Clientset
repoClientset apiclient.Clientset
liveStateCache statecache.LiveStateCache
namespace string
}
func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1.ApplicationSource, appLabelKey, revision string, noCache bool) ([]*unstructured.Unstructured, []*unstructured.Unstructured, *repository.ManifestResponse, error) {
func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1.ApplicationSource, appLabelKey, revision string, noCache bool) ([]*unstructured.Unstructured, []*unstructured.Unstructured, *apiclient.ManifestResponse, error) {
helmRepos, err := m.db.ListHelmRepos(context.Background())
if err != nil {
return nil, nil, nil, err
}
repo := m.getRepo(source.RepoURL)
repo, err := m.db.GetRepository(context.Background(), source.RepoURL)
if err != nil {
return nil, nil, nil, err
}
conn, repoClient, err := m.repoClientset.NewRepoServerClient()
if err != nil {
return nil, nil, nil, err
@@ -95,12 +103,21 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
revision = source.TargetRevision
}
tools := make([]*appv1.ConfigManagementPlugin, len(m.settings.ConfigManagementPlugins))
for i := range m.settings.ConfigManagementPlugins {
tools[i] = &m.settings.ConfigManagementPlugins[i]
plugins, err := m.settingsMgr.GetConfigManagementPlugins()
if err != nil {
return nil, nil, nil, err
}
manifestInfo, err := repoClient.GenerateManifest(context.Background(), &repository.ManifestRequest{
tools := make([]*appv1.ConfigManagementPlugin, len(plugins))
for i := range plugins {
tools[i] = &plugins[i]
}
buildOptions, err := m.settingsMgr.GetKustomizeBuildOptions()
if err != nil {
return nil, nil, nil, err
}
manifestInfo, err := repoClient.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: repo,
HelmRepos: helmRepos,
Revision: revision,
@@ -110,17 +127,30 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
Namespace: app.Spec.Destination.Namespace,
ApplicationSource: &source,
Plugins: tools,
KustomizeOptions: &appv1.KustomizeOptions{
BuildOptions: buildOptions,
},
})
if err != nil {
return nil, nil, nil, err
}
targetObjs, hooks, err := unmarshalManifests(manifestInfo.Manifests)
if err != nil {
return nil, nil, nil, err
}
return targetObjs, hooks, manifestInfo, nil
}
func unmarshalManifests(manifests []string) ([]*unstructured.Unstructured, []*unstructured.Unstructured, error) {
targetObjs := make([]*unstructured.Unstructured, 0)
hooks := make([]*unstructured.Unstructured, 0)
for _, manifest := range manifestInfo.Manifests {
for _, manifest := range manifests {
obj, err := v1alpha1.UnmarshalToUnstructured(manifest)
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}
if resource.Ignore(obj) {
continue
}
if hookutil.IsHook(obj) {
hooks = append(hooks, obj)
@@ -128,31 +158,169 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
targetObjs = append(targetObjs, obj)
}
}
return targetObjs, hooks, manifestInfo, nil
return targetObjs, hooks, nil
}
func DeduplicateTargetObjects(
server string,
namespace string,
objs []*unstructured.Unstructured,
infoProvider ResourceInfoProvider,
) ([]*unstructured.Unstructured, []v1alpha1.ApplicationCondition, error) {
targetByKey := make(map[kubeutil.ResourceKey][]*unstructured.Unstructured)
for i := range objs {
obj := objs[i]
isNamespaced, err := infoProvider.IsNamespaced(server, obj)
if err != nil {
return objs, nil, err
}
if !isNamespaced {
obj.SetNamespace("")
} else if obj.GetNamespace() == "" {
obj.SetNamespace(namespace)
}
key := kubeutil.GetResourceKey(obj)
targetByKey[key] = append(targetByKey[key], obj)
}
conditions := make([]v1alpha1.ApplicationCondition, 0)
result := make([]*unstructured.Unstructured, 0)
for key, targets := range targetByKey {
if len(targets) > 1 {
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionRepeatedResourceWarning,
Message: fmt.Sprintf("Resource %s appeared %d times among application resources.", key.String(), len(targets)),
})
}
result = append(result, targets[len(targets)-1])
}
return result, conditions, nil
}
// dedupLiveResources handles removes live resource duplicates with the same UID. Duplicates are created in a separate resource groups.
// E.g. apps/Deployment produces duplicate in extensions/Deployment, authorization.openshift.io/ClusterRole produces duplicate in rbac.authorization.k8s.io/ClusterRole etc.
// The method removes such duplicates unless it was defined in git ( exists in target resources list ). At least one duplicate stays.
// If non of duplicates are in git at random one stays
func dedupLiveResources(targetObjs []*unstructured.Unstructured, liveObjsByKey map[kubeutil.ResourceKey]*unstructured.Unstructured) {
targetObjByKey := make(map[kubeutil.ResourceKey]*unstructured.Unstructured)
for i := range targetObjs {
targetObjByKey[kubeutil.GetResourceKey(targetObjs[i])] = targetObjs[i]
}
liveObjsById := make(map[types.UID][]*unstructured.Unstructured)
for k := range liveObjsByKey {
obj := liveObjsByKey[k]
if obj != nil {
liveObjsById[obj.GetUID()] = append(liveObjsById[obj.GetUID()], obj)
}
}
for id := range liveObjsById {
objs := liveObjsById[id]
if len(objs) > 1 {
duplicatesLeft := len(objs)
for i := range objs {
obj := objs[i]
resourceKey := kubeutil.GetResourceKey(obj)
if _, ok := targetObjByKey[resourceKey]; !ok {
delete(liveObjsByKey, resourceKey)
duplicatesLeft--
if duplicatesLeft == 1 {
break
}
}
}
}
}
}
func (m *appStateManager) getComparisonSettings(app *appv1.Application) (string, map[string]v1alpha1.ResourceOverride, diff.Normalizer, error) {
resourceOverrides, err := m.settingsMgr.GetResourceOverrides()
if err != nil {
return "", nil, nil, err
}
appLabelKey, err := m.settingsMgr.GetAppInstanceLabelKey()
if err != nil {
return "", nil, nil, err
}
diffNormalizer, err := argo.NewDiffNormalizer(app.Spec.IgnoreDifferences, resourceOverrides)
if err != nil {
return "", nil, nil, err
}
return appLabelKey, resourceOverrides, diffNormalizer, nil
}
// CompareAppState compares application git state to the live app state, using the specified
// revision and supplied source. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, noCache bool) (*comparisonResult, error) {
diffNormalizer, err := argo.NewDiffNormalizer(app.Spec.IgnoreDifferences, m.settings.ResourceOverrides)
func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, noCache bool, localManifests []string) *comparisonResult {
appLabelKey, resourceOverrides, diffNormalizer, err := m.getComparisonSettings(app)
// return unknown comparison result if basic comparison settings cannot be loaded
if err != nil {
return nil, err
return &comparisonResult{
syncStatus: &v1alpha1.SyncStatus{
ComparedTo: appv1.ComparedTo{Source: source, Destination: app.Spec.Destination},
Status: appv1.SyncStatusCodeUnknown,
},
healthStatus: &appv1.HealthStatus{Status: appv1.HealthStatusUnknown},
}
}
logCtx := log.WithField("application", app.Name)
logCtx.Infof("Comparing app state (cluster: %s, namespace: %s)", app.Spec.Destination.Server, app.Spec.Destination.Namespace)
observedAt := metav1.Now()
// do best effort loading live and target state to present as much information about app state as possible
failedToLoadObjs := false
conditions := make([]v1alpha1.ApplicationCondition, 0)
appLabelKey := m.settings.GetAppInstanceLabelKey()
targetObjs, hooks, manifestInfo, err := m.getRepoObjs(app, source, appLabelKey, revision, noCache)
if err != nil {
targetObjs = make([]*unstructured.Unstructured, 0)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
failedToLoadObjs = true
logCtx := log.WithField("application", app.Name)
logCtx.Infof("Comparing app state (cluster: %s, namespace: %s)", app.Spec.Destination.Server, app.Spec.Destination.Namespace)
var targetObjs []*unstructured.Unstructured
var hooks []*unstructured.Unstructured
var manifestInfo *apiclient.ManifestResponse
if len(localManifests) == 0 {
targetObjs, hooks, manifestInfo, err = m.getRepoObjs(app, source, appLabelKey, revision, noCache)
if err != nil {
targetObjs = make([]*unstructured.Unstructured, 0)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
failedToLoadObjs = true
}
} else {
targetObjs, hooks, err = unmarshalManifests(localManifests)
if err != nil {
targetObjs = make([]*unstructured.Unstructured, 0)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
failedToLoadObjs = true
}
manifestInfo = nil
}
targetObjs, dedupConditions, err := DeduplicateTargetObjects(app.Spec.Destination.Server, app.Spec.Destination.Namespace, targetObjs, m.liveStateCache)
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
}
conditions = append(conditions, dedupConditions...)
resFilter, err := m.settingsMgr.GetResourcesFilter()
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
} else {
for i := len(targetObjs) - 1; i >= 0; i-- {
targetObj := targetObjs[i]
gvk := targetObj.GroupVersionKind()
if resFilter.IsExcludedResource(gvk.Group, gvk.Kind, app.Spec.Destination.Server) {
targetObjs = append(targetObjs[:i], targetObjs[i+1:]...)
conditions = append(conditions, v1alpha1.ApplicationCondition{
Type: v1alpha1.ApplicationConditionExcludedResourceWarning,
Message: fmt.Sprintf("Resource %s/%s %s is excluded in the settings", gvk.Group, gvk.Kind, targetObj.GetName()),
})
}
}
}
logCtx.Debugf("Generated config manifests")
liveObjByKey, err := m.liveStateCache.GetManagedLiveObjs(app, targetObjs)
dedupLiveResources(targetObjs, liveObjByKey)
if err != nil {
liveObjByKey = make(map[kubeutil.ResourceKey]*unstructured.Unstructured)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
@@ -175,7 +343,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
for i, obj := range targetObjs {
gvk := obj.GroupVersionKind()
ns := util.FirstNonEmpty(obj.GetNamespace(), app.Spec.Destination.Namespace)
if namespaced, err := m.liveStateCache.IsNamespaced(app.Spec.Destination.Server, obj.GroupVersionKind()); err == nil && !namespaced {
if namespaced, err := m.liveStateCache.IsNamespaced(app.Spec.Destination.Server, obj); err == nil && !namespaced {
ns = ""
}
key := kubeutil.NewResourceKey(gvk.Group, gvk.Kind, ns, obj.GetName())
@@ -198,16 +366,19 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
// Do the actual comparison
diffResults, err := diff.DiffArray(targetObjs, managedLiveObj, diffNormalizer)
if err != nil {
return nil, err
diffResults = &diff.DiffResultList{}
failedToLoadObjs = true
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
}
syncCode := v1alpha1.SyncStatusCodeSynced
managedResources := make([]managedResource, len(targetObjs))
resourceSummaries := make([]v1alpha1.ResourceStatus, len(targetObjs))
for i := 0; i < len(targetObjs); i++ {
obj := managedLiveObj[i]
for i, targetObj := range targetObjs {
liveObj := managedLiveObj[i]
obj := liveObj
if obj == nil {
obj = targetObjs[i]
obj = targetObj
}
if obj == nil {
continue
@@ -215,35 +386,44 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
gvk := obj.GroupVersionKind()
resState := v1alpha1.ResourceStatus{
Namespace: util.FirstNonEmpty(obj.GetNamespace(), app.Spec.Destination.Namespace),
Name: obj.GetName(),
Kind: gvk.Kind,
Version: gvk.Version,
Group: gvk.Group,
Hook: hookutil.IsHook(obj),
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
Kind: gvk.Kind,
Version: gvk.Version,
Group: gvk.Group,
Hook: hookutil.IsHook(obj),
RequiresPruning: targetObj == nil && liveObj != nil,
}
diffResult := diffResults.Diffs[i]
if resState.Hook {
if resState.Hook || resource.Ignore(obj) {
// For resource hooks, don't store sync status, and do not affect overall sync status
} else if diffResult.Modified || targetObjs[i] == nil || managedLiveObj[i] == nil {
} else if diffResult.Modified || targetObj == nil || liveObj == nil {
// Set resource state to OutOfSync since one of the following is true:
// * target and live resource are different
// * target resource not defined and live resource is extra
// * target resource present but live resource is missing
resState.Status = v1alpha1.SyncStatusCodeOutOfSync
syncCode = v1alpha1.SyncStatusCodeOutOfSync
// we ignore the status if the obj needs pruning AND we have the annotation
needsPruning := targetObj == nil && liveObj != nil
if !(needsPruning && resource.HasAnnotationOption(obj, common.AnnotationCompareOptions, "IgnoreExtraneous")) {
syncCode = v1alpha1.SyncStatusCodeOutOfSync
}
} else {
resState.Status = v1alpha1.SyncStatusCodeSynced
}
// we can't say anything about the status if we were unable to get the target objects
if failedToLoadObjs {
resState.Status = v1alpha1.SyncStatusCodeUnknown
}
managedResources[i] = managedResource{
Name: resState.Name,
Namespace: resState.Namespace,
Group: resState.Group,
Kind: resState.Kind,
Version: resState.Version,
Live: managedLiveObj[i],
Target: targetObjs[i],
Live: liveObj,
Target: targetObj,
Diff: diffResult,
Hook: resState.Hook,
}
@@ -264,13 +444,15 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
syncStatus.Revision = manifestInfo.Revision
}
healthStatus, err := health.SetApplicationHealth(resourceSummaries, GetLiveObjs(managedResources), m.settings.ResourceOverrides)
healthStatus, err := health.SetApplicationHealth(resourceSummaries, GetLiveObjs(managedResources), resourceOverrides, func(obj *unstructured.Unstructured) bool {
return !isSelfReferencedApp(app, kubeutil.GetObjectRef(obj))
})
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
}
compRes := comparisonResult{
observedAt: observedAt,
syncStatus: &syncStatus,
healthStatus: healthStatus,
resources: resourceSummaries,
@@ -278,18 +460,11 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
conditions: conditions,
hooks: hooks,
diffNormalizer: diffNormalizer,
appSourceType: v1alpha1.ApplicationSourceType(manifestInfo.SourceType),
}
return &compRes, nil
}
func (m *appStateManager) getRepo(repoURL string) *v1alpha1.Repository {
repo, err := m.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}
if manifestInfo != nil {
compRes.appSourceType = v1alpha1.ApplicationSourceType(manifestInfo.SourceType)
}
return repo
return &compRes
}
func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource) error {
@@ -324,12 +499,13 @@ func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revi
func NewAppStateManager(
db db.ArgoDB,
appclientset appclientset.Interface,
repoClientset reposerver.Clientset,
repoClientset apiclient.Clientset,
namespace string,
kubectl kubeutil.Kubectl,
settings *settings.ArgoCDSettings,
settingsMgr *settings.SettingsManager,
liveStateCache statecache.LiveStateCache,
projInformer cache.SharedIndexInformer,
metricsServer *metrics.MetricsServer,
) AppStateManager {
return &appStateManager{
liveStateCache: liveStateCache,
@@ -338,7 +514,8 @@ func NewAppStateManager(
kubectl: kubectl,
repoClientset: repoClientset,
namespace: namespace,
settings: settings,
settingsMgr: settingsMgr,
projInformer: projInformer,
metricsServer: metricsServer,
}
}

View File

@@ -5,12 +5,14 @@ import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/common"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/reposerver/apiclient"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util/kube"
)
@@ -19,7 +21,7 @@ import (
func TestCompareAppStateEmpty(t *testing.T) {
app := newFakeApp()
data := fakeData{
manifestResponse: &repository.ManifestResponse{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
@@ -28,8 +30,7 @@ func TestCompareAppStateEmpty(t *testing.T) {
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
assert.Equal(t, 0, len(compRes.resources))
@@ -42,7 +43,7 @@ func TestCompareAppStateMissing(t *testing.T) {
app := newFakeApp()
data := fakeData{
apps: []runtime.Object{app},
manifestResponse: &repository.ManifestResponse{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{string(test.PodManifest)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
@@ -51,8 +52,7 @@ func TestCompareAppStateMissing(t *testing.T) {
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
assert.Equal(t, 1, len(compRes.resources))
@@ -67,7 +67,7 @@ func TestCompareAppStateExtra(t *testing.T) {
app := newFakeApp()
key := kube.ResourceKey{Group: "", Kind: "Pod", Namespace: test.FakeDestNamespace, Name: app.Name}
data := fakeData{
manifestResponse: &repository.ManifestResponse{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
@@ -78,8 +78,7 @@ func TestCompareAppStateExtra(t *testing.T) {
},
}
ctrl := newFakeController(&data)
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
assert.Equal(t, 1, len(compRes.resources))
@@ -96,7 +95,7 @@ func TestCompareAppStateHook(t *testing.T) {
app := newFakeApp()
data := fakeData{
apps: []runtime.Object{app},
manifestResponse: &repository.ManifestResponse{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{string(podBytes)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
@@ -105,15 +104,41 @@ func TestCompareAppStateHook(t *testing.T) {
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
assert.Equal(t, 0, len(compRes.resources))
assert.Equal(t, 0, len(compRes.managedResources))
assert.Equal(t, 1, len(compRes.hooks))
assert.Equal(t, 0, len(compRes.conditions))
}
// checks that ignore resources are detected, but excluded from status
func TestCompareAppStateCompareOptionIgnoreExtraneous(t *testing.T) {
pod := test.NewPod()
pod.SetAnnotations(map[string]string{common.AnnotationCompareOptions: "IgnoreExtraneous"})
app := newFakeApp()
data := fakeData{
apps: []runtime.Object{app},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 0)
assert.Len(t, compRes.managedResources, 0)
assert.Len(t, compRes.conditions, 0)
}
// TestCompareAppStateExtraHook tests when there is an extra _hook_ object in live but not defined in git
func TestCompareAppStateExtraHook(t *testing.T) {
pod := test.NewPod()
@@ -122,7 +147,7 @@ func TestCompareAppStateExtraHook(t *testing.T) {
app := newFakeApp()
key := kube.ResourceKey{Group: "", Kind: "Pod", Namespace: test.FakeDestNamespace, Name: app.Name}
data := fakeData{
manifestResponse: &repository.ManifestResponse{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
@@ -133,11 +158,127 @@ func TestCompareAppStateExtraHook(t *testing.T) {
},
}
ctrl := newFakeController(&data)
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
assert.Equal(t, 1, len(compRes.resources))
assert.Equal(t, 1, len(compRes.managedResources))
assert.Equal(t, 0, len(compRes.hooks))
assert.Equal(t, 0, len(compRes.conditions))
}
func toJSON(t *testing.T, obj *unstructured.Unstructured) string {
data, err := json.Marshal(obj)
assert.NoError(t, err)
return string(data)
}
func TestCompareAppStateDuplicatedNamespacedResources(t *testing.T) {
obj1 := test.NewPod()
obj1.SetNamespace(test.FakeDestNamespace)
obj2 := test.NewPod()
obj3 := test.NewPod()
obj3.SetNamespace("kube-system")
app := newFakeApp()
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{toJSON(t, obj1), toJSON(t, obj2), toJSON(t, obj3)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(obj1): obj1,
kube.GetResourceKey(obj3): obj3,
},
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Contains(t, compRes.conditions, argoappv1.ApplicationCondition{
Message: "Resource /Pod/fake-dest-ns/my-pod appeared 2 times among application resources.",
Type: argoappv1.ApplicationConditionRepeatedResourceWarning,
})
assert.Equal(t, 2, len(compRes.resources))
}
var defaultProj = argoappv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: test.FakeArgoCDNamespace,
},
Spec: argoappv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []argoappv1.ApplicationDestination{
{
Server: "*",
Namespace: "*",
},
},
},
}
func TestSetHealth(t *testing.T) {
app := newFakeApp()
deployment := kube.MustToUnstructured(&v1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1beta1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: "demo",
Namespace: "default",
},
})
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(deployment): deployment,
},
})
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.Equal(t, compRes.healthStatus.Status, argoappv1.HealthStatusHealthy)
}
func TestSetHealthSelfReferencedApp(t *testing.T) {
app := newFakeApp()
unstructuredApp := kube.MustToUnstructured(app)
deployment := kube.MustToUnstructured(&v1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1beta1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: "demo",
Namespace: "default",
},
})
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(deployment): deployment,
kube.GetResourceKey(unstructuredApp): unstructuredApp,
},
})
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
assert.Equal(t, compRes.healthStatus.Status, argoappv1.HealthStatusHealthy)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util/kube/kubetest"
)
var clusterRoleHook = `
{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRole",
"metadata": {
"name": "cluster-role-hook",
"annotations": {
"argocd.argoproj.io/hook": "PostSync"
}
}
}`
func TestSyncHookProjectPermissions(t *testing.T) {
syncCtx := newTestSyncCtx(&v1.APIResourceList{
GroupVersion: "v1",
APIResources: []v1.APIResource{
{Name: "pod", Namespaced: true, Kind: "Pod", Group: "v1"},
},
}, &v1.APIResourceList{
GroupVersion: "rbac.authorization.k8s.io/v1",
APIResources: []v1.APIResource{
{Name: "clusterroles", Namespaced: false, Kind: "ClusterRole", Group: "rbac.authorization.k8s.io"},
},
})
syncCtx.kubectl = kubetest.MockKubectlCmd{}
crHook, _ := v1alpha1.UnmarshalToUnstructured(clusterRoleHook)
syncCtx.compareResult = &comparisonResult{
hooks: []*unstructured.Unstructured{
crHook,
},
managedResources: []managedResource{{
Target: test.NewPod(),
}},
}
syncCtx.proj.Spec.ClusterResourceWhitelist = []v1.GroupKind{}
syncCtx.syncOp.SyncStrategy = nil
syncCtx.sync()
assert.Equal(t, v1alpha1.OperationFailed, syncCtx.opState.Phase)
assert.Len(t, syncCtx.syncRes.Resources, 0)
assert.Contains(t, syncCtx.opState.Message, "not permitted in project")
// Now add the resource to the whitelist and try again. Resource should be created
syncCtx.proj.Spec.ClusterResourceWhitelist = []v1.GroupKind{
{Group: "rbac.authorization.k8s.io", Kind: "ClusterRole"},
}
syncCtx.syncOp.SyncStrategy = nil
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResultCodeSynced, syncCtx.syncRes.Resources[0].Status)
}

View File

@@ -2,334 +2,55 @@ package controller
import (
"fmt"
"reflect"
"strings"
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
apiv1 "k8s.io/api/core/v1"
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/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/util"
hookutil "github.com/argoproj/argo-cd/util/hook"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
// 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) {
if !sc.startedPreSyncPhase() {
if !sc.verifyPermittedHooks(hooks) {
return
}
}
// 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() {
sc.log.Infof("PostSync application health check: %s", sc.compareResult.healthStatus.Status)
if sc.compareResult.healthStatus.Status != appv1.HealthStatusHealthy {
sc.setOperationPhase(appv1.OperationRunning, fmt.Sprintf("waiting for %s state to run %s hooks (current health: %s)",
appv1.HealthStatusHealthy, appv1.HookTypePostSync, sc.compareResult.healthStatus.Status))
return
}
}
if !sc.runHooks(hooks, appv1.HookTypePostSync) {
return
}
// if we get here, all hooks successfully completed
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced")
}
// verifyPermittedHooks verifies all hooks are permitted in the project
func (sc *syncContext) verifyPermittedHooks(hooks []*unstructured.Unstructured) bool {
for _, hook := range hooks {
gvk := hook.GroupVersionKind()
serverRes, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
if err != nil {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("unable to identify api resource type: %v", gvk))
return false
}
if !sc.proj.IsResourcePermitted(metav1.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, serverRes.Namespaced) {
sc.setOperationPhase(appv1.OperationFailed, fmt.Sprintf("Hook resource %s:%s is not permitted in project %s", gvk.Group, gvk.Kind, sc.proj.Name))
return false
}
if serverRes.Namespaced && !sc.proj.IsDestinationPermitted(appv1.ApplicationDestination{Namespace: hook.GetNamespace(), Server: sc.server}) {
gvk := hook.GroupVersionKind()
sc.setResourceDetails(&appv1.ResourceResult{
Name: hook.GetName(),
Group: gvk.Group,
Version: gvk.Version,
Kind: hook.GetKind(),
Namespace: hook.GetNamespace(),
Message: fmt.Sprintf("namespace %v is not permitted in project '%s'", hook.GetNamespace(), sc.proj.Name),
Status: appv1.ResultCodeSyncFailed,
})
return false
}
}
return true
}
// getHooks returns all Argo CD hooks, optionally filtered by ones of the specific type(s)
func (sc *syncContext) getHooks(hookTypes ...appv1.HookType) ([]*unstructured.Unstructured, error) {
var hooks []*unstructured.Unstructured
for _, hook := range sc.compareResult.hooks {
if hook.GetNamespace() == "" {
hook.SetNamespace(sc.namespace)
}
if !hookutil.IsArgoHook(hook) {
// TODO: in the future, if we want to map helm hooks to Argo CD lifecycles, we should
// include helm hooks in the returned list
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
gvk := hook.GroupVersionKind()
sc.setResourceDetails(&appv1.ResourceResult{
Name: hook.GetName(),
Group: gvk.Group,
Version: gvk.Version,
Kind: hook.GetKind(),
Namespace: hook.GetNamespace(),
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.Resources)
if !completed {
return false
}
if !successful {
sc.setOperationPhase(appv1.OperationFailed, fmt.Sprintf("%s hook failed", hookType))
return false
}
return true
}
// 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.AnnotationKeyHook] != "" {
// 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)
}
// 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.HookPhase.Completed() {
return false, nil
}
gvk := hook.GroupVersionKind()
apiResource, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
if err != nil {
return false, err
}
resource := kube.ToGroupVersionResource(gvk.GroupVersion().String(), apiResource)
resIf := kube.ToResourceInterface(sc.dynamicIf, apiResource, resource, hook.GetNamespace())
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)
}
_, err := sc.kubectl.ApplyResource(sc.config, hook, hook.GetNamespace(), 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.HookPhase.Completed() {
if enforceHookDeletePolicy(hook, hookStatus.HookPhase) {
err = sc.deleteHook(hook.GetName(), hook.GetNamespace(), hook.GroupVersionKind())
if err != nil {
hookStatus.HookPhase = appv1.OperationFailed
hookStatus.Message = fmt.Sprintf("failed to delete %s hook: %v", hookStatus.HookPhase, err)
}
}
}
return sc.updateHookStatus(hookStatus), nil
}
// enforceHookDeletePolicy examines the hook deletion policy of a object and deletes it based on the status
func enforceHookDeletePolicy(hook *unstructured.Unstructured, phase appv1.OperationPhase) bool {
func enforceHookDeletePolicy(hook *unstructured.Unstructured, operation v1alpha1.OperationPhase) bool {
annotations := hook.GetAnnotations()
if annotations == nil {
return false
}
deletePolicies := strings.Split(annotations[common.AnnotationKeyHookDeletePolicy], ",")
for _, dp := range deletePolicies {
policy := appv1.HookDeletePolicy(strings.TrimSpace(dp))
if policy == appv1.HookDeletePolicyHookSucceeded && phase == appv1.OperationSucceeded {
policy := v1alpha1.HookDeletePolicy(strings.TrimSpace(dp))
if policy == v1alpha1.HookDeletePolicyHookSucceeded && operation == v1alpha1.OperationSucceeded {
return true
}
if policy == appv1.HookDeletePolicyHookFailed && phase == appv1.OperationFailed {
if policy == v1alpha1.HookDeletePolicyHookFailed && operation == v1alpha1.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.AnnotationKeyHook], ",")
for _, ht := range resHookTypes {
if string(hookType) == strings.TrimSpace(ht) {
return true
}
}
return false
}
// newHookStatus returns a hook status from an _live_ unstructured object
func newHookStatus(hook *unstructured.Unstructured, hookType appv1.HookType) appv1.ResourceResult {
// getOperationPhase returns a hook status from an _live_ unstructured object
func getOperationPhase(hook *unstructured.Unstructured) (operation v1alpha1.OperationPhase, message string) {
gvk := hook.GroupVersionKind()
hookStatus := appv1.ResourceResult{
Name: hook.GetName(),
Kind: hook.GetKind(),
Group: gvk.Group,
Version: gvk.Version,
HookType: hookType,
HookPhase: appv1.OperationRunning,
Namespace: hook.GetNamespace(),
}
if isBatchJob(gvk) {
updateStatusFromBatchJob(hook, &hookStatus)
return getStatusFromBatchJob(hook)
} else if isArgoWorkflow(gvk) {
updateStatusFromArgoWorkflow(hook, &hookStatus)
return getStatusFromArgoWorkflow(hook)
} else if isPod(gvk) {
updateStatusFromPod(hook, &hookStatus)
return getStatusFromPod(hook)
} else {
hookStatus.HookPhase = appv1.OperationSucceeded
hookStatus.Message = fmt.Sprintf("%s created", hook.GetName())
return v1alpha1.OperationSucceeded, fmt.Sprintf("%s created", hook.GetName())
}
return hookStatus
}
// isRunnable returns if the resource object is a runnable type which needs to be terminated
func isRunnable(res *appv1.ResourceResult) bool {
gvk := res.GroupVersionKind()
func isRunnable(gvk schema.GroupVersionKind) bool {
return isBatchJob(gvk) || isArgoWorkflow(gvk) || isPod(gvk)
}
@@ -337,18 +58,16 @@ func isBatchJob(gvk schema.GroupVersionKind) bool {
return gvk.Group == "batch" && gvk.Kind == "Job"
}
func updateStatusFromBatchJob(hook *unstructured.Unstructured, hookStatus *appv1.ResourceResult) {
// TODO this is a copy-and-paste of health.getJobHealth(), refactor out?
func getStatusFromBatchJob(hook *unstructured.Unstructured) (operation v1alpha1.OperationPhase, message string) {
var job batch.Job
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &job)
if err != nil {
hookStatus.HookPhase = appv1.OperationError
hookStatus.Message = err.Error()
return
return v1alpha1.OperationError, err.Error()
}
failed := false
var failMsg string
complete := false
var message string
for _, condition := range job.Status.Conditions {
switch condition.Type {
case batch.JobFailed:
@@ -361,14 +80,11 @@ func updateStatusFromBatchJob(hook *unstructured.Unstructured, hookStatus *appv1
}
}
if !complete {
hookStatus.HookPhase = appv1.OperationRunning
hookStatus.Message = message
return v1alpha1.OperationRunning, message
} else if failed {
hookStatus.HookPhase = appv1.OperationFailed
hookStatus.Message = failMsg
return v1alpha1.OperationFailed, failMsg
} else {
hookStatus.HookPhase = appv1.OperationSucceeded
hookStatus.Message = message
return v1alpha1.OperationSucceeded, message
}
}
@@ -376,38 +92,36 @@ func isArgoWorkflow(gvk schema.GroupVersionKind) bool {
return gvk.Group == "argoproj.io" && gvk.Kind == "Workflow"
}
func updateStatusFromArgoWorkflow(hook *unstructured.Unstructured, hookStatus *appv1.ResourceResult) {
// TODO - should we move this to health.go?
func getStatusFromArgoWorkflow(hook *unstructured.Unstructured) (operation v1alpha1.OperationPhase, message string) {
var wf wfv1.Workflow
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &wf)
if err != nil {
hookStatus.HookPhase = appv1.OperationError
hookStatus.Message = err.Error()
return
return v1alpha1.OperationError, err.Error()
}
switch wf.Status.Phase {
case wfv1.NodePending, wfv1.NodeRunning:
hookStatus.HookPhase = appv1.OperationRunning
return v1alpha1.OperationRunning, wf.Status.Message
case wfv1.NodeSucceeded:
hookStatus.HookPhase = appv1.OperationSucceeded
return v1alpha1.OperationSucceeded, wf.Status.Message
case wfv1.NodeFailed:
hookStatus.HookPhase = appv1.OperationFailed
return v1alpha1.OperationFailed, wf.Status.Message
case wfv1.NodeError:
hookStatus.HookPhase = appv1.OperationError
return v1alpha1.OperationError, wf.Status.Message
}
hookStatus.Message = wf.Status.Message
return v1alpha1.OperationSucceeded, wf.Status.Message
}
func isPod(gvk schema.GroupVersionKind) bool {
return gvk.Group == "" && gvk.Kind == "Pod"
}
func updateStatusFromPod(hook *unstructured.Unstructured, hookStatus *appv1.ResourceResult) {
// TODO - this is very similar to health.getPodHealth() should we use that instead?
func getStatusFromPod(hook *unstructured.Unstructured) (v1alpha1.OperationPhase, string) {
var pod apiv1.Pod
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &pod)
if err != nil {
hookStatus.HookPhase = appv1.OperationError
hookStatus.Message = err.Error()
return
return v1alpha1.OperationError, err.Error()
}
getFailMessage := func(ctr *apiv1.ContainerStatus) string {
if ctr.State.Terminated != nil {
@@ -426,135 +140,22 @@ func updateStatusFromPod(hook *unstructured.Unstructured, hookStatus *appv1.Reso
switch pod.Status.Phase {
case apiv1.PodPending, apiv1.PodRunning:
hookStatus.HookPhase = appv1.OperationRunning
return v1alpha1.OperationRunning, ""
case apiv1.PodSucceeded:
hookStatus.HookPhase = appv1.OperationSucceeded
return v1alpha1.OperationSucceeded, ""
case apiv1.PodFailed:
hookStatus.HookPhase = appv1.OperationFailed
if pod.Status.Message != "" {
// Pod has a nice error message. Use that.
hookStatus.Message = pod.Status.Message
return
return v1alpha1.OperationFailed, pod.Status.Message
}
for _, ctr := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) {
if msg := getFailMessage(&ctr); msg != "" {
hookStatus.Message = msg
return
return v1alpha1.OperationFailed, msg
}
}
return v1alpha1.OperationFailed, ""
case apiv1.PodUnknown:
hookStatus.HookPhase = appv1.OperationError
return v1alpha1.OperationError, ""
}
}
func (sc *syncContext) getHookStatus(hookObj *unstructured.Unstructured, hookType appv1.HookType) *appv1.ResourceResult {
for _, hr := range sc.syncRes.Resources {
if !hr.IsHook() {
continue
}
ns := util.FirstNonEmpty(hookObj.GetNamespace(), sc.namespace)
if hookEqual(hr, hookObj.GroupVersionKind().Group, hookObj.GetKind(), ns, hookObj.GetName(), hookType) {
return hr
}
}
return nil
}
func hookEqual(hr *appv1.ResourceResult, group, kind, namespace, name string, hookType appv1.HookType) bool {
return bool(
hr.Group == group &&
hr.Kind == kind &&
hr.Namespace == namespace &&
hr.Name == name &&
hr.HookType == hookType)
}
// updateHookStatus updates the status of a hook. Returns true if the hook was modified
func (sc *syncContext) updateHookStatus(hookStatus appv1.ResourceResult) bool {
sc.lock.Lock()
defer sc.lock.Unlock()
for i, prev := range sc.syncRes.Resources {
if !prev.IsHook() {
continue
}
if hookEqual(prev, hookStatus.Group, hookStatus.Kind, hookStatus.Namespace, hookStatus.Name, hookStatus.HookType) {
if reflect.DeepEqual(prev, hookStatus) {
return false
}
if prev.HookPhase != hookStatus.HookPhase {
sc.log.Infof("Hook %s %s/%s hookPhase: %s -> %s", hookStatus.HookType, prev.Kind, prev.Name, prev.HookPhase, hookStatus.HookPhase)
}
if prev.Status != hookStatus.Status {
sc.log.Infof("Hook %s %s/%s status: %s -> %s", hookStatus.HookType, prev.Kind, prev.Name, prev.Status, hookStatus.Status)
}
if prev.Message != hookStatus.Message {
sc.log.Infof("Hook %s %s/%s message: '%s' -> '%s'", hookStatus.HookType, prev.Kind, prev.Name, prev.Message, hookStatus.Message)
}
sc.syncRes.Resources[i] = &hookStatus
return true
}
}
sc.syncRes.Resources = append(sc.syncRes.Resources, &hookStatus)
sc.log.Infof("Set new hook %s %s/%s. phase: %s, message: %s", hookStatus.HookType, hookStatus.Kind, hookStatus.Name, hookStatus.HookPhase, 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.ResourceResult) (bool, bool) {
isSuccessful := true
for _, hookStatus := range hookStatuses {
if !hookStatus.IsHook() {
continue
}
if hookStatus.HookType != hookType {
continue
}
if !hookStatus.HookPhase.Completed() {
return false, false
}
if !hookStatus.HookPhase.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.Resources {
if !hookStatus.IsHook() {
continue
}
if hookStatus.HookPhase.Completed() {
continue
}
if isRunnable(hookStatus) {
hookStatus.HookPhase = appv1.OperationFailed
err := sc.deleteHook(hookStatus.Name, hookStatus.Namespace, hookStatus.GroupVersionKind())
if err != nil {
hookStatus.Message = fmt.Sprintf("Failed to delete %s hook %s/%s: %v", hookStatus.HookType, hookStatus.Kind, hookStatus.Name, err)
terminateSuccessful = false
} else {
hookStatus.Message = fmt.Sprintf("Deleted %s hook %s/%s", hookStatus.HookType, 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, namespace string, gvk schema.GroupVersionKind) error {
apiResource, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
if err != nil {
return err
}
resource := kube.ToGroupVersionResource(gvk.GroupVersion().String(), apiResource)
resIf := kube.ToResourceInterface(sc.dynamicIf, apiResource, resource, namespace)
propagationPolicy := metav1.DeletePropagationForeground
return resIf.Delete(name, &metav1.DeleteOptions{PropagationPolicy: &propagationPolicy})
return v1alpha1.OperationRunning, ""
}

25
controller/sync_phase.go Normal file
View File

@@ -0,0 +1,25 @@
package controller
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/hook"
)
func syncPhases(obj *unstructured.Unstructured) []v1alpha1.SyncPhase {
if hook.Skip(obj) {
return nil
} else if hook.IsHook(obj) {
var phases []v1alpha1.SyncPhase
for _, hookType := range hook.Types(obj) {
switch hookType {
case v1alpha1.HookTypePreSync, v1alpha1.HookTypeSync, v1alpha1.HookTypePostSync, v1alpha1.HookTypeSyncFail:
phases = append(phases, v1alpha1.SyncPhase(hookType))
}
}
return phases
} else {
return []v1alpha1.SyncPhase{v1alpha1.SyncPhaseSync}
}
}

View File

@@ -0,0 +1,50 @@
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/test"
)
func TestSyncPhaseNone(t *testing.T) {
assert.Equal(t, []SyncPhase{SyncPhaseSync}, syncPhases(&unstructured.Unstructured{}))
}
func TestSyncPhasePreSync(t *testing.T) {
assert.Equal(t, []SyncPhase{SyncPhasePreSync}, syncPhases(pod("PreSync")))
}
func TestSyncPhaseSync(t *testing.T) {
assert.Equal(t, []SyncPhase{SyncPhaseSync}, syncPhases(pod("Sync")))
}
func TestSyncPhaseSkip(t *testing.T) {
assert.Nil(t, syncPhases(pod("Skip")))
}
// garbage hooks are still hooks, but have no phases, because some user spelled something wrong
func TestSyncPhaseGarbage(t *testing.T) {
assert.Nil(t, syncPhases(pod("Garbage")))
}
func TestSyncPhasePost(t *testing.T) {
assert.Equal(t, []SyncPhase{SyncPhasePostSync}, syncPhases(pod("PostSync")))
}
func TestSyncPhaseFail(t *testing.T) {
assert.Equal(t, []SyncPhase{SyncPhaseSyncFail}, syncPhases(pod("SyncFail")))
}
func TestSyncPhaseTwoPhases(t *testing.T) {
assert.Equal(t, []SyncPhase{SyncPhasePreSync, SyncPhasePostSync}, syncPhases(pod("PreSync,PostSync")))
}
func pod(hookType string) *unstructured.Unstructured {
pod := test.NewPod()
pod.SetAnnotations(map[string]string{"argocd.argoproj.io/hook": hookType})
return pod
}

119
controller/sync_task.go Normal file
View File

@@ -0,0 +1,119 @@
package controller
import (
"fmt"
"strconv"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/hook"
)
// 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 {
phase v1alpha1.SyncPhase
liveObj *unstructured.Unstructured
targetObj *unstructured.Unstructured
skipDryRun bool
syncStatus v1alpha1.ResultCode
operationState v1alpha1.OperationPhase
message string
}
func ternary(val bool, a, b string) string {
if val {
return a
} else {
return b
}
}
func (t *syncTask) String() string {
return fmt.Sprintf("%s/%d %s %s/%s:%s/%s %s->%s (%s,%s,%s)",
t.phase, t.wave(),
ternary(t.isHook(), "hook", "resource"), t.group(), t.kind(), t.namespace(), t.name(),
ternary(t.liveObj != nil, "obj", "nil"), ternary(t.targetObj != nil, "obj", "nil"),
t.syncStatus, t.operationState, t.message,
)
}
func (t *syncTask) isPrune() bool {
return t.targetObj == nil
}
// return the target object (if this exists) otherwise the live object
// some caution - often you explicitly want the live object not the target object
func (t *syncTask) obj() *unstructured.Unstructured {
return obj(t.targetObj, t.liveObj)
}
func (t *syncTask) wave() int {
text := t.obj().GetAnnotations()[common.AnnotationSyncWave]
if text == "" {
return 0
}
val, err := strconv.Atoi(text)
if err != nil {
return 0
}
return val
}
func (t *syncTask) isHook() bool {
return hook.IsHook(t.obj())
}
func (t *syncTask) group() string {
return t.groupVersionKind().Group
}
func (t *syncTask) kind() string {
return t.groupVersionKind().Kind
}
func (t *syncTask) version() string {
return t.groupVersionKind().Version
}
func (t *syncTask) groupVersionKind() schema.GroupVersionKind {
return t.obj().GroupVersionKind()
}
func (t *syncTask) name() string {
return t.obj().GetName()
}
func (t *syncTask) namespace() string {
return t.obj().GetNamespace()
}
func (t *syncTask) pending() bool {
return t.operationState == ""
}
func (t *syncTask) running() bool {
return t.operationState == v1alpha1.OperationRunning
}
func (t *syncTask) completed() bool {
return t.operationState.Completed()
}
func (t *syncTask) successful() bool {
return t.operationState.Successful()
}
func (t *syncTask) hookType() v1alpha1.HookType {
if t.isHook() {
return v1alpha1.HookType(t.phase)
} else {
return ""
}
}

View File

@@ -0,0 +1,38 @@
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/test"
)
func Test_syncTask_hookType(t *testing.T) {
type fields struct {
phase SyncPhase
liveObj *unstructured.Unstructured
}
tests := []struct {
name string
fields fields
want HookType
}{
{"Empty", fields{SyncPhaseSync, test.NewPod()}, ""},
{"PreSyncHook", fields{SyncPhasePreSync, test.NewHook(HookTypePreSync)}, HookTypePreSync},
{"SyncHook", fields{SyncPhaseSync, test.NewHook(HookTypeSync)}, HookTypeSync},
{"PostSyncHook", fields{SyncPhasePostSync, test.NewHook(HookTypePostSync)}, HookTypePostSync},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
task := &syncTask{
phase: tt.fields.phase,
liveObj: tt.fields.liveObj,
}
hookType := task.hookType()
assert.EqualValues(t, tt.want, hookType)
})
}
}

185
controller/sync_tasks.go Normal file
View File

@@ -0,0 +1,185 @@
package controller
import (
"strings"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
// kindOrder represents the correct order of Kubernetes resources within a manifest
var syncPhaseOrder = map[v1alpha1.SyncPhase]int{
v1alpha1.SyncPhasePreSync: -1,
v1alpha1.SyncPhaseSync: 0,
v1alpha1.SyncPhasePostSync: 1,
v1alpha1.SyncPhaseSyncFail: 2,
}
// kindOrder represents the correct order of Kubernetes resources within a manifest
// https://github.com/helm/helm/blob/master/pkg/tiller/kind_sorter.go
var kindOrder = map[string]int{}
func init() {
kinds := []string{
"Namespace",
"ResourceQuota",
"LimitRange",
"PodSecurityPolicy",
"PodDisruptionBudget",
"Secret",
"ConfigMap",
"StorageClass",
"PersistentVolume",
"PersistentVolumeClaim",
"ServiceAccount",
"CustomResourceDefinition",
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"StatefulSet",
"Job",
"CronJob",
"Ingress",
"APIService",
}
for i, kind := range kinds {
// make sure none of the above entries are zero, we need that for custom resources
kindOrder[kind] = i - len(kinds)
}
}
type syncTasks []*syncTask
func (s syncTasks) Len() int {
return len(s)
}
func (s syncTasks) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// order is
// 1. phase
// 2. wave
// 3. kind
// 4. name
func (s syncTasks) Less(i, j int) bool {
tA := s[i]
tB := s[j]
d := syncPhaseOrder[tA.phase] - syncPhaseOrder[tB.phase]
if d != 0 {
return d < 0
}
d = tA.wave() - tB.wave()
if d != 0 {
return d < 0
}
a := tA.obj()
b := tB.obj()
// we take advantage of the fact that if the kind is not in the kindOrder map,
// then it will return the default int value of zero, which is the highest value
d = kindOrder[a.GetKind()] - kindOrder[b.GetKind()]
if d != 0 {
return d < 0
}
return a.GetName() < b.GetName()
}
func (s syncTasks) Filter(predicate func(task *syncTask) bool) (tasks syncTasks) {
for _, task := range s {
if predicate(task) {
tasks = append(tasks, task)
}
}
return tasks
}
func (s syncTasks) Split(predicate func(task *syncTask) bool) (trueTasks, falseTasks syncTasks) {
for _, task := range s {
if predicate(task) {
trueTasks = append(trueTasks, task)
} else {
falseTasks = append(falseTasks, task)
}
}
return trueTasks, falseTasks
}
func (s syncTasks) All(predicate func(task *syncTask) bool) bool {
for _, task := range s {
if !predicate(task) {
return false
}
}
return true
}
func (s syncTasks) Any(predicate func(task *syncTask) bool) bool {
for _, task := range s {
if predicate(task) {
return true
}
}
return false
}
func (s syncTasks) Find(predicate func(task *syncTask) bool) *syncTask {
for _, task := range s {
if predicate(task) {
return task
}
}
return nil
}
func (s syncTasks) String() string {
var values []string
for _, task := range s {
values = append(values, task.String())
}
return "[" + strings.Join(values, ", ") + "]"
}
func (s syncTasks) phase() v1alpha1.SyncPhase {
if len(s) > 0 {
return s[0].phase
}
return ""
}
func (s syncTasks) wave() int {
if len(s) > 0 {
return s[0].wave()
}
return 0
}
func (s syncTasks) lastPhase() v1alpha1.SyncPhase {
if len(s) > 0 {
return s[len(s)-1].phase
}
return ""
}
func (s syncTasks) lastWave() int {
if len(s) > 0 {
return s[len(s)-1].wave()
}
return 0
}
func (s syncTasks) multiStep() bool {
return s.wave() != s.lastWave() || s.phase() != s.lastPhase()
}

View File

@@ -0,0 +1,392 @@
package controller
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/common"
. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
. "github.com/argoproj/argo-cd/test"
)
func Test_syncTasks_kindOrder(t *testing.T) {
assert.Equal(t, -27, kindOrder["Namespace"])
assert.Equal(t, -1, kindOrder["APIService"])
assert.Equal(t, 0, kindOrder["MyCRD"])
}
func TestSortSyncTask(t *testing.T) {
sort.Sort(unsortedTasks)
assert.Equal(t, sortedTasks, unsortedTasks)
}
func TestAnySyncTasks(t *testing.T) {
res := unsortedTasks.Any(func(task *syncTask) bool {
return task.name() == "a"
})
assert.True(t, res)
res = unsortedTasks.Any(func(task *syncTask) bool {
return task.name() == "does-not-exist"
})
assert.False(t, res)
}
func TestAllSyncTasks(t *testing.T) {
res := unsortedTasks.All(func(task *syncTask) bool {
return task.name() != ""
})
assert.False(t, res)
res = unsortedTasks.All(func(task *syncTask) bool {
return task.name() == "a"
})
assert.False(t, res)
}
func TestSplitSyncTasks(t *testing.T) {
named, unnamed := sortedTasks.Split(func(task *syncTask) bool {
return task.name() != ""
})
assert.Equal(t, named, namedObjTasks)
assert.Equal(t, unnamed, unnamedTasks)
}
var unsortedTasks = syncTasks{
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
phase: SyncPhaseSyncFail, targetObj: &unstructured.Unstructured{},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"argocd.argoproj.io/sync-wave": "1",
},
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"name": "b",
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"name": "a",
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"argocd.argoproj.io/sync-wave": "-1",
},
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
{
phase: SyncPhasePreSync,
targetObj: &unstructured.Unstructured{},
},
{
phase: SyncPhasePostSync, targetObj: &unstructured.Unstructured{},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
}
var sortedTasks = syncTasks{
{
phase: SyncPhasePreSync,
targetObj: &unstructured.Unstructured{},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"argocd.argoproj.io/sync-wave": "-1",
},
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"name": "a",
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"name": "b",
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"argocd.argoproj.io/sync-wave": "1",
},
},
},
},
},
{
phase: SyncPhasePostSync,
targetObj: &unstructured.Unstructured{},
},
{
phase: SyncPhaseSyncFail,
targetObj: &unstructured.Unstructured{},
},
}
var namedObjTasks = syncTasks{
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"name": "a",
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"name": "b",
},
},
},
},
}
var unnamedTasks = syncTasks{
{
phase: SyncPhasePreSync,
targetObj: &unstructured.Unstructured{},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"argocd.argoproj.io/sync-wave": "-1",
},
},
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"argocd.argoproj.io/sync-wave": "1",
},
},
},
},
},
{
phase: SyncPhasePostSync,
targetObj: &unstructured.Unstructured{},
},
{
phase: SyncPhaseSyncFail,
targetObj: &unstructured.Unstructured{},
},
}
func Test_syncTasks_Filter(t *testing.T) {
tasks := syncTasks{{phase: SyncPhaseSync}, {phase: SyncPhasePostSync}}
assert.Equal(t, syncTasks{{phase: SyncPhaseSync}}, tasks.Filter(func(t *syncTask) bool {
return t.phase == SyncPhaseSync
}))
}
func TestSyncNamespaceAgainstCRD(t *testing.T) {
crd := &syncTask{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Workflow",
},
}}
namespace := &syncTask{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Namespace",
},
},
}
unsorted := syncTasks{crd, namespace}
sort.Sort(unsorted)
assert.Equal(t, syncTasks{namespace, crd}, unsorted)
}
func Test_syncTasks_multiStep(t *testing.T) {
t.Run("Single", func(t *testing.T) {
tasks := syncTasks{{liveObj: Annotate(NewPod(), common.AnnotationSyncWave, "-1"), phase: SyncPhaseSync}}
assert.Equal(t, SyncPhaseSync, tasks.phase())
assert.Equal(t, -1, tasks.wave())
assert.Equal(t, SyncPhaseSync, tasks.lastPhase())
assert.Equal(t, -1, tasks.lastWave())
assert.False(t, tasks.multiStep())
})
t.Run("Double", func(t *testing.T) {
tasks := syncTasks{
{liveObj: Annotate(NewPod(), common.AnnotationSyncWave, "-1"), phase: SyncPhasePreSync},
{liveObj: Annotate(NewPod(), common.AnnotationSyncWave, "1"), phase: SyncPhasePostSync},
}
assert.Equal(t, SyncPhasePreSync, tasks.phase())
assert.Equal(t, -1, tasks.wave())
assert.Equal(t, SyncPhasePostSync, tasks.lastPhase())
assert.Equal(t, 1, tasks.lastWave())
assert.True(t, tasks.multiStep())
})
}

View File

@@ -2,12 +2,11 @@ package controller
import (
"fmt"
"sort"
"reflect"
"testing"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -19,7 +18,8 @@ import (
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/repository"
. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/apiclient"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/kube/kubetest"
@@ -45,7 +45,9 @@ func newTestSyncCtx(resources ...*v1.APIResourceList) *syncContext {
config: &rest.Config{},
namespace: test.FakeArgoCDNamespace,
server: test.FakeClusterURL,
syncRes: &v1alpha1.SyncOperationResult{},
syncRes: &v1alpha1.SyncOperationResult{
Revision: "FooBarBaz",
},
syncOp: &v1alpha1.SyncOperation{
Prune: true,
SyncStrategy: &v1alpha1.SyncStrategy{
@@ -70,7 +72,7 @@ func newTestSyncCtx(resources ...*v1.APIResourceList) *syncContext {
disco: fakeDisco,
log: log.WithFields(log.Fields{"application": "fake-app"}),
}
sc.kubectl = kubetest.MockKubectlCmd{}
sc.kubectl = &kubetest.MockKubectlCmd{}
return &sc
}
@@ -104,18 +106,19 @@ func TestSyncCreateInSortedOrder(t *testing.T) {
}},
}
syncCtx.sync()
assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase)
assert.Len(t, syncCtx.syncRes.Resources, 2)
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "Pod" {
assert.Equal(t, v1alpha1.ResultCodeSynced, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "Service" {
assert.Equal(t, v1alpha1.ResultCodeSynced, syncCtx.syncRes.Resources[i].Status)
result := syncCtx.syncRes.Resources[i]
if result.Kind == "Pod" {
assert.Equal(t, v1alpha1.ResultCodeSynced, result.Status)
assert.Equal(t, "", result.Message)
} else if result.Kind == "Service" {
assert.Equal(t, "", result.Message)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncCreateNotWhitelistedClusterResources(t *testing.T) {
@@ -136,7 +139,7 @@ func TestSyncCreateNotWhitelistedClusterResources(t *testing.T) {
{Group: "argoproj.io", Kind: "*"},
}
syncCtx.kubectl = kubetest.MockKubectlCmd{}
syncCtx.kubectl = &kubetest.MockKubectlCmd{}
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{
Live: nil,
@@ -147,8 +150,9 @@ func TestSyncCreateNotWhitelistedClusterResources(t *testing.T) {
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResultCodeSyncFailed, syncCtx.syncRes.Resources[0].Status)
assert.Contains(t, syncCtx.syncRes.Resources[0].Message, "not permitted in project")
result := syncCtx.syncRes.Resources[0]
assert.Equal(t, v1alpha1.ResultCodeSyncFailed, result.Status)
assert.Contains(t, result.Message, "not permitted in project")
}
func TestSyncBlacklistedNamespacedResources(t *testing.T) {
@@ -166,73 +170,83 @@ func TestSyncBlacklistedNamespacedResources(t *testing.T) {
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResultCodeSyncFailed, syncCtx.syncRes.Resources[0].Status)
assert.Contains(t, syncCtx.syncRes.Resources[0].Message, "not permitted in project")
result := syncCtx.syncRes.Resources[0]
assert.Equal(t, v1alpha1.ResultCodeSyncFailed, result.Status)
assert.Contains(t, result.Message, "not permitted in project")
}
func TestSyncSuccessfully(t *testing.T) {
syncCtx := newTestSyncCtx()
pod := test.NewPod()
pod.SetNamespace(test.FakeArgoCDNamespace)
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{
Live: nil,
Target: test.NewService(),
}, {
Live: test.NewPod(),
Live: pod,
Target: nil,
}},
}
syncCtx.sync()
assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase)
assert.Len(t, syncCtx.syncRes.Resources, 2)
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "Pod" {
assert.Equal(t, v1alpha1.ResultCodePruned, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "Service" {
assert.Equal(t, v1alpha1.ResultCodeSynced, syncCtx.syncRes.Resources[i].Status)
result := syncCtx.syncRes.Resources[i]
if result.Kind == "Pod" {
assert.Equal(t, v1alpha1.ResultCodePruned, result.Status)
assert.Equal(t, "pruned", result.Message)
} else if result.Kind == "Service" {
assert.Equal(t, v1alpha1.ResultCodeSynced, result.Status)
assert.Equal(t, "", result.Message)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncDeleteSuccessfully(t *testing.T) {
syncCtx := newTestSyncCtx()
svc := test.NewService()
svc.SetNamespace(test.FakeArgoCDNamespace)
pod := test.NewPod()
pod.SetNamespace(test.FakeArgoCDNamespace)
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{
Live: test.NewService(),
Live: svc,
Target: nil,
}, {
Live: test.NewPod(),
Live: pod,
Target: nil,
}},
}
syncCtx.sync()
assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase)
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "Pod" {
assert.Equal(t, v1alpha1.ResultCodePruned, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "Service" {
assert.Equal(t, v1alpha1.ResultCodePruned, syncCtx.syncRes.Resources[i].Status)
result := syncCtx.syncRes.Resources[i]
if result.Kind == "Pod" {
assert.Equal(t, v1alpha1.ResultCodePruned, result.Status)
assert.Equal(t, "pruned", result.Message)
} else if result.Kind == "Service" {
assert.Equal(t, v1alpha1.ResultCodePruned, result.Status)
assert.Equal(t, "pruned", result.Message)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncCreateFailure(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = kubetest.MockKubectlCmd{
testSvc := test.NewService()
syncCtx.kubectl = &kubetest.MockKubectlCmd{
Commands: map[string]kubetest.KubectlOutput{
"test-service": {
testSvc.GetName(): {
Output: "",
Err: fmt.Errorf("error: error validating \"test.yaml\": error validating data: apiVersion not set; if you choose to ignore these errors, turn validation off with --validate=false"),
Err: fmt.Errorf("foo"),
},
},
}
testSvc := test.NewService()
testSvc.SetAPIVersion("")
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{
Live: nil,
@@ -241,21 +255,24 @@ func TestSyncCreateFailure(t *testing.T) {
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResultCodeSyncFailed, syncCtx.syncRes.Resources[0].Status)
result := syncCtx.syncRes.Resources[0]
assert.Equal(t, v1alpha1.ResultCodeSyncFailed, result.Status)
assert.Equal(t, "foo", result.Message)
}
func TestSyncPruneFailure(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = kubetest.MockKubectlCmd{
syncCtx.kubectl = &kubetest.MockKubectlCmd{
Commands: map[string]kubetest.KubectlOutput{
"test-service": {
Output: "",
Err: fmt.Errorf(" error: timed out waiting for \"test-service\" to be synced"),
Err: fmt.Errorf("foo"),
},
},
}
testSvc := test.NewService()
testSvc.SetName("test-service")
testSvc.SetNamespace(test.FakeArgoCDNamespace)
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{
Live: testSvc,
@@ -263,155 +280,11 @@ func TestSyncPruneFailure(t *testing.T) {
}},
}
syncCtx.sync()
assert.Equal(t, v1alpha1.OperationFailed, syncCtx.opState.Phase)
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResultCodeSyncFailed, syncCtx.syncRes.Resources[0].Status)
}
func unsortedManifest() []syncTask {
return []syncTask{
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
}
}
func sortedManifest() []syncTask {
return []syncTask{
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
}
}
func TestSortKubernetesResourcesSuccessfully(t *testing.T) {
unsorted := unsortedManifest()
ks := newKindSorter(unsorted, resourceOrder)
sort.Sort(ks)
expectedOrder := sortedManifest()
assert.Equal(t, len(unsorted), len(expectedOrder))
for i, sorted := range unsorted {
assert.Equal(t, expectedOrder[i], sorted)
}
}
func TestSortManifestHandleNil(t *testing.T) {
task := syncTask{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
}
manifest := []syncTask{
{},
task,
}
ks := newKindSorter(manifest, resourceOrder)
sort.Sort(ks)
assert.Equal(t, task, manifest[0])
assert.Nil(t, manifest[1].targetObj)
}
func TestSyncNamespaceAgainstCRD(t *testing.T) {
crd := syncTask{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": "argoproj.io/alpha1",
"kind": "Workflow",
},
}}
namespace := syncTask{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Namespace",
},
},
}
unsorted := []syncTask{crd, namespace}
ks := newKindSorter(unsorted, resourceOrder)
sort.Sort(ks)
expectedOrder := []syncTask{namespace, crd}
assert.Equal(t, len(unsorted), len(expectedOrder))
for i, sorted := range unsorted {
assert.Equal(t, expectedOrder[i], sorted)
}
result := syncCtx.syncRes.Resources[0]
assert.Equal(t, v1alpha1.ResultCodeSyncFailed, result.Status)
assert.Equal(t, "foo", result.Message)
}
func TestDontSyncOrPruneHooks(t *testing.T) {
@@ -421,23 +294,141 @@ func TestDontSyncOrPruneHooks(t *testing.T) {
targetPod.SetAnnotations(map[string]string{common.AnnotationKeyHook: "PreSync"})
liveSvc := test.NewService()
liveSvc.SetName("dont-prune-me")
liveSvc.SetNamespace(test.FakeArgoCDNamespace)
liveSvc.SetAnnotations(map[string]string{common.AnnotationKeyHook: "PreSync"})
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{
Live: nil,
Target: targetPod,
Hook: true,
}, {
Live: liveSvc,
Target: nil,
Hook: true,
}},
hooks: []*unstructured.Unstructured{targetPod, liveSvc},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 0)
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase)
}
// make sure that we do not prune resources with Prune=false
func TestDontPrunePruneFalse(t *testing.T) {
syncCtx := newTestSyncCtx()
pod := test.NewPod()
pod.SetAnnotations(map[string]string{common.AnnotationSyncOptions: "Prune=false"})
pod.SetNamespace(test.FakeArgoCDNamespace)
syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Live: pod}}}
syncCtx.sync()
assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase)
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResultCodePruneSkipped, syncCtx.syncRes.Resources[0].Status)
assert.Equal(t, "ignored (no prune)", syncCtx.syncRes.Resources[0].Message)
syncCtx.sync()
assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase)
}
// make sure Validate=false means we don't validate
func TestSyncOptionValidate(t *testing.T) {
tests := []struct {
name string
annotationVal string
want bool
}{
{"Empty", "", true},
{"True", "Validate=true", true},
{"False", "Validate=false", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
syncCtx := newTestSyncCtx()
pod := test.NewPod()
pod.SetAnnotations(map[string]string{common.AnnotationSyncOptions: tt.annotationVal})
pod.SetNamespace(test.FakeArgoCDNamespace)
syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Target: pod, Live: pod}}}
syncCtx.sync()
kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
assert.Equal(t, tt.want, kubectl.LastValidate)
})
}
}
func TestSelectiveSyncOnly(t *testing.T) {
syncCtx := newTestSyncCtx()
pod1 := test.NewPod()
pod1.SetName("pod-1")
pod2 := test.NewPod()
pod2.SetName("pod-2")
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{Target: pod1}},
}
syncCtx.syncResources = []v1alpha1.SyncOperationResource{{Kind: "Pod", Name: "pod-1"}}
tasks, successful := syncCtx.getSyncTasks()
assert.True(t, successful)
assert.Len(t, tasks, 1)
assert.Equal(t, "pod-1", tasks[0].name())
}
func TestUnnamedHooksGetUniqueNames(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.syncOp.SyncStrategy.Apply = nil
pod := test.NewPod()
pod.SetName("")
pod.SetAnnotations(map[string]string{common.AnnotationKeyHook: "PreSync,PostSync"})
syncCtx.compareResult = &comparisonResult{hooks: []*unstructured.Unstructured{pod}}
tasks, successful := syncCtx.getSyncTasks()
assert.True(t, successful)
assert.Len(t, tasks, 2)
assert.Contains(t, tasks[0].name(), "foobarb-presync-")
assert.Contains(t, tasks[1].name(), "foobarb-postsync-")
assert.Equal(t, "", pod.GetName())
}
func TestManagedResourceAreNotNamed(t *testing.T) {
syncCtx := newTestSyncCtx()
pod := test.NewPod()
pod.SetName("")
syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Target: pod}}}
tasks, successful := syncCtx.getSyncTasks()
assert.True(t, successful)
assert.Len(t, tasks, 1)
assert.Equal(t, "", tasks[0].name())
assert.Equal(t, "", pod.GetName())
}
func TestDeDupingTasks(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.syncOp.SyncStrategy.Apply = nil
pod := test.NewPod()
pod.SetAnnotations(map[string]string{common.AnnotationKeyHook: "Sync"})
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{Target: pod}},
hooks: []*unstructured.Unstructured{pod},
}
tasks, successful := syncCtx.getSyncTasks()
assert.True(t, successful)
assert.Len(t, tasks, 1)
}
func TestObjectsGetANamespace(t *testing.T) {
syncCtx := newTestSyncCtx()
pod := test.NewPod()
syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Target: pod}}}
tasks, successful := syncCtx.getSyncTasks()
assert.True(t, successful)
assert.Len(t, tasks, 1)
assert.Equal(t, test.FakeArgoCDNamespace, tasks[0].namespace())
assert.Equal(t, "", pod.GetNamespace())
}
func TestPersistRevisionHistory(t *testing.T) {
@@ -453,7 +444,7 @@ func TestPersistRevisionHistory(t *testing.T) {
}
data := fakeData{
apps: []runtime.Object{app, defaultProject},
manifestResponse: &repository.ManifestResponse{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
@@ -490,7 +481,7 @@ func TestPersistRevisionHistoryRollback(t *testing.T) {
}
data := fakeData{
apps: []runtime.Object{app, defaultProject},
manifestResponse: &repository.ManifestResponse{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
@@ -526,3 +517,146 @@ func TestPersistRevisionHistoryRollback(t *testing.T) {
assert.Equal(t, source, updatedApp.Status.History[0].Source)
assert.Equal(t, "abc123", updatedApp.Status.History[0].Revision)
}
func TestSyncFailureHookWithSuccessfulSync(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.syncOp.SyncStrategy.Apply = nil
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{Target: test.NewPod()}},
hooks: []*unstructured.Unstructured{test.NewHook(HookTypeSyncFail)},
}
syncCtx.sync()
assert.Equal(t, OperationSucceeded, syncCtx.opState.Phase)
// only one result, we did not run the failure failureHook
assert.Len(t, syncCtx.syncRes.Resources, 1)
}
func TestSyncFailureHookWithFailedSync(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.syncOp.SyncStrategy.Apply = nil
pod := test.NewPod()
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{Target: pod}},
hooks: []*unstructured.Unstructured{test.NewHook(HookTypeSyncFail)},
}
syncCtx.kubectl = &kubetest.MockKubectlCmd{
Commands: map[string]kubetest.KubectlOutput{pod.GetName(): {Err: fmt.Errorf("")}},
}
syncCtx.sync()
syncCtx.sync()
assert.Equal(t, OperationFailed, syncCtx.opState.Phase)
assert.Len(t, syncCtx.syncRes.Resources, 2)
}
func TestRunSyncFailHooksFailed(t *testing.T) {
// Tests that other SyncFail Hooks run even if one of them fail.
syncCtx := newTestSyncCtx()
syncCtx.syncOp.SyncStrategy.Apply = nil
pod := test.NewPod()
successfulSyncFailHook := test.NewHook(HookTypeSyncFail)
successfulSyncFailHook.SetName("successful-sync-fail-hook")
failedSyncFailHook := test.NewHook(HookTypeSyncFail)
failedSyncFailHook.SetName("failed-sync-fail-hook")
syncCtx.compareResult = &comparisonResult{
managedResources: []managedResource{{Target: pod}},
hooks: []*unstructured.Unstructured{successfulSyncFailHook, failedSyncFailHook},
}
syncCtx.kubectl = &kubetest.MockKubectlCmd{
Commands: map[string]kubetest.KubectlOutput{
// Fail operation
pod.GetName(): {Err: fmt.Errorf("")},
// Fail a single SyncFail hook
failedSyncFailHook.GetName(): {Err: fmt.Errorf("")}},
}
syncCtx.sync()
syncCtx.sync()
fmt.Println(syncCtx.syncRes.Resources)
fmt.Println(syncCtx.opState.Phase)
// Operation as a whole should fail
assert.Equal(t, OperationFailed, syncCtx.opState.Phase)
// failedSyncFailHook should fail
assert.Equal(t, OperationFailed, syncCtx.syncRes.Resources[1].HookPhase)
assert.Equal(t, ResultCodeSyncFailed, syncCtx.syncRes.Resources[1].Status)
// successfulSyncFailHook should be synced running (it is an nginx pod)
assert.Equal(t, OperationRunning, syncCtx.syncRes.Resources[2].HookPhase)
assert.Equal(t, ResultCodeSynced, syncCtx.syncRes.Resources[2].Status)
}
func Test_syncContext_isSelectiveSync(t *testing.T) {
type fields struct {
compareResult *comparisonResult
syncResources []SyncOperationResource
}
oneSyncResource := []SyncOperationResource{{}}
oneResource := func(group, kind, name string, hook bool) *comparisonResult {
return &comparisonResult{resources: []v1alpha1.ResourceStatus{{Group: group, Kind: kind, Name: name, Hook: hook}}}
}
tests := []struct {
name string
fields fields
want bool
}{
{"Empty", fields{}, false},
{"OneCompareResult", fields{oneResource("", "", "", false), []SyncOperationResource{}}, true},
{"OneSyncResource", fields{&comparisonResult{}, oneSyncResource}, true},
{"Equal", fields{oneResource("", "", "", false), oneSyncResource}, false},
{"EqualOutOfOrder", fields{&comparisonResult{resources: []v1alpha1.ResourceStatus{{Group: "a"}, {Group: "b"}}}, []SyncOperationResource{{Group: "b"}, {Group: "a"}}}, false},
{"KindDifferent", fields{oneResource("foo", "", "", false), oneSyncResource}, true},
{"GroupDifferent", fields{oneResource("", "foo", "", false), oneSyncResource}, true},
{"NameDifferent", fields{oneResource("", "", "foo", false), oneSyncResource}, true},
{"HookIgnored", fields{oneResource("", "", "", true), []SyncOperationResource{}}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &syncContext{
compareResult: tt.fields.compareResult,
syncResources: tt.fields.syncResources,
}
if got := sc.isSelectiveSync(); got != tt.want {
t.Errorf("syncContext.isSelectiveSync() = %v, want %v", got, tt.want)
}
})
}
}
func Test_syncContext_liveObj(t *testing.T) {
type fields struct {
compareResult *comparisonResult
}
type args struct {
obj *unstructured.Unstructured
}
obj := test.NewPod()
obj.SetNamespace("my-ns")
found := test.NewPod()
tests := []struct {
name string
fields fields
args args
want *unstructured.Unstructured
}{
{"None", fields{compareResult: &comparisonResult{managedResources: []managedResource{}}}, args{obj: &unstructured.Unstructured{}}, nil},
{"Found", fields{compareResult: &comparisonResult{managedResources: []managedResource{{Group: obj.GroupVersionKind().Group, Kind: obj.GetKind(), Namespace: obj.GetNamespace(), Name: obj.GetName(), Live: found}}}}, args{obj: obj}, found},
{"EmptyNamespace", fields{compareResult: &comparisonResult{managedResources: []managedResource{{Group: obj.GroupVersionKind().Group, Kind: obj.GetKind(), Name: obj.GetName(), Live: found}}}}, args{obj: obj}, found},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &syncContext{
compareResult: tt.fields.compareResult,
}
if got := sc.liveObj(tt.args.obj); !reflect.DeepEqual(got, tt.want) {
t.Errorf("syncContext.liveObj() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -10,53 +10,57 @@ Then, to get a good grounding in Go, try out [the tutorial](https://tour.golang.
Install:
* [docker](https://docs.docker.com/install/#supported-platforms)
* [git](https://git-scm.com/) and [git-lfs](https://git-lfs.github.com/)
* [golang](https://golang.org/)
* [dep](https://github.com/golang/dep)
* [protobuf](https://developers.google.com/protocol-buffers/)
* [ksonnet](https://github.com/ksonnet/ksonnet#install)
* [helm](https://github.com/helm/helm/releases)
* [kustomize](https://github.com/kubernetes-sigs/kustomize/releases)
* [go-swagger](https://github.com/go-swagger/go-swagger/blob/master/docs/install.md)
* [jq](https://stedolan.github.io/jq/)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
* [kubectx](https://kubectx.dev)
* [minikube](https://kubernetes.io/docs/setup/minikube/) or Docker for Desktop
```
brew tap go-swagger/go-swagger
brew install go dep protobuf kubectl ksonnet/tap/ks kubernetes-helm jq go-swagger
Brew users can quickly install the lot:
```bash
brew install git-lfs go dep kubectl kubectx ksonnet/tap/ks kubernetes-helm kustomize
```
Set up environment variables (e.g. is `~/.bashrc`):
```
```bash
export GOPATH=~/go
export PATH=$PATH:$GOPATH/bin
```
Install go dependencies:
Checkout the code:
```
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
go get -u gopkg.in/alecthomas/gometalinter.v2
go get -u github.com/mattn/goreman
gometalinter.v2 --install
```bash
go get -u github.com/argoproj/argo-cd
cd ~/go/src/github.com/argoproj/argo-cd
```
## Building
Ensure dependencies are up to date first:
```shell
make dep
```
go get -u github.com/argoproj/argo-cd
dep ensure
Build `cli`, `image`, and `argocd-util` as default targets by running make:
```bash
make
```
The make command can take a while, and we recommend building the specific component you are working on
* `make codegen` - Builds protobuf and swagger files
* `make codegen` - Builds protobuf and swagger files.
Note: `make codegen` is slow because it uses docker + volume mounts. To improve performance you might install binaries from `./hack/Dockerfile.dev-tools`
and use `make codegen-local`. It is still recommended to run `make codegen` once before sending PR to make sure correct version of codegen tools is used.
* `make cli` - Make the argocd CLI tool
* `make server` - Make the API/repo/controller server
* `make argocd-util` - Make the administrator's utility, used for certain tasks such as import/export
@@ -65,23 +69,19 @@ The make command can take a while, and we recommend building the specific compon
To run unit tests:
```
```bash
make test
```
To run e2e tests:
```
make test-e2e
```
Check out the following [documentation](https://github.com/argoproj/argo-cd/blob/master/docs/developer-guide/test-e2e.md) for instructions on running the e2e tests.
## Running Locally
It is much easier to run and debug if you run ArgoCD on your local machine than in the Kubernetes cluster.
You should scale the deployemnts to zero:
You should scale the deployments to zero:
```
```bash
kubectl -n argocd scale deployment.extensions/argocd-application-controller --replicas 0
kubectl -n argocd scale deployment.extensions/argocd-dex-server --replicas 0
kubectl -n argocd scale deployment.extensions/argocd-repo-server --replicas 0
@@ -89,27 +89,18 @@ kubectl -n argocd scale deployment.extensions/argocd-server --replicas 0
kubectl -n argocd scale deployment.extensions/argocd-redis --replicas 0
```
Then checkout and build the UI next to your code
```
cd ~/go/src/github.com/argoproj
git clone git@github.com:argoproj/argo-cd-ui.git
```
Follow the UI's [README](https://github.com/argoproj/argo-cd-ui/blob/master/README.md) to build it.
Note: you'll need to use the https://localhost:6443 cluster now.
Then start the services:
```
```bash
cd ~/go/src/github.com/argoproj/argo-cd
goreman start
make start
```
You can now execute `argocd` command against your locally running ArgoCD by appending `--server localhost:8080 --plaintext --insecure`, e.g.:
```
```bash
argocd app set guestbook --path guestbook --repo https://github.com/argoproj/argocd-example-apps.git --dest-server https://localhost:6443 --dest-namespace default --server localhost:8080 --plaintext --insecure
```
@@ -123,19 +114,19 @@ You may need to run containers locally, so here's how:
Create login to Docker Hub, then login.
```
```bash
docker login
```
Add your username as the environment variable, e.g. to your `~/.bash_profile`:
```
```bash
export IMAGE_NAMESPACE=alexcollinsintuit
```
If you have not built the UI image (see [the UI README](https://github.com/argoproj/argo-cd-ui/blob/master/README.md)), then do the following:
If you have not built the UI image (see [the UI README](https://github.com/argoproj/argo-cd/blob/master/ui/README.md)), then do the following:
```
```bash
docker pull argoproj/argocd-ui:latest
docker tag argoproj/argocd-ui:latest $IMAGE_NAMESPACE/argocd-ui:latest
docker push $IMAGE_NAMESPACE/argocd-ui:latest
@@ -143,25 +134,25 @@ docker push $IMAGE_NAMESPACE/argocd-ui:latest
Build the images:
```
```bash
DOCKER_PUSH=true make image
```
Update the manifests:
```
```bash
make manifests
```
Install the manifests:
```
```bash
kubectl -n argocd apply --force -f manifests/install.yaml
```
Scale your deployments up:
```
```bash
kubectl -n argocd scale deployment.extensions/argocd-application-controller --replicas 1
kubectl -n argocd scale deployment.extensions/argocd-dex-server --replicas 1
kubectl -n argocd scale deployment.extensions/argocd-repo-server --replicas 1
@@ -169,17 +160,4 @@ kubectl -n argocd scale deployment.extensions/argocd-server --replicas 1
kubectl -n argocd scale deployment.extensions/argocd-redis --replicas 1
```
Now you can set-up the port-forwarding (see [README](README.md)) and open the UI or CLI.
## Pre-commit Checks
Before you commit, make sure you've formatted and linted your code, or your PR will fail CI:
```
STAGED_GO_FILES=$(git diff --cached --name-only | grep ".go$")
gofmt -w $STAGED_GO_FILES
make codgen
make precommit ;# lint and test
```
Now you can set-up the port-forwarding and open the UI or CLI.

View File

@@ -1,38 +0,0 @@
# Argo CD Documentation
## [Getting Started](getting_started.md)
## Concepts
* [Architecture](architecture.md)
* [Tracking Strategies](tracking_strategies.md)
## Quick Reference
| Name | Kind | Description |
|------|------|-------------|
| [`argocd-cm.yaml`](argocd-cm.yaml) | ConfigMap | General Argo CD configuration |
| [`argocd-secret.yaml`](argocd-secret.yaml) | Secret | Password, Certificates, Signing Key |
| [`argocd-rbac-cm.yaml`](argocd-rbac-cm.yaml) | ConfigMap | RBAC Configuration |
| [`application.yaml`](application.yaml) | Application | Example application spec |
| [`project.yaml`](argocd-rbac-cm.yaml) | AppProject | Example project spec |
## Features
* [Application Sources](application_sources.md)
* [Application Parameters](parameters.md)
* [Projects](projects.md)
* [Automated Sync](auto_sync.md)
* [Resource Health](health.md)
* [Resource Hooks](resource_hooks.md)
* [Resource Diffing](diffing.md)
* [Single Sign On](sso.md)
* [Webhooks](webhook.md)
* [RBAC](rbac.md)
* [Declarative Setup](declarative-setup.md)
* [Prometheus Metrics](metrics.md)
* [Custom Tooling](custom_tools.md)
## Other
* [Security](security.md)
* [Best Practices](best_practices.md)
* [Configuring Ingress](ingress.md)
* [Integration with CI Pipelines](ci_automation.md)
* [F.A.Q.](faq.md)

6
docs/SUPPORT.md Normal file
View File

@@ -0,0 +1,6 @@
# Support
1. Make sure you've read [understanding the basics](understand_the_basics.md) the [getting started guide](getting_started.md).
2. Looked for an answer [the frequently asked questions](faq.md).
3. Ask a question in [the Argo CD Slack channel ⧉](https://argoproj.github.io/community/join-slack).
4. [Read issues, report a bug, or request a feature ⧉](https://github.com/argoproj/argo-cd/issues)

View File

@@ -1,136 +0,0 @@
# Application Source Types
Argo CD supports several different ways in which kubernetes manifests can be defined:
* [ksonnet](https://ksonnet.io) applications
* [kustomize](https://kustomize.io) applications
* [helm](https://helm.sh) charts
* Directory of YAML/json/jsonnet manifests
* Any custom config management tool configured as a config management plugin
Some additional considerations should be made when deploying apps of a particular type:
## Ksonnet
### Environments
Ksonnet has a first class concept of an "environment." To create an application from a ksonnet
app directory, an environment must be specified. For example, the following command creates the
"guestbook-default" app, which points to the `default` environment:
```
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
```
### Parameters
Ksonnet parameters all belong to a component. For example, the following are the parameters
available in the guestbook app, all of which belong to the `guestbook-ui` component:
```
$ ks param list
COMPONENT PARAM VALUE
========= ===== =====
guestbook-ui containerPort 80
guestbook-ui image "gcr.io/heptio-images/ks-guestbook-demo:0.1"
guestbook-ui name "guestbook-ui"
guestbook-ui replicas 1
guestbook-ui servicePort 80
guestbook-ui type "LoadBalancer"
```
When overriding ksonnet parameters in Argo CD, the component name should also be specified in the
`argocd app set` command, in the form of `-p COMPONENT=PARAM=VALUE`. For example:
```
argocd app set guestbook-default -p guestbook-ui=image=gcr.io/heptio-images/ks-guestbook-demo:0.1
```
## Helm
### Values Files
Helm has the ability to use a different, or even multiple "values.yaml" files to derive its
parameters from. Alternate or multiple values file(s), can be specified using the `--values`
flag. The flag can be repeated to support multiple values files:
```
argocd app set helm-guestbook --values values-production.yaml
```
### Helm Parameters
Helm has the ability to set parameter values, which override any values in
a `values.yaml`. For example, `service.type` is a common parameter which is exposed in a Helm chart:
```
helm template . --set service.type=LoadBalancer
```
Similarly Argo CD can override values in the `values.yaml` parameters using `argo app set` command,
in the form of `-p PARAM=VALUE`. For example:
```
argocd app set helm-guestbook -p service.type=LoadBalancer
```
### Helm Hooks
Helm hooks are equivalent in concept to [Argo CD resource hooks](resource_hooks.md). In helm, a hook
is any normal kubernetes resource annotated with the `helm.sh/hook` annotation. When Argo CD deploys
helm application which contains helm hooks, all helm hook resources are currently ignored during
the `kubectl apply` of the manifests. There is an
[open issue](https://github.com/argoproj/argo-cd/issues/355) to map Helm hooks to Argo CD's concept
of Pre/Post/Sync hooks.
### Random Data
Helm templating has the ability to generate random data during chart rendering via the
`randAlphaNum` function. Many helm charts from the [charts repository](https://github.com/helm/charts)
make use of this feature. For example, the following is the secret for the
[redis helm chart](https://github.com/helm/charts/blob/master/stable/redis/templates/secrets.yaml):
```
data:
{{- if .Values.password }}
redis-password: {{ .Values.password | b64enc | quote }}
{{- else }}
redis-password: {{ randAlphaNum 10 | b64enc | quote }}
{{- end }}
```
The Argo CD application controller periodically compares git state against the live state, running
the `helm template <CHART>` command to generate the helm manifests. Because the random value is
regenerated every time the comparison is made, any application which makes use of the `randAlphaNum`
function will always be in an `OutOfSync` state. This can be mitigated by explicitly setting a
value, in the values.yaml such that the value is stable between each comparison. For example:
```
argocd app set redis -p password=abc123
```
## Config Management Plugins
Argo CD allows integrating more config management tools using config management plugins. Following changes are required to configure new plugin:
* Make sure required binaries are available in `argocd-repo-server` pod. The binaries can be added via volume mounts or using custom image (see [custom_tools](custom_tools.md)).
* Register a new plugin in `argocd-cm` ConfigMap:
```yaml
data:
configManagementPlugins: |
- name: pluginName
init: # Optional command to initialize application source directory
command: ["sample command"]
args: ["sample args"]
generate: # Command to generate manifests YAML
command: ["sample command"]
args: ["sample args"]
```
The `generate` command must print a valid YAML stream to stdout. Both `init` and `generate` commands are executed inside the application source directory.
Commands have access to system environment variables and following additional variables:
`ARGOCD_APP_NAME` - name of application; `ARGOCD_APP_NAMESPACE` - destination application namespace
* Create an application and specify required config management plugin name.
```
argocd app create <appName> --config-management-plugin <pluginName>
```
More config management plugin examples are available in [argocd-example-apps](https://github.com/argoproj/argocd-example-apps/tree/master/plugins).

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 MiB

After

Width:  |  Height:  |  Size: 3.5 MiB

View File

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/assets/dashboard.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -1,58 +0,0 @@
# Automation from CI Pipelines
Argo CD follows the GitOps model of deployment, where desired configuration changes are first
pushed to git, and the cluster state then syncs to the desired state in git. This is a departure
from imperative pipelines which do not traditionally use git repositories to hold application
config.
To push new container images into to a cluster managed by Argo CD, the following workflow (or
variations), might be used:
1. Build and publish a new container image
```
docker build -t mycompany/guestbook:v2.0 .
docker push mycompany/guestbook:v2.0
```
2. Update the local manifests using your preferred templating tool, and push the changes to git.
NOTE: the use of a different git repository to hold your kubernetes manifests (separate from
your application source code), is highly recommended. See [best practices](best_practices.md)
for further rationale.
```
git clone https://github.com/mycompany/guestbook-config.git
cd guestbook-config
# kustomize
kustomize edit set imagetag mycompany/guestbook:v2.0
# ksonnet
ks param set guestbook image mycompany/guestbook:v2.0
# plain yaml
kubectl patch --local -f config-deployment.yaml -p '{"spec":{"template":{"spec":{"containers":[{"name":"guestbook","image":"mycompany/guestbook:v2.0"}]}}}}' -o yaml
git add . -m "Update guestbook to v2.0"
git push
```
3. Synchronize the app (Optional)
For convenience, the argocd CLI can be downloaded directly from the API server. This is
useful so that the CLI used in the CI pipeline is always kept in-sync and uses argocd binary
that is always compatible with the Argo CD API server.
```
export ARGOCD_SERVER=argocd.mycompany.com
export ARGOCD_AUTH_TOKEN=<JWT token generated from project>
curl -sSL -o /usr/local/bin/argocd https://${ARGOCD_SERVER}/download/argocd-linux-amd64
argocd app sync guestbook
argocd app wait guestbook
```
If [automated synchronization](auto_sync.md) is configured for the application, this step is
unnecessary. The controller will automatically detect the new config (fast tracked using a
[webhook](webhook.md), or polled every 3 minutes), and automatically sync the new manifests.

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