Compare commits

..

64 Commits

Author SHA1 Message Date
Alexander Matyushentsev
cf4e6bc953 Update manifests to v0.12.3 2019-04-29 22:14:42 -07:00
Alexander Matyushentsev
9fb462ed42 Issue 1476 - Avoid validating repository in application controller (#1535) 2019-04-29 15:48:00 -07:00
Alexander Matyushentsev
867e4b2d44 Issue #1476 - Add repo server grpc call timeout (#1528) 2019-04-29 15:44:45 -07:00
Alexander Matyushentsev
ff0c23fd46 Update manifests to v0.12.2 2019-04-22 14:28:14 -07:00
Alexander Matyushentsev
400b5f57d5 Issue #1476 - Fix racing condition in controller cache (#1485) 2019-04-18 08:24:19 -07:00
dthomson25
5f9c84b218 Fix Failing Linter (#1350) 2019-04-18 08:24:05 -07:00
Alexander Matyushentsev
88611d2b61 Generate random name for grpc proxy unix socket file instead of time stamp (#1455) 2019-04-12 14:27:42 -07:00
Alexander Matyushentsev
1d70f47f68 Issue #1446 - Delete helm temp directories (#1449) 2019-04-12 07:35:19 -07:00
Alexander Matyushentsev
6f6caae5e9 Issue #1389 - Fix null pointer exception in secret normalization function (#1443) 2019-04-12 07:35:14 -07:00
Alexander Matyushentsev
a3a972611b Issue #1425 - Argo CD should not delete CRDs (#1428) 2019-04-11 09:07:55 -07:00
Alexander Matyushentsev
6a1751ad8d Update manifests to v0.12.1 2019-04-09 14:03:22 -07:00
Alexander Matyushentsev
16e061ffb6 Run 'go fmt' for application.go and server.go (#1417) 2019-04-09 10:31:26 -07:00
dthomson25
5953a00204 Add patch audit (#1416)
* Add auditing to patching commands

* Omit Patch Resource logs to prevent secret leaks
2019-04-09 08:57:37 -07:00
Alexander Matyushentsev
d0f20393cc Issue #1406 - Don't try deleting application resource if it already have (#1407) 2019-04-09 08:30:48 -07:00
Alexander Matyushentsev
161e56c844 Issue #1404 - App controller unnecessary set namespace to cluster level resources (#1405) 2019-04-09 08:30:39 -07:00
Alexander Matyushentsev
410e00a5f5 Issue #1374 - Add k8s objects circular dependency protection to getApp method (#1379) 2019-04-09 08:30:29 -07:00
Alexander Matyushentsev
cafb0e1265 Issue #1366 - Fix null pointer dereference error in 'argocd app wait' (#1380) 2019-04-09 08:28:24 -07:00
Alexander Matyushentsev
2ff8650cef Issue #1012 - kubectl v1.13 fails to convert extensions/NetworkPolicy (#1360) 2019-04-09 08:28:20 -07:00
Tom Wieczorek
fe9619ba49 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-09 08:28:16 -07:00
Alexander Matyushentsev
0968103655 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-09 08:28:03 -07:00
Alexander Matyushentsev
f17e0ce7a6 Issue #1218 - Allow using any name for secrets which store cluster credentials (#1336) 2019-04-09 08:26:01 -07:00
Alexander Matyushentsev
7b888e6d9f 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-04-09 08:25:57 -07:00
Jesse Suen
7d1b9f79f7 Update argocd-util import/export to support proper backup and restore (#1328) 2019-04-09 08:25:51 -07:00
Alex Collins
7878a6a9b0 Adds support for kustomize edit set image. Closes #1275 (#1324) 2019-04-09 08:25:46 -07:00
Alex Collins
6fec791452 Fixs deps (#1325) 2019-04-09 08:25:41 -07:00
Alexander Matyushentsev
f19f6c29a7 Issue #1319 - Fix invalid group filtering in 'patch-resource' command (#1320) 2019-04-09 08:25:22 -07:00
Alexander Matyushentsev
3625d65ea7 Issue #1135 - Run e2e tests in throw-away kubernetes cluster (#1318)
* Issue #1135 - Run e2e tests in throw-away kubernetes cluster
2019-04-09 08:24:49 -07:00
Jesse Suen
cd4bb2553d Update version and manifests v0.12.0 2019-03-22 11:58:31 -07:00
Jesse Suen
47e86f6488 Use Recreate deployment strategy for controller (#1315) 2019-03-22 11:52:40 -07:00
Jesse Suen
b0282b17f9 Fix goroutine leak in RetryUntilSucceed (#1314) 2019-03-22 11:52:34 -07:00
Jesse Suen
f6661cc841 Support a separate OAuth2 CLI clientID different from server (#1307) 2019-03-22 03:31:00 -07:00
Andre Krueger
e8deeb2622 Honor os environment variables for helm commands (#1306) 2019-03-22 03:31:00 -07:00
Alexander Matyushentsev
b0301b43dd Issue #1308 - argo diff --local fails if live object does not exist (#1309) 2019-03-21 15:38:47 -07:00
Jesse Suen
d5ee07ef62 Update VERSION to v0.12.0-rc6 2019-03-20 14:56:19 -07:00
Jesse Suen
9468012cba Update manifests to v0.12.0-rc6 2019-03-20 14:48:48 -07:00
Alexander Matyushentsev
3ffc4586dc Unavailable cache should not prevent reconciling/syncing application (#1303) 2019-03-20 14:44:09 -07:00
Jesse Suen
14c9b63f21 Update redis-ha chart to resolve redis failover issues (#1301) 2019-03-20 14:42:30 -07:00
Marc
20dea3fa9e only print to stdout, if there is a diff + exit code (#1288) 2019-03-20 08:53:49 -07:00
Alexander Matyushentsev
8f990a9b91 Issue #1258 - Disable CGO_ENABLED for server/controller binaries (#1286) 2019-03-20 08:53:40 -07:00
Alexander Matyushentsev
3ca2a8bc2a Controller don't stop running watches on cluster resync (#1298) 2019-03-20 08:53:31 -07:00
Alexander Matyushentsev
268df4364d Update manifests to v0.12.0-rc5 2019-03-18 23:49:42 -07:00
Alexander Matyushentsev
68bb7e2046 Issue #1290 - Fix concurrent read/write error in state cache (#1293) 2019-03-18 23:39:18 -07:00
Jesse Suen
4e921a279c Fix a goroutine leak in api-server application.PodLogs and application.Watch (#1292) 2019-03-18 21:51:33 -07:00
Alexander Matyushentsev
ff72b82bd6 Issue #1287 - Fix local diff of non-namespaced resources. Also handle duplicates in local diff (#1289) 2019-03-18 21:51:26 -07:00
Jesse Suen
17393c3e70 Fix isssue where argocd app set -p required repo privileges. (#1280)
Grant patch privileges to argocd-server
2019-03-18 14:42:18 -07:00
Alexander Matyushentsev
3ed3a44944 Issue #1070 - Handle duplicated resource definitions (#1284) 2019-03-18 14:41:53 -07:00
Jesse Suen
27922c8f83 Add golang prometheus metrics to controller and repo-server (#1281) 2019-03-18 14:41:13 -07:00
Jesse Suen
c35f35666f Git cloning via SSH was not verifying host public key (#1276) 2019-03-15 14:29:40 -07:00
Alexander Matyushentsev
6a61987d3d Rename Application observedAt to reconciledAt and use observedAt to notify about partial app refresh (#1270) 2019-03-14 16:42:59 -07:00
Alexander Matyushentsev
c68e4a5a56 Bug fix: set 'Version' field while saving application resources tree (#1268) 2019-03-14 15:53:21 -07:00
Alexander Matyushentsev
1525f8e051 Avoid doing full reconciliation unless application 'managed' resource has changed (#1267) 2019-03-14 15:01:38 -07:00
Jesse Suen
1c2b248d27 Support kustomize apps with remote bases in private repos in the same host (#1264) 2019-03-14 14:26:07 -07:00
Alexander Matyushentsev
22fe538645 Update manifests to v0.12.0-rc4 2019-03-12 14:27:35 -07:00
Alexander Matyushentsev
24d73e56f1 Issue #1252 - Application controller incorrectly build application objects tree (#1253) 2019-03-12 12:21:50 -07:00
Alexander Matyushentsev
8f93bdc2a5 Issue #1247 - Fix CRD creation/deletion handling (#1249) 2019-03-12 12:21:45 -07:00
Alex Collins
6d982ca397 Migrates from gometalinter to golangci-lint. Closes #1225 (#1226) 2019-03-12 12:21:34 -07:00
Jesse Suen
eff67bffad Replace git fetch implementation with git CLI (from go-git) (#1244) 2019-03-08 14:09:22 -08:00
Jesse Suen
2b781eea49 Bump version and manifests to v0.12.0-rc3 2019-03-06 16:53:14 -08:00
Jesse Suen
2f0dc20235 Fix nil pointer dereference in CompareAppState (#1234) 2019-03-06 13:46:52 -08:00
Jesse Suen
36dc50c121 Bump version and manifests to v0.12.0-rc2 2019-03-06 02:01:07 -08:00
Alexander Matyushentsev
f8f974e871 Issue #1231 - Deprecated resource kinds from 'extensions' groups are not reconciled correctly (#1232) 2019-03-06 01:59:33 -08:00
Alexander Matyushentsev
c4b474ae98 Issue #1229 - App creation failed for public repository (#1230) 2019-03-06 01:59:26 -08:00
Jesse Suen
8d98d6e058 Sort kustomize params in GetAppDetails 2019-03-05 15:45:12 -08:00
Jesse Suen
233708ecdd Set release to v0.12.0-rc1 2019-03-05 15:23:34 -08:00
294 changed files with 277236 additions and 24461 deletions

View File

@@ -1,6 +1,5 @@
ignore:
- "**/*.pb.go"
- "**/*.pb.gw.go"
- "**/*_test.go"
- "pkg/apis/.*"
- "pkg/client/.*"

View File

@@ -1,27 +0,0 @@
---
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**
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.
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +0,0 @@
---
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.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

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

1
.github/stale.yml vendored
View File

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

1
.gitignore vendored
View File

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

View File

@@ -19,3 +19,4 @@ linters:
- ineffassign
- unconvert
- misspell
disable-all: true

View File

@@ -1,291 +1,5 @@
# Changelog
## v1.0.0
### New Features
#### Network View
TODO
#### 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
* 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.
### 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
#### 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
## 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

@@ -18,42 +18,35 @@ Install:
* [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/)
* [kubectx](https://kubectx.dev)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
* [minikube](https://kubernetes.io/docs/setup/minikube/) or Docker for Desktop
Brew users can quickly install the lot:
```bash
brew tap go-swagger/go-swagger
brew install go dep protobuf kubectl kubectx ksonnet/tap/ks kubernetes-helm jq go-swagger
```
!!! note "Kustomize"
Since Argo CD supports Kustomize v1.0 and v2.0, you will need to install both versions in order for the unit tests to run. The Kustomize 1 unit test expects to find a `kustomize1` binary in the path. You can use this [link](https://github.com/argoproj/argo-cd/blob/master/Dockerfile#L66-L69) to find the Kustomize 1 currently used by Argo CD and modify the curl command to download the correct OS.
brew tap go-swagger/go-swagger
brew install go dep protobuf kubectl ksonnet/tap/ks kubernetes-helm jq go-swagger
```
Set up environment variables (e.g. is `~/.bashrc`):
```bash
```
export GOPATH=~/go
export PATH=$PATH:$GOPATH/bin
```
Install go dependencies:
```bash
```
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 github.com/golangci/golangci-lint/cmd/golangci-lint
go get -u github.com/mattn/goreman
go get -u gotest.tools/gotestsum
```
## Building
```bash
```
go get -u github.com/argoproj/argo-cd
dep ensure
make
@@ -70,19 +63,23 @@ The make command can take a while, and we recommend building the specific compon
To run unit tests:
```bash
```
make test
```
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.
To run e2e tests:
```
make test-e2e
```
## 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 deployments to zero:
You should scale the deployemnts 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
@@ -103,14 +100,14 @@ 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
make start
goreman 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
```
@@ -124,19 +121,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:
```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
@@ -144,25 +141,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
@@ -170,13 +167,13 @@ 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 and open the UI or CLI.
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:
```bash
```
STAGED_GO_FILES=$(git diff --cached --name-only | grep ".go$")
gofmt -w $STAGED_GO_FILES

55
Gopkg.lock generated
View File

@@ -48,14 +48,6 @@
pruneopts = ""
revision = "09c41003ee1d5015b75f331e52215512e7145b8d"
[[projects]]
branch = "master"
digest = "1:0cac9c70f3308d54ed601878aa66423eb95c374616fdd7d2ea4e2d18b045ae62"
name = "github.com/ant31/crd-validation"
packages = ["pkg"]
pruneopts = ""
revision = "38f6a293f140402953f884b015014e0cd519bbb3"
[[projects]]
branch = "master"
digest = "1:0caf9208419fa5db5a0ca7112affaa9550c54291dda8e2abac0c0e76181c959e"
@@ -1226,7 +1218,7 @@
[[projects]]
branch = "release-1.12"
digest = "1:3e3e9df293bd6f9fd64effc9fa1f0edcd97e6c74145cd9ab05d35719004dc41f"
digest = "1:ed04c5203ecbf6358fb6a774b0ecd40ea992d6dcc42adc1d3b7cf9eceb66b6c8"
name = "k8s.io/api"
packages = [
"admission/v1beta1",
@@ -1264,19 +1256,15 @@
"storage/v1beta1",
]
pruneopts = ""
revision = "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
revision = "475331a8afff5587f47d0470a93f79c60c573c03"
[[projects]]
branch = "master"
digest = "1:49e0fcdcaeaf937c6c608d1da19eb80de74fe990021278d49d46e10288659be6"
branch = "release-1.12"
digest = "1:39be82077450762b5e14b5268e679a14ac0e9c7d3286e2fcface437556a29e4c"
name = "k8s.io/apiextensions-apiserver"
packages = [
"pkg/apis/apiextensions",
"pkg/apis/apiextensions/v1beta1",
"pkg/features",
]
packages = ["pkg/features"]
pruneopts = ""
revision = "7f7d2b94eca3a7a1c49840e119a8bc03c3afb1e3"
revision = "ca1024863b48cf0701229109df75ac5f0bb4907e"
[[projects]]
branch = "release-1.12"
@@ -1335,15 +1323,15 @@
revision = "f71dbbc36e126f5a371b85f6cca96bc8c57db2b6"
[[projects]]
branch = "release-1.12"
digest = "1:b2c55ff9df6d053e40094b943f949c257c3f7dcdbb035c11487c93c96df9eade"
branch = "master"
digest = "1:cb3ac215bfac54696f64a6e5c46524a7fc0f7a8f9b7a22cccb2e1e83ac2d013f"
name = "k8s.io/apiserver"
packages = [
"pkg/features",
"pkg/util/feature",
]
pruneopts = ""
revision = "5e1c1f41ee34b3bb153f928f8c91c2a6dd9482a9"
revision = "19cf388d0a374e95329bf7d98e9bfd7da8853be0"
[[projects]]
branch = "release-9.0"
@@ -1461,7 +1449,7 @@
[[projects]]
branch = "release-1.12"
digest = "1:8108815d1aef9159daabdb3f0fcef04a88765536daf0c0cd29a31fdba135ee54"
digest = "1:e6fffdf0dfeb0d189a7c6d735e76e7564685d3b6513f8b19d3651191cb6b084b"
name = "k8s.io/code-generator"
packages = [
"cmd/go-to-protobuf",
@@ -1470,22 +1458,21 @@
"third_party/forked/golang/reflect",
]
pruneopts = ""
revision = "b1289fc74931d4b6b04bd1a259acfc88a2cb0a66"
revision = "3dcf91f64f638563e5106f21f50c31fa361c918d"
[[projects]]
branch = "master"
digest = "1:6a2a63e09a59caff3fd2d36d69b7b92c2fe7cf783390f0b7349fb330820f9a8e"
digest = "1:15710582bd5ceff07eee4726884f75f97f90366fde9307b8dd09500c75722456"
name = "k8s.io/gengo"
packages = [
"args",
"examples/set-gen/sets",
"generator",
"namer",
"parser",
"types",
]
pruneopts = ""
revision = "e17681d19d3ac4837a019ece36c2a0ec31ffe985"
revision = "8394c995ab8fbe52216f38d0e1a37de36d820528"
[[projects]]
digest = "1:4f5eb833037cc0ba0bf8fe9cae6be9df62c19dd1c869415275c708daa8ccfda5"
@@ -1496,19 +1483,15 @@
version = "v0.1.0"
[[projects]]
digest = "1:aae856b28533bfdb39112514290f7722d00a55a99d2d0b897c6ee82e6feb87b3"
branch = "master"
digest = "1:9a648ff9eb89673d2870c22fc011ec5db0fcff6c4e5174a650298e51be71bbf1"
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 = "master"
revision = "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
[[projects]]
digest = "1:6061aa42761235df375f20fa4a1aa6d1845cba3687575f3adb2ef3f3bc540af5"
@@ -1547,7 +1530,6 @@
input-imports = [
"github.com/Masterminds/semver",
"github.com/TomOnTime/utfutil",
"github.com/ant31/crd-validation/pkg",
"github.com/argoproj/argo/pkg/apis/workflow/v1alpha1",
"github.com/argoproj/argo/util",
"github.com/argoproj/pkg/exec",
@@ -1562,7 +1544,6 @@
"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",
@@ -1571,7 +1552,6 @@
"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",
@@ -1637,7 +1617,6 @@
"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/apimachinery/pkg/api/equality",
"k8s.io/apimachinery/pkg/api/errors",
"k8s.io/apimachinery/pkg/apis/meta/v1",
@@ -1671,8 +1650,6 @@
"k8s.io/client-go/util/flowcontrol",
"k8s.io/client-go/util/workqueue",
"k8s.io/code-generator/cmd/go-to-protobuf",
"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,7 +7,6 @@ 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",
@@ -62,7 +61,3 @@ required = [
[[constraint]]
branch = "master"
name = "github.com/yudai/gojsondiff"
[[override]]
revision = "master"
name = "k8s.io/kube-openapi"

View File

@@ -9,9 +9,6 @@ GIT_COMMIT=$(shell git rev-parse HEAD)
GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)
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)
TEST_CMD=$(shell [ "`which gotestsum`" != "" ] && echo gotestsum -- || echo go test)
PATH:=$(PATH):$(PWD)/hack
# docker image publishing options
DOCKER_PUSH=false
@@ -53,16 +50,12 @@ 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
codegen: protogen clientgen openapigen
codegen: protogen clientgen format-code
.PHONY: cli
cli: clean-debug
@@ -132,29 +125,21 @@ builder-image:
dep-ensure:
dep ensure -no-vendor
.PHONY: format-code
format-code:
./hack/format-code.sh
.PHONY: lint
lint:
golangci-lint run --fix
.PHONY: build
build:
go build `go list ./... | grep -v resource_customizations`
golangci-lint run
.PHONY: test
test:
$(TEST_CMD) -covermode=count -coverprofile=coverage.out `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
go test -covermode=count -coverprofile=coverage.out `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
.PHONY: test-e2e
test-e2e: cli
$(TEST_CMD) -v -failfast -timeout 20m ./test/e2e
.PHONY: start-e2e
start-e2e: cli
killall goreman || true
kubectl create ns argocd-e2e || true
kubens argocd-e2e
kustomize build test/manifests/base | kubectl apply -f -
make start
go test -v -failfast -timeout 20m ./test/e2e
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in packr boxes
.PHONY: clean-debug
@@ -165,14 +150,8 @@ clean-debug:
clean: clean-debug
-rm -rf ${CURRENT_DIR}/dist
.PHONY: start
start:
killall goreman || true
kubens argocd
goreman start
.PHONY: pre-commit
pre-commit: dep-ensure codegen build lint test
pre-commit: dep-ensure codegen format-code test lint
.PHONY: release-precheck
release-precheck: manifests

View File

@@ -1,5 +1,5 @@
controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:6379 --repo-server localhost: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:6379 --disable-auth --insecure --dex-server http://localhost:5556 --repo-server localhost:8081 --staticassets ../argo-cd-ui/dist/app"
repo-server: sh -c "FORCE_LOG_COLORS=1 go run ./cmd/argocd-repo-server/main.go --loglevel debug --redis localhost:6379"
controller: sh -c "ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:6379 --repo-server localhost:8081"
api-server: sh -c "ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-server/main.go --loglevel debug --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 --name argocd-redis -i -p 6379:6379 redis:5.0.3-alpine --save ""--appendonly no
redis: docker run --rm -i -p 6379:6379 redis:5.0.3-alpine --save "" --appendonly no

View File

@@ -7,26 +7,100 @@
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.
![Argo CD UI](docs/assets/argocd-ui.gif)
![Argo CD UI](docs/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
## Who uses Argo CD?
### Quickstart
Organizations below are **officially** using Argo CD. Please send a PR with your organization name if you are using Argo CD.
```bash
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```
1. [Intuit](https://www.intuit.com/)
2. [KompiTech GmbH](https://www.kompitech.com/)
3. [Yieldlab](https://www.yieldlab.de/)
4. [Ticketmaster](https://ticketmaster.com)
5. [CyberAgent](https://www.cyberagent.co.jp/en/)
6. [OpenSaaS Studio](https://opensaas.studio)
7. [Riskified](https://www.riskified.com/)
Follow our [getting started guide](docs/getting_started.md). Further [documentation](docs/)
is provided for additional features.
## Documentation
## How it works
To learn more about Argo CD [go to the complete documentation](https://argoproj.github.io/argo-cd/).
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
## 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

View File

@@ -1 +1 @@
1.0.2
0.12.3

View File

@@ -68,11 +68,6 @@
},
"name": "project",
"in": "query"
},
{
"type": "string",
"name": "resourceVersion",
"in": "query"
}
],
"responses": {
@@ -185,7 +180,7 @@
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/v1alpha1ApplicationTree"
"$ref": "#/definitions/applicationResourceTreeResponse"
}
}
}
@@ -217,11 +212,6 @@
},
"name": "project",
"in": "query"
},
{
"type": "string",
"name": "resourceVersion",
"in": "query"
}
],
"responses": {
@@ -560,85 +550,6 @@
}
}
},
"/api/v1/applications/{name}/resource/actions": {
"get": {
"tags": [
"ApplicationService"
],
"operationId": "ListResourceActions",
"parameters": [
{
"type": "string",
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "namespace",
"in": "query"
},
{
"type": "string",
"name": "resourceName",
"in": "query"
},
{
"type": "string",
"name": "version",
"in": "query"
},
{
"type": "string",
"name": "group",
"in": "query"
},
{
"type": "string",
"name": "kind",
"in": "query"
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/applicationResourceActionsListResponse"
}
}
}
},
"post": {
"tags": [
"ApplicationService"
],
"operationId": "RunResourceAction",
"parameters": [
{
"type": "string",
"name": "name",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/applicationApplicationResponse"
}
}
}
}
},
"/api/v1/applications/{name}/rollback": {
"post": {
"tags": [
@@ -787,6 +698,33 @@
}
}
},
"/api/v1/clusters-kubeconfig": {
"post": {
"tags": [
"ClusterService"
],
"summary": "CreateFromKubeConfig installs the argocd-manager service account into the cluster specified in the given kubeconfig and context",
"operationId": "CreateFromKubeConfig",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/clusterClusterCreateFromKubeConfigRequest"
}
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/v1alpha1Cluster"
}
}
}
}
},
"/api/v1/clusters/{cluster.server}": {
"put": {
"tags": [
@@ -1269,11 +1207,6 @@
},
"name": "helm.valueFiles",
"in": "query"
},
{
"type": "string",
"name": "ksonnet.environment",
"in": "query"
}
],
"responses": {
@@ -1370,11 +1303,6 @@
},
"name": "project",
"in": "query"
},
{
"type": "string",
"name": "resourceVersion",
"in": "query"
}
],
"responses": {
@@ -1517,17 +1445,36 @@
"applicationOperationTerminateResponse": {
"type": "object"
},
"applicationResourceActionsListResponse": {
"applicationResourceTreeResponse": {
"type": "object",
"properties": {
"actions": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ResourceAction"
"$ref": "#/definitions/v1alpha1ResourceNode"
}
}
}
},
"clusterClusterCreateFromKubeConfigRequest": {
"type": "object",
"properties": {
"context": {
"type": "string"
},
"inCluster": {
"type": "boolean",
"format": "boolean"
},
"kubeconfig": {
"type": "string"
},
"upsert": {
"type": "boolean",
"format": "boolean"
}
}
},
"clusterClusterResponse": {
"type": "object"
},
@@ -1567,12 +1514,6 @@
},
"name": {
"type": "string"
},
"scopes": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@@ -1699,14 +1640,6 @@
}
}
},
"repositoryKsonnetAppDetailsQuery": {
"type": "object",
"properties": {
"environment": {
"type": "string"
}
}
},
"repositoryKsonnetAppSpec": {
"type": "object",
"title": "KsonnetAppSpec contains Ksonnet app response\nThis roughly reflects: ksonnet/ksonnet/metadata/app/schema.go",
@@ -2034,20 +1967,6 @@
}
}
},
"v1LoadBalancerIngress": {
"description": "LoadBalancerIngress represents the status of a load-balancer ingress point:\ntraffic intended for the service should be sent to an ingress point.",
"type": "object",
"properties": {
"hostname": {
"type": "string",
"title": "Hostname is set for load-balancer ingress points that are DNS based\n(typically AWS load-balancers)\n+optional"
},
"ip": {
"type": "string",
"title": "IP is set for load-balancer ingress points that are IP based\n(typically GCE or OpenStack load-balancers)\n+optional"
}
}
},
"v1MicroTime": {
"description": "MicroTime is version of Time with microsecond level precision.\n\n+protobuf.options.marshal=false\n+protobuf.as=Timestamp\n+protobuf.options.(gogoproto.goproto_stringer)=false",
"type": "object",
@@ -2459,6 +2378,13 @@
"description": "ApplicationSource contains information about github repository, path within repository and target application environment.",
"type": "object",
"properties": {
"componentParameterOverrides": {
"type": "array",
"title": "ComponentParameterOverrides are a list of parameter override values\nDEPRECATED: use app source specific config instead",
"items": {
"$ref": "#/definitions/v1alpha1ComponentParameter"
}
},
"directory": {
"$ref": "#/definitions/v1alpha1ApplicationSourceDirectory"
},
@@ -2653,45 +2579,11 @@
"sourceType": {
"type": "string"
},
"summary": {
"$ref": "#/definitions/v1alpha1ApplicationSummary"
},
"sync": {
"$ref": "#/definitions/v1alpha1SyncStatus"
}
}
},
"v1alpha1ApplicationSummary": {
"type": "object",
"properties": {
"externalURLs": {
"description": "ExternalURLs holds all external URLs of application child resources.",
"type": "array",
"items": {
"type": "string"
}
},
"images": {
"description": "Images holds all images of application child resources.",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1alpha1ApplicationTree": {
"type": "object",
"title": "ApplicationTree holds nodes which belongs to the application",
"properties": {
"nodes": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ResourceNode"
}
}
}
},
"v1alpha1ApplicationWatchEvent": {
"description": "ApplicationWatchEvent contains information about application change.",
"type": "object",
@@ -2774,6 +2666,21 @@
}
}
},
"v1alpha1ComponentParameter": {
"type": "object",
"title": "ComponentParameter contains information about component parameter value",
"properties": {
"component": {
"type": "string"
},
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"v1alpha1ConnectionState": {
"type": "object",
"title": "ConnectionState contains information about remote resource connection state",
@@ -2997,37 +2904,6 @@
}
}
},
"v1alpha1ResourceAction": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"params": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ResourceActionParam"
}
}
}
},
"v1alpha1ResourceActionParam": {
"type": "object",
"properties": {
"default": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"v1alpha1ResourceDiff": {
"type": "object",
"title": "ResourceDiff holds the diff of a live and target resource object",
@@ -3079,101 +2955,25 @@
}
}
},
"v1alpha1ResourceNetworkingInfo": {
"type": "object",
"title": "ResourceNetworkingInfo holds networking resource related information",
"properties": {
"externalURLs": {
"description": "ExternalURLs holds list of URLs which should be available externally. List is populated for ingress resources using rules hostnames.",
"type": "array",
"items": {
"type": "string"
}
},
"ingress": {
"type": "array",
"items": {
"$ref": "#/definitions/v1LoadBalancerIngress"
}
},
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"targetLabels": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"targetRefs": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ResourceRef"
}
}
}
},
"v1alpha1ResourceNode": {
"type": "object",
"title": "ResourceNode contains information about live resource and its children",
"properties": {
"health": {
"$ref": "#/definitions/v1alpha1HealthStatus"
},
"images": {
"children": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/v1alpha1ResourceNode"
}
},
"group": {
"type": "string"
},
"info": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1InfoItem"
}
},
"networkingInfo": {
"$ref": "#/definitions/v1alpha1ResourceNetworkingInfo"
},
"parentRefs": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ResourceRef"
}
},
"resourceRef": {
"$ref": "#/definitions/v1alpha1ResourceRef"
},
"resourceVersion": {
"type": "string"
}
}
},
"v1alpha1ResourceOverride": {
"type": "object",
"title": "ResourceOverride holds configuration to customize resource diffing and health assessment",
"properties": {
"actions": {
"type": "string"
},
"healthLua": {
"type": "string"
},
"ignoreDifferences": {
"type": "string"
}
}
},
"v1alpha1ResourceRef": {
"type": "object",
"title": "ResourceRef includes fields which unique identify resource",
"properties": {
"group": {
"type": "string"
},
"kind": {
"type": "string"
},
@@ -3183,11 +2983,26 @@
"namespace": {
"type": "string"
},
"resourceVersion": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"v1alpha1ResourceOverride": {
"type": "object",
"title": "ResourceOverride holds configuration to customize resource diffing and health assessment",
"properties": {
"healthLua": {
"type": "string"
},
"ignoreDifferences": {
"type": "string"
}
}
},
"v1alpha1ResourceResult": {
"type": "object",
"title": "ResourceResult holds the operation result details of a specific resource",

View File

@@ -55,9 +55,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)

View File

@@ -10,6 +10,8 @@ import (
"os/exec"
"syscall"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/util"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -23,9 +25,6 @@ import (
"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/util/cli"
"github.com/argoproj/argo-cd/util/db"
@@ -189,7 +188,7 @@ func NewGenDexConfigCommand() *cobra.Command {
errors.CheckError(err)
maskedDexCfgBytes, err := yaml.Marshal(dexCfg)
errors.CheckError(err)
fmt.Print(string(maskedDexCfgBytes))
fmt.Printf(string(maskedDexCfgBytes))
} else {
err = ioutil.WriteFile(out, dexCfgBytes, 0644)
errors.CheckError(err)

View File

@@ -43,27 +43,13 @@ import (
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/hook"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/templates"
)
var (
appExample = templates.Examples(`
# List all the applications.
argocd app list
# Get the details of a application
argocd app get my-app
# Set an override parameter
argocd app set my-app -p image.tag=v1.0.1`)
)
// NewApplicationCommand returns a new instance of an `argocd app` command
func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "app",
Short: "Manage applications",
Example: appExample,
Use: "app",
Short: "Manage applications",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
@@ -85,7 +71,6 @@ func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
command.AddCommand(NewApplicationEditCommand(clientOpts))
command.AddCommand(NewApplicationPatchCommand(clientOpts))
command.AddCommand(NewApplicationPatchResourceCommand(clientOpts))
command.AddCommand(NewApplicationResourceActionsCommand(clientOpts))
return command
}
@@ -536,6 +521,9 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
}
}
}
if len(app.Spec.Source.Ksonnet.Parameters) == 0 {
app.Spec.Source.ComponentParameterOverrides = nil
}
}
if app.Spec.Source.Helm != nil {
for _, paramStr := range parameters {
@@ -548,6 +536,9 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
}
}
}
if len(app.Spec.Source.Helm.Parameters) == 0 {
app.Spec.Source.ComponentParameterOverrides = nil
}
specValueFiles := app.Spec.Source.Helm.ValueFiles
for _, valuesFile := range valuesFiles {
for i, vf := range specValueFiles {
@@ -706,11 +697,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
errors.CheckError(err)
key = kube.GetResourceKey(target)
}
if key.Kind == kube.SecretKind && key.Group == "" {
// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
delete(localObjs, key)
continue
}
if local, ok := localObjs[key]; ok || live != nil {
if local != nil && !kube.IsCRD(local) {
err = kube.SetAppInstanceLabel(local, argoSettings.AppLabelKey, appName)
@@ -808,7 +795,7 @@ func printDiff(name string, live *unstructured.Unstructured, target *unstructure
tempDir, err := ioutil.TempDir("", "argocd-diff")
errors.CheckError(err)
targetFile := path.Join(tempDir, name)
targetFile := path.Join(tempDir, fmt.Sprintf("%s", name))
targetData := []byte("")
if target != nil {
targetData, err = yaml.Marshal(target)
@@ -954,47 +941,6 @@ func formatConditionsSummary(app argoappv1.Application) string {
return summary
}
const (
resourceFieldDelimiter = ":"
resourceFieldCount = 3
labelFieldDelimiter = "="
)
func parseSelectedResources(resources []string) []argoappv1.SyncOperationResource {
var selectedResources []argoappv1.SyncOperationResource
if resources != nil {
selectedResources = []argoappv1.SyncOperationResource{}
for _, r := range resources {
fields := strings.Split(r, resourceFieldDelimiter)
if len(fields) != resourceFieldCount {
log.Fatalf("Resource should have GROUP%sKIND%sNAME, but instead got: %s", resourceFieldDelimiter, resourceFieldDelimiter, r)
}
rsrc := argoappv1.SyncOperationResource{
Group: fields[0],
Kind: fields[1],
Name: fields[2],
}
selectedResources = append(selectedResources, rsrc)
}
}
return selectedResources
}
func parseLabels(labels []string) (map[string]string, error) {
var selectedLabels map[string]string
if labels != nil {
selectedLabels = map[string]string{}
for _, r := range labels {
fields := strings.Split(r, labelFieldDelimiter)
if len(fields) != 2 {
return nil, fmt.Errorf("labels should have key%svalue, but instead got: %s", labelFieldDelimiter, r)
}
selectedLabels[fields[0]] = fields[1]
}
}
return selectedLabels, nil
}
// NewApplicationWaitCommand returns a new instance of an `argocd app wait` command
func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
@@ -1003,7 +949,6 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
watchSuspended bool
watchOperations bool
timeout uint
resources []string
)
var command = &cobra.Command{
Use: "wait APPNAME",
@@ -1013,23 +958,26 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
c.HelpFunc()(c, args)
os.Exit(1)
}
if watchSuspended && watchHealth {
log.Fatal("Wait command can not have both the --health and --suspended flags set")
}
if !watchSync && !watchHealth && !watchOperations && !watchSuspended {
watchSync = true
watchHealth = true
watchOperations = true
watchSuspended = false
}
selectedResources := parseSelectedResources(resources)
appName := args[0]
acdClient := argocdclient.NewClientOrDie(clientOpts)
_, err := waitOnApplicationStatus(acdClient, appName, timeout, watchSync, watchHealth, watchOperations, watchSuspended, selectedResources)
_, err := waitOnApplicationStatus(acdClient, appName, timeout, watchSync, watchHealth, watchOperations, watchSuspended, nil)
errors.CheckError(err)
},
}
command.Flags().BoolVar(&watchSync, "sync", false, "Wait for sync")
command.Flags().BoolVar(&watchHealth, "health", false, "Wait for health")
command.Flags().BoolVar(&watchSuspended, "suspended", false, "Wait for suspended")
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().BoolVar(&watchOperations, "operation", false, "Wait for pending operations")
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
return command
@@ -1062,15 +1010,11 @@ func printAppResources(w io.Writer, app *argoappv1.Application, showOperation bo
fmt.Fprintf(w, "GROUP\tKIND\tNAMESPACE\tNAME\tSTATUS\tHEALTH\n")
}
for _, res := range app.Status.Resources {
healthStatus := ""
if res.Health != nil {
healthStatus = res.Health.Status
}
if showOperation {
message := messages[fmt.Sprintf("%s/%s/%s/%s", res.Group, res.Kind, res.Namespace, res.Name)]
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", res.Group, res.Kind, res.Namespace, res.Name, res.Status, healthStatus, "", message)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", res.Group, res.Kind, res.Namespace, res.Name, res.Status, res.Health.Status, "", message)
} else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s", res.Group, res.Kind, res.Namespace, res.Name, res.Status, healthStatus)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s", res.Group, res.Kind, res.Namespace, res.Name, res.Status, res.Health.Status)
}
fmt.Fprint(w, "\n")
}
@@ -1088,14 +1032,17 @@ func printAppResources(w io.Writer, app *argoappv1.Application, showOperation bo
func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
revision string
resources []string
labels []string
resources *[]string
prune bool
dryRun bool
timeout uint
strategy string
force bool
)
const (
resourceFieldDelimiter = ":"
resourceFieldCount = 3
)
var command = &cobra.Command{
Use: "sync APPNAME",
Short: "Sync an application to its target state",
@@ -1107,57 +1054,28 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
acdClient := argocdclient.NewClientOrDie(clientOpts)
conn, appIf := acdClient.NewApplicationClientOrDie()
defer util.Close(conn)
appName := args[0]
selectedLabels, parseErr := parseLabels(labels)
if parseErr != nil {
log.Fatal(parseErr)
}
if len(selectedLabels) > 0 {
ctx := context.Background()
if revision == "" {
revision = "HEAD"
}
q := application.ApplicationManifestQuery{
Name: &appName,
Revision: revision,
}
res, err := appIf.GetManifests(ctx, &q)
if err != nil {
log.Fatal(err)
}
for _, mfst := range res.Manifests {
obj, err := argoappv1.UnmarshalToUnstructured(mfst)
errors.CheckError(err)
for key, selectedValue := range selectedLabels {
if objectValue, ok := obj.GetLabels()[key]; ok && selectedValue == objectValue {
gvk := obj.GroupVersionKind()
resources = append(resources, fmt.Sprintf("%s:%s:%s", gvk.Group, gvk.Kind, obj.GetName()))
}
var syncResources []argoappv1.SyncOperationResource
if resources != nil {
syncResources = []argoappv1.SyncOperationResource{}
for _, r := range *resources {
fields := strings.Split(r, resourceFieldDelimiter)
if len(fields) != resourceFieldCount {
log.Fatalf("Resource should have GROUP%sKIND%sNAME, but instead got: %s", resourceFieldDelimiter, resourceFieldDelimiter, r)
}
}
// If labels are provided and none are found return error only if specific resources were also not
// specified.
if len(resources) == 0 {
log.Fatalf("No matching resources found for labels: %v", labels)
return
rsrc := argoappv1.SyncOperationResource{
Group: fields[0],
Kind: fields[1],
Name: fields[2],
}
syncResources = append(syncResources, rsrc)
}
}
selectedResources := parseSelectedResources(resources)
syncReq := application.ApplicationSyncRequest{
Name: &appName,
DryRun: dryRun,
Revision: revision,
Resources: selectedResources,
Resources: syncResources,
Prune: prune,
}
switch strategy {
@@ -1174,32 +1092,28 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
_, err := appIf.Sync(ctx, &syncReq)
errors.CheckError(err)
app, err := waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, selectedResources)
app, err := waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, syncResources)
errors.CheckError(err)
// Only get resources to be pruned if sync was application-wide
if len(selectedResources) == 0 {
pruningRequired := 0
for _, resDetails := range app.Status.OperationState.SyncResult.Resources {
if resDetails.Status == argoappv1.ResultCodePruneSkipped {
pruningRequired++
}
}
if pruningRequired > 0 {
log.Fatalf("%d resources require pruning", pruningRequired)
pruningRequired := 0
for _, resDetails := range app.Status.OperationState.SyncResult.Resources {
if resDetails.Status == argoappv1.ResultCodePruneSkipped {
pruningRequired++
}
}
if pruningRequired > 0 {
log.Fatalf("%d resources require pruning", pruningRequired)
}
if !app.Status.OperationState.Phase.Successful() && !dryRun {
os.Exit(1)
}
if !app.Status.OperationState.Phase.Successful() && !dryRun {
os.Exit(1)
}
},
}
command.Flags().BoolVar(&dryRun, "dry-run", false, "Preview apply without affecting cluster")
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
command.Flags().StringVar(&revision, "revision", "", "Sync to a specific revision. Preserves parameter overrides")
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().StringArrayVar(&labels, "label", []string{}, fmt.Sprintf("Sync only specific resources with a label. This option may be specified repeatedly."))
resources = command.Flags().StringArray("resource", nil, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
command.Flags().StringVar(&strategy, "strategy", "", "Sync strategy (one of: apply|hook)")
command.Flags().BoolVar(&force, "force", false, "Use a force apply")
@@ -1219,17 +1133,13 @@ type resourceState struct {
}
func newResourceStateFromStatus(res *argoappv1.ResourceStatus) *resourceState {
healthStatus := ""
if res.Health != nil {
healthStatus = res.Health.Status
}
return &resourceState{
Group: res.Group,
Kind: res.Kind,
Namespace: res.Namespace,
Name: res.Name,
Status: string(res.Status),
Health: healthStatus,
Health: res.Health.Status,
}
}
@@ -1273,8 +1183,20 @@ func (rs *resourceState) Merge(newState *resourceState) bool {
return updated
}
func calculateResourceStates(app *argoappv1.Application, selectedResources []argoappv1.SyncOperationResource) map[string]*resourceState {
resStates := getResourceStates(app, selectedResources)
func calculateResourceStates(app *argoappv1.Application, syncResources []argoappv1.SyncOperationResource) map[string]*resourceState {
resStates := make(map[string]*resourceState)
for _, res := range app.Status.Resources {
if len(syncResources) > 0 && !argo.ContainsSyncResource(res.Name, res.GroupVersionKind(), syncResources) {
continue
}
newState := newResourceStateFromStatus(&res)
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
var opResult *argoappv1.SyncOperationResult
if app.Status.OperationState != nil {
@@ -1299,42 +1221,9 @@ func calculateResourceStates(app *argoappv1.Application, selectedResources []arg
return resStates
}
func getResourceStates(app *argoappv1.Application, selectedResources []argoappv1.SyncOperationResource) map[string]*resourceState {
resStates := make(map[string]*resourceState)
for _, res := range app.Status.Resources {
if len(selectedResources) > 0 && !argo.ContainsSyncResource(res.Name, res.GroupVersionKind(), selectedResources) {
continue
}
newState := newResourceStateFromStatus(&res)
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
return resStates
}
func checkResourceStatus(watchSync bool, watchHealth bool, watchOperation bool, watchSuspended bool, healthStatus string, syncStatus string, operationStatus *argoappv1.Operation) bool {
healthCheckPassed := true
if watchSuspended && watchHealth {
healthCheckPassed = healthStatus == argoappv1.HealthStatusHealthy ||
healthStatus == argoappv1.HealthStatusSuspended
} else if watchSuspended {
healthCheckPassed = healthStatus == argoappv1.HealthStatusSuspended
} else if watchHealth {
healthCheckPassed = healthStatus == argoappv1.HealthStatusHealthy
}
synced := !watchSync || syncStatus == string(argoappv1.SyncStatusCodeSynced)
operational := !watchOperation || operationStatus == nil
return synced && healthCheckPassed && operational
}
const waitFormatString = "%s\t%5s\t%10s\t%10s\t%20s\t%8s\t%7s\t%10s\t%s\n"
func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout uint, watchSync bool, watchHealth bool, watchOperation bool, watchSuspended bool, selectedResources []argoappv1.SyncOperationResource) (*argoappv1.Application, error) {
func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout uint, watchSync bool, watchHealth bool, watchOperation bool, watchSuspened bool, syncResources []argoappv1.SyncOperationResource) (*argoappv1.Application, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -1389,30 +1278,17 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
if app.Operation != nil {
refresh = true
}
var selectedResourcesAreReady bool
// If selected resources are included, wait only on those resources, otherwise wait on the application as a whole.
if len(selectedResources) > 0 {
selectedResourcesAreReady = true
for _, state := range getResourceStates(app, selectedResources) {
resourceIsReady := checkResourceStatus(watchSync, watchHealth, false, watchSuspended, state.Health, state.Status, nil)
if !resourceIsReady {
selectedResourcesAreReady = false
break
}
}
} else {
// Wait on the application as a whole
selectedResourcesAreReady = checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, app.Status.Health.Status, string(app.Status.Sync.Status), appEvent.Application.Operation)
}
if len(app.Status.GetErrorConditions()) == 0 && selectedResourcesAreReady {
// consider skipped checks successful
synced := !watchSync || app.Status.Sync.Status == argoappv1.SyncStatusCodeSynced
healthy := !watchHealth || app.Status.Health.Status == argoappv1.HealthStatusHealthy
operational := !watchOperation || appEvent.Application.Operation == nil
suspended := !watchSuspened || app.Status.Health.Status == argoappv1.HealthStatusSuspended
if len(app.Status.GetErrorConditions()) == 0 && synced && healthy && operational && suspended {
printFinalStatus(app)
return app, nil
}
newStates := calculateResourceStates(app, selectedResources)
newStates := calculateResourceStates(app, syncResources)
for _, newState := range newStates {
var doPrint bool
stateKey := newState.Key()
@@ -1785,43 +1661,6 @@ func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
return &command
}
func filterResources(command *cobra.Command, resources []*argoappv1.ResourceDiff, group, kind, namespace, resourceName string, all bool) []*unstructured.Unstructured {
liveObjs, err := liveObjects(resources)
errors.CheckError(err)
filteredObjects := make([]*unstructured.Unstructured, 0)
for i := range liveObjs {
obj := liveObjs[i]
gvk := obj.GroupVersionKind()
if command.Flags().Changed("group") && group != gvk.Group {
continue
}
if namespace != "" && namespace != obj.GetNamespace() {
continue
}
if resourceName != "" && resourceName != obj.GetName() {
continue
}
if kind == gvk.Kind {
copy := obj.DeepCopy()
filteredObjects = append(filteredObjects, copy)
}
}
if len(filteredObjects) == 0 {
log.Fatal("No matching resource found")
}
if len(filteredObjects) > 1 && !all {
log.Fatal("Multiple resources match inputs. Use the --all flag to patch multiple resources")
}
firstGroup := filteredObjects[0].GroupVersionKind().Group
for i := range filteredObjects {
obj := filteredObjects[i]
if obj.GroupVersionKind().Group != firstGroup {
log.Fatal("Multiple groups found in objects to patch. Specify which group to patch with --group flag")
}
}
return filteredObjects
}
func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var patch string
var patchType string
@@ -1830,7 +1669,7 @@ func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions)
var kind string
var group string
var all bool
command := &cobra.Command{
command := cobra.Command{
Use: "patch-resource APPNAME",
Short: "Patch resource in an application",
}
@@ -1845,7 +1684,7 @@ func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions)
errors.CheckError(err)
command.Flags().StringVar(&group, "group", "", "Group")
command.Flags().StringVar(&namespace, "namespace", "", "Namespace")
command.Flags().BoolVar(&all, "all", false, "Indicates whether to patch multiple matching of resources")
command.Flags().BoolVar(&all, "all", false, "Indicates where to patch multiple matching of resources")
command.Run = func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
@@ -1858,7 +1697,39 @@ func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions)
ctx := context.Background()
resources, err := appIf.ManagedResources(ctx, &application.ResourcesQuery{ApplicationName: &appName})
errors.CheckError(err)
objectsToPatch := filterResources(command, resources.Items, group, kind, namespace, resourceName, all)
liveObjs, err := liveObjects(resources.Items)
errors.CheckError(err)
objectsToPatch := make([]*unstructured.Unstructured, 0)
for i := range liveObjs {
obj := liveObjs[i]
gvk := obj.GroupVersionKind()
if command.Flags().Changed("group") && group != gvk.Group {
continue
}
if namespace != "" && namespace != obj.GetNamespace() {
continue
}
if resourceName != "" && resourceName != obj.GetName() {
continue
}
if kind == gvk.Kind {
copy := obj.DeepCopy()
objectsToPatch = append(objectsToPatch, copy)
}
}
if len(objectsToPatch) == 0 {
log.Fatal("No matching resource found to patch")
}
if len(objectsToPatch) > 1 && !all {
log.Fatal("Multiple resources match inputs. Use the --all flag to patch multiple resources")
}
group := objectsToPatch[0].GroupVersionKind().Group
for i := range objectsToPatch {
obj := objectsToPatch[i]
if obj.GroupVersionKind().Group != group {
log.Fatal("Multiple groups found in objects to patch. Specify which group to patch with --group flag")
}
}
for i := range objectsToPatch {
obj := objectsToPatch[i]
gvk := obj.GroupVersionKind()
@@ -1877,5 +1748,5 @@ func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions)
}
}
return command
return &command
}

View File

@@ -1,150 +0,0 @@
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"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/server/application"
"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, &application.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, &application.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, &application.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(), &application.ResourceActionRunRequest{
Name: &appName,
Namespace: obj.GetNamespace(),
ResourceName: objResourceName,
Group: gvk.Group,
Kind: gvk.Kind,
Action: actionName,
})
errors.CheckError(err)
}
}
return command
}

View File

@@ -1,25 +0,0 @@
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)
}

View File

@@ -154,8 +154,20 @@ 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)

View File

@@ -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.Fprint(w, successPage)
fmt.Fprintf(w, successPage)
completionChan <- ""
}
srv := &http.Server{Addr: "localhost:" + strconv.Itoa(port)}

View File

@@ -68,7 +68,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
// See issue #315
err := git.TestRepo(repo.Repo, "", "", repo.SSHPrivateKey, repo.InsecureIgnoreHostKey)
if err != nil {
if yes, _ := git.IsSSHURL(repo.Repo); yes {
if git.IsSSHURL(repo.Repo) {
// If we failed using git SSH credentials, then the repo is automatically bad
log.Fatal(err)
}

View File

@@ -109,8 +109,8 @@ 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 = "1.0.0"
MinClientVersion = "0.12.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 = "1.0.0"
CacheVersion = "0.12.0"
)

View File

@@ -138,8 +138,9 @@ func CreateClusterRoleBinding(
// InstallClusterManagerRBAC installs RBAC resources for a cluster manager to operate a cluster. Returns a token
func InstallClusterManagerRBAC(clientset kubernetes.Interface) (string, error) {
const ns = "kube-system"
var err error
err := CreateServiceAccount(clientset, ArgoCDManagerServiceAccount, ns)
err = CreateServiceAccount(clientset, ArgoCDManagerServiceAccount, ns)
if err != nil {
return "", err
}

View File

@@ -10,9 +10,9 @@ import (
"sync"
"time"
"github.com/argoproj/argo-cd/pkg/apis/application"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
v1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -112,62 +112,54 @@ func NewApplicationController(
}
appInformer, appLister := ctrl.newApplicationInformerAndLister()
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, cache.Indexers{})
metricsAddr := fmt.Sprintf("0.0.0.0:%d", common.PortArgoCDMetrics)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister)
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settings, kubectlCmd, ctrl.metricsServer, ctrl.handleAppUpdated)
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd, ctrl.settings, stateCache, projInformer, ctrl.metricsServer)
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settings, kubectlCmd, func(appName string, fullRefresh bool) {
ctrl.requestAppRefresh(appName, fullRefresh)
ctrl.appRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, appName))
})
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd, ctrl.settings, stateCache, projInformer)
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 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) getApp(name string) (*appv1.Application, error) {
obj, exists, err := ctrl.appInformer.GetStore().GetByKey(fmt.Sprintf("%s/%s", ctrl.namespace, name))
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) handleAppUpdated(appName string, fullRefresh 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 {
ctrl.requestAppRefresh(appName, fullRefresh)
}
ctrl.appRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, appName))
}
func (ctrl *ApplicationController) setAppManagedResources(a *appv1.Application, comparisonResult *comparisonResult) (*appv1.ApplicationTree, error) {
func (ctrl *ApplicationController) setAppManagedResources(a *appv1.Application, comparisonResult *comparisonResult) error {
managedResources, err := ctrl.managedResources(a, comparisonResult)
if err != nil {
return nil, err
return err
}
tree, err := ctrl.getResourceTree(a, managedResources)
tree, err := ctrl.resourceTree(a, managedResources)
if err != nil {
return nil, err
return err
}
err = ctrl.cache.SetAppResourcesTree(a.Name, tree)
if err != nil {
return nil, err
return err
}
return tree, ctrl.cache.SetAppManagedResources(a.Name, managedResources)
return ctrl.cache.SetAppManagedResources(a.Name, managedResources)
}
func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managedResources []*appv1.ResourceDiff) (*appv1.ApplicationTree, error) {
nodes := make([]appv1.ResourceNode, 0)
func (ctrl *ApplicationController) resourceTree(a *appv1.Application, managedResources []*appv1.ResourceDiff) ([]*appv1.ResourceNode, error) {
items := make([]*appv1.ResourceNode, 0)
for i := range managedResources {
managedResource := managedResources[i]
var live = &unstructured.Unstructured{}
@@ -180,28 +172,34 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed
if err != nil {
return nil, err
}
version := ""
resourceVersion := ""
if live != nil {
resourceVersion = live.GetResourceVersion()
version = live.GroupVersionKind().Version
} else if target != nil {
version = target.GroupVersionKind().Version
}
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, kube.GetResourceKey(live), func(child appv1.ResourceNode) {
nodes = append(nodes, child)
})
node := appv1.ResourceNode{
Version: version,
ResourceVersion: resourceVersion,
Name: managedResource.Name,
Kind: managedResource.Kind,
Group: managedResource.Group,
Namespace: managedResource.Namespace,
}
if live != nil {
children, err := ctrl.stateCache.GetChildren(a.Spec.Destination.Server, live)
if err != nil {
return nil, err
}
node.Children = children
}
items = append(items, &node)
}
return &appv1.ApplicationTree{Nodes: nodes}, nil
return items, nil
}
func (ctrl *ApplicationController) managedResources(a *appv1.Application, comparisonResult *comparisonResult) ([]*appv1.ResourceDiff, error) {
@@ -351,10 +349,6 @@ 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")
@@ -373,20 +367,13 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
}
objs := make([]*unstructured.Unstructured, 0)
for k := range objsMap {
if objsMap[k].GetDeletionTimestamp() == nil && shouldBeDeleted(app, objsMap[k]) {
if objsMap[k].GetDeletionTimestamp() == nil && !kube.IsCRD(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.kubectl.DeleteResource(config, obj.GroupVersionKind(), obj.GetName(), obj.GetNamespace(), false)
return ctrl.stateCache.Delete(app.Spec.Destination.Server, obj)
})
if err != nil {
return err
@@ -396,11 +383,6 @@ 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
@@ -614,7 +596,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
startTime := time.Now()
defer func() {
reconcileDuration := time.Since(startTime)
reconcileDuration := time.Now().Sub(startTime)
ctrl.metricsServer.IncReconcile(origApp, reconcileDuration)
logCtx := log.WithFields(log.Fields{"application": origApp.Name, "time_ms": reconcileDuration.Seconds() * 1e3, "full": fullRefresh})
logCtx.Info("Reconciliation completed")
@@ -626,10 +608,9 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
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 {
if tree, err := ctrl.resourceTree(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
@@ -657,11 +638,9 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
ctrl.normalizeApplication(origApp, app, compareResult.appSourceType)
conditions = append(conditions, compareResult.conditions...)
}
tree, err := ctrl.setAppManagedResources(app, compareResult)
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)
@@ -894,7 +873,10 @@ func alreadyAttemptedSync(app *appv1.Application, commitSHA string) bool {
specSource.TargetRevision = ""
syncResSource := app.Status.OperationState.SyncResult.Source.DeepCopy()
syncResSource.TargetRevision = ""
return reflect.DeepEqual(app.Spec.Source, app.Status.OperationState.SyncResult.Source)
if !reflect.DeepEqual(app.Spec.Source, app.Status.OperationState.SyncResult.Source) {
return false
}
return true
}
func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister) {
@@ -967,7 +949,6 @@ func (ctrl *ApplicationController) watchSettings(ctx context.Context) {
ctrl.settingsMgr.Subscribe(updateCh)
prevAppLabelKey := ctrl.settings.GetAppInstanceLabelKey()
prevResourceExclusions := ctrl.settings.ResourceExclusions
prevResourceInclusions := ctrl.settings.ResourceInclusions
done := false
for !done {
select {
@@ -980,15 +961,10 @@ func (ctrl *ApplicationController) watchSettings(ctx context.Context) {
prevAppLabelKey = newAppLabelKey
}
if !reflect.DeepEqual(prevResourceExclusions, newSettings.ResourceExclusions) {
log.WithFields(log.Fields{"prevResourceExclusions": prevResourceExclusions, "newResourceExclusions": newSettings.ResourceExclusions}).Info("resource exclusions modified")
log.Infof("resource exclusions modified")
ctrl.stateCache.Invalidate()
prevResourceExclusions = newSettings.ResourceExclusions
}
if !reflect.DeepEqual(prevResourceInclusions, newSettings.ResourceInclusions) {
log.WithFields(log.Fields{"prevResourceInclusions": prevResourceInclusions, "newResourceInclusions": newSettings.ResourceInclusions}).Info("resource inclusions modified")
ctrl.stateCache.Invalidate()
prevResourceInclusions = newSettings.ResourceInclusions
}
case <-ctx.Done():
done = true
}

View File

@@ -5,8 +5,6 @@ import (
"testing"
"time"
"github.com/argoproj/argo-cd/common"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -121,7 +119,6 @@ var fakeApp = `
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
uid: "123"
name: my-app
namespace: ` + test.FakeArgoCDNamespace + `
spec:
@@ -356,26 +353,20 @@ func TestAutoSyncParameterOverrides(t *testing.T) {
// TestFinalizeAppDeletion verifies application deletion
func TestFinalizeAppDeletion(t *testing.T) {
app := newFakeApp()
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,
}})
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
patched := false
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
defaultReactor := fakeAppCs.ReactionChain[0]
patched := false
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)
assert.NoError(t, err)
assert.True(t, patched)
// 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
}
// TestNormalizeApplication verifies we normalize an application during reconciliation
@@ -451,18 +442,3 @@ 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, _ := ctrl.isRefreshRequested(app.Name)
assert.False(t, isRequested)
ctrl.handleAppUpdated(app.Name, true, corev1.ObjectReference{UID: "test", Kind: kube.DeploymentKind, Name: "test", Namespace: "default"})
isRequested, _ = ctrl.isRefreshRequested(app.Name)
assert.True(t, isRequested)
}

View File

@@ -5,13 +5,11 @@ import (
"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"
@@ -21,18 +19,18 @@ import (
type LiveStateCache interface {
IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error)
// Executes give callback against resource specified by the key and all its children
IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode)) error
// Returns child nodes for a given k8s resource
GetChildren(server string, obj *unstructured.Unstructured) ([]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
// Invalidate invalidates the entire cluster state cache
Invalidate()
}
type AppUpdatedHandler = func(appName string, fullRefresh bool, ref v1.ObjectReference)
func GetTargetObjKey(a *appv1.Application, un *unstructured.Unstructured, isNamespaced bool) kube.ResourceKey {
key := kube.GetResourceKey(un)
if !isNamespaced {
@@ -44,35 +42,34 @@ 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,
metricsServer *metrics.MetricsServer,
onAppUpdated AppUpdatedHandler) LiveStateCache {
func NewLiveStateCache(db db.ArgoDB, appInformer cache.SharedIndexInformer, settings *settings.ArgoCDSettings, kubectl kube.Kubectl, onAppUpdated func(appName string, fullRefresh bool)) LiveStateCache {
return &liveStateCache{
appInformer: appInformer,
db: db,
clusters: make(map[string]*clusterInfo),
lock: &sync.Mutex{},
onAppUpdated: onAppUpdated,
kubectl: kubectl,
settings: settings,
metricsServer: metricsServer,
appInformer: appInformer,
db: db,
clusters: make(map[string]*clusterInfo),
lock: &sync.Mutex{},
onAppUpdated: onAppUpdated,
kubectl: kubectl,
settings: settings,
}
}
type liveStateCache struct {
db db.ArgoDB
clusters map[string]*clusterInfo
lock *sync.Mutex
appInformer cache.SharedIndexInformer
onAppUpdated AppUpdatedHandler
kubectl kube.Kubectl
settings *settings.ArgoCDSettings
metricsServer *metrics.MetricsServer
db db.ArgoDB
clusters map[string]*clusterInfo
lock *sync.Mutex
appInformer cache.SharedIndexInformer
onAppUpdated func(appName string, fullRefresh bool)
kubectl kube.Kubectl
settings *settings.ArgoCDSettings
}
func (c *liveStateCache) processEvent(event watch.EventType, obj *unstructured.Unstructured, url string) error {
info, err := c.getSyncedCluster(url)
if err != nil {
return err
}
return info.processEvent(event, obj)
}
func (c *liveStateCache) getCluster(server string) (*clusterInfo, error) {
@@ -127,6 +124,14 @@ 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, obj *unstructured.Unstructured) (bool, error) {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
@@ -135,13 +140,12 @@ func (c *liveStateCache) IsNamespaced(server string, obj *unstructured.Unstructu
return clusterInfo.isNamespaced(obj), nil
}
func (c *liveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode)) error {
func (c *liveStateCache) GetChildren(server string, obj *unstructured.Unstructured) ([]appv1.ResourceNode, error) {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
return err
return nil, err
}
clusterInfo.iterateHierarchy(key, action)
return nil
return clusterInfo.getChildren(obj), nil
}
func (c *liveStateCache) GetManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
@@ -149,7 +153,7 @@ func (c *liveStateCache) GetManagedLiveObjs(a *appv1.Application, targetObjs []*
if err != nil {
return nil, err
}
return clusterInfo.getManagedLiveObjs(a, targetObjs, c.metricsServer)
return clusterInfo.getManagedLiveObjs(a, targetObjs)
}
func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {

View File

@@ -7,9 +7,8 @@ import (
"sync"
"time"
"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"
@@ -18,7 +17,6 @@ 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"
)
@@ -45,7 +43,7 @@ type clusterInfo struct {
nodes map[kube.ResourceKey]*node
nsIndex map[string]map[kube.ResourceKey]*node
onAppUpdated AppUpdatedHandler
onAppUpdated func(appName string, fullRefresh bool)
kubectl kube.Kubectl
cluster *appv1.Cluster
log *log.Entry
@@ -82,7 +80,7 @@ func (c *clusterInfo) replaceResourceCache(gk schema.GroupKind, resourceVersion
}
}
func (c *clusterInfo) createObjInfo(un *unstructured.Unstructured, appInstanceLabel string) *node {
func 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 {
@@ -92,19 +90,23 @@ func (c *clusterInfo) createObjInfo(un *unstructured.Unstructured, appInstanceLa
APIVersion: "",
})
}
nodeInfo := &node{
info := &node{
resourceVersion: un.GetResourceVersion(),
ref: kube.GetObjectRef(un),
ownerRefs: ownerRefs,
ref: v1.ObjectReference{
APIVersion: un.GetAPIVersion(),
Kind: un.GetKind(),
Name: un.GetName(),
Namespace: un.GetNamespace(),
},
ownerRefs: ownerRefs,
info: getNodeInfo(un),
}
populateNodeInfo(un, nodeInfo)
appName := kube.GetAppInstanceLabel(un, appInstanceLabel)
if len(ownerRefs) == 0 && appName != "" {
nodeInfo.appName = appName
nodeInfo.resource = un
info.appName = appName
info.resource = un
}
nodeInfo.health, _ = health.GetResourceHealth(un, c.settings.ResourceOverrides)
return nodeInfo
return info
}
func (c *clusterInfo) setNode(n *node) {
@@ -292,7 +294,7 @@ func (c *clusterInfo) sync() (err error) {
lock.Lock()
for i := range list.Items {
c.setNode(c.createObjInfo(&list.Items[i], c.settings.GetAppInstanceLabelKey()))
c.setNode(createObjInfo(&list.Items[i], c.settings.GetAppInstanceLabelKey()))
}
lock.Unlock()
return nil
@@ -325,19 +327,19 @@ func (c *clusterInfo) ensureSynced() error {
return c.syncError
}
func (c *clusterInfo) iterateHierarchy(key kube.ResourceKey, action func(child appv1.ResourceNode)) {
func (c *clusterInfo) getChildren(obj *unstructured.Unstructured) []appv1.ResourceNode {
c.lock.Lock()
defer c.lock.Unlock()
if objInfo, ok := c.nodes[key]; ok {
action(objInfo.asResourceNode())
nsNodes := c.nsIndex[key.Namespace]
children := make([]appv1.ResourceNode, 0)
if objInfo, ok := c.nodes[kube.GetResourceKey(obj)]; ok {
nsNodes := c.nsIndex[obj.GetNamespace()]
for _, child := range nsNodes {
if objInfo.isParentOf(child) {
action(child.asResourceNode())
child.iterateChildren(nsNodes, map[kube.ResourceKey]bool{objInfo.resourceKey(): true}, action)
children = append(children, child.childResourceNodes(nsNodes, map[kube.ResourceKey]bool{objInfo.resourceKey(): true}))
}
}
}
return children
}
func (c *clusterInfo) isNamespaced(obj *unstructured.Unstructured) bool {
@@ -347,7 +349,7 @@ func (c *clusterInfo) isNamespaced(obj *unstructured.Unstructured) bool {
return true
}
func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured, metricsServer *metrics.MetricsServer) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
c.lock.Lock()
defer c.lock.Unlock()
@@ -358,7 +360,6 @@ 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{}
@@ -375,7 +376,7 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
managedObj = existingObj.resource
} else {
var err error
managedObj, err = c.kubectl.GetResource(config, targetObj.GroupVersionKind(), existingObj.ref.Name, existingObj.ref.Namespace)
managedObj, err = c.kubectl.GetResource(c.cluster.RESTConfig(), targetObj.GroupVersionKind(), existingObj.ref.Name, existingObj.ref.Namespace)
if err != nil {
if errors.IsNotFound(err) {
return nil
@@ -387,19 +388,9 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
}
if managedObj != nil {
converted, err := c.kubectl.ConvertToVersion(managedObj, targetObj.GroupVersionKind().Group, targetObj.GroupVersionKind().Version)
managedObj, err := c.kubectl.ConvertToVersion(managedObj, targetObj.GroupVersionKind().Group, targetObj.GroupVersionKind().Version)
if err != nil {
// 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
return err
}
lock.Lock()
managedObjs[key] = managedObj
@@ -414,6 +405,10 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
return managedObjs, nil
}
func (c *clusterInfo) delete(obj *unstructured.Unstructured) error {
return c.kubectl.DeleteResource(c.cluster.RESTConfig(), obj.GroupVersionKind(), obj.GetName(), obj.GetNamespace(), false)
}
func (c *clusterInfo) processEvent(event watch.EventType, un *unstructured.Unstructured) error {
c.lock.Lock()
defer c.lock.Unlock()
@@ -435,7 +430,7 @@ func (c *clusterInfo) onNodeUpdated(exists bool, existingNode *node, un *unstruc
if exists {
nodes = append(nodes, existingNode)
}
newObj := c.createObjInfo(un, c.settings.GetAppInstanceLabelKey())
newObj := createObjInfo(un, c.settings.GetAppInstanceLabelKey())
c.setNode(newObj)
nodes = append(nodes, newObj)
toNotify := make(map[string]bool)
@@ -450,7 +445,7 @@ func (c *clusterInfo) onNodeUpdated(exists bool, existingNode *node, un *unstruc
}
}
for name, full := range toNotify {
c.onAppUpdated(name, full, newObj.ref)
c.onAppUpdated(name, full)
}
}
@@ -462,7 +457,7 @@ func (c *clusterInfo) onNodeRemoved(key kube.ResourceKey, n *node) {
c.removeNode(key)
if appName != "" {
c.onAppUpdated(appName, n.isRootAppNode(), n.ref)
c.onAppUpdated(appName, n.isRootAppNode())
}
}

View File

@@ -7,16 +7,17 @@ import (
"sync"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic/fake"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
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/errors"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
@@ -72,49 +73,6 @@ var (
name: helm-guestbook
namespace: default
resourceVersion: "123"`)
testService = strToUnstructured(`
apiVersion: v1
kind: Service
metadata:
name: helm-guestbook
namespace: default
resourceVersion: "123"
spec:
selector:
app: guestbook
type: LoadBalancer
status:
loadBalancer:
ingress:
- hostname: localhost`)
testIngress = strToUnstructured(`
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: helm-guestbook
namespace: default
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 {
@@ -146,7 +104,7 @@ func newClusterExt(kubectl kube.Kubectl) *clusterInfo {
return &clusterInfo{
lock: &sync.Mutex{},
nodes: make(map[kube.ResourceKey]*node),
onAppUpdated: func(appName string, fullRefresh bool, reference corev1.ObjectReference) {},
onAppUpdated: func(appName string, fullRefresh bool) {},
kubectl: kubectl,
nsIndex: make(map[string]map[kube.ResourceKey]*node),
cluster: &appv1.Cluster{},
@@ -158,55 +116,34 @@ func newClusterExt(kubectl kube.Kubectl) *clusterInfo {
}
}
func getChildren(cluster *clusterInfo, un *unstructured.Unstructured) []appv1.ResourceNode {
hierarchy := make([]appv1.ResourceNode, 0)
cluster.iterateHierarchy(kube.GetResourceKey(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 := getChildren(cluster, testRS)
rsChildren := cluster.getChildren(testRS)
assert.Equal(t, []appv1.ResourceNode{{
ResourceRef: appv1.ResourceRef{
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod",
Group: "",
Version: "v1",
},
ParentRefs: []appv1.ResourceRef{{
Group: "apps",
Version: "",
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
}},
Health: &appv1.HealthStatus{Status: appv1.HealthStatusUnknown},
NetworkingInfo: &appv1.ResourceNetworkingInfo{Labels: testPod.GetLabels()},
ResourceVersion: "123",
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod",
Group: "",
Version: "v1",
Info: []appv1.InfoItem{{Name: "Containers", Value: "0/0"}},
}}, rsChildren)
deployChildren := getChildren(cluster, testDeploy)
assert.Equal(t, append([]appv1.ResourceNode{{
ResourceRef: appv1.ResourceRef{
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
Group: "apps",
Version: "v1",
},
Children: make([]appv1.ResourceNode, 0),
ResourceVersion: "123",
Health: &appv1.HealthStatus{Status: appv1.HealthStatusHealthy},
}}, rsChildren)
deployChildren := cluster.getChildren(testDeploy)
assert.Equal(t, []appv1.ResourceNode{{
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
Group: "apps",
Version: "v1",
ResourceVersion: "123",
Children: rsChildren,
Info: []appv1.InfoItem{},
ParentRefs: []appv1.ResourceRef{{Group: "apps", Version: "", Kind: "Deployment", Namespace: "default", Name: "helm-guestbook"}},
}}, rsChildren...), deployChildren)
}}, deployChildren)
}
func TestGetManagedLiveObjs(t *testing.T) {
@@ -229,7 +166,7 @@ metadata:
Namespace: "default",
},
},
}, []*unstructured.Unstructured{targetDeploy}, nil)
}, []*unstructured.Unstructured{targetDeploy})
assert.Nil(t, err)
assert.Equal(t, managedObjs, map[kube.ResourceKey]*unstructured.Unstructured{
kube.NewResourceKey("apps", "Deployment", "default", "helm-guestbook"): testDeploy,
@@ -244,7 +181,7 @@ func TestChildDeletedEvent(t *testing.T) {
err = cluster.processEvent(watch.Deleted, testPod)
assert.Nil(t, err)
rsChildren := getChildren(cluster, testRS)
rsChildren := cluster.getChildren(testRS)
assert.Equal(t, []appv1.ResourceNode{}, rsChildren)
}
@@ -268,47 +205,27 @@ func TestProcessNewChildEvent(t *testing.T) {
err = cluster.processEvent(watch.Added, newPod)
assert.Nil(t, err)
rsChildren := getChildren(cluster, testRS)
rsChildren := cluster.getChildren(testRS)
sort.Slice(rsChildren, func(i, j int) bool {
return strings.Compare(rsChildren[i].Name, rsChildren[j].Name) < 0
})
assert.Equal(t, []appv1.ResourceNode{{
ResourceRef: appv1.ResourceRef{
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod",
Group: "",
Version: "v1",
},
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",
}},
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod",
Group: "",
Version: "v1",
Info: []appv1.InfoItem{{Name: "Containers", Value: "0/0"}},
Children: make([]appv1.ResourceNode, 0),
ResourceVersion: "123",
}, {
ResourceRef: appv1.ResourceRef{
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod2",
Group: "",
Version: "v1",
},
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",
}},
Kind: "Pod",
Namespace: "default",
Name: "helm-guestbook-pod2",
Group: "",
Version: "v1",
Info: []appv1.InfoItem{{Name: "Containers", Value: "0/0"}},
Children: make([]appv1.ResourceNode, 0),
ResourceVersion: "123",
}}, rsChildren)
}
@@ -355,7 +272,7 @@ func TestUpdateResourceTags(t *testing.T) {
func TestUpdateAppResource(t *testing.T) {
updatesReceived := make([]string, 0)
cluster := newCluster(testPod, testRS, testDeploy)
cluster.onAppUpdated = func(appName string, fullRefresh bool, _ corev1.ObjectReference) {
cluster.onAppUpdated = func(appName string, fullRefresh bool) {
updatesReceived = append(updatesReceived, fmt.Sprintf("%s: %v", appName, fullRefresh))
}
@@ -380,8 +297,8 @@ func TestCircularReference(t *testing.T) {
assert.Nil(t, err)
children := getChildren(cluster, dep)
assert.Len(t, children, 2)
children := cluster.getChildren(dep)
assert.Len(t, children, 1)
node := cluster.nodes[kube.GetResourceKey(dep)]
assert.NotNil(t, node)

View File

@@ -9,148 +9,23 @@ import (
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"
)
func populateNodeInfo(un *unstructured.Unstructured, node *node) {
func getNodeInfo(un *unstructured.Unstructured) []v1alpha1.InfoItem {
gvk := un.GroupVersionKind()
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
}
if gvk.Kind == kube.PodKind && gvk.Group == "" {
return getPodInfo(un)
}
node.info = []v1alpha1.InfoItem{}
return []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) {
func getPodInfo(un *unstructured.Unstructured) []v1alpha1.InfoItem {
pod := v1.Pod{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &pod)
if err != nil {
node.info = []v1alpha1.InfoItem{}
return
return []v1alpha1.InfoItem{}
}
restarts := 0
totalContainers := len(pod.Spec.Containers)
@@ -161,19 +36,6 @@ func populatePodInfo(un *unstructured.Unstructured, node *node) {
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]
@@ -237,10 +99,9 @@ func populatePodInfo(un *unstructured.Unstructured, node *node) {
reason = "Terminating"
}
node.info = make([]v1alpha1.InfoItem, 0)
info := make([]v1alpha1.InfoItem, 0)
if reason != "" {
node.info = append(node.info, v1alpha1.InfoItem{Name: "Status Reason", Value: reason})
info = append(info, v1alpha1.InfoItem{Name: "Status Reason", Value: reason})
}
node.info = append(node.info, v1alpha1.InfoItem{Name: "Containers", Value: fmt.Sprintf("%d/%d", readyContainers, totalContainers)})
node.networkingInfo = &v1alpha1.ResourceNetworkingInfo{Labels: un.GetLabels()}
return append(info, v1alpha1.InfoItem{Name: "Containers", Value: fmt.Sprintf("%d/%d", readyContainers, totalContainers)})
}

View File

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

@@ -27,6 +27,29 @@ func (_m *LiveStateCache) Delete(server string, obj *unstructured.Unstructured)
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)
@@ -76,20 +99,6 @@ func (_m *LiveStateCache) IsNamespaced(server string, obj *unstructured.Unstruct
return r0, r1
}
// IterateHierarchy provides a mock function with given fields: server, key, action
func (_m *LiveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(v1alpha1.ResourceNode)) error {
ret := _m.Called(server, key, action)
var r0 error
if rf, ok := ret.Get(0).(func(string, kube.ResourceKey, func(v1alpha1.ResourceNode)) error); ok {
r0 = rf(server, key, action)
} else {
r0 = ret.Error(0)
}
return r0
}
// Run provides a mock function with given fields: ctx
func (_m *LiveStateCache) Run(ctx context.Context) {
_m.Called(ctx)

View File

@@ -18,12 +18,7 @@ type node struct {
ownerRefs []metav1.OwnerReference
info []appv1.InfoItem
appName string
// 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
resource *unstructured.Unstructured
}
func (n *node) isRootAppNode() bool {
@@ -91,44 +86,30 @@ func newResourceKeySet(set map[kube.ResourceKey]bool, keys ...kube.ResourceKey)
return newSet
}
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}
}
return appv1.ResourceNode{
ResourceRef: appv1.ResourceRef{
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 {
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 {
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 {
action(child.asResourceNode())
child.iterateChildren(ns, newResourceKeySet(parents, n.resourceKey()), action)
children = append(children, ns[childKey].childResourceNodes(ns, newResourceKeySet(parents, n.resourceKey())))
}
}
}
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,
}
}

View File

@@ -4,16 +4,12 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/argoproj/argo-cd/util/settings"
)
var c = &clusterInfo{settings: &settings.ArgoCDSettings{}}
func TestIsParentOf(t *testing.T) {
child := c.createObjInfo(testPod, "")
parent := c.createObjInfo(testRS, "")
grandParent := c.createObjInfo(testDeploy, "")
child := createObjInfo(testPod, "")
parent := createObjInfo(testRS, "")
grandParent := createObjInfo(testDeploy, "")
assert.True(t, parent.isParentOf(child))
assert.False(t, grandParent.isParentOf(child))
@@ -22,8 +18,8 @@ func TestIsParentOf(t *testing.T) {
func TestIsParentOfSameKindDifferentGroup(t *testing.T) {
rs := testRS.DeepCopy()
rs.SetAPIVersion("somecrd.io/v1")
child := c.createObjInfo(testPod, "")
invalidParent := c.createObjInfo(rs, "")
child := createObjInfo(testPod, "")
invalidParent := createObjInfo(rs, "")
assert.False(t, invalidParent.isParentOf(child))
}

View File

@@ -2,7 +2,6 @@ package metrics
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
@@ -18,7 +17,6 @@ import (
type MetricsServer struct {
*http.Server
syncCounter *prometheus.CounterVec
k8sRequestCounter *prometheus.CounterVec
reconcileHistogram *prometheus.HistogramVec
}
@@ -74,14 +72,6 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister) *Metri
append(descAppDefaultLabels, "phase"),
)
appRegistry.MustRegister(syncCounter)
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{
@@ -101,7 +91,6 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister) *Metri
Handler: mux,
},
syncCounter: syncCounter,
k8sRequestCounter: k8sRequestCounter,
reconcileHistogram: reconcileHistogram,
}
}
@@ -114,11 +103,6 @@ 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())

View File

@@ -1,37 +0,0 @@
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,7 +14,6 @@ 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"
@@ -74,7 +73,6 @@ type comparisonResult struct {
// appStateManager allows to compare applications to git
type appStateManager struct {
metricsServer *metrics.MetricsServer
db db.ArgoDB
settings *settings.ArgoCDSettings
appclientset appclientset.Interface
@@ -90,10 +88,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
if err != nil {
return nil, nil, nil, err
}
repo, err := m.db.GetRepository(context.Background(), source.RepoURL)
if err != nil {
return nil, nil, nil, err
}
repo := m.getRepo(source.RepoURL)
conn, repoClient, err := m.repoClientset.NewRepoServerClient()
if err != nil {
return nil, nil, nil, err
@@ -316,10 +311,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
syncStatus.Revision = manifestInfo.Revision
}
healthStatus, err := health.SetApplicationHealth(resourceSummaries, GetLiveObjs(managedResources), m.settings.ResourceOverrides, func(obj *unstructured.Unstructured) bool {
return !isSelfReferencedApp(app, kubeutil.GetObjectRef(obj))
})
healthStatus, err := health.SetApplicationHealth(resourceSummaries, GetLiveObjs(managedResources), m.settings.ResourceOverrides)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
}
@@ -340,6 +332,15 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
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}
}
return repo
}
func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource) error {
var nextID int64
if len(app.Status.History) > 0 {
@@ -378,7 +379,6 @@ func NewAppStateManager(
settings *settings.ArgoCDSettings,
liveStateCache statecache.LiveStateCache,
projInformer cache.SharedIndexInformer,
metricsServer *metrics.MetricsServer,
) AppStateManager {
return &appStateManager{
liveStateCache: liveStateCache,
@@ -389,6 +389,5 @@ func NewAppStateManager(
namespace: namespace,
settings: settings,
projInformer: projInformer,
metricsServer: metricsServer,
}
}

View File

@@ -4,9 +4,6 @@ import (
"encoding/json"
"testing"
v1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -181,83 +178,3 @@ func TestCompareAppStateDuplicatedNamespacedResources(t *testing.T) {
})
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: &repository.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(deployment): deployment,
},
})
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
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: &repository.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, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
assert.Equal(t, compRes.healthStatus.Status, argoappv1.HealthStatusHealthy)
}

View File

@@ -14,7 +14,6 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/controller/metrics"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/util/argo"
@@ -117,7 +116,7 @@ func (m *appStateManager) SyncAppState(app *appv1.Application, state *appv1.Oper
return
}
restConfig := metrics.AddMetricsTransportWrapper(m.metricsServer, app, clst.RESTConfig())
restConfig := clst.RESTConfig()
dynamicIf, err := dynamic.NewForConfig(restConfig)
if err != nil {
state.Phase = appv1.OperationError
@@ -330,7 +329,10 @@ func (sc *syncContext) generateSyncTasks() ([]syncTask, bool) {
// startedPreSyncPhase detects if we already started the PreSync stage of a sync operation.
// This is equal to if we have anything in our resource or hook list
func (sc *syncContext) startedPreSyncPhase() bool {
return len(sc.syncRes.Resources) > 0
if len(sc.syncRes.Resources) > 0 {
return true
}
return false
}
// startedSyncPhase detects if we have already started the Sync stage of a sync operation.
@@ -462,7 +464,8 @@ func (sc *syncContext) doApplySync(syncTasks []syncTask, dryRun, force, update b
wg.Add(1)
go func(t syncTask) {
defer wg.Done()
resDetails := sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
var resDetails appv1.ResourceResult
resDetails = sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
if !resDetails.Status.Successful() {
syncSuccessful = false
}

38
docs/README.md Normal file
View File

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

View File

@@ -1,6 +0,0 @@
# 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.slack.com/messages/CASHNF6MS).
4. [Read issues, report a bug, or request a feature ⧉](https://github.com/argoproj/argo-cd/issues)

61
docs/application.yaml Normal file
View File

@@ -0,0 +1,61 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guestbook
spec:
# The project the application belongs to.
project: default
# Source of the application manifests
source:
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
path: guestbook
# helm specific config
helm:
valueFiles:
- values-prod.yaml
# kustomize specific config
kustomize:
namePrefix: prod-
# directory
directory:
recurse: true
jsonnet:
# A list of Jsonnet External Variables
extVars:
- name: foo
value: bar
# You can use "code to determine if the value is either string (false, the default) or Jsonnet code (if code is true).
- code: true
name: baz
value: "true"
# A list of Jsonnet Top-level Arguments
tlas:
- code: false
name: foo
value: bar
# plugin specific config
plugin:
- name: mypluginname
# Destination cluster and namespace to deploy the application
destination:
server: https://kubernetes.default.svc
namespace: guestbook
# Sync policy
syncPolicy:
automated:
prune: true
# Ignore differences at the specified json pointers
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas

View File

@@ -2,18 +2,14 @@
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
* [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:
## Kustomize
Ops. We haven't got around to writing this part yet.
## Ksonnet
### Environments
@@ -21,7 +17,7 @@ Ksonnet has a first class concept of an "environment." To create an application
app directory, an environment must be specified. For example, the following command creates the
"guestbook-default" app, which points to the `default` environment:
```bash
```
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
```
@@ -29,7 +25,7 @@ argocd app create guestbook-default --repo https://github.com/argoproj/argocd-ex
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:
```bash
```
$ ks param list
COMPONENT PARAM VALUE
========= ===== =====
@@ -43,8 +39,7 @@ 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:
```bash
```
argocd app set guestbook-default -p guestbook-ui=image=gcr.io/heptio-images/ks-guestbook-demo:0.1
```
@@ -56,7 +51,7 @@ Helm has the ability to use a different, or even multiple "values.yaml" files to
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:
```bash
```
argocd app set helm-guestbook --values values-production.yaml
```
@@ -64,15 +59,12 @@ argocd app set helm-guestbook --values values-production.yaml
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:
```bash
```
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:
```bash
```
argocd app set helm-guestbook -p service.type=LoadBalancer
```
@@ -90,9 +82,9 @@ of Pre/Post/Sync hooks.
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/secret.yaml):
[redis helm chart](https://github.com/helm/charts/blob/master/stable/redis/templates/secrets.yaml):
```yaml
```
data:
{{- if .Values.password }}
redis-password: {{ .Values.password | b64enc | quote }}
@@ -101,13 +93,13 @@ data:
{{- end }}
```
The Argo CD application controller periodically compares Git state against the live state, running
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:
```bash
```
argocd app set redis -p password=abc123
```
@@ -115,7 +107,7 @@ argocd app set redis -p password=abc123
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](../operator-manual/custom_tools.md)).
* 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
@@ -137,7 +129,7 @@ Commands have access to system environment variables and following additional va
* Create an application and specify required config management plugin name.
```bash
```
argocd app create <appName> --config-management-plugin <pluginName>
```

View File

@@ -1,34 +1,32 @@
# Architectural Overview
# Argo CD - Architectural Overview
![Argo CD Architecture](../assets/argocd_architecture.png)
![Argo CD Architecture](argocd_architecture.png)
## Components
### API Server
The API server is a gRPC/REST server which exposes the API consumed by the Web UI, CLI, and CI/CD
systems. It has the following responsibilities:
* application management and status reporting
* invoking of application operations (e.g. sync, rollback, user-defined actions)
* repository and cluster credential management (stored as K8s secrets)
* authentication and auth delegation to external identity providers
* RBAC enforcement
* listener/forwarder for Git webhook events
* listener/forwarder for git webhook events
### Repository Server
The repository server is an internal service which maintains a local cache of the Git repository
The repository server is an internal service which maintains a local cache of the git repository
holding the application manifests. It is responsible for generating and returning the Kubernetes
manifests when provided the following inputs:
* repository URL
* revision (commit, tag, branch)
* git revision (commit, tag, branch)
* application path
* template specific settings: parameters, ksonnet environments, helm values.yaml
### Application Controller
The application controller is a Kubernetes controller which continuously monitors running
applications and compares the current, live state against the desired target state (as specified in
the repo). It detects `OutOfSync` application state and optionally takes corrective action. It
the git repo). It detects `OutOfSync` application state and optionally takes corrective action. It
is responsible for invoking any user-defined hooks for lifcecycle events (PreSync, Sync, PostSync)

View File

@@ -29,8 +29,6 @@ data:
issuer: https://dev-123456.oktapreview.com
clientID: aaaabbbbccccddddeee
clientSecret: $oidc.okta.clientSecret
# Optional set of OIDC scopes to request. If omitted, defaults to: ["openid", "profile", "email", "groups"]
requestedScopes: ["openid", "profile", "email"]
# Git repositories configure Argo CD with (optional).
# This list is updated when configuring/removing repos from the UI/CLI
@@ -63,7 +61,7 @@ data:
resource.customizations: |
admissionregistration.k8s.io/MutatingWebhookConfiguration:
# List of json pointers in the object to ignore differences
ignoreDifferences: |
ignoreDifferences:
jsonPointers:
- webhooks/0/clientConfig/caBundle
certmanager.k8s.io/Certificate:

View File

@@ -19,8 +19,3 @@ data:
# authorizing API requests (optional). If omitted or empty, users may be still be able to login,
# but will see no apps, projects, etc...
policy.default: role:readonly
# scopes controls which OIDC scopes to examine during rbac enforcement (in addition to `sub` scope).
# If omitted, defaults to: `[groups]`. The scope value can be a string, or a list of strings.
scopes: [cognito:groups, email]

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.

Before

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,10 +1,10 @@
# Automated Sync Policy
Argo CD has the ability to automatically sync an application when it detects differences between
the desired manifests in Git, and the live state in the cluster. A benefit of automatic sync is that
the desired manifests in git, and the live state in the cluster. A benefit of automatic sync is that
CI/CD pipelines no longer need direct access to the Argo CD API server to perform the deployment.
Instead, the pipeline makes a commit and push to the Git repository with the changes to the
manifests in the tracking Git repo.
Instead, the pipeline makes a commit and push to the git repository with the changes to the
manifests in the tracking git repo.
To configure automated sync run:
```bash
@@ -22,7 +22,7 @@ spec:
## Automatic Pruning
By default (and as a safety mechanism), automated sync will not delete resources when Argo CD detects
the resource is no longer defined in Git. To prune the resources, a manual sync can always be
the resource is no longer defined in git. To prune the resources, a manual sync can always be
performed (with pruning checked). Pruning can also be enabled to happen automatically as part of the
automated sync by running:

View File

@@ -1,8 +1,8 @@
# Best Practices
## Separating Config Vs. Source Code Repositories
## Separating config vs. source code repositories
Using a separate Git repository to hold your kubernetes manifests, keeping the config separate
Using a separate git repository to hold your kubernetes manifests, keeping the config separate
from your application source code, is highly recommended for the following reasons:
1. It provides a clean separation of application code vs. application config. There will be times
@@ -11,10 +11,10 @@ from your application source code, is highly recommended for the following reaso
a Deployment spec.
2. Cleaner audit log. For auditing purposes, a repo which only holds configuration will have a much
cleaner Git history of what changes were made, without the noise coming from check-ins due to
cleaner git history of what changes were made, without the noise coming from check-ins due to
normal development activity.
3. Your application may be comprised of services built from multiple Git repositories, but is
3. Your application may be comprised of services built from multiple git repositories, but is
deployed as a single unit. Oftentimes, microservices applications are comprised of services
with different versioning schemes, and release cycles (e.g. ELK, Kafka + Zookeeper). It may not
make sense to store the manifests in one of the source code repositories of a single component.
@@ -24,17 +24,17 @@ from your application source code, is highly recommended for the following reaso
unintentionally. By having separate repos, commit access can be given to the source code repo,
and not the application config repo.
5. If you are automating your CI pipeline, pushing manifest changes to the same Cit repository can
trigger an infinite loop of build jobs and Git commit triggers. Having a separate repo to push
5. If you are automating your CI pipeline, pushing manifest changes to the same git repository can
trigger an infinite loop of build jobs and git commit triggers. Having a separate repo to push
config changes to, prevents this from happening.
## Leaving Room For Imperativeness
## Leaving room for imperativeness
It may be desired to leave room for some imperativeness/automation, and not have everything defined
in your Git manifests. For example, if you want the number of your deployment's replicas to be
in your git manifests. For example, if you want the number of your deployment's replicas to be
managed by [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/),
then you would not want to track `replicas` in Git.
then you would not want to track `replicas` in git.
```yaml
apiVersion: apps/v1
@@ -54,14 +54,14 @@ spec:
...
```
## Ensuring Manifests At Git Revisions Are Truly Immutable
## Ensuring manifests at git revisions are truly immutable
When using templating tools like `helm` or `kustomize`, it is possible for manifests to change
their meaning from one day to the next. This is typically caused by changes made to an upstream helm
repository or kustomize base.
For example, consider the following kustomization.yaml
```yaml
bases:
- github.com/argoproj/argo-cd//manifests/cluster-install
@@ -69,10 +69,9 @@ bases:
The above kustomization has a remote base to he HEAD revision of the argo-cd repo. Since this
is not stable target, the manifests for this kustomize application can suddenly change meaning, even without
any changes to your own Git repository.
A better version would be to use a Git tag or commit SHA. For example:
any changes to your own git repository.
A better version would be to use a git tag or commit SHA. For example:
```yaml
bases:
- github.com/argoproj/argo-cd//manifests/cluster-install?ref=v0.11.1

58
docs/ci_automation.md Normal file
View File

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

View File

@@ -1,16 +0,0 @@
# Core Concepts
Let's assume you're familiar with core Git, Docker, Kubernetes, Continuous Delivery, and GitOps concepts.
* **Application** A group of Kubernetes resources as defined by a manifest. This is a Custom Resource Definition (CRD).
* **Application source type** Which **Tool** is used to build the application.
* **Target state** The desired state of an application, as represented by files in a Git repository.
* **Live state** The live state of that application. What pods etc are deployed.
* **Sync status** Whether or not the live state matches the target state. Is the deployed application the same as Git says it should be?
* **Sync** The process of making an application move to its target state. E.g. by applying changes to a Kubernetes cluster.
* **Sync operation status** Whether or not a sync succeeded.
* **Refresh** Compare the latest code in Git with the live state. Figure out what is different.
* **Health** The health the application, is it running correctly? Can it serve requests?
* **Tool** A tool to create manifests from a directory of files. E.g. Kustomize or Ksonnet. See **Application Source Type**.
* **Configuration management tool** See **Tool**.
* **Configuration management plugin** A custom tool.

View File

@@ -7,12 +7,12 @@ other than what Argo CD bundles. Some reasons to do this might be:
* To upgrade/downgrade to a specific version of a tool due to bugs or bug fixes.
* To install additional dependencies which to be used by kustomize's configmap/secret generators
(e.g. curl, vault, gpg, AWS CLI)
* To install a [config management plugin](../user-guide/application_sources.md#config-management-plugins)
* In the future, to install a [custom templating tool](https://github.com/argoproj/argo-cd/issues/701)
As the Argo CD repo-server is the single service responsible for generating Kubernetes manifests, it
can be customized to use alternative toolchain required by your environment.
## Adding Tools Via Volume Mounts
## Adding tools via volume mounts
The first technique is to use an `init` container and a `volumeMount` to copy a different verison of
a tool into the repo-server container. In the following example, an init container is overwriting
@@ -44,7 +44,7 @@ the helm binary with a different version than what is bundled in Argo CD:
subPath: helm
```
## BYOI (Build Your Own Image)
## BYOI (build your own image)
Sometimes replacing a binary isn't sufficient and you need to install other dependencies. The
following example builds an entirely customized repo-server from a Dockerfile, installing extra

View File

@@ -9,14 +9,13 @@ Argo CD applications, projects and settings can be defined declaratively using K
| [`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`](project.yaml) | AppProject | Example project spec |
| [`project.yaml`](argocd-rbac-cm.yaml) | AppProject | Example project spec |
## Applications
The Application CRD is the Kubernetes resource object representing a deployed application instance
in an environment. It is defined by two key pieces of information:
* `source` reference to the desired state in Git (repository, revision, path, environment)
* `source` reference to the desired state in git (repository, revision, path, environment)
* `destination` reference to the target cluster and namespace.
A minimal Application spec is as follows:
@@ -39,24 +38,9 @@ spec:
See [application.yaml](application.yaml) for additional fields
!!! warning
By default, deleting an application will not perform a cascade delete, thereby deleting its resources. You must add the finalizer if you want this behaviour - which you may well not want.
```yaml
metadata:
finalizers:
- resources-finalizer.argocd.argoproj.io
```
### App of Apps of Apps
You can create an application that creates other applications, which in turn can create other applications.
This allows you to declaratively manage a group of applications that can be deployed and configured in concert.
## Projects
The AppProject CRD is the Kubernetes resource object representing a logical grouping of applications.
It is defined by the following key pieces of information:
* `sourceRepos` reference to the repositories that applications within the project can pull manifests from.
* `destinations` reference to clusters and namespaces that applications within the project can deploy into.
* `roles` list of entities with definitions of their access to resources within the project.
@@ -70,7 +54,7 @@ metadata:
name: my-project
spec:
description: Example Project
# Allow manifests to deploy from any Git repos
# Allow manifests to deploy from any git repos
sourceRepos:
- '*'
# Only permit applications to deploy to the guestbook namespace in the same cluster
@@ -82,7 +66,7 @@ spec:
- group: ''
kind: Namespace
# Allow all namespaced-scoped resources to be created, except for ResourceQuota, LimitRange, NetworkPolicy
namespaceResourceBlacklist:
clusterResourceWhitelist:
- group: ''
kind: ResourceQuota
- group: ''
@@ -115,9 +99,10 @@ Repository credentials are stored in secret. Use following steps to configure a
1. Create secret which contains repository credentials. Consider using [bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets) to store encrypted secret
definition as a Kubernetes manifest.
2. Register repository in the `argocd-cm` config map. Each repository must have `url` field and, depending on whether you connect using HTTPS or SSH, `usernameSecret` and `passwordSecret` (for HTTPS) or `sshPrivateKeySecret` (for SSH).
Example for HTTPS:
2. Register repository in `argocd-cm` config map. Each repository must have `url` field and `usernameSecret`, `passwordSecret` or `sshPrivateKeySecret`.
Example:
```yaml
apiVersion: v1
@@ -133,112 +118,17 @@ data:
usernameSecret:
name: my-secret
key: username
```
Example for SSH:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
data:
repositories: |
- url: git@github.com:argoproj/my-private-repository
sshPrivateKeySecret:
name: my-secret
key: sshPrivateKey
```
!!! tip
The Kubernetes documentation has [instructions for creating a secret containing a private key](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys).
### Repository Credentials (v1.1+)
If you want to use the same credentials for multiple repositories, you can use `repository.credentials`:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
data:
repositories: |
- url: https://github.com/argoproj/private-repo
- url: https://github.com/argoproj/other-private-repo
repository.credentials: |
- url: https://github.com/argoproj
passwordSecret:
name: my-secret
key: password
usernameSecret:
name: my-secret
key: username
```
Argo CD will only use the credentials if you omit `usernameSecret`, `passwordSecret`, and `sshPrivateKeySecret` fields (`insecureIgnoreHostKey` is ignored).
A credential may be match if it's URL is the prefix of the repository's URL. The means that credentials may match, e.g in the above example both [https://github.com/argoproj](https://github.com/argoproj) and [https://github.com](https://github.com) would match. Argo CD selects the first one that matches.
!!! tip
Order your credentials with the most specific at the top and the least specific at the bottom.
A complete example.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
data:
repositories: |
# this has it's own credentials
- url: https://github.com/argoproj/private-repo
passwordSecret:
name: private-repo-secret
key: password
usernameSecret:
name: private-repo-secret
key: username
sshPrivateKeySecret:
name: private-repo-secret
key: sshPrivateKey
- url: https://github.com/argoproj/other-private-repo
- url: https://github.com/otherproj/another-private-repo
repository.credentials: |
# this will be used for the second repo
- url: https://github.com/argoproj
passwordSecret:
name: other-private-repo-secret
key: password
usernameSecret:
name: other-private-repo-secret
key: username
sshPrivateKeySecret:
name: other-private-repo-secret
key: sshPrivateKey
# this will be used for the third repo
- url: https://github.com
passwordSecret:
name: another-private-repo-secret
key: password
usernameSecret:
name: another-private-repo-secret
key: username
sshPrivateKeySecret:
name: another-private-repo-secret
key: sshPrivateKey
```
## Clusters
Cluster credentials are stored in secrets same as repository credentials but does not require entry in `argocd-cm` config map. Each secret must have label
`argocd.argoproj.io/secret-type: cluster`.
The secret data must include following fields:
* `name` - cluster name
* `server` - cluster api server url
* `config` - JSON representation of following data structure:
@@ -293,7 +183,7 @@ stringData:
}
```
## Helm Chart Repositories
## Helm Chart repositories
Non standard Helm Chart repositories have to be registered under the `helm.repositories` key in the
`argocd-cm` ConfigMap. Each repository must have `url` and `name` fields. For private Helm repos you
@@ -378,10 +268,10 @@ Notes:
* SSO configuration details: [SSO](sso.md)
* RBAC configuration details: [RBAC](rbac.md)
## Manage Argo CD Using Argo CD
## Manage Argo CD using Argo CD
Argo CD is able to manage itself since all settings are represented by Kubernetes manifests. The suggested way is to create [Kustomize](https://github.com/kubernetes-sigs/kustomize)
based application which uses base Argo CD manifests from [https://github.com/argoproj/argo-cd] and apply required changes on top.
based application which uses base Argo CD manifests from https://github.com/argoproj/argo-cd and apply required changes on top.
Example of `kustomization.yaml`:
@@ -401,6 +291,4 @@ patchesStrategicMerge:
The live example of self managed Argo CD config is available at https://cd.apps.argoproj.io and with configuration
stored at [argoproj/argoproj-deployments](https://github.com/argoproj/argoproj-deployments/tree/master/argocd).
!!! note
You will need to sign-in using your github account to get access to https://cd.apps.argoproj.io
> NOTE: You will need to sign-in using your github account to get access to https://cd.apps.argoproj.io

View File

@@ -1,3 +0,0 @@
# API Docs
You can find Swagger docs but setting the path `/swagger-ui` to your Argo CD UI's. E.g. [http://localhost:8080/swagger-ui](http://localhost:8080/swagger-ui).

View File

@@ -1,10 +0,0 @@
# Overview
!!! warning "You probably don't want to be reading this section of the docs."
This part of the manual is aimed at people wanting to develop third-party applications that interact with Argo CD, e.g.
* An chat bot
* An Slack integration
!!! note
Please make sure you've completed the [getting started guide](../getting_started.md).

View File

@@ -1,30 +0,0 @@
# Site
## Developing And Testing
The web site is build using `mkdocs` and `mkdocs-material`.
To test:
```bash
mkdocs serve
```
Check for broken external links:
```bash
find docs -name '*.md' -exec grep -l http {} + | xargs awesome_bot -t 3 --allow-dupe --allow-redirect -w argocd.example.com:443,argocd.example.com,kubernetes.default.svc:443,kubernetes.default.svc,mycluster.com,https://github.com/argoproj/my-private-repository,192.168.0.20,storage.googleapis.com,localhost:8080,localhost:6443,your-kubernetes-cluster-addr,10.97.164.88 --skip-save-results --
```
## Deploying
```bash
mkdocs gh-deploy
```
## Analytics
!!! tip
Don't forget to disable your ad-blocker when testing.
We collect [Google Analytics](https://analytics.google.com/analytics/web/#/report-home/a105170809w198079555p192782995).

View File

@@ -1,19 +0,0 @@
# E2E Tests
The directory contains E2E tests and test applications. The test assume that Argo CD services are installed into `argocd-e2e` namespace or cluster in current context. One throw-away
namespace `argocd-e2e***` is created prior to tests execute. The throw-away namespace is used as a target namespace for test applications.
The `test/e2e/testdata` directory contains various Argo CD applications. Before test execution directory is copies into `/tmp/argocd-e2e***` temp directory and used in tests as a
Git repository via file url: `file:///tmp/argocd-e2e***`.
## Running Tests Locally
1. Start the e2e version `make start-e2e`
1. Run the tests: `make test-e2e`
You can observe the tests by using the UI [http://localhost:8080/applications](http://localhost:8080/applications).
## CI Set-up
The tests are executed by Argo Workflow defined at `.argo-ci/ci.yaml`. CI job The builds an Argo CD image, deploy argo cd components into throw-away kubernetes cluster provisioned
using k3s and run e2e tests against it.

View File

@@ -11,13 +11,13 @@ submitted to Kubernetes in a manner which contradicts Git.
which generates different data every time `helm template` is invoked.
* For Horizontal Pod Autoscaling (HPA) objects, the HPA controller is known to reorder `spec.metrics`
in a specific order. See [kubernetes issue #74099](https://github.com/kubernetes/kubernetes/issues/74099).
To work around this, you can order `spec.replicas` in Git in the same order that the controller
To work around this, you can order `spec.replicas` in git in the same order that the controller
prefers.
In case it is impossible to fix the upstream issue, Argo CD allows you to optionally ignore differences of problematic resources.
The diffing customization can be configured for single or multiple application resources or at a system level.
## Application Level Configuration
## Application level configuration
Argo CD allows ignoring differences at a specific JSON path. The following sample application is configured to ignore differences in `spec.replicas` for all deployments:
@@ -43,7 +43,7 @@ spec:
- /spec/replicas
```
## System-Level Configuration
## System-level configuration
The comparison of resources with well-known issues can be customized at a system level. Ignored differences can be configured for a specified group and kind
in `resource.customizations` key of `argocd-cm` ConfigMap. Following is an example of a customization which ignores the `caBundle` field
@@ -53,7 +53,7 @@ of a `MutatingWebhookConfiguration` webhooks:
data:
resource.customizations: |
admissionregistration.k8s.io/MutatingWebhookConfiguration:
ignoreDifferences: |
ignoreDifferences:
jsonPointers:
- /webhooks/0/clientConfig/caBundle
- webhooks/0/clientConfig/caBundle
```

View File

@@ -2,7 +2,7 @@
## Why is my application still `OutOfSync` immediately after a successful Sync?
See [Diffing](user-guide/diffing.md) documentation for reasons resources can be OutOfSync, and ways to configure
See [Diffing](diffing.md) documentation for reasons resources can be OutOfSync, and ways to configure
Argo CD to ignore fields when differences are expected.
@@ -19,40 +19,10 @@ to return `Progressing` state instead of `Healthy`.
[kubernetes/kubernetes#68573](https://github.com/kubernetes/kubernetes/issues/68573) the `status.updatedReplicas` is not populated. So unless you run Kubernetes version which
include the fix [kubernetes/kubernetes#67570](https://github.com/kubernetes/kubernetes/pull/67570) `StatefulSet` might stay in `Progressing` state.
As workaround Argo CD allows providing [health check](operator-manual/health.md) customization which overrides default behavior.
As workaround Argo CD allows providing [health check](health.md) customization which overrides default behavior.
## I forgot the admin password, how do I reset it?
Edit the `argocd-secret` secret and update the `admin.password` field with a new bcrypt hash. You
can use a site like https://www.browserling.com/tools/bcrypt to generate a new hash. Another option
is to delete both the `admin.password` and `admin.passwordMtime` keys and restart argocd-server.
## Argo CD cannot deploy Helm Chart based applications without internet access, how can I solve it?
Argo CD might fail to generate Helm chart manifests if the chart has dependencies located in external repositories. To solve the problem you need to make sure that `requirements.yaml`
uses only internally available Helm repositories. Even if the chart uses only dependencies from internal repos Helm might decide to refresh `stable` repo. As workaround override
`stable` repo URL in `argocd-cm` config map:
```yaml
data:
helm.repositories: |
- url: http://<internal-helm-repo-host>:8080
name: stable
```
## I've configured [cluster secret](./operator-manual/declarative-setup.md#clusters) but it does not show up in CLI/UI, how do I fix it?
Check if cluster secret has `argocd.argoproj.io/secret-type: cluster` label. If secret has the label but the cluster is still not visible then make sure it might be a
permission issue. Try to list clusters using `admin` user (e.g. `argocd login --username admin && argocd cluster list`).
## Argo CD is unable to connect to my cluster, how do I troubleshoot it?
Use the following steps to reconstruct configured cluster config and connect to your cluster manually using kubectl:
```bash
kubectl exec -it <argocd-pod-name> bash # ssh into any argocd server pod
argocd-util kubeconfig https://<cluster-url> /tmp/config --namespace argocd # generate your cluster config
KUBECONFIG=/tmp/config kubectl get pods # test connection manually
```
Now you can manually verify that cluster is accessible from the Argo CD pod.

View File

@@ -1,46 +1,40 @@
# Getting Started
!!! tip
This guide assumes you have a grounding in the tools that Argo CD is based on. Please read the [understanding the basics](understand_the_basics.md).
# Argo CD Getting Started
## Requirements
* Installed [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line tool
* Have a [kubeconfig](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) file (default location is `~/.kube/config`).
## 1. Install Argo CD
```bash
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```
This will create a new namespace, `argocd`, where Argo CD services and application resources will live.
On GKE, you will need grant your account the ability to create new cluster roles:
```bash
kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
```
NOTE:
* On GKE, you will need grant your account the ability to create new cluster roles:
```bash
kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
```
## 2. Download Argo CD CLI
Download the latest Argo CD version from [https://github.com/argoproj/argo-cd/releases/latest].
Download the latest Argo CD version from https://github.com/argoproj/argo-cd/releases/latest.
Also available in Mac Homebrew:
```bash
brew tap argoproj/tap
brew install argoproj/tap/argocd
```
## 3. Access The Argo CD API Server
## 3. Access the Argo CD API server
By default, the Argo CD API server is not exposed with an external IP. To access the API server,
choose one of the following techniques to expose the Argo CD API server:
### Service Type Load Balancer
### Service Type LoadBalancer
Change the argocd-server service type to `LoadBalancer`:
```bash
@@ -48,7 +42,7 @@ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}
```
### Ingress
Follow the [ingress documentation](operator-manual/ingress.md) on how to configure Argo CD with ingress.
Follow the [ingress documentation](ingress.md) on how to configure Argo CD with ingress.
### Port Forwarding
Kubectl port-forwarding can also be used to connect to the API server without exposing the service.
@@ -56,33 +50,29 @@ Kubectl port-forwarding can also be used to connect to the API server without ex
```bash
kubectl port-forward svc/argocd-server -n argocd 8080:443
```
The API server can then be accessed using the localhost:8080
## 4. Login Using The CLI
## 4. Login using the CLI
Login as the `admin` user. The initial password is autogenerated to be the pod name of the
Argo CD API server. This can be retrieved with the command:
```bash
kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2
```
Using the above password, login to Argo CD's IP or hostname:
```bash
argocd login <ARGOCD_SERVER>
```
Change the password using the command:
```bash
argocd account update-password
```
## 5. Register A Cluster To Deploy Apps To (Optional)
## 5. Register a cluster to deploy apps to (optional)
This step registers a cluster's credentials to Argo CD, and is only necessary when deploying to
an external cluster. When deploying internally (to the same cluster that Argo CD is running in),
@@ -103,39 +93,39 @@ The above command installs a ServiceAccount (`argocd-manager`), into the kube-sy
that kubectl context, and binds the service account to an admin-level ClusterRole. Argo CD uses this
service account token to perform its management tasks (i.e. deploy/monitoring).
!!! note
The rules of the `argocd-manager-role` role can be modified such that it only has `create`, `update`, `patch`, `delete` privileges to a limited set of namespaces, groups, kinds.
However `get`, `list`, `watch` privileges are required at the cluster-scope for Argo CD to function.
> NOTE: the rules of the `argocd-manager-role` role can be modified such that it only has
`create`, `update`, `patch`, `delete` privileges to a limited set of namespaces, groups, kinds.
However `get`, `list`, `watch` privileges are required at the cluster-scope for Argo CD to function.
## 6. Create An Application From A Git Repository
## 6. Create an application from a git repository
An example repository containing a guestbook application is available at
An example git repository containing a guestbook application is available at
https://github.com/argoproj/argocd-example-apps.git to demonstrate how Argo CD works.
### Creating Apps Via CLI
### Creating apps via CLI
~~~bash
```bash
argocd app create guestbook \
--repo https://github.com/argoproj/argocd-example-apps.git \
--path guestbook \
--dest-server https://kubernetes.default.svc \
--dest-namespace default
~~~
```
### Creating Apps Via UI
### Creating apps via UI
Open a browser to the Argo CD external UI, and login using the credentials, IP/hostname set in step 4.
Connect the https://github.com/argoproj/argocd-example-apps.git repo to Argo CD:
![connect repo](assets/connect_repo.png)
After connecting a repository, select the guestbook application for creation:
After connecting a git repository, select the guestbook application for creation:
![select app](assets/select_app.png)
![create app](assets/create_app.png)
## 7. Sync (Deploy) The Application
## 7. Sync (deploy) the application
Once the guestbook application is created, you can now view its status:
@@ -164,12 +154,15 @@ deployed, and no Kubernetes resources have been created. To sync (deploy) the ap
argocd app sync guestbook
```
This command retrieves the manifests from the repository and performs a `kubectl apply` of the
This command retrieves the manifests from git repository and performs a `kubectl apply` of the
manifests. The guestbook app is now running and you can now view its resource components, logs,
events, and assessed health status:
### From UI:
![guestbook app](assets/guestbook-app.png)
![view app](assets/guestbook-tree.png)
## 8. Next Steps
Argo CD supports additional features such as automated sync, SSO, WebHooks, RBAC, Projects. See the
rest of the [documentation](./) for details.

View File

@@ -63,9 +63,9 @@ The `obj` is a global variable which contains the resource. The script must retu
NOTE: as a security measure you don't have access to most of the standard Lua libraries.
### Way 2. Contribute a Custom Health Check
### Way 2. Contribute a Custom Health Check to https://github.com/argoproj/argo-cd
A health check can be bundled into Argo CD. Custom health check scripts are located in the `resource_customizations` directory of [https://github.com/argoproj/argo-cd](https://github.com/argoproj/argo-cd). This must have the following directory structure:
A health check can be bundled into Argo CD. Custom health check scripts are located in the `resource_customizations` directory. This must have the following directory structure:
```
argo-cd

View File

@@ -1,93 +0,0 @@
# Overview
## What Is Argo CD?
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.
![Argo CD UI](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
### Quick Start
```bash
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```
Follow our [getting started guide](getting_started.md). Further [documentation](docs/)
is provided for additional features.
## How it works
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](user-guide/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](assets/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](operator-manual/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
## 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, describes 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)
## Development Status
Argo CD is actively developed and is being used in production to deploy SaaS services at Intuit

View File

@@ -2,7 +2,6 @@
Argo CD runs both a gRPC server (used by the CLI), as well as a HTTP/HTTPS server (used by the UI).
Both protocols are exposed by the argocd-server service object on the following ports:
* 443 - gRPC/HTTPS
* 80 - HTTP (redirects to HTTPS)
@@ -10,7 +9,7 @@ There are several ways how Ingress can be configured.
## [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx)
### Option 1: SSL-Passthrough
### Option 1: ssl-passthrough
Because Argo CD serves multiple protocols (gRPC/HTTPS) on the same port (443), this provides a
challenge when attempting to define a single nginx ingress object and rule for the argocd-service,
@@ -45,7 +44,7 @@ and responds appropriately. Note that the `nginx.ingress.kubernetes.io/ssl-passt
requires that the `--enable-ssl-passthrough` flag be added to the command line arguments to
`nginx-ingress-controller`.
### Option 2: Multiple Ingress Objects And Hosts
### Option 2: Multiple ingress objects and hosts
Since ingress-nginx Ingress supports only a single protocol per Ingress object, an alternative
way would be to define two Ingress objects. One for HTTP/HTTPS, and the other for gRPC:
@@ -120,14 +119,14 @@ the API server -- one for gRPC and the other for HTTP/HTTPS. However it allow TL
happen at the ingress controller.
## AWS Application Load Balancers (ALBs) And Classic ELB (HTTP Mode)
## AWS Application Load Balancers (ALBs) and Classic ELB (HTTP mode)
Neither ALBs and Classic ELB in HTTP mode, do not have full support for HTTP2/gRPC which is the
protocol used by the `argocd` CLI. Thus, when using an AWS load balancer, either Classic ELB in
passthrough mode is needed, or NLBs.
## UI Base Path
## UI base path
If Argo CD UI is available under non-root path (e.g. `/argo-cd` instead of `/`) then UI path should be configured in API server.
To configure UI path add `--basehref` flag into `argocd-server` deployment command:

View File

@@ -1,4 +1,4 @@
# Releasing
# Argo CD Release Instructions
1. Tag, build, and push argo-cd-ui
```bash

14
docs/metrics.md Normal file
View File

@@ -0,0 +1,14 @@
# Prometheus Metrics
Argo CD exposes two sets of prometheus metrics
## Application Metrics
Metrics about applications. Scraped at the `argocd-metrics:8082/metrics` endpoint.
* Gauge for application health status
* Gauge for application sync status
* Counter for application sync history
## API Server Metrics
Metrics about API Server API request and response activity (request totals, response codes, etc...).
Scraped at the `argocd-server-metrics:8083/metrics` endpoint.

View File

@@ -1,75 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guestbook
# You'll usually want to add your resources to the argocd namespace.
namespace: argocd
# Add a this finalizer ONLY if you want these to cascade delete.
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
# The project the application belongs to.
project: default
# Source of the application manifests
source:
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
path: guestbook
# helm specific config
helm:
valueFiles:
- values-prod.yaml
# kustomize specific config
kustomize:
# Optional image name prefix
namePrefix: prod-
# Optional image tags passed to "kustomize edit set imagetag" is Kustomize 1 only.
imageTags:
- name: gcr.io/heptio-images/ks-guestbook-demo
value: "0.2"
# Optional images passed to "kustomize edit set image" is Kustomize 2 only.
images:
- gcr.io/heptio-images/ks-guestbook-demo:0.2
# directory
directory:
recurse: true
jsonnet:
# A list of Jsonnet External Variables
extVars:
- name: foo
value: bar
# You can use "code to determine if the value is either string (false, the default) or Jsonnet code (if code is true).
- code: true
name: baz
value: "true"
# A list of Jsonnet Top-level Arguments
tlas:
- code: false
name: foo
value: bar
# plugin specific config
plugin:
- name: mypluginname
# Destination cluster and namespace to deploy the application
destination:
server: https://kubernetes.default.svc
namespace: guestbook
# Sync policy
syncPolicy:
automated:
prune: true
# Ignore differences at the specified json pointers
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas

View File

@@ -1,6 +0,0 @@
# Overview
This guide is for administrator and operator wanting to install and configure Argo CD for other developers.
!!! note
Please make sure you've completed the [getting started guide](../getting_started.md).

View File

@@ -1,70 +0,0 @@
# Metrics
Argo CD exposes two sets of Prometheus metrics
## Application Metrics
Metrics about applications. Scraped at the `argocd-metrics:8082/metrics` endpoint.
* Gauge for application health status
* Gauge for application sync status
* Counter for application sync history
## API Server Metrics
Metrics about API Server API request and response activity (request totals, response codes, etc...).
Scraped at the `argocd-server-metrics:8083/metrics` endpoint.
## Prometheus Operator
If using Prometheus Operator, the following ServiceMonitor example manifests can be used.
Change `metadata.labels.release` to the name of label selected by your Prometheus.
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-metrics
labels:
release: prometheus-operator
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-metrics
endpoints:
- port: metrics
```
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-server-metrics
labels:
release: prometheus-operator
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-server-metrics
endpoints:
- port: metrics
```
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-repo-server-metrics
labels:
release: prometheus-operator
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-repo-server
endpoints:
- port: metrics
```
## Dashboards
You can find an example Grafana dashboard [here](https://github.com/argoproj/argo-cd/blob/master/examples/dashboard.json)
![dashboard](../assets/dashboard.jpg)

View File

@@ -1,20 +1,18 @@
# Parameter Overrides
Argo CD provides a mechanism to override the parameters of a ksonnet/helm app. This provides flexibility
in having most of the application manifests defined in Git, while leaving room for *some* parts of the
k8s manifests determined dynamically, or outside of Git. It also serves as an alternative way of
in having most of the application manifests defined in git, while leaving room for *some* parts of the
k8s manifests determined dynamically, or outside of git. It also serves as an alternative way of
redeploying an application by changing application parameters via Argo CD, instead of making the
changes to the manifests in Git.
changes to the manifests in git.
!!! tip
Many consider this mode of operation as an anti-pattern to GitOps, since the source of
truth becomes a union of the Git repository, and the application overrides. The Argo CD parameter
overrides feature is provided mainly as a convenience to developers and is intended to be used in
dev/test environments, vs. production environments.
**NOTE:** many consider this mode of operation as an anti-pattern to GitOps, since the source of
truth becomes a union of the git repository, and the application overrides. The Argo CD parameter
overrides feature is provided mainly as a convenience to developers and is intended to be used in
dev/test environments, vs. production environments.
To use parameter overrides, run the `argocd app set -p (COMPONENT=)PARAM=VALUE` command:
```bash
```
argocd app set guestbook -p guestbook=image=example/guestbook:abcd123
argocd app sync guestbook
```
@@ -32,7 +30,7 @@ The following are situations where parameter overrides would be useful:
version of their guestbook application after every build in the tip of master. To address this use
case, the application would expose a parameter named `image`, whose value used in the `dev`
environment contains a placeholder value (e.g. `example/guestbook:replaceme`). The placeholder value
would be determined externally (outside of Git) such as a build system. Then, as part of the build
would be determined externally (outside of git) such as a build system. Then, as part of the build
pipeline, the parameter value of the `image` would be continually updated to the freshly built image
(e.g. `argocd app set guestbook -p guestbook=image=example/guestbook:abcd123`). A sync operation
would result in the application being redeployed with the new image.
@@ -43,6 +41,6 @@ the public repository and customize the deployment with different parameters, wi
forking the repository to make the changes. For example, to install Redis from the Helm chart
repository and customize the the database password, you would run:
```bash
```
argocd app create redis --repo https://github.com/helm/charts.git --path stable/redis --dest-server https://kubernetes.default.svc --dest-namespace default -p password=abc123
```

View File

@@ -6,7 +6,7 @@ spec:
# Project description
description: Example Project
# Allow manifests to deploy from any Git repos
# Allow manifests to deploy from any git repos
sourceRepos:
- '*'
@@ -21,7 +21,7 @@ spec:
kind: Namespace
# Allow all namespaced-scoped resources to be created, except for ResourceQuota, LimitRange, NetworkPolicy
namespaceResourceBlacklist:
clusterResourceWhitelist:
- group: ''
kind: ResourceQuota
- group: ''
@@ -48,4 +48,4 @@ spec:
# NOTE: JWT tokens can only be generated by the API server and the token is not persisted
# anywhere by Argo CD. It can be prematurely revoked by removing the entry from this list.
jwtTokens:
- iat: 1535390316
- iat: 1535390316

View File

@@ -3,12 +3,12 @@
Projects provide a logical grouping of applications, which is useful when Argo CD is used by multiple
teams. Projects provide the following features:
* restrict *what* may be deployed (trusted Git source repositories)
* restrict *what* may be deployed (trusted git source repositories)
* restrict *where* apps may be deployed to (destination clusters and namespaces)
* restrict what kinds of objects may or may not be deployed (e.g. RBAC, CRDs, DaemonSets, NetworkPolicy etc...)
* defining project roles to provide application RBAC (bound to OIDC groups and/or JWT tokens)
### The Default Project
### The default project
Every application belongs to a single project. If unspecified, an application belongs to the
`default` project, which is created automatically and by default, permits deployments from any
@@ -31,16 +31,16 @@ spec:
Additional projects can be created to give separate teams different levels of access to namespaces.
The following command creates a new project `myproject` which can deploy applications to namespace
`mynamespace` of cluster `https://kubernetes.default.svc`. The permitted Git source repository is
`mynamespace` of cluster `https://kubernetes.default.svc`. The permitted git source repository is
set to `https://github.com/argoproj/argocd-example-apps.git` repository.
```bash
```
argocd proj create myproject -d https://kubernetes.default.svc,mynamespace -s https://github.com/argoproj/argocd-example-apps.git
```
### Managing Projects
Permitted source Git repositories are managed using commands:
Permitted source git repositories are managed using commands:
```bash
argocd proj add-source <PROJECT> <REPO>
@@ -48,8 +48,7 @@ argocd proj remove-source <PROJECT> <REPO>
```
Permitted destination clusters and namespaces are managed with the commands:
```bash
```
argocd proj add-destination <PROJECT> <CLUSTER>,<NAMESPACE>
argocd proj remove-destination <PROJECT> <CLUSTER>,<NAMESPACE>
```
@@ -57,15 +56,14 @@ argocd proj remove-destination <PROJECT> <CLUSTER>,<NAMESPACE>
Permitted destination K8s resource kinds are managed with the commands. Note that namespaced-scoped
resources are restricted via a blacklist, whereas cluster-scoped resources are restricted via
whitelist.
```bash
```
argocd proj allow-cluster-resource <PROJECT> <GROUP> <KIND>
argocd proj allow-namespace-resource <PROJECT> <GROUP> <KIND>
argocd proj deny-cluster-resource <PROJECT> <GROUP> <KIND>
argocd proj deny-namespace-resource <PROJECT> <GROUP> <KIND>
```
### Assign Application To A Project
### Assign application to a project
The application project can be changed using `app set` command. In order to change the project of
an app, the user must have permissions to access the new project.
@@ -74,7 +72,7 @@ an app, the user must have permissions to access the new project.
argocd app set guestbook-default --project myproject
```
### Configuring RBAC With Projects
### Configuring RBAC with projects
Once projects have been defined, RBAC rules can be written to restrict access to the applications
in the project. The following example configures RBAC for two GitHub teams: `team1` and `team2`,

View File

@@ -10,11 +10,9 @@ configured, additional RBAC roles can be defined, and SSO groups can man be mapp
## Configure RBAC
RBAC configuration allows defining roles and groups. Argo CD has two pre-defined roles:
* `role:readonly` - read-only access to all resources
* `role:admin` - unrestricted access to all resources
These role definitions can be seen in [builtin-policy.csv](https://github.com/argoproj/argo-cd/blob/master/assets/builtin-policy.csv)
These role definitions can be seen in [builtin-policy.csv](../assets/builtin-policy.csv)
Additional roles and groups can be configured in `argocd-rbac-cm` ConfigMap. The example below
configures a custom role, named `org-admin`. The role is assigned to any user which belongs to

View File

@@ -4,7 +4,6 @@
Synchronization can be configured using resource hooks. Hooks are ways to interject custom logic before, during,
and after a Sync operation. Some use cases for hooks are:
* Using a `PreSync` hook to perform a database schema migration before deploying a new version of the app.
* Using a `Sync` hook to orchestrate a complex deployment requiring more sophistication than the
kubernetes rolling update strategy (e.g. a blue/green deployment).

View File

@@ -14,7 +14,7 @@ in one of the following ways:
1. For the local `admin` user, a username/password is exchanged for a JWT using the `/api/v1/session`
endpoint. This token is signed & issued by the Argo CD API server itself, and has no expiration.
When the admin password is updated, all existing admin JWT tokens are immediately revoked.
The password is stored as a bcrypt hash in the [`argocd-secret`](https://github.com/argoproj/argo-cd/blob/master/manifests/base/config/argocd-secret.yaml) Secret.
The password is stored as a bcrypt hash in the [`argocd-secret`](../manifests/base/argocd-secret.yaml) Secret.
2. For Single Sign-On users, the user completes an OAuth2 login flow to the configured OIDC identity
provider (either delegated through the bundled Dex provider, or directly to a self-managed OIDC
@@ -48,9 +48,8 @@ API server can enforce the use of TLS 1.2 using the flag: `--tlsminversion 1.2`.
Argo CD never returns sensitive data from its API, and redacts all sensitive data in API payloads
and logs. This includes:
* cluster credentials
* Git credentials
* git credentials
* OAuth2 client secrets
* Kubernetes Secret values
@@ -91,9 +90,8 @@ argocd cluster rm https://your-kubernetes-cluster-addr
## Cluster RBAC
By default, Argo CD uses a [clusteradmin level role](https://github.com/argoproj/argo-cd/blob/master/manifests/base/application-controller/argocd-application-controller-role.yaml)
By default, Argo CD uses a [clusteradmin level role](../manifests/cluster-install/application-controller/argocd-application-controller-clusterrole.yaml)
in order to:
1. watch & operate on cluster state
2. deploy resources to the cluster
@@ -119,18 +117,19 @@ kubectl edit clusterrole argocd-server
kubectl edit clusterrole argocd-application-controller
```
!!! tip
If you want to deny ArgoCD access to a kind of resource then add it as an [excluded resource](declarative-setup.md#resource-exclusion).
Note:
* If you to deny ArgoCD access to a kind of resource then add it as an [excluded resource](declarative-setup.md#resource-exclusion).
## Auditing
As a GitOps deployment tool, the Git commit history provides a natural audit log of what changes
As a GitOps deployment tool, the git commit history provides a natural audit log of what changes
were made to application configuration, when they were made, and by whom. However, this audit log
only applies to what happened in Git and does not necessarily correlate one-to-one with events
only applies to what happened in git and does not necessarily correlate one-to-one with events
that happen in a cluster. For example, User A could have made multiple commits to application
manifests, but User B could have just only synced those changes to the cluster sometime later.
To complement the Git revision history, Argo CD emits Kubernetes Events of application activity,
To complement the git revision history, Argo CD emits Kubernetes Events of application activity,
indicating the responsible actor when applicable. For example:
```bash
@@ -162,7 +161,6 @@ at three minute intervals, just fast-tracked by the webhook event.
## Reporting Vulnerabilities
Please report security vulnerabilities by e-mailing:
* [Jesse_Suen@intuit.com](mailto:Jesse_Suen@intuit.com)
* [Alexander_Matyushentsev@intuit.com](mailto:Alexander_Matyushentsev@intuit.com)
* [Edward_Lee@intuit.com](mailto:Edward_Lee@intuit.com)
* Jesse_Suen@intuit.com
* Alexander_Matyushentsev@intuit.com
* Edward_Lee@intuit.com

View File

@@ -5,7 +5,7 @@
Argo CD does not have any local users other than the built-in `admin` user. All other users are
expected to login via SSO. There are two ways that SSO can be configured:
* Bundled Dex OIDC provider - use this option if your current provider does not support OIDC (e.g. SAML,
* Bundled Dex OIDC provider - use this option your current provider does not support OIDC (e.g. SAML,
LDAP) or if you wish to leverage any of Dex's connector features (e.g. the ability to map GitHub
organizations and teams to OIDC groups claims).
@@ -28,18 +28,17 @@ steps should be similar for other identity providers.
In GitHub, register a new application. The callback address should be the `/api/dex/callback`
endpoint of your Argo CD URL (e.g. https://argocd.example.com/api/dex/callback).
![Register OAuth App](../assets/register-app.png "Register OAuth App")
![Register OAuth App](assets/register-app.png "Register OAuth App")
After registering the app, you will receive an OAuth2 client ID and secret. These values will be
inputted into the Argo CD configmap.
![OAuth2 Client Config](../assets/oauth2-config.png "OAuth2 Client Config")
![OAuth2 Client Config](assets/oauth2-config.png "OAuth2 Client Config")
### 2. Configure Argo CD for SSO
Edit the argocd-cm configmap:
```bash
```
kubectl edit configmap argocd-cm -n argocd
```
@@ -52,7 +51,7 @@ kubectl edit configmap argocd-cm -n argocd
`connectors.config.orgs` list, add one or more GitHub organizations. Any member of the org will
then be able to login to Argo CD to perform management tasks.
```yaml
```
data:
url: https://argocd.example.com
@@ -83,7 +82,6 @@ data:
After saving, the changes should take affect automatically.
NOTES:
* Any values which start with '$' will look to a key in argocd-secret of the same name (minus the $),
to obtain the actual value. This allows you to store the `clientSecret` as a kubernetes secret.
* There is no need to set `redirectURI` in the `connectors.config` as shown in the dex documentation.
@@ -91,12 +89,12 @@ NOTES:
correct external callback URL (e.g. https://argocd.example.com/api/dex/callback)
## Existing OIDC Provider
## Existing OIDC provider
To configure Argo CD to delegate authenticate to your existing OIDC provider, add the OAuth2
configuration to the `argocd-cm` ConfigMap under the `oidc.config` key:
```yaml
```
data:
url: https://argocd.example.com

View File

@@ -1,7 +1,7 @@
# Tracking and Deployment Strategies
An Argo CD application spec provides several different ways of track kubernetes resource manifests in
Git. This document describes the different techniques and the means of deploying those manifests to
git. This document describes the different techniques and the means of deploying those manifests to
the target environment.
## HEAD / Branch Tracking
@@ -15,17 +15,17 @@ changes to the tracked branch/symbolic reference, which will then be detected by
## Tag Tracking
If a tag is specified, the manifests at the specified Git tag will be used to perform the sync
If a tag is specified, the manifests at the specified git tag will be used to perform the sync
comparison. This provides some advantages over branch tracking in that a tag is generally considered
more stable, and less frequently updated, with some manual judgement of what constitutes a tag.
To redeploy an application, the user uses Git to change the meaning of a tag by retagging it to a
To redeploy an application, the user uses git to change the meaning of a tag by retagging it to a
different commit SHA. Argo CD will detect the new meaning of the tag when performing the
comparison/sync.
## Commit Pinning
If a Git commit SHA is specified, the application is effectively pinned to the manifests defined at
If a git commit SHA is specified, the application is effectively pinned to the manifests defined at
the specified commit. This is the most restrictive of the techniques and is typically used to
control production environments.
@@ -38,9 +38,9 @@ on an application which is pinned to a revision.
In all tracking strategies, the application has the option to sync automatically. If [auto-sync](auto_sync.md)
is configured, the new resources manifests will be applied automatically -- as soon as a difference
is detected between the target state (Git) and live state. If auto-sync is disabled, a manual sync
is detected between the target state (git) and live state. If auto-sync is disabled, a manual sync
will be needed using the Argo UI, CLI, or API.
## Parameter Overrides
Note that in all tracking strategies, any [parameter overrides](parameters.md) set in the
application instance take precedence over the Git state.
application instance take precedence over the git state.

View File

@@ -1,17 +0,0 @@
# Understand The Basics
Before effectively using Argo CD, it is necessary to understand the underlying technology that the platform is built on. It is also necessary to understand the features being provided to you and how to use them. The section below provides some useful links to build up this understanding.
 
## Learn The Fundamentals
* Go through the online Docker and Kubernetes tutorials
* [A Beginner-Friendly Introduction to Containers, VMs and Docker](https://medium.freecodecamp.org/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b)
* [Introduction to Kubernetes](https://courses.edx.org/courses/course-v1:LinuxFoundationX+LFS158x+2T2017/course/)
* [Tutorials](https://kubernetes.io/docs/tutorials/)
* [Hands on labs](https://katacoda.com/courses/kubernetes/)
* Depending on how you plan to template your applications:
* [Kustomize](https://kustomize.io)
* [Helm](https://helm.sh)
* [Ksonnet](https://ksonnet.io)
* If you're integrating with Jenkins:
* [Jenkins User Guide](https://jenkins.io)

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:
## Build And Publish A New Container Image
```bash
docker build -t mycompany/guestbook:v2.0 .
docker push mycompany/guestbook:v2.0
```
## Update The Local Manifests Using Your Preferred Templating Tool, And Push The Changes To Git
!!! tip
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.
```bash
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
```
## 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.
```bash
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](../operator-manual/webhook.md), or polled every 3 minutes), and automatically sync the new manifests.

View File

@@ -1,6 +0,0 @@
# Overview
This guide is for developers who have Argo CD installed for them and are managing applications.
!!! note
Please make sure you've completed the [getting started guide](../getting_started.md).

View File

@@ -1,71 +0,0 @@
# Private Repositories
## Credentials
If application manifests are located in private repository then repository credentials have to be configured. Argo CD supports both HTTP and SSH Git credentials.
### HTTP Username And Password Credential
Private repositories that require a username and password typically have a URL that start with "https://" rather than "git@" or "ssh://".
Credentials can be configured using Argo CD CLI:
```bash
argocd repo add https://github.com/argoproj/argocd-example-apps --username <username> --password <password>
```
or UI:
1. Navigate to `Settings/Repositories`
1. Click `Connect Repo` button and enter HTTP credentials
![connect repo](../assets/connect_repo.png)
#### Access Token
Instead of using username and password you might use access token. Following instructions of your Git hosting service to generate the token:
* [Github](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line)
* [Gitlab](https://docs.gitlab.com/ee/user/project/deploy_tokens/)
* [Bitbucket](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html)
Then, connect the repository using an empty string as a username and access token value as a password.
### SSH Private Key Credential
Private repositories that require an SSH private key have a URL that typically start with "git@" or "ssh://" rather than "https://".
The Argo CD UI don't support configuring SSH credentials. The SSH credentials can only be configured using the Argo CD CLI:
```
argocd repo add git@github.com:argoproj/argocd-example-apps.git --ssh-private-key-path ~/.ssh/id_rsa
```
## Self-Signed Certificates
If you are using self-hosted Git hosting service with the self-signed certificate then you need to disable certificate validation for that Git host.
Following options are available:
Add repository using Argo CD CLI and `--insecure-ignore-host-key` flag:
```bash
argocd repo add git@github.com:argoproj/argocd-example-apps.git --ssh-private-key-path ~/.ssh/id_rsa
```
The flag disables certificate validation only for specified repository.
!!! warning
The `--insecure-ignore-host-key` flag does not work for HTTPS Git URLs. See [#1513](https://github.com/argoproj/argo-cd/issues/1513).
You can add Git service hostname to the `/etc/ssh/ssh_known_hosts` in each Argo CD deployment and disables cert validation for Git SSL URLs. For more information see
[example](https://github.com/argoproj/argo-cd/tree/master/examples/known-hosts) which demonstrates how `/etc/ssh/ssh_known_hosts` can be customized.
!!! note
The `/etc/ssh/ssh_known_hosts` should include Git host on each Argo CD deployment as well as on a computer where `argocd repo add` is executed. After resolving issue
[#1514](https://github.com/argoproj/argo-cd/issues/1514) only `argocd-repo-server` deployment has to be customized.
## Declarative Configuration
See [declarative setup](../operator-manual/declarative-setup#Repositories)

View File

@@ -1,18 +0,0 @@
# Tool Detection
The tool used to build an application is detected as follows:
If a specific tool is explicitly configured, then that tool is selected to create your application's manifests.
If not, then the tool is detected implicitly as follows:
* **Ksonnet** if there are two files, one named `app.yaml` and one named `components/params.libsonnet`.
* **Helm** if there's a file matching `Chart.yaml`.
* **Kustomize** if there's a `kustomization.yaml`, `kustomization.yml`, or `Kustomization`
Otherwise it is assumed to be a plain **directory** application.
## References
* [reposerver/repository/repository.go/GetAppSourceType](https://github.com/argoproj/argo-cd/blob/master/reposerver/repository/repository.go#L286)
* [server/repository/repository.go/listAppTypes](https://github.com/argoproj/argo-cd/blob/master/server/repository/repository.go#L97)

View File

@@ -2,29 +2,29 @@
## Overview
Argo CD polls Git repositories every three minutes to detect changes to the manifests. To eliminate
Argo CD polls git repositories every three minutes to detect changes to the manifests. To eliminate
this delay from polling, the API server can be configured to receive webhook events. Argo CD supports
Git webhook notifications from GitHub, GitLab, and BitBucket. The following explains how to configure
a Git webhook for GitHub, but the same process should be applicable to other providers.
git webhook notifications from GitHub, GitLab, and BitBucket. The following explains how to configure
a git webhook for GitHub, but the same process should be applicable to other providers.
### 1. Create The WebHook In The Git Provider
### 1. Create the webhook in the git provider
In your Git provider, navigate to the settings page where webhooks can be configured. The payload
URL configured in the Git provider should use the `/api/webhook` endpoint of your Argo CD instance
(e.g. [https://argocd.example.com/api/webhook]). If you wish to use a shared secret, input an
In your git provider, navigate to the settings page where webhooks can be configured. The payload
URL configured in the git provider should use the `/api/webhook` endpoint of your Argo CD instance
(e.g. https://argocd.example.com/api/webhook). If you wish to use a shared secret, input an
arbitrary value in the secret. This value will be used when configuring the webhook in the next step.
![Add Webhook](../assets/webhook-config.png "Add Webhook")
![Add Webhook](assets/webhook-config.png "Add Webhook")
### 2. Configure Argo CD With The WebHook Secret Optional)
### 2. Configure Argo CD with the webhook secret (optional)
Configuring a webhook shared secret is optional, since Argo CD will still refresh applications
related to the Git repository, even with unauthenticated webhook events. This is safe to do since
related to the git repository, even with unauthenticated webhook events. This is safe to do since
the contents of webhook payloads are considered untrusted, and will only result in a refresh of the
application (a process which already occurs at three-minute intervals). If Argo CD is publicly
accessible, then configuring a webhook secret is recommended to prevent a DDoS attack.
In the `argocd-secret` kubernetes secret, configure one of the following keys with the Git
In the `argocd-secret` kubernetes secret, configure one of the following keys with the git
provider's webhook secret configured in step 1.
| Provider | K8s Secret Key |
@@ -34,8 +34,7 @@ provider's webhook secret configured in step 1.
| BitBucket | `bitbucket.webhook.uuid` |
Edit the Argo CD kubernetes secret:
```bash
```
kubectl edit secret argocd-secret -n argocd
```
@@ -44,7 +43,7 @@ which saves you the trouble of base64 encoding the values and copying it to the
Simply copy the shared webhook secret created in step 1, to the corresponding
GitHub/GitLab/BitBucket key under the `stringData` field:
```yaml
```
apiVersion: v1
kind: Secret
metadata:
@@ -63,6 +62,7 @@ stringData:
# bitbucket webhook secret
bitbucket.webhook.uuid: your-bitbucket-uuid
```
After saving, the changes should take affect automatically.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
apiVersion: 0.1.0
gitVersion:
commitSha: 422d521c05aa905df949868143b26445f5e4eda5
refSpec: master
kind: ksonnet.io/registry
libraries:
apache:
path: apache
version: master
efk:
path: efk
version: master
mariadb:
path: mariadb
version: master
memcached:
path: memcached
version: master
mongodb:
path: mongodb
version: master
mysql:
path: mysql
version: master
nginx:
path: nginx
version: master
node:
path: node
version: master
postgres:
path: postgres
version: master
redis:
path: redis
version: master
tomcat:
path: tomcat
version: master

View File

@@ -0,0 +1,18 @@
apiVersion: 0.1.0
environments:
minikube:
destination:
namespace: default
server: https://192.168.99.100:8443
k8sVersion: v1.7.0
path: minikube
kind: ksonnet.io/app
name: test-app
registries:
incubator:
gitVersion:
commitSha: 422d521c05aa905df949868143b26445f5e4eda5
refSpec: master
protocol: github
uri: github.com/ksonnet/parts/tree/master/incubator
version: 0.0.1

View File

@@ -0,0 +1,29 @@
local env = std.extVar("__ksonnet/environments");
local params = std.extVar("__ksonnet/params").components["guestbook-ui"];
local k = import "k.libsonnet";
local deployment = k.apps.v1beta1.deployment;
local container = k.apps.v1beta1.deployment.mixin.spec.template.spec.containersType;
local containerPort = container.portsType;
local service = k.core.v1.service;
local servicePort = k.core.v1.service.mixin.spec.portsType;
local targetPort = params.containerPort;
local labels = {app: params.name};
local appService = service
.new(
params.name,
labels,
servicePort.new(params.servicePort, targetPort))
.withType(params.type);
local appDeployment = deployment
.new(
params.name,
params.replicas,
container
.new(params.name, params.image)
.withPorts(containerPort.new(targetPort)) + if params.command != null then { command: [ params.command ] } else {},
labels).withProgressDeadlineSeconds(5);
k.core.v1.list.new([appService, appDeployment])

View File

@@ -9,7 +9,7 @@
"guestbook-ui": {
containerPort: 80,
image: "gcr.io/heptio-images/ks-guestbook-demo:0.2",
name: "ks-guestbook-ui",
name: "guestbook-ui",
replicas: 1,
servicePort: 80,
type: "ClusterIP",

View File

@@ -0,0 +1,7 @@
local base = import "base.libsonnet";
local k = import "k.libsonnet";
base + {
// Insert user-specified overrides here. For example if a component is named "nginx-deployment", you might have something like:
// "nginx-deployment"+: k.deployment.mixin.metadata.labels({foo: "bar"})
}

View File

@@ -0,0 +1,10 @@
local params = import "../../components/params.libsonnet";
params + {
components +: {
// Insert component parameter overrides here. Ex:
// guestbook +: {
// name: "guestbook-dev",
// replicas: params.global.replicas,
// },
},
}

View File

@@ -0,0 +1,80 @@
local k8s = import "k8s.libsonnet";
local apps = k8s.apps;
local core = k8s.core;
local extensions = k8s.extensions;
local hidden = {
mapContainers(f):: {
local podContainers = super.spec.template.spec.containers,
spec+: {
template+: {
spec+: {
// IMPORTANT: This overwrites the 'containers' field
// for this deployment.
containers: std.map(f, podContainers),
},
},
},
},
mapContainersWithName(names, f) ::
local nameSet =
if std.type(names) == "array"
then std.set(names)
else std.set([names]);
local inNameSet(name) = std.length(std.setInter(nameSet, std.set([name]))) > 0;
self.mapContainers(
function(c)
if std.objectHas(c, "name") && inNameSet(c.name)
then f(c)
else c
),
};
k8s + {
apps:: apps + {
v1beta1:: apps.v1beta1 + {
local v1beta1 = apps.v1beta1,
daemonSet:: v1beta1.daemonSet + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
deployment:: v1beta1.deployment + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
},
},
core:: core + {
v1:: core.v1 + {
list:: {
new(items)::
{apiVersion: "v1"} +
{kind: "List"} +
self.items(items),
items(items):: if std.type(items) == "array" then {items+: items} else {items+: [items]},
},
},
},
extensions:: extensions + {
v1beta1:: extensions.v1beta1 + {
local v1beta1 = extensions.v1beta1,
daemonSet:: v1beta1.daemonSet + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
deployment:: v1beta1.deployment + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
},
},
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
local k8s = import "k8s.libsonnet";
local apps = k8s.apps;
local core = k8s.core;
local extensions = k8s.extensions;
local hidden = {
mapContainers(f):: {
local podContainers = super.spec.template.spec.containers,
spec+: {
template+: {
spec+: {
// IMPORTANT: This overwrites the 'containers' field
// for this deployment.
containers: std.map(f, podContainers),
},
},
},
},
mapContainersWithName(names, f) ::
local nameSet =
if std.type(names) == "array"
then std.set(names)
else std.set([names]);
local inNameSet(name) = std.length(std.setInter(nameSet, std.set([name]))) > 0;
self.mapContainers(
function(c)
if std.objectHas(c, "name") && inNameSet(c.name)
then f(c)
else c
),
};
k8s + {
apps:: apps + {
v1beta1:: apps.v1beta1 + {
local v1beta1 = apps.v1beta1,
daemonSet:: v1beta1.daemonSet + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
deployment:: v1beta1.deployment + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
},
},
core:: core + {
v1:: core.v1 + {
list:: {
new(items)::
{apiVersion: "v1"} +
{kind: "List"} +
self.items(items),
items(items):: if std.type(items) == "array" then {items+: items} else {items+: [items]},
},
},
},
extensions:: extensions + {
v1beta1:: extensions.v1beta1 + {
local v1beta1 = extensions.v1beta1,
daemonSet:: v1beta1.daemonSet + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
deployment:: v1beta1.deployment + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
},
},
}

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