Compare commits
5 Commits
release-0.
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4d0bd3926 | ||
|
|
18dc82d14d | ||
|
|
e720abb58b | ||
|
|
a2e9a9ee49 | ||
|
|
cf3776903d |
@@ -29,13 +29,7 @@ spec:
|
||||
- name: cmd
|
||||
value: "{{item}}"
|
||||
withItems:
|
||||
- dep ensure && make cli lint test
|
||||
- name: test-e2e
|
||||
template: ci-builder
|
||||
arguments:
|
||||
parameters:
|
||||
- name: cmd
|
||||
value: "dep ensure && make test-e2e"
|
||||
- dep ensure && make cli lint test test-e2e
|
||||
|
||||
- name: ci-builder
|
||||
inputs:
|
||||
|
||||
147
CHANGELOG.md
@@ -1,119 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## v0.7.2 (2018-08-21)
|
||||
- API discovery becomes best effort when partial resource list is returned (issue #524)
|
||||
|
||||
## v0.7.1 (2018-08-03)
|
||||
+ Surface helm parameters to the application level (#485)
|
||||
+ [UI] Improve application creation wizard (#459)
|
||||
+ [UI] Show indicator when refresh is still in progress (#493)
|
||||
* [UI] Improve data loading error notification (#446)
|
||||
* Infer username from claims during an `argocd relogin` (#475)
|
||||
* Expand RBAC role to be able to create application events. Fix username claims extraction
|
||||
- Fix scalability issues with the ListApps API (#494)
|
||||
- Fix issue where application server was retrieving events from incorrect cluster (#478)
|
||||
- Fix failure in identifying app source type when path was '.'
|
||||
- AppProjectSpec SourceRepos mislabeled (#490)
|
||||
- Failed e2e test was not failing CI workflow
|
||||
* Fix linux download link in getting_started.md (#487) (@chocopowwwa)
|
||||
|
||||
## v0.7.0 (2018-07-27)
|
||||
+ Support helm charts and yaml directories as an application source
|
||||
+ Audit trails in the form of API call logs
|
||||
+ Generate kubernetes events for application state changes
|
||||
+ Add ksonnet version to version endpoint (#433)
|
||||
+ Show CLI progress for sync and rollback
|
||||
+ Make use of dex refresh tokens and store them into local config
|
||||
+ Expire local superuser tokens when their password changes
|
||||
+ Add `argocd relogin` command as a convenience around login to current context
|
||||
- Fix saving default connection status for repos and clusters
|
||||
- Fix undesired fail-fast behavior of health check
|
||||
- Fix memory leak in the cluster resource watch
|
||||
- Health check for StatefulSets, DaemonSet, and ReplicaSets were failing due to use of wrong converters
|
||||
|
||||
## v0.6.2 (2018-07-23)
|
||||
- Health check for StatefulSets, DaemonSet, and ReplicaSets were failing due to use of wrong converters
|
||||
|
||||
## v0.6.1 (2018-07-18)
|
||||
- Fix regression where deployment health check incorrectly reported Healthy
|
||||
+ Intercept dex SSO errors and present them in Argo login page
|
||||
|
||||
## v0.6.0 (2018-07-16)
|
||||
+ Support PreSync, Sync, PostSync resource hooks
|
||||
+ Introduce Application Projects for finer grain RBAC controls
|
||||
+ Swagger Docs & UI
|
||||
+ Support in-cluster deployments internal kubernetes service name
|
||||
+ Refactoring & Improvements
|
||||
* Improved error handling, status and condition reporting
|
||||
* Remove installer in favor of kubectl apply instructions
|
||||
* Add validation when setting application parameters
|
||||
* Cascade deletion is decided during app deletion, instead of app creation
|
||||
- Fix git authentication implementation when using using SSH key
|
||||
- app-name label was inadvertently injected into spec.selector if selector was omitted from v1beta1 specs
|
||||
|
||||
## v0.5.4 (2018-06-27)
|
||||
- Refresh flag to sync should be optional, not required
|
||||
|
||||
## v0.5.3 (2018-06-20)
|
||||
+ Support cluster management using the internal k8s API address https://kubernetes.default.svc (#307)
|
||||
+ Support diffing a local ksonnet app to the live application state (resolves #239) (#298)
|
||||
+ Add ability to show last operation result in app get. Show path in app list -o wide (#297)
|
||||
+ Update dependencies: ksonnet v0.11, golang v1.10, debian v9.4 (#296)
|
||||
+ Add ability to force a refresh of an app during get (resolves #269) (#293)
|
||||
+ Automatically restart API server upon certificate changes (#292)
|
||||
|
||||
## v0.5.2 (2018-06-14)
|
||||
+ Resource events tab on application details page (#286)
|
||||
+ Display pod status on application details page (#231)
|
||||
|
||||
## v0.5.1 (2018-06-13)
|
||||
- API server incorrectly compose application fully qualified name for RBAC check (#283)
|
||||
- UI crash while rendering application operation info if operation failed
|
||||
|
||||
## v0.5.0 (2018-06-12)
|
||||
+ RBAC access control
|
||||
+ Repository/Cluster state monitoring
|
||||
+ ArgoCD settings import/export
|
||||
+ Application creation UI wizard
|
||||
+ argocd app manifests for printing the application manifests
|
||||
+ argocd app unset command to unset parameter overrides
|
||||
+ Fail app sync if prune flag is required (#276)
|
||||
+ Take into account number of unavailable replicas to decided if deployment is healthy or not #270
|
||||
+ Add ability to show parameters and overrides in CLI (resolves #240)
|
||||
- Repo names containing underscores were not being accepted (#258)
|
||||
- Cookie token was not parsed properly when mixed with other site cookies
|
||||
|
||||
## v0.4.7 (2018-06-07)
|
||||
- Fix argocd app wait health checking logic
|
||||
|
||||
## v0.4.6 (2018-06-06)
|
||||
- Retry argocd app wait connection errors from EOF watch. Show detailed state changes
|
||||
|
||||
## v0.4.5 (2018-05-31)
|
||||
+ Add argocd app unset command to unset parameter overrides
|
||||
- Cookie token was not parsed properly when mixed with other site cookies
|
||||
|
||||
## v0.4.4 (2018-05-30)
|
||||
+ Add ability to show parameters and overrides in CLI (resolves #240)
|
||||
+ Add Events API endpoint
|
||||
+ Issue #238 - add upsert flag to 'argocd app create' command
|
||||
+ Add repo browsing endpoint (#229)
|
||||
+ Support subscribing to settings updates and auto-restart of dex and API server
|
||||
- Issue #233 - Controller does not persist rollback operation result
|
||||
- App sync frequently fails due to concurrent app modification
|
||||
|
||||
## v0.4.3 (2018-05-21)
|
||||
- Move local branch deletion as part of git Reset() (resolves #185) (#222)
|
||||
- Fix exit code for app wait (#219)
|
||||
|
||||
## v0.4.2 (2018-05-21)
|
||||
+ Show URL in argocd app get
|
||||
- Remove interactive context name prompt during login which broke login automation
|
||||
* Rename force flag to cascade in argocd app delete
|
||||
|
||||
## v0.4.1 (2018-05-18)
|
||||
+ Implemented argocd app wait command
|
||||
|
||||
## v0.4.0 (2018-05-17)
|
||||
+ SSO Integration
|
||||
+ GitHub Webhook
|
||||
@@ -126,36 +12,3 @@
|
||||
* Manifests are memoized in repo server
|
||||
- Fix connection timeouts to SSH repos
|
||||
|
||||
## v0.3.2 (2018-05-03)
|
||||
+ Application sync should delete 'unexpected' resources #139
|
||||
+ Update ksonnet to v0.10.1
|
||||
+ Detect unexpected resources
|
||||
- Fix: App sync frequently fails due to concurrent app modification #147
|
||||
- Fix: improve app state comparator: #136, #132
|
||||
|
||||
## v0.3.1 (2018-04-24)
|
||||
+ Add new rollback RPC with numeric identifiers
|
||||
+ New argo app history and argo app rollback command
|
||||
+ Switch to gogo/protobuf for golang code generation
|
||||
- Fix: create .argocd directory during argo login (issue #123)
|
||||
- Fix: Allow overriding server or namespace separately (issue #110)
|
||||
|
||||
## v0.3.0 (2018-04-23)
|
||||
+ Auth support
|
||||
+ TLS support
|
||||
+ DAG-based application view
|
||||
+ Bulk watch
|
||||
+ ksonnet v0.10.0-alpha.3
|
||||
+ kubectl apply deployment strategy
|
||||
+ CLI improvements for app management
|
||||
|
||||
## v0.2.0 (2018-04-03)
|
||||
+ Rollback UI
|
||||
+ Override parameters
|
||||
|
||||
## v0.1.0 (2018-03-12)
|
||||
+ Define app in Github with dev and preprod environment using KSonnet
|
||||
+ Add cluster Diff App with a cluster Deploy app in a cluster
|
||||
+ Deploy a new version of the app in the cluster
|
||||
+ App sync based on Github app config change - polling only
|
||||
+ Basic UI: App diff between Git and k8s cluster for all environments Basic GUI
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
## Requirements
|
||||
Make sure you have following tools installed
|
||||
* [docker](https://docs.docker.com/install/#supported-platforms)
|
||||
* [golang](https://golang.org/)
|
||||
* [dep](https://github.com/golang/dep)
|
||||
* [protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [ksonnet](https://github.com/ksonnet/ksonnet#install)
|
||||
* [helm](https://github.com/helm/helm/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/).
|
||||
Make sure you have following tools installed [golang](https://golang.org/), [dep](https://github.com/golang/dep), [protobuf](https://developers.google.com/protocol-buffers/),
|
||||
[kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
|
||||
|
||||
```
|
||||
$ brew tap go-swagger/go-swagger
|
||||
$ brew install go dep protobuf kubectl ksonnet/tap/ks kubernetes-helm jq go-swagger
|
||||
$ brew install go dep protobuf kubectl
|
||||
$ go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
$ go get -u github.com/go-swagger/go-swagger/cmd/swagger
|
||||
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
|
||||
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
|
||||
```
|
||||
|
||||
Nice to have [gometalinter](https://github.com/alecthomas/gometalinter) and [goreman](https://github.com/mattn/goreman):
|
||||
@@ -32,23 +20,6 @@ $ go get -u github.com/argoproj/argo-cd
|
||||
$ dep ensure
|
||||
$ make
|
||||
```
|
||||
NOTE: The make command can take a while, and we recommend building the specific component you are working on
|
||||
* `make cli` - Make the argocd CLI tool
|
||||
* `make server` - Make the API/repo/controller server
|
||||
* `make codegen` - Builds protobuf and swagger files
|
||||
* `make argocd-util` - Make the administrator's utility, used for certain tasks such as import/export
|
||||
|
||||
## Generating ArgoCD manifests for a specific image repository/tag
|
||||
|
||||
During development, the `update-manifests.sh` script, can be used to conveniently regenerate the
|
||||
ArgoCD installation manifests with a customized image namespace and tag. This enables developers
|
||||
to easily apply manifests which are using the images that they pushed into their personal container
|
||||
repository.
|
||||
|
||||
```
|
||||
$ IMAGE_NAMESPACE=jessesuen IMAGE_TAG=latest ./hack/update-manifests.sh
|
||||
$ kubectl apply -n argocd -f ./manifests/install.yaml
|
||||
```
|
||||
|
||||
## Running locally
|
||||
|
||||
@@ -67,10 +38,3 @@ $ kubectl create -f install/manifests/01_application-crd.yaml
|
||||
```
|
||||
$ goreman start
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
* Ensure argocd is installed: ./dist/argocd install
|
||||
* Ensure you're logged in: ./dist/argocd login --username admin --password <whatever password you set at install> localhost:8080
|
||||
* Ensure that roles are configured: kubectl create -f install/manifests/02c_argocd-rbac-cm.yaml
|
||||
* Ensure minikube is running: minikube stop && minikube start
|
||||
* Ensure Argo CD is aware of minikube: ./dist/argocd cluster add minikube
|
||||
|
||||
13
COPYRIGHT
Normal file
@@ -0,0 +1,13 @@
|
||||
Copyright 2017-2018 The Argo Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License..
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:9.4 as builder
|
||||
FROM debian:9.3 as builder
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
@@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Install go
|
||||
ENV GO_VERSION 1.10.3
|
||||
ENV GO_VERSION 1.9.3
|
||||
ENV GO_ARCH amd64
|
||||
ENV GOPATH /root/go
|
||||
ENV PATH ${GOPATH}/bin:/usr/local/go/bin:${PATH}
|
||||
@@ -25,7 +25,7 @@ RUN cd /usr/local && \
|
||||
unzip protoc-*.zip && \
|
||||
wget https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 -O /usr/local/bin/dep && \
|
||||
chmod +x /usr/local/bin/dep && \
|
||||
wget https://github.com/gobuffalo/packr/releases/download/v1.11.0/packr_1.11.0_linux_amd64.tar.gz && \
|
||||
wget https://github.com/gobuffalo/packr/releases/download/v1.10.4/packr_1.10.4_linux_amd64.tar.gz && \
|
||||
tar -vxf packr*.tar.gz -C /tmp/ && \
|
||||
mv /tmp/packr /usr/local/bin/packr
|
||||
|
||||
@@ -58,7 +58,7 @@ FROM golang:1.10 as cli-tooling
|
||||
#RUN go get -v -u github.com/ksonnet/ksonnet && mv ${GOPATH}/bin/ksonnet /ks
|
||||
|
||||
# Option 2: use official tagged ksonnet release
|
||||
env KSONNET_VERSION=0.11.0
|
||||
env KSONNET_VERSION=0.10.2
|
||||
RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION}/ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
|
||||
tar -C /tmp/ -xf ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
|
||||
mv /tmp/ks_${KSONNET_VERSION}_linux_amd64/ks /ks
|
||||
@@ -66,10 +66,6 @@ RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION
|
||||
RUN curl -o /kubectl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
|
||||
chmod +x /kubectl
|
||||
|
||||
env HELM_VERSION=2.9.1
|
||||
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
|
||||
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
|
||||
mv /tmp/linux-amd64/helm /helm
|
||||
|
||||
##############################################################
|
||||
FROM debian:9.3
|
||||
@@ -78,7 +74,6 @@ RUN apt-get update && apt-get install -y git && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY --from=cli-tooling /ks /usr/local/bin/ks
|
||||
COPY --from=cli-tooling /helm /usr/local/bin/helm
|
||||
COPY --from=cli-tooling /kubectl /usr/local/bin/kubectl
|
||||
# workaround ksonnet issue https://github.com/ksonnet/ksonnet/issues/298
|
||||
ENV USER=root
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.10.3
|
||||
FROM golang:1.9.2
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
@@ -15,14 +15,8 @@ RUN curl -o /kubectl -LO https://storage.googleapis.com/kubernetes-release/relea
|
||||
chmod +x /kubectl && mv /kubectl /usr/local/bin/kubectl
|
||||
|
||||
# Install ksonnet
|
||||
env KSONNET_VERSION=0.11.0
|
||||
env KSONNET_VERSION=0.10.2
|
||||
RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION}/ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
|
||||
tar -C /tmp/ -xf ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
|
||||
mv /tmp/ks_${KSONNET_VERSION}_linux_amd64/ks /usr/local/bin/ks && \
|
||||
rm -rf /tmp/ks_${KSONNET_VERSION}
|
||||
|
||||
# Install helm
|
||||
env HELM_VERSION=2.9.1
|
||||
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
|
||||
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
|
||||
mv /tmp/linux-amd64/helm /usr/local/bin/helm
|
||||
|
||||
629
Gopkg.lock
generated
33
Gopkg.toml
@@ -3,9 +3,6 @@ required = [
|
||||
"github.com/gogo/protobuf/protoc-gen-gogofast",
|
||||
"golang.org/x/sync/errgroup",
|
||||
"k8s.io/code-generator/cmd/go-to-protobuf",
|
||||
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway",
|
||||
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger",
|
||||
"github.com/golang/protobuf/protoc-gen-go",
|
||||
]
|
||||
|
||||
[[constraint]]
|
||||
@@ -16,25 +13,25 @@ required = [
|
||||
name = "github.com/grpc-ecosystem/grpc-gateway"
|
||||
version = "v1.3.1"
|
||||
|
||||
# override argo outdated dependency
|
||||
# override ksonnet's release-1.8 dependency
|
||||
[[override]]
|
||||
branch = "release-1.10"
|
||||
name = "k8s.io/api"
|
||||
|
||||
[[override]]
|
||||
branch = "release-1.10"
|
||||
branch = "release-1.9"
|
||||
name = "k8s.io/apimachinery"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/apiextensions-apiserver"
|
||||
branch = "release-1.10"
|
||||
branch = "release-1.9"
|
||||
name = "k8s.io/api"
|
||||
|
||||
[[constraint]]
|
||||
branch = "release-1.10"
|
||||
name = "k8s.io/apiextensions-apiserver"
|
||||
branch = "release-1.9"
|
||||
|
||||
[[constraint]]
|
||||
branch = "release-1.9"
|
||||
name = "k8s.io/code-generator"
|
||||
|
||||
[[override]]
|
||||
branch = "release-7.0"
|
||||
[[constraint]]
|
||||
branch = "release-6.0"
|
||||
name = "k8s.io/client-go"
|
||||
|
||||
[[constraint]]
|
||||
@@ -43,13 +40,9 @@ required = [
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/ksonnet/ksonnet"
|
||||
version = "v0.11.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gobuffalo/packr"
|
||||
version = "v1.11.0"
|
||||
version = "v0.10.1"
|
||||
|
||||
# override ksonnet's logrus dependency
|
||||
[[override]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
revision = "ea8897e79973357ba785ac2533559a6297e83c44"
|
||||
version = "v1.0.3"
|
||||
|
||||
2
LICENSE
@@ -187,7 +187,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017-2018 The Argo Authors
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
34
Makefile
@@ -57,36 +57,31 @@ codegen: protogen clientgen
|
||||
# NOTE: we use packr to do the build instead of go, since we embed .yaml files into the go binary.
|
||||
# This enables ease of maintenance of the yaml files.
|
||||
.PHONY: cli
|
||||
cli: clean-debug
|
||||
${PACKR_CMD} build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/${CLI_NAME} ./cmd/argocd
|
||||
cli:
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/${CLI_NAME} ./cmd/argocd
|
||||
|
||||
.PHONY: cli-linux
|
||||
cli-linux: clean-debug
|
||||
cli-linux:
|
||||
docker build --iidfile /tmp/argocd-linux-id --target builder --build-arg MAKE_TARGET="cli IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-linux-amd64" -f Dockerfile-argocd .
|
||||
docker create --name tmp-argocd-linux `cat /tmp/argocd-linux-id`
|
||||
docker cp tmp-argocd-linux:/root/go/src/github.com/argoproj/argo-cd/dist/argocd-linux-amd64 dist/
|
||||
docker rm tmp-argocd-linux
|
||||
|
||||
.PHONY: cli-darwin
|
||||
cli-darwin: clean-debug
|
||||
cli-darwin:
|
||||
docker build --iidfile /tmp/argocd-darwin-id --target builder --build-arg MAKE_TARGET="cli GOOS=darwin IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-darwin-amd64" -f Dockerfile-argocd .
|
||||
docker create --name tmp-argocd-darwin `cat /tmp/argocd-darwin-id`
|
||||
docker cp tmp-argocd-darwin:/root/go/src/github.com/argoproj/argo-cd/dist/argocd-darwin-amd64 dist/
|
||||
docker rm tmp-argocd-darwin
|
||||
|
||||
.PHONY: argocd-util
|
||||
argocd-util: clean-debug
|
||||
argocd-util:
|
||||
CGO_ENABLED=0 go build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/argocd-util ./cmd/argocd-util
|
||||
|
||||
.PHONY: install-manifest
|
||||
install-manifest:
|
||||
if [ "${IMAGE_NAMESPACE}" = "" ] ; then echo "IMAGE_NAMESPACE must be set to build install manifest" ; exit 1 ; fi
|
||||
./hack/update-manifests.sh
|
||||
|
||||
.PHONY: server
|
||||
server: clean-debug
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd/argocd-server
|
||||
|
||||
server:
|
||||
CGO_ENABLED=0 go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd/argocd-server
|
||||
|
||||
.PHONY: server-image
|
||||
server-image:
|
||||
docker build --build-arg BINARY=argocd-server -t $(IMAGE_PREFIX)argocd-server:$(IMAGE_TAG) -f Dockerfile-argocd .
|
||||
@@ -125,26 +120,21 @@ lint:
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
|
||||
go test `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e:
|
||||
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
|
||||
clean-debug:
|
||||
-find ${CURRENT_DIR} -name debug.test | xargs rm -f
|
||||
go test ./test/e2e
|
||||
|
||||
.PHONY: clean
|
||||
clean: clean-debug
|
||||
clean:
|
||||
-rm -rf ${CURRENT_DIR}/dist
|
||||
|
||||
.PHONY: precheckin
|
||||
precheckin: test lint
|
||||
|
||||
.PHONY: release-precheck
|
||||
release-precheck: install-manifest
|
||||
release-precheck:
|
||||
@if [ "$(GIT_TREE_STATE)" != "clean" ]; then echo 'git tree state is $(GIT_TREE_STATE)' ; exit 1; fi
|
||||
@if [ -z "$(GIT_TAG)" ]; then echo 'commit must be tagged to perform release' ; exit 1; fi
|
||||
|
||||
|
||||
3
Procfile
@@ -1,4 +1,5 @@
|
||||
controller: go run ./cmd/argocd-application-controller/main.go
|
||||
controller: go run ./cmd/argocd-application-controller/main.go --app-resync 10
|
||||
api-server: go run ./cmd/argocd-server/main.go --insecure --disable-auth
|
||||
repo-server: go run ./cmd/argocd-repo-server/main.go --loglevel debug
|
||||
dex: sh -c "go run ./cmd/argocd-util/main.go gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p 5556:5556 -p 5557:5557 -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/coreos/dex:v2.10.0 serve /dex.yaml"
|
||||
redis: docker run --rm -p 6379:6379 redis:3.2.11
|
||||
84
README.md
@@ -1,11 +1,9 @@
|
||||
|
||||
# Argo CD - Declarative Continuous Delivery for Kubernetes
|
||||
# Argo CD - GitOps Continuous Delivery for Kubernetes
|
||||
|
||||
## What is Argo CD?
|
||||
|
||||
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.
|
||||
|
||||

|
||||
Argo CD is a declarative, continuous delivery service based on ksonnet for Kubernetes.
|
||||
|
||||
## Why Argo CD?
|
||||
|
||||
@@ -14,30 +12,25 @@ Application deployment and lifecycle management should be automated, auditable,
|
||||
|
||||
## Getting Started
|
||||
|
||||
Follow our [getting started guide](docs/getting_started.md). Further [documentation](docs/)
|
||||
is provided for additional features.
|
||||
Follow our [getting started guide](docs/getting_started.md).
|
||||
|
||||
## 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:
|
||||
* [ksonnet](https://ksonnet.io) applications
|
||||
* [helm](https://helm.sh) charts
|
||||
* Simple directory of YAML/json manifests
|
||||
Argo CD uses git repositories as the source of truth for defining the desired application state as
|
||||
well as the target deployment environments. Kubernetes manifests are specified as
|
||||
[ksonnet](https://ksonnet.io) applications. Argo CD automates the deployment of the desired
|
||||
application states in the specified target environments.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
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
|
||||
A deployed application whose live state deviates from the target state is considered out-of-sync.
|
||||
Argo CD reports & visualizes the differences as well as 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.
|
||||
@@ -48,22 +41,51 @@ For additional details, see [architecture overview](docs/architecture.md).
|
||||
|
||||
* Automated deployment of applications to specified target environments
|
||||
* Continuous monitoring of deployed applications
|
||||
* Automated or manual syncing of applications to its desired state
|
||||
* Web and CLI based visualization of applications and differences between live vs. desired state
|
||||
* Automated or manual syncing of applications to its target state
|
||||
* Web and CLI based visualization of applications and differences between live vs. target state
|
||||
* Rollback/Roll-anywhere to any application state committed in the git repository
|
||||
* Health assessment statuses on all components of the application
|
||||
* SSO Integration (OIDC, OAuth2, LDAP, SAML 2.0, GitLab, Microsoft, LinkedIn)
|
||||
* SSO Integration (OIDC, LDAP, SAML 2.0, GitLab, Microsoft, LinkedIn)
|
||||
* Webhook Integration (GitHub, BitBucket, GitLab)
|
||||
* PreSync, Sync, PostSync hooks to support complex application rollouts (e.g.blue/green & canary upgrades)
|
||||
* Audit trails for application events and API calls
|
||||
* Parameter overrides for overriding ksonnet/helm parameters in git
|
||||
|
||||
## What is ksonnet?
|
||||
|
||||
* [Jsonnet](http://jsonnet.org), the basis for ksonnet, is a domain specific configuration language,
|
||||
which provides extreme flexibility for composing and manipulating JSON/YAML specifications.
|
||||
* [Ksonnet](http://ksonnet.io) goes one step further by applying Jsonnet principles to Kubernetes
|
||||
manifests. It provides an opinionated file & directory structure to organize applications into
|
||||
reusable components, parameters, and environments. Environments can be hierarchical, which promotes
|
||||
both re-use and granular customization of application and environment specifications.
|
||||
|
||||
## Why ksonnet?
|
||||
|
||||
Application configuration management is a hard problem and grows rapidly in complexity as you deploy
|
||||
more applications, against more and more environments. Current templating systems, such as Jinja,
|
||||
and Golang templating, are unnatural ways to maintain kubernetes manifests, and are not well suited to
|
||||
capture subtle configuration differences between environments. Its ability to compose and re-use
|
||||
application and environment configurations is also very limited.
|
||||
|
||||
Imagine we have a single guestbook application deployed in following environments:
|
||||
|
||||
| Environment | K8s Version | Application Image | DB Connection String | Environment Vars | Sidecars |
|
||||
|---------------|-------------|------------------------|-----------------------|------------------|---------------|
|
||||
| minikube | 1.10.0 | jesse/guestbook:latest | sql://locahost/db | DEBUG=true | |
|
||||
| dev | 1.9.0 | app/guestbook:latest | sql://dev-test/db | DEBUG=true | |
|
||||
| staging | 1.8.0 | app/guestbook:e3c0263 | sql://staging/db | | istio,dnsmasq |
|
||||
| us-west-1 | 1.8.0 | app/guestbook:abc1234 | sql://prod/db | FOO_FEATURE=true | istio,dnsmasq |
|
||||
| us-west-2 | 1.8.0 | app/guestbook:abc1234 | sql://prod/db | | istio,dnsmasq |
|
||||
| us-east-1 | 1.9.0 | app/guestbook:abc1234 | sql://prod/db | BAR_FEATURE=true | istio,dnsmasq |
|
||||
|
||||
Ksonnet:
|
||||
* Enables composition and re-use of common YAML specifications
|
||||
* Allows overrides, additions, and subtractions of YAML sub-components specific to each environment
|
||||
* Guarantees proper generation of K8s manifests suitable for the corresponding Kubernetes API version
|
||||
* Provides [kubernetes-specific jsonnet libraries](https://github.com/ksonnet/ksonnet-lib) to enable
|
||||
concise definition of kubernetes manifests
|
||||
|
||||
## Development Status
|
||||
* Argo CD is being used in production to deploy SaaS services at Intuit
|
||||
* Argo CD is in early development
|
||||
|
||||
## Roadmap
|
||||
* Auto-sync toggle to directly apply git state changes to live state
|
||||
* Service account/access key management for CI pipelines
|
||||
* Support for additional config management tools (Kustomize?)
|
||||
* Revamped UI, and feature parity with CLI
|
||||
* Customizable application actions
|
||||
* PreSync, PostSync, OutOfSync hooks
|
||||
* Customized application actions as Argo workflows
|
||||
* Blue/Green & canary upgrades
|
||||
|
||||
@@ -8,23 +8,22 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
// load the gcp plugin (required to authenticate against GKE clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// load the oidc plugin (required to authenticate with OpenID Connect).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
|
||||
"github.com/argoproj/argo-cd"
|
||||
"github.com/argoproj/argo-cd/controller"
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/reposerver"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/stats"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
// load the gcp plugin (required to authenticate against GKE clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// load the oidc plugin (required to authenticate with OpenID Connect).
|
||||
"github.com/argoproj/argo-cd/reposerver"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -73,29 +72,23 @@ func newCommand() *cobra.Command {
|
||||
}
|
||||
db := db.NewDB(namespace, kubeClient)
|
||||
resyncDuration := time.Duration(appResyncPeriod) * time.Second
|
||||
repoClientset := reposerver.NewRepositoryServerClientset(repoServerAddress)
|
||||
appStateManager := controller.NewAppStateManager(db, appClient, repoClientset, namespace)
|
||||
appStateManager := controller.NewAppStateManager(db, appClient, reposerver.NewRepositoryServerClientset(repoServerAddress), namespace)
|
||||
appHealthManager := controller.NewAppHealthManager(db, namespace)
|
||||
|
||||
appController := controller.NewApplicationController(
|
||||
namespace,
|
||||
kubeClient,
|
||||
appClient,
|
||||
repoClientset,
|
||||
db,
|
||||
appStateManager,
|
||||
appHealthManager,
|
||||
resyncDuration,
|
||||
&controllerConfig)
|
||||
secretController := controller.NewSecretController(kubeClient, repoClientset, resyncDuration, namespace)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
log.Infof("Application Controller (version: %s) starting (namespace: %s)", argocd.GetVersion(), namespace)
|
||||
stats.RegisterStackDumper()
|
||||
stats.StartStatsTicker(10 * time.Minute)
|
||||
stats.RegisterHeapDumper("memprofile")
|
||||
|
||||
go secretController.Run(ctx)
|
||||
go appController.Run(ctx, statusProcessors, operationProcessors)
|
||||
// Wait forever
|
||||
select {}
|
||||
|
||||
@@ -4,10 +4,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo-cd"
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
@@ -16,7 +12,9 @@ import (
|
||||
"github.com/argoproj/argo-cd/util/cache"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
"github.com/argoproj/argo-cd/util/ksonnet"
|
||||
"github.com/argoproj/argo-cd/util/stats"
|
||||
"github.com/go-redis/redis"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,9 +45,6 @@ func newCommand() *cobra.Command {
|
||||
|
||||
log.Infof("argocd-repo-server %s serving on %s", argocd.GetVersion(), listener.Addr())
|
||||
log.Infof("ksonnet version: %s", ksVers)
|
||||
stats.RegisterStackDumper()
|
||||
stats.StartStatsTicker(10 * time.Minute)
|
||||
stats.RegisterHeapDumper("memprofile")
|
||||
err = grpc.Serve(listener)
|
||||
errors.CheckError(err)
|
||||
return nil
|
||||
@@ -61,13 +56,13 @@ func newCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
func newCache() cache.Cache {
|
||||
return cache.NewInMemoryCache(repository.DefaultRepoCacheExpiration)
|
||||
// client := redis.NewClient(&redis.Options{
|
||||
// Addr: "localhost:6379",
|
||||
// Password: "",
|
||||
// DB: 0,
|
||||
// })
|
||||
// return cache.NewRedisCache(client, repository.DefaultRepoCacheExpiration)
|
||||
//return cache.NewInMemoryCache(repository.DefaultRepoCacheExpiration)
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "",
|
||||
DB: 0,
|
||||
})
|
||||
return cache.NewRedisCache(client, repository.DefaultRepoCacheExpiration)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -2,21 +2,16 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/reposerver"
|
||||
"github.com/argoproj/argo-cd/server"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/stats"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// NewCommand returns a new instance of an argocd command
|
||||
@@ -24,7 +19,6 @@ func NewCommand() *cobra.Command {
|
||||
var (
|
||||
insecure bool
|
||||
logLevel string
|
||||
glogLevel int
|
||||
clientConfig clientcmd.ClientConfig
|
||||
staticAssetsDir string
|
||||
repoServerAddress string
|
||||
@@ -39,11 +33,6 @@ func NewCommand() *cobra.Command {
|
||||
errors.CheckError(err)
|
||||
log.SetLevel(level)
|
||||
|
||||
// Set the glog level for the k8s go-client
|
||||
_ = flag.CommandLine.Parse([]string{})
|
||||
_ = flag.Lookup("logtostderr").Value.Set("true")
|
||||
_ = flag.Lookup("v").Value.Set(strconv.Itoa(glogLevel))
|
||||
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
|
||||
@@ -63,13 +52,9 @@ func NewCommand() *cobra.Command {
|
||||
RepoClientset: repoclientset,
|
||||
DisableAuth: disableAuth,
|
||||
}
|
||||
|
||||
stats.RegisterStackDumper()
|
||||
stats.StartStatsTicker(10 * time.Minute)
|
||||
stats.RegisterHeapDumper("memprofile")
|
||||
argocd := server.NewServer(argoCDOpts)
|
||||
|
||||
for {
|
||||
argocd := server.NewServer(argoCDOpts)
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
argocd.Run(ctx, 8080)
|
||||
@@ -82,7 +67,6 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().BoolVar(&insecure, "insecure", false, "Run server without TLS")
|
||||
command.Flags().StringVar(&staticAssetsDir, "staticassets", "", "Static assets directory path")
|
||||
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().IntVar(&glogLevel, "gloglevel", 0, "Set the glog logging level")
|
||||
command.Flags().StringVar(&repoServerAddress, "repo-server", "localhost:8081", "Repo server address.")
|
||||
command.Flags().BoolVar(&disableAuth, "disable-auth", false, "Disable client authentication")
|
||||
command.AddCommand(cli.NewVersionCmd(cliName))
|
||||
|
||||
@@ -6,22 +6,14 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/dex"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
@@ -34,9 +26,6 @@ import (
|
||||
const (
|
||||
// CLIName is the name of the CLI
|
||||
cliName = "argocd-util"
|
||||
|
||||
// YamlSeparator separates sections of a YAML file
|
||||
yamlSeparator = "\n---\n"
|
||||
)
|
||||
|
||||
// NewCommand returns a new instance of an argocd command
|
||||
@@ -56,9 +45,6 @@ func NewCommand() *cobra.Command {
|
||||
command.AddCommand(cli.NewVersionCmd(cliName))
|
||||
command.AddCommand(NewRunDexCommand())
|
||||
command.AddCommand(NewGenDexConfigCommand())
|
||||
command.AddCommand(NewImportCommand())
|
||||
command.AddCommand(NewExportCommand())
|
||||
command.AddCommand(NewSettingsCommand())
|
||||
|
||||
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
return command
|
||||
@@ -168,214 +154,6 @@ func NewGenDexConfigCommand() *cobra.Command {
|
||||
return &command
|
||||
}
|
||||
|
||||
// NewImportCommand defines a new command for exporting Kubernetes and Argo CD resources.
|
||||
func NewImportCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "import SOURCE",
|
||||
Short: "Import Argo CD data from stdin (specify `-') or a file",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var (
|
||||
input []byte
|
||||
err error
|
||||
newSettings *settings.ArgoCDSettings
|
||||
newRepos []*v1alpha1.Repository
|
||||
newClusters []*v1alpha1.Cluster
|
||||
newApps []*v1alpha1.Application
|
||||
newRBACCM *apiv1.ConfigMap
|
||||
)
|
||||
|
||||
if in := args[0]; in == "-" {
|
||||
input, err = ioutil.ReadAll(os.Stdin)
|
||||
errors.CheckError(err)
|
||||
} else {
|
||||
input, err = ioutil.ReadFile(in)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
inputStrings := strings.Split(string(input), yamlSeparator)
|
||||
|
||||
err = yaml.Unmarshal([]byte(inputStrings[0]), &newSettings)
|
||||
errors.CheckError(err)
|
||||
|
||||
err = yaml.Unmarshal([]byte(inputStrings[1]), &newRepos)
|
||||
errors.CheckError(err)
|
||||
|
||||
err = yaml.Unmarshal([]byte(inputStrings[2]), &newClusters)
|
||||
errors.CheckError(err)
|
||||
|
||||
err = yaml.Unmarshal([]byte(inputStrings[3]), &newApps)
|
||||
errors.CheckError(err)
|
||||
|
||||
err = yaml.Unmarshal([]byte(inputStrings[4]), &newRBACCM)
|
||||
errors.CheckError(err)
|
||||
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(config)
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(kubeClientset, namespace)
|
||||
err = settingsMgr.SaveSettings(newSettings)
|
||||
errors.CheckError(err)
|
||||
db := db.NewDB(namespace, kubeClientset)
|
||||
|
||||
_, err = kubeClientset.CoreV1().ConfigMaps(namespace).Create(newRBACCM)
|
||||
errors.CheckError(err)
|
||||
|
||||
for _, repo := range newRepos {
|
||||
_, err := db.CreateRepository(context.Background(), repo)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, cluster := range newClusters {
|
||||
_, err := db.CreateCluster(context.Background(), cluster)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
appClientset := appclientset.NewForConfigOrDie(config)
|
||||
for _, app := range newApps {
|
||||
out, err := appClientset.ArgoprojV1alpha1().Applications(namespace).Create(app)
|
||||
errors.CheckError(err)
|
||||
log.Println(out)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
|
||||
return &command
|
||||
}
|
||||
|
||||
// NewExportCommand defines a new command for exporting Kubernetes and Argo CD resources.
|
||||
func NewExportCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
out string
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Export all Argo CD data to stdout (default) or a file",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(config)
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(kubeClientset, namespace)
|
||||
settings, err := settingsMgr.GetSettings()
|
||||
errors.CheckError(err)
|
||||
// certificate data is included in secrets that are exported alongside
|
||||
settings.Certificate = nil
|
||||
|
||||
db := db.NewDB(namespace, kubeClientset)
|
||||
clusters, err := db.ListClusters(context.Background())
|
||||
errors.CheckError(err)
|
||||
|
||||
repos, err := db.ListRepositories(context.Background())
|
||||
errors.CheckError(err)
|
||||
|
||||
appClientset := appclientset.NewForConfigOrDie(config)
|
||||
apps, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(metav1.ListOptions{})
|
||||
errors.CheckError(err)
|
||||
|
||||
rbacCM, err := kubeClientset.CoreV1().ConfigMaps(namespace).Get(common.ArgoCDRBACConfigMapName, metav1.GetOptions{})
|
||||
errors.CheckError(err)
|
||||
|
||||
// remove extraneous cruft from output
|
||||
rbacCM.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: rbacCM.ObjectMeta.Name,
|
||||
}
|
||||
|
||||
// remove extraneous cruft from output
|
||||
for idx, app := range apps.Items {
|
||||
apps.Items[idx].ObjectMeta = metav1.ObjectMeta{
|
||||
Name: app.ObjectMeta.Name,
|
||||
Finalizers: app.ObjectMeta.Finalizers,
|
||||
}
|
||||
apps.Items[idx].Status = v1alpha1.ApplicationStatus{
|
||||
History: app.Status.History,
|
||||
}
|
||||
apps.Items[idx].Operation = nil
|
||||
}
|
||||
|
||||
// take a list of exportable objects, marshal them to YAML,
|
||||
// and return a string joined by a delimiter
|
||||
output := func(delimiter string, oo ...interface{}) string {
|
||||
out := make([]string, 0)
|
||||
for _, o := range oo {
|
||||
data, err := yaml.Marshal(o)
|
||||
errors.CheckError(err)
|
||||
out = append(out, string(data))
|
||||
}
|
||||
return strings.Join(out, delimiter)
|
||||
}(yamlSeparator, settings, repos.Items, clusters.Items, apps.Items, rbacCM)
|
||||
|
||||
if out == "-" {
|
||||
fmt.Println(output)
|
||||
} else {
|
||||
err = ioutil.WriteFile(out, []byte(output), 0644)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
command.Flags().StringVarP(&out, "out", "o", "-", "Output to the specified file instead of stdout")
|
||||
|
||||
return &command
|
||||
}
|
||||
|
||||
// NewSettingsCommand returns a new instance of `argocd-util settings` command
|
||||
func NewSettingsCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
updateSuperuser bool
|
||||
superuserPassword string
|
||||
updateSignature bool
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "settings",
|
||||
Short: "Creates or updates ArgoCD settings",
|
||||
Long: "Creates or updates ArgoCD settings",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
conf, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, wasSpecified, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
if !(wasSpecified) {
|
||||
namespace = "argocd"
|
||||
}
|
||||
|
||||
kubeclientset, err := kubernetes.NewForConfig(conf)
|
||||
errors.CheckError(err)
|
||||
settingsMgr := settings.NewSettingsManager(kubeclientset, namespace)
|
||||
|
||||
_ = settings.UpdateSettings(superuserPassword, settingsMgr, updateSignature, updateSuperuser, namespace)
|
||||
},
|
||||
}
|
||||
command.Flags().BoolVar(&updateSuperuser, "update-superuser", false, "force updating the superuser password")
|
||||
command.Flags().StringVar(&superuserPassword, "superuser-password", "", "password for super user")
|
||||
command.Flags().BoolVar(&updateSignature, "update-signature", false, "force updating the server-side token signing signature")
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(command)
|
||||
return command
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := NewCommand().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/server/account"
|
||||
"github.com/argoproj/argo-cd/util"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func NewAccountCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "account",
|
||||
Short: "Manage account settings",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
},
|
||||
}
|
||||
command.AddCommand(NewAccountUpdatePasswordCommand(clientOpts))
|
||||
return command
|
||||
}
|
||||
|
||||
func NewAccountUpdatePasswordCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
currentPassword string
|
||||
newPassword string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "update-password",
|
||||
Short: "Update password",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 0 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if currentPassword == "" {
|
||||
fmt.Print("*** Enter current password: ")
|
||||
password, err := terminal.ReadPassword(syscall.Stdin)
|
||||
errors.CheckError(err)
|
||||
currentPassword = string(password)
|
||||
fmt.Print("\n")
|
||||
}
|
||||
if newPassword == "" {
|
||||
newPassword = settings.ReadAndConfirmPassword()
|
||||
}
|
||||
|
||||
updatePasswordRequest := account.UpdatePasswordRequest{
|
||||
NewPassword: newPassword,
|
||||
CurrentPassword: currentPassword,
|
||||
}
|
||||
|
||||
conn, usrIf := argocdclient.NewClientOrDie(clientOpts).NewAccountClientOrDie()
|
||||
defer util.Close(conn)
|
||||
_, err := usrIf.UpdatePassword(context.Background(), &updatePasswordRequest)
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("Password updated\n")
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVar(¤tPassword, "current-password", "", "current password you wish to change")
|
||||
command.Flags().StringVar(&newPassword, "new-password", "", "new password you want to update to")
|
||||
return command
|
||||
}
|
||||
@@ -42,10 +42,6 @@ func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientc
|
||||
|
||||
// NewClusterAddCommand returns a new instance of an `argocd cluster add` command
|
||||
func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command {
|
||||
var (
|
||||
inCluster bool
|
||||
upsert bool
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "add",
|
||||
Short: fmt.Sprintf("%s cluster add CONTEXT", cliName),
|
||||
@@ -75,21 +71,12 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
|
||||
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
|
||||
defer util.Close(conn)
|
||||
clst := NewCluster(args[0], conf, managerBearerToken)
|
||||
if inCluster {
|
||||
clst.Server = common.KubernetesInternalAPIServerAddr
|
||||
}
|
||||
clstCreateReq := cluster.ClusterCreateRequest{
|
||||
Cluster: clst,
|
||||
Upsert: upsert,
|
||||
}
|
||||
clst, err = clusterIf.Create(context.Background(), &clstCreateReq)
|
||||
clst, err = clusterIf.Create(context.Background(), clst)
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("Cluster '%s' added\n", clst.Name)
|
||||
},
|
||||
}
|
||||
command.PersistentFlags().StringVar(&pathOpts.LoadingRules.ExplicitPath, pathOpts.ExplicitFileFlag, pathOpts.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
|
||||
command.Flags().BoolVar(&inCluster, "in-cluster", false, "Indicates ArgoCD resides inside this cluster and should connect using the internal k8s hostname (kubernetes.default.svc)")
|
||||
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing cluster with the same name even if the spec differs")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -109,20 +96,9 @@ func printKubeContexts(ca clientcmd.ConfigAccess) {
|
||||
}
|
||||
sort.Strings(contextNames)
|
||||
|
||||
if config.Clusters == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range contextNames {
|
||||
// ignore malformed kube config entries
|
||||
context := config.Contexts[name]
|
||||
if context == nil {
|
||||
continue
|
||||
}
|
||||
cluster := config.Clusters[context.Cluster]
|
||||
if cluster == nil {
|
||||
continue
|
||||
}
|
||||
prefix := " "
|
||||
if config.CurrentContext == name {
|
||||
prefix = "*"
|
||||
@@ -224,9 +200,9 @@ func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
clusters, err := clusterIf.List(context.Background(), &cluster.ClusterQuery{})
|
||||
errors.CheckError(err)
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintf(w, "SERVER\tNAME\tSTATUS\tMESSAGE\n")
|
||||
fmt.Fprintf(w, "SERVER\tNAME\tMESSAGE\n")
|
||||
for _, c := range clusters.Items {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", c.Server, c.Name, c.ConnectionState.Status, c.ConnectionState.Message)
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", c.Server, c.Name, c.Message)
|
||||
}
|
||||
_ = w.Flush()
|
||||
},
|
||||
|
||||
75
cmd/argocd/commands/install.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
"github.com/argoproj/argo-cd/install"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// NewInstallCommand returns a new instance of `argocd install` command
|
||||
func NewInstallCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
installOpts install.InstallOptions
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Install Argo CD",
|
||||
Long: "Install Argo CD",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
conf, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, wasSpecified, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
if wasSpecified {
|
||||
installOpts.Namespace = namespace
|
||||
}
|
||||
installer, err := install.NewInstaller(conf, installOpts)
|
||||
errors.CheckError(err)
|
||||
installer.Install()
|
||||
},
|
||||
}
|
||||
command.Flags().BoolVar(&installOpts.Upgrade, "upgrade", false, "upgrade controller/ui deployments and configmap if already installed")
|
||||
command.Flags().BoolVar(&installOpts.DryRun, "dry-run", false, "print the kubernetes manifests to stdout instead of installing")
|
||||
command.Flags().StringVar(&installOpts.SuperuserPassword, "superuser-password", "", "password for super user")
|
||||
command.Flags().StringVar(&installOpts.ControllerImage, "controller-image", install.DefaultControllerImage, "use a specified controller image")
|
||||
command.Flags().StringVar(&installOpts.ServerImage, "server-image", install.DefaultServerImage, "use a specified api server image")
|
||||
command.Flags().StringVar(&installOpts.UIImage, "ui-image", install.DefaultUIImage, "use a specified ui image")
|
||||
command.Flags().StringVar(&installOpts.RepoServerImage, "repo-server-image", install.DefaultRepoServerImage, "use a specified repo server image")
|
||||
command.Flags().StringVar(&installOpts.ImagePullPolicy, "image-pull-policy", "", "set the image pull policy of the pod specs")
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(command)
|
||||
command.AddCommand(newSettingsCommand())
|
||||
return command
|
||||
}
|
||||
|
||||
// newSettingsCommand returns a new instance of `argocd install settings` command
|
||||
func newSettingsCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
installOpts install.InstallOptions
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "settings",
|
||||
Short: "Creates or updates ArgoCD settings",
|
||||
Long: "Creates or updates ArgoCD settings",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
conf, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, wasSpecified, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
if wasSpecified {
|
||||
installOpts.Namespace = namespace
|
||||
}
|
||||
installer, err := install.NewInstaller(conf, installOpts)
|
||||
errors.CheckError(err)
|
||||
installer.InstallSettings()
|
||||
},
|
||||
}
|
||||
command.Flags().BoolVar(&installOpts.UpdateSuperuser, "update-superuser", false, "force updating the superuser password")
|
||||
command.Flags().StringVar(&installOpts.SuperuserPassword, "superuser-password", "", "password for super user")
|
||||
command.Flags().BoolVar(&installOpts.UpdateSignature, "update-signature", false, "force updating the server-side token signing signature")
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(command)
|
||||
return command
|
||||
}
|
||||
@@ -77,7 +77,6 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
|
||||
// Perform the login
|
||||
var tokenString string
|
||||
var refreshToken string
|
||||
if !sso {
|
||||
tokenString = passwordLogin(acdClient, username, password)
|
||||
} else {
|
||||
@@ -86,7 +85,15 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
if !ssoConfigured(acdSet) {
|
||||
log.Fatalf("ArgoCD instance is not configured with SSO")
|
||||
}
|
||||
tokenString, refreshToken = oauth2Login(server, clientOpts.PlainText)
|
||||
tokenString = oauth2Login(server, clientOpts.PlainText)
|
||||
// The token which we just received from the OAuth2 flow, was from dex. ArgoCD
|
||||
// currently does not back dex with any kind of persistent storage (it is run
|
||||
// in-memory). As a result, this token cannot be used in any permanent capacity.
|
||||
// Restarts of dex will result in a different signing key, and sessions becoming
|
||||
// invalid. Instead we turn-around and ask ArgoCD to re-sign the token (who *does*
|
||||
// have persistence of signing keys), and is what we store in the config. Should we
|
||||
// ever decide to have a database layer for dex, the next line can be removed.
|
||||
tokenString = tokenLogin(acdClient, tokenString)
|
||||
}
|
||||
|
||||
parser := &jwt.Parser{
|
||||
@@ -109,9 +116,8 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
Insecure: globalClientOpts.Insecure,
|
||||
})
|
||||
localCfg.UpsertUser(localconfig.User{
|
||||
Name: ctxName,
|
||||
AuthToken: tokenString,
|
||||
RefreshToken: refreshToken,
|
||||
Name: ctxName,
|
||||
AuthToken: tokenString,
|
||||
})
|
||||
if ctxName == "" {
|
||||
ctxName = server
|
||||
@@ -157,9 +163,8 @@ func getFreePort() (int, error) {
|
||||
return ln.Addr().(*net.TCPAddr).Port, ln.Close()
|
||||
}
|
||||
|
||||
// oauth2Login opens a browser, runs a temporary HTTP server to delegate OAuth2 login flow and
|
||||
// returns the JWT token and a refresh token (if supported)
|
||||
func oauth2Login(host string, plaintext bool) (string, string) {
|
||||
// oauth2Login opens a browser, runs a temporary HTTP server to delegate OAuth2 login flow and returns the JWT token
|
||||
func oauth2Login(host string, plaintext bool) string {
|
||||
ctx := context.Background()
|
||||
port, err := getFreePort()
|
||||
errors.CheckError(err)
|
||||
@@ -178,7 +183,6 @@ func oauth2Login(host string, plaintext bool) (string, string) {
|
||||
}
|
||||
srv := &http.Server{Addr: ":" + strconv.Itoa(port)}
|
||||
var tokenString string
|
||||
var refreshToken string
|
||||
loginCompleted := make(chan struct{})
|
||||
|
||||
callbackHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -211,9 +215,8 @@ func oauth2Login(host string, plaintext bool) (string, string) {
|
||||
log.Fatal(errMsg)
|
||||
return
|
||||
}
|
||||
refreshToken, _ = tok.Extra("refresh_token").(string)
|
||||
|
||||
log.Debugf("Token: %s", tokenString)
|
||||
log.Debugf("Refresh Token: %s", tokenString)
|
||||
successPage := `
|
||||
<div style="height:100px; width:100%!; display:flex; flex-direction: column; justify-content: center; align-items:center; background-color:#2ecc71; color:white; font-size:22"><div>Authentication successful!</div></div>
|
||||
<p style="margin-top:20px; font-size:18; text-align:center">Authentication was successful, you can now return to CLI. This page will close automatically</p>
|
||||
@@ -245,7 +248,7 @@ func oauth2Login(host string, plaintext bool) (string, string) {
|
||||
}()
|
||||
<-loginCompleted
|
||||
_ = srv.Shutdown(ctx)
|
||||
return tokenString, refreshToken
|
||||
return tokenString
|
||||
}
|
||||
|
||||
func passwordLogin(acdClient argocdclient.Client, username, password string) string {
|
||||
@@ -260,3 +263,14 @@ func passwordLogin(acdClient argocdclient.Client, username, password string) str
|
||||
errors.CheckError(err)
|
||||
return createdSession.Token
|
||||
}
|
||||
|
||||
func tokenLogin(acdClient argocdclient.Client, token string) string {
|
||||
sessConn, sessionIf := acdClient.NewSessionClientOrDie()
|
||||
defer util.Close(sessConn)
|
||||
sessionRequest := session.SessionCreateRequest{
|
||||
Token: token,
|
||||
}
|
||||
createdSession, err := sessionIf.Create(context.Background(), &sessionRequest)
|
||||
errors.CheckError(err)
|
||||
return createdSession.Token
|
||||
}
|
||||
|
||||
@@ -1,335 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"strings"
|
||||
|
||||
"context"
|
||||
|
||||
"fmt"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/server/project"
|
||||
"github.com/argoproj/argo-cd/util"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type projectOpts struct {
|
||||
description string
|
||||
destinations []string
|
||||
sources []string
|
||||
}
|
||||
|
||||
func (opts *projectOpts) GetDestinations() []v1alpha1.ApplicationDestination {
|
||||
destinations := make([]v1alpha1.ApplicationDestination, 0)
|
||||
for _, destStr := range opts.destinations {
|
||||
parts := strings.Split(destStr, ",")
|
||||
if len(parts) != 2 {
|
||||
log.Fatalf("Expected destination of the form: server,namespace. Received: %s", destStr)
|
||||
} else {
|
||||
destinations = append(destinations, v1alpha1.ApplicationDestination{
|
||||
Server: parts[0],
|
||||
Namespace: parts[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
return destinations
|
||||
}
|
||||
|
||||
// NewProjectCommand returns a new instance of an `argocd proj` command
|
||||
func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "proj",
|
||||
Short: "Manage projects",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
},
|
||||
}
|
||||
command.AddCommand(NewProjectCreateCommand(clientOpts))
|
||||
command.AddCommand(NewProjectDeleteCommand(clientOpts))
|
||||
command.AddCommand(NewProjectListCommand(clientOpts))
|
||||
command.AddCommand(NewProjectSetCommand(clientOpts))
|
||||
command.AddCommand(NewProjectAddDestinationCommand(clientOpts))
|
||||
command.AddCommand(NewProjectRemoveDestinationCommand(clientOpts))
|
||||
command.AddCommand(NewProjectAddSourceCommand(clientOpts))
|
||||
command.AddCommand(NewProjectRemoveSourceCommand(clientOpts))
|
||||
return command
|
||||
}
|
||||
|
||||
func addProjFlags(command *cobra.Command, opts *projectOpts) {
|
||||
command.Flags().StringVarP(&opts.description, "description", "", "desc", "Project description")
|
||||
command.Flags().StringArrayVarP(&opts.destinations, "dest", "d", []string{},
|
||||
"Allowed deployment destination. Includes comma separated server url and namespace (e.g. https://192.168.99.100:8443,default")
|
||||
command.Flags().StringArrayVarP(&opts.sources, "src", "s", []string{}, "Allowed deployment source repository URL.")
|
||||
}
|
||||
|
||||
// NewProjectCreateCommand returns a new instance of an `argocd proj create` command
|
||||
func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
opts projectOpts
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "create PROJECT",
|
||||
Short: "Create a project",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
proj := v1alpha1.AppProject{
|
||||
ObjectMeta: v1.ObjectMeta{Name: projName},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
Description: opts.description,
|
||||
Destinations: opts.GetDestinations(),
|
||||
SourceRepos: opts.sources,
|
||||
},
|
||||
}
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer util.Close(conn)
|
||||
|
||||
_, err := projIf.Create(context.Background(), &project.ProjectCreateRequest{Project: &proj})
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
addProjFlags(command, &opts)
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectSetCommand returns a new instance of an `argocd proj set` command
|
||||
func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
opts projectOpts
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "set PROJECT",
|
||||
Short: "Set project parameters",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer util.Close(conn)
|
||||
|
||||
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
|
||||
visited := 0
|
||||
c.Flags().Visit(func(f *pflag.Flag) {
|
||||
visited++
|
||||
switch f.Name {
|
||||
case "description":
|
||||
proj.Spec.Description = opts.description
|
||||
case "dest":
|
||||
proj.Spec.Destinations = opts.GetDestinations()
|
||||
case "src":
|
||||
proj.Spec.SourceRepos = opts.sources
|
||||
}
|
||||
})
|
||||
if visited == 0 {
|
||||
log.Error("Please set at least one option to update")
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
addProjFlags(command, &opts)
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectAddDestinationCommand returns a new instance of an `argocd proj add-destination` command
|
||||
func NewProjectAddDestinationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "add-destination PROJECT SERVER NAMESPACE",
|
||||
Short: "Add project destination",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
server := args[1]
|
||||
namespace := args[2]
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer util.Close(conn)
|
||||
|
||||
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
|
||||
for _, dest := range proj.Spec.Destinations {
|
||||
if dest.Namespace == namespace && dest.Server == server {
|
||||
log.Fatal("Specified destination is already defined in project")
|
||||
}
|
||||
}
|
||||
proj.Spec.Destinations = append(proj.Spec.Destinations, v1alpha1.ApplicationDestination{Server: server, Namespace: namespace})
|
||||
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectRemoveDestinationCommand returns a new instance of an `argocd proj remove-destination` command
|
||||
func NewProjectRemoveDestinationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "remove-destination PROJECT SERVER NAMESPACE",
|
||||
Short: "Remove project destination",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
server := args[1]
|
||||
namespace := args[2]
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer util.Close(conn)
|
||||
|
||||
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
|
||||
index := -1
|
||||
for i, dest := range proj.Spec.Destinations {
|
||||
if dest.Namespace == namespace && dest.Server == server {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
log.Fatal("Specified destination does not exist in project")
|
||||
} else {
|
||||
proj.Spec.Destinations = append(proj.Spec.Destinations[:index], proj.Spec.Destinations[index+1:]...)
|
||||
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
|
||||
errors.CheckError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectAddSourceCommand returns a new instance of an `argocd proj add-src` command
|
||||
func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "add-source PROJECT URL",
|
||||
Short: "Add project source repository",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
url := args[1]
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer util.Close(conn)
|
||||
|
||||
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
|
||||
for _, item := range proj.Spec.SourceRepos {
|
||||
if item == git.NormalizeGitURL(url) {
|
||||
log.Fatal("Specified source repository is already defined in project")
|
||||
}
|
||||
}
|
||||
proj.Spec.SourceRepos = append(proj.Spec.SourceRepos, url)
|
||||
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectRemoveSourceCommand returns a new instance of an `argocd proj remove-src` command
|
||||
func NewProjectRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "remove-source PROJECT URL",
|
||||
Short: "Remove project source repository",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
url := args[1]
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer util.Close(conn)
|
||||
|
||||
proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
|
||||
index := -1
|
||||
for i, item := range proj.Spec.SourceRepos {
|
||||
if item == git.NormalizeGitURL(url) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
log.Fatal("Specified source repository does not exist in project")
|
||||
} else {
|
||||
proj.Spec.SourceRepos = append(proj.Spec.SourceRepos[:index], proj.Spec.SourceRepos[index+1:]...)
|
||||
_, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj})
|
||||
errors.CheckError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectDeleteCommand returns a new instance of an `argocd proj delete` command
|
||||
func NewProjectDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "delete PROJECT",
|
||||
Short: "Delete project",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer util.Close(conn)
|
||||
for _, name := range args {
|
||||
_, err := projIf.Delete(context.Background(), &project.ProjectQuery{Name: name})
|
||||
errors.CheckError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectListCommand returns a new instance of an `argocd proj list` command
|
||||
func NewProjectListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List projects",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer util.Close(conn)
|
||||
projects, err := projIf.List(context.Background(), &project.ProjectQuery{})
|
||||
errors.CheckError(err)
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\tDESTINATIONS\n")
|
||||
for _, p := range projects.Items {
|
||||
fmt.Fprintf(w, "%s\t%s\t%v\n", p.Name, p.Spec.Description, p.Spec.Destinations)
|
||||
}
|
||||
_ = w.Flush()
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/util/localconfig"
|
||||
"github.com/argoproj/argo-cd/util/session"
|
||||
)
|
||||
|
||||
// NewReloginCommand returns a new instance of `argocd relogin` command
|
||||
func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
password string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "relogin",
|
||||
Short: "Refresh an expired authenticate token",
|
||||
Long: "Refresh an expired authenticate token",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 0 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
|
||||
errors.CheckError(err)
|
||||
if localCfg == nil {
|
||||
log.Fatalf("No context found. Login using `argocd login`")
|
||||
}
|
||||
configCtx, err := localCfg.ResolveContext(localCfg.CurrentContext)
|
||||
errors.CheckError(err)
|
||||
|
||||
parser := &jwt.Parser{
|
||||
SkipClaimsValidation: true,
|
||||
}
|
||||
claims := jwt.StandardClaims{}
|
||||
_, _, err = parser.ParseUnverified(configCtx.User.AuthToken, &claims)
|
||||
errors.CheckError(err)
|
||||
|
||||
var tokenString string
|
||||
var refreshToken string
|
||||
if claims.Issuer == session.SessionManagerClaimsIssuer {
|
||||
clientOpts := argocdclient.ClientOptions{
|
||||
ConfigPath: "",
|
||||
ServerAddr: configCtx.Server.Server,
|
||||
Insecure: configCtx.Server.Insecure,
|
||||
PlainText: configCtx.Server.PlainText,
|
||||
}
|
||||
acdClient := argocdclient.NewClientOrDie(&clientOpts)
|
||||
fmt.Printf("Relogging in as '%s'\n", claims.Subject)
|
||||
tokenString = passwordLogin(acdClient, claims.Subject, password)
|
||||
} else {
|
||||
fmt.Println("Reinitiating SSO login")
|
||||
tokenString, refreshToken = oauth2Login(configCtx.Server.Server, configCtx.Server.PlainText)
|
||||
}
|
||||
|
||||
localCfg.UpsertUser(localconfig.User{
|
||||
Name: localCfg.CurrentContext,
|
||||
AuthToken: tokenString,
|
||||
RefreshToken: refreshToken,
|
||||
})
|
||||
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("Context '%s' updated\n", localCfg.CurrentContext)
|
||||
},
|
||||
}
|
||||
command.Flags().StringVar(&password, "password", "", "the password of an account to authenticate")
|
||||
return command
|
||||
}
|
||||
@@ -7,9 +7,6 @@ import (
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
@@ -17,6 +14,8 @@ import (
|
||||
"github.com/argoproj/argo-cd/util"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewRepoCommand returns a new instance of an `argocd repo` command
|
||||
@@ -40,7 +39,6 @@ func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
repo appsv1.Repository
|
||||
upsert bool
|
||||
sshPrivateKeyPath string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
@@ -59,28 +57,20 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
}
|
||||
repo.SSHPrivateKey = string(keyData)
|
||||
}
|
||||
// First test the repo *without* username/password. This gives us a hint on whether this
|
||||
// is a private repo.
|
||||
// NOTE: it is important not to run git commands to test git credentials on the user's
|
||||
// system since it may mess with their git credential store (e.g. osx keychain).
|
||||
// See issue #315
|
||||
err := git.TestRepo(repo.Repo, "", "", repo.SSHPrivateKey)
|
||||
err := git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey)
|
||||
if err != nil {
|
||||
if git.IsSSHURL(repo.Repo) {
|
||||
// If we failed using git SSH credentials, then the repo is automatically bad
|
||||
if repo.Username != "" && repo.Password != "" || git.IsSSHURL(repo.Repo) {
|
||||
// if everything was supplied or repo URL is SSH url, one of the inputs was definitely bad
|
||||
log.Fatal(err)
|
||||
}
|
||||
// If we can't test the repo, it's probably private. Prompt for credentials and
|
||||
// let the server test it.
|
||||
// If we can't test the repo, it's probably private. Prompt for credentials and try again.
|
||||
repo.Username, repo.Password = cli.PromptCredentials(repo.Username, repo.Password)
|
||||
err = git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
|
||||
defer util.Close(conn)
|
||||
repoCreateReq := repository.RepoCreateRequest{
|
||||
Repo: &repo,
|
||||
Upsert: upsert,
|
||||
}
|
||||
createdRepo, err := repoIf.Create(context.Background(), &repoCreateReq)
|
||||
createdRepo, err := repoIf.Create(context.Background(), &repo)
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("repository '%s' added\n", createdRepo.Repo)
|
||||
},
|
||||
@@ -88,7 +78,6 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
|
||||
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
|
||||
command.Flags().StringVar(&sshPrivateKeyPath, "sshPrivateKeyPath", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
|
||||
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -124,9 +113,9 @@ func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
repos, err := repoIf.List(context.Background(), &repository.RepoQuery{})
|
||||
errors.CheckError(err)
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintf(w, "REPO\tUSER\tSTATUS\tMESSAGE\n")
|
||||
fmt.Fprintf(w, "REPO\tUSER\tMESSAGE\n")
|
||||
for _, r := range repos.Items {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", r.Repo, r.Username, r.ConnectionState.Status, r.ConnectionState.Message)
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", r.Repo, r.Username, r.Message)
|
||||
}
|
||||
_ = w.Flush()
|
||||
},
|
||||
|
||||
@@ -27,11 +27,10 @@ func NewCommand() *cobra.Command {
|
||||
command.AddCommand(NewClusterCommand(&clientOpts, pathOpts))
|
||||
command.AddCommand(NewApplicationCommand(&clientOpts))
|
||||
command.AddCommand(NewLoginCommand(&clientOpts))
|
||||
command.AddCommand(NewReloginCommand(&clientOpts))
|
||||
command.AddCommand(NewRepoCommand(&clientOpts))
|
||||
command.AddCommand(NewInstallCommand())
|
||||
command.AddCommand(NewUninstallCommand())
|
||||
command.AddCommand(NewContextCommand(&clientOpts))
|
||||
command.AddCommand(NewProjectCommand(&clientOpts))
|
||||
command.AddCommand(NewAccountCommand(&clientOpts))
|
||||
|
||||
defaultLocalConfigPath, err := localconfig.DefaultLocalConfigPath()
|
||||
errors.CheckError(err)
|
||||
|
||||
40
cmd/argocd/commands/uninstall.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
"github.com/argoproj/argo-cd/install"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// NewUninstallCommand returns a new instance of `argocd install` command
|
||||
func NewUninstallCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
installOpts install.InstallOptions
|
||||
deleteNamespace bool
|
||||
deleteCRD bool
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "Uninstall Argo CD",
|
||||
Long: "Uninstall Argo CD",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
conf, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, wasSpecified, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
if wasSpecified {
|
||||
installOpts.Namespace = namespace
|
||||
}
|
||||
installer, err := install.NewInstaller(conf, installOpts)
|
||||
errors.CheckError(err)
|
||||
installer.Uninstall(deleteNamespace, deleteCRD)
|
||||
},
|
||||
}
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(command)
|
||||
command.Flags().BoolVar(&deleteNamespace, "delete-namespace", false, "Also delete the namespace during uninstall")
|
||||
command.Flags().BoolVar(&deleteCRD, "delete-crd", false, "Also delete the Application CRD during uninstall")
|
||||
return command
|
||||
}
|
||||
@@ -54,7 +54,6 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
fmt.Printf(" GoVersion: %s\n", serverVers.GoVersion)
|
||||
fmt.Printf(" Compiler: %s\n", serverVers.Compiler)
|
||||
fmt.Printf(" Platform: %s\n", serverVers.Platform)
|
||||
fmt.Printf(" Ksonnet Version: %s\n", serverVers.KsonnetVersion)
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,16 +19,12 @@ const (
|
||||
AuthCookieName = "argocd.token"
|
||||
// ResourcesFinalizerName is a number of application CRD finalizer
|
||||
ResourcesFinalizerName = "resources-finalizer." + MetadataPrefix
|
||||
|
||||
// KubernetesInternalAPIServerAddr is address of the k8s API server when accessing internal to the cluster
|
||||
KubernetesInternalAPIServerAddr = "https://kubernetes.default.svc"
|
||||
)
|
||||
|
||||
const (
|
||||
ArgoCDAdminUsername = "admin"
|
||||
ArgoCDSecretName = "argocd-secret"
|
||||
ArgoCDConfigMapName = "argocd-cm"
|
||||
ArgoCDRBACConfigMapName = "argocd-rbac-cm"
|
||||
ArgoCDAdminUsername = "admin"
|
||||
ArgoCDSecretName = "argocd-secret"
|
||||
ArgoCDConfigMapName = "argocd-cm"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,10 +44,6 @@ const (
|
||||
ArgoCDCLIClientAppID = "argo-cd-cli"
|
||||
// EnvVarSSODebug is an environment variable to enable additional OAuth debugging in the API server
|
||||
EnvVarSSODebug = "ARGOCD_SSO_DEBUG"
|
||||
// EnvVarRBACDebug is an environment variable to enable additional RBAC debugging in the API server
|
||||
EnvVarRBACDebug = "ARGOCD_RBAC_DEBUG"
|
||||
// DefaultAppProjectName contains name of default app project. The default app project allows deploying application to any cluster.
|
||||
DefaultAppProjectName = "default"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -62,20 +53,6 @@ var (
|
||||
// LabelKeySecretType contains the type of argocd secret (either 'cluster' or 'repo')
|
||||
LabelKeySecretType = MetadataPrefix + "/secret-type"
|
||||
|
||||
// AnnotationConnectionStatus contains connection state status
|
||||
AnnotationConnectionStatus = MetadataPrefix + "/connection-status"
|
||||
// AnnotationConnectionMessage contains additional information about connection status
|
||||
AnnotationConnectionMessage = MetadataPrefix + "/connection-message"
|
||||
// AnnotationConnectionModifiedAt contains timestamp when connection state had been modified
|
||||
AnnotationConnectionModifiedAt = MetadataPrefix + "/connection-modified-at"
|
||||
|
||||
// AnnotationHook contains the hook type of a resource
|
||||
AnnotationHook = MetadataPrefix + "/hook"
|
||||
// AnnotationHookDeletePolicy is the policy of deleting a hook
|
||||
AnnotationHookDeletePolicy = MetadataPrefix + "/hook-delete-policy"
|
||||
// AnnotationHelmHook is the helm hook annotation
|
||||
AnnotationHelmHook = "helm.sh/hook"
|
||||
|
||||
// LabelKeyApplicationControllerInstanceID is the label which allows to separate application among multiple running application controllers.
|
||||
LabelKeyApplicationControllerInstanceID = application.ApplicationFullName + "/controller-instanceid"
|
||||
|
||||
|
||||
@@ -4,13 +4,17 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/kube"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"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"
|
||||
@@ -19,22 +23,11 @@ import (
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
|
||||
"github.com/argoproj/argo-cd/reposerver"
|
||||
"github.com/argoproj/argo-cd/util/argo"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/health"
|
||||
"github.com/argoproj/argo-cd/util/kube"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,13 +40,12 @@ type ApplicationController struct {
|
||||
namespace string
|
||||
kubeClientset kubernetes.Interface
|
||||
applicationClientset appclientset.Interface
|
||||
auditLogger *argo.AuditLogger
|
||||
appRefreshQueue workqueue.RateLimitingInterface
|
||||
appOperationQueue workqueue.RateLimitingInterface
|
||||
appInformer cache.SharedIndexInformer
|
||||
appStateManager AppStateManager
|
||||
appHealthManager AppHealthManager
|
||||
statusRefreshTimeout time.Duration
|
||||
repoClientset reposerver.Clientset
|
||||
db db.ArgoDB
|
||||
forceRefreshApps map[string]bool
|
||||
forceRefreshAppsMutex *sync.Mutex
|
||||
@@ -69,9 +61,9 @@ func NewApplicationController(
|
||||
namespace string,
|
||||
kubeClientset kubernetes.Interface,
|
||||
applicationClientset appclientset.Interface,
|
||||
repoClientset reposerver.Clientset,
|
||||
db db.ArgoDB,
|
||||
appStateManager AppStateManager,
|
||||
appHealthManager AppHealthManager,
|
||||
appResyncPeriod time.Duration,
|
||||
config *ApplicationControllerConfig,
|
||||
) *ApplicationController {
|
||||
@@ -81,16 +73,15 @@ func NewApplicationController(
|
||||
namespace: namespace,
|
||||
kubeClientset: kubeClientset,
|
||||
applicationClientset: applicationClientset,
|
||||
repoClientset: repoClientset,
|
||||
appRefreshQueue: appRefreshQueue,
|
||||
appOperationQueue: appOperationQueue,
|
||||
appStateManager: appStateManager,
|
||||
appHealthManager: appHealthManager,
|
||||
appInformer: newApplicationInformer(applicationClientset, appRefreshQueue, appOperationQueue, appResyncPeriod, config),
|
||||
db: db,
|
||||
statusRefreshTimeout: appResyncPeriod,
|
||||
forceRefreshApps: make(map[string]bool),
|
||||
forceRefreshAppsMutex: &sync.Mutex{},
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, "application-controller"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +155,7 @@ func (ctrl *ApplicationController) watchClusterResources(ctx context.Context, it
|
||||
|
||||
}
|
||||
|
||||
// WatchAppsResources watches for resource changes annotated with application label on all registered clusters and schedule corresponding app refresh.
|
||||
// watchAppsResources watches for resource changes annotated with application label on all registered clusters and schedule corresponding app refresh.
|
||||
func (ctrl *ApplicationController) watchAppsResources() {
|
||||
watchingClusters := make(map[string]context.CancelFunc)
|
||||
|
||||
@@ -204,7 +195,7 @@ func retryUntilSucceed(action func() error, desc string, ctx context.Context, ti
|
||||
log.Infof("Stop retrying %s", desc)
|
||||
return
|
||||
} else {
|
||||
log.Warnf("Failed to %s: %+v, retrying in %v", desc, err, timeout)
|
||||
log.Warnf("Failed to %s: %v, retrying in %v", desc, err, timeout)
|
||||
time.Sleep(timeout)
|
||||
}
|
||||
}
|
||||
@@ -286,7 +277,6 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
|
||||
Type: appv1.ApplicationConditionDeletionError,
|
||||
Message: err.Error(),
|
||||
})
|
||||
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonStatusRefreshed, Action: "refresh_status"}, v1.EventTypeWarning)
|
||||
} else {
|
||||
log.Infof("Successfully deleted resources for application %s", app.Name)
|
||||
}
|
||||
@@ -320,102 +310,102 @@ func (ctrl *ApplicationController) setAppCondition(app *appv1.Application, condi
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Application) {
|
||||
var state *appv1.OperationState
|
||||
state := appv1.OperationState{Phase: appv1.OperationRunning, Operation: *app.Operation, StartedAt: metav1.Now()}
|
||||
// Recover from any unexpected panics and automatically set the status to be failed
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
|
||||
// TODO: consider adding Error OperationStatus in addition to Failed
|
||||
state.Phase = appv1.OperationError
|
||||
if rerr, ok := r.(error); ok {
|
||||
state.Message = rerr.Error()
|
||||
} else {
|
||||
state.Message = fmt.Sprintf("%v", r)
|
||||
}
|
||||
ctrl.setOperationState(app, state)
|
||||
ctrl.setOperationState(app.Name, state, app.Operation)
|
||||
}
|
||||
}()
|
||||
if isOperationInProgress(app) {
|
||||
// If we get here, we are about process an operation but we notice it is already in progress.
|
||||
// We need to detect if the app object we pulled off the informer is stale and doesn't
|
||||
// reflect the fact that the operation is completed. We don't want to perform the operation
|
||||
// again. To detect this, always retrieve the latest version to ensure it is not stale.
|
||||
if app.Status.OperationState != nil && !app.Status.OperationState.Phase.Completed() {
|
||||
// If we get here, we are about process an operation but we notice it is already Running.
|
||||
// We need to detect if the controller crashed before completing the operation, or if the
|
||||
// the app object we pulled off the informer is simply stale and doesn't reflect the fact
|
||||
// that the operation is completed. We don't want to perform the operation again. To detect
|
||||
// this, always retrieve the latest version to ensure it is not stale.
|
||||
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to retrieve latest application state: %v", err)
|
||||
return
|
||||
}
|
||||
if !isOperationInProgress(freshApp) {
|
||||
if freshApp.Status.OperationState == nil || freshApp.Status.OperationState.Phase.Completed() {
|
||||
log.Infof("Skipping operation on stale application state (%s)", app.ObjectMeta.Name)
|
||||
return
|
||||
}
|
||||
app = freshApp
|
||||
state = app.Status.OperationState.DeepCopy()
|
||||
log.Infof("Resuming in-progress operation. app: %s, phase: %s, message: %s", app.ObjectMeta.Name, state.Phase, state.Message)
|
||||
log.Warnf("Found interrupted application operation %s %v", app.ObjectMeta.Name, app.Status.OperationState)
|
||||
} else {
|
||||
state = &appv1.OperationState{Phase: appv1.OperationRunning, Operation: *app.Operation, StartedAt: metav1.Now()}
|
||||
ctrl.setOperationState(app, state)
|
||||
log.Infof("Initialized new operation. app: %s, operation: %v", app.ObjectMeta.Name, *app.Operation)
|
||||
ctrl.setOperationState(app.Name, state, app.Operation)
|
||||
}
|
||||
ctrl.appStateManager.SyncAppState(app, state)
|
||||
|
||||
if state.Phase == appv1.OperationRunning {
|
||||
// It's possible for an app to be terminated while we were operating on it. We do not want
|
||||
// to clobber the Terminated state with Running. Get the latest app state to check for this.
|
||||
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
if freshApp.Status.OperationState != nil && freshApp.Status.OperationState.Phase == appv1.OperationTerminating {
|
||||
state.Phase = appv1.OperationTerminating
|
||||
state.Message = "operation is terminating"
|
||||
// after this, we will get requeued to the workqueue, but next time the
|
||||
// SyncAppState will operate in a Terminating phase, allowing the worker to perform
|
||||
// cleanup (e.g. delete jobs, workflows, etc...)
|
||||
if app.Operation.Sync != nil {
|
||||
opRes := ctrl.appStateManager.SyncAppState(app, app.Operation.Sync.Revision, nil, app.Operation.Sync.DryRun, app.Operation.Sync.Prune)
|
||||
state.Phase = opRes.Phase
|
||||
state.Message = opRes.Message
|
||||
state.SyncResult = opRes.SyncResult
|
||||
} else if app.Operation.Rollback != nil {
|
||||
var deploymentInfo *appv1.DeploymentInfo
|
||||
for _, info := range app.Status.History {
|
||||
if info.ID == app.Operation.Rollback.ID {
|
||||
deploymentInfo = &info
|
||||
break
|
||||
}
|
||||
}
|
||||
if deploymentInfo == nil {
|
||||
state.Phase = appv1.OperationFailed
|
||||
state.Message = fmt.Sprintf("application %s does not have deployment with id %v", app.Name, app.Operation.Rollback.ID)
|
||||
} else {
|
||||
opRes := ctrl.appStateManager.SyncAppState(app, deploymentInfo.Revision, &deploymentInfo.ComponentParameterOverrides, app.Operation.Rollback.DryRun, app.Operation.Rollback.Prune)
|
||||
state.Phase = opRes.Phase
|
||||
state.Message = opRes.Message
|
||||
state.RollbackResult = opRes.SyncResult
|
||||
}
|
||||
} else {
|
||||
state.Phase = appv1.OperationFailed
|
||||
state.Message = "Invalid operation request"
|
||||
}
|
||||
|
||||
ctrl.setOperationState(app, state)
|
||||
if state.Phase.Completed() {
|
||||
// if we just completed an operation, force a refresh so that UI will report up-to-date
|
||||
// sync/health information
|
||||
ctrl.forceAppRefresh(app.ObjectMeta.Name)
|
||||
}
|
||||
ctrl.setOperationState(app.Name, state, app.Operation)
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) setOperationState(app *appv1.Application, state *appv1.OperationState) {
|
||||
func (ctrl *ApplicationController) setOperationState(appName string, state appv1.OperationState, operation *appv1.Operation) {
|
||||
retryUntilSucceed(func() error {
|
||||
var inProgressOpValue *appv1.Operation
|
||||
if state.Phase == "" {
|
||||
// expose any bugs where we neglect to set phase
|
||||
panic("no phase was set")
|
||||
}
|
||||
if state.Phase.Completed() {
|
||||
now := metav1.Now()
|
||||
state.FinishedAt = &now
|
||||
if !state.Phase.Completed() {
|
||||
// If operation is still running, we populate the app.operation field, which prevents
|
||||
// any other operation from running at the same time. Otherwise, it is cleared by setting
|
||||
// it to nil which indicates no operation is in progress.
|
||||
inProgressOpValue = operation
|
||||
} else {
|
||||
nowTime := metav1.Now()
|
||||
state.FinishedAt = &nowTime
|
||||
}
|
||||
patch := map[string]interface{}{
|
||||
|
||||
patch, err := json.Marshal(map[string]interface{}{
|
||||
"status": map[string]interface{}{
|
||||
"operationState": state,
|
||||
},
|
||||
}
|
||||
if state.Phase.Completed() {
|
||||
// If operation is completed, clear the operation field to indicate no operation is
|
||||
// in progress.
|
||||
patch["operation"] = nil
|
||||
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonResourceUpdated, Action: "refresh_status"}, v1.EventTypeNormal)
|
||||
}
|
||||
if reflect.DeepEqual(app.Status.OperationState, state) {
|
||||
log.Infof("No operation updates necessary to '%s'. Skipping patch", app.Name)
|
||||
return nil
|
||||
}
|
||||
patchJSON, err := json.Marshal(patch)
|
||||
"operation": inProgressOpValue,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace)
|
||||
_, err = appClient.Patch(app.Name, types.MergePatchType, patchJSON)
|
||||
_, err = appClient.Patch(appName, types.MergePatchType, patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("updated '%s' operation (phase: %s)", app.Name, state.Phase)
|
||||
log.Infof("updated '%s' operation (phase: %s)", appName, state.Phase)
|
||||
return nil
|
||||
}, "Update application operation state", context.Background(), updateOperationStateTimeout)
|
||||
}
|
||||
@@ -425,8 +415,10 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
|
||||
if shutdown {
|
||||
processNext = false
|
||||
return
|
||||
} else {
|
||||
processNext = true
|
||||
}
|
||||
processNext = true
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
|
||||
@@ -448,194 +440,64 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
|
||||
log.Warnf("Key '%s' in index is not an application", appKey)
|
||||
return
|
||||
}
|
||||
if !ctrl.needRefreshAppStatus(app, ctrl.statusRefreshTimeout) {
|
||||
return
|
||||
|
||||
isForceRefreshed := ctrl.isRefreshForced(app.Name)
|
||||
if isForceRefreshed || app.NeedRefreshAppStatus(ctrl.statusRefreshTimeout) {
|
||||
log.Infof("Refreshing application '%s' status (force refreshed: %v)", app.Name, isForceRefreshed)
|
||||
|
||||
comparisonResult, parameters, healthState, err := ctrl.tryRefreshAppStatus(app.DeepCopy())
|
||||
if err != nil {
|
||||
comparisonResult = &appv1.ComparisonResult{
|
||||
Status: appv1.ComparisonStatusError,
|
||||
Error: fmt.Sprintf("Failed to get application status for application '%s': %v", app.Name, err),
|
||||
ComparedTo: app.Spec.Source,
|
||||
ComparedAt: metav1.Time{Time: time.Now().UTC()},
|
||||
}
|
||||
parameters = nil
|
||||
healthState = &appv1.HealthStatus{Status: appv1.HealthStatusUnknown}
|
||||
}
|
||||
ctrl.updateAppStatus(app.Name, app.Namespace, comparisonResult, parameters, *healthState)
|
||||
}
|
||||
|
||||
app = app.DeepCopy()
|
||||
conditions, hasErrors := ctrl.refreshAppConditions(app)
|
||||
if hasErrors {
|
||||
comparisonResult := app.Status.ComparisonResult.DeepCopy()
|
||||
comparisonResult.Status = appv1.ComparisonStatusUnknown
|
||||
health := app.Status.Health.DeepCopy()
|
||||
health.Status = appv1.HealthStatusUnknown
|
||||
ctrl.updateAppStatus(app, comparisonResult, health, nil, conditions)
|
||||
return
|
||||
}
|
||||
|
||||
comparisonResult, manifestInfo, compConditions, err := ctrl.appStateManager.CompareAppState(app, "", nil)
|
||||
if err != nil {
|
||||
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
|
||||
} else {
|
||||
conditions = append(conditions, compConditions...)
|
||||
}
|
||||
|
||||
var parameters []*appv1.ComponentParameter
|
||||
if manifestInfo != nil {
|
||||
parameters = manifestInfo.Params
|
||||
}
|
||||
|
||||
healthState, err := setApplicationHealth(comparisonResult)
|
||||
if err != nil {
|
||||
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
|
||||
}
|
||||
ctrl.updateAppStatus(app, comparisonResult, healthState, parameters, conditions)
|
||||
return
|
||||
}
|
||||
|
||||
// needRefreshAppStatus answers if application status needs to be refreshed.
|
||||
// Returns true if application never been compared, has changed or comparison result has expired.
|
||||
func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application, statusRefreshTimeout time.Duration) bool {
|
||||
var reason string
|
||||
if ctrl.isRefreshForced(app.Name) {
|
||||
reason = "force refresh"
|
||||
} else if app.Status.ComparisonResult.Status == appv1.ComparisonStatusUnknown {
|
||||
reason = "comparison status unknown"
|
||||
} else if !app.Spec.Source.Equals(app.Status.ComparisonResult.ComparedTo) {
|
||||
reason = "spec.source differs"
|
||||
} else if app.Status.ComparisonResult.ComparedAt.Add(statusRefreshTimeout).Before(time.Now().UTC()) {
|
||||
reason = fmt.Sprintf("comparison expired. comparedAt: %v, expiry: %v", app.Status.ComparisonResult.ComparedAt, statusRefreshTimeout)
|
||||
}
|
||||
if reason != "" {
|
||||
log.Infof("Refreshing application '%s' status (%s)", app.Name, reason)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application) ([]appv1.ApplicationCondition, bool) {
|
||||
conditions := make([]appv1.ApplicationCondition, 0)
|
||||
proj, err := argo.GetAppProject(&app.Spec, ctrl.applicationClientset, ctrl.namespace)
|
||||
func (ctrl *ApplicationController) tryRefreshAppStatus(app *appv1.Application) (*appv1.ComparisonResult, *[]appv1.ComponentParameter, *appv1.HealthStatus, error) {
|
||||
comparisonResult, manifestInfo, err := ctrl.appStateManager.CompareAppState(app)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
conditions = append(conditions, appv1.ApplicationCondition{
|
||||
Type: appv1.ApplicationConditionInvalidSpecError,
|
||||
Message: fmt.Sprintf("Application referencing project %s which does not exist", app.Spec.Project),
|
||||
})
|
||||
} else {
|
||||
conditions = append(conditions, appv1.ApplicationCondition{
|
||||
Type: appv1.ApplicationConditionUnknownError,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
specConditions, err := argo.GetSpecErrors(context.Background(), &app.Spec, proj, ctrl.repoClientset, ctrl.db)
|
||||
if err != nil {
|
||||
conditions = append(conditions, appv1.ApplicationCondition{
|
||||
Type: appv1.ApplicationConditionUnknownError,
|
||||
Message: err.Error(),
|
||||
})
|
||||
} else {
|
||||
conditions = append(conditions, specConditions...)
|
||||
}
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
log.Infof("App %s comparison result: prev: %s. current: %s", app.Name, app.Status.ComparisonResult.Status, comparisonResult.Status)
|
||||
|
||||
// List of condition types which have to be reevaluated by controller; all remaining conditions should stay as is.
|
||||
reevaluateTypes := map[appv1.ApplicationConditionType]bool{
|
||||
appv1.ApplicationConditionInvalidSpecError: true,
|
||||
appv1.ApplicationConditionUnknownError: true,
|
||||
appv1.ApplicationConditionComparisonError: true,
|
||||
appv1.ApplicationConditionSharedResourceWarning: true,
|
||||
parameters := make([]appv1.ComponentParameter, len(manifestInfo.Params))
|
||||
for i := range manifestInfo.Params {
|
||||
parameters[i] = *manifestInfo.Params[i]
|
||||
}
|
||||
appConditions := make([]appv1.ApplicationCondition, 0)
|
||||
for i := 0; i < len(app.Status.Conditions); i++ {
|
||||
condition := app.Status.Conditions[i]
|
||||
if _, ok := reevaluateTypes[condition.Type]; !ok {
|
||||
appConditions = append(appConditions, condition)
|
||||
}
|
||||
healthState, err := ctrl.appHealthManager.GetAppHealth(app.Spec.Destination.Server, app.Spec.Destination.Namespace, comparisonResult)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
hasErrors := false
|
||||
for i := range conditions {
|
||||
condition := conditions[i]
|
||||
appConditions = append(appConditions, condition)
|
||||
if condition.IsError() {
|
||||
hasErrors = true
|
||||
}
|
||||
|
||||
}
|
||||
return appConditions, hasErrors
|
||||
return comparisonResult, ¶meters, healthState, nil
|
||||
}
|
||||
|
||||
// setApplicationHealth updates the health statuses of all resources performed in the comparison
|
||||
func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
|
||||
var savedErr error
|
||||
appHealth := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
|
||||
if comparisonResult.Status == appv1.ComparisonStatusUnknown {
|
||||
appHealth.Status = appv1.HealthStatusUnknown
|
||||
}
|
||||
for i, resource := range comparisonResult.Resources {
|
||||
if resource.LiveState == "null" {
|
||||
resource.Health = appv1.HealthStatus{Status: appv1.HealthStatusMissing}
|
||||
} else {
|
||||
var obj unstructured.Unstructured
|
||||
err := json.Unmarshal([]byte(resource.LiveState), &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
healthState, err := health.GetAppHealth(&obj)
|
||||
if err != nil && savedErr == nil {
|
||||
savedErr = err
|
||||
}
|
||||
resource.Health = *healthState
|
||||
}
|
||||
comparisonResult.Resources[i] = resource
|
||||
if health.IsWorse(appHealth.Status, resource.Health.Status) {
|
||||
appHealth.Status = resource.Health.Status
|
||||
}
|
||||
}
|
||||
return &appHealth, savedErr
|
||||
}
|
||||
|
||||
// updateAppStatus persists updates to application status. Detects if there patch
|
||||
func (ctrl *ApplicationController) updateAppStatus(
|
||||
app *appv1.Application,
|
||||
comparisonResult *appv1.ComparisonResult,
|
||||
healthState *appv1.HealthStatus,
|
||||
parameters []*appv1.ComponentParameter,
|
||||
conditions []appv1.ApplicationCondition,
|
||||
) {
|
||||
modifiedApp := app.DeepCopy()
|
||||
if comparisonResult != nil {
|
||||
modifiedApp.Status.ComparisonResult = *comparisonResult
|
||||
log.Infof("App %s comparison result: prev: %s. current: %s", app.Name, app.Status.ComparisonResult.Status, comparisonResult.Status)
|
||||
appName string, namespace string, comparisonResult *appv1.ComparisonResult, parameters *[]appv1.ComponentParameter, healthState appv1.HealthStatus) {
|
||||
statusPatch := make(map[string]interface{})
|
||||
statusPatch["comparisonResult"] = comparisonResult
|
||||
statusPatch["parameters"] = parameters
|
||||
statusPatch["health"] = healthState
|
||||
patch, err := json.Marshal(map[string]interface{}{
|
||||
"status": statusPatch,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(namespace)
|
||||
_, err = appClient.Patch(appName, types.MergePatchType, patch)
|
||||
}
|
||||
if healthState != nil {
|
||||
modifiedApp.Status.Health = *healthState
|
||||
}
|
||||
if parameters != nil {
|
||||
modifiedApp.Status.Parameters = make([]appv1.ComponentParameter, len(parameters))
|
||||
for i := range parameters {
|
||||
modifiedApp.Status.Parameters[i] = *parameters[i]
|
||||
}
|
||||
}
|
||||
if conditions != nil {
|
||||
modifiedApp.Status.Conditions = conditions
|
||||
}
|
||||
origBytes, err := json.Marshal(app)
|
||||
if err != nil {
|
||||
log.Errorf("Error updating application %s (marshal orig app): %v", app.Name, err)
|
||||
return
|
||||
}
|
||||
modifiedBytes, err := json.Marshal(modifiedApp)
|
||||
if err != nil {
|
||||
log.Errorf("Error updating application %s (marshal modified app): %v", app.Name, err)
|
||||
return
|
||||
}
|
||||
patch, err := strategicpatch.CreateTwoWayMergePatch(origBytes, modifiedBytes, appv1.Application{})
|
||||
if err != nil {
|
||||
log.Errorf("Error calculating patch for app %s update: %v", app.Name, err)
|
||||
return
|
||||
}
|
||||
if string(patch) == "{}" {
|
||||
log.Infof("No status changes to %s. Skipping patch", app.Name)
|
||||
return
|
||||
}
|
||||
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
|
||||
_, err = appClient.Patch(app.Name, types.MergePatchType, patch)
|
||||
if err != nil {
|
||||
log.Warnf("Error updating application %s: %v", app.Name, err)
|
||||
log.Warnf("Error updating application: %v", err)
|
||||
} else {
|
||||
log.Infof("Application %s update successful", app.Name)
|
||||
log.Info("Application update successful")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,7 +558,3 @@ func newApplicationInformer(
|
||||
)
|
||||
return informer
|
||||
}
|
||||
|
||||
func isOperationInProgress(app *appv1.Application) bool {
|
||||
return app.Status.OperationState != nil && !app.Status.OperationState.Phase.Completed()
|
||||
}
|
||||
161
controller/health.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/kube"
|
||||
"k8s.io/api/apps/v1"
|
||||
coreV1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
maxHistoryCnt = 5
|
||||
)
|
||||
|
||||
type AppHealthManager interface {
|
||||
GetAppHealth(server string, namespace string, comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error)
|
||||
}
|
||||
|
||||
type kubeAppHealthManager struct {
|
||||
db db.ArgoDB
|
||||
namespace string
|
||||
}
|
||||
|
||||
func NewAppHealthManager(db db.ArgoDB, namespace string) AppHealthManager {
|
||||
return &kubeAppHealthManager{db: db, namespace: namespace}
|
||||
}
|
||||
|
||||
func (ctrl *kubeAppHealthManager) getServiceHealth(config *rest.Config, namespace string, name string) (*appv1.HealthStatus, error) {
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service, err := clientSet.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
health := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
|
||||
if service.Spec.Type == coreV1.ServiceTypeLoadBalancer {
|
||||
health.Status = appv1.HealthStatusProgressing
|
||||
for _, ingress := range service.Status.LoadBalancer.Ingress {
|
||||
if ingress.Hostname != "" || ingress.IP != "" {
|
||||
health.Status = appv1.HealthStatusHealthy
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return &health, nil
|
||||
}
|
||||
|
||||
func (ctrl *kubeAppHealthManager) getDeploymentHealth(config *rest.Config, namespace string, name string) (*appv1.HealthStatus, error) {
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deployment, err := clientSet.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if deployment.Generation <= deployment.Status.ObservedGeneration {
|
||||
cond := getDeploymentCondition(deployment.Status, v1.DeploymentProgressing)
|
||||
if cond != nil && cond.Reason == "ProgressDeadlineExceeded" {
|
||||
return &appv1.HealthStatus{
|
||||
Status: appv1.HealthStatusDegraded,
|
||||
StatusDetails: fmt.Sprintf("Deployment %q exceeded its progress deadline", name),
|
||||
}, nil
|
||||
} else if deployment.Spec.Replicas != nil && deployment.Status.UpdatedReplicas < *deployment.Spec.Replicas {
|
||||
return &appv1.HealthStatus{
|
||||
Status: appv1.HealthStatusProgressing,
|
||||
StatusDetails: fmt.Sprintf("Waiting for rollout to finish: %d out of %d new replicas have been updated...\n", deployment.Status.UpdatedReplicas, *deployment.Spec.Replicas),
|
||||
}, nil
|
||||
} else if deployment.Status.Replicas > deployment.Status.UpdatedReplicas {
|
||||
return &appv1.HealthStatus{
|
||||
Status: appv1.HealthStatusProgressing,
|
||||
StatusDetails: fmt.Sprintf("Waiting for rollout to finish: %d old replicas are pending termination...\n", deployment.Status.Replicas-deployment.Status.UpdatedReplicas),
|
||||
}, nil
|
||||
} else if deployment.Status.AvailableReplicas < deployment.Status.UpdatedReplicas {
|
||||
return &appv1.HealthStatus{
|
||||
Status: appv1.HealthStatusProgressing,
|
||||
StatusDetails: fmt.Sprintf("Waiting for rollout to finish: %d of %d updated replicas are available...\n", deployment.Status.AvailableReplicas, deployment.Status.UpdatedReplicas),
|
||||
}, nil
|
||||
}
|
||||
} else {
|
||||
return &appv1.HealthStatus{
|
||||
Status: appv1.HealthStatusProgressing,
|
||||
StatusDetails: "Waiting for rollout to finish: observed deployment generation less then desired generation",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &appv1.HealthStatus{
|
||||
Status: appv1.HealthStatusHealthy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getDeploymentCondition(status v1.DeploymentStatus, condType v1.DeploymentConditionType) *v1.DeploymentCondition {
|
||||
for i := range status.Conditions {
|
||||
c := status.Conditions[i]
|
||||
if c.Type == condType {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctrl *kubeAppHealthManager) GetAppHealth(server string, namespace string, comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
|
||||
clst, err := ctrl.db.GetCluster(context.Background(), server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
restConfig := clst.RESTConfig()
|
||||
|
||||
appHealth := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
|
||||
for i := range comparisonResult.Resources {
|
||||
resource := comparisonResult.Resources[i]
|
||||
if resource.LiveState == "null" {
|
||||
resource.Health = appv1.HealthStatus{Status: appv1.HealthStatusUnknown}
|
||||
} else {
|
||||
var obj unstructured.Unstructured
|
||||
err := json.Unmarshal([]byte(resource.LiveState), &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch obj.GetKind() {
|
||||
case kube.DeploymentKind:
|
||||
state, err := ctrl.getDeploymentHealth(restConfig, namespace, obj.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resource.Health = *state
|
||||
case kube.ServiceKind:
|
||||
state, err := ctrl.getServiceHealth(restConfig, namespace, obj.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resource.Health = *state
|
||||
default:
|
||||
resource.Health = appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
|
||||
}
|
||||
|
||||
if resource.Health.Status == appv1.HealthStatusProgressing {
|
||||
if appHealth.Status == appv1.HealthStatusHealthy {
|
||||
appHealth.Status = appv1.HealthStatusProgressing
|
||||
}
|
||||
} else if resource.Health.Status == appv1.HealthStatusDegraded {
|
||||
if appHealth.Status == appv1.HealthStatusHealthy || appHealth.Status == appv1.HealthStatusProgressing {
|
||||
appHealth.Status = appv1.HealthStatusDegraded
|
||||
}
|
||||
}
|
||||
}
|
||||
comparisonResult.Resources[i] = resource
|
||||
}
|
||||
return &appHealth, nil
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/reposerver"
|
||||
"github.com/argoproj/argo-cd/reposerver/repository"
|
||||
"github.com/argoproj/argo-cd/util"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
log "github.com/sirupsen/logrus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
)
|
||||
|
||||
type SecretController struct {
|
||||
kubeClient kubernetes.Interface
|
||||
secretQueue workqueue.RateLimitingInterface
|
||||
secretInformer cache.SharedIndexInformer
|
||||
repoClientset reposerver.Clientset
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (ctrl *SecretController) Run(ctx context.Context) {
|
||||
go ctrl.secretInformer.Run(ctx.Done())
|
||||
if !cache.WaitForCacheSync(ctx.Done(), ctrl.secretInformer.HasSynced) {
|
||||
log.Error("Timed out waiting for caches to sync")
|
||||
return
|
||||
}
|
||||
go wait.Until(func() {
|
||||
for ctrl.processSecret() {
|
||||
}
|
||||
}, time.Second, ctx.Done())
|
||||
}
|
||||
|
||||
func (ctrl *SecretController) processSecret() (processNext bool) {
|
||||
secretKey, shutdown := ctrl.secretQueue.Get()
|
||||
if shutdown {
|
||||
processNext = false
|
||||
return
|
||||
} else {
|
||||
processNext = true
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
|
||||
}
|
||||
ctrl.secretQueue.Done(secretKey)
|
||||
}()
|
||||
obj, exists, err := ctrl.secretInformer.GetIndexer().GetByKey(secretKey.(string))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get secret '%s' from informer index: %+v", secretKey, err)
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
// This happens after secret was deleted, but the work queue still had an entry for it.
|
||||
return
|
||||
}
|
||||
secret, ok := obj.(*corev1.Secret)
|
||||
if !ok {
|
||||
log.Warnf("Key '%s' in index is not an secret", secretKey)
|
||||
return
|
||||
}
|
||||
|
||||
if secret.Labels[common.LabelKeySecretType] == common.SecretTypeCluster {
|
||||
cluster := db.SecretToCluster(secret)
|
||||
ctrl.updateState(secret, ctrl.getClusterState(cluster))
|
||||
} else if secret.Labels[common.LabelKeySecretType] == common.SecretTypeRepository {
|
||||
repo := db.SecretToRepo(secret)
|
||||
ctrl.updateState(secret, ctrl.getRepoConnectionState(repo))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctrl *SecretController) getRepoConnectionState(repo *v1alpha1.Repository) v1alpha1.ConnectionState {
|
||||
state := v1alpha1.ConnectionState{
|
||||
ModifiedAt: repo.ConnectionState.ModifiedAt,
|
||||
Status: v1alpha1.ConnectionStatusUnknown,
|
||||
}
|
||||
closer, client, err := ctrl.repoClientset.NewRepositoryClient()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to create repository client: %v", err)
|
||||
return state
|
||||
}
|
||||
|
||||
defer util.Close(closer)
|
||||
|
||||
_, err = client.ListDir(context.Background(), &repository.ListDirRequest{Repo: repo, Path: ".gitignore"})
|
||||
if err == nil {
|
||||
state.Status = v1alpha1.ConnectionStatusSuccessful
|
||||
} else {
|
||||
state.Status = v1alpha1.ConnectionStatusFailed
|
||||
state.Message = err.Error()
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func (ctrl *SecretController) getClusterState(cluster *v1alpha1.Cluster) v1alpha1.ConnectionState {
|
||||
state := v1alpha1.ConnectionState{
|
||||
ModifiedAt: cluster.ConnectionState.ModifiedAt,
|
||||
Status: v1alpha1.ConnectionStatusUnknown,
|
||||
}
|
||||
kubeClientset, err := kubernetes.NewForConfig(cluster.RESTConfig())
|
||||
if err == nil {
|
||||
_, err = kubeClientset.Discovery().ServerVersion()
|
||||
}
|
||||
if err == nil {
|
||||
state.Status = v1alpha1.ConnectionStatusSuccessful
|
||||
} else {
|
||||
state.Status = v1alpha1.ConnectionStatusFailed
|
||||
state.Message = err.Error()
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func (ctrl *SecretController) updateState(secret *corev1.Secret, state v1alpha1.ConnectionState) {
|
||||
annotationsPatch := make(map[string]string)
|
||||
for key, value := range db.AnnotationsFromConnectionState(&state) {
|
||||
if secret.Annotations[key] != value {
|
||||
annotationsPatch[key] = value
|
||||
}
|
||||
}
|
||||
if len(annotationsPatch) > 0 {
|
||||
annotationsPatch[common.AnnotationConnectionModifiedAt] = metav1.Now().Format(time.RFC3339)
|
||||
patchData, err := json.Marshal(map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"annotations": annotationsPatch,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnf("Unable to prepare secret state annotation patch: %v", err)
|
||||
} else {
|
||||
_, err := ctrl.kubeClient.CoreV1().Secrets(secret.Namespace).Patch(secret.Name, types.MergePatchType, patchData)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to patch secret state annotation: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newSecretInformer(client kubernetes.Interface, resyncPeriod time.Duration, namespace string, secretQueue workqueue.RateLimitingInterface) cache.SharedIndexInformer {
|
||||
informerFactory := informers.NewFilteredSharedInformerFactory(
|
||||
client,
|
||||
resyncPeriod,
|
||||
namespace,
|
||||
func(options *metav1.ListOptions) {
|
||||
var req *labels.Requirement
|
||||
req, err := labels.NewRequirement(common.LabelKeySecretType, selection.In, []string{common.SecretTypeCluster, common.SecretTypeRepository})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
options.FieldSelector = fields.Everything().String()
|
||||
labelSelector := labels.NewSelector().Add(*req)
|
||||
options.LabelSelector = labelSelector.String()
|
||||
},
|
||||
)
|
||||
informer := informerFactory.Core().V1().Secrets().Informer()
|
||||
informer.AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
key, err := cache.MetaNamespaceKeyFunc(obj)
|
||||
if err == nil {
|
||||
secretQueue.Add(key)
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
key, err := cache.MetaNamespaceKeyFunc(new)
|
||||
if err == nil {
|
||||
secretQueue.Add(key)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
return informer
|
||||
}
|
||||
|
||||
func NewSecretController(kubeClient kubernetes.Interface, repoClientset reposerver.Clientset, resyncPeriod time.Duration, namespace string) *SecretController {
|
||||
secretQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
|
||||
return &SecretController{
|
||||
kubeClient: kubeClient,
|
||||
secretQueue: secretQueue,
|
||||
secretInformer: newSecretInformer(kubeClient, resyncPeriod, namespace, secretQueue),
|
||||
namespace: namespace,
|
||||
repoClientset: repoClientset,
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
@@ -21,22 +14,25 @@ import (
|
||||
"github.com/argoproj/argo-cd/util"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/diff"
|
||||
"github.com/argoproj/argo-cd/util/kube"
|
||||
kubeutil "github.com/argoproj/argo-cd/util/kube"
|
||||
)
|
||||
|
||||
const (
|
||||
maxHistoryCnt = 5
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// AppStateManager defines methods which allow to compare application spec and actual application state.
|
||||
type AppStateManager interface {
|
||||
CompareAppState(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) (
|
||||
*v1alpha1.ComparisonResult, *repository.ManifestResponse, []v1alpha1.ApplicationCondition, error)
|
||||
SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
|
||||
CompareAppState(app *v1alpha1.Application) (*v1alpha1.ComparisonResult, *repository.ManifestResponse, error)
|
||||
SyncAppState(app *v1alpha1.Application, revision string, overrides *[]v1alpha1.ComponentParameter, dryRun bool, prune bool) *v1alpha1.OperationState
|
||||
}
|
||||
|
||||
// ksonnetAppStateManager allows to compare application using KSonnet CLI
|
||||
type ksonnetAppStateManager struct {
|
||||
// KsonnetAppStateManager allows to compare application using KSonnet CLI
|
||||
type KsonnetAppStateManager struct {
|
||||
db db.ArgoDB
|
||||
appclientset appclientset.Interface
|
||||
repoClientset reposerver.Clientset
|
||||
@@ -45,7 +41,7 @@ type ksonnetAppStateManager struct {
|
||||
|
||||
// groupLiveObjects deduplicate list of kubernetes resources and choose correct version of resource: if resource has corresponding expected application resource then method pick
|
||||
// kubernetes resource with matching version, otherwise chooses single kubernetes resource with any version
|
||||
func groupLiveObjects(liveObjs []*unstructured.Unstructured, targetObjs []*unstructured.Unstructured) map[string]*unstructured.Unstructured {
|
||||
func (ks *KsonnetAppStateManager) groupLiveObjects(liveObjs []*unstructured.Unstructured, targetObjs []*unstructured.Unstructured) map[string]*unstructured.Unstructured {
|
||||
targetByFullName := make(map[string]*unstructured.Unstructured)
|
||||
for _, obj := range targetObjs {
|
||||
targetByFullName[getResourceFullName(obj)] = obj
|
||||
@@ -83,33 +79,19 @@ func groupLiveObjects(liveObjs []*unstructured.Unstructured, targetObjs []*unstr
|
||||
return liveByFullName
|
||||
}
|
||||
|
||||
func (s *ksonnetAppStateManager) getTargetObjs(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, *repository.ManifestResponse, error) {
|
||||
repo := s.getRepo(app.Spec.Source.RepoURL)
|
||||
conn, repoClient, err := s.repoClientset.NewRepositoryClient()
|
||||
// CompareAppState compares application spec and real app state using KSonnet
|
||||
func (ks *KsonnetAppStateManager) CompareAppState(app *v1alpha1.Application) (*v1alpha1.ComparisonResult, *repository.ManifestResponse, error) {
|
||||
repo := ks.getRepo(app.Spec.Source.RepoURL)
|
||||
conn, repoClient, err := ks.repoClientset.NewRepositoryClient()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer util.Close(conn)
|
||||
|
||||
if revision == "" {
|
||||
revision = app.Spec.Source.TargetRevision
|
||||
}
|
||||
|
||||
// Decide what overrides to compare with.
|
||||
var mfReqOverrides []*v1alpha1.ComponentParameter
|
||||
if overrides != nil {
|
||||
// If overrides is supplied, use that
|
||||
mfReqOverrides = make([]*v1alpha1.ComponentParameter, len(overrides))
|
||||
for i := range overrides {
|
||||
item := overrides[i]
|
||||
mfReqOverrides[i] = &item
|
||||
}
|
||||
} else {
|
||||
// Otherwise, use the overrides in the app spec
|
||||
mfReqOverrides = make([]*v1alpha1.ComponentParameter, len(app.Spec.Source.ComponentParameterOverrides))
|
||||
overrides := make([]*v1alpha1.ComponentParameter, len(app.Spec.Source.ComponentParameterOverrides))
|
||||
if app.Spec.Source.ComponentParameterOverrides != nil {
|
||||
for i := range app.Spec.Source.ComponentParameterOverrides {
|
||||
item := app.Spec.Source.ComponentParameterOverrides[i]
|
||||
mfReqOverrides[i] = &item
|
||||
overrides[i] = &item
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,53 +99,40 @@ func (s *ksonnetAppStateManager) getTargetObjs(app *v1alpha1.Application, revisi
|
||||
Repo: repo,
|
||||
Environment: app.Spec.Source.Environment,
|
||||
Path: app.Spec.Source.Path,
|
||||
Revision: revision,
|
||||
ComponentParameterOverrides: mfReqOverrides,
|
||||
Revision: app.Spec.Source.TargetRevision,
|
||||
ComponentParameterOverrides: overrides,
|
||||
AppLabel: app.Name,
|
||||
ValueFiles: app.Spec.Source.ValuesFiles,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
targetObjs := make([]*unstructured.Unstructured, 0)
|
||||
for _, manifest := range manifestInfo.Manifests {
|
||||
targetObjs := make([]*unstructured.Unstructured, len(manifestInfo.Manifests))
|
||||
for i, manifest := range manifestInfo.Manifests {
|
||||
obj, err := v1alpha1.UnmarshalToUnstructured(manifest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if isHook(obj) {
|
||||
continue
|
||||
}
|
||||
targetObjs = append(targetObjs, obj)
|
||||
targetObjs[i] = obj
|
||||
}
|
||||
return targetObjs, manifestInfo, nil
|
||||
}
|
||||
|
||||
func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetObjs []*unstructured.Unstructured) (
|
||||
[]*unstructured.Unstructured, map[string]*unstructured.Unstructured, error) {
|
||||
server, namespace := app.Spec.Destination.Server, app.Spec.Destination.Namespace
|
||||
|
||||
log.Infof("Comparing app %s state in cluster %s (namespace: %s)", app.ObjectMeta.Name, server, namespace)
|
||||
// Get the REST config for the cluster corresponding to the environment
|
||||
clst, err := s.db.GetCluster(context.Background(), app.Spec.Destination.Server)
|
||||
clst, err := ks.db.GetCluster(context.Background(), server)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
restConfig := clst.RESTConfig()
|
||||
|
||||
// Retrieve the live versions of the objects. exclude any hook objects
|
||||
labeledObjs, err := kubeutil.GetResourcesWithLabel(restConfig, app.Spec.Destination.Namespace, common.LabelApplicationName, app.Name)
|
||||
// Retrieve the live versions of the objects
|
||||
liveObjs, err := kubeutil.GetResourcesWithLabel(restConfig, namespace, common.LabelApplicationName, app.Name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
liveObjs := make([]*unstructured.Unstructured, 0)
|
||||
for _, obj := range labeledObjs {
|
||||
if isHook(obj) {
|
||||
continue
|
||||
}
|
||||
liveObjs = append(liveObjs, obj)
|
||||
}
|
||||
|
||||
liveObjByFullName := groupLiveObjects(liveObjs, targetObjs)
|
||||
liveObjByFullName := ks.groupLiveObjects(liveObjs, targetObjs)
|
||||
|
||||
controlledLiveObj := make([]*unstructured.Unstructured, len(targetObjs))
|
||||
|
||||
@@ -176,7 +145,7 @@ func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetOb
|
||||
for i, targetObj := range targetObjs {
|
||||
fullName := getResourceFullName(targetObj)
|
||||
liveObj := liveObjByFullName[fullName]
|
||||
if liveObj == nil && targetObj.GetName() != "" {
|
||||
if liveObj == nil {
|
||||
// If we get here, it indicates we did not find the live resource when querying using
|
||||
// our app label. However, it is possible that the resource was created/modified outside
|
||||
// of ArgoCD. In order to determine that it is truly missing, we fall back to perform a
|
||||
@@ -190,7 +159,7 @@ func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetOb
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
liveObj, err = kubeutil.GetLiveResource(dclient, targetObj, apiResource, app.Spec.Destination.Namespace)
|
||||
liveObj, err = kubeutil.GetLiveResource(dclient, targetObj, apiResource, namespace)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -199,43 +168,6 @@ func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetOb
|
||||
delete(liveObjByFullName, fullName)
|
||||
}
|
||||
|
||||
return controlledLiveObj, liveObjByFullName, nil
|
||||
}
|
||||
|
||||
// CompareAppState compares application git state to the live app state, using the specified
|
||||
// revision and supplied overrides. If revision or overrides are empty, then compares against
|
||||
// revision and overrides in the app spec.
|
||||
func (s *ksonnetAppStateManager) CompareAppState(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) (
|
||||
*v1alpha1.ComparisonResult, *repository.ManifestResponse, []v1alpha1.ApplicationCondition, error) {
|
||||
|
||||
failedToLoadObjs := false
|
||||
conditions := make([]v1alpha1.ApplicationCondition, 0)
|
||||
targetObjs, manifestInfo, err := s.getTargetObjs(app, revision, overrides)
|
||||
if err != nil {
|
||||
targetObjs = make([]*unstructured.Unstructured, 0)
|
||||
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
|
||||
failedToLoadObjs = true
|
||||
}
|
||||
|
||||
controlledLiveObj, liveObjByFullName, err := s.getLiveObjs(app, targetObjs)
|
||||
if err != nil {
|
||||
controlledLiveObj = make([]*unstructured.Unstructured, len(targetObjs))
|
||||
liveObjByFullName = make(map[string]*unstructured.Unstructured)
|
||||
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
|
||||
failedToLoadObjs = true
|
||||
}
|
||||
|
||||
for _, liveObj := range controlledLiveObj {
|
||||
if liveObj != nil && liveObj.GetLabels() != nil {
|
||||
if appLabelVal, ok := liveObj.GetLabels()[common.LabelApplicationName]; ok && appLabelVal != "" && appLabelVal != app.Name {
|
||||
conditions = append(conditions, v1alpha1.ApplicationCondition{
|
||||
Type: v1alpha1.ApplicationConditionSharedResourceWarning,
|
||||
Message: fmt.Sprintf("Resource %s/%s is controller by applications '%s' and '%s'", liveObj.GetKind(), liveObj.GetName(), app.Name, appLabelVal),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move root level live resources to controlledLiveObj and add nil to targetObjs to indicate that target object is missing
|
||||
for fullName := range liveObjByFullName {
|
||||
liveObj := liveObjByFullName[fullName]
|
||||
@@ -245,14 +177,11 @@ func (s *ksonnetAppStateManager) CompareAppState(app *v1alpha1.Application, revi
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Comparing app %s state in cluster %s (namespace: %s)", app.ObjectMeta.Name, app.Spec.Destination.Server, app.Spec.Destination.Namespace)
|
||||
|
||||
// Do the actual comparison
|
||||
diffResults, err := diff.DiffArray(targetObjs, controlledLiveObj)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
comparisonStatus := v1alpha1.ComparisonStatusSynced
|
||||
|
||||
resources := make([]v1alpha1.ResourceState, len(targetObjs))
|
||||
@@ -277,7 +206,7 @@ func (s *ksonnetAppStateManager) CompareAppState(app *v1alpha1.Application, revi
|
||||
} else {
|
||||
targetObjBytes, err := json.Marshal(targetObjs[i].Object)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
resState.TargetState = string(targetObjBytes)
|
||||
}
|
||||
@@ -290,7 +219,7 @@ func (s *ksonnetAppStateManager) CompareAppState(app *v1alpha1.Application, revi
|
||||
} else {
|
||||
liveObjBytes, err := json.Marshal(controlledLiveObj[i].Object)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
resState.LiveState = string(liveObjBytes)
|
||||
}
|
||||
@@ -303,22 +232,19 @@ func (s *ksonnetAppStateManager) CompareAppState(app *v1alpha1.Application, revi
|
||||
if liveResource != nil {
|
||||
childResources, err := getChildren(liveResource, liveObjByFullName)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
resource.ChildLiveResources = childResources
|
||||
resources[i] = resource
|
||||
}
|
||||
}
|
||||
if failedToLoadObjs {
|
||||
comparisonStatus = v1alpha1.ComparisonStatusUnknown
|
||||
}
|
||||
compResult := v1alpha1.ComparisonResult{
|
||||
ComparedTo: app.Spec.Source,
|
||||
ComparedAt: metav1.Time{Time: time.Now().UTC()},
|
||||
Resources: resources,
|
||||
Status: comparisonStatus,
|
||||
}
|
||||
return &compResult, manifestInfo, conditions, nil
|
||||
return &compResult, manifestInfo, nil
|
||||
}
|
||||
|
||||
func hasParent(obj *unstructured.Unstructured) bool {
|
||||
@@ -360,7 +286,29 @@ func getResourceFullName(obj *unstructured.Unstructured) string {
|
||||
return fmt.Sprintf("%s:%s", obj.GetKind(), obj.GetName())
|
||||
}
|
||||
|
||||
func (s *ksonnetAppStateManager) getRepo(repoURL string) *v1alpha1.Repository {
|
||||
func (s *KsonnetAppStateManager) SyncAppState(
|
||||
app *v1alpha1.Application, revision string, overrides *[]v1alpha1.ComponentParameter, dryRun bool, prune bool) *v1alpha1.OperationState {
|
||||
|
||||
if revision != "" {
|
||||
app.Spec.Source.TargetRevision = revision
|
||||
}
|
||||
|
||||
if overrides != nil {
|
||||
app.Spec.Source.ComponentParameterOverrides = *overrides
|
||||
}
|
||||
|
||||
opRes, manifest := s.syncAppResources(app, dryRun, prune)
|
||||
if !dryRun && opRes.Phase.Successful() {
|
||||
err := s.persistDeploymentInfo(app, manifest.Revision, manifest.Params, nil)
|
||||
if err != nil {
|
||||
opRes.Phase = v1alpha1.OperationError
|
||||
opRes.Message = fmt.Sprintf("failed to record sync to history: %v", err)
|
||||
}
|
||||
}
|
||||
return opRes
|
||||
}
|
||||
|
||||
func (s *KsonnetAppStateManager) getRepo(repoURL string) *v1alpha1.Repository {
|
||||
repo, err := s.db.GetRepository(context.Background(), repoURL)
|
||||
if err != nil {
|
||||
// If we couldn't retrieve from the repo service, assume public repositories
|
||||
@@ -369,7 +317,7 @@ func (s *ksonnetAppStateManager) getRepo(repoURL string) *v1alpha1.Repository {
|
||||
return repo
|
||||
}
|
||||
|
||||
func (s *ksonnetAppStateManager) persistDeploymentInfo(
|
||||
func (s *KsonnetAppStateManager) persistDeploymentInfo(
|
||||
app *v1alpha1.Application, revision string, envParams []*v1alpha1.ComponentParameter, overrides *[]v1alpha1.ComponentParameter) error {
|
||||
|
||||
params := make([]v1alpha1.ComponentParameter, len(envParams))
|
||||
@@ -377,16 +325,16 @@ func (s *ksonnetAppStateManager) persistDeploymentInfo(
|
||||
param := *envParams[i]
|
||||
params[i] = param
|
||||
}
|
||||
var nextID int64 = 0
|
||||
var nextId int64 = 0
|
||||
if len(app.Status.History) > 0 {
|
||||
nextID = app.Status.History[len(app.Status.History)-1].ID + 1
|
||||
nextId = app.Status.History[len(app.Status.History)-1].ID + 1
|
||||
}
|
||||
history := append(app.Status.History, v1alpha1.DeploymentInfo{
|
||||
ComponentParameterOverrides: app.Spec.Source.ComponentParameterOverrides,
|
||||
Revision: revision,
|
||||
Params: params,
|
||||
DeployedAt: metav1.NewTime(time.Now().UTC()),
|
||||
ID: nextID,
|
||||
DeployedAt: metav1.NewTime(time.Now()),
|
||||
ID: nextId,
|
||||
})
|
||||
|
||||
if len(history) > maxHistoryCnt {
|
||||
@@ -405,6 +353,131 @@ func (s *ksonnetAppStateManager) persistDeploymentInfo(
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *KsonnetAppStateManager) syncAppResources(
|
||||
app *v1alpha1.Application,
|
||||
dryRun bool,
|
||||
prune bool) (*v1alpha1.OperationState, *repository.ManifestResponse) {
|
||||
|
||||
opRes := v1alpha1.OperationState{
|
||||
SyncResult: &v1alpha1.SyncOperationResult{},
|
||||
}
|
||||
|
||||
comparison, manifestInfo, err := s.CompareAppState(app)
|
||||
if err != nil {
|
||||
opRes.Phase = v1alpha1.OperationError
|
||||
opRes.Message = err.Error()
|
||||
return &opRes, manifestInfo
|
||||
}
|
||||
|
||||
clst, err := s.db.GetCluster(context.Background(), app.Spec.Destination.Server)
|
||||
if err != nil {
|
||||
opRes.Phase = v1alpha1.OperationError
|
||||
opRes.Message = err.Error()
|
||||
return &opRes, manifestInfo
|
||||
}
|
||||
config := clst.RESTConfig()
|
||||
|
||||
opRes.SyncResult.Resources = make([]*v1alpha1.ResourceDetails, len(comparison.Resources))
|
||||
liveObjs := make([]*unstructured.Unstructured, len(comparison.Resources))
|
||||
targetObjs := make([]*unstructured.Unstructured, len(comparison.Resources))
|
||||
|
||||
// First perform a `kubectl apply --dry-run` against all the manifests. This will detect most
|
||||
// (but not all) validation issues with the users' manifests (e.g. will detect syntax issues,
|
||||
// but will not not detect if they are mutating immutable fields). If anything fails, we will
|
||||
// refuse to perform the sync.
|
||||
dryRunSuccessful := true
|
||||
for i, resourceState := range comparison.Resources {
|
||||
liveObj, err := resourceState.LiveObject()
|
||||
if err != nil {
|
||||
opRes.Phase = v1alpha1.OperationError
|
||||
opRes.Message = fmt.Sprintf("Failed to unmarshal live object: %v", err)
|
||||
return &opRes, manifestInfo
|
||||
}
|
||||
targetObj, err := resourceState.TargetObject()
|
||||
if err != nil {
|
||||
opRes.Phase = v1alpha1.OperationError
|
||||
opRes.Message = fmt.Sprintf("Failed to unmarshal target object: %v", err)
|
||||
return &opRes, manifestInfo
|
||||
}
|
||||
liveObjs[i] = liveObj
|
||||
targetObjs[i] = targetObj
|
||||
resDetails, successful := syncObject(config, app.Spec.Destination.Namespace, targetObj, liveObj, prune, true)
|
||||
if !successful {
|
||||
dryRunSuccessful = false
|
||||
}
|
||||
opRes.SyncResult.Resources[i] = &resDetails
|
||||
}
|
||||
if !dryRunSuccessful {
|
||||
opRes.Phase = v1alpha1.OperationFailed
|
||||
opRes.Message = "one or more objects failed to apply (dry run)"
|
||||
return &opRes, manifestInfo
|
||||
}
|
||||
if dryRun {
|
||||
opRes.Phase = v1alpha1.OperationSucceeded
|
||||
opRes.Message = "successfully synced (dry run)"
|
||||
return &opRes, manifestInfo
|
||||
}
|
||||
// If we get here, all objects passed their dry-run, so we are now ready to actually perform the
|
||||
// `kubectl apply`. Loop through the resources again, this time without dry-run.
|
||||
syncSuccessful := true
|
||||
for i := range comparison.Resources {
|
||||
resDetails, successful := syncObject(config, app.Spec.Destination.Namespace, targetObjs[i], liveObjs[i], prune, false)
|
||||
if !successful {
|
||||
syncSuccessful = false
|
||||
}
|
||||
opRes.SyncResult.Resources[i] = &resDetails
|
||||
}
|
||||
if !syncSuccessful {
|
||||
opRes.Message = "one or more objects failed to apply"
|
||||
opRes.Phase = v1alpha1.OperationFailed
|
||||
} else {
|
||||
opRes.Message = "successfully synced"
|
||||
opRes.Phase = v1alpha1.OperationSucceeded
|
||||
}
|
||||
return &opRes, manifestInfo
|
||||
}
|
||||
|
||||
// syncObject performs a sync of a single resource
|
||||
func syncObject(config *rest.Config, namespace string, targetObj, liveObj *unstructured.Unstructured, prune, dryRun bool) (v1alpha1.ResourceDetails, bool) {
|
||||
obj := targetObj
|
||||
if obj == nil {
|
||||
obj = liveObj
|
||||
}
|
||||
resDetails := v1alpha1.ResourceDetails{
|
||||
Name: obj.GetName(),
|
||||
Kind: obj.GetKind(),
|
||||
Namespace: namespace,
|
||||
}
|
||||
needsDelete := targetObj == nil
|
||||
successful := true
|
||||
if needsDelete {
|
||||
if prune {
|
||||
if dryRun {
|
||||
resDetails.Message = "pruned (dry run)"
|
||||
} else {
|
||||
err := kubeutil.DeleteResource(config, liveObj, namespace)
|
||||
if err != nil {
|
||||
resDetails.Message = err.Error()
|
||||
successful = false
|
||||
} else {
|
||||
resDetails.Message = "pruned"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resDetails.Message = "ignored (requires pruning)"
|
||||
}
|
||||
} else {
|
||||
message, err := kube.ApplyResource(config, targetObj, namespace, dryRun)
|
||||
if err != nil {
|
||||
resDetails.Message = err.Error()
|
||||
successful = false
|
||||
} else {
|
||||
resDetails.Message = message
|
||||
}
|
||||
}
|
||||
return resDetails, successful
|
||||
}
|
||||
|
||||
// NewAppStateManager creates new instance of Ksonnet app comparator
|
||||
func NewAppStateManager(
|
||||
db db.ArgoDB,
|
||||
@@ -412,7 +485,7 @@ func NewAppStateManager(
|
||||
repoClientset reposerver.Clientset,
|
||||
namespace string,
|
||||
) AppStateManager {
|
||||
return &ksonnetAppStateManager{
|
||||
return &KsonnetAppStateManager{
|
||||
db: db,
|
||||
appclientset: appclientset,
|
||||
repoClientset: repoClientset,
|
||||
|
||||
@@ -1,858 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/reposerver/repository"
|
||||
"github.com/argoproj/argo-cd/util/argo"
|
||||
"github.com/argoproj/argo-cd/util/kube"
|
||||
)
|
||||
|
||||
type syncContext struct {
|
||||
appName string
|
||||
comparison *appv1.ComparisonResult
|
||||
config *rest.Config
|
||||
dynClientPool dynamic.ClientPool
|
||||
disco *discovery.DiscoveryClient
|
||||
namespace string
|
||||
syncOp *appv1.SyncOperation
|
||||
syncRes *appv1.SyncOperationResult
|
||||
opState *appv1.OperationState
|
||||
manifestInfo *repository.ManifestResponse
|
||||
log *log.Entry
|
||||
// lock to protect concurrent updates of the result list
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (s *ksonnetAppStateManager) SyncAppState(app *appv1.Application, state *appv1.OperationState) {
|
||||
// Sync requests are usually requested with ambiguous revisions (e.g. master, HEAD, v1.2.3).
|
||||
// This can change meaning when resuming operations (e.g a hook sync). After calculating a
|
||||
// concrete git commit SHA, the SHA is remembered in the status.operationState.syncResult and
|
||||
// rollbackResult fields. This ensures that when resuming an operation, we sync to the same
|
||||
// revision that we initially started with.
|
||||
var revision string
|
||||
var syncOp appv1.SyncOperation
|
||||
var syncRes *appv1.SyncOperationResult
|
||||
var overrides []appv1.ComponentParameter
|
||||
|
||||
if state.Operation.Sync != nil {
|
||||
syncOp = *state.Operation.Sync
|
||||
if state.SyncResult != nil {
|
||||
syncRes = state.SyncResult
|
||||
revision = state.SyncResult.Revision
|
||||
} else {
|
||||
syncRes = &appv1.SyncOperationResult{}
|
||||
state.SyncResult = syncRes
|
||||
}
|
||||
} else if state.Operation.Rollback != nil {
|
||||
var deploymentInfo *appv1.DeploymentInfo
|
||||
for _, info := range app.Status.History {
|
||||
if info.ID == app.Operation.Rollback.ID {
|
||||
deploymentInfo = &info
|
||||
break
|
||||
}
|
||||
}
|
||||
if deploymentInfo == nil {
|
||||
state.Phase = appv1.OperationFailed
|
||||
state.Message = fmt.Sprintf("application %s does not have deployment with id %v", app.Name, app.Operation.Rollback.ID)
|
||||
return
|
||||
}
|
||||
// Rollback is just a convenience around Sync
|
||||
syncOp = appv1.SyncOperation{
|
||||
Revision: deploymentInfo.Revision,
|
||||
DryRun: state.Operation.Rollback.DryRun,
|
||||
Prune: state.Operation.Rollback.Prune,
|
||||
SyncStrategy: &appv1.SyncStrategy{Apply: &appv1.SyncStrategyApply{}},
|
||||
}
|
||||
overrides = deploymentInfo.ComponentParameterOverrides
|
||||
if state.RollbackResult != nil {
|
||||
syncRes = state.RollbackResult
|
||||
revision = state.RollbackResult.Revision
|
||||
} else {
|
||||
syncRes = &appv1.SyncOperationResult{}
|
||||
state.RollbackResult = syncRes
|
||||
}
|
||||
} else {
|
||||
state.Phase = appv1.OperationFailed
|
||||
state.Message = "Invalid operation request: no operation specified"
|
||||
return
|
||||
}
|
||||
|
||||
if revision == "" {
|
||||
// if we get here, it means we did not remember a commit SHA which we should be syncing to.
|
||||
// This typically indicates we are just about to begin a brand new sync/rollback operation.
|
||||
// Take the value in the requested operation. We will resolve this to a SHA later.
|
||||
revision = syncOp.Revision
|
||||
}
|
||||
comparison, manifestInfo, conditions, err := s.CompareAppState(app, revision, overrides)
|
||||
if err != nil {
|
||||
state.Phase = appv1.OperationError
|
||||
state.Message = err.Error()
|
||||
return
|
||||
}
|
||||
errConditions := make([]appv1.ApplicationCondition, 0)
|
||||
for i := range conditions {
|
||||
if conditions[i].IsError() {
|
||||
errConditions = append(errConditions, conditions[i])
|
||||
}
|
||||
}
|
||||
if len(errConditions) > 0 {
|
||||
state.Phase = appv1.OperationError
|
||||
state.Message = argo.FormatAppConditions(errConditions)
|
||||
return
|
||||
}
|
||||
// We now have a concrete commit SHA. Set this in the sync result revision so that we remember
|
||||
// what we should be syncing to when resuming operations.
|
||||
syncRes.Revision = manifestInfo.Revision
|
||||
|
||||
clst, err := s.db.GetCluster(context.Background(), app.Spec.Destination.Server)
|
||||
if err != nil {
|
||||
state.Phase = appv1.OperationError
|
||||
state.Message = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
restConfig := clst.RESTConfig()
|
||||
dynClientPool := dynamic.NewDynamicClientPool(restConfig)
|
||||
disco, err := discovery.NewDiscoveryClientForConfig(restConfig)
|
||||
if err != nil {
|
||||
state.Phase = appv1.OperationError
|
||||
state.Message = fmt.Sprintf("Failed to initialize dynamic client: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
syncCtx := syncContext{
|
||||
appName: app.Name,
|
||||
comparison: comparison,
|
||||
config: restConfig,
|
||||
dynClientPool: dynClientPool,
|
||||
disco: disco,
|
||||
namespace: app.Spec.Destination.Namespace,
|
||||
syncOp: &syncOp,
|
||||
syncRes: syncRes,
|
||||
opState: state,
|
||||
manifestInfo: manifestInfo,
|
||||
log: log.WithFields(log.Fields{"application": app.Name}),
|
||||
}
|
||||
|
||||
if state.Phase == appv1.OperationTerminating {
|
||||
syncCtx.terminate()
|
||||
} else {
|
||||
syncCtx.sync()
|
||||
}
|
||||
|
||||
if !syncOp.DryRun && syncCtx.opState.Phase.Successful() {
|
||||
err := s.persistDeploymentInfo(app, manifestInfo.Revision, manifestInfo.Params, nil)
|
||||
if err != nil {
|
||||
state.Phase = appv1.OperationError
|
||||
state.Message = fmt.Sprintf("failed to record sync to history: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncTask holds the live and target object. At least one should be non-nil. A targetObj of nil
|
||||
// indicates the live object needs to be pruned. A liveObj of nil indicates the object has yet to
|
||||
// be deployed
|
||||
type syncTask struct {
|
||||
liveObj *unstructured.Unstructured
|
||||
targetObj *unstructured.Unstructured
|
||||
}
|
||||
|
||||
// sync has performs the actual apply or hook based sync
|
||||
func (sc *syncContext) sync() {
|
||||
syncTasks, successful := sc.generateSyncTasks()
|
||||
if !successful {
|
||||
return
|
||||
}
|
||||
// Perform a `kubectl apply --dry-run` against all the manifests. This will detect most (but
|
||||
// not all) validation issues with the user's manifests (e.g. will detect syntax issues, but
|
||||
// will not not detect if they are mutating immutable fields). If anything fails, we will refuse
|
||||
// to perform the sync.
|
||||
if !sc.startedPreSyncPhase() {
|
||||
// Optimization: we only wish to do this once per operation, performing additional dry-runs
|
||||
// is harmless, but redundant. The indicator we use to detect if we have already performed
|
||||
// the dry-run for this operation, is if the resource or hook list is empty.
|
||||
if !sc.doApplySync(syncTasks, true, false, sc.syncOp.DryRun) {
|
||||
sc.setOperationPhase(appv1.OperationFailed, "one or more objects failed to apply (dry run)")
|
||||
return
|
||||
}
|
||||
if sc.syncOp.DryRun {
|
||||
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced (dry run)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// All objects passed a `kubectl apply --dry-run`, so we are now ready to actually perform the sync.
|
||||
if sc.syncOp.SyncStrategy == nil {
|
||||
// default sync strategy to hook if no strategy
|
||||
sc.syncOp.SyncStrategy = &appv1.SyncStrategy{Hook: &appv1.SyncStrategyHook{}}
|
||||
}
|
||||
if sc.syncOp.SyncStrategy.Apply != nil {
|
||||
if !sc.startedSyncPhase() {
|
||||
if !sc.doApplySync(syncTasks, false, sc.syncOp.SyncStrategy.Apply.Force, true) {
|
||||
sc.setOperationPhase(appv1.OperationFailed, "one or more objects failed to apply")
|
||||
return
|
||||
}
|
||||
// If apply was successful, return here and force an app refresh. This is so the app
|
||||
// will become requeued into the workqueue, to force a new sync/health assessment before
|
||||
// marking the operation as completed
|
||||
return
|
||||
}
|
||||
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced")
|
||||
} else if sc.syncOp.SyncStrategy.Hook != nil {
|
||||
hooks, err := sc.getHooks()
|
||||
if err != nil {
|
||||
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("failed to generate hooks resources: %v", err))
|
||||
return
|
||||
}
|
||||
sc.doHookSync(syncTasks, hooks)
|
||||
} else {
|
||||
sc.setOperationPhase(appv1.OperationFailed, "Unknown sync strategy")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *syncContext) forceAppRefresh() {
|
||||
sc.comparison.ComparedAt = metav1.Time{}
|
||||
}
|
||||
|
||||
// generateSyncTasks() generates the list of sync tasks we will be performing during this sync.
|
||||
func (sc *syncContext) generateSyncTasks() ([]syncTask, bool) {
|
||||
syncTasks := make([]syncTask, 0)
|
||||
for _, resourceState := range sc.comparison.Resources {
|
||||
liveObj, err := resourceState.LiveObject()
|
||||
if err != nil {
|
||||
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("Failed to unmarshal live object: %v", err))
|
||||
return nil, false
|
||||
}
|
||||
targetObj, err := resourceState.TargetObject()
|
||||
if err != nil {
|
||||
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("Failed to unmarshal target object: %v", err))
|
||||
return nil, false
|
||||
}
|
||||
syncTask := syncTask{
|
||||
liveObj: liveObj,
|
||||
targetObj: targetObj,
|
||||
}
|
||||
syncTasks = append(syncTasks, syncTask)
|
||||
}
|
||||
return syncTasks, true
|
||||
}
|
||||
|
||||
// startedPreSyncPhase detects if we already started the PreSync stage of a sync operation.
|
||||
// This is equal to if we have anything in our resource or hook list
|
||||
func (sc *syncContext) startedPreSyncPhase() bool {
|
||||
if len(sc.syncRes.Resources) > 0 {
|
||||
return true
|
||||
}
|
||||
if len(sc.syncRes.Hooks) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// startedSyncPhase detects if we have already started the Sync stage of a sync operation.
|
||||
// This is equal to if the resource list is non-empty, or we we see Sync/PostSync hooks
|
||||
func (sc *syncContext) startedSyncPhase() bool {
|
||||
if len(sc.syncRes.Resources) > 0 {
|
||||
return true
|
||||
}
|
||||
for _, hookStatus := range sc.syncRes.Hooks {
|
||||
if hookStatus.Type == appv1.HookTypeSync || hookStatus.Type == appv1.HookTypePostSync {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// startedPostSyncPhase detects if we have already started the PostSync stage. This is equal to if
|
||||
// we see any PostSync hooks
|
||||
func (sc *syncContext) startedPostSyncPhase() bool {
|
||||
for _, hookStatus := range sc.syncRes.Hooks {
|
||||
if hookStatus.Type == appv1.HookTypePostSync {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (sc *syncContext) setOperationPhase(phase appv1.OperationPhase, message string) {
|
||||
if sc.opState.Phase != phase || sc.opState.Message != message {
|
||||
sc.log.Infof("Updating operation state. phase: %s -> %s, message: '%s' -> '%s'", sc.opState.Phase, phase, sc.opState.Message, message)
|
||||
}
|
||||
sc.opState.Phase = phase
|
||||
sc.opState.Message = message
|
||||
}
|
||||
|
||||
// applyObject performs a `kubectl apply` of a single resource
|
||||
func (sc *syncContext) applyObject(targetObj *unstructured.Unstructured, dryRun bool, force bool) appv1.ResourceDetails {
|
||||
resDetails := appv1.ResourceDetails{
|
||||
Name: targetObj.GetName(),
|
||||
Kind: targetObj.GetKind(),
|
||||
Namespace: sc.namespace,
|
||||
}
|
||||
message, err := kube.ApplyResource(sc.config, targetObj, sc.namespace, dryRun, force)
|
||||
if err != nil {
|
||||
resDetails.Message = err.Error()
|
||||
resDetails.Status = appv1.ResourceDetailsSyncFailed
|
||||
return resDetails
|
||||
}
|
||||
resDetails.Message = message
|
||||
resDetails.Status = appv1.ResourceDetailsSynced
|
||||
return resDetails
|
||||
}
|
||||
|
||||
// pruneObject deletes the object if both prune is true and dryRun is false. Otherwise appropriate message
|
||||
func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dryRun bool) appv1.ResourceDetails {
|
||||
resDetails := appv1.ResourceDetails{
|
||||
Name: liveObj.GetName(),
|
||||
Kind: liveObj.GetKind(),
|
||||
Namespace: liveObj.GetNamespace(),
|
||||
}
|
||||
if prune {
|
||||
if dryRun {
|
||||
resDetails.Message = "pruned (dry run)"
|
||||
resDetails.Status = appv1.ResourceDetailsSyncedAndPruned
|
||||
} else {
|
||||
err := kube.DeleteResource(sc.config, liveObj, sc.namespace)
|
||||
if err != nil {
|
||||
resDetails.Message = err.Error()
|
||||
resDetails.Status = appv1.ResourceDetailsSyncFailed
|
||||
} else {
|
||||
resDetails.Message = "pruned"
|
||||
resDetails.Status = appv1.ResourceDetailsSyncedAndPruned
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resDetails.Message = "ignored (requires pruning)"
|
||||
resDetails.Status = appv1.ResourceDetailsPruningRequired
|
||||
}
|
||||
return resDetails
|
||||
}
|
||||
|
||||
// performs a apply based sync of the given sync tasks (possibly pruning the objects).
|
||||
// If update is true, will updates the resource details with the result.
|
||||
// Or if the prune/apply failed, will also update the result.
|
||||
func (sc *syncContext) doApplySync(syncTasks []syncTask, dryRun, force, update bool) bool {
|
||||
syncSuccessful := true
|
||||
// apply all resources in parallel
|
||||
var wg sync.WaitGroup
|
||||
for _, task := range syncTasks {
|
||||
wg.Add(1)
|
||||
go func(t syncTask) {
|
||||
defer wg.Done()
|
||||
var resDetails appv1.ResourceDetails
|
||||
if t.targetObj == nil {
|
||||
resDetails = sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
|
||||
} else {
|
||||
if isHook(t.targetObj) {
|
||||
return
|
||||
}
|
||||
resDetails = sc.applyObject(t.targetObj, dryRun, force)
|
||||
}
|
||||
if !resDetails.Status.Successful() {
|
||||
syncSuccessful = false
|
||||
}
|
||||
if update || !resDetails.Status.Successful() {
|
||||
sc.setResourceDetails(&resDetails)
|
||||
}
|
||||
}(task)
|
||||
}
|
||||
wg.Wait()
|
||||
return syncSuccessful
|
||||
}
|
||||
|
||||
// doHookSync initiates (or continues) a hook-based sync. This method will be invoked when there may
|
||||
// already be in-flight (potentially incomplete) jobs/workflows, and should be idempotent.
|
||||
func (sc *syncContext) doHookSync(syncTasks []syncTask, hooks []*unstructured.Unstructured) {
|
||||
// 1. Run PreSync hooks
|
||||
if !sc.runHooks(hooks, appv1.HookTypePreSync) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Run Sync hooks (e.g. blue-green sync workflow)
|
||||
// Before performing Sync hooks, apply any normal manifests which aren't annotated with a hook.
|
||||
// We only want to do this once per operation.
|
||||
shouldContinue := true
|
||||
if !sc.startedSyncPhase() {
|
||||
if !sc.syncNonHookTasks(syncTasks) {
|
||||
sc.setOperationPhase(appv1.OperationFailed, "one or more objects failed to apply")
|
||||
return
|
||||
}
|
||||
shouldContinue = false
|
||||
}
|
||||
if !sc.runHooks(hooks, appv1.HookTypeSync) {
|
||||
shouldContinue = false
|
||||
}
|
||||
if !shouldContinue {
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Run PostSync hooks
|
||||
// Before running PostSync hooks, we want to make rollout is complete (app is healthy). If we
|
||||
// already started the post-sync phase, then we do not need to perform the health check.
|
||||
postSyncHooks, _ := sc.getHooks(appv1.HookTypePostSync)
|
||||
if len(postSyncHooks) > 0 && !sc.startedPostSyncPhase() {
|
||||
healthState, err := setApplicationHealth(sc.comparison)
|
||||
sc.log.Infof("PostSync application health check: %s", healthState.Status)
|
||||
if err != nil {
|
||||
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("failed to check application health: %v", err))
|
||||
return
|
||||
}
|
||||
if healthState.Status != appv1.HealthStatusHealthy {
|
||||
sc.setOperationPhase(appv1.OperationRunning, fmt.Sprintf("waiting for %s state to run %s hooks (current health: %s)", appv1.HealthStatusHealthy, appv1.HookTypePostSync, healthState.Status))
|
||||
return
|
||||
}
|
||||
}
|
||||
if !sc.runHooks(hooks, appv1.HookTypePostSync) {
|
||||
return
|
||||
}
|
||||
|
||||
// if we get here, all hooks successfully completed
|
||||
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced")
|
||||
}
|
||||
|
||||
// getHooks returns all ArgoCD hooks, optionally filtered by ones of the specific type(s)
|
||||
func (sc *syncContext) getHooks(hookTypes ...appv1.HookType) ([]*unstructured.Unstructured, error) {
|
||||
var hooks []*unstructured.Unstructured
|
||||
for _, manifest := range sc.manifestInfo.Manifests {
|
||||
var hook unstructured.Unstructured
|
||||
err := json.Unmarshal([]byte(manifest), &hook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isArgoHook(&hook) {
|
||||
// TODO: in the future, if we want to map helm hooks to ArgoCD lifecycles, we should
|
||||
// include helm hooks in the returned list
|
||||
continue
|
||||
}
|
||||
if len(hookTypes) > 0 {
|
||||
match := false
|
||||
for _, desiredType := range hookTypes {
|
||||
if isHookType(&hook, desiredType) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
}
|
||||
hooks = append(hooks, &hook)
|
||||
}
|
||||
return hooks, nil
|
||||
}
|
||||
|
||||
// runHooks iterates & filters the target manifests for resources of the specified hook type, then
|
||||
// creates the resource. Updates the sc.opRes.hooks with the current status. Returns whether or not
|
||||
// we should continue to the next hook phase.
|
||||
func (sc *syncContext) runHooks(hooks []*unstructured.Unstructured, hookType appv1.HookType) bool {
|
||||
shouldContinue := true
|
||||
for _, hook := range hooks {
|
||||
if hookType == appv1.HookTypeSync && isHookType(hook, appv1.HookTypeSkip) {
|
||||
// If we get here, we are invoking all sync hooks and reached a resource that is
|
||||
// annotated with the Skip hook. This will update the resource details to indicate it
|
||||
// was skipped due to annotation
|
||||
sc.setResourceDetails(&appv1.ResourceDetails{
|
||||
Name: hook.GetName(),
|
||||
Kind: hook.GetKind(),
|
||||
Namespace: sc.namespace,
|
||||
Message: "Skipped",
|
||||
})
|
||||
continue
|
||||
}
|
||||
if !isHookType(hook, hookType) {
|
||||
continue
|
||||
}
|
||||
updated, err := sc.runHook(hook, hookType)
|
||||
if err != nil {
|
||||
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("%s hook error: %v", hookType, err))
|
||||
return false
|
||||
}
|
||||
if updated {
|
||||
// If the result of running a hook, caused us to modify hook resource state, we should
|
||||
// not proceed to the next hook phase. This is because before proceeding to the next
|
||||
// phase, we want a full health assessment to happen. By returning early, we allow
|
||||
// the application to get requeued into the controller workqueue, and on the next
|
||||
// process iteration, a new CompareAppState() will be performed to get the most
|
||||
// up-to-date live state. This enables us to accurately wait for an application to
|
||||
// become Healthy before proceeding to run PostSync tasks.
|
||||
shouldContinue = false
|
||||
}
|
||||
}
|
||||
if !shouldContinue {
|
||||
sc.log.Infof("Stopping after %s phase due to modifications to hook resource state", hookType)
|
||||
return false
|
||||
}
|
||||
completed, successful := areHooksCompletedSuccessful(hookType, sc.syncRes.Hooks)
|
||||
if !completed {
|
||||
return false
|
||||
}
|
||||
if !successful {
|
||||
sc.setOperationPhase(appv1.OperationFailed, fmt.Sprintf("%s hook failed", hookType))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// runHook runs the supplied hook and updates the hook status. Returns true if the result of
|
||||
// invoking this method resulted in changes to any hook status
|
||||
func (sc *syncContext) runHook(hook *unstructured.Unstructured, hookType appv1.HookType) (bool, error) {
|
||||
// Hook resources names are deterministic, whether they are defined by the user (metadata.name),
|
||||
// or formulated at the time of the operation (metadata.generateName). If user specifies
|
||||
// metadata.generateName, then we will generate a formulated metadata.name before submission.
|
||||
if hook.GetName() == "" {
|
||||
postfix := strings.ToLower(fmt.Sprintf("%s-%s-%d", sc.syncRes.Revision[0:7], hookType, sc.opState.StartedAt.UTC().Unix()))
|
||||
generatedName := hook.GetGenerateName()
|
||||
hook = hook.DeepCopy()
|
||||
hook.SetName(fmt.Sprintf("%s%s", generatedName, postfix))
|
||||
}
|
||||
// Check our hook statuses to see if we already completed this hook.
|
||||
// If so, this method is a noop
|
||||
prevStatus := sc.getHookStatus(hook, hookType)
|
||||
if prevStatus != nil && prevStatus.Status.Completed() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
gvk := hook.GroupVersionKind()
|
||||
dclient, err := sc.dynClientPool.ClientForGroupVersionKind(gvk)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
apiResource, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
resIf := dclient.Resource(apiResource, sc.namespace)
|
||||
|
||||
var liveObj *unstructured.Unstructured
|
||||
existing, err := resIf.Get(hook.GetName(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if !apierr.IsNotFound(err) {
|
||||
return false, fmt.Errorf("Failed to get status of %s hook %s '%s': %v", hookType, gvk, hook.GetName(), err)
|
||||
}
|
||||
hook = hook.DeepCopy()
|
||||
err = kube.SetLabel(hook, common.LabelApplicationName, sc.appName)
|
||||
if err != nil {
|
||||
sc.log.Warnf("Failed to set application label on hook %v: %v", hook, err)
|
||||
}
|
||||
_, err := kube.ApplyResource(sc.config, hook, sc.namespace, false, false)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to create %s hook %s '%s': %v", hookType, gvk, hook.GetName(), err)
|
||||
}
|
||||
created, err := resIf.Get(hook.GetName(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("Failed to get status of %s hook %s '%s': %v", hookType, gvk, hook.GetName(), err)
|
||||
}
|
||||
sc.log.Infof("%s hook %s '%s' created", hookType, gvk, created.GetName())
|
||||
sc.setOperationPhase(appv1.OperationRunning, fmt.Sprintf("running %s hooks", hookType))
|
||||
liveObj = created
|
||||
} else {
|
||||
liveObj = existing
|
||||
}
|
||||
hookStatus := newHookStatus(liveObj, hookType)
|
||||
if hookStatus.Status.Completed() {
|
||||
if enforceDeletePolicy(hook, hookStatus.Status) {
|
||||
err = sc.deleteHook(hook.GetName(), hook.GetKind(), hook.GetAPIVersion())
|
||||
if err != nil {
|
||||
hookStatus.Status = appv1.OperationFailed
|
||||
hookStatus.Message = fmt.Sprintf("failed to delete %s hook: %v", hookStatus.Status, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sc.updateHookStatus(hookStatus), nil
|
||||
}
|
||||
|
||||
// enforceDeletePolicy examines the hook deletion policy of a object and deletes it based on the status
|
||||
func enforceDeletePolicy(hook *unstructured.Unstructured, phase appv1.OperationPhase) bool {
|
||||
annotations := hook.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return false
|
||||
}
|
||||
deletePolicies := strings.Split(annotations[common.AnnotationHookDeletePolicy], ",")
|
||||
for _, dp := range deletePolicies {
|
||||
policy := appv1.HookDeletePolicy(strings.TrimSpace(dp))
|
||||
if policy == appv1.HookDeletePolicyHookSucceeded && phase == appv1.OperationSucceeded {
|
||||
return true
|
||||
}
|
||||
if policy == appv1.HookDeletePolicyHookFailed && phase == appv1.OperationFailed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isHookType tells whether or not the supplied object is a hook of the specified type
|
||||
func isHookType(hook *unstructured.Unstructured, hookType appv1.HookType) bool {
|
||||
annotations := hook.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return false
|
||||
}
|
||||
resHookTypes := strings.Split(annotations[common.AnnotationHook], ",")
|
||||
for _, ht := range resHookTypes {
|
||||
if string(hookType) == strings.TrimSpace(ht) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isHook indicates if the object is either a ArgoCD or Helm hook
|
||||
func isHook(obj *unstructured.Unstructured) bool {
|
||||
return isArgoHook(obj) || isHelmHook(obj)
|
||||
}
|
||||
|
||||
// isHelmHook indicates if the supplied object is a helm hook
|
||||
func isHelmHook(obj *unstructured.Unstructured) bool {
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := annotations[common.AnnotationHook]
|
||||
return ok
|
||||
}
|
||||
|
||||
// isArgoHook indicates if the supplied object is an ArgoCD application lifecycle hook
|
||||
// (vs. a normal, synced application resource)
|
||||
func isArgoHook(obj *unstructured.Unstructured) bool {
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return false
|
||||
}
|
||||
resHookTypes := strings.Split(annotations[common.AnnotationHook], ",")
|
||||
for _, hookType := range resHookTypes {
|
||||
hookType = strings.TrimSpace(hookType)
|
||||
switch appv1.HookType(hookType) {
|
||||
case appv1.HookTypePreSync, appv1.HookTypeSync, appv1.HookTypePostSync:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// syncNonHookTasks syncs or prunes the objects that are not handled by hooks using an apply sync.
|
||||
// returns true if the sync was successful
|
||||
func (sc *syncContext) syncNonHookTasks(syncTasks []syncTask) bool {
|
||||
var nonHookTasks []syncTask
|
||||
for _, task := range syncTasks {
|
||||
if task.targetObj == nil {
|
||||
nonHookTasks = append(nonHookTasks, task)
|
||||
} else {
|
||||
annotations := task.targetObj.GetAnnotations()
|
||||
if annotations != nil && annotations[common.AnnotationHook] != "" {
|
||||
// we are doing a hook sync and this resource is annotated with a hook annotation
|
||||
continue
|
||||
}
|
||||
// if we get here, this resource does not have any hook annotation so we
|
||||
// should perform an `kubectl apply`
|
||||
nonHookTasks = append(nonHookTasks, task)
|
||||
}
|
||||
}
|
||||
return sc.doApplySync(nonHookTasks, false, sc.syncOp.SyncStrategy.Hook.Force, true)
|
||||
}
|
||||
|
||||
// setResourceDetails sets a resource details in the SyncResult.Resources list
|
||||
func (sc *syncContext) setResourceDetails(details *appv1.ResourceDetails) {
|
||||
sc.lock.Lock()
|
||||
defer sc.lock.Unlock()
|
||||
for i, res := range sc.syncRes.Resources {
|
||||
if res.Kind == details.Kind && res.Name == details.Name {
|
||||
// update existing value
|
||||
if res.Status != details.Status {
|
||||
sc.log.Infof("updated resource %s/%s status: %s -> %s", res.Kind, res.Name, res.Status, details.Status)
|
||||
}
|
||||
if res.Message != details.Message {
|
||||
sc.log.Infof("updated resource %s/%s message: %s -> %s", res.Kind, res.Name, res.Message, details.Message)
|
||||
}
|
||||
sc.syncRes.Resources[i] = details
|
||||
return
|
||||
}
|
||||
}
|
||||
sc.log.Infof("added resource %s/%s status: %s, message: %s", details.Kind, details.Name, details.Status, details.Message)
|
||||
sc.syncRes.Resources = append(sc.syncRes.Resources, details)
|
||||
}
|
||||
|
||||
func (sc *syncContext) getHookStatus(hookObj *unstructured.Unstructured, hookType appv1.HookType) *appv1.HookStatus {
|
||||
for _, hr := range sc.syncRes.Hooks {
|
||||
if hr.Name == hookObj.GetName() && hr.Kind == hookObj.GetKind() && hr.Type == hookType {
|
||||
return hr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newHookStatus(hook *unstructured.Unstructured, hookType appv1.HookType) appv1.HookStatus {
|
||||
hookStatus := appv1.HookStatus{
|
||||
Name: hook.GetName(),
|
||||
Kind: hook.GetKind(),
|
||||
APIVersion: hook.GetAPIVersion(),
|
||||
Type: hookType,
|
||||
}
|
||||
switch hookStatus.Kind {
|
||||
case "Job":
|
||||
var job batch.Job
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &job)
|
||||
if err != nil {
|
||||
hookStatus.Status = appv1.OperationError
|
||||
hookStatus.Message = err.Error()
|
||||
} else {
|
||||
failed := false
|
||||
var failMsg string
|
||||
complete := false
|
||||
var message string
|
||||
for _, condition := range job.Status.Conditions {
|
||||
switch condition.Type {
|
||||
case batch.JobFailed:
|
||||
failed = true
|
||||
complete = true
|
||||
failMsg = condition.Message
|
||||
case batch.JobComplete:
|
||||
complete = true
|
||||
message = condition.Message
|
||||
}
|
||||
}
|
||||
if !complete {
|
||||
hookStatus.Status = appv1.OperationRunning
|
||||
hookStatus.Message = message
|
||||
} else if failed {
|
||||
hookStatus.Status = appv1.OperationFailed
|
||||
hookStatus.Message = failMsg
|
||||
} else {
|
||||
hookStatus.Status = appv1.OperationSucceeded
|
||||
hookStatus.Message = message
|
||||
}
|
||||
}
|
||||
case "Workflow":
|
||||
var wf wfv1.Workflow
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &wf)
|
||||
if err != nil {
|
||||
hookStatus.Status = appv1.OperationError
|
||||
hookStatus.Message = err.Error()
|
||||
} else {
|
||||
switch wf.Status.Phase {
|
||||
case wfv1.NodeRunning:
|
||||
hookStatus.Status = appv1.OperationRunning
|
||||
case wfv1.NodeSucceeded:
|
||||
hookStatus.Status = appv1.OperationSucceeded
|
||||
case wfv1.NodeFailed:
|
||||
hookStatus.Status = appv1.OperationFailed
|
||||
case wfv1.NodeError:
|
||||
hookStatus.Status = appv1.OperationError
|
||||
}
|
||||
hookStatus.Message = wf.Status.Message
|
||||
}
|
||||
default:
|
||||
hookStatus.Status = appv1.OperationSucceeded
|
||||
hookStatus.Message = fmt.Sprintf("%s created", hook.GetName())
|
||||
}
|
||||
return hookStatus
|
||||
}
|
||||
|
||||
// updateHookStatus updates the status of a hook. Returns true if the hook was modified
|
||||
func (sc *syncContext) updateHookStatus(hookStatus appv1.HookStatus) bool {
|
||||
sc.lock.Lock()
|
||||
defer sc.lock.Unlock()
|
||||
for i, prev := range sc.syncRes.Hooks {
|
||||
if prev.Name == hookStatus.Name && prev.Kind == hookStatus.Kind && prev.Type == hookStatus.Type {
|
||||
if reflect.DeepEqual(prev, hookStatus) {
|
||||
return false
|
||||
}
|
||||
if prev.Status != hookStatus.Status {
|
||||
sc.log.Infof("Hook %s %s/%s status: %s -> %s", hookStatus.Type, prev.Kind, prev.Name, prev.Status, hookStatus.Status)
|
||||
}
|
||||
if prev.Message != hookStatus.Message {
|
||||
sc.log.Infof("Hook %s %s/%s message: '%s' -> '%s'", hookStatus.Type, prev.Kind, prev.Name, prev.Message, hookStatus.Message)
|
||||
}
|
||||
sc.syncRes.Hooks[i] = &hookStatus
|
||||
return true
|
||||
}
|
||||
}
|
||||
sc.syncRes.Hooks = append(sc.syncRes.Hooks, &hookStatus)
|
||||
sc.log.Infof("Set new hook %s %s/%s. status: %s, message: %s", hookStatus.Type, hookStatus.Kind, hookStatus.Name, hookStatus.Status, hookStatus.Message)
|
||||
return true
|
||||
}
|
||||
|
||||
// areHooksCompletedSuccessful checks if all the hooks of the specified type are completed and successful
|
||||
func areHooksCompletedSuccessful(hookType appv1.HookType, hookStatuses []*appv1.HookStatus) (bool, bool) {
|
||||
isSuccessful := true
|
||||
for _, hookStatus := range hookStatuses {
|
||||
if hookStatus.Type != hookType {
|
||||
continue
|
||||
}
|
||||
if !hookStatus.Status.Completed() {
|
||||
return false, false
|
||||
}
|
||||
if !hookStatus.Status.Successful() {
|
||||
isSuccessful = false
|
||||
}
|
||||
}
|
||||
return true, isSuccessful
|
||||
}
|
||||
|
||||
// terminate looks for any running jobs/workflow hooks and deletes the resource
|
||||
func (sc *syncContext) terminate() {
|
||||
terminateSuccessful := true
|
||||
for _, hookStatus := range sc.syncRes.Hooks {
|
||||
if hookStatus.Status.Completed() {
|
||||
continue
|
||||
}
|
||||
switch hookStatus.Kind {
|
||||
case "Job", "Workflow":
|
||||
hookStatus.Status = appv1.OperationFailed
|
||||
err := sc.deleteHook(hookStatus.Name, hookStatus.Kind, hookStatus.APIVersion)
|
||||
if err != nil {
|
||||
hookStatus.Message = fmt.Sprintf("Failed to delete %s hook %s/%s: %v", hookStatus.Type, hookStatus.Kind, hookStatus.Name, err)
|
||||
terminateSuccessful = false
|
||||
} else {
|
||||
hookStatus.Message = fmt.Sprintf("Deleted %s hook %s/%s", hookStatus.Type, hookStatus.Kind, hookStatus.Name)
|
||||
}
|
||||
sc.updateHookStatus(*hookStatus)
|
||||
}
|
||||
}
|
||||
if terminateSuccessful {
|
||||
sc.setOperationPhase(appv1.OperationFailed, "Operation terminated")
|
||||
} else {
|
||||
sc.setOperationPhase(appv1.OperationError, "Operation termination had errors")
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *syncContext) deleteHook(name, kind, apiVersion string) error {
|
||||
groupVersion := strings.Split(apiVersion, "/")
|
||||
if len(groupVersion) != 2 {
|
||||
return fmt.Errorf("Failed to terminate app. Unrecognized group/version: %s", apiVersion)
|
||||
}
|
||||
gvk := schema.GroupVersionKind{
|
||||
Group: groupVersion[0],
|
||||
Version: groupVersion[1],
|
||||
Kind: kind,
|
||||
}
|
||||
dclient, err := sc.dynClientPool.ClientForGroupVersionKind(gvk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiResource, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resIf := dclient.Resource(apiResource, sc.namespace)
|
||||
return resIf.Delete(name, &metav1.DeleteOptions{})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func newTestSyncCtx() *syncContext {
|
||||
return &syncContext{
|
||||
comparison: &v1alpha1.ComparisonResult{},
|
||||
config: &rest.Config{},
|
||||
namespace: "test-namespace",
|
||||
syncOp: &v1alpha1.SyncOperation{},
|
||||
opState: &v1alpha1.OperationState{},
|
||||
log: log.WithFields(log.Fields{"application": "fake-app"}),
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWorkflows(t *testing.T) {
|
||||
// syncCtx := newTestSyncCtx()
|
||||
// syncCtx.doWorkflowSync(nil, nil)
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
# ArgoCD Documentation
|
||||
|
||||
## [Getting Started](getting_started.md)
|
||||
|
||||
## Concepts
|
||||
* [Architecture](architecture.md)
|
||||
* [Tracking Strategies](tracking_strategies.md)
|
||||
|
||||
## Features
|
||||
* [Application Sources](application_sources.md)
|
||||
* [Application Parameters](parameters.md)
|
||||
* [Resource Health](health.md)
|
||||
* [Resource Hooks](resource_hooks.md)
|
||||
* [Single Sign On](sso.md)
|
||||
* [Webhooks](webhook.md)
|
||||
* [RBAC](rbac.md)
|
||||
@@ -1,102 +0,0 @@
|
||||
# Application Source Types
|
||||
|
||||
ArgoCD supports several different ways in which kubernetes manifests can be defined:
|
||||
|
||||
* [ksonnet](https://ksonnet.io) applications
|
||||
* [helm](https://helm.sh) charts
|
||||
* Simple directory of YAML/json manifests
|
||||
|
||||
Some additional considerations should be made when deploying apps of a particular type:
|
||||
|
||||
## Ksonnet
|
||||
|
||||
### Environments
|
||||
Ksonnet has a first class concept of an "environment." To create an application from a ksonnet
|
||||
app directory, an environment must be specified. For example, the following command creates the
|
||||
"guestbook-default" app, which points to the `default` environment:
|
||||
|
||||
```
|
||||
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Ksonnet parameters all belong to a component. For example, the following are the parameters
|
||||
available in the guestbook app, all of which belong to the `guestbook-ui` component:
|
||||
|
||||
```
|
||||
$ ks param list
|
||||
COMPONENT PARAM VALUE
|
||||
========= ===== =====
|
||||
guestbook-ui containerPort 80
|
||||
guestbook-ui image "gcr.io/heptio-images/ks-guestbook-demo:0.1"
|
||||
guestbook-ui name "guestbook-ui"
|
||||
guestbook-ui replicas 1
|
||||
guestbook-ui servicePort 80
|
||||
guestbook-ui type "LoadBalancer"
|
||||
```
|
||||
|
||||
When overriding ksonnet parameters in ArgoCD, the component name should also be specified in the
|
||||
`argocd app set` command, in the form of `-p COMPONENT=PARAM=VALUE`. For example:
|
||||
```
|
||||
argocd app set guestbook-default -p guestbook-ui=image=gcr.io/heptio-images/ks-guestbook-demo:0.1
|
||||
```
|
||||
|
||||
## Helm
|
||||
|
||||
### Values Files
|
||||
|
||||
Helm has the ability to use a different, or even multiple "values.yaml" files to derive its
|
||||
parameters from. Alternate or multiple values file(s), can be specified using the `--values`
|
||||
flag. The flag can be repeated to support multiple values files:
|
||||
|
||||
```
|
||||
argocd app set helm-guestbook --values values-production.yaml
|
||||
```
|
||||
|
||||
### Helm Parameters
|
||||
|
||||
Helm has the ability to set parameter values, which override any values in
|
||||
a `values.yaml`. For example, `service.type` is a common parameter which is exposed in a Helm chart:
|
||||
```
|
||||
helm template . --set service.type=LoadBalancer
|
||||
```
|
||||
Similarly ArgoCD can override values in the `values.yaml` parameters using `argo app set` command,
|
||||
in the form of `-p PARAM=VALUE`. For example:
|
||||
```
|
||||
argocd app set helm-guestbook -p service.type=LoadBalancer
|
||||
```
|
||||
|
||||
### Helm Hooks
|
||||
|
||||
Helm hooks are equivalent in concept to [ArgoCD resource hooks](resource_hooks.md). In helm, a hook
|
||||
is any normal kubernetes resource annotated with the `helm.sh/hook` annotation. When ArgoCD deploys
|
||||
helm application which contains helm hooks, all helm hook resources are currently ignored during
|
||||
the `kubectl apply` of the manifests. There is an
|
||||
[open issue](https://github.com/argoproj/argo-cd/issues/355) to map Helm hooks to ArgoCD's concept
|
||||
of Pre/Post/Sync hooks.
|
||||
|
||||
### Random Data
|
||||
|
||||
Helm templating has the ability to generate random data during chart rendering via the
|
||||
`randAlphaNum` function. Many helm charts from the [charts repository](https://github.com/helm/charts)
|
||||
make use of this feature. For example, the following is the secret for the
|
||||
[redis helm chart](https://github.com/helm/charts/blob/master/stable/redis/templates/secrets.yaml):
|
||||
|
||||
```
|
||||
data:
|
||||
{{- if .Values.password }}
|
||||
redis-password: {{ .Values.password | b64enc | quote }}
|
||||
{{- else }}
|
||||
redis-password: {{ randAlphaNum 10 | b64enc | quote }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
The ArgoCD application controller periodically compares git state against the live state, running
|
||||
the `helm template <CHART>` command to generate the helm manifests. Because the random value is
|
||||
regenerated every time the comparison is made, any application which makes use of the `randAlphaNum`
|
||||
function will always be in an `OutOfSync` state. This can be mitigated by explicitly setting a
|
||||
value, in the values.yaml such that the value is stable between each comparison. For example:
|
||||
|
||||
```
|
||||
argocd app set redis -p password=abc123
|
||||
```
|
||||
@@ -22,31 +22,15 @@ manifests when provided the following inputs:
|
||||
* repository URL
|
||||
* git revision (commit, tag, branch)
|
||||
* application path
|
||||
* template specific settings: parameters, ksonnet environments, helm values.yaml
|
||||
* application environment
|
||||
|
||||
### 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 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)
|
||||
the git repo). It detects out-of-sync application state and optionally takes corrective action. It
|
||||
is responsible for invoking any user-defined handlers (argo workflows) for Sync, OutOfSync events
|
||||
|
||||
### Application CRD (Custom Resource Definition)
|
||||
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)
|
||||
* `destination` reference to the target cluster and namespace.
|
||||
|
||||
An example spec is as follows:
|
||||
|
||||
```
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/argoproj/argocd-example-apps.git
|
||||
targetRevision: HEAD
|
||||
path: guestbook
|
||||
environment: default
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
```
|
||||
in an environment. It holds a reference to the desired target state (repo, revision, app, environment)
|
||||
of which the application controller will enforce state against.
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 MiB |
BIN
docs/argocd-ui.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 73 KiB |
@@ -1,44 +1,31 @@
|
||||
# ArgoCD Getting Started
|
||||
# Argo CD Getting Started
|
||||
|
||||
An example guestbook application is provided to demonstrate how ArgoCD works.
|
||||
An example Ksonnet guestbook application is provided to demonstrates how Argo CD works.
|
||||
|
||||
## Requirements
|
||||
* Installed [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line tool
|
||||
* Installed [minikube](https://github.com/kubernetes/minikube#installation)
|
||||
* Installed the [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 ArgoCD
|
||||
```
|
||||
kubectl create namespace argocd
|
||||
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v0.7.1/manifests/install.yaml
|
||||
```
|
||||
This will create a new namespace, `argocd`, where ArgoCD services and application resources will live.
|
||||
## 1. Download Argo CD
|
||||
|
||||
NOTE:
|
||||
* On GKE with RBAC enabled, you may need to grant your account the ability to create new cluster roles
|
||||
Download the latest Argo CD version
|
||||
```
|
||||
kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
|
||||
```
|
||||
|
||||
## 2. Download ArgoCD CLI
|
||||
|
||||
Download the latest ArgoCD version:
|
||||
|
||||
On Mac:
|
||||
```
|
||||
brew install argoproj/tap/argocd
|
||||
```
|
||||
|
||||
On Linux:
|
||||
|
||||
```
|
||||
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.7.1/argocd-linux-amd64
|
||||
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.4.3/argocd-darwin-amd64
|
||||
chmod +x /usr/local/bin/argocd
|
||||
```
|
||||
|
||||
## 3. Open access to ArgoCD API server
|
||||
|
||||
By default, the ArgoCD API server is not exposed with an external IP. To expose the API server,
|
||||
change the service type to `LoadBalancer`:
|
||||
## 2. Install Argo CD
|
||||
```
|
||||
argocd install
|
||||
```
|
||||
This will create a new namespace, `argocd`, where Argo CD services and application resources will live.
|
||||
|
||||
## 3. Open access to Argo CD API server
|
||||
|
||||
By default, the Argo CD API server is not exposed with an external IP. To expose the API server,
|
||||
change service type to `LoadBalancer`:
|
||||
|
||||
```
|
||||
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
|
||||
@@ -46,132 +33,49 @@ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}
|
||||
|
||||
## 4. Login to the server from the CLI
|
||||
|
||||
Login with using the `admin` user. The initial password is autogenerated to be the pod name of the
|
||||
ArgoCD API server. This can be retrieved with the command:
|
||||
```
|
||||
kubectl get pods -n argocd -l app=argocd-server -o name | cut -d'/' -f 2
|
||||
argocd login $(minikube service argocd-server -n argocd --url | cut -d'/' -f 3)
|
||||
```
|
||||
|
||||
Using the above password, login to ArgoCD's external IP:
|
||||
Now, the Argo CD cli is configured to talk to API server and you can deploy your first application.
|
||||
|
||||
On Minikube:
|
||||
```
|
||||
argocd login $(minikube service argocd-server -n argocd --url | cut -d'/' -f 3) --name minikube
|
||||
```
|
||||
Other clusters:
|
||||
```
|
||||
kubectl get svc -n argocd argocd-server
|
||||
argocd login <EXTERNAL-IP>
|
||||
```
|
||||
## 5. Connect and deploy the Guestbook application
|
||||
|
||||
After logging in, change the password using the command:
|
||||
```
|
||||
argocd account update-password
|
||||
argocd relogin
|
||||
```
|
||||
|
||||
|
||||
## 5. Register a cluster to deploy apps to
|
||||
|
||||
We will now register a cluster to deploy applications to. First list all clusters contexts in your
|
||||
kubconfig:
|
||||
```
|
||||
argocd cluster add
|
||||
```
|
||||
|
||||
Choose a context name from the list and supply it to `argocd cluster add CONTEXTNAME`. For example,
|
||||
for minikube context, run:
|
||||
```
|
||||
argocd cluster add minikube --in-cluster
|
||||
```
|
||||
|
||||
The above command installs an `argocd-manager` ServiceAccount and ClusterRole into the cluster
|
||||
associated with the supplied kubectl context. ArgoCD uses the service account token to perform its
|
||||
management tasks (i.e. deploy/monitoring).
|
||||
|
||||
The `--in-cluster` option indicates that the cluster we are registering, is the same cluster that
|
||||
ArgoCD is running in. This allows ArgoCD to connect to the cluster using the internal kubernetes
|
||||
hostname (kubernetes.default.svc). When registering a cluster external to ArgoCD, the `--in-cluster`
|
||||
flag should be omitted.
|
||||
|
||||
## 6. Create the application from a git repository
|
||||
|
||||
### Creating apps via UI
|
||||
|
||||
Open a browser to the ArgoCD external UI, and login using the credentials set in step 4.
|
||||
|
||||
On Minikube:
|
||||
```
|
||||
minikube service argocd-server -n argocd
|
||||
```
|
||||
|
||||
Connect a git repository containing your apps. An example repository containing a sample
|
||||
guestbook application is available at https://github.com/argoproj/argocd-example-apps.git.
|
||||
|
||||

|
||||
|
||||
After connecting a git repository, select the guestbook application for creation:
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
### Creating apps via CLI
|
||||
|
||||
Applications can be also be created using the ArgoCD CLI:
|
||||
1. Register the minikube cluster to Argo CD:
|
||||
|
||||
```
|
||||
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
|
||||
argocd cluster add minikube
|
||||
```
|
||||
The `argocd cluster add CONTEXT` command installs an `argocd-manager` ServiceAccount and ClusterRole into
|
||||
the cluster associated with the supplied kubectl context. Argo CD then uses the associated service account
|
||||
token to perform its required management tasks (i.e. deploy/monitoring).
|
||||
|
||||
2. Add the guestbook application and github repository containing the Guestbook application
|
||||
|
||||
```
|
||||
argocd app create --name guestbook --repo https://github.com/argoproj/argo-cd.git --path examples/guestbook --env minikube --dest-server https://$(minikube ip):8443
|
||||
```
|
||||
|
||||
## 7. Sync (deploy) the application
|
||||
Once the application is added, you can now see its status:
|
||||
|
||||
Once the guestbook application is created, you can now view its status:
|
||||
|
||||
From UI:
|
||||

|
||||
|
||||
From CLI:
|
||||
```
|
||||
$ argocd app get guestbook-default
|
||||
Name: guestbook-default
|
||||
Server: https://kubernetes.default.svc
|
||||
Namespace: default
|
||||
URL: https://192.168.64.36:31880/applications/argocd/guestbook-default
|
||||
Environment: default
|
||||
Repo: https://github.com/argoproj/argocd-example-apps.git
|
||||
Path: guestbook
|
||||
Target: HEAD
|
||||
|
||||
KIND NAME STATUS HEALTH
|
||||
Service guestbook-ui OutOfSync
|
||||
Deployment guestbook-ui OutOfSync
|
||||
argocd app list
|
||||
argocd app get guestbook
|
||||
```
|
||||
|
||||
The application status is initially in an `OutOfSync` state, since the application has yet to be
|
||||
deployed, and no Kubernetes resources have been created. To sync (deploy) the application, run:
|
||||
|
||||
```
|
||||
$ argocd app sync guestbook-default
|
||||
Application: guestbook-default
|
||||
Operation: Sync
|
||||
Phase: Succeeded
|
||||
Message: successfully synced
|
||||
|
||||
KIND NAME MESSAGE
|
||||
Service guestbook-ui service "guestbook-ui" created
|
||||
Deployment guestbook-ui deployment.apps "guestbook-ui" created
|
||||
argocd app sync guestbook
|
||||
```
|
||||
|
||||
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:
|
||||
[](https://asciinema.org/a/uYnbFMy5WI2rc9S49oEAyGLb0)
|
||||
|
||||

|
||||
Argo CD also allows to view and manager applications using web UI. Get the web UI URL by running:
|
||||
|
||||
## 8. Next Steps
|
||||
```
|
||||
minikube service argocd-server -n argocd --url
|
||||
```
|
||||
|
||||
ArgoCD supports additional features such as SSO, WebHooks, RBAC, Projects. See the rest of
|
||||
the [documentation](./) for details.
|
||||

|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Resource Health
|
||||
|
||||
## Overview
|
||||
ArgoCD provides built-in health assessment for several standard Kubernetes types, which is then
|
||||
surfaced to the overall Application health status as a whole. The following checks are made for
|
||||
specific types of kuberentes resources:
|
||||
|
||||
### Deployment, ReplicaSet, StatefulSet DaemonSet
|
||||
|
||||
* Observed generation is equal to desired generation.
|
||||
* Number of **updated** replicas equals the number of desired replicas.
|
||||
|
||||
### Service
|
||||
* If service type is of type `LoadBalancer`, the `status.loadBalancer.ingress` list is non-empty,
|
||||
with at least one value for `hostname` or `IP`.
|
||||
|
||||
### Ingress
|
||||
* The `status.loadBalancer.ingress` list is non-empty, with at least one value for `hostname` or `IP`.
|
||||
@@ -1,39 +0,0 @@
|
||||
# Parameter Overrides
|
||||
|
||||
ArgoCD provides a mechanism to override the parameters of a ksonnet/helm app. This gives some extra
|
||||
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 redeploying an application by changing application parameters via ArgoCD, instead
|
||||
of making the changes to the manifests in git.
|
||||
|
||||
**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 ArgoCD parameter
|
||||
overrides feature is provided mainly convenience to developers and is intended to be used more for
|
||||
dev/test environments, vs. production environments.
|
||||
|
||||
To use parameter overrides, run the `argocd app set -p (COMPONENT=)PARAM=VALUE` command:
|
||||
```
|
||||
argocd app set guestbook -p guestbook=image=example/guestbook:abcd123
|
||||
argocd app sync guestbook
|
||||
```
|
||||
|
||||
The following are situations where parameter overrides would be useful:
|
||||
|
||||
1. A team maintains a "dev" environment, which needs to be continually updated with the latest
|
||||
version of their guestbook application after every build in the tip of master. To address this use
|
||||
case, the application would expose an 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 systems. 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.
|
||||
|
||||
2. A repository of helm manifests is already publicly available (e.g. https://github.com/helm/charts).
|
||||
Since commit access to the repository is unavailable, it is useful to be able to install charts from
|
||||
the public repository, customizing the deployment with different parameters, without resorting to
|
||||
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:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
103
docs/rbac.md
@@ -1,103 +0,0 @@
|
||||
# RBAC
|
||||
|
||||
## Overview
|
||||
|
||||
The feature RBAC allows restricting access to ArgoCD resources. ArgoCD does not have own user management system and has only one built-in user `admin`. The `admin` user is a
|
||||
superuser and it has full access. RBAC requires configuring [SSO](./sso.md) integration. Once [SSO](./sso.md) is connected you can define RBAC roles and map roles to groups.
|
||||
|
||||
## Configure RBAC
|
||||
|
||||
RBAC configuration allows defining roles and groups. ArgoCD has two pre-defined roles: role `role:readonly` which provides read-only access to all resources and role `role:admin`
|
||||
which provides full access. Role definitions are available in [builtin-policy.csv](../util/rbac/builtin-policy.csv) file.
|
||||
|
||||
Additional roles and groups can be configured in `argocd-rbac-cm` ConfigMap. The example below custom role `org-admin`. The role is assigned to any user which belongs to
|
||||
`your-github-org:your-team` group. All other users get `role:readonly` and cannot modify ArgoCD settings.
|
||||
|
||||
*ConfigMap `argocd-rbac-cm` example:*
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
data:
|
||||
policy.default: role:readonly
|
||||
policy.csv: |
|
||||
p, role:org-admin, applications, *, */*
|
||||
p, role:org-admin, applications/*, *, */*
|
||||
|
||||
p, role:org-admin, clusters, get, *
|
||||
p, role:org-admin, repositories, get, *
|
||||
p, role:org-admin, repositories/apps, get, *
|
||||
|
||||
p, role:org-admin, repositories, create, *
|
||||
p, role:org-admin, repositories, update, *
|
||||
p, role:org-admin, repositories, delete, *
|
||||
|
||||
g, your-github-org:your-team, role:org-admin
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-rbac-cm
|
||||
```
|
||||
|
||||
## Configure Projects
|
||||
|
||||
Argo projects allow grouping applications which is useful if ArgoCD is used by multiple teams. Additionally, projects restrict source repositories and destination
|
||||
Kubernetes clusters which can be used by applications belonging to the project.
|
||||
|
||||
### 1. Create new project
|
||||
|
||||
Following command creates project `myproject` which can deploy applications to namespace `default` of cluster `https://kubernetes.default.svc`. The valid application source is defined in the `https://github.com/argoproj/argocd-example-apps.git` repository.
|
||||
|
||||
```
|
||||
argocd proj create myproject -d https://kubernetes.default.svc,default -s https://github.com/argoproj/argocd-example-apps.git
|
||||
```
|
||||
|
||||
Project sources and destinations can be managed using commands
|
||||
```
|
||||
argocd project add-destination
|
||||
argocd project remove-destination
|
||||
argocd project add-source
|
||||
argocd project remove-source
|
||||
```
|
||||
|
||||
### 2. Assign application to a project
|
||||
|
||||
Each application belongs to a project. By default, all application belongs to the default project which provides access to any source repo/cluster. The application project can be
|
||||
changes using `app set` command:
|
||||
|
||||
```
|
||||
argocd app set guestbook-default --project myproject
|
||||
```
|
||||
|
||||
### 3. Update RBAC rules
|
||||
|
||||
Following example configure admin access for two teams. Each team has access only two application of one project (`team1` can access `default` project and `team2` can access
|
||||
`myproject` project).
|
||||
|
||||
*ConfigMap `argocd-rbac-cm` example:*
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
data:
|
||||
policy.default: ""
|
||||
policy.csv: |
|
||||
p, role:team1-admin, applications, *, default/*
|
||||
p, role:team1-admin, applications/*, *, default/*
|
||||
|
||||
p, role:team1-admin, applications, *, myproject/*
|
||||
p, role:team1-admin, applications/*, *, myproject/*
|
||||
|
||||
p, role:org-admin, clusters, get, *
|
||||
p, role:org-admin, repositories, get, *
|
||||
p, role:org-admin, repositories/apps, get, *
|
||||
|
||||
p, role:org-admin, repositories, create, *
|
||||
p, role:org-admin, repositories, update, *
|
||||
p, role:org-admin, repositories, delete, *
|
||||
|
||||
g, role:team1-admin, org-admin
|
||||
g, role:team2-admin, org-admin
|
||||
g, your-github-org:your-team1, role:team1-admin
|
||||
g, your-github-org:your-team2, role:team2-admin
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-rbac-cm
|
||||
```
|
||||
@@ -1,61 +0,0 @@
|
||||
# Resource Hooks
|
||||
|
||||
## Overview
|
||||
|
||||
Hooks are ways to interject custom logic before, during, and after a Sync operation. Some use cases
|
||||
for hooks are:
|
||||
* Using a `PreSync` hook to perform a database schema migration before deploying a new version of the app.
|
||||
* Using a `Sync` hook to orchestrate a complex deployment requiring more sophistication than the
|
||||
kubernetes rolling update strategy (e.g. a blue/green deployment).
|
||||
* Using a `PostSync` hook to run integration and health checks after a deployment.
|
||||
|
||||
## Usage
|
||||
Hooks are simply kubernetes manifests annotated with the `argocd.argoproj.io/hook` annotation. To
|
||||
make use of hooks, simply add the annotation to any resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
generateName: schema-migrate-
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
```
|
||||
|
||||
During a Sync operation, ArgoCD will create the resource during the appropriate stage of the
|
||||
deployment. Hooks can be any type of Kuberentes resource kind, but tend to be most useful as
|
||||
[Kubernetes Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/)
|
||||
or [Argo Workflows](https://github.com/argoproj/argo). Multiple hooks can be specified as a comma
|
||||
separated list.
|
||||
|
||||
## Available Hooks
|
||||
The following hooks are defined:
|
||||
|
||||
| Hook | Description |
|
||||
|------|-------------|
|
||||
| `PreSync` | Executes prior to the apply of the manifests. |
|
||||
| `Sync` | Executes after all `PreSync` hooks completed and were successful. Occurs in conjuction with the apply of the manifests. |
|
||||
| `Skip` | Indicates to ArgoCD to skip the apply of the manifest. This is typically used in conjunction with a `Sync` hook which is presumably handling the deployment in an alternate way (e.g. blue-green deployment) |
|
||||
| `PostSync` | Executes after all `Sync` hooks completed and were successful, a succcessful apply, and all resources in a `Healthy` state. |
|
||||
|
||||
|
||||
## Hook Deletion Policies
|
||||
|
||||
Hooks can be deleted in an automatic fashion using the annotation: `argocd.argoproj.io/hook-delete-policy`.
|
||||
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
generateName: integration-test-
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: OnSuccess
|
||||
```
|
||||
|
||||
The following policies define when the hook will be deleted.
|
||||
|
||||
| Policy | Description |
|
||||
|--------|-------------|
|
||||
| `OnSuccess` | The hook resource is deleted after the hook succeeded (e.g. Job/Workflow completed successfully). |
|
||||
| `OnFailure` | The hook resource is deleted after the hook failed. |
|
||||
51
docs/sso.md
@@ -3,9 +3,9 @@
|
||||
## Overview
|
||||
|
||||
ArgoCD embeds and bundles [Dex](https://github.com/coreos/dex) as part of its installation, for the
|
||||
purpose of delegating authentication to an external identity provider. Multiple types of identity
|
||||
purposes of delegating authentication to an external identity provider. Multiple types of identity
|
||||
providers are supported (OIDC, SAML, LDAP, GitHub, etc...). SSO configuration of ArgoCD requires
|
||||
editing the `argocd-cm` ConfigMap with
|
||||
editing the `argocd-cm` ConfigMap with a
|
||||
[Dex connector](https://github.com/coreos/dex/tree/master/Documentation/connectors) settings.
|
||||
|
||||
This document describes how to configure ArgoCD SSO using GitHub (OAuth2) as an example, but the
|
||||
@@ -45,39 +45,16 @@ data:
|
||||
|
||||
dex.config: |
|
||||
connectors:
|
||||
# GitHub example
|
||||
- type: github
|
||||
id: github
|
||||
name: GitHub
|
||||
config:
|
||||
clientID: aabbccddeeff00112233
|
||||
clientSecret: $dex.github.clientSecret
|
||||
orgs:
|
||||
- name: your-github-org
|
||||
|
||||
# GitHub enterprise example
|
||||
- type: github
|
||||
id: acme-github
|
||||
name: Acme GitHub
|
||||
config:
|
||||
hostName: github.acme.com
|
||||
clientID: abcdefghijklmnopqrst
|
||||
clientSecret: $dex.acme.clientSecret
|
||||
orgs:
|
||||
- name: your-github-org
|
||||
|
||||
# OIDC example (e.g. Okta)
|
||||
- type: oidc
|
||||
id: okta
|
||||
name: Okta
|
||||
config:
|
||||
issuer: https://dev-123456.oktapreview.com
|
||||
clientID: aaaabbbbccccddddeee
|
||||
clientSecret: $dex.okta.clientSecret
|
||||
- type: github
|
||||
id: github
|
||||
name: GitHub
|
||||
config:
|
||||
clientID: 5aae0fcec2c11634be8c
|
||||
clientSecret: c6fcb18177869174bd09be2c51259fb049c9d4e5
|
||||
orgs:
|
||||
- name: your-github-org
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -85,3 +62,11 @@ NOTES:
|
||||
ArgoCD will automatically use the correct `redirectURI` for any OAuth2 connectors, to match the
|
||||
correct external callback URL (e.g. https://argocd.example.com/api/dex/callback)
|
||||
|
||||
### 3. Restart ArgoCD for changes to take effect
|
||||
Any changes to the `argocd-cm` ConfigMap or `argocd-secret` Secret, currently require a restart of
|
||||
the ArgoCD API server for the settings to take effect. Delete the `argocd-server` pod to force a
|
||||
restart. [Issue #174](https://github.com/argoproj/argo-cd/issues/174) will address this limitation.
|
||||
|
||||
```
|
||||
kubectl delete pod -l app=argocd-server
|
||||
```
|
||||
|
||||
@@ -1,45 +1,41 @@
|
||||
# Tracking and Deployment Strategies
|
||||
|
||||
An ArgoCD application spec provides several different ways of track kubernetes resource manifests in
|
||||
git. This document describes the different techniques and the means of deploying those manifests to
|
||||
the target environment.
|
||||
An ArgoCD application spec provides several different ways of track kubernetes resource manifests in git. This document describes the different techniques and the means of deploying those manifests to the target environment.
|
||||
|
||||
## Branch Tracking
|
||||
|
||||
If a branch name is specified, ArgoCD will continually compare live state against the resource
|
||||
manifests defined at the tip of the specified branch.
|
||||
If a branch name is specified, ArgoCD will continually compare live state against the resource manifests defined at the tip of the specified branch.
|
||||
|
||||
To redeploy an application, a user makes changes to the manifests, and commit/pushes those the
|
||||
changes to the tracked branch, which will then be detected by ArgoCD controller.
|
||||
To redeploy an application, a user makes changes to the manifests, and commit/pushes those the changes to the tracked branch, which will then be detected by ArgoCD controller.
|
||||
|
||||
## Tag Tracking
|
||||
|
||||
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.
|
||||
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
|
||||
different commit SHA. ArgoCD will detect the new meaning of the tag when performing the
|
||||
comparison/sync.
|
||||
To redeploy an application, the user uses git to change the meaning of a tag by retagging it to a different commit SHA. ArgoCD 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
|
||||
the specified commit. This is the most restrictive of the techniques and is typically used to
|
||||
control production environments.
|
||||
If a git commit SHA is specified, the application is effectively pinned to the manifests defined at the specified commit. This is the most restrictive of the techniques and is typically used to control production environments.
|
||||
|
||||
Since commit SHAs cannot change meaning, the only way to change the live state of an application
|
||||
which is pinned to a commit, is by updating the tracking revision in the application to a different
|
||||
commit containing the new manifests. Note that [parameter overrides](parameters.md) can still be set
|
||||
on an application which is pinned to a revision.
|
||||
|
||||
## Auto-Sync [(Not Yet Implemented)]((https://github.com/argoproj/argo-cd/issues/79))
|
||||
|
||||
In all tracking strategies, the application will have the option to sync automatically. If auto-sync
|
||||
is configured, the new resources manifests will be applied automatically -- as soon as a difference
|
||||
is detected between the target state (git) and live state. If auto-sync is disabled, a manual sync
|
||||
will be needed using the Argo UI, CLI, or API.
|
||||
Since commit SHAs cannot change meaning, the only way to change the live state of an application which is pinned to a commit, is by updating the tracking revision in the application to a different commit containing the new manifests.
|
||||
Note that parameter overrides can still be made against a application which is pinned to a revision.
|
||||
|
||||
## Parameter Overrides
|
||||
Note that in all tracking strategies, any [parameter overrides](parameters.md) set in the
|
||||
application instance take precedence over the git state.
|
||||
|
||||
ArgoCD provides means to override the parameters of a ksonnet app. This gives some extra flexibility in having *some* parts of the k8s manifests determined dynamically. It also serves as an alternative way of redeploying an application by changing application parameters via ArgoCD, instead of making the changes to the manifests in git.
|
||||
|
||||
The following is an example of where this would be useful: A team maintains a "dev" environment, which needs to be continually updated with the latest version of their guestbook application after every build in the tip of master. To address this use case, the ksonnet application should expose an parameter named `image`, whose value used in the `dev` environment contains a placeholder value (e.g. `example/guestbook:replaceme`), intended to be set externally (outside of git) such as a build systems. As part of the build pipeline, the parameter value of the `image` would be continually updated to the freshly built image (e.g. `example/guestbook:abcd123`). A sync operation would result in the application being redeployed with the new image.
|
||||
|
||||
ArgoCD provides these operations conveniently via the CLI, or alternatively via the gRPC/REST API.
|
||||
```
|
||||
$ argocd app set guestbook -p guestbook=image=example/guestbook:abcd123
|
||||
$ argocd app sync guestbook
|
||||
```
|
||||
|
||||
Note that in all tracking strategies, any parameter overrides set in the application instance will be honored.
|
||||
|
||||
|
||||
## [Auto-Sync](https://github.com/argoproj/argo-cd/issues/79) (Not Yet Implemented)
|
||||
|
||||
In all tracking strategies, the application will have the option to sync automatically. If auto-sync is configured, the new resources manifests will be applied automatically -- as soon as a difference is detected between the target state (git) and live state. If auto-sync is disabled, a manual sync will be needed using the Argo UI, CLI, or API.
|
||||
|
||||
@@ -60,4 +60,11 @@ stringData:
|
||||
|
||||
```
|
||||
|
||||
After saving, the changes should take affect automatically.
|
||||
### 3. Restart ArgoCD for changes to take effect
|
||||
Any changes to the `argocd-cm` ConfigMap or `argocd-secret` Secret, currently require a restart of
|
||||
the ArgoCD API server for the settings to take effect. Delete the `argocd-server` pod to force a
|
||||
restart. [Issue #174](https://github.com/argoproj/argo-cd/issues/174) will address this limitation.
|
||||
|
||||
```
|
||||
kubectl delete pod -l app=argocd-server
|
||||
```
|
||||
|
||||
@@ -24,6 +24,6 @@ local appDeployment = deployment
|
||||
container
|
||||
.new(params.name, params.image)
|
||||
.withPorts(containerPort.new(targetPort)) + if params.command != null then { command: [ params.command ] } else {},
|
||||
labels).withProgressDeadlineSeconds(5);
|
||||
labels);
|
||||
|
||||
k.core.v1.list.new([appService, appDeployment])
|
||||
@@ -1,4 +1,4 @@
|
||||
#! /usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
# This script auto-generates protobuf related files. It is intended to be run manually when either
|
||||
# API types are added/modified, or server gRPC calls are added. The generated files should then
|
||||
@@ -55,8 +55,6 @@ GOPROTOBINARY=gogofast
|
||||
|
||||
# protoc-gen-grpc-gateway is used to build <service>.pb.gw.go files from from .proto files
|
||||
go build -i -o dist/protoc-gen-grpc-gateway ./vendor/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
|
||||
# protoc-gen-swagger is used to build swagger.json
|
||||
go build -i -o dist/protoc-gen-swagger ./vendor/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
|
||||
|
||||
# Generate server/<service>/(<service>.pb.go|<service>.pb.gw.go)
|
||||
PROTO_FILES=$(find $PROJECT_ROOT \( -name "*.proto" -and -path '*/server/*' -or -path '*/reposerver/*' -and -name "*.proto" \))
|
||||
@@ -79,44 +77,5 @@ for i in ${PROTO_FILES}; do
|
||||
-I${GOGO_PROTOBUF_PATH} \
|
||||
--${GOPROTOBINARY}_out=plugins=grpc:$GOPATH/src \
|
||||
--grpc-gateway_out=logtostderr=true:$GOPATH/src \
|
||||
--swagger_out=logtostderr=true:. \
|
||||
$i
|
||||
done
|
||||
|
||||
# collect_swagger gathers swagger files into a subdirectory
|
||||
collect_swagger() {
|
||||
SWAGGER_ROOT="$1"
|
||||
EXPECTED_COLLISIONS="$2"
|
||||
SWAGGER_OUT="${SWAGGER_ROOT}/swagger.json"
|
||||
PRIMARY_SWAGGER=`mktemp`
|
||||
COMBINED_SWAGGER=`mktemp`
|
||||
|
||||
cat <<EOF > "${PRIMARY_SWAGGER}"
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Consolidate Services",
|
||||
"description": "Description of all APIs",
|
||||
"version": "version not set"
|
||||
},
|
||||
"paths": {}
|
||||
}
|
||||
EOF
|
||||
|
||||
/bin/rm -f "${SWAGGER_OUT}"
|
||||
|
||||
/usr/bin/find "${SWAGGER_ROOT}" -name '*.swagger.json' -exec /usr/local/bin/swagger mixin -c "${EXPECTED_COLLISIONS}" "${PRIMARY_SWAGGER}" '{}' \+ > "${COMBINED_SWAGGER}"
|
||||
/usr/local/bin/jq -r 'del(.definitions[].properties[]? | select(."$ref"!=null and .description!=null).description) | del(.definitions[].properties[]? | select(."$ref"!=null and .title!=null).title)' "${COMBINED_SWAGGER}" > "${SWAGGER_OUT}"
|
||||
|
||||
/bin/rm "${PRIMARY_SWAGGER}" "${COMBINED_SWAGGER}"
|
||||
}
|
||||
|
||||
# clean up generated swagger files (should come after collect_swagger)
|
||||
clean_swagger() {
|
||||
SWAGGER_ROOT="$1"
|
||||
/usr/bin/find "${SWAGGER_ROOT}" -name '*.swagger.json' -delete
|
||||
}
|
||||
|
||||
collect_swagger server 15
|
||||
clean_swagger server
|
||||
clean_swagger reposerver
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
IMAGE_NAMESPACE=${IMAGE_NAMESPACE:='argoproj'}
|
||||
IMAGE_TAG=${IMAGE_TAG:='latest'}
|
||||
|
||||
for i in "$(ls manifests/components/*.yaml)"; do
|
||||
sed -i '' 's@\( image: \(.*\)/\(argocd-.*\):.*\)@ image: '"${IMAGE_NAMESPACE}"'/\3:'"${IMAGE_TAG}"'@g' $i
|
||||
done
|
||||
|
||||
echo "# This is an auto-generated file. DO NOT EDIT" > manifests/install.yaml
|
||||
cat manifests/components/*.yaml >> manifests/install.yaml
|
||||
379
install/install.go
Normal file
@@ -0,0 +1,379 @@
|
||||
package install
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/errors"
|
||||
"github.com/argoproj/argo-cd/util/diff"
|
||||
"github.com/argoproj/argo-cd/util/kube"
|
||||
"github.com/argoproj/argo-cd/util/password"
|
||||
"github.com/argoproj/argo-cd/util/session"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
tlsutil "github.com/argoproj/argo-cd/util/tls"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/gobuffalo/packr"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/yudai/gojsondiff/formatter"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
appsv1beta2 "k8s.io/api/apps/v1beta2"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
var (
|
||||
// These values will be overridden by the link flags during build
|
||||
// (e.g. imageTag will use the official release tag on tagged builds)
|
||||
imageNamespace = "argoproj"
|
||||
imageTag = "latest"
|
||||
|
||||
// Default namespace and image names which `argocd install` uses during install
|
||||
DefaultInstallNamespace = "argocd"
|
||||
DefaultControllerImage = imageNamespace + "/argocd-application-controller:" + imageTag
|
||||
DefaultUIImage = imageNamespace + "/argocd-ui:" + imageTag
|
||||
DefaultServerImage = imageNamespace + "/argocd-server:" + imageTag
|
||||
DefaultRepoServerImage = imageNamespace + "/argocd-repo-server:" + imageTag
|
||||
)
|
||||
|
||||
// InstallOptions stores a collection of installation settings.
|
||||
type InstallOptions struct {
|
||||
DryRun bool
|
||||
Upgrade bool
|
||||
UpdateSuperuser bool
|
||||
UpdateSignature bool
|
||||
SuperuserPassword string
|
||||
Namespace string
|
||||
ControllerImage string
|
||||
UIImage string
|
||||
ServerImage string
|
||||
RepoServerImage string
|
||||
ImagePullPolicy string
|
||||
}
|
||||
|
||||
type Installer struct {
|
||||
InstallOptions
|
||||
box packr.Box
|
||||
config *rest.Config
|
||||
dynClientPool dynamic.ClientPool
|
||||
disco discovery.DiscoveryInterface
|
||||
}
|
||||
|
||||
func NewInstaller(config *rest.Config, opts InstallOptions) (*Installer, error) {
|
||||
shallowCopy := *config
|
||||
inst := Installer{
|
||||
InstallOptions: opts,
|
||||
box: packr.NewBox("./manifests"),
|
||||
config: &shallowCopy,
|
||||
}
|
||||
if opts.Namespace == "" {
|
||||
inst.Namespace = DefaultInstallNamespace
|
||||
}
|
||||
var err error
|
||||
inst.dynClientPool = dynamic.NewDynamicClientPool(inst.config)
|
||||
inst.disco, err = discovery.NewDiscoveryClientForConfig(inst.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &inst, nil
|
||||
}
|
||||
|
||||
func (i *Installer) Install() {
|
||||
i.InstallNamespace()
|
||||
i.InstallApplicationCRD()
|
||||
i.InstallSettings()
|
||||
i.InstallApplicationController()
|
||||
i.InstallArgoCDServer()
|
||||
i.InstallArgoCDRepoServer()
|
||||
}
|
||||
|
||||
func (i *Installer) Uninstall(deleteNamespace, deleteCRD bool) {
|
||||
manifests := i.box.List()
|
||||
for _, manifestPath := range manifests {
|
||||
if strings.HasSuffix(manifestPath, ".yaml") || strings.HasSuffix(manifestPath, ".yml") {
|
||||
var obj unstructured.Unstructured
|
||||
i.unmarshalManifest(manifestPath, &obj)
|
||||
switch strings.ToLower(obj.GetKind()) {
|
||||
case "namespace":
|
||||
if !deleteNamespace {
|
||||
log.Infof("Skipped deletion of Namespace: '%s'", obj.GetName())
|
||||
continue
|
||||
}
|
||||
case "customresourcedefinition":
|
||||
if !deleteCRD {
|
||||
log.Infof("Skipped deletion of CustomResourceDefinition: '%s'", obj.GetName())
|
||||
continue
|
||||
}
|
||||
}
|
||||
i.MustUninstallResource(&obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Installer) InstallNamespace() {
|
||||
if i.Namespace != DefaultInstallNamespace {
|
||||
// don't create namespace if a different one was supplied
|
||||
return
|
||||
}
|
||||
var namespace apiv1.Namespace
|
||||
i.unmarshalManifest("00_namespace.yaml", &namespace)
|
||||
namespace.ObjectMeta.Name = i.Namespace
|
||||
i.MustInstallResource(kube.MustToUnstructured(&namespace))
|
||||
}
|
||||
|
||||
func (i *Installer) InstallApplicationCRD() {
|
||||
var applicationCRD apiextensionsv1beta1.CustomResourceDefinition
|
||||
i.unmarshalManifest("01_application-crd.yaml", &applicationCRD)
|
||||
i.MustInstallResource(kube.MustToUnstructured(&applicationCRD))
|
||||
}
|
||||
|
||||
func (i *Installer) InstallSettings() {
|
||||
kubeclientset, err := kubernetes.NewForConfig(i.config)
|
||||
errors.CheckError(err)
|
||||
settingsMgr := settings.NewSettingsManager(kubeclientset, i.Namespace)
|
||||
cdSettings, err := settingsMgr.GetSettings()
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
cdSettings = &settings.ArgoCDSettings{}
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if cdSettings.ServerSignature == nil || i.UpdateSignature {
|
||||
// set JWT signature
|
||||
signature, err := session.MakeSignature(32)
|
||||
errors.CheckError(err)
|
||||
cdSettings.ServerSignature = signature
|
||||
}
|
||||
|
||||
if cdSettings.LocalUsers == nil {
|
||||
cdSettings.LocalUsers = make(map[string]string)
|
||||
}
|
||||
if _, ok := cdSettings.LocalUsers[common.ArgoCDAdminUsername]; !ok || i.UpdateSuperuser {
|
||||
passwordRaw := i.SuperuserPassword
|
||||
if passwordRaw == "" {
|
||||
passwordRaw = readAndConfirmPassword()
|
||||
}
|
||||
hashedPassword, err := password.HashPassword(passwordRaw)
|
||||
errors.CheckError(err)
|
||||
cdSettings.LocalUsers = map[string]string{
|
||||
common.ArgoCDAdminUsername: hashedPassword,
|
||||
}
|
||||
}
|
||||
|
||||
if cdSettings.Certificate == nil {
|
||||
// generate TLS cert
|
||||
hosts := []string{
|
||||
"localhost",
|
||||
"argocd-server",
|
||||
fmt.Sprintf("argocd-server.%s", i.Namespace),
|
||||
fmt.Sprintf("argocd-server.%s.svc", i.Namespace),
|
||||
fmt.Sprintf("argocd-server.%s.svc.cluster.local", i.Namespace),
|
||||
}
|
||||
certOpts := tlsutil.CertOptions{
|
||||
Hosts: hosts,
|
||||
Organization: "Argo CD",
|
||||
IsCA: true,
|
||||
}
|
||||
cert, err := tlsutil.GenerateX509KeyPair(certOpts)
|
||||
errors.CheckError(err)
|
||||
cdSettings.Certificate = cert
|
||||
}
|
||||
|
||||
err = settingsMgr.SaveSettings(cdSettings)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
func readAndConfirmPassword() string {
|
||||
for {
|
||||
fmt.Print("*** Enter an admin password: ")
|
||||
password, err := terminal.ReadPassword(syscall.Stdin)
|
||||
errors.CheckError(err)
|
||||
fmt.Print("\n")
|
||||
fmt.Print("*** Confirm the admin password: ")
|
||||
confirmPassword, err := terminal.ReadPassword(syscall.Stdin)
|
||||
errors.CheckError(err)
|
||||
fmt.Print("\n")
|
||||
if string(password) == string(confirmPassword) {
|
||||
return string(password)
|
||||
}
|
||||
log.Error("Passwords do not match")
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Installer) InstallApplicationController() {
|
||||
var applicationControllerServiceAccount apiv1.ServiceAccount
|
||||
var applicationControllerRole rbacv1.Role
|
||||
var applicationControllerRoleBinding rbacv1.RoleBinding
|
||||
var applicationControllerDeployment appsv1beta2.Deployment
|
||||
i.unmarshalManifest("03a_application-controller-sa.yaml", &applicationControllerServiceAccount)
|
||||
i.unmarshalManifest("03b_application-controller-role.yaml", &applicationControllerRole)
|
||||
i.unmarshalManifest("03c_application-controller-rolebinding.yaml", &applicationControllerRoleBinding)
|
||||
i.unmarshalManifest("03d_application-controller-deployment.yaml", &applicationControllerDeployment)
|
||||
applicationControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.ControllerImage
|
||||
applicationControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
|
||||
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerServiceAccount))
|
||||
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerRole))
|
||||
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerRoleBinding))
|
||||
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerDeployment))
|
||||
}
|
||||
|
||||
func (i *Installer) InstallArgoCDServer() {
|
||||
var argoCDServerServiceAccount apiv1.ServiceAccount
|
||||
var argoCDServerControllerRole rbacv1.Role
|
||||
var argoCDServerControllerRoleBinding rbacv1.RoleBinding
|
||||
var argoCDServerControllerDeployment appsv1beta2.Deployment
|
||||
var argoCDServerService apiv1.Service
|
||||
i.unmarshalManifest("04a_argocd-server-sa.yaml", &argoCDServerServiceAccount)
|
||||
i.unmarshalManifest("04b_argocd-server-role.yaml", &argoCDServerControllerRole)
|
||||
i.unmarshalManifest("04c_argocd-server-rolebinding.yaml", &argoCDServerControllerRoleBinding)
|
||||
i.unmarshalManifest("04d_argocd-server-deployment.yaml", &argoCDServerControllerDeployment)
|
||||
i.unmarshalManifest("04e_argocd-server-service.yaml", &argoCDServerService)
|
||||
argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[0].Image = i.ServerImage
|
||||
argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
|
||||
argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[1].Image = i.UIImage
|
||||
argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[1].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
|
||||
argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.ServerImage
|
||||
argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
|
||||
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerServiceAccount))
|
||||
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerRole))
|
||||
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerRoleBinding))
|
||||
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerDeployment))
|
||||
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerService))
|
||||
|
||||
}
|
||||
|
||||
func (i *Installer) InstallArgoCDRepoServer() {
|
||||
var argoCDRepoServerControllerDeployment appsv1beta2.Deployment
|
||||
var argoCDRepoServerService apiv1.Service
|
||||
i.unmarshalManifest("05a_argocd-repo-server-deployment.yaml", &argoCDRepoServerControllerDeployment)
|
||||
i.unmarshalManifest("05b_argocd-repo-server-service.yaml", &argoCDRepoServerService)
|
||||
argoCDRepoServerControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.RepoServerImage
|
||||
argoCDRepoServerControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
|
||||
i.MustInstallResource(kube.MustToUnstructured(&argoCDRepoServerControllerDeployment))
|
||||
i.MustInstallResource(kube.MustToUnstructured(&argoCDRepoServerService))
|
||||
}
|
||||
|
||||
func (i *Installer) unmarshalManifest(fileName string, obj interface{}) {
|
||||
yamlBytes, err := i.box.MustBytes(fileName)
|
||||
errors.CheckError(err)
|
||||
err = yaml.Unmarshal(yamlBytes, obj)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
func (i *Installer) MustInstallResource(obj *unstructured.Unstructured) *unstructured.Unstructured {
|
||||
obj, err := i.InstallResource(obj)
|
||||
errors.CheckError(err)
|
||||
return obj
|
||||
}
|
||||
|
||||
func isNamespaced(obj *unstructured.Unstructured) bool {
|
||||
switch obj.GetKind() {
|
||||
case "Namespace", "ClusterRole", "ClusterRoleBinding", "CustomResourceDefinition":
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// InstallResource creates or updates a resource. If installed resource is up-to-date, does nothing
|
||||
func (i *Installer) InstallResource(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
if isNamespaced(obj) {
|
||||
obj.SetNamespace(i.Namespace)
|
||||
}
|
||||
// remove 'creationTimestamp' and 'status' fields from object so that the diff will not be modified
|
||||
obj.SetCreationTimestamp(metav1.Time{})
|
||||
delete(obj.Object, "status")
|
||||
if i.DryRun {
|
||||
printYAML(obj)
|
||||
return nil, nil
|
||||
}
|
||||
gvk := obj.GroupVersionKind()
|
||||
dclient, err := i.dynClientPool.ClientForGroupVersionKind(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiResource, err := kube.ServerResourceForGroupVersionKind(i.disco, gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reIf := dclient.Resource(apiResource, i.Namespace)
|
||||
liveObj, err := reIf.Create(obj)
|
||||
if err == nil {
|
||||
fmt.Printf("%s '%s' created\n", liveObj.GetKind(), liveObj.GetName())
|
||||
return liveObj, nil
|
||||
}
|
||||
if !apierr.IsAlreadyExists(err) {
|
||||
return nil, err
|
||||
}
|
||||
liveObj, err = reIf.Get(obj.GetName(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diffRes := diff.Diff(obj, liveObj)
|
||||
if !diffRes.Modified {
|
||||
fmt.Printf("%s '%s' up-to-date\n", liveObj.GetKind(), liveObj.GetName())
|
||||
return liveObj, nil
|
||||
}
|
||||
if !i.Upgrade {
|
||||
log.Println(diffRes.ASCIIFormat(obj, formatter.AsciiFormatterConfig{}))
|
||||
return nil, fmt.Errorf("%s '%s' already exists. Rerun with --upgrade to update", obj.GetKind(), obj.GetName())
|
||||
|
||||
}
|
||||
liveObj, err = reIf.Update(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("%s '%s' updated\n", liveObj.GetKind(), liveObj.GetName())
|
||||
return liveObj, nil
|
||||
}
|
||||
|
||||
func (i *Installer) MustUninstallResource(obj *unstructured.Unstructured) {
|
||||
err := i.UninstallResource(obj)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
// UninstallResource deletes a resource from the cluster
|
||||
func (i *Installer) UninstallResource(obj *unstructured.Unstructured) error {
|
||||
if isNamespaced(obj) {
|
||||
obj.SetNamespace(i.Namespace)
|
||||
}
|
||||
gvk := obj.GroupVersionKind()
|
||||
dclient, err := i.dynClientPool.ClientForGroupVersionKind(gvk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiResource, err := kube.ServerResourceForGroupVersionKind(i.disco, gvk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reIf := dclient.Resource(apiResource, i.Namespace)
|
||||
deletePolicy := metav1.DeletePropagationForeground
|
||||
err = reIf.Delete(obj.GetName(), &metav1.DeleteOptions{PropagationPolicy: &deletePolicy})
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
fmt.Printf("%s '%s' not found\n", obj.GetKind(), obj.GetName())
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s '%s' deleted\n", obj.GetKind(), obj.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
func printYAML(obj interface{}) {
|
||||
objBytes, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal %v", obj)
|
||||
}
|
||||
fmt.Printf("---\n%s\n", string(objBytes))
|
||||
}
|
||||
4
install/manifests/00_namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: argocd
|
||||
@@ -1,4 +1,3 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
57
install/manifests/02a_argocd-cm.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
# NOTE: the values here are just a example and are not the values used during an install.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm
|
||||
namespace: argocd
|
||||
data:
|
||||
# url is the externally facing base URL of ArgoCD.
|
||||
# This field is required when configuring SSO, which ArgoCD uses as part the redirectURI for the
|
||||
# dex connectors. When configuring the application in the SSO provider (e.g. github, okta), the
|
||||
# authorization callback URL will be url + /api/dex/callback. For example, if ArgoCD's url is
|
||||
# https://example.com, then the auth callback to set in the SSO provider should be:
|
||||
# https://example.com/api/dex/callback
|
||||
url: http://localhost:8080
|
||||
|
||||
# dex.config holds the contents of the configuration yaml for the dex OIDC/Oauth2 provider sidecar.
|
||||
# Only a subset of a full dex config is required, namely the connectors list. ArgoCD will generate
|
||||
# the complete dex config based on the configured URL, and the known callback endpoint which the
|
||||
# ArgoCD API server exposes (i.e. /api/dex/callback).
|
||||
dex.config: |
|
||||
# connectors is a list of dex connector configurations. For details on available connectors and
|
||||
# how to configure them, see: https://github.com/coreos/dex/tree/master/Documentation/connectors
|
||||
# NOTE:
|
||||
# * Any values which start with '$' will look to a key in argocd-secret of the same name, to
|
||||
# obtain the actual value.
|
||||
# * ArgoCD will automatically set the 'redirectURI' field in any OAuth2 connectors, to match the
|
||||
# external callback URL (e.g. https://example.com/api/dex/callback)
|
||||
connectors:
|
||||
# GitHub example
|
||||
- type: github
|
||||
id: github
|
||||
name: GitHub
|
||||
config:
|
||||
clientID: aabbccddeeff00112233
|
||||
clientSecret: $dex.github.clientSecret
|
||||
orgs:
|
||||
- name: your-github-org
|
||||
|
||||
# GitHub enterprise example
|
||||
- type: github
|
||||
id: acme-github
|
||||
name: Acme GitHub
|
||||
config:
|
||||
hostName: github.acme.com
|
||||
clientID: abcdefghijklmnopqrst
|
||||
clientSecret: $dex.acme.clientSecret
|
||||
orgs:
|
||||
- name: your-github-org
|
||||
|
||||
# OIDC example (e.g. Okta)
|
||||
- type: oidc
|
||||
id: okta
|
||||
name: Okta
|
||||
config:
|
||||
issuer: https://dev-123456.oktapreview.com
|
||||
clientID: aaaabbbbccccddddeee
|
||||
clientSecret: $dex.okta.clientSecret
|
||||
27
install/manifests/02b_argocd-secret.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# NOTE: the values in this secret are provided as working manifest example and are not the values
|
||||
# used during an install. New values will be generated as part of `argocd install`
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: argocd-secret
|
||||
namespace: argocd
|
||||
type: Opaque
|
||||
stringData:
|
||||
# bcrypt hash of the string "password"
|
||||
admin.password: $2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W
|
||||
# random server signature key for session validation
|
||||
server.secretkey: aEDvv73vv70F77+9CRBSNu+/vTYQ77+9EUFh77+9LzFyJ++/vXfLsO+/vWRbeu+/ve+/vQ==
|
||||
|
||||
# The following keys hold the shared secret for authenticating GitHub/GitLab/BitBucket webhook
|
||||
# events. To enable webhooks, configure one or more of the following keys with the shared git
|
||||
# provider webhook secret. The payload URL configured in the git provider should use the
|
||||
# /api/webhook endpoint of your ArgoCD instance (e.g. https://argocd.example.com/api/webhook)
|
||||
github.webhook.secret: shhhh! it's a github secret
|
||||
gitlab.webhook.secret: shhhh! it's a gitlab secret
|
||||
bitbucket.webhook.uuid: your-bitbucket-uuid
|
||||
|
||||
# the following of user defined keys which are referenced in the example argocd-cm configmap
|
||||
# as pat of SSO configuration.
|
||||
dex.github.clientSecret: nv1vx8w4gw5byrflujfkxww6ykh85yq818aorvwy
|
||||
dex.acme.clientSecret: 5pp7dyre3d5nyk0ree1tr0gd68k18xn94x8lfae9
|
||||
dex.okta.clientSecret: x41ztv6ufyf07oyoopc6f62p222c00mox2ciquvt
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: application-controller
|
||||
namespace: argocd
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: application-controller-role
|
||||
namespace: argocd
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
@@ -11,14 +11,10 @@ rules:
|
||||
verbs:
|
||||
- get
|
||||
- watch
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- applications
|
||||
- appprojects
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
@@ -27,11 +23,3 @@ rules:
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- list
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: application-controller-role-binding
|
||||
namespace: argocd
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: application-controller
|
||||
namespace: argocd
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
@@ -14,6 +14,6 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- command: [/argocd-application-controller, --repo-server, 'argocd-repo-server:8081']
|
||||
image: argoproj/argocd-application-controller:v0.7.2
|
||||
image: argoproj/argocd-application-controller:latest
|
||||
name: application-controller
|
||||
serviceAccountName: application-controller
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: argocd-server
|
||||
namespace: argocd
|
||||
@@ -1,13 +1,34 @@
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: argocd-server-role
|
||||
namespace: argocd
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- pods/exec
|
||||
- pods/log
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- create
|
||||
@@ -21,7 +42,6 @@ rules:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- applications
|
||||
- appprojects
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
@@ -30,10 +50,3 @@ rules:
|
||||
- update
|
||||
- delete
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- list
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: argocd-server-role-binding
|
||||
namespace: argocd
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: argocd-server
|
||||
namespace: argocd
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
@@ -15,20 +15,20 @@ spec:
|
||||
serviceAccountName: argocd-server
|
||||
initContainers:
|
||||
- name: copyutil
|
||||
image: argoproj/argocd-server:v0.7.2
|
||||
image: argoproj/argocd-server:latest
|
||||
command: [cp, /argocd-util, /shared]
|
||||
volumeMounts:
|
||||
- mountPath: /shared
|
||||
name: static-files
|
||||
- name: ui
|
||||
image: argoproj/argocd-ui:v0.7.2
|
||||
image: argoproj/argocd-ui:latest
|
||||
command: [cp, -r, /app, /shared]
|
||||
volumeMounts:
|
||||
- mountPath: /shared
|
||||
name: static-files
|
||||
containers:
|
||||
- name: argocd-server
|
||||
image: argoproj/argocd-server:v0.7.2
|
||||
image: argoproj/argocd-server:latest
|
||||
command: [/argocd-server, --staticassets, /shared/app, --repo-server, 'argocd-repo-server:8081']
|
||||
volumeMounts:
|
||||
- mountPath: /shared
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: argocd-server
|
||||
namespace: argocd
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: argocd-repo-server
|
||||
namespace: argocd
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
@@ -14,7 +14,11 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: argocd-repo-server
|
||||
image: argoproj/argocd-repo-server:v0.7.2
|
||||
image: argoproj/argocd-repo-server:latest
|
||||
command: [/argocd-repo-server]
|
||||
ports:
|
||||
- containerPort: 8081
|
||||
- name: redis
|
||||
image: redis:3.2.11
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: argocd-repo-server
|
||||
namespace: argocd
|
||||
spec:
|
||||
ports:
|
||||
- port: 8081
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: appprojects.argoproj.io
|
||||
spec:
|
||||
group: argoproj.io
|
||||
names:
|
||||
kind: AppProject
|
||||
plural: appprojects
|
||||
shortNames:
|
||||
- appproj
|
||||
- appprojs
|
||||
scope: Namespaced
|
||||
version: v1alpha1
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm
|
||||
data:
|
||||
# See https://github.com/argoproj/argo-cd/blob/master/docs/sso.md#2-configure-argocd-for-sso
|
||||
# for more details about how to setup data config needed for sso
|
||||
|
||||
# URL is the external URL of ArgoCD
|
||||
#url:
|
||||
|
||||
# A dex connector configuration
|
||||
#dex.config:
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
# NOTE: the values in this secret will be populated by the initial startup of the API
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: argocd-secret
|
||||
type: Opaque
|
||||
# bcrypt hash of the admin password
|
||||
#admin.password:
|
||||
|
||||
# random server signature key for session validation
|
||||
#server.secretkey:
|
||||
|
||||
# TLS certificate and private key for API server
|
||||
#server.crt:
|
||||
#server.key:
|
||||
|
||||
# The following keys hold the shared secret for authenticating GitHub/GitLab/BitBucket webhook
|
||||
# events. To enable webhooks, configure one or more of the following keys with the shared git
|
||||
# provider webhook secret. The payload URL configured in the git provider should use the
|
||||
# /api/webhook endpoint of your ArgoCD instance (e.g. https://argocd.example.com/api/webhook)
|
||||
#github.webhook.secret:
|
||||
#gitlab.webhook.secret:
|
||||
#bitbucket.webhook.uuid:
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-rbac-cm
|
||||
data:
|
||||
# policy.csv holds the CSV file policy file which contains additional policy and role definitions.
|
||||
# ArgoCD defines two built-in roles:
|
||||
# * role:readonly - readonly access to all objects
|
||||
# * role:admin - admin access to all objects
|
||||
# The built-in policy can be seen under util/rbac/builtin-policy.csv
|
||||
#policy.csv: ""
|
||||
|
||||
# There are two policy formats:
|
||||
# 1. Applications (which belong to a project):
|
||||
# p, <user/group>, <resource>, <action>, <project>/<object>
|
||||
# 2. All other resources:
|
||||
# p, <user/group>, <resource>, <action>, <object>
|
||||
|
||||
# For example, the following rule gives all members of 'my-org:team1' the ability to sync
|
||||
# applications in the project named: my-project
|
||||
# p, my-org:team1, applications, sync, my-project/*
|
||||
|
||||
# policy.default holds the default policy which will ArgoCD will fall back to, when authorizing
|
||||
# a user for API requests
|
||||
policy.default: role:readonly
|
||||
@@ -1,315 +0,0 @@
|
||||
# This is an auto-generated file. DO NOT EDIT
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: applications.argoproj.io
|
||||
spec:
|
||||
group: argoproj.io
|
||||
names:
|
||||
kind: Application
|
||||
plural: applications
|
||||
shortNames:
|
||||
- app
|
||||
scope: Namespaced
|
||||
version: v1alpha1
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: appprojects.argoproj.io
|
||||
spec:
|
||||
group: argoproj.io
|
||||
names:
|
||||
kind: AppProject
|
||||
plural: appprojects
|
||||
shortNames:
|
||||
- appproj
|
||||
- appprojs
|
||||
scope: Namespaced
|
||||
version: v1alpha1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm
|
||||
data:
|
||||
# See https://github.com/argoproj/argo-cd/blob/master/docs/sso.md#2-configure-argocd-for-sso
|
||||
# for more details about how to setup data config needed for sso
|
||||
|
||||
# URL is the external URL of ArgoCD
|
||||
#url:
|
||||
|
||||
# A dex connector configuration
|
||||
#dex.config:
|
||||
---
|
||||
# NOTE: the values in this secret will be populated by the initial startup of the API
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: argocd-secret
|
||||
type: Opaque
|
||||
# bcrypt hash of the admin password
|
||||
#admin.password:
|
||||
|
||||
# random server signature key for session validation
|
||||
#server.secretkey:
|
||||
|
||||
# TLS certificate and private key for API server
|
||||
#server.crt:
|
||||
#server.key:
|
||||
|
||||
# The following keys hold the shared secret for authenticating GitHub/GitLab/BitBucket webhook
|
||||
# events. To enable webhooks, configure one or more of the following keys with the shared git
|
||||
# provider webhook secret. The payload URL configured in the git provider should use the
|
||||
# /api/webhook endpoint of your ArgoCD instance (e.g. https://argocd.example.com/api/webhook)
|
||||
#github.webhook.secret:
|
||||
#gitlab.webhook.secret:
|
||||
#bitbucket.webhook.uuid:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-rbac-cm
|
||||
data:
|
||||
# policy.csv holds the CSV file policy file which contains additional policy and role definitions.
|
||||
# ArgoCD defines two built-in roles:
|
||||
# * role:readonly - readonly access to all objects
|
||||
# * role:admin - admin access to all objects
|
||||
# The built-in policy can be seen under util/rbac/builtin-policy.csv
|
||||
#policy.csv: ""
|
||||
|
||||
# There are two policy formats:
|
||||
# 1. Applications (which belong to a project):
|
||||
# p, <user/group>, <resource>, <action>, <project>/<object>
|
||||
# 2. All other resources:
|
||||
# p, <user/group>, <resource>, <action>, <object>
|
||||
|
||||
# For example, the following rule gives all members of 'my-org:team1' the ability to sync
|
||||
# applications in the project named: my-project
|
||||
# p, my-org:team1, applications, sync, my-project/*
|
||||
|
||||
# policy.default holds the default policy which will ArgoCD will fall back to, when authorizing
|
||||
# a user for API requests
|
||||
policy.default: role:readonly
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: application-controller
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: application-controller-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- watch
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- applications
|
||||
- appprojects
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- list
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: application-controller-role-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: application-controller-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: application-controller
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: application-controller
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: application-controller
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: application-controller
|
||||
spec:
|
||||
containers:
|
||||
- command: [/argocd-application-controller, --repo-server, 'argocd-repo-server:8081']
|
||||
image: argoproj/argocd-application-controller:v0.7.2
|
||||
name: application-controller
|
||||
serviceAccountName: application-controller
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: argocd-server
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: argocd-server-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
- configmaps
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- applications
|
||||
- appprojects
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- delete
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- list
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: argocd-server-role-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: argocd-server-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: argocd-server
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: argocd-server
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: argocd-server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: argocd-server
|
||||
spec:
|
||||
serviceAccountName: argocd-server
|
||||
initContainers:
|
||||
- name: copyutil
|
||||
image: argoproj/argocd-server:v0.7.2
|
||||
command: [cp, /argocd-util, /shared]
|
||||
volumeMounts:
|
||||
- mountPath: /shared
|
||||
name: static-files
|
||||
- name: ui
|
||||
image: argoproj/argocd-ui:v0.7.2
|
||||
command: [cp, -r, /app, /shared]
|
||||
volumeMounts:
|
||||
- mountPath: /shared
|
||||
name: static-files
|
||||
containers:
|
||||
- name: argocd-server
|
||||
image: argoproj/argocd-server:v0.7.2
|
||||
command: [/argocd-server, --staticassets, /shared/app, --repo-server, 'argocd-repo-server:8081']
|
||||
volumeMounts:
|
||||
- mountPath: /shared
|
||||
name: static-files
|
||||
- name: dex
|
||||
image: quay.io/coreos/dex:v2.10.0
|
||||
command: [/shared/argocd-util, rundex]
|
||||
volumeMounts:
|
||||
- mountPath: /shared
|
||||
name: static-files
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: static-files
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: argocd-server
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
- name: https
|
||||
protocol: TCP
|
||||
port: 443
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: argocd-server
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: argocd-repo-server
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: argocd-repo-server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: argocd-repo-server
|
||||
spec:
|
||||
containers:
|
||||
- name: argocd-repo-server
|
||||
image: argoproj/argocd-repo-server:v0.7.2
|
||||
command: [/argocd-repo-server]
|
||||
ports:
|
||||
- containerPort: 8081
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: argocd-repo-server
|
||||
spec:
|
||||
ports:
|
||||
- port: 8081
|
||||
targetPort: 8081
|
||||
selector:
|
||||
app: argocd-repo-server
|
||||
@@ -8,30 +8,20 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/server/account"
|
||||
"github.com/argoproj/argo-cd/server/application"
|
||||
"github.com/argoproj/argo-cd/server/cluster"
|
||||
"github.com/argoproj/argo-cd/server/project"
|
||||
"github.com/argoproj/argo-cd/server/repository"
|
||||
"github.com/argoproj/argo-cd/server/session"
|
||||
"github.com/argoproj/argo-cd/server/settings"
|
||||
"github.com/argoproj/argo-cd/server/version"
|
||||
grpc_util "github.com/argoproj/argo-cd/util/grpc"
|
||||
"github.com/argoproj/argo-cd/util/localconfig"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -58,10 +48,6 @@ type Client interface {
|
||||
NewSettingsClientOrDie() (*grpc.ClientConn, settings.SettingsServiceClient)
|
||||
NewVersionClient() (*grpc.ClientConn, version.VersionServiceClient, error)
|
||||
NewVersionClientOrDie() (*grpc.ClientConn, version.VersionServiceClient)
|
||||
NewProjectClient() (*grpc.ClientConn, project.ProjectServiceClient, error)
|
||||
NewProjectClientOrDie() (*grpc.ClientConn, project.ProjectServiceClient)
|
||||
NewAccountClient() (*grpc.ClientConn, account.AccountServiceClient, error)
|
||||
NewAccountClientOrDie() (*grpc.ClientConn, account.AccountServiceClient)
|
||||
}
|
||||
|
||||
// ClientOptions hold address, security, and other settings for the API client.
|
||||
@@ -76,12 +62,11 @@ type ClientOptions struct {
|
||||
}
|
||||
|
||||
type client struct {
|
||||
ServerAddr string
|
||||
PlainText bool
|
||||
Insecure bool
|
||||
CertPEMData []byte
|
||||
AuthToken string
|
||||
RefreshToken string
|
||||
ServerAddr string
|
||||
PlainText bool
|
||||
Insecure bool
|
||||
CertPEMData []byte
|
||||
AuthToken string
|
||||
}
|
||||
|
||||
// NewClient creates a new API client from a set of config options.
|
||||
@@ -91,7 +76,6 @@ func NewClient(opts *ClientOptions) (Client, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ctxName string
|
||||
if localCfg != nil {
|
||||
configCtx, err := localCfg.ResolveContext(opts.Context)
|
||||
if err != nil {
|
||||
@@ -108,8 +92,6 @@ func NewClient(opts *ClientOptions) (Client, error) {
|
||||
c.PlainText = configCtx.Server.PlainText
|
||||
c.Insecure = configCtx.Server.Insecure
|
||||
c.AuthToken = configCtx.User.AuthToken
|
||||
c.RefreshToken = configCtx.User.RefreshToken
|
||||
ctxName = configCtx.Name
|
||||
}
|
||||
}
|
||||
// Override server address if specified in env or CLI flag
|
||||
@@ -149,97 +131,9 @@ func NewClient(opts *ClientOptions) (Client, error) {
|
||||
if opts.Insecure {
|
||||
c.Insecure = true
|
||||
}
|
||||
if localCfg != nil {
|
||||
err = c.refreshAuthToken(localCfg, ctxName, opts.ConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// refreshAuthToken refreshes a JWT auth token if it is invalid (e.g. expired)
|
||||
func (c *client) refreshAuthToken(localCfg *localconfig.LocalConfig, ctxName, configPath string) error {
|
||||
configCtx, err := localCfg.ResolveContext(ctxName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.RefreshToken == "" {
|
||||
// If we have no refresh token, there's no point in doing anything
|
||||
return nil
|
||||
}
|
||||
parser := &jwt.Parser{
|
||||
SkipClaimsValidation: true,
|
||||
}
|
||||
var claims jwt.StandardClaims
|
||||
_, _, err = parser.ParseUnverified(configCtx.User.AuthToken, &claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if claims.Valid() == nil {
|
||||
// token is still valid
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug("Auth token no longer valid. Refreshing")
|
||||
tlsConfig, err := c.tlsConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
}
|
||||
ctx := oidc.ClientContext(context.Background(), httpClient)
|
||||
var scheme string
|
||||
if c.PlainText {
|
||||
scheme = "http"
|
||||
} else {
|
||||
scheme = "https"
|
||||
}
|
||||
conf := &oauth2.Config{
|
||||
ClientID: common.ArgoCDCLIClientAppID,
|
||||
Scopes: []string{"openid", "profile", "email", "groups", "offline_access"},
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s://%s%s/auth", scheme, c.ServerAddr, common.DexAPIEndpoint),
|
||||
TokenURL: fmt.Sprintf("%s://%s%s/token", scheme, c.ServerAddr, common.DexAPIEndpoint),
|
||||
},
|
||||
RedirectURL: fmt.Sprintf("%s://%s/auth/callback", scheme, c.ServerAddr),
|
||||
}
|
||||
t := &oauth2.Token{
|
||||
RefreshToken: c.RefreshToken,
|
||||
}
|
||||
token, err := conf.TokenSource(ctx, t).Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawIDToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return errors.New("no id_token in token response")
|
||||
}
|
||||
refreshToken, _ := token.Extra("refresh_token").(string)
|
||||
c.AuthToken = rawIDToken
|
||||
c.RefreshToken = refreshToken
|
||||
localCfg.UpsertUser(localconfig.User{
|
||||
Name: ctxName,
|
||||
AuthToken: c.AuthToken,
|
||||
RefreshToken: c.RefreshToken,
|
||||
})
|
||||
err = localconfig.WriteLocalConfig(*localCfg, configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewClientOrDie creates a new API client from a set of config options, or fails fatally if the new client creation fails.
|
||||
func NewClientOrDie(opts *ClientOptions) Client {
|
||||
client, err := NewClient(opts)
|
||||
@@ -262,17 +156,25 @@ func (c jwtCredentials) RequireTransportSecurity() bool {
|
||||
func (c jwtCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
|
||||
return map[string]string{
|
||||
MetaDataTokenKey: c.Token,
|
||||
"tokens": c.Token, // legacy key. delete eventually
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) NewConn() (*grpc.ClientConn, error) {
|
||||
var creds credentials.TransportCredentials
|
||||
if !c.PlainText {
|
||||
tlsConfig, err := c.tlsConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var tlsConfig tls.Config
|
||||
if len(c.CertPEMData) > 0 {
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(c.CertPEMData) {
|
||||
return nil, fmt.Errorf("credentials: failed to append certificates")
|
||||
}
|
||||
tlsConfig.RootCAs = cp
|
||||
}
|
||||
creds = credentials.NewTLS(tlsConfig)
|
||||
if c.Insecure {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
creds = credentials.NewTLS(&tlsConfig)
|
||||
}
|
||||
endpointCredentials := jwtCredentials{
|
||||
Token: c.AuthToken,
|
||||
@@ -280,21 +182,6 @@ func (c *client) NewConn() (*grpc.ClientConn, error) {
|
||||
return grpc_util.BlockingDial(context.Background(), "tcp", c.ServerAddr, creds, grpc.WithPerRPCCredentials(endpointCredentials))
|
||||
}
|
||||
|
||||
func (c *client) tlsConfig() (*tls.Config, error) {
|
||||
var tlsConfig tls.Config
|
||||
if len(c.CertPEMData) > 0 {
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(c.CertPEMData) {
|
||||
return nil, fmt.Errorf("credentials: failed to append certificates")
|
||||
}
|
||||
tlsConfig.RootCAs = cp
|
||||
}
|
||||
if c.Insecure {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
return &tlsConfig, nil
|
||||
}
|
||||
|
||||
func (c *client) ClientOptions() ClientOptions {
|
||||
return ClientOptions{
|
||||
ServerAddr: c.ServerAddr,
|
||||
@@ -405,37 +292,3 @@ func (c *client) NewVersionClientOrDie() (*grpc.ClientConn, version.VersionServi
|
||||
}
|
||||
return conn, versionIf
|
||||
}
|
||||
|
||||
func (c *client) NewProjectClient() (*grpc.ClientConn, project.ProjectServiceClient, error) {
|
||||
conn, err := c.NewConn()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
projIf := project.NewProjectServiceClient(conn)
|
||||
return conn, projIf, nil
|
||||
}
|
||||
|
||||
func (c *client) NewProjectClientOrDie() (*grpc.ClientConn, project.ProjectServiceClient) {
|
||||
conn, projIf, err := c.NewProjectClient()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
|
||||
}
|
||||
return conn, projIf
|
||||
}
|
||||
|
||||
func (c *client) NewAccountClient() (*grpc.ClientConn, account.AccountServiceClient, error) {
|
||||
conn, err := c.NewConn()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
usrIf := account.NewAccountServiceClient(conn)
|
||||
return conn, usrIf, nil
|
||||
}
|
||||
|
||||
func (c *client) NewAccountClientOrDie() (*grpc.ClientConn, account.AccountServiceClient) {
|
||||
conn, usrIf, err := c.NewAccountClient()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
|
||||
}
|
||||
return conn, usrIf
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ const (
|
||||
ApplicationShortName string = "app"
|
||||
ApplicationFullName string = ApplicationPlural + "." + Group
|
||||
|
||||
// AppProject constants
|
||||
AppProjectKind string = "AppProject"
|
||||
AppProjectSingular string = "appproject"
|
||||
AppProjectPlural string = "appprojects"
|
||||
AppProjectShortName string = "appproject"
|
||||
AppProjectFullName string = AppProjectPlural + "." + Group
|
||||
// Cluster constants
|
||||
ClusterKind string = "Cluster"
|
||||
ClusterSingular string = "cluster"
|
||||
ClusterPlural string = "clusters"
|
||||
ClusterShortName string = "cluster"
|
||||
ClusterFullName string = ClusterPlural + "." + Group
|
||||
)
|
||||
|
||||
@@ -13,36 +13,6 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
|
||||
// Package-wide variables from generator "generated".
|
||||
option go_package = "v1alpha1";
|
||||
|
||||
// AppProject is a definition of AppProject resource.
|
||||
// +genclient
|
||||
// +genclient:noStatus
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
message AppProject {
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||
|
||||
optional AppProjectSpec spec = 2;
|
||||
}
|
||||
|
||||
// AppProjectList is list of AppProject resources
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
message AppProjectList {
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
|
||||
|
||||
repeated AppProject items = 2;
|
||||
}
|
||||
|
||||
// AppProjectSpec represents
|
||||
message AppProjectSpec {
|
||||
// SourceRepos contains list of git repository URLs which can be used for deployment
|
||||
repeated string sourceRepos = 1;
|
||||
|
||||
// Destinations contains list of destinations available for deployment
|
||||
repeated ApplicationDestination destinations = 2;
|
||||
|
||||
// Description contains optional project description
|
||||
optional string description = 3;
|
||||
}
|
||||
|
||||
// Application is a definition of Application resource.
|
||||
// +genclient
|
||||
// +genclient:noStatus
|
||||
@@ -85,24 +55,21 @@ message ApplicationList {
|
||||
|
||||
// ApplicationSource contains information about github repository, path within repository and target application environment.
|
||||
message ApplicationSource {
|
||||
// RepoURL is the git repository URL of the application manifests
|
||||
// RepoURL is the repository URL containing the ksonnet application.
|
||||
optional string repoURL = 1;
|
||||
|
||||
// Path is a directory path within the repository containing a
|
||||
// Path is a directory path within repository which contains ksonnet application.
|
||||
optional string path = 2;
|
||||
|
||||
// Environment is a ksonnet application environment name
|
||||
// Environment is a ksonnet application environment name.
|
||||
optional string environment = 3;
|
||||
|
||||
// TargetRevision defines the commit, tag, or branch in which to sync the application to.
|
||||
// If omitted, will sync to HEAD
|
||||
optional string targetRevision = 4;
|
||||
|
||||
// ComponentParameterOverrides are a list of parameter override values
|
||||
// Environment parameter override values
|
||||
repeated ComponentParameter componentParameterOverrides = 5;
|
||||
|
||||
// ValuesFiles is a list of Helm values files to use when generating a template
|
||||
repeated string valuesFiles = 6;
|
||||
}
|
||||
|
||||
// ApplicationSpec represents desired application state. Contains link to repository with application definition and additional parameters link definition revision.
|
||||
@@ -112,9 +79,6 @@ message ApplicationSpec {
|
||||
|
||||
// Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml
|
||||
optional ApplicationDestination destination = 2;
|
||||
|
||||
// Project is a application project name. Empty name means that application belongs to 'default' project.
|
||||
optional string project = 3;
|
||||
}
|
||||
|
||||
// ApplicationStatus contains information about application status in target environment.
|
||||
@@ -155,8 +119,8 @@ message Cluster {
|
||||
// Config holds cluster information for connecting to a cluster
|
||||
optional ClusterConfig config = 3;
|
||||
|
||||
// ConnectionState contains information about cluster connection state
|
||||
optional ConnectionState connectionState = 4;
|
||||
// Message can hold a status message or error.
|
||||
optional string message = 4;
|
||||
}
|
||||
|
||||
// ClusterConfig is the configuration attributes. This structure is subset of the go-client
|
||||
@@ -192,6 +156,8 @@ message ComparisonResult {
|
||||
optional string status = 5;
|
||||
|
||||
repeated ResourceState resources = 6;
|
||||
|
||||
optional string error = 7;
|
||||
}
|
||||
|
||||
// ComponentParameter contains information about component parameter value
|
||||
@@ -203,15 +169,6 @@ message ComponentParameter {
|
||||
optional string value = 3;
|
||||
}
|
||||
|
||||
// ConnectionState contains information about remote resource connection state
|
||||
message ConnectionState {
|
||||
optional string status = 1;
|
||||
|
||||
optional string message = 2;
|
||||
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time attemptedAt = 3;
|
||||
}
|
||||
|
||||
// DeploymentInfo contains information relevant to an application deployment
|
||||
message DeploymentInfo {
|
||||
repeated ComponentParameter params = 1;
|
||||
@@ -231,27 +188,6 @@ message HealthStatus {
|
||||
optional string statusDetails = 2;
|
||||
}
|
||||
|
||||
// HookStatus contains status about a hook invocation
|
||||
message HookStatus {
|
||||
// Name is the resource name
|
||||
optional string name = 1;
|
||||
|
||||
// Kind is the resource kind
|
||||
optional string kind = 2;
|
||||
|
||||
// APIVersion is the resource API version
|
||||
optional string apiVersion = 3;
|
||||
|
||||
// Type is the type of hook (e.g. PreSync, Sync, PostSync, Skip)
|
||||
optional string type = 4;
|
||||
|
||||
// Status a simple, high-level summary of where the resource is in its lifecycle
|
||||
optional string status = 5;
|
||||
|
||||
// A human readable message indicating details about why the resource is in this condition.
|
||||
optional string message = 6;
|
||||
}
|
||||
|
||||
// Operation contains requested operation parameters.
|
||||
message Operation {
|
||||
optional SyncOperation sync = 1;
|
||||
@@ -293,7 +229,7 @@ message Repository {
|
||||
|
||||
optional string sshPrivateKey = 4;
|
||||
|
||||
optional ConnectionState connectionState = 5;
|
||||
optional string message = 5;
|
||||
}
|
||||
|
||||
// RepositoryList is a collection of Repositories.
|
||||
@@ -311,8 +247,6 @@ message ResourceDetails {
|
||||
optional string namespace = 3;
|
||||
|
||||
optional string message = 4;
|
||||
|
||||
optional string status = 5;
|
||||
}
|
||||
|
||||
// ResourceNode contains information about live resource and its children
|
||||
@@ -345,53 +279,16 @@ message RollbackOperation {
|
||||
|
||||
// SyncOperation contains sync operation details.
|
||||
message SyncOperation {
|
||||
// Revision is the git revision in which to sync the application to
|
||||
optional string revision = 1;
|
||||
|
||||
// Prune deletes resources that are no longer tracked in git
|
||||
optional bool prune = 2;
|
||||
|
||||
// DryRun will perform a `kubectl apply --dry-run` without actually performing the sync
|
||||
optional bool dryRun = 3;
|
||||
|
||||
// SyncStrategy describes how to perform the sync
|
||||
optional SyncStrategy syncStrategy = 4;
|
||||
}
|
||||
|
||||
// SyncOperationResult represent result of sync operation
|
||||
message SyncOperationResult {
|
||||
// Resources holds the sync result of each individual resource
|
||||
repeated ResourceDetails resources = 1;
|
||||
|
||||
// Revision holds the git commit SHA of the sync
|
||||
optional string revision = 2;
|
||||
|
||||
// Hooks contains list of hook resource statuses associated with this operation
|
||||
repeated HookStatus hooks = 3;
|
||||
}
|
||||
|
||||
// SyncStrategy indicates the
|
||||
message SyncStrategy {
|
||||
// Apply wil perform a `kubectl apply` to perform the sync. This is the default strategy
|
||||
optional SyncStrategyApply apply = 1;
|
||||
|
||||
// Hook will submit any referenced resources to perform the sync
|
||||
optional SyncStrategyHook hook = 2;
|
||||
}
|
||||
|
||||
// SyncStrategyApply uses `kubectl apply` to perform the apply
|
||||
message SyncStrategyApply {
|
||||
// Force indicates whether or not to supply the --force flag to `kubectl apply`.
|
||||
// The --force flag deletes and re-create the resource, when PATCH encounters conflict and has
|
||||
// retried for 5 times.
|
||||
optional bool force = 1;
|
||||
}
|
||||
|
||||
// SyncStrategyHook will perform a sync using hooks annotations.
|
||||
// If no hook annotation is specified falls back to `kubectl apply`.
|
||||
message SyncStrategyHook {
|
||||
// Embed SyncStrategyApply type to inherit any `apply` options
|
||||
optional SyncStrategyApply syncStrategyApply = 1;
|
||||
}
|
||||
|
||||
// TLSClientConfig contains settings to enable transport layer security
|
||||
|
||||
@@ -11,9 +11,7 @@ type objectMeta struct {
|
||||
}
|
||||
|
||||
func (a *Application) GetMetadata() *objectMeta {
|
||||
var om objectMeta
|
||||
if a != nil {
|
||||
om.Name = &a.Name
|
||||
return &objectMeta{
|
||||
Name: &a.Name,
|
||||
}
|
||||
return &om
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ var (
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
SchemeGroupVersion = schema.GroupVersion{Group: application.Group, Version: "v1alpha1"}
|
||||
ApplicationSchemaGroupVersionKind = schema.GroupVersionKind{Group: application.Group, Version: "v1alpha1", Kind: application.ApplicationKind}
|
||||
AppProjectSchemaGroupVersionKind = schema.GroupVersionKind{Group: application.Group, Version: "v1alpha1", Kind: application.AppProjectKind}
|
||||
)
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group-qualified GroupResource.
|
||||
@@ -30,8 +29,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Application{},
|
||||
&ApplicationList{},
|
||||
&AppProject{},
|
||||
&AppProjectList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
||||
@@ -2,28 +2,20 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
)
|
||||
|
||||
// SyncOperation contains sync operation details.
|
||||
type SyncOperation struct {
|
||||
// Revision is the git revision in which to sync the application to
|
||||
Revision string `json:"revision,omitempty" protobuf:"bytes,1,opt,name=revision"`
|
||||
// Prune deletes resources that are no longer tracked in git
|
||||
Prune bool `json:"prune,omitempty" protobuf:"bytes,2,opt,name=prune"`
|
||||
// DryRun will perform a `kubectl apply --dry-run` without actually performing the sync
|
||||
DryRun bool `json:"dryRun,omitempty" protobuf:"bytes,3,opt,name=dryRun"`
|
||||
// SyncStrategy describes how to perform the sync
|
||||
SyncStrategy *SyncStrategy `json:"syncStrategy,omitempty" protobuf:"bytes,4,opt,name=syncStrategy"`
|
||||
Prune bool `json:"prune,omitempty" protobuf:"bytes,2,opt,name=prune"`
|
||||
DryRun bool `json:"dryRun,omitempty" protobuf:"bytes,3,opt,name=dryRun"`
|
||||
}
|
||||
|
||||
type RollbackOperation struct {
|
||||
@@ -41,11 +33,10 @@ type Operation struct {
|
||||
type OperationPhase string
|
||||
|
||||
const (
|
||||
OperationRunning OperationPhase = "Running"
|
||||
OperationTerminating OperationPhase = "Terminating"
|
||||
OperationFailed OperationPhase = "Failed"
|
||||
OperationError OperationPhase = "Error"
|
||||
OperationSucceeded OperationPhase = "Succeeded"
|
||||
OperationRunning OperationPhase = "Running"
|
||||
OperationFailed OperationPhase = "Failed"
|
||||
OperationError OperationPhase = "Error"
|
||||
OperationSucceeded OperationPhase = "Succeeded"
|
||||
)
|
||||
|
||||
func (os OperationPhase) Completed() bool {
|
||||
@@ -78,95 +69,16 @@ type OperationState struct {
|
||||
FinishedAt *metav1.Time `json:"finishedAt" protobuf:"bytes,7,opt,name=finishedAt"`
|
||||
}
|
||||
|
||||
// SyncStrategy indicates the
|
||||
type SyncStrategy struct {
|
||||
// Apply wil perform a `kubectl apply` to perform the sync. This is the default strategy
|
||||
Apply *SyncStrategyApply `json:"apply,omitempty" protobuf:"bytes,1,opt,name=apply"`
|
||||
// Hook will submit any referenced resources to perform the sync
|
||||
Hook *SyncStrategyHook `json:"hook,omitempty" protobuf:"bytes,2,opt,name=hook"`
|
||||
}
|
||||
|
||||
// SyncStrategyApply uses `kubectl apply` to perform the apply
|
||||
type SyncStrategyApply struct {
|
||||
// Force indicates whether or not to supply the --force flag to `kubectl apply`.
|
||||
// The --force flag deletes and re-create the resource, when PATCH encounters conflict and has
|
||||
// retried for 5 times.
|
||||
Force bool `json:"force,omitempty" protobuf:"bytes,1,opt,name=force"`
|
||||
}
|
||||
|
||||
// SyncStrategyHook will perform a sync using hooks annotations.
|
||||
// If no hook annotation is specified falls back to `kubectl apply`.
|
||||
type SyncStrategyHook struct {
|
||||
// Embed SyncStrategyApply type to inherit any `apply` options
|
||||
SyncStrategyApply `protobuf:"bytes,1,opt,name=syncStrategyApply"`
|
||||
}
|
||||
|
||||
type HookType string
|
||||
|
||||
const (
|
||||
HookTypePreSync HookType = "PreSync"
|
||||
HookTypeSync HookType = "Sync"
|
||||
HookTypePostSync HookType = "PostSync"
|
||||
HookTypeSkip HookType = "Skip"
|
||||
|
||||
// NOTE: we may consider adding SyncFail hook. With a SyncFail hook, finalizer-like logic could
|
||||
// be implemented by specifying both PostSync,SyncFail in the hook annotation:
|
||||
// (e.g.: argocd.argoproj.io/hook: PostSync,SyncFail)
|
||||
//HookTypeSyncFail HookType = "SyncFail"
|
||||
)
|
||||
|
||||
type HookDeletePolicy string
|
||||
|
||||
const (
|
||||
HookDeletePolicyHookSucceeded HookDeletePolicy = "HookSucceeded"
|
||||
HookDeletePolicyHookFailed HookDeletePolicy = "HookFailed"
|
||||
)
|
||||
|
||||
// HookStatus contains status about a hook invocation
|
||||
type HookStatus struct {
|
||||
// Name is the resource name
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
// Kind is the resource kind
|
||||
Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"`
|
||||
// APIVersion is the resource API version
|
||||
APIVersion string `json:"apiVersion" protobuf:"bytes,3,opt,name=apiVersion"`
|
||||
// Type is the type of hook (e.g. PreSync, Sync, PostSync, Skip)
|
||||
Type HookType `json:"type" protobuf:"bytes,4,opt,name=type"`
|
||||
// Status a simple, high-level summary of where the resource is in its lifecycle
|
||||
Status OperationPhase `json:"status" protobuf:"bytes,5,opt,name=status"`
|
||||
// A human readable message indicating details about why the resource is in this condition.
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
|
||||
}
|
||||
|
||||
// SyncOperationResult represent result of sync operation
|
||||
type SyncOperationResult struct {
|
||||
// Resources holds the sync result of each individual resource
|
||||
Resources []*ResourceDetails `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"`
|
||||
// Revision holds the git commit SHA of the sync
|
||||
Revision string `json:"revision" protobuf:"bytes,2,opt,name=revision"`
|
||||
// Hooks contains list of hook resource statuses associated with this operation
|
||||
Hooks []*HookStatus `json:"hooks,omitempty" protobuf:"bytes,3,opt,name=hooks"`
|
||||
}
|
||||
|
||||
type ResourceSyncStatus string
|
||||
|
||||
const (
|
||||
ResourceDetailsSynced ResourceSyncStatus = "Synced"
|
||||
ResourceDetailsSyncFailed ResourceSyncStatus = "SyncFailed"
|
||||
ResourceDetailsSyncedAndPruned ResourceSyncStatus = "SyncedAndPruned"
|
||||
ResourceDetailsPruningRequired ResourceSyncStatus = "PruningRequired"
|
||||
)
|
||||
|
||||
func (s ResourceSyncStatus) Successful() bool {
|
||||
return s != ResourceDetailsSyncFailed
|
||||
Resources []*ResourceDetails `json:"resources" protobuf:"bytes,1,opt,name=resources"`
|
||||
}
|
||||
|
||||
type ResourceDetails struct {
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"`
|
||||
Namespace string `json:"namespace" protobuf:"bytes,3,opt,name=namespace"`
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,4,opt,name=message"`
|
||||
Status ResourceSyncStatus `json:"status,omitempty" protobuf:"bytes,5,opt,name=status"`
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"`
|
||||
Namespace string `json:"namespace" protobuf:"bytes,3,opt,name=namespace"`
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,4,opt,name=message"`
|
||||
}
|
||||
|
||||
// DeploymentInfo contains information relevant to an application deployment
|
||||
@@ -216,32 +128,28 @@ type ApplicationSpec struct {
|
||||
Source ApplicationSource `json:"source" protobuf:"bytes,1,opt,name=source"`
|
||||
// Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml
|
||||
Destination ApplicationDestination `json:"destination" protobuf:"bytes,2,name=destination"`
|
||||
// Project is a application project name. Empty name means that application belongs to 'default' project.
|
||||
Project string `json:"project" protobuf:"bytes,3,name=project"`
|
||||
}
|
||||
|
||||
// ComponentParameter contains information about component parameter value
|
||||
type ComponentParameter struct {
|
||||
Component string `json:"component,omitempty" protobuf:"bytes,1,opt,name=component"`
|
||||
Component string `json:"component" protobuf:"bytes,1,opt,name=component"`
|
||||
Name string `json:"name" protobuf:"bytes,2,opt,name=name"`
|
||||
Value string `json:"value" protobuf:"bytes,3,opt,name=value"`
|
||||
}
|
||||
|
||||
// ApplicationSource contains information about github repository, path within repository and target application environment.
|
||||
type ApplicationSource struct {
|
||||
// RepoURL is the git repository URL of the application manifests
|
||||
// RepoURL is the repository URL containing the ksonnet application.
|
||||
RepoURL string `json:"repoURL" protobuf:"bytes,1,opt,name=repoURL"`
|
||||
// Path is a directory path within the repository containing a
|
||||
// Path is a directory path within repository which contains ksonnet application.
|
||||
Path string `json:"path" protobuf:"bytes,2,opt,name=path"`
|
||||
// Environment is a ksonnet application environment name
|
||||
Environment string `json:"environment,omitempty" protobuf:"bytes,3,opt,name=environment"`
|
||||
// Environment is a ksonnet application environment name.
|
||||
Environment string `json:"environment" protobuf:"bytes,3,opt,name=environment"`
|
||||
// TargetRevision defines the commit, tag, or branch in which to sync the application to.
|
||||
// If omitted, will sync to HEAD
|
||||
TargetRevision string `json:"targetRevision,omitempty" protobuf:"bytes,4,opt,name=targetRevision"`
|
||||
// ComponentParameterOverrides are a list of parameter override values
|
||||
// Environment parameter override values
|
||||
ComponentParameterOverrides []ComponentParameter `json:"componentParameterOverrides,omitempty" protobuf:"bytes,5,opt,name=componentParameterOverrides"`
|
||||
// ValuesFiles is a list of Helm values files to use when generating a template
|
||||
ValuesFiles []string `json:"valuesFiles,omitempty" protobuf:"bytes,6,opt,name=valuesFiles"`
|
||||
}
|
||||
|
||||
// ApplicationDestination contains deployment destination information
|
||||
@@ -257,7 +165,8 @@ type ComparisonStatus string
|
||||
|
||||
// Possible comparison results
|
||||
const (
|
||||
ComparisonStatusUnknown ComparisonStatus = "Unknown"
|
||||
ComparisonStatusUnknown ComparisonStatus = ""
|
||||
ComparisonStatusError ComparisonStatus = "Error"
|
||||
ComparisonStatusSynced ComparisonStatus = "Synced"
|
||||
ComparisonStatusOutOfSync ComparisonStatus = "OutOfSync"
|
||||
)
|
||||
@@ -272,23 +181,11 @@ type ApplicationStatus struct {
|
||||
Conditions []ApplicationCondition `json:"conditions,omitempty" protobuf:"bytes,6,opt,name=conditions"`
|
||||
}
|
||||
|
||||
// ApplicationConditionType represents type of application condition. Type name has following convention:
|
||||
// prefix "Error" means error condition
|
||||
// prefix "Warning" means warning condition
|
||||
// prefix "Info" means informational condition
|
||||
type ApplicationConditionType = string
|
||||
|
||||
const (
|
||||
// ApplicationConditionDeletionError indicates that controller failed to delete application
|
||||
ApplicationConditionDeletionError = "DeletionError"
|
||||
// ApplicationConditionInvalidSpecError indicates that application source is invalid
|
||||
ApplicationConditionInvalidSpecError = "InvalidSpecError"
|
||||
// ApplicationComparisonError indicates controller failed to compare application state
|
||||
ApplicationConditionComparisonError = "ComparisonError"
|
||||
// ApplicationConditionUnknownError indicates an unknown controller error
|
||||
ApplicationConditionUnknownError = "UnknownError"
|
||||
// ApplicationConditionSharedResourceWarning indicates that controller detected resources which belongs to more than one application
|
||||
ApplicationConditionSharedResourceWarning = "SharedResourceWarning"
|
||||
)
|
||||
|
||||
// ApplicationCondition contains details about current application condition
|
||||
@@ -305,6 +202,7 @@ type ComparisonResult struct {
|
||||
ComparedTo ApplicationSource `json:"comparedTo" protobuf:"bytes,2,opt,name=comparedTo"`
|
||||
Status ComparisonStatus `json:"status" protobuf:"bytes,5,opt,name=status,casttype=ComparisonStatus"`
|
||||
Resources []ResourceState `json:"resources" protobuf:"bytes,6,opt,name=resources"`
|
||||
Error string `json:"error" protobuf:"bytes,7,opt,name=error"`
|
||||
}
|
||||
|
||||
type HealthStatus struct {
|
||||
@@ -315,11 +213,10 @@ type HealthStatus struct {
|
||||
type HealthStatusCode = string
|
||||
|
||||
const (
|
||||
HealthStatusUnknown = "Unknown"
|
||||
HealthStatusUnknown = ""
|
||||
HealthStatusProgressing = "Progressing"
|
||||
HealthStatusHealthy = "Healthy"
|
||||
HealthStatusDegraded = "Degraded"
|
||||
HealthStatusMissing = "Missing"
|
||||
)
|
||||
|
||||
// ResourceNode contains information about live resource and its children
|
||||
@@ -337,22 +234,6 @@ type ResourceState struct {
|
||||
Health HealthStatus `json:"health,omitempty" protobuf:"bytes,5,opt,name=health"`
|
||||
}
|
||||
|
||||
// ConnectionStatus represents connection status
|
||||
type ConnectionStatus = string
|
||||
|
||||
const (
|
||||
ConnectionStatusUnknown = "Unknown"
|
||||
ConnectionStatusSuccessful = "Successful"
|
||||
ConnectionStatusFailed = "Failed"
|
||||
)
|
||||
|
||||
// ConnectionState contains information about remote resource connection state
|
||||
type ConnectionState struct {
|
||||
Status ConnectionStatus `json:"status" protobuf:"bytes,1,opt,name=status"`
|
||||
Message string `json:"message" protobuf:"bytes,2,opt,name=message"`
|
||||
ModifiedAt *metav1.Time `json:"attemptedAt" protobuf:"bytes,3,opt,name=attemptedAt"`
|
||||
}
|
||||
|
||||
// Cluster is the definition of a cluster resource
|
||||
type Cluster struct {
|
||||
// Server is the API server URL of the Kubernetes cluster
|
||||
@@ -364,8 +245,8 @@ type Cluster struct {
|
||||
// Config holds cluster information for connecting to a cluster
|
||||
Config ClusterConfig `json:"config" protobuf:"bytes,3,opt,name=config"`
|
||||
|
||||
// ConnectionState contains information about cluster connection state
|
||||
ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,4,opt,name=connectionState"`
|
||||
// Message can hold a status message or error.
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,4,opt,name=message"`
|
||||
}
|
||||
|
||||
// ClusterList is a collection of Clusters.
|
||||
@@ -412,11 +293,11 @@ type TLSClientConfig struct {
|
||||
|
||||
// Repository is a Git repository holding application configurations
|
||||
type Repository struct {
|
||||
Repo string `json:"repo" protobuf:"bytes,1,opt,name=repo"`
|
||||
Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
|
||||
Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
|
||||
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
|
||||
ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,5,opt,name=connectionState"`
|
||||
Repo string `json:"repo" protobuf:"bytes,1,opt,name=repo"`
|
||||
Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
|
||||
Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
|
||||
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
|
||||
}
|
||||
|
||||
// RepositoryList is a collection of Repositories.
|
||||
@@ -425,45 +306,6 @@ type RepositoryList struct {
|
||||
Items []Repository `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
||||
// AppProjectList is list of AppProject resources
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type AppProjectList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
|
||||
Items []AppProject `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
||||
// AppProject is a definition of AppProject resource.
|
||||
// +genclient
|
||||
// +genclient:noStatus
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type AppProject struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
|
||||
Spec AppProjectSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
|
||||
}
|
||||
|
||||
// AppProjectSpec represents
|
||||
type AppProjectSpec struct {
|
||||
// SourceRepos contains list of git repository URLs which can be used for deployment
|
||||
SourceRepos []string `json:"sourceRepos" protobuf:"bytes,1,name=sourceRepos"`
|
||||
|
||||
// Destinations contains list of destinations available for deployment
|
||||
Destinations []ApplicationDestination `json:"destinations" protobuf:"bytes,2,name=destination"`
|
||||
|
||||
// Description contains optional project description
|
||||
Description string `json:"description,omitempty" protobuf:"bytes,3,opt,name=description"`
|
||||
}
|
||||
|
||||
func GetDefaultProject(namespace string) AppProject {
|
||||
return AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: common.DefaultAppProjectName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (app *Application) getFinalizerIndex(name string) int {
|
||||
for i, finalizer := range app.Finalizers {
|
||||
if finalizer == name {
|
||||
@@ -491,66 +333,19 @@ func (app *Application) SetCascadedDeletion(prune bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetErrorConditions returns list of application error conditions
|
||||
func (status *ApplicationStatus) GetErrorConditions() []ApplicationCondition {
|
||||
result := make([]ApplicationCondition, 0)
|
||||
for i := range status.Conditions {
|
||||
condition := status.Conditions[i]
|
||||
if condition.IsError() {
|
||||
result = append(result, condition)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IsError returns true if condition is error condition
|
||||
func (condition *ApplicationCondition) IsError() bool {
|
||||
return strings.HasSuffix(condition.Type, "Error")
|
||||
// NeedRefreshAppStatus answers if application status needs to be refreshed. Returns true if application never been compared, has changed or comparison result has expired.
|
||||
func (app *Application) NeedRefreshAppStatus(statusRefreshTimeout time.Duration) bool {
|
||||
return app.Status.ComparisonResult.Status == ComparisonStatusUnknown ||
|
||||
!app.Spec.Source.Equals(app.Status.ComparisonResult.ComparedTo) ||
|
||||
app.Status.ComparisonResult.ComparedAt.Add(statusRefreshTimeout).Before(time.Now())
|
||||
}
|
||||
|
||||
// Equals compares two instances of ApplicationSource and return true if instances are equal.
|
||||
func (source ApplicationSource) Equals(other ApplicationSource) bool {
|
||||
return reflect.DeepEqual(source, other)
|
||||
}
|
||||
|
||||
func (spec ApplicationSpec) BelongsToDefaultProject() bool {
|
||||
return spec.GetProject() == common.DefaultAppProjectName
|
||||
}
|
||||
|
||||
func (spec ApplicationSpec) GetProject() string {
|
||||
if spec.Project == "" {
|
||||
return common.DefaultAppProjectName
|
||||
}
|
||||
return spec.Project
|
||||
}
|
||||
|
||||
func (proj AppProject) IsDefault() bool {
|
||||
return proj.Name == "" || proj.Name == common.DefaultAppProjectName
|
||||
}
|
||||
|
||||
func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
|
||||
if proj.IsDefault() {
|
||||
return true
|
||||
}
|
||||
normalizedURL := git.NormalizeGitURL(src.RepoURL)
|
||||
for _, repoURL := range proj.Spec.SourceRepos {
|
||||
if git.NormalizeGitURL(repoURL) == normalizedURL {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (proj AppProject) IsDestinationPermitted(dst ApplicationDestination) bool {
|
||||
if proj.IsDefault() {
|
||||
return true
|
||||
}
|
||||
for _, item := range proj.Spec.Destinations {
|
||||
if item.Server == dst.Server && item.Namespace == dst.Namespace {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return source.TargetRevision == other.TargetRevision &&
|
||||
source.RepoURL == other.RepoURL &&
|
||||
source.Path == other.Path &&
|
||||
source.Environment == other.Environment
|
||||
}
|
||||
|
||||
// RESTConfig returns a go-client REST config from cluster
|
||||
|
||||
@@ -9,92 +9,6 @@ import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppProject) DeepCopyInto(out *AppProject) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProject.
|
||||
func (in *AppProject) DeepCopy() *AppProject {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppProject)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *AppProject) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppProjectList) DeepCopyInto(out *AppProjectList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]AppProject, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProjectList.
|
||||
func (in *AppProjectList) DeepCopy() *AppProjectList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppProjectList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *AppProjectList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppProjectSpec) DeepCopyInto(out *AppProjectSpec) {
|
||||
*out = *in
|
||||
if in.SourceRepos != nil {
|
||||
in, out := &in.SourceRepos, &out.SourceRepos
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Destinations != nil {
|
||||
in, out := &in.Destinations, &out.Destinations
|
||||
*out = make([]ApplicationDestination, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProjectSpec.
|
||||
func (in *AppProjectSpec) DeepCopy() *AppProjectSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppProjectSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Application) DeepCopyInto(out *Application) {
|
||||
*out = *in
|
||||
@@ -205,11 +119,6 @@ func (in *ApplicationSource) DeepCopyInto(out *ApplicationSource) {
|
||||
*out = make([]ComponentParameter, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ValuesFiles != nil {
|
||||
in, out := &in.ValuesFiles, &out.ValuesFiles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -306,7 +215,6 @@ func (in *ApplicationWatchEvent) DeepCopy() *ApplicationWatchEvent {
|
||||
func (in *Cluster) DeepCopyInto(out *Cluster) {
|
||||
*out = *in
|
||||
in.Config.DeepCopyInto(&out.Config)
|
||||
in.ConnectionState.DeepCopyInto(&out.ConnectionState)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -402,31 +310,6 @@ func (in *ComponentParameter) DeepCopy() *ComponentParameter {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ConnectionState) DeepCopyInto(out *ConnectionState) {
|
||||
*out = *in
|
||||
if in.ModifiedAt != nil {
|
||||
in, out := &in.ModifiedAt, &out.ModifiedAt
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(v1.Time)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionState.
|
||||
func (in *ConnectionState) DeepCopy() *ConnectionState {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ConnectionState)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeploymentInfo) DeepCopyInto(out *DeploymentInfo) {
|
||||
*out = *in
|
||||
@@ -470,22 +353,6 @@ func (in *HealthStatus) DeepCopy() *HealthStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HookStatus) DeepCopyInto(out *HookStatus) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HookStatus.
|
||||
func (in *HookStatus) DeepCopy() *HookStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HookStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Operation) DeepCopyInto(out *Operation) {
|
||||
*out = *in
|
||||
@@ -495,7 +362,7 @@ func (in *Operation) DeepCopyInto(out *Operation) {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(SyncOperation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
if in.Rollback != nil {
|
||||
@@ -568,7 +435,6 @@ func (in *OperationState) DeepCopy() *OperationState {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Repository) DeepCopyInto(out *Repository) {
|
||||
*out = *in
|
||||
in.ConnectionState.DeepCopyInto(&out.ConnectionState)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -589,9 +455,7 @@ func (in *RepositoryList) DeepCopyInto(out *RepositoryList) {
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Repository, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -688,15 +552,6 @@ func (in *RollbackOperation) DeepCopy() *RollbackOperation {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SyncOperation) DeepCopyInto(out *SyncOperation) {
|
||||
*out = *in
|
||||
if in.SyncStrategy != nil {
|
||||
in, out := &in.SyncStrategy, &out.SyncStrategy
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(SyncStrategy)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -725,18 +580,6 @@ func (in *SyncOperationResult) DeepCopyInto(out *SyncOperationResult) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.Hooks != nil {
|
||||
in, out := &in.Hooks, &out.Hooks
|
||||
*out = make([]*HookStatus, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] == nil {
|
||||
(*out)[i] = nil
|
||||
} else {
|
||||
(*out)[i] = new(HookStatus)
|
||||
(*in)[i].DeepCopyInto((*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -750,73 +593,6 @@ func (in *SyncOperationResult) DeepCopy() *SyncOperationResult {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SyncStrategy) DeepCopyInto(out *SyncStrategy) {
|
||||
*out = *in
|
||||
if in.Apply != nil {
|
||||
in, out := &in.Apply, &out.Apply
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(SyncStrategyApply)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
if in.Hook != nil {
|
||||
in, out := &in.Hook, &out.Hook
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(SyncStrategyHook)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncStrategy.
|
||||
func (in *SyncStrategy) DeepCopy() *SyncStrategy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SyncStrategy)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SyncStrategyApply) DeepCopyInto(out *SyncStrategyApply) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncStrategyApply.
|
||||
func (in *SyncStrategyApply) DeepCopy() *SyncStrategyApply {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SyncStrategyApply)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SyncStrategyHook) DeepCopyInto(out *SyncStrategyHook) {
|
||||
*out = *in
|
||||
out.SyncStrategyApply = in.SyncStrategyApply
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncStrategyHook.
|
||||
func (in *SyncStrategyHook) DeepCopy() *SyncStrategyHook {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SyncStrategyHook)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TLSClientConfig) DeepCopyInto(out *TLSClientConfig) {
|
||||
*out = *in
|
||||
|
||||
@@ -25,15 +25,7 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset {
|
||||
|
||||
fakePtr := testing.Fake{}
|
||||
fakePtr.AddReactor("*", "*", testing.ObjectReaction(o))
|
||||
fakePtr.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
||||
gvr := action.GetResource()
|
||||
ns := action.GetNamespace()
|
||||
watch, err := o.Watch(gvr, ns)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return true, watch, nil
|
||||
})
|
||||
fakePtr.AddWatchReactor("*", testing.DefaultWatchReactor(watch.NewFake(), nil))
|
||||
|
||||
return &Clientset{fakePtr, &fakediscovery.FakeDiscovery{Fake: &fakePtr}}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func init() {
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// clientsetscheme "k8s.io/client-go/kuberentes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
@@ -33,4 +33,5 @@ func init() {
|
||||
// correctly.
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
argoprojv1alpha1.AddToScheme(scheme)
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func init() {
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// clientsetscheme "k8s.io/client-go/kuberentes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
@@ -33,4 +33,5 @@ func init() {
|
||||
// correctly.
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
argoprojv1alpha1.AddToScheme(scheme)
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
type ArgoprojV1alpha1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
AppProjectsGetter
|
||||
ApplicationsGetter
|
||||
}
|
||||
|
||||
@@ -18,10 +17,6 @@ type ArgoprojV1alpha1Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *ArgoprojV1alpha1Client) AppProjects(namespace string) AppProjectInterface {
|
||||
return newAppProjects(c, namespace)
|
||||
}
|
||||
|
||||
func (c *ArgoprojV1alpha1Client) Applications(namespace string) ApplicationInterface {
|
||||
return newApplications(c, namespace)
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
scheme "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/scheme"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// AppProjectsGetter has a method to return a AppProjectInterface.
|
||||
// A group's client should implement this interface.
|
||||
type AppProjectsGetter interface {
|
||||
AppProjects(namespace string) AppProjectInterface
|
||||
}
|
||||
|
||||
// AppProjectInterface has methods to work with AppProject resources.
|
||||
type AppProjectInterface interface {
|
||||
Create(*v1alpha1.AppProject) (*v1alpha1.AppProject, error)
|
||||
Update(*v1alpha1.AppProject) (*v1alpha1.AppProject, error)
|
||||
Delete(name string, options *v1.DeleteOptions) error
|
||||
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
|
||||
Get(name string, options v1.GetOptions) (*v1alpha1.AppProject, error)
|
||||
List(opts v1.ListOptions) (*v1alpha1.AppProjectList, error)
|
||||
Watch(opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.AppProject, err error)
|
||||
AppProjectExpansion
|
||||
}
|
||||
|
||||
// appProjects implements AppProjectInterface
|
||||
type appProjects struct {
|
||||
client rest.Interface
|
||||
ns string
|
||||
}
|
||||
|
||||
// newAppProjects returns a AppProjects
|
||||
func newAppProjects(c *ArgoprojV1alpha1Client, namespace string) *appProjects {
|
||||
return &appProjects{
|
||||
client: c.RESTClient(),
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Get takes name of the appProject, and returns the corresponding appProject object, and an error if there is any.
|
||||
func (c *appProjects) Get(name string, options v1.GetOptions) (result *v1alpha1.AppProject, err error) {
|
||||
result = &v1alpha1.AppProject{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("appprojects").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of AppProjects that match those selectors.
|
||||
func (c *appProjects) List(opts v1.ListOptions) (result *v1alpha1.AppProjectList, err error) {
|
||||
result = &v1alpha1.AppProjectList{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("appprojects").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested appProjects.
|
||||
func (c *appProjects) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
opts.Watch = true
|
||||
return c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("appprojects").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
// Create takes the representation of a appProject and creates it. Returns the server's representation of the appProject, and an error, if there is any.
|
||||
func (c *appProjects) Create(appProject *v1alpha1.AppProject) (result *v1alpha1.AppProject, err error) {
|
||||
result = &v1alpha1.AppProject{}
|
||||
err = c.client.Post().
|
||||
Namespace(c.ns).
|
||||
Resource("appprojects").
|
||||
Body(appProject).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a appProject and updates it. Returns the server's representation of the appProject, and an error, if there is any.
|
||||
func (c *appProjects) Update(appProject *v1alpha1.AppProject) (result *v1alpha1.AppProject, err error) {
|
||||
result = &v1alpha1.AppProject{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("appprojects").
|
||||
Name(appProject.Name).
|
||||
Body(appProject).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the appProject and deletes it. Returns an error if one occurs.
|
||||
func (c *appProjects) Delete(name string, options *v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("appprojects").
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *appProjects) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("appprojects").
|
||||
VersionedParams(&listOptions, scheme.ParameterCodec).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched appProject.
|
||||
func (c *appProjects) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.AppProject, err error) {
|
||||
result = &v1alpha1.AppProject{}
|
||||
err = c.client.Patch(pt).
|
||||
Namespace(c.ns).
|
||||
Resource("appprojects").
|
||||
SubResource(subresources...).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
@@ -10,10 +10,6 @@ type FakeArgoprojV1alpha1 struct {
|
||||
*testing.Fake
|
||||
}
|
||||
|
||||
func (c *FakeArgoprojV1alpha1) AppProjects(namespace string) v1alpha1.AppProjectInterface {
|
||||
return &FakeAppProjects{c, namespace}
|
||||
}
|
||||
|
||||
func (c *FakeArgoprojV1alpha1) Applications(namespace string) v1alpha1.ApplicationInterface {
|
||||
return &FakeApplications{c, namespace}
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
package fake
|
||||
|
||||
import (
|
||||
v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakeAppProjects implements AppProjectInterface
|
||||
type FakeAppProjects struct {
|
||||
Fake *FakeArgoprojV1alpha1
|
||||
ns string
|
||||
}
|
||||
|
||||
var appprojectsResource = schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "appprojects"}
|
||||
|
||||
var appprojectsKind = schema.GroupVersionKind{Group: "argoproj.io", Version: "v1alpha1", Kind: "AppProject"}
|
||||
|
||||
// Get takes name of the appProject, and returns the corresponding appProject object, and an error if there is any.
|
||||
func (c *FakeAppProjects) Get(name string, options v1.GetOptions) (result *v1alpha1.AppProject, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewGetAction(appprojectsResource, c.ns, name), &v1alpha1.AppProject{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.AppProject), err
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of AppProjects that match those selectors.
|
||||
func (c *FakeAppProjects) List(opts v1.ListOptions) (result *v1alpha1.AppProjectList, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewListAction(appprojectsResource, appprojectsKind, c.ns, opts), &v1alpha1.AppProjectList{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||
if label == nil {
|
||||
label = labels.Everything()
|
||||
}
|
||||
list := &v1alpha1.AppProjectList{}
|
||||
for _, item := range obj.(*v1alpha1.AppProjectList).Items {
|
||||
if label.Matches(labels.Set(item.Labels)) {
|
||||
list.Items = append(list.Items, item)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested appProjects.
|
||||
func (c *FakeAppProjects) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
return c.Fake.
|
||||
InvokesWatch(testing.NewWatchAction(appprojectsResource, c.ns, opts))
|
||||
|
||||
}
|
||||
|
||||
// Create takes the representation of a appProject and creates it. Returns the server's representation of the appProject, and an error, if there is any.
|
||||
func (c *FakeAppProjects) Create(appProject *v1alpha1.AppProject) (result *v1alpha1.AppProject, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewCreateAction(appprojectsResource, c.ns, appProject), &v1alpha1.AppProject{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.AppProject), err
|
||||
}
|
||||
|
||||
// Update takes the representation of a appProject and updates it. Returns the server's representation of the appProject, and an error, if there is any.
|
||||
func (c *FakeAppProjects) Update(appProject *v1alpha1.AppProject) (result *v1alpha1.AppProject, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateAction(appprojectsResource, c.ns, appProject), &v1alpha1.AppProject{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.AppProject), err
|
||||
}
|
||||
|
||||
// Delete takes name of the appProject and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeAppProjects) Delete(name string, options *v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(appprojectsResource, c.ns, name), &v1alpha1.AppProject{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *FakeAppProjects) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
action := testing.NewDeleteCollectionAction(appprojectsResource, c.ns, listOptions)
|
||||
|
||||
_, err := c.Fake.Invokes(action, &v1alpha1.AppProjectList{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched appProject.
|
||||
func (c *FakeAppProjects) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.AppProject, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(appprojectsResource, c.ns, name, data, subresources...), &v1alpha1.AppProject{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.AppProject), err
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
package v1alpha1
|
||||
|
||||
type AppProjectExpansion interface{}
|
||||
|
||||
type ApplicationExpansion interface{}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// This file was automatically generated by informer-gen
|
||||
|
||||
package argoproj
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// This file was automatically generated by informer-gen
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
application_v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
versioned "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
internalinterfaces "github.com/argoproj/argo-cd/pkg/client/informers/externalversions/internalinterfaces"
|
||||
v1alpha1 "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// AppProjectInformer provides access to a shared informer and lister for
|
||||
// AppProjects.
|
||||
type AppProjectInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() v1alpha1.AppProjectLister
|
||||
}
|
||||
|
||||
type appProjectInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewAppProjectInformer constructs a new informer for AppProject type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewAppProjectInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredAppProjectInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredAppProjectInformer constructs a new informer for AppProject type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFilteredAppProjectInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.ArgoprojV1alpha1().AppProjects(namespace).List(options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.ArgoprojV1alpha1().AppProjects(namespace).Watch(options)
|
||||
},
|
||||
},
|
||||
&application_v1alpha1.AppProject{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *appProjectInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredAppProjectInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *appProjectInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&application_v1alpha1.AppProject{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *appProjectInformer) Lister() v1alpha1.AppProjectLister {
|
||||
return v1alpha1.NewAppProjectLister(f.Informer().GetIndexer())
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// This file was automatically generated by informer-gen
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
@@ -6,8 +8,6 @@ import (
|
||||
|
||||
// Interface provides access to all the informers in this group version.
|
||||
type Interface interface {
|
||||
// AppProjects returns a AppProjectInformer.
|
||||
AppProjects() AppProjectInformer
|
||||
// Applications returns a ApplicationInformer.
|
||||
Applications() ApplicationInformer
|
||||
}
|
||||
@@ -23,11 +23,6 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
|
||||
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// AppProjects returns a AppProjectInformer.
|
||||
func (v *version) AppProjects() AppProjectInformer {
|
||||
return &appProjectInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
|
||||
// Applications returns a ApplicationInformer.
|
||||
func (v *version) Applications() ApplicationInformer {
|
||||
return &applicationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// This file was automatically generated by informer-gen
|
||||
|
||||
package externalversions
|
||||
|
||||
import (
|
||||
|
||||