Compare commits

..

3 Commits

Author SHA1 Message Date
Jesse Suen
08c63ec234 Update install manifests to v0.6.2 2018-07-20 22:18:27 -07:00
Jesse Suen
41f950fd43 Bump version to v0.6.2 2018-07-20 21:52:38 -07:00
Jesse Suen
826ee0dfa0 Health check was using wrong converter for statefulsets, daemonset, replicasets (#439) 2018-07-20 21:49:43 -07:00
238 changed files with 4121 additions and 23559 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@@ -1,215 +1,5 @@
# Changelog
## v0.9.0
### Notes about upgrading from v0.8
* The `server.crt` and `server.key` fields of `argocd-secret` had been renamed to `tls.crt` and `tls.key` for
better integration with cert manager(issue #617). Existing `argocd-secret` should be updated accordingly to
preserve existing TLS certificate.
* Cluster wide resources should be allowed in default project (due to issue #330):
```
argocd project allow-cluster-resource default '*' '*'
```
### Changes since v0.8:
+ Auto-sync option in application CRD instance (issue #79)
+ Support raw jsonnet as an application source (issue #540)
+ Reorder K8s resources to correct creation order (issue #102)
+ Redact K8s secrets from API server payloads (issue #470)
+ Support --in-cluster authentication without providing a kubeconfig (issue #527)
+ Special handling of CustomResourceDefinitions (issue #613)
+ ArgoCD should download helm chart dependencies (issue #582)
+ Export ArgoCD stats as prometheus style metrics (issue #513)
+ Support restricting TLS version (issue #609)
+ Use 'kubectl auth reconcile' before 'kubectl apply' (issue #523)
+ Projects need controls on cluster-scoped resources (issue #330)
+ Support IAM Authentication for managing external K8s clusters (issue #482)
+ Compatibility with cert manager (issue #617)
* Enable TLS for repo server (issue #553)
* Split out dex into it's own deployment (instead of sidecar) (issue #555)
+ [UI] Support selection of helm values files in App creation wizard (issue #499)
+ [UI] Support specifying source revision in App creation wizard allow (issue #503)
+ [UI] Improve resource diff rendering (issue #457)
+ [UI] Indicate number of ready containers in pod (issue #539)
+ [UI] Indicate when app is overriding parameters (issue #503)
+ [UI] Provide a YAML view of resources (issue #396)
+ [UI] Project Role/Token management from UI (issue #548)
+ [UI] App creation wizard should allow specifying source revision (issue #562)
+ [UI] Ability to modify application from UI (issue #615)
+ [UI] indicate when operation is in progress or has failed (issue #566)
- Fix issue where changes were not pulled when tracking a branch (issue #567)
- Lazy enforcement of unknown cluster/namespace restricted resources (issue #599)
- Fix controller hot loop when app source contains bad manifests (issue #568)
- [UI] Fix issue where projects filter does not work when application got changed
- [UI] Creating apps from directories is not obvious (issue #565)
- Helm hooks are being deployed as resources (issue #605)
- Disagreement in three way diff calculation (issue #597)
- SIGSEGV in kube.GetResourcesWithLabel (issue #587)
- ArgoCD fails to deploy resources list (issue #584)
- Branch tracking not working properly (issue #567)
- Controller hot loop when application source has bad manifests (issue #568)
## v0.8.2 (2018-09-12)
- Downgrade ksonnet from v0.12.0 to v0.11.0 due to quote unescape regression
- Fix CLI panic when performing an initial `argocd sync/wait`
## v0.8.1 (2018-09-10)
+ [UI] Support selection of helm values files in App creation wizard (issue #499)
+ [UI] Support specifying source revision in App creation wizard allow (issue #503)
+ [UI] Improve resource diff rendering (issue #457)
+ [UI] Indicate number of ready containers in pod (issue #539)
+ [UI] Indicate when app is overriding parameters (issue #503)
+ [UI] Provide a YAML view of resources (issue #396)
- Fix issue where changes were not pulled when tracking a branch (issue #567)
- Fix controller hot loop when app source contains bad manifests (issue #568)
- [UI] Fix issue where projects filter does not work when application got changed
## v0.8.0 (2018-09-04)
### Notes about upgrading from v0.7
* The RBAC model has been improved to support explicit denies. What this means is that any previous
RBAC policy rules, need to be rewritten to include one extra column with the effect:
`allow` or `deny`. For example, if a rule was written like this:
```
p, my-org:my-team, applications, get, */*
```
It should be rewritten to look like this:
```
p, my-org:my-team, applications, get, */*, allow
```
### Changes since v0.7:
+ Support kustomize as an application source (issue #510)
+ Introduce project tokens for automation access (issue #498)
+ Add ability to delete a single application resource to support immutable updates (issue #262)
+ Update RBAC model to support explicit denies (issue #497)
+ Ability to view Kubernetes events related to application projects for auditing
+ Add PVC healthcheck to controller (issue #501)
+ Run all containers as an unprivileged user (issue #528)
* Upgrade ksonnet to v0.12.0
* Add readiness probes to API server (issue #522)
* Use gRPC error codes instead of fmt.Errorf (#532)
- API discovery becomes best effort when partial resource list is returned (issue #524)
- Fix `argocd app wait` printing incorrect Sync output (issue #542)
- Fix issue where argocd could not sync to a tag (#541)
- Fix issue where static assets were browser cached between upgrades (issue #489)
## v0.7.2 (2018-08-21)
- API discovery becomes best effort when partial resource list is returned (issue #524)
## v0.7.1 (2018-08-03)
+ Surface helm parameters to the application level (#485)
+ [UI] Improve application creation wizard (#459)
+ [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
@@ -222,36 +12,3 @@ RBAC policy rules, need to be rewritten to include one extra column with the eff
* 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

View File

@@ -1,19 +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)
* [kustomize](https://github.com/kubernetes-sigs/kustomize/releases)
* [go-swagger](https://github.com/go-swagger/go-swagger/blob/master/docs/install.md)
* [jq](https://stedolan.github.io/jq/)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
Make sure you have following tools installed [docker](https://docs.docker.com/install/#supported-platforms), [golang](https://golang.org/), [dep](https://github.com/golang/dep), [protobuf](https://developers.google.com/protocol-buffers/), [ksonnet](https://github.com/ksonnet/ksonnet#install), [go-swagger](https://github.com/go-swagger/go-swagger/blob/master/docs/install.md), and [jq](https://stedolan.github.io/jq/)
[kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
```
$ brew 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 ksonnet/tap/ks jq go-swagger
$ go get -u github.com/golang/protobuf/protoc-gen-go
$ go get -u github.com/go-swagger/go-swagger/cmd/swagger
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
@@ -39,18 +30,6 @@ NOTE: The make command can take a while, and we recommend building the specific
* `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
You need to have access to kubernetes cluster (including [minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) or [docker edge](https://docs.docker.com/docker-for-mac/install/) ) in order to run Argo CD on your laptop:

View File

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

83
Dockerfile-argocd Normal file
View File

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

22
Dockerfile-ci-builder Normal file
View File

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

656
Gopkg.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -62,25 +62,27 @@ cli: clean-debug
.PHONY: cli-linux
cli-linux: clean-debug
docker build --iidfile /tmp/argocd-linux-id --target argocd-build --build-arg MAKE_TARGET="cli IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-linux-amd64" .
docker 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:/go/src/github.com/argoproj/argo-cd/dist/argocd-linux-amd64 dist/
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
docker build --iidfile /tmp/argocd-darwin-id --target argocd-build --build-arg MAKE_TARGET="cli GOOS=darwin IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-darwin-amd64" .
docker 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:/go/src/github.com/argoproj/argo-cd/dist/argocd-darwin-amd64 dist/
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
CGO_ENABLED=0 go build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/argocd-util ./cmd/argocd-util
.PHONY: manifests
manifests:
./hack/update-manifests.sh
.PHONY: install-manifest
install-manifest:
if [ "${IMAGE_NAMESPACE}" = "" ] ; then echo "IMAGE_NAMESPACE must be set to build install manifest" ; exit 1 ; fi
echo "# This is an auto-generated file. DO NOT EDIT" > manifests/install.yaml
cat manifests/components/*.yaml | sed 's@\( image: argoproj/\(.*\):latest\)@ image: '"${IMAGE_NAMESPACE}"'/\2:'"${IMAGE_TAG}"'@g' >> manifests/install.yaml
.PHONY: server
server: clean-debug
@@ -88,7 +90,7 @@ server: clean-debug
.PHONY: server-image
server-image:
docker build --build-arg BINARY=argocd-server -t $(IMAGE_PREFIX)argocd-server:$(IMAGE_TAG) .
docker build --build-arg BINARY=argocd-server -t $(IMAGE_PREFIX)argocd-server:$(IMAGE_TAG) -f Dockerfile-argocd .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-server:$(IMAGE_TAG) ; fi
.PHONY: repo-server
@@ -97,7 +99,7 @@ repo-server:
.PHONY: repo-server-image
repo-server-image:
docker build --build-arg BINARY=argocd-repo-server -t $(IMAGE_PREFIX)argocd-repo-server:$(IMAGE_TAG) .
docker build --build-arg BINARY=argocd-repo-server -t $(IMAGE_PREFIX)argocd-repo-server:$(IMAGE_TAG) -f Dockerfile-argocd .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-repo-server:$(IMAGE_TAG) ; fi
.PHONY: controller
@@ -106,17 +108,17 @@ controller:
.PHONY: controller-image
controller-image:
docker build --build-arg BINARY=argocd-application-controller -t $(IMAGE_PREFIX)argocd-application-controller:$(IMAGE_TAG) .
docker build --build-arg BINARY=argocd-application-controller -t $(IMAGE_PREFIX)argocd-application-controller:$(IMAGE_TAG) -f Dockerfile-argocd .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-application-controller:$(IMAGE_TAG) ; fi
.PHONY: cli-image
cli-image:
docker build --build-arg BINARY=argocd -t $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) .
docker build --build-arg BINARY=argocd -t $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) -f Dockerfile-argocd .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) ; fi
.PHONY: builder-image
builder-image:
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) -f Dockerfile-ci-builder .
.PHONY: lint
lint:
@@ -124,16 +126,11 @@ lint:
.PHONY: test
test:
go test -v `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
.PHONY: test-coverage
test-coverage:
go test -v -covermode=count -coverprofile=coverage.out `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
@if [ "$(COVERALLS_TOKEN)" != "" ] ; then goveralls -ignore `find . -name '*.pb*.go' | grep -v vendor/ | sed 's!^./!!' | paste -d, -s -` -coverprofile=coverage.out -service=argo-ci -repotoken "$(COVERALLS_TOKEN)"; else echo 'No COVERALLS_TOKEN env var specified. Skipping submission to Coveralls.io'; fi
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
go test ./test/e2e
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in packr boxes
.PHONY: clean-debug
@@ -148,10 +145,9 @@ clean: clean-debug
precheckin: test lint
.PHONY: release-precheck
release-precheck: manifests
release-precheck: install-manifest
@if [ "$(GIT_TREE_STATE)" != "clean" ]; then echo 'git tree state is $(GIT_TREE_STATE)' ; exit 1; fi
@if [ -z "$(GIT_TAG)" ]; then echo 'commit must be tagged to perform release' ; exit 1; fi
@if [ "$(GIT_TAG)" != "v`cat VERSION`" ]; then echo 'VERSION does not match git tag'; exit 1; fi
.PHONY: release
release: release-precheck precheckin cli-darwin cli-linux server-image controller-image repo-server-image cli-image

View File

@@ -1,10 +1,9 @@
[![Coverage Status](https://coveralls.io/repos/github/argoproj/argo-cd/badge.svg?branch=master)](https://coveralls.io/github/argoproj/argo-cd?branch=master)
# Argo CD - Declarative Continuous Delivery for Kubernetes
## 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.
![Argo CD UI](docs/argocd-ui.gif)
@@ -20,32 +19,21 @@ is provided for additional features.
## How it works
Argo CD follows the **GitOps** pattern of using git repositories as the source of truth for defining
the desired application state. Kubernetes manifests can be specified in several ways:
* [ksonnet](https://ksonnet.io) applications
* [kustomize](https://kustomize.io) applications
* [helm](https://helm.sh) charts
* Plain directory of YAML/json manifests
Argo CD follows the **GitOps** pattern of using git repositories as the source of truth for defining the
desired application state. Kubernetes manifests are specified as [ksonnet](https://ksonnet.io)
applications. Argo CD automates the deployment of the desired
application states in the specified target environments.
![Argo CD Architecture](docs/argocd_architecture.png)
Argo CD automates the deployment of the desired application states in the specified target environments.
Application deployments can track updates to branches, tags, or pinned to a specific version of
manifests at a git commit. See [tracking strategies](docs/tracking_strategies.md) for additional
details about the different tracking strategies available.
For a quick 10 minute overview of ArgoCD, check out the demo presented to the Sig Apps community
meeting:
[![Alt text](https://img.youtube.com/vi/aWDIQMbp1cc/0.jpg)](https://youtu.be/aWDIQMbp1cc?t=1m4s)
## Architecture
![Argo CD Architecture](docs/argocd_architecture.png)
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.
@@ -55,7 +43,6 @@ For additional details, see [architecture overview](docs/architecture.md).
## Features
* Automated deployment of applications to specified target environments
* Flexibility in support for multiple config management tools (Ksonnet, Kustomize, Helm, plain-YAML)
* Continuous monitoring of deployed applications
* Automated or manual syncing of applications to its desired state
* Web and CLI based visualization of applications and differences between live vs. desired state
@@ -64,14 +51,47 @@ For additional details, see [architecture overview](docs/architecture.md).
* SSO Integration (OIDC, OAuth2, LDAP, SAML 2.0, GitLab, Microsoft, LinkedIn)
* Webhook Integration (GitHub, BitBucket, GitLab)
* PreSync, Sync, PostSync hooks to support complex application rollouts (e.g.blue/green & canary upgrades)
* Audit trails for application events and API calls
* Parameter overrides for overriding ksonnet/helm parameters in git
* Service account/access key management for CI pipelines
## 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.11.0 | app/guestbook:latest | sql://dev-test/db | DEBUG=true | |
| staging | 1.10.0 | app/guestbook:e3c0263 | sql://staging/db | | istio,dnsmasq |
| us-west-1 | 1.9.0 | app/guestbook:abc1234 | sql://prod/db | FOO_FEATURE=true | istio,dnsmasq |
| us-west-2 | 1.10.0 | app/guestbook:abc1234 | sql://prod/db | | istio,dnsmasq |
| us-east-1 | 1.9.0 | app/guestbook:abc1234 | sql://prod/db | BAR_FEATURE=true | istio,dnsmasq |
Ksonnet:
* 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
## Roadmap
* Auto-sync toggle to directly apply git state changes to live state
* Revamped UI, and feature parity with CLI
* Audit trails for application events and API calls
* Service account/access key management for CI pipelines
* Revamped UI
* Customizable application actions

View File

@@ -1 +1 @@
0.9.2
0.6.2

View File

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

View File

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

View File

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

View File

@@ -366,8 +366,7 @@ func NewSettingsCommand() *cobra.Command {
errors.CheckError(err)
settingsMgr := settings.NewSettingsManager(kubeclientset, namespace)
_, err = settings.UpdateSettings(superuserPassword, settingsMgr, updateSignature, updateSuperuser, namespace)
errors.CheckError(err)
_ = settings.UpdateSettings(superuserPassword, settingsMgr, updateSignature, updateSuperuser, namespace)
},
}
command.Flags().BoolVar(&updateSuperuser, "update-superuser", false, "force updating the superuser password")

View File

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

View File

@@ -7,7 +7,6 @@ import (
"io"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"text/tabwriter"
@@ -31,7 +30,7 @@ import (
"github.com/argoproj/argo-cd/server/application"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/config"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/diff"
"github.com/argoproj/argo-cd/util/ksonnet"
kubeutil "github.com/argoproj/argo-cd/util/kube"
@@ -72,27 +71,29 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
upsert bool
)
var command = &cobra.Command{
Use: "create APPNAME",
Use: "create",
Short: "Create an application from a git location",
Run: func(c *cobra.Command, args []string) {
if len(args) != 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
var app argoappv1.Application
if fileURL != "" {
parsedURL, err := url.ParseRequestURI(fileURL)
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
err = config.UnmarshalLocalFile(fileURL, &app)
err = cli.UnmarshalLocalFile(fileURL, &app)
} else {
err = config.UnmarshalRemoteFile(fileURL, &app)
err = cli.UnmarshalRemoteFile(fileURL, &app)
}
errors.CheckError(err)
if err != nil {
log.Fatal(err)
}
} else {
if len(args) == 1 {
if appName != "" && appName != args[0] {
log.Fatalf("--name argument '%s' does not match app name %s", appName, args[0])
}
appName = args[0]
}
if appOpts.repoURL == "" || appOpts.appPath == "" || appName == "" {
log.Fatal("name, repo, path are required")
if appOpts.repoURL == "" || appOpts.appPath == "" || appOpts.env == "" || appName == "" {
log.Fatal("name, repo, path, env are required")
os.Exit(1)
}
app = argoappv1.Application{
ObjectMeta: metav1.ObjectMeta{
@@ -116,21 +117,6 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
app.Spec.Destination.Namespace = appOpts.destNamespace
}
setParameterOverrides(&app, appOpts.parameters)
if len(appOpts.valuesFiles) > 0 {
app.Spec.Source.ValuesFiles = appOpts.valuesFiles
}
switch appOpts.syncPolicy {
case "automated":
app.Spec.SyncPolicy = &argoappv1.SyncPolicy{
Automated: &argoappv1.SyncPolicyAutomated{
Prune: appOpts.autoPrune,
},
}
case "none", "":
app.Spec.SyncPolicy = nil
default:
log.Fatalf("Invalid sync-policy: %s", appOpts.syncPolicy)
}
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
appCreateRequest := application.ApplicationCreateRequest{
@@ -143,7 +129,7 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
},
}
command.Flags().StringVarP(&fileURL, "file", "f", "", "Filename or URL to Kubernetes manifests for the app")
command.Flags().StringVar(&appName, "name", "", "A name for the app, ignored if a file is set (DEPRECATED)")
command.Flags().StringVar(&appName, "name", "", "A name for the app, ignored if a file is set")
command.Flags().BoolVar(&upsert, "upsert", false, "Allows to override application with the same name even if supplied application spec is different from existing spec")
addAppFlags(command, &appOpts)
return command
@@ -185,25 +171,10 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
fmt.Printf(printOpFmtStr, "Server:", app.Spec.Destination.Server)
fmt.Printf(printOpFmtStr, "Namespace:", app.Spec.Destination.Namespace)
fmt.Printf(printOpFmtStr, "URL:", appURL(acdClient, app))
fmt.Printf(printOpFmtStr, "Environment:", app.Spec.Source.Environment)
fmt.Printf(printOpFmtStr, "Repo:", app.Spec.Source.RepoURL)
fmt.Printf(printOpFmtStr, "Target:", app.Spec.Source.TargetRevision)
fmt.Printf(printOpFmtStr, "Path:", app.Spec.Source.Path)
if app.Spec.Source.Environment != "" {
fmt.Printf(printOpFmtStr, "Environment:", app.Spec.Source.Environment)
}
if len(app.Spec.Source.ValuesFiles) > 0 {
fmt.Printf(printOpFmtStr, "Helm Values:", strings.Join(app.Spec.Source.ValuesFiles, ","))
}
var syncPolicy string
if app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.Automated != nil {
syncPolicy = "Automated"
if app.Spec.SyncPolicy.Automated.Prune {
syncPolicy += " (Prune)"
}
} else {
syncPolicy = "<none>"
}
fmt.Printf(printOpFmtStr, "Sync Policy:", syncPolicy)
fmt.Printf(printOpFmtStr, "Target:", app.Spec.Source.TargetRevision)
if len(app.Status.Conditions) > 0 {
fmt.Println()
@@ -280,19 +251,10 @@ func printParams(app *argoappv1.Application) {
}
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
isKsonnet := app.Spec.Source.Environment != ""
if isKsonnet {
fmt.Fprintf(w, "COMPONENT\tNAME\tVALUE\tOVERRIDE\n")
for _, p := range app.Status.Parameters {
overrideValue := overrides[fmt.Sprintf("%s/%s", p.Component, p.Name)]
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", p.Component, p.Name, truncateString(p.Value, paramLenLimit), truncateString(overrideValue, paramLenLimit))
}
} else {
fmt.Fprintf(w, "NAME\tVALUE\n")
for _, p := range app.Spec.Source.ComponentParameterOverrides {
fmt.Fprintf(w, "%s\t%s\n", p.Name, truncateString(p.Value, paramLenLimit))
}
fmt.Fprintf(w, "COMPONENT\tNAME\tVALUE\tOVERRIDE\n")
for _, p := range app.Status.Parameters {
overrideValue := overrides[fmt.Sprintf("%s/%s", p.Component, p.Name)]
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", p.Component, p.Name, truncateString(p.Value, paramLenLimit), truncateString(overrideValue, paramLenLimit))
}
_ = w.Flush()
}
@@ -327,25 +289,12 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
app.Spec.Source.Environment = appOpts.env
case "revision":
app.Spec.Source.TargetRevision = appOpts.revision
case "values":
app.Spec.Source.ValuesFiles = appOpts.valuesFiles
case "dest-server":
app.Spec.Destination.Server = appOpts.destServer
case "dest-namespace":
app.Spec.Destination.Namespace = appOpts.destNamespace
case "project":
app.Spec.Project = appOpts.project
case "sync-policy":
switch appOpts.syncPolicy {
case "automated":
app.Spec.SyncPolicy = &argoappv1.SyncPolicy{
Automated: &argoappv1.SyncPolicyAutomated{},
}
case "none":
app.Spec.SyncPolicy = nil
default:
log.Fatalf("Invalid sync-policy: %s", appOpts.syncPolicy)
}
}
})
if visited == 0 {
@@ -353,13 +302,6 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
c.HelpFunc()(c, args)
os.Exit(1)
}
if c.Flags().Changed("auto-prune") {
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
log.Fatal("Cannot set --auto-prune: application not configured with automatic sync")
}
app.Spec.SyncPolicy.Automated.Prune = appOpts.autoPrune
}
setParameterOverrides(app, appOpts.parameters)
oldOverrides := app.Spec.Source.ComponentParameterOverrides
updatedSpec, err := appIf.UpdateSpec(context.Background(), &application.ApplicationUpdateSpecRequest{
@@ -396,10 +338,7 @@ type appOptions struct {
destServer string
destNamespace string
parameters []string
valuesFiles []string
project string
syncPolicy string
autoPrune bool
}
func addAppFlags(command *cobra.Command, opts *appOptions) {
@@ -410,23 +349,19 @@ func addAppFlags(command *cobra.Command, opts *appOptions) {
command.Flags().StringVar(&opts.destServer, "dest-server", "", "K8s cluster URL (overrides the server URL specified in the ksonnet app.yaml)")
command.Flags().StringVar(&opts.destNamespace, "dest-namespace", "", "K8s target namespace (overrides the namespace specified in the ksonnet app.yaml)")
command.Flags().StringArrayVarP(&opts.parameters, "parameter", "p", []string{}, "set a parameter override (e.g. -p guestbook=image=example/guestbook:latest)")
command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Helm values file(s) to use")
command.Flags().StringVar(&opts.project, "project", "", "Application project name")
command.Flags().StringVar(&opts.syncPolicy, "sync-policy", "", "Set the sync policy (one of: automated, none)")
command.Flags().BoolVar(&opts.autoPrune, "auto-prune", false, "Set automatic pruning when sync is automated")
}
// NewApplicationUnsetCommand returns a new instance of an `argocd app unset` command
func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
parameters []string
valuesFiles []string
parameters []string
)
var command = &cobra.Command{
Use: "unset APPNAME -p COMPONENT=PARAM",
Short: "Unset application parameters",
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 || (len(parameters) == 0 && len(valuesFiles) == 0) {
if len(args) != 1 || len(parameters) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
@@ -435,44 +370,22 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
defer util.Close(conn)
app, err := appIf.Get(context.Background(), &application.ApplicationQuery{Name: &appName})
errors.CheckError(err)
isKsonnetApp := app.Spec.Source.Environment != ""
updated := false
for _, paramStr := range parameters {
if isKsonnetApp {
parts := strings.SplitN(paramStr, "=", 2)
if len(parts) != 2 {
log.Fatalf("Expected parameter of the form: component=param. Received: %s", paramStr)
}
overrides := app.Spec.Source.ComponentParameterOverrides
for i, override := range overrides {
if override.Component == parts[0] && override.Name == parts[1] {
app.Spec.Source.ComponentParameterOverrides = append(overrides[0:i], overrides[i+1:]...)
updated = true
break
}
}
} else {
overrides := app.Spec.Source.ComponentParameterOverrides
for i, override := range overrides {
if override.Name == paramStr {
app.Spec.Source.ComponentParameterOverrides = append(overrides[0:i], overrides[i+1:]...)
updated = true
break
}
}
parts := strings.SplitN(paramStr, "=", 2)
if len(parts) != 2 {
log.Fatalf("Expected parameter of the form: component=param. Received: %s", paramStr)
}
}
for _, valuesFile := range valuesFiles {
for i, vf := range app.Spec.Source.ValuesFiles {
if vf == valuesFile {
app.Spec.Source.ValuesFiles = append(app.Spec.Source.ValuesFiles[0:i], app.Spec.Source.ValuesFiles[i+1:]...)
overrides := app.Spec.Source.ComponentParameterOverrides
for i, override := range overrides {
if override.Component == parts[0] && override.Name == parts[1] {
app.Spec.Source.ComponentParameterOverrides = append(overrides[0:i], overrides[i+1:]...)
updated = true
break
}
}
}
if !updated {
return
}
@@ -484,7 +397,6 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
},
}
command.Flags().StringArrayVarP(&parameters, "parameter", "p", []string{}, "unset a parameter override (e.g. -p guestbook=image)")
command.Flags().StringArrayVar(&valuesFiles, "values", []string{}, "unset one or more helm values files")
return command
}
@@ -680,10 +592,9 @@ func formatConditionsSummary(app argoappv1.Application) string {
// NewApplicationWaitCommand returns a new instance of an `argocd app wait` command
func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
watchSync bool
watchHealth bool
watchOperations bool
timeout uint
syncOnly bool
healthOnly bool
timeout uint
)
var command = &cobra.Command{
Use: "wait APPNAME",
@@ -693,22 +604,49 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
c.HelpFunc()(c, args)
os.Exit(1)
}
if !watchSync && !watchHealth && !watchOperations {
watchSync = true
watchHealth = true
watchOperations = true
if syncOnly && healthOnly {
log.Fatalln("Please specify at most one of --sync-only or --health-only.")
}
appName := args[0]
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
_, err := waitOnApplicationStatus(appIf, appName, timeout, watchSync, watchHealth, watchOperations)
if timeout != 0 {
time.AfterFunc(time.Duration(timeout)*time.Second, func() {
cancel()
})
}
// print the initial components to format the tabwriter columns
app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: &appName})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
printAppResources(w, app, false)
_ = w.Flush()
prevCompRes := &app.Status.ComparisonResult
appEventCh := watchApp(ctx, appIf, appName)
for appEvent := range appEventCh {
app := appEvent.Application
printAppStateChange(w, prevCompRes, &app)
_ = w.Flush()
prevCompRes = &app.Status.ComparisonResult
synced := app.Status.ComparisonResult.Status == argoappv1.ComparisonStatusSynced
healthy := app.Status.Health.Status == argoappv1.HealthStatusHealthy
if len(app.Status.GetErrorConditions()) == 0 && ((synced && healthy) || (synced && syncOnly) || (healthy && healthOnly)) {
log.Printf("App %q matches desired state", appName)
return
}
}
log.Fatalf("Timed out (%ds) waiting for app %q match desired state", timeout, appName)
},
}
command.Flags().BoolVar(&watchSync, "sync", false, "Wait for sync")
command.Flags().BoolVar(&watchHealth, "health", false, "Wait for health")
command.Flags().BoolVar(&watchOperations, "operation", false, "Wait for pending operations")
command.Flags().BoolVar(&syncOnly, "sync-only", false, "Wait only for sync")
command.Flags().BoolVar(&healthOnly, "health-only", false, "Wait only for health")
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
return command
}
@@ -820,6 +758,38 @@ func printAppResources(w io.Writer, app *argoappv1.Application, showOperation bo
}
}
// printAppStateChange prints a component state change if it was different from the last time we saw it
func printAppStateChange(w io.Writer, prevComp *argoappv1.ComparisonResult, app *argoappv1.Application) {
getPrevResState := func(kind, name string) (argoappv1.ComparisonStatus, argoappv1.HealthStatusCode) {
for _, res := range prevComp.Resources {
obj, err := argoappv1.UnmarshalToUnstructured(res.TargetState)
errors.CheckError(err)
if obj == nil {
obj, err = argoappv1.UnmarshalToUnstructured(res.LiveState)
errors.CheckError(err)
}
if obj.GetKind() == kind && obj.GetName() == name {
return res.Status, res.Health.Status
}
}
return "", ""
}
if len(app.Status.ComparisonResult.Resources) > 0 {
for _, res := range app.Status.ComparisonResult.Resources {
obj, err := argoappv1.UnmarshalToUnstructured(res.TargetState)
errors.CheckError(err)
if obj == nil {
obj, err = argoappv1.UnmarshalToUnstructured(res.LiveState)
errors.CheckError(err)
}
prevSync, prevHealth := getPrevResState(obj.GetKind(), obj.GetName())
if prevSync != res.Status || prevHealth != res.Health.Status {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", obj.GetKind(), obj.GetName(), res.Status, res.Health.Status)
}
}
}
}
// NewApplicationSyncCommand returns a new instance of an `argocd app sync` command
func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
@@ -860,10 +830,23 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
ctx := context.Background()
_, err := appIf.Sync(ctx, &syncReq)
errors.CheckError(err)
app, err := waitOnApplicationStatus(appIf, appName, timeout, false, false, true)
app, err := waitUntilOperationCompleted(appIf, appName, timeout)
errors.CheckError(err)
// get refreshed app before printing to show accurate sync/health status
app, err = appIf.Get(ctx, &application.ApplicationQuery{Name: &appName, Refresh: true})
errors.CheckError(err)
fmt.Printf(printOpFmtStr, "Application:", appName)
printOperationResult(app.Status.OperationState)
if len(app.Status.ComparisonResult.Resources) > 0 {
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
printAppResources(w, app, true)
_ = w.Flush()
}
pruningRequired := 0
for _, resDetails := range app.Status.OperationState.SyncResult.Resources {
if resDetails.Status == argoappv1.ResourceDetailsPruningRequired {
@@ -888,187 +871,26 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
return command
}
// ResourceState tracks the state of a resource when waiting on an application status.
type resourceState struct {
Kind string
Name string
Status string
Health string
Hook string
Message string
}
func newResourceState(kind, name, status, health, hook, message string) *resourceState {
return &resourceState{
Kind: kind,
Name: name,
Status: status,
Health: health,
Hook: hook,
Message: message,
}
}
// Key returns a unique-ish key for the resource.
func (rs *resourceState) Key() string {
return fmt.Sprintf("%s/%s", rs.Kind, rs.Name)
}
func (rs *resourceState) String() string {
return fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s", rs.Kind, rs.Name, rs.Status, rs.Health, rs.Hook, rs.Message)
}
// Merge merges the new state with any different contents from another resourceState.
// Blank fields in the receiver state will be updated to non-blank.
// Non-blank fields in the receiver state will never be updated to blank.
// Returns whether or not any keys were updated.
func (rs *resourceState) Merge(newState *resourceState) bool {
updated := false
for _, field := range []string{"Status", "Health", "Hook", "Message"} {
v := reflect.ValueOf(rs).Elem().FieldByName(field)
currVal := v.String()
newVal := reflect.ValueOf(newState).Elem().FieldByName(field).String()
if newVal != "" && currVal != newVal {
v.SetString(newVal)
updated = true
}
}
return updated
}
func calculateResourceStates(app *argoappv1.Application) map[string]*resourceState {
resStates := make(map[string]*resourceState)
for _, res := range app.Status.ComparisonResult.Resources {
obj, err := argoappv1.UnmarshalToUnstructured(res.TargetState)
errors.CheckError(err)
if obj == nil {
obj, err = argoappv1.UnmarshalToUnstructured(res.LiveState)
errors.CheckError(err)
}
newState := newResourceState(obj.GetKind(), obj.GetName(), string(res.Status), res.Health.Status, "", "")
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
var opResult *argoappv1.SyncOperationResult
if app.Status.OperationState != nil {
if app.Status.OperationState.SyncResult != nil {
opResult = app.Status.OperationState.SyncResult
} else if app.Status.OperationState.RollbackResult != nil {
opResult = app.Status.OperationState.SyncResult
}
}
if opResult == nil {
return resStates
}
for _, hook := range opResult.Hooks {
newState := newResourceState(hook.Kind, hook.Name, string(hook.Status), "", string(hook.Type), hook.Message)
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
for _, res := range opResult.Resources {
newState := newResourceState(res.Kind, res.Name, "", "", "", res.Message)
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
return resStates
}
func waitOnApplicationStatus(appClient application.ApplicationServiceClient, appName string, timeout uint, watchSync, watchHealth, watchOperation bool) (*argoappv1.Application, error) {
func waitUntilOperationCompleted(appClient application.ApplicationServiceClient, appName string, timeout uint) (*argoappv1.Application, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// refresh controls whether or not we refresh the app before printing the final status.
// We only want to do this when an operation is in progress, since operations are the only
// time when the sync status lags behind when an operation completes
refresh := false
printFinalStatus := func(app *argoappv1.Application) {
var err error
if refresh {
app, err = appClient.Get(context.Background(), &application.ApplicationQuery{Name: &appName, Refresh: true})
errors.CheckError(err)
}
fmt.Println()
fmt.Printf(printOpFmtStr, "Application:", app.Name)
if watchOperation {
printOperationResult(app.Status.OperationState)
}
if len(app.Status.ComparisonResult.Resources) > 0 {
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 5, 0, 2, ' ', 0)
printAppResources(w, app, watchOperation)
_ = w.Flush()
}
}
if timeout != 0 {
time.AfterFunc(time.Duration(timeout)*time.Second, func() {
cancel()
})
}
w := tabwriter.NewWriter(os.Stdout, 5, 0, 2, ' ', 0)
fmt.Fprintln(w, "KIND\tNAME\tSTATUS\tHEALTH\tHOOK\tOPERATIONMSG")
prevStates := make(map[string]*resourceState)
appEventCh := watchApp(ctx, appClient, appName)
var app *argoappv1.Application
for appEvent := range appEventCh {
app = &appEvent.Application
if app.Operation != nil {
refresh = true
if appEvent.Application.Status.OperationState != nil && appEvent.Application.Status.OperationState.Phase.Completed() {
return &appEvent.Application, nil
}
// consider skipped checks successful
synced := !watchSync || app.Status.ComparisonResult.Status == argoappv1.ComparisonStatusSynced
healthy := !watchHealth || app.Status.Health.Status == argoappv1.HealthStatusHealthy
operational := !watchOperation || appEvent.Application.Operation == nil
if len(app.Status.GetErrorConditions()) == 0 && synced && healthy && operational {
printFinalStatus(app)
return app, nil
}
newStates := calculateResourceStates(app)
for _, newState := range newStates {
var doPrint bool
stateKey := newState.Key()
if prevState, found := prevStates[stateKey]; found {
doPrint = prevState.Merge(newState)
} else {
prevStates[stateKey] = newState
doPrint = true
}
if doPrint {
fmt.Fprintln(w, prevStates[stateKey])
}
}
_ = w.Flush()
}
printFinalStatus(app)
return nil, fmt.Errorf("Timed out (%ds) waiting for app %q match desired state", timeout, appName)
}
// setParameterOverrides updates an existing or appends a new parameter override in the application
// If the app is a ksonnet app, then parameters are expected to be in the form: component=param=value
// Otherwise, the app is assumed to be a helm app and is expected to be in the form:
// param=value
func setParameterOverrides(app *argoappv1.Application, parameters []string) {
if len(parameters) == 0 {
return
@@ -1079,28 +901,15 @@ func setParameterOverrides(app *argoappv1.Application, parameters []string) {
} else {
newParams = make([]argoappv1.ComponentParameter, 0)
}
isKsonnetApp := app.Spec.Source.Environment != ""
for _, paramStr := range parameters {
var newParam argoappv1.ComponentParameter
if isKsonnetApp {
parts := strings.SplitN(paramStr, "=", 3)
if len(parts) != 3 {
log.Fatalf("Expected ksonnet parameter of the form: component=param=value. Received: %s", paramStr)
}
newParam = argoappv1.ComponentParameter{
Component: parts[0],
Name: parts[1],
Value: parts[2],
}
} else {
parts := strings.SplitN(paramStr, "=", 2)
if len(parts) != 2 {
log.Fatalf("Expected helm parameter of the form: param=value. Received: %s", paramStr)
}
newParam = argoappv1.ComponentParameter{
Name: parts[0],
Value: parts[1],
}
parts := strings.SplitN(paramStr, "=", 3)
if len(parts) != 3 {
log.Fatalf("Expected parameter of the form: component=param=value. Received: %s", paramStr)
}
newParam := argoappv1.ComponentParameter{
Component: parts[0],
Name: parts[1],
Value: parts[2],
}
index := -1
for i, cp := range newParams {
@@ -1210,8 +1019,18 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
})
errors.CheckError(err)
_, err = waitOnApplicationStatus(appIf, appName, timeout, false, false, true)
app, err = waitUntilOperationCompleted(appIf, appName, timeout)
errors.CheckError(err)
// get refreshed app before printing to show accurate sync/health status
app, err = appIf.Get(ctx, &application.ApplicationQuery{Name: &appName, Refresh: true})
errors.CheckError(err)
fmt.Printf(printOpFmtStr, "Application:", appName)
printOperationResult(app.Status.OperationState)
if !app.Status.OperationState.Phase.Successful() {
os.Exit(1)
}
},
}
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")

View File

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

View File

@@ -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
}

View File

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

View File

@@ -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
}

View File

@@ -87,7 +87,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
}
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
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
}

View File

@@ -27,7 +27,6 @@ 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(NewContextCommand(&clientOpts))
command.AddCommand(NewProjectCommand(&clientOpts))

View File

@@ -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)
}
},

View File

@@ -73,8 +73,6 @@ var (
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"
@@ -102,8 +100,4 @@ var ArgoCDManagerPolicyRules = []rbacv1.PolicyRule{
Resources: []string{"*"},
Verbs: []string{"*"},
},
{
NonResourceURLs: []string{"*"},
Verbs: []string{"*"},
},
}

View File

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

View File

@@ -10,10 +10,12 @@ import (
"time"
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"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/strategicpatch"
@@ -43,9 +45,7 @@ const (
type ApplicationController struct {
namespace string
kubeClientset kubernetes.Interface
kubectl kube.Kubectl
applicationClientset appclientset.Interface
auditLogger *argo.AuditLogger
appRefreshQueue workqueue.RateLimitingInterface
appOperationQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
@@ -68,28 +68,27 @@ func NewApplicationController(
kubeClientset kubernetes.Interface,
applicationClientset appclientset.Interface,
repoClientset reposerver.Clientset,
db db.ArgoDB,
appStateManager AppStateManager,
appResyncPeriod time.Duration,
config *ApplicationControllerConfig,
) *ApplicationController {
db := db.NewDB(namespace, kubeClientset)
kubectlCmd := kube.KubectlCmd{}
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd)
ctrl := ApplicationController{
appRefreshQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
appOperationQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
return &ApplicationController{
namespace: namespace,
kubeClientset: kubeClientset,
kubectl: kubectlCmd,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appRefreshQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
appOperationQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
appRefreshQueue: appRefreshQueue,
appOperationQueue: appOperationQueue,
appStateManager: appStateManager,
appInformer: newApplicationInformer(applicationClientset, appRefreshQueue, appOperationQueue, appResyncPeriod, config),
db: db,
statusRefreshTimeout: appResyncPeriod,
forceRefreshApps: make(map[string]bool),
forceRefreshAppsMutex: &sync.Mutex{},
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, "application-controller"),
}
ctrl.appInformer = ctrl.newApplicationInformer()
return &ctrl
}
// Run starts the Application CRD controller.
@@ -98,14 +97,13 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
defer ctrl.appRefreshQueue.ShutDown()
go ctrl.appInformer.Run(ctx.Done())
go ctrl.watchAppsResources()
if !cache.WaitForCacheSync(ctx.Done(), ctrl.appInformer.HasSynced) {
log.Error("Timed out waiting for caches to sync")
return
}
go ctrl.watchAppsResources()
for i := 0; i < statusProcessors; i++ {
go wait.Until(func() {
for ctrl.processAppRefreshQueueItem() {
@@ -141,13 +139,8 @@ func (ctrl *ApplicationController) isRefreshForced(appName string) bool {
// watchClusterResources watches for resource changes annotated with application label on specified cluster and schedule corresponding app refresh.
func (ctrl *ApplicationController) watchClusterResources(ctx context.Context, item appv1.Cluster) {
retryUntilSucceed(func() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered from panic: %v\n", r)
}
}()
config := item.RESTConfig()
config := item.RESTConfig()
retryUntilSucceed(func() error {
ch, err := kube.WatchResourcesWithLabel(ctx, config, "", common.LabelApplicationName)
if err != nil {
return err
@@ -164,68 +157,26 @@ func (ctrl *ApplicationController) watchClusterResources(ctx context.Context, it
}
}
return fmt.Errorf("resource updates channel has closed")
}, fmt.Sprintf("watch app resources on %s", item.Server), ctx, watchResourcesRetryTimeout)
}, fmt.Sprintf("watch app resources on %s", config.Host), ctx, watchResourcesRetryTimeout)
}
func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {
for _, obj := range apps {
if app, ok := obj.(*appv1.Application); ok && app.Spec.Destination.Server == cluster.Server {
return true
}
}
return false
}
// WatchAppsResources watches for resource changes annotated with application label on all registered clusters and schedule corresponding app refresh.
func (ctrl *ApplicationController) watchAppsResources() {
watchingClusters := make(map[string]struct {
cancel context.CancelFunc
cluster *appv1.Cluster
})
watchingClusters := make(map[string]context.CancelFunc)
retryUntilSucceed(func() error {
clusterEventCallback := func(event *db.ClusterEvent) {
info, ok := watchingClusters[event.Cluster.Server]
hasApps := isClusterHasApps(ctrl.appInformer.GetStore().List(), event.Cluster)
// cluster resources must be watched only if cluster has at least one app
if (event.Type == watch.Deleted || !hasApps) && ok {
info.cancel()
return ctrl.db.WatchClusters(context.Background(), func(event *db.ClusterEvent) {
cancel, ok := watchingClusters[event.Cluster.Server]
if event.Type == watch.Deleted && ok {
cancel()
delete(watchingClusters, event.Cluster.Server)
} else if event.Type != watch.Deleted && !ok && hasApps {
} else if event.Type != watch.Deleted && !ok {
ctx, cancel := context.WithCancel(context.Background())
watchingClusters[event.Cluster.Server] = struct {
cancel context.CancelFunc
cluster *appv1.Cluster
}{
cancel: cancel,
cluster: event.Cluster,
}
watchingClusters[event.Cluster.Server] = cancel
go ctrl.watchClusterResources(ctx, *event.Cluster)
}
}
onAppModified := func(obj interface{}) {
if app, ok := obj.(*appv1.Application); ok {
var cluster *appv1.Cluster
info, infoOk := watchingClusters[app.Spec.Destination.Server]
if infoOk {
cluster = info.cluster
} else {
cluster, _ = ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
}
if cluster != nil {
// trigger cluster event every time when app created/deleted to either start or stop watching resources
clusterEventCallback(&db.ClusterEvent{Cluster: cluster, Type: watch.Modified})
}
}
}
ctrl.appInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: onAppModified, DeleteFunc: onAppModified})
return ctrl.db.WatchClusters(context.Background(), clusterEventCallback)
})
}, "watch clusters", context.Background(), watchResourcesRetryTimeout)
<-context.Background().Done()
@@ -250,7 +201,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)
}
}
@@ -298,13 +249,12 @@ func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext b
}
func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Application) {
logCtx := log.WithField("application", app.Name)
logCtx.Infof("Deleting resources")
log.Infof("Deleting resources for application %s", app.Name)
// Get refreshed application info, since informer app copy might be stale
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(app.Name, metav1.GetOptions{})
if err != nil {
if !errors.IsNotFound(err) {
logCtx.Errorf("Unable to get refreshed application info prior deleting resources: %v", err)
log.Errorf("Unable to get refreshed application info prior deleting resources: %v", err)
}
return
}
@@ -328,14 +278,13 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
}
}
if err != nil {
log.Errorf("Unable to delete application resources: %v", err)
ctrl.setAppCondition(app, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionDeletionError,
Message: err.Error(),
})
message := fmt.Sprintf("Unable to delete application resources: %v", err)
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonStatusRefreshed, Type: v1.EventTypeWarning}, message)
} else {
logCtx.Info("Successfully deleted resources")
log.Infof("Successfully deleted resources for application %s", app.Name)
}
}
@@ -367,12 +316,11 @@ func (ctrl *ApplicationController) setAppCondition(app *appv1.Application, condi
}
func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Application) {
logCtx := log.WithField("application", app.Name)
var state *appv1.OperationState
// Recover from any unexpected panics and automatically set the status to be failed
defer func() {
if r := recover(); r != nil {
logCtx.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
state.Phase = appv1.OperationError
if rerr, ok := r.(error); ok {
state.Message = rerr.Error()
@@ -389,20 +337,20 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
// again. To detect this, always retrieve the latest version to ensure it is not stale.
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
logCtx.Errorf("Failed to retrieve latest application state: %v", err)
log.Errorf("Failed to retrieve latest application state: %v", err)
return
}
if !isOperationInProgress(freshApp) {
logCtx.Infof("Skipping operation on stale application state")
log.Infof("Skipping operation on stale application state (%s)", app.ObjectMeta.Name)
return
}
app = freshApp
state = app.Status.OperationState.DeepCopy()
logCtx.Infof("Resuming in-progress operation. phase: %s, message: %s", state.Phase, state.Message)
log.Infof("Resuming in-progress operation. app: %s, phase: %s, message: %s", app.ObjectMeta.Name, state.Phase, state.Message)
} else {
state = &appv1.OperationState{Phase: appv1.OperationRunning, Operation: *app.Operation, StartedAt: metav1.Now()}
ctrl.setOperationState(app, state)
logCtx.Infof("Initialized new operation: %v", *app.Operation)
log.Infof("Initialized new operation. app: %s, operation: %v", app.ObjectMeta.Name, *app.Operation)
}
ctrl.appStateManager.SyncAppState(app, state)
@@ -463,18 +411,6 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
return err
}
log.Infof("updated '%s' operation (phase: %s)", app.Name, state.Phase)
if state.Phase.Completed() {
eventInfo := argo.EventInfo{Reason: argo.EventReasonOperationCompleted}
var message string
if state.Phase.Successful() {
eventInfo.Type = v1.EventTypeNormal
message = "Operation succeeded"
} else {
eventInfo.Type = v1.EventTypeWarning
message = fmt.Sprintf("Operation failed: %v", state.Message)
}
ctrl.auditLogger.LogAppEvent(app, eventInfo, message)
}
return nil
}, "Update application operation state", context.Background(), updateOperationStateTimeout)
}
@@ -534,16 +470,10 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
parameters = manifestInfo.Params
}
healthState, err := setApplicationHealth(ctrl.kubectl, comparisonResult)
healthState, err := setApplicationHealth(comparisonResult)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
}
syncErrCond := ctrl.autoSync(app, comparisonResult)
if syncErrCond != nil {
conditions = append(conditions, *syncErrCond)
}
ctrl.updateAppStatus(app, comparisonResult, healthState, parameters, conditions)
return
}
@@ -551,20 +481,18 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
// needRefreshAppStatus answers if application status needs to be refreshed.
// Returns true if application never been compared, has changed or comparison result has expired.
func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application, statusRefreshTimeout time.Duration) bool {
logCtx := log.WithFields(log.Fields{"application": app.Name})
var reason string
expired := app.Status.ComparisonResult.ComparedAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
if ctrl.isRefreshForced(app.Name) {
reason = "force refresh"
} else if app.Status.ComparisonResult.Status == appv1.ComparisonStatusUnknown && expired {
} 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 expired {
} 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 != "" {
logCtx.Infof("Refreshing app status (%s)", reason)
log.Infof("Refreshing application '%s' status (%s)", app.Name, reason)
return true
}
return false
@@ -603,7 +531,6 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
appv1.ApplicationConditionUnknownError: true,
appv1.ApplicationConditionComparisonError: true,
appv1.ApplicationConditionSharedResourceWarning: true,
appv1.ApplicationConditionSyncError: true,
}
appConditions := make([]appv1.ApplicationCondition, 0)
for i := 0; i < len(app.Status.Conditions); i++ {
@@ -625,8 +552,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
}
// setApplicationHealth updates the health statuses of all resources performed in the comparison
func setApplicationHealth(kubectl kube.Kubectl, comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
var savedErr error
func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
appHealth := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
if comparisonResult.Status == appv1.ComparisonStatusUnknown {
appHealth.Status = appv1.HealthStatusUnknown
@@ -640,9 +566,9 @@ func setApplicationHealth(kubectl kube.Kubectl, comparisonResult *appv1.Comparis
if err != nil {
return nil, err
}
healthState, err := health.GetAppHealth(kubectl, &obj)
if err != nil && savedErr == nil {
savedErr = err
healthState, err := health.GetAppHealth(&obj)
if err != nil {
return nil, err
}
resource.Health = *healthState
}
@@ -651,7 +577,7 @@ func setApplicationHealth(kubectl kube.Kubectl, comparisonResult *appv1.Comparis
appHealth.Status = resource.Health.Status
}
}
return &appHealth, savedErr
return &appHealth, nil
}
// updateAppStatus persists updates to application status. Detects if there patch
@@ -662,21 +588,12 @@ func (ctrl *ApplicationController) updateAppStatus(
parameters []*appv1.ComponentParameter,
conditions []appv1.ApplicationCondition,
) {
logCtx := log.WithFields(log.Fields{"application": app.Name})
modifiedApp := app.DeepCopy()
if comparisonResult != nil {
modifiedApp.Status.ComparisonResult = *comparisonResult
if app.Status.ComparisonResult.Status != comparisonResult.Status {
message := fmt.Sprintf("Updated sync status: %s -> %s", app.Status.ComparisonResult.Status, comparisonResult.Status)
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonResourceUpdated, Type: v1.EventTypeNormal}, message)
}
logCtx.Infof("Comparison result: prev: %s. current: %s", app.Status.ComparisonResult.Status, comparisonResult.Status)
log.Infof("App %s comparison result: prev: %s. current: %s", app.Name, app.Status.ComparisonResult.Status, comparisonResult.Status)
}
if healthState != nil {
if modifiedApp.Status.Health.Status != healthState.Status {
message := fmt.Sprintf("Updated health status: %s -> %s", modifiedApp.Status.Health.Status, healthState.Status)
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonResourceUpdated, Type: v1.EventTypeNormal}, message)
}
modifiedApp.Status.Health = *healthState
}
if parameters != nil {
@@ -690,104 +607,59 @@ func (ctrl *ApplicationController) updateAppStatus(
}
origBytes, err := json.Marshal(app)
if err != nil {
logCtx.Errorf("Error updating (marshal orig app): %v", err)
log.Errorf("Error updating application %s (marshal orig app): %v", app.Name, err)
return
}
modifiedBytes, err := json.Marshal(modifiedApp)
if err != nil {
logCtx.Errorf("Error updating (marshal modified app): %v", err)
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 {
logCtx.Errorf("Error calculating patch for update: %v", err)
log.Errorf("Error calculating patch for app %s update: %v", app.Name, err)
return
}
if string(patch) == "{}" {
logCtx.Infof("No status changes. Skipping 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 {
logCtx.Warnf("Error updating application: %v", err)
log.Warnf("Error updating application %s: %v", app.Name, err)
} else {
logCtx.Infof("Update successful")
log.Infof("Application %s update successful", app.Name)
}
}
// autoSync will initiate a sync operation for an application configured with automated sync
func (ctrl *ApplicationController) autoSync(app *appv1.Application, comparisonResult *appv1.ComparisonResult) *appv1.ApplicationCondition {
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
return nil
}
logCtx := log.WithFields(log.Fields{"application": app.Name})
if app.Operation != nil {
logCtx.Infof("Skipping auto-sync: another operation is in progress")
return nil
}
// Only perform auto-sync if we detect OutOfSync status. This is to prevent us from attempting
// a sync when application is already in a Synced or Unknown state
if comparisonResult.Status != appv1.ComparisonStatusOutOfSync {
logCtx.Infof("Skipping auto-sync: application status is %s", comparisonResult.Status)
return nil
}
desiredCommitSHA := comparisonResult.Revision
func newApplicationInformer(
appClientset appclientset.Interface,
appQueue workqueue.RateLimitingInterface,
appOperationQueue workqueue.RateLimitingInterface,
appResyncPeriod time.Duration,
config *ApplicationControllerConfig) cache.SharedIndexInformer {
// It is possible for manifests to remain OutOfSync even after a sync/kubectl apply (e.g.
// auto-sync with pruning disabled). We need to ensure that we do not keep Syncing an
// application in an infinite loop. To detect this, we only attempt the Sync if the revision
// and parameter overrides are different from our most recent sync operation.
if alreadyAttemptedSync(app, desiredCommitSHA) {
if app.Status.OperationState.Phase != appv1.OperationSucceeded {
logCtx.Warnf("Skipping auto-sync: failed previous sync attempt to %s", desiredCommitSHA)
message := fmt.Sprintf("Failed sync attempt to %s: %s", desiredCommitSHA, app.Status.OperationState.Message)
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: message}
}
logCtx.Infof("Skipping auto-sync: most recent sync already to %s", desiredCommitSHA)
return nil
}
op := appv1.Operation{
Sync: &appv1.SyncOperation{
Revision: desiredCommitSHA,
Prune: app.Spec.SyncPolicy.Automated.Prune,
ParameterOverrides: app.Spec.Source.ComponentParameterOverrides,
},
}
appIf := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
_, err := argo.SetAppOperation(context.Background(), appIf, ctrl.auditLogger, app.Name, &op)
if err != nil {
logCtx.Errorf("Failed to initiate auto-sync to %s: %v", desiredCommitSHA, err)
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: err.Error()}
}
message := fmt.Sprintf("Initiated automated sync to '%s'", desiredCommitSHA)
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonOperationStarted, Type: v1.EventTypeNormal}, message)
logCtx.Info(message)
return nil
}
// alreadyAttemptedSync returns whether or not the most recent sync was performed against the
// commitSHA and with the same parameter overrides which are currently set in the app
func alreadyAttemptedSync(app *appv1.Application, commitSHA string) bool {
if app.Status.OperationState == nil || app.Status.OperationState.Operation.Sync == nil || app.Status.OperationState.SyncResult == nil {
return false
}
if app.Status.OperationState.SyncResult.Revision != commitSHA {
return false
}
if !reflect.DeepEqual(appv1.ParameterOverrides(app.Spec.Source.ComponentParameterOverrides), app.Status.OperationState.Operation.Sync.ParameterOverrides) {
return false
}
return true
}
func (ctrl *ApplicationController) newApplicationInformer() cache.SharedIndexInformer {
appInformerFactory := appinformers.NewFilteredSharedInformerFactory(
ctrl.applicationClientset,
ctrl.statusRefreshTimeout,
ctrl.namespace,
func(options *metav1.ListOptions) {},
appClientset,
appResyncPeriod,
config.Namespace,
func(options *metav1.ListOptions) {
var instanceIDReq *labels.Requirement
var err error
if config.InstanceID != "" {
instanceIDReq, err = labels.NewRequirement(common.LabelKeyApplicationControllerInstanceID, selection.Equals, []string{config.InstanceID})
} else {
instanceIDReq, err = labels.NewRequirement(common.LabelKeyApplicationControllerInstanceID, selection.DoesNotExist, nil)
}
if err != nil {
panic(err)
}
options.FieldSelector = fields.Everything().String()
labelSelector := labels.NewSelector().Add(*instanceIDReq)
options.LabelSelector = labelSelector.String()
},
)
informer := appInformerFactory.Argoproj().V1alpha1().Applications().Informer()
informer.AddEventHandler(
@@ -795,32 +667,23 @@ func (ctrl *ApplicationController) newApplicationInformer() cache.SharedIndexInf
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
ctrl.appRefreshQueue.Add(key)
ctrl.appOperationQueue.Add(key)
appQueue.Add(key)
appOperationQueue.Add(key)
}
},
UpdateFunc: func(old, new interface{}) {
key, err := cache.MetaNamespaceKeyFunc(new)
if err != nil {
return
if err == nil {
appQueue.Add(key)
appOperationQueue.Add(key)
}
oldApp, oldOK := old.(*appv1.Application)
newApp, newOK := new.(*appv1.Application)
if oldOK && newOK {
if toggledAutomatedSync(oldApp, newApp) {
log.WithField("application", newApp.Name).Info("Enabled automated sync")
ctrl.forceAppRefresh(newApp.Name)
}
}
ctrl.appRefreshQueue.Add(key)
ctrl.appOperationQueue.Add(key)
},
DeleteFunc: func(obj interface{}) {
// IndexerInformer uses a delta queue, therefore for deletes we have to use this
// key function.
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err == nil {
ctrl.appRefreshQueue.Add(key)
appQueue.Add(key)
}
},
},
@@ -831,17 +694,3 @@ func (ctrl *ApplicationController) newApplicationInformer() cache.SharedIndexInf
func isOperationInProgress(app *appv1.Application) bool {
return app.Status.OperationState != nil && !app.Status.OperationState.Phase.Completed()
}
// toggledAutomatedSync tests if an app went from auto-sync disabled to enabled.
// if it was toggled to be enabled, the informer handler will force a refresh
func toggledAutomatedSync(old *appv1.Application, new *appv1.Application) bool {
if new.Spec.SyncPolicy == nil || new.Spec.SyncPolicy.Automated == nil {
return false
}
// auto-sync is enabled. check if it was previously disabled
if old.Spec.SyncPolicy == nil || old.Spec.SyncPolicy.Automated == nil {
return true
}
// nothing changed
return false
}

View File

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

View File

@@ -3,9 +3,16 @@ package controller
import (
"context"
"encoding/json"
"runtime/debug"
"time"
"runtime/debug"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/db"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -18,12 +25,6 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/git"
)
type SecretController struct {
@@ -92,13 +93,22 @@ func (ctrl *SecretController) getRepoConnectionState(repo *v1alpha1.Repository)
ModifiedAt: repo.ConnectionState.ModifiedAt,
Status: v1alpha1.ConnectionStatusUnknown,
}
err := git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey)
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
}

View File

@@ -7,7 +7,6 @@ import (
"time"
log "github.com/sirupsen/logrus"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
@@ -36,11 +35,10 @@ type AppStateManager interface {
SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
}
// appStateManager allows to compare application using KSonnet CLI
type appStateManager struct {
// ksonnetAppStateManager allows to compare application using KSonnet CLI
type ksonnetAppStateManager struct {
db db.ArgoDB
appclientset appclientset.Interface
kubectl kubeutil.Kubectl
repoClientset reposerver.Clientset
namespace string
}
@@ -85,7 +83,7 @@ func groupLiveObjects(liveObjs []*unstructured.Unstructured, targetObjs []*unstr
return liveByFullName
}
func (s *appStateManager) getTargetObjs(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, *repository.ManifestResponse, error) {
func (s *ksonnetAppStateManager) getTargetObjs(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, *repository.ManifestResponse, error) {
repo := s.getRepo(app.Spec.Source.RepoURL)
conn, repoClient, err := s.repoClientset.NewRepositoryClient()
if err != nil {
@@ -122,8 +120,6 @@ func (s *appStateManager) getTargetObjs(app *v1alpha1.Application, revision stri
Revision: revision,
ComponentParameterOverrides: mfReqOverrides,
AppLabel: app.Name,
ValueFiles: app.Spec.Source.ValuesFiles,
Namespace: app.Spec.Destination.Namespace,
})
if err != nil {
return nil, nil, err
@@ -143,7 +139,7 @@ func (s *appStateManager) getTargetObjs(app *v1alpha1.Application, revision stri
return targetObjs, manifestInfo, nil
}
func (s *appStateManager) getLiveObjs(app *v1alpha1.Application, targetObjs []*unstructured.Unstructured) (
func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetObjs []*unstructured.Unstructured) (
[]*unstructured.Unstructured, map[string]*unstructured.Unstructured, error) {
// Get the REST config for the cluster corresponding to the environment
@@ -191,27 +187,24 @@ func (s *appStateManager) getLiveObjs(app *v1alpha1.Application, targetObjs []*u
}
apiResource, err := kubeutil.ServerResourceForGroupVersionKind(disco, gvk)
if err != nil {
if !apierr.IsNotFound(err) {
return nil, nil, err
}
// If we get here, the app is comprised of a custom resource which has yet to be registered
} else {
liveObj, err = kubeutil.GetLiveResource(dclient, targetObj, apiResource, app.Spec.Destination.Namespace)
if err != nil {
return nil, nil, err
}
return nil, nil, err
}
liveObj, err = kubeutil.GetLiveResource(dclient, targetObj, apiResource, app.Spec.Destination.Namespace)
if err != nil {
return nil, nil, err
}
}
controlledLiveObj[i] = liveObj
delete(liveObjByFullName, fullName)
}
return controlledLiveObj, liveObjByFullName, nil
}
// CompareAppState compares application git state to the live app state, using the specified
// revision and supplied overrides. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
func (s *appStateManager) CompareAppState(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) (
func (s *ksonnetAppStateManager) CompareAppState(app *v1alpha1.Application, revision string, overrides []v1alpha1.ComponentParameter) (
*v1alpha1.ComparisonResult, *repository.ManifestResponse, []v1alpha1.ApplicationCondition, error) {
failedToLoadObjs := false
@@ -324,10 +317,6 @@ func (s *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
Resources: resources,
Status: comparisonStatus,
}
if manifestInfo != nil {
compResult.Revision = manifestInfo.Revision
}
return &compResult, manifestInfo, conditions, nil
}
@@ -370,7 +359,7 @@ func getResourceFullName(obj *unstructured.Unstructured) string {
return fmt.Sprintf("%s:%s", obj.GetKind(), obj.GetName())
}
func (s *appStateManager) getRepo(repoURL string) *v1alpha1.Repository {
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
@@ -379,7 +368,7 @@ func (s *appStateManager) getRepo(repoURL string) *v1alpha1.Repository {
return repo
}
func (s *appStateManager) persistDeploymentInfo(
func (s *ksonnetAppStateManager) persistDeploymentInfo(
app *v1alpha1.Application, revision string, envParams []*v1alpha1.ComponentParameter, overrides *[]v1alpha1.ComponentParameter) error {
params := make([]v1alpha1.ComponentParameter, len(envParams))
@@ -421,12 +410,10 @@ func NewAppStateManager(
appclientset appclientset.Interface,
repoClientset reposerver.Clientset,
namespace string,
kubectl kubeutil.Kubectl,
) AppStateManager {
return &appStateManager{
return &ksonnetAppStateManager{
db: db,
appclientset: appclientset,
kubectl: kubectl,
repoClientset: repoClientset,
namespace: namespace,
}

View File

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

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"sync"
@@ -30,12 +29,10 @@ import (
type syncContext struct {
appName string
proj *appv1.AppProject
comparison *appv1.ComparisonResult
config *rest.Config
dynClientPool dynamic.ClientPool
disco discovery.DiscoveryInterface
kubectl kube.Kubectl
disco *discovery.DiscoveryClient
namespace string
syncOp *appv1.SyncOperation
syncRes *appv1.SyncOperationResult
@@ -46,8 +43,8 @@ type syncContext struct {
lock sync.Mutex
}
func (s *appStateManager) SyncAppState(app *appv1.Application, state *appv1.OperationState) {
// Sync requests might be requested with ambiguous revisions (e.g. master, HEAD, v1.2.3).
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
@@ -59,7 +56,6 @@ func (s *appStateManager) SyncAppState(app *appv1.Application, state *appv1.Oper
if state.Operation.Sync != nil {
syncOp = *state.Operation.Sync
overrides = []appv1.ComponentParameter(state.Operation.Sync.ParameterOverrides)
if state.SyncResult != nil {
syncRes = state.SyncResult
revision = state.SyncResult.Revision
@@ -144,21 +140,12 @@ func (s *appStateManager) SyncAppState(app *appv1.Application, state *appv1.Oper
return
}
proj, err := argo.GetAppProject(&app.Spec, s.appclientset, s.namespace)
if err != nil {
state.Phase = appv1.OperationError
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
return
}
syncCtx := syncContext{
appName: app.Name,
proj: proj,
comparison: comparison,
config: restConfig,
dynClientPool: dynClientPool,
disco: disco,
kubectl: s.kubectl,
namespace: app.Spec.Destination.Namespace,
syncOp: &syncOp,
syncRes: syncRes,
@@ -323,13 +310,12 @@ func (sc *syncContext) applyObject(targetObj *unstructured.Unstructured, dryRun
Kind: targetObj.GetKind(),
Namespace: sc.namespace,
}
message, err := sc.kubectl.ApplyResource(sc.config, targetObj, sc.namespace, dryRun, force)
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
@@ -347,7 +333,7 @@ func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dr
resDetails.Message = "pruned (dry run)"
resDetails.Status = appv1.ResourceDetailsSyncedAndPruned
} else {
err := sc.kubectl.DeleteResource(sc.config, liveObj, sc.namespace)
err := kube.DeleteResource(sc.config, liveObj, sc.namespace)
if err != nil {
resDetails.Message = err.Error()
resDetails.Status = appv1.ResourceDetailsSyncFailed
@@ -363,49 +349,26 @@ func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dr
return resDetails
}
func hasCRDOfGroupKind(tasks []syncTask, group, kind string) bool {
for _, task := range tasks {
if kube.IsCRD(task.targetObj) {
crdGroup, ok, err := unstructured.NestedString(task.targetObj.Object, "spec", "group")
if err != nil || !ok {
continue
}
crdKind, ok, err := unstructured.NestedString(task.targetObj.Object, "spec", "names", "kind")
if err != nil || !ok {
continue
}
if group == crdGroup && crdKind == kind {
return true
}
}
}
return false
}
// performs a apply based sync of the given sync tasks (possibly pruning the objects).
// If update is true, will updates the resource details with the result.
// Or if the prune/apply failed, will also update the result.
func (sc *syncContext) doApplySync(syncTasks []syncTask, dryRun, force, update bool) bool {
syncSuccessful := true
var createTasks []syncTask
var pruneTasks []syncTask
for _, syncTask := range syncTasks {
if syncTask.targetObj == nil {
pruneTasks = append(pruneTasks, syncTask)
} else {
createTasks = append(createTasks, syncTask)
}
}
sort.Sort(newKindSorter(createTasks, resourceOrder))
// apply all resources in parallel
var wg sync.WaitGroup
for _, task := range pruneTasks {
for _, task := range syncTasks {
wg.Add(1)
go func(t syncTask) {
defer wg.Done()
var resDetails appv1.ResourceDetails
resDetails = sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
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
}
@@ -415,76 +378,6 @@ func (sc *syncContext) doApplySync(syncTasks []syncTask, dryRun, force, update b
}(task)
}
wg.Wait()
processCreateTasks := func(tasks []syncTask, gvk schema.GroupVersionKind) {
serverRes, err := kube.ServerResourceForGroupVersionKind(sc.disco, gvk)
if err != nil {
// Special case for custom resources: if custom resource definition is not supported by the cluster by defined in application then
// skip verification using `kubectl apply --dry-run` and since CRD should be created during app synchronization.
if dryRun && apierr.IsNotFound(err) && hasCRDOfGroupKind(createTasks, gvk.Group, gvk.Kind) {
return
} else {
syncSuccessful = false
for _, task := range tasks {
sc.setResourceDetails(&appv1.ResourceDetails{
Name: task.targetObj.GetName(),
Kind: task.targetObj.GetKind(),
Namespace: sc.namespace,
Message: err.Error(),
Status: appv1.ResourceDetailsSyncFailed,
})
}
return
}
}
if !sc.proj.IsResourcePermitted(metav1.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, serverRes.Namespaced) {
syncSuccessful = false
for _, task := range tasks {
sc.setResourceDetails(&appv1.ResourceDetails{
Name: task.targetObj.GetName(),
Kind: task.targetObj.GetKind(),
Namespace: sc.namespace,
Message: fmt.Sprintf("Resource %s:%s is not permitted in project %s.", gvk.Group, gvk.Kind, sc.proj.Name),
Status: appv1.ResourceDetailsSyncFailed,
})
}
return
}
var createWg sync.WaitGroup
for i := range tasks {
createWg.Add(1)
go func(t syncTask) {
defer createWg.Done()
if isHook(t.targetObj) {
return
}
resDetails := sc.applyObject(t.targetObj, dryRun, force)
if !resDetails.Status.Successful() {
syncSuccessful = false
}
if update || !resDetails.Status.Successful() {
sc.setResourceDetails(&resDetails)
}
}(tasks[i])
}
createWg.Wait()
}
var tasksGroup []syncTask
for _, task := range createTasks {
//Only wait if the type of the next task is different than the previous type
if len(tasksGroup) > 0 && tasksGroup[0].targetObj.GetKind() != task.targetObj.GetKind() {
processCreateTasks(tasksGroup, tasksGroup[0].targetObj.GroupVersionKind())
tasksGroup = []syncTask{task}
} else {
tasksGroup = append(tasksGroup, task)
}
}
if len(tasksGroup) > 0 {
processCreateTasks(tasksGroup, tasksGroup[0].targetObj.GroupVersionKind())
}
return syncSuccessful
}
@@ -519,7 +412,7 @@ func (sc *syncContext) doHookSync(syncTasks []syncTask, hooks []*unstructured.Un
// already started the post-sync phase, then we do not need to perform the health check.
postSyncHooks, _ := sc.getHooks(appv1.HookTypePostSync)
if len(postSyncHooks) > 0 && !sc.startedPostSyncPhase() {
healthState, err := setApplicationHealth(sc.kubectl, sc.comparison)
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))
@@ -538,7 +431,7 @@ func (sc *syncContext) doHookSync(syncTasks []syncTask, hooks []*unstructured.Un
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced")
}
// getHooks returns all ArgoCD hooks, optionally filtered by ones of the specific type(s)
// getHooks returns all hooks, or ones of the specific type(s)
func (sc *syncContext) getHooks(hookTypes ...appv1.HookType) ([]*unstructured.Unstructured, error) {
var hooks []*unstructured.Unstructured
for _, manifest := range sc.manifestInfo.Manifests {
@@ -547,9 +440,7 @@ func (sc *syncContext) getHooks(hookTypes ...appv1.HookType) ([]*unstructured.Un
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
if !isHook(&hook) {
continue
}
if len(hookTypes) > 0 {
@@ -662,7 +553,7 @@ func (sc *syncContext) runHook(hook *unstructured.Unstructured, hookType appv1.H
if err != nil {
sc.log.Warnf("Failed to set application label on hook %v: %v", hook, err)
}
_, err := sc.kubectl.ApplyResource(sc.config, hook, sc.namespace, false, false)
_, 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)
}
@@ -723,24 +614,9 @@ func isHookType(hook *unstructured.Unstructured, hookType appv1.HookType) bool {
return false
}
// isHook indicates if the object is either a ArgoCD or Helm hook
// isHook tells whether or not the supplied object is a application lifecycle hook, or a normal,
// synced application resource
func isHook(obj *unstructured.Unstructured) bool {
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.AnnotationHelmHook]
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
@@ -963,89 +839,3 @@ func (sc *syncContext) deleteHook(name, kind, apiVersion string) error {
resIf := dclient.Resource(apiResource, sc.namespace)
return resIf.Delete(name, &metav1.DeleteOptions{})
}
// This code is mostly taken from https://github.com/helm/helm/blob/release-2.10/pkg/tiller/kind_sorter.go
// sortOrder is an ordering of Kinds.
type sortOrder []string
// resourceOrder represents the correct order of Kubernetes resources within a manifest
var resourceOrder sortOrder = []string{
"Namespace",
"ResourceQuota",
"LimitRange",
"PodSecurityPolicy",
"Secret",
"ConfigMap",
"StorageClass",
"PersistentVolume",
"PersistentVolumeClaim",
"ServiceAccount",
"CustomResourceDefinition",
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"StatefulSet",
"Job",
"CronJob",
"Ingress",
"APIService",
}
type kindSorter struct {
ordering map[string]int
manifests []syncTask
}
func newKindSorter(m []syncTask, s sortOrder) *kindSorter {
o := make(map[string]int, len(s))
for v, k := range s {
o[k] = v
}
return &kindSorter{
manifests: m,
ordering: o,
}
}
func (k *kindSorter) Len() int { return len(k.manifests) }
func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] }
func (k *kindSorter) Less(i, j int) bool {
a := k.manifests[i].targetObj
if a == nil {
return false
}
b := k.manifests[j].targetObj
if b == nil {
return true
}
first, aok := k.ordering[a.GetKind()]
second, bok := k.ordering[b.GetKind()]
// if same kind (including unknown) sub sort alphanumeric
if first == second {
// if both are unknown and of different kind sort by kind alphabetically
if !aok && !bok && a.GetKind() != b.GetKind() {
return a.GetKind() < b.GetKind()
}
return a.GetName() < b.GetName()
}
// unknown kind is last
if !aok {
return false
}
if !bok {
return true
}
// sort different kinds
return first < second
}

View File

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

View File

@@ -7,8 +7,6 @@
* [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)

View File

@@ -1,103 +0,0 @@
# Application Source Types
ArgoCD supports several different ways in which kubernetes manifests can be defined:
* [ksonnet](https://ksonnet.io) applications
* [kustomize](https://kustomize.io) applications
* [helm](https://helm.sh) charts
* Simple directory of YAML/json manifests
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
```

View File

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

View File

@@ -1,37 +1,30 @@
# ArgoCD Getting Started
An example guestbook application is provided to demonstrate how ArgoCD works.
An example Ksonnet guestbook application is provided to demonstrates how ArgoCD works.
## Requirements
* Installed [minikube](https://github.com/kubernetes/minikube#installation)
* Installed [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line tool
* Have a [kubeconfig](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) file (default location is `~/.kube/config`).
## 1. Install ArgoCD
```bash
```
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v0.8.2/manifests/install.yaml
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/install.yaml
```
This will create a new namespace, `argocd`, where ArgoCD services and application resources will live.
NOTE:
* On GKE with RBAC enabled, you may need to grant your account the ability to create new cluster roles
```bash
kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
```
$ 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:
```bash
brew install argoproj/tap/argocd
```
On Linux:
```bash
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.8.2/argocd-linux-amd64
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.6.0/argocd-darwin-amd64
chmod +x /usr/local/bin/argocd
```
@@ -40,42 +33,33 @@ chmod +x /usr/local/bin/argocd
By default, the ArgoCD API server is not exposed with an external IP. To expose the API server,
change the service type to `LoadBalancer`:
```bash
```
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
```
### Notes about Ingress and AWS Load Balancers
* If using Ingress objects without TLS from the ingress-controller to ArgoCD API server, you will
need to add the `--insecure` command line flag to the argocd-server deployment.
* AWS Classic ELB (in HTTP mode) and ALB do not have full support for HTTP2/gRPC which is the
protocol used by the `argocd` CLI. When using an AWS load balancer, either Classic ELB in
passthrough mode is needed, or NLBs.
## 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:
```bash
```
kubectl get pods -n argocd -l app=argocd-server -o name | cut -d'/' -f 2
```
Using the above password, login to ArgoCD's external IP:
On Minikube:
```bash
```
argocd login $(minikube service argocd-server -n argocd --url | cut -d'/' -f 3) --name minikube
```
Other clusters:
```bash
kubectl get svc -n argocd argocd-server
```
kubectl get svc argocd-server
argocd login <EXTERNAL-IP>
```
After logging in, change the password using the command:
```bash
```
argocd account update-password
argocd relogin
```
@@ -83,13 +67,13 @@ argocd relogin
We will now register a cluster to deploy applications to. First list all clusters contexts in your
kubconfig:
```bash
```
argocd cluster add
```
Choose a context name from the list and supply it to `argocd cluster add CONTEXTNAME`. For example,
for minikube context, run:
```bash
```
argocd cluster add minikube --in-cluster
```
@@ -109,7 +93,7 @@ flag should be omitted.
Open a browser to the ArgoCD external UI, and login using the credentials set in step 4.
On Minikube:
```bash
```
minikube service argocd-server -n argocd
```
@@ -130,8 +114,8 @@ After connecting a git repository, select the guestbook application for creation
Applications can be also be created using the ArgoCD CLI:
```bash
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
```
argocd app create --name guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
```
## 7. Sync (deploy) the application
@@ -142,7 +126,7 @@ From UI:
![create app](assets/guestbook-app.png)
From CLI:
```bash
```
$ argocd app get guestbook-default
Name: guestbook-default
Server: https://kubernetes.default.svc
@@ -161,7 +145,7 @@ Deployment guestbook-ui OutOfSync
The application status is initially in an `OutOfSync` state, since the application has yet to be
deployed, and no Kubernetes resources have been created. To sync (deploy) the application, run:
```bash
```
$ argocd app sync guestbook-default
Application: guestbook-default
Operation: Sync
@@ -173,13 +157,17 @@ Service guestbook-ui service "guestbook-ui" created
Deployment guestbook-ui deployment.apps "guestbook-ui" created
```
This command retrieves the manifests from git repository and performs a `kubectl apply` of the
manifests. The guestbook app is now running and you can now view its resource
This command retrieves the manifests from the ksonnet app in the git repository and performs a
`kubectl apply` of the manifests. The guestbook app is now running and you can now view its resource
components, logs, events, and assessed health:
![view app](assets/guestbook-tree.png)
## 8. Next Steps
ArgoCD supports additional features such as SSO, WebHooks, RBAC, Projects. See the rest of
the [documentation](./) for details.
ArgoCD supports additional features such as SSO, WebHooks, RBAC. See the following guides on setting
these up:
* [Configuring SSO](sso.md)
* [Configuring RBAC](rbac.md)
* [Configuring WebHooks](webhook.md)

View File

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

View File

@@ -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
```

View File

@@ -2,17 +2,13 @@
## Overview
The RBAC feature enables restriction of access to ArgoCD resources. ArgoCD does not have its own
user management system and has only one built-in user `admin`. The `admin` user is a superuser and
it has unrestricted access to the system. RBAC requires [SSO configuration](./sso.md). Once SSO is
configured, additional RBAC roles can be defined, and SSO groups can man be mapped to roles.
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:readonly` - read-only access to all resources
* `role:admin` - unrestricted access to all resources
These role definitions can be seen in [builtin-policy.csv](../util/rbac/builtin-policy.csv)
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.
@@ -24,16 +20,16 @@ apiVersion: v1
data:
policy.default: role:readonly
policy.csv: |
p, role:org-admin, applications, *, */*, allow
p, role:org-admin, applications/*, *, */*, allow
p, role:org-admin, applications, *, */*
p, role:org-admin, applications/*, *, */*
p, role:org-admin, clusters, get, *, allow
p, role:org-admin, repositories, get, *, allow
p, role:org-admin, repositories/apps, get, *, allow
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, *, allow
p, role:org-admin, repositories, update, *, allow
p, role:org-admin, repositories, delete, *, allow
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
@@ -48,19 +44,15 @@ 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.
Following command creates project `myproject` which can deploy applications to namespace `default` of cluster `https://kubernetes.default.svc`. The source ksonnet application
should be defined in `https://github.com/argoproj/argocd-example-apps.git` repository.
```
argocd proj create myproject -d https://kubernetes.default.svc,default -s https://github.com/argoproj/argocd-example-apps.git
```
Project sources and destinations can be managed using commands
```
argocd project add-destination
argocd project remove-destination
argocd project add-source
argocd project remove-source
```
Project sources and destinations can be managed using commands `argocd project add-destination`, `argocd project remove-destination`, `argocd project add-source`
and `argocd project remove-source`.
### 2. Assign application to a project
@@ -83,19 +75,19 @@ apiVersion: v1
data:
policy.default: ""
policy.csv: |
p, role:team1-admin, applications, *, default/*, allow
p, role:team1-admin, applications/*, *, default/*, allow
p, role:team1-admin, applications, *, default/*
p, role:team1-admin, applications/*, *, default/*
p, role:team1-admin, applications, *, myproject/*, allow
p, role:team1-admin, applications/*, *, myproject/*, allow
p, role:team1-admin, applications, *, myproject/*
p, role:team1-admin, applications/*, *, myproject/*
p, role:org-admin, clusters, get, *, allow
p, role:org-admin, repositories, get, *, allow
p, role:org-admin, repositories/apps, get, *, allow
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, *, allow
p, role:org-admin, repositories, update, *, allow
p, role:org-admin, repositories, delete, *, allow
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
@@ -105,58 +97,3 @@ kind: ConfigMap
metadata:
name: argocd-rbac-cm
```
## Project Roles
Projects include a feature called roles that allow users to define access to project's applications. A project can have multiple roles, and those roles can have different access granted to them. These permissions are called policies, and they are stored within the role as a list of casbin strings. A role's policy can only grant access to that role and are limited to applications within the role's project. However, the policies have an option for granting wildcard access to any application within a project.
In order to create roles in a project and add policies to a role, a user will need permission to update a project. The following commands can be used to manage a role.
```
argoproj proj role list
argoproj proj role get
argoproj proj role create
argoproj proj role delete
argoproj proj role add-policy
argoproj proj role remove-policy
```
Project roles can not be used unless a user creates a entity that is associated with that project role. ArgoCD supports creating JWT tokens with a role associated with it. Since the JWT token is associated with a role's policies, any changes to the role's policies will immediately take effect for that JWT token.
A user will need permission to update a project in order to create a JWT token for a role, and they can use the following commands to manage the JWT tokens.
```
argoproj proj role create-token
argoproj proj role delete-token
```
Since the JWT tokens aren't stored in ArgoCD, they can only be retrieved when they are created. A user can leverage them in the cli by either passing them in using the `--auth-token` flag or setting the ARGOCD_AUTH_TOKEN environment variable. The JWT tokens can be used until they expire or are revoked. The JWT tokens can created with or without an expiration, but the default on the cli is creates them without an expirations date. Even if a token has not expired, it can not be used if the token has been revoke.
Below is an example of leveraging a JWT token to access the guestbook application. It makes the assumption that the user already has a project named myproject and an application called guestbook-default.
```
PROJ=myproject
APP=guestbook-default
ROLE=get-role
argocd proj role create $PROJ $ROLE
argocd proj role create-token $PROJ $ROLE -e 10m
JWT=<value from command above>
argocd proj role list $PROJ
argocd proj role get $PROJ $ROLE
#This command will fail because the JWT Token associated with the project role does not have a policy to allow access to the application
argocd app get $APP --auth-token $JWT
# Adding a policy to grant access to the application for the new role
argocd proj role add-policy $PROJ $ROLE --action get --permission allow --object $APP
argocd app get $PROJ-$ROLE --auth-token $JWT
# Removing the policy we added and adding one with a wildcard.
argocd proj role remove-policy $PROJ $TOKEN -a get -o $PROJ-$TOKEN
argocd proj role remove-policy $PROJ $TOKEN -a get -o '*'
# The wildcard allows us to access the application due to the wildcard.
argocd app get $PROJ-$TOKEN --auth-token $JWT
argocd proj role get $PROJ
argocd proj role get $PROJ $ROLE
# Revoking the JWT token
argocd proj role delete-token $PROJ $ROLE <id field from the last command>
# This will fail since the JWT Token was deleted for the project role.
argocd app get $APP --auth-token $JWT
```

View File

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

View File

@@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-server-clusterrole
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- delete

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
@@ -26,11 +27,3 @@ rules:
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list

View File

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

View File

@@ -1,3 +1,4 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:

View File

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

View File

@@ -1,3 +1,4 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
@@ -29,10 +30,3 @@ rules:
- update
- delete
- patch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list

View File

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

View File

@@ -1,3 +1,4 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -13,6 +14,12 @@ spec:
spec:
serviceAccountName: argocd-server
initContainers:
- name: copyutil
image: argoproj/argocd-server:latest
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
- name: ui
image: argoproj/argocd-ui:latest
command: [cp, -r, /app, /shared]
@@ -26,12 +33,12 @@ spec:
volumeMounts:
- mountPath: /shared
name: static-files
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
- 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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
# This is an auto-generated file. DO NOT EDIT
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
@@ -29,20 +30,74 @@ spec:
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: v1
kind: ServiceAccount
metadata:
name: argocd-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dex-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
@@ -71,95 +126,6 @@ rules:
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argocd-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get
- list
- watch
- update
- delete
- patch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dex-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: application-controller-clusterrole
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-server-clusterrole
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
@@ -173,130 +139,6 @@ subjects:
- kind: ServiceAccount
name: application-controller
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argocd-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: argocd-server-role
subjects:
- kind: ServiceAccount
name: argocd-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dex-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dex-server-role
subjects:
- kind: ServiceAccount
name: dex-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: application-controller-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: application-controller-clusterrole
subjects:
- kind: ServiceAccount
name: application-controller
namespace: argocd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-server-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-server-clusterrole
subjects:
- kind: ServiceAccount
name: argocd-server
namespace: argocd
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
---
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
name: argocd-metrics
spec:
ports:
- name: http
port: 8082
protocol: TCP
targetPort: 8082
selector:
app: argocd-server
---
apiVersion: v1
kind: Service
metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
targetPort: 8081
selector:
app: argocd-repo-server
---
apiVersion: v1
kind: Service
metadata:
name: argocd-server
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
- name: https
port: 443
protocol: TCP
targetPort: 8080
selector:
app: argocd-server
---
apiVersion: v1
kind: Service
metadata:
name: dex-server
spec:
ports:
- name: http
port: 5556
protocol: TCP
targetPort: 5556
- name: grpc
port: 5557
protocol: TCP
targetPort: 5557
selector:
app: dex-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -311,40 +153,59 @@ spec:
app: application-controller
spec:
containers:
- command:
- /argocd-application-controller
- --repo-server
- argocd-repo-server:8081
image: argoproj/argocd-application-controller:v0.9.2
- command: [/argocd-application-controller, --repo-server, 'argocd-repo-server:8081']
image: argoproj/argocd-application-controller:v0.6.2
name: application-controller
serviceAccountName: application-controller
---
apiVersion: apps/v1
kind: Deployment
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-repo-server
spec:
selector:
matchLabels:
app: argocd-repo-server
template:
metadata:
labels:
app: argocd-repo-server
spec:
automountServiceAccountToken: false
containers:
- command:
- /argocd-repo-server
image: argoproj/argocd-repo-server:v0.9.2
name: argocd-repo-server
ports:
- containerPort: 8081
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
name: argocd-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argocd-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get
- list
- watch
- update
- delete
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argocd-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: argocd-server-role
subjects:
- kind: ServiceAccount
name: argocd-server
---
apiVersion: apps/v1
kind: Deployment
@@ -359,76 +220,81 @@ spec:
labels:
app: argocd-server
spec:
containers:
- command:
- /argocd-server
- --staticassets
- /shared/app
- --repo-server
- argocd-repo-server:8081
image: argoproj/argocd-server:v0.9.2
name: argocd-server
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
volumeMounts:
- mountPath: /shared
name: static-files
initContainers:
- command:
- cp
- -r
- /app
- /shared
image: argoproj/argocd-ui:v0.9.2
name: ui
volumeMounts:
- mountPath: /shared
name: static-files
serviceAccountName: argocd-server
initContainers:
- name: copyutil
image: argoproj/argocd-server:v0.6.2
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
- name: ui
image: argoproj/argocd-ui:v0.6.2
command: [cp, -r, /app, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
containers:
- name: argocd-server
image: argoproj/argocd-server:v0.6.2
command: [/argocd-server, --staticassets, /shared/app, --repo-server, 'argocd-repo-server:8081']
volumeMounts:
- mountPath: /shared
name: static-files
- name: dex
image: quay.io/coreos/dex:v2.10.0
command: [/shared/argocd-util, rundex]
volumeMounts:
- mountPath: /shared
name: static-files
volumes:
- emptyDir: {}
name: static-files
---
apiVersion: v1
kind: Service
metadata:
name: argocd-server
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
- name: https
protocol: TCP
port: 443
targetPort: 8080
selector:
app: argocd-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dex-server
name: argocd-repo-server
spec:
selector:
matchLabels:
app: dex-server
app: argocd-repo-server
template:
metadata:
labels:
app: dex-server
app: argocd-repo-server
spec:
containers:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.11.0
name: dex
- name: argocd-repo-server
image: argoproj/argocd-repo-server:v0.6.2
command: [/argocd-repo-server]
ports:
- containerPort: 5556
- containerPort: 5557
volumeMounts:
- mountPath: /shared
name: static-files
initContainers:
- command:
- cp
- /argocd-util
- /shared
image: argoproj/argocd-server:v0.9.2
name: copyutil
volumeMounts:
- mountPath: /shared
name: static-files
serviceAccountName: dex-server
volumes:
- emptyDir: {}
name: static-files
- containerPort: 8081
---
apiVersion: v1
kind: Service
metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
targetPort: 8081
selector:
app: argocd-repo-server

View File

@@ -1,380 +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: ServiceAccount
metadata:
name: application-controller
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dex-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: application-controller-role
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- watch
- list
- patch
- update
- apiGroups:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argocd-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get
- list
- watch
- update
- delete
- patch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dex-server-role
rules:
- apiGroups:
- ""
resources:
- secrets
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: application-controller-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: application-controller-role
subjects:
- kind: ServiceAccount
name: application-controller
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argocd-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: argocd-server-role
subjects:
- kind: ServiceAccount
name: argocd-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dex-server-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dex-server-role
subjects:
- kind: ServiceAccount
name: dex-server
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
---
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
name: argocd-metrics
spec:
ports:
- name: http
port: 8082
protocol: TCP
targetPort: 8082
selector:
app: argocd-server
---
apiVersion: v1
kind: Service
metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
targetPort: 8081
selector:
app: argocd-repo-server
---
apiVersion: v1
kind: Service
metadata:
name: argocd-server
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
- name: https
port: 443
protocol: TCP
targetPort: 8080
selector:
app: argocd-server
---
apiVersion: v1
kind: Service
metadata:
name: dex-server
spec:
ports:
- name: http
port: 5556
protocol: TCP
targetPort: 5556
- name: grpc
port: 5557
protocol: TCP
targetPort: 5557
selector:
app: dex-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-controller
spec:
selector:
matchLabels:
app: application-controller
template:
metadata:
labels:
app: application-controller
spec:
containers:
- command:
- /argocd-application-controller
- --repo-server
- argocd-repo-server:8081
image: argoproj/argocd-application-controller:v0.9.2
name: application-controller
serviceAccountName: application-controller
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-repo-server
spec:
selector:
matchLabels:
app: argocd-repo-server
template:
metadata:
labels:
app: argocd-repo-server
spec:
automountServiceAccountToken: false
containers:
- command:
- /argocd-repo-server
image: argoproj/argocd-repo-server:v0.9.2
name: argocd-repo-server
ports:
- containerPort: 8081
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-server
spec:
selector:
matchLabels:
app: argocd-server
template:
metadata:
labels:
app: argocd-server
spec:
containers:
- command:
- /argocd-server
- --staticassets
- /shared/app
- --repo-server
- argocd-repo-server:8081
image: argoproj/argocd-server:v0.9.2
name: argocd-server
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
volumeMounts:
- mountPath: /shared
name: static-files
initContainers:
- command:
- cp
- -r
- /app
- /shared
image: argoproj/argocd-ui:v0.9.2
name: ui
volumeMounts:
- mountPath: /shared
name: static-files
serviceAccountName: argocd-server
volumes:
- emptyDir: {}
name: static-files
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dex-server
spec:
selector:
matchLabels:
app: dex-server
template:
metadata:
labels:
app: dex-server
spec:
containers:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.11.0
name: dex
ports:
- containerPort: 5556
- containerPort: 5557
volumeMounts:
- mountPath: /shared
name: static-files
initContainers:
- command:
- cp
- /argocd-util
- /shared
image: argoproj/argocd-server:v0.9.2
name: copyutil
volumeMounts:
- mountPath: /shared
name: static-files
serviceAccountName: dex-server
volumes:
- emptyDir: {}
name: static-files

View File

@@ -8,20 +8,9 @@ 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"
@@ -32,6 +21,9 @@ import (
"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 (
@@ -76,12 +68,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 +82,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 +98,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 +137,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 +162,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 +188,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,

File diff suppressed because it is too large Load Diff

View File

@@ -13,15 +13,6 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1alpha1";
// AWSAuthConfig is an AWS IAM authentication configuration
message AWSAuthConfig {
// ClusterName contains AWS cluster name
optional string clusterName = 1;
// RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.
optional string roleARN = 2;
}
// AppProject is a definition of AppProject resource.
// +genclient
// +genclient:noStatus
@@ -43,21 +34,13 @@ message AppProjectList {
// AppProjectSpec represents
message AppProjectSpec {
// SourceRepos contains list of git repository URLs which can be used for deployment
repeated string sourceRepos = 1;
repeated string sources = 1;
// Destinations contains list of destinations available for deployment
repeated ApplicationDestination destinations = 2;
// Description contains optional project description
optional string description = 3;
repeated ProjectRole roles = 4;
// ClusterResourceWhitelist contains list of whitelisted cluster level resources
repeated k8s.io.apimachinery.pkg.apis.meta.v1.GroupKind clusterResourceWhitelist = 5;
// NamespaceResourceBlacklist contains list of blacklisted namespace level resources
repeated k8s.io.apimachinery.pkg.apis.meta.v1.GroupKind namespaceResourceBlacklist = 6;
}
// Application is a definition of Application resource.
@@ -102,24 +85,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.
@@ -132,9 +112,6 @@ message ApplicationSpec {
// Project is a application project name. Empty name means that application belongs to 'default' project.
optional string project = 3;
// SyncPolicy controls when a sync will be performed
optional SyncPolicy syncPolicy = 4;
}
// ApplicationStatus contains information about application status in target environment.
@@ -194,9 +171,6 @@ message ClusterConfig {
// TLSClientConfig contains settings to enable transport layer security
optional TLSClientConfig tlsClientConfig = 4;
// AWSAuthConfig contains IAM authentication configuration
optional AWSAuthConfig awsAuthConfig = 5;
}
// ClusterList is a collection of Clusters.
@@ -215,8 +189,6 @@ message ComparisonResult {
optional string status = 5;
repeated ResourceState resources = 6;
optional string revision = 7;
}
// ComponentParameter contains information about component parameter value
@@ -277,13 +249,6 @@ message HookStatus {
optional string message = 6;
}
// JWTToken holds the issuedAt and expiresAt values of a token
message JWTToken {
optional int64 iat = 1;
optional int64 exp = 2;
}
// Operation contains requested operation parameters.
message Operation {
optional SyncOperation sync = 1;
@@ -315,27 +280,6 @@ message OperationState {
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time finishedAt = 7;
}
// ParameterOverrides masks the value so protobuf can generate
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
message ParameterOverrides {
// items, if empty, will result in an empty slice
repeated ComponentParameter items = 1;
}
// ProjectRole represents a role that has access to a project
message ProjectRole {
optional string name = 1;
optional string description = 2;
// Policies Stores a list of casbin formated strings that define access policies for the role in the project.
repeated string policies = 3;
repeated JWTToken jwtTokens = 4;
}
// Repository is a Git repository holding application configurations
message Repository {
optional string repo = 1;
@@ -398,8 +342,7 @@ message RollbackOperation {
// SyncOperation contains sync operation details.
message SyncOperation {
// Revision is the git revision in which to sync the application to.
// If omitted, will use the revision specified in app spec.
// 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
@@ -410,11 +353,6 @@ message SyncOperation {
// SyncStrategy describes how to perform the sync
optional SyncStrategy syncStrategy = 4;
// ParameterOverrides applies any parameter overrides as part of the sync
// If nil, uses the parameter override set in application.
// If empty, sets no parameter overrides
optional ParameterOverrides parameterOverrides = 5;
}
// SyncOperationResult represent result of sync operation
@@ -429,24 +367,12 @@ message SyncOperationResult {
repeated HookStatus hooks = 3;
}
// SyncPolicy controls when a sync will be performed in response to updates in git
message SyncPolicy {
// Automated will keep an application synced to the target revision
optional SyncPolicyAutomated automated = 1;
}
// SyncPolicyAutomated controls the behavior of an automated sync
message SyncPolicyAutomated {
// Prune will prune resources automatically as part of automated sync (default: false)
optional bool prune = 1;
}
// SyncStrategy controls the manner in which a sync is performed
// SyncStrategy indicates the
message SyncStrategy {
// Apply wil perform a `kubectl apply` to perform the sync.
// 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. This is the default strategy
// Hook will submit any referenced resources to perform the sync
optional SyncStrategyHook hook = 2;
}

View File

@@ -2,16 +2,12 @@ package v1alpha1
import (
"encoding/json"
fmt "fmt"
"path/filepath"
"reflect"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd/api"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/util/git"
@@ -19,8 +15,7 @@ import (
// SyncOperation contains sync operation details.
type SyncOperation struct {
// Revision is the git revision in which to sync the application to.
// If omitted, will use the revision specified in app spec.
// 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"`
@@ -28,19 +23,6 @@ type SyncOperation struct {
DryRun bool `json:"dryRun,omitempty" protobuf:"bytes,3,opt,name=dryRun"`
// SyncStrategy describes how to perform the sync
SyncStrategy *SyncStrategy `json:"syncStrategy,omitempty" protobuf:"bytes,4,opt,name=syncStrategy"`
// ParameterOverrides applies any parameter overrides as part of the sync
// If nil, uses the parameter override set in application.
// If empty, sets no parameter overrides
ParameterOverrides ParameterOverrides `json:"parameterOverrides" protobuf:"bytes,5,opt,name=parameterOverrides"`
}
// ParameterOverrides masks the value so protobuf can generate
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type ParameterOverrides []ComponentParameter
func (po ParameterOverrides) String() string {
return fmt.Sprintf("%v", []ComponentParameter(po))
}
type RollbackOperation struct {
@@ -95,23 +77,11 @@ type OperationState struct {
FinishedAt *metav1.Time `json:"finishedAt" protobuf:"bytes,7,opt,name=finishedAt"`
}
// SyncPolicy controls when a sync will be performed in response to updates in git
type SyncPolicy struct {
// Automated will keep an application synced to the target revision
Automated *SyncPolicyAutomated `json:"automated,omitempty" protobuf:"bytes,1,opt,name=automated"`
}
// SyncPolicyAutomated controls the behavior of an automated sync
type SyncPolicyAutomated struct {
// Prune will prune resources automatically as part of automated sync (default: false)
Prune bool `json:"prune,omitempty" protobuf:"bytes,1,opt,name=prune"`
}
// SyncStrategy controls the manner in which a sync is performed
// SyncStrategy indicates the
type SyncStrategy struct {
// Apply wil perform a `kubectl apply` to perform the sync.
// 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. This is the default strategy
// Hook will submit any referenced resources to perform the sync
Hook *SyncStrategyHook `json:"hook,omitempty" protobuf:"bytes,2,opt,name=hook"`
}
@@ -247,32 +217,28 @@ type ApplicationSpec struct {
Destination ApplicationDestination `json:"destination" protobuf:"bytes,2,name=destination"`
// Project is a application project name. Empty name means that application belongs to 'default' project.
Project string `json:"project" protobuf:"bytes,3,name=project"`
// SyncPolicy controls when a sync will be performed
SyncPolicy *SyncPolicy `json:"syncPolicy,omitempty" protobuf:"bytes,4,name=syncPolicy"`
}
// ComponentParameter contains information about component parameter value
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
@@ -314,10 +280,8 @@ const (
ApplicationConditionDeletionError = "DeletionError"
// ApplicationConditionInvalidSpecError indicates that application source is invalid
ApplicationConditionInvalidSpecError = "InvalidSpecError"
// ApplicationConditionComparisonError indicates controller failed to compare application state
// ApplicationComparisonError indicates controller failed to compare application state
ApplicationConditionComparisonError = "ComparisonError"
// ApplicationConditionSyncError indicates controller failed to automatically sync the application
ApplicationConditionSyncError = "SyncError"
// ApplicationConditionUnknownError indicates an unknown controller error
ApplicationConditionUnknownError = "UnknownError"
// ApplicationConditionSharedResourceWarning indicates that controller detected resources which belongs to more than one application
@@ -338,7 +302,6 @@ type ComparisonResult struct {
ComparedTo ApplicationSource `json:"comparedTo" protobuf:"bytes,2,opt,name=comparedTo"`
Status ComparisonStatus `json:"status" protobuf:"bytes,5,opt,name=status,casttype=ComparisonStatus"`
Resources []ResourceState `json:"resources" protobuf:"bytes,6,opt,name=resources"`
Revision string `json:"revision" protobuf:"bytes,7,opt,name=revision"`
}
type HealthStatus struct {
@@ -408,15 +371,6 @@ type ClusterList struct {
Items []Cluster `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// AWSAuthConfig is an AWS IAM authentication configuration
type AWSAuthConfig struct {
// ClusterName contains AWS cluster name
ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,1,opt,name=clusterName"`
// RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.
RoleARN string `json:"roleARN,omitempty" protobuf:"bytes,2,opt,name=roleARN"`
}
// ClusterConfig is the configuration attributes. This structure is subset of the go-client
// rest.Config with annotations added for marshalling.
type ClusterConfig struct {
@@ -431,9 +385,6 @@ type ClusterConfig struct {
// TLSClientConfig contains settings to enable transport layer security
TLSClientConfig `json:"tlsClientConfig" protobuf:"bytes,4,opt,name=tlsClientConfig"`
// AWSAuthConfig contains IAM authentication configuration
AWSAuthConfig *AWSAuthConfig `json:"awsAuthConfig" protobuf:"bytes,5,opt,name=awsAuthConfig"`
}
// TLSClientConfig contains settings to enable transport layer security
@@ -489,48 +440,25 @@ type AppProject struct {
Spec AppProjectSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
}
// ProjectPoliciesString returns Casbin formated string of a project's polcies for each role
func (proj *AppProject) ProjectPoliciesString() string {
var policies []string
for _, role := range proj.Spec.Roles {
policies = append(policies, role.Policies...)
}
return strings.Join(policies, "\n")
}
// AppProjectSpec represents
type AppProjectSpec struct {
// SourceRepos contains list of git repository URLs which can be used for deployment
SourceRepos []string `json:"sourceRepos" protobuf:"bytes,1,name=sourceRepos"`
SourceRepos []string `json:"sources" protobuf:"bytes,1,name=destination"`
// Destinations contains list of destinations available for deployment
Destinations []ApplicationDestination `json:"destinations" protobuf:"bytes,2,name=destination"`
// Description contains optional project description
Description string `json:"description,omitempty" protobuf:"bytes,3,opt,name=description"`
Roles []ProjectRole `json:"roles,omitempty" protobuf:"bytes,4,rep,name=roles"`
// ClusterResourceWhitelist contains list of whitelisted cluster level resources
ClusterResourceWhitelist []metav1.GroupKind `json:"clusterResourceWhitelist,omitempty" protobuf:"bytes,5,opt,name=clusterResourceWhitelist"`
// NamespaceResourceBlacklist contains list of blacklisted namespace level resources
NamespaceResourceBlacklist []metav1.GroupKind `json:"namespaceResourceBlacklist,omitempty" protobuf:"bytes,6,opt,name=namespaceResourceBlacklist"`
}
// ProjectRole represents a role that has access to a project
type ProjectRole struct {
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
Description string `json:"description" protobuf:"bytes,2,opt,name=description"`
// Policies Stores a list of casbin formated strings that define access policies for the role in the project.
Policies []string `json:"policies" protobuf:"bytes,3,rep,name=policies"`
JWTTokens []JWTToken `json:"jwtTokens" protobuf:"bytes,4,rep,name=jwtTokens"`
}
// JWTToken holds the issuedAt and expiresAt values of a token
type JWTToken struct {
IssuedAt int64 `json:"iat,omitempty" protobuf:"int64,1,opt,name=iat"`
ExpiresAt int64 `json:"exp,omitempty" protobuf:"int64,2,opt,name=exp"`
func GetDefaultProject(namespace string) AppProject {
return AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: common.DefaultAppProjectName,
Namespace: namespace,
},
}
}
func (app *Application) getFinalizerIndex(name string) int {
@@ -579,7 +507,10 @@ func (condition *ApplicationCondition) IsError() bool {
// 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)
return source.TargetRevision == other.TargetRevision &&
source.RepoURL == other.RepoURL &&
source.Path == other.Path &&
source.Environment == other.Environment
}
func (spec ApplicationSpec) BelongsToDefaultProject() bool {
@@ -593,35 +524,16 @@ func (spec ApplicationSpec) GetProject() string {
return spec.Project
}
func isResourceInList(res metav1.GroupKind, list []metav1.GroupKind) bool {
for _, item := range list {
ok, err := filepath.Match(item.Kind, res.Kind)
if ok && err == nil {
ok, err = filepath.Match(item.Group, res.Group)
if ok && err == nil {
return true
}
}
}
return false
func (proj AppProject) IsDefault() bool {
return proj.Name == "" || proj.Name == common.DefaultAppProjectName
}
func (proj AppProject) IsResourcePermitted(res metav1.GroupKind, namespaced bool) bool {
if namespaced {
return !isResourceInList(res, proj.Spec.NamespaceResourceBlacklist)
} else {
return isResourceInList(res, proj.Spec.ClusterResourceWhitelist)
}
}
// IsSourcePermitted validates if the provided application's source is a one of the allowed sources for the project.
func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
if proj.IsDefault() {
return true
}
normalizedURL := git.NormalizeGitURL(src.RepoURL)
for _, repoURL := range proj.Spec.SourceRepos {
if repoURL == "*" {
return true
}
if git.NormalizeGitURL(repoURL) == normalizedURL {
return true
}
@@ -629,13 +541,13 @@ func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
return false
}
// IsDestinationPermitted validiates if the provided application's destination is one of the allowed destinations for the project
func (proj AppProject) IsDestinationPermitted(dst ApplicationDestination) bool {
if proj.IsDefault() {
return true
}
for _, item := range proj.Spec.Destinations {
if item.Server == dst.Server || item.Server == "*" {
if item.Namespace == dst.Namespace || item.Namespace == "*" {
return true
}
if item.Server == dst.Server && item.Namespace == dst.Namespace {
return true
}
}
return false
@@ -643,42 +555,18 @@ func (proj AppProject) IsDestinationPermitted(dst ApplicationDestination) bool {
// RESTConfig returns a go-client REST config from cluster
func (c *Cluster) RESTConfig() *rest.Config {
if c.Server == common.KubernetesInternalAPIServerAddr && c.Config.Username == "" && c.Config.Password == "" && c.Config.BearerToken == "" {
config, err := rest.InClusterConfig()
if err != nil {
panic("Unable to create in-cluster config")
}
return config
}
tlsClientConfig := rest.TLSClientConfig{
Insecure: c.Config.TLSClientConfig.Insecure,
ServerName: c.Config.TLSClientConfig.ServerName,
CertData: c.Config.TLSClientConfig.CertData,
KeyData: c.Config.TLSClientConfig.KeyData,
CAData: c.Config.TLSClientConfig.CAData,
}
if c.Config.AWSAuthConfig != nil {
args := []string{"token", "-i", c.Config.AWSAuthConfig.ClusterName}
if c.Config.AWSAuthConfig.RoleARN != "" {
args = append(args, "-r", c.Config.AWSAuthConfig.RoleARN)
}
return &rest.Config{
Host: c.Server,
TLSClientConfig: tlsClientConfig,
ExecProvider: &api.ExecConfig{
APIVersion: "client.authentication.k8s.io/v1alpha1",
Command: "aws-iam-authenticator",
Args: args,
},
}
}
return &rest.Config{
Host: c.Server,
Username: c.Config.Username,
Password: c.Config.Password,
BearerToken: c.Config.BearerToken,
TLSClientConfig: tlsClientConfig,
Host: c.Server,
Username: c.Config.Username,
Password: c.Config.Password,
BearerToken: c.Config.BearerToken,
TLSClientConfig: rest.TLSClientConfig{
Insecure: c.Config.TLSClientConfig.Insecure,
ServerName: c.Config.TLSClientConfig.ServerName,
CertData: c.Config.TLSClientConfig.CertData,
KeyData: c.Config.TLSClientConfig.KeyData,
CAData: c.Config.TLSClientConfig.CAData,
},
}
}

View File

@@ -9,22 +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 *AWSAuthConfig) DeepCopyInto(out *AWSAuthConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSAuthConfig.
func (in *AWSAuthConfig) DeepCopy() *AWSAuthConfig {
if in == nil {
return nil
}
out := new(AWSAuthConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppProject) DeepCopyInto(out *AppProject) {
*out = *in
@@ -98,23 +82,6 @@ func (in *AppProjectSpec) DeepCopyInto(out *AppProjectSpec) {
*out = make([]ApplicationDestination, len(*in))
copy(*out, *in)
}
if in.Roles != nil {
in, out := &in.Roles, &out.Roles
*out = make([]ProjectRole, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ClusterResourceWhitelist != nil {
in, out := &in.ClusterResourceWhitelist, &out.ClusterResourceWhitelist
*out = make([]v1.GroupKind, len(*in))
copy(*out, *in)
}
if in.NamespaceResourceBlacklist != nil {
in, out := &in.NamespaceResourceBlacklist, &out.NamespaceResourceBlacklist
*out = make([]v1.GroupKind, len(*in))
copy(*out, *in)
}
return
}
@@ -238,11 +205,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
}
@@ -261,15 +223,6 @@ func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
*out = *in
in.Source.DeepCopyInto(&out.Source)
out.Destination = in.Destination
if in.SyncPolicy != nil {
in, out := &in.SyncPolicy, &out.SyncPolicy
if *in == nil {
*out = nil
} else {
*out = new(SyncPolicy)
(*in).DeepCopyInto(*out)
}
}
return
}
@@ -366,15 +319,6 @@ func (in *Cluster) DeepCopy() *Cluster {
func (in *ClusterConfig) DeepCopyInto(out *ClusterConfig) {
*out = *in
in.TLSClientConfig.DeepCopyInto(&out.TLSClientConfig)
if in.AWSAuthConfig != nil {
in, out := &in.AWSAuthConfig, &out.AWSAuthConfig
if *in == nil {
*out = nil
} else {
*out = new(AWSAuthConfig)
**out = **in
}
}
return
}
@@ -537,22 +481,6 @@ func (in *HookStatus) DeepCopy() *HookStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JWTToken) DeepCopyInto(out *JWTToken) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTToken.
func (in *JWTToken) DeepCopy() *JWTToken {
if in == nil {
return nil
}
out := new(JWTToken)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Operation) DeepCopyInto(out *Operation) {
*out = *in
@@ -632,32 +560,6 @@ func (in *OperationState) DeepCopy() *OperationState {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProjectRole) DeepCopyInto(out *ProjectRole) {
*out = *in
if in.Policies != nil {
in, out := &in.Policies, &out.Policies
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.JWTTokens != nil {
in, out := &in.JWTTokens, &out.JWTTokens
*out = make([]JWTToken, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectRole.
func (in *ProjectRole) DeepCopy() *ProjectRole {
if in == nil {
return nil
}
out := new(ProjectRole)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Repository) DeepCopyInto(out *Repository) {
*out = *in
@@ -790,11 +692,6 @@ func (in *SyncOperation) DeepCopyInto(out *SyncOperation) {
(*in).DeepCopyInto(*out)
}
}
if in.ParameterOverrides != nil {
in, out := &in.ParameterOverrides, &out.ParameterOverrides
*out = make(ParameterOverrides, len(*in))
copy(*out, *in)
}
return
}
@@ -848,47 +745,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 *SyncPolicy) DeepCopyInto(out *SyncPolicy) {
*out = *in
if in.Automated != nil {
in, out := &in.Automated, &out.Automated
if *in == nil {
*out = nil
} else {
*out = new(SyncPolicyAutomated)
**out = **in
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncPolicy.
func (in *SyncPolicy) DeepCopy() *SyncPolicy {
if in == nil {
return nil
}
out := new(SyncPolicy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncPolicyAutomated) DeepCopyInto(out *SyncPolicyAutomated) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncPolicyAutomated.
func (in *SyncPolicyAutomated) DeepCopy() *SyncPolicyAutomated {
if in == nil {
return nil
}
out := new(SyncPolicyAutomated)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyncStrategy) DeepCopyInto(out *SyncStrategy) {
*out = *in

View File

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

View File

@@ -7,27 +7,17 @@ import (
"io/ioutil"
"os"
"path"
"regexp"
"strings"
"time"
"github.com/google/go-jsonnet"
"github.com/ksonnet/ksonnet/pkg/app"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/helm"
"github.com/argoproj/argo-cd/util/ksonnet"
ksutil "github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/kustomize"
)
const (
@@ -35,15 +25,6 @@ const (
DefaultRepoCacheExpiration = 24 * time.Hour
)
type AppSourceType string
const (
AppSourceKsonnet AppSourceType = "ksonnet"
AppSourceHelm AppSourceType = "helm"
AppSourceKustomize AppSourceType = "kustomize"
AppSourceDirectory AppSourceType = "directory"
)
// Service implements ManifestService interface
type Service struct {
repoLock *util.KeyLock
@@ -62,7 +43,17 @@ func NewService(gitFactory git.ClientFactory, cache cache.Cache) *Service {
// ListDir lists the contents of a GitHub repo
func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, error) {
gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision)
appRepoPath := tempRepoPath(q.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer s.repoLock.Unlock(appRepoPath)
gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey)
err := gitClient.Init()
if err != nil {
return nil, err
}
commitSHA, err := gitClient.LsRemote(q.Revision)
if err != nil {
return nil, err
}
@@ -74,9 +65,7 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er
return &res, nil
}
s.repoLock.Lock(gitClient.Root())
defer s.repoLock.Unlock(gitClient.Root())
commitSHA, err = checkoutRevision(gitClient, commitSHA)
err = checkoutRevision(gitClient, q.Revision)
if err != nil {
return nil, err
}
@@ -90,7 +79,7 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er
Items: lsFiles,
}
err = s.cache.Set(&cache.Item{
Key: listDirCacheKey(commitSHA, q),
Key: cacheKey,
Object: &res,
Expiration: DefaultRepoCacheExpiration,
})
@@ -101,21 +90,16 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er
}
func (s *Service) GetFile(ctx context.Context, q *GetFileRequest) (*GetFileResponse, error) {
gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision)
appRepoPath := tempRepoPath(q.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer s.repoLock.Unlock(appRepoPath)
gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey)
err := gitClient.Init()
if err != nil {
return nil, err
}
cacheKey := getFileCacheKey(commitSHA, q)
var res GetFileResponse
err = s.cache.Get(cacheKey, &res)
if err == nil {
log.Infof("getfile cache hit: %s", cacheKey)
return &res, nil
}
s.repoLock.Lock(gitClient.Root())
defer s.repoLock.Unlock(gitClient.Root())
commitSHA, err = checkoutRevision(gitClient, commitSHA)
err = checkoutRevision(gitClient, q.Revision)
if err != nil {
return nil, err
}
@@ -123,27 +107,36 @@ func (s *Service) GetFile(ctx context.Context, q *GetFileRequest) (*GetFileRespo
if err != nil {
return nil, err
}
res = GetFileResponse{
res := GetFileResponse{
Data: data,
}
err = s.cache.Set(&cache.Item{
Key: getFileCacheKey(commitSHA, q),
Object: &res,
Expiration: DefaultRepoCacheExpiration,
})
if err != nil {
log.Warnf("getfile cache set error %s: %v", cacheKey, err)
}
return &res, nil
}
func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*ManifestResponse, error) {
gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision)
var res ManifestResponse
if git.IsCommitSHA(q.Revision) {
cacheKey := manifestCacheKey(q.Revision, q)
err := s.cache.Get(cacheKey, &res)
if err == nil {
log.Infof("manifest cache hit: %s", cacheKey)
return &res, nil
}
}
appRepoPath := tempRepoPath(q.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer s.repoLock.Unlock(appRepoPath)
gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey)
err := gitClient.Init()
if err != nil {
return nil, err
}
commitSHA, err := gitClient.LsRemote(q.Revision)
if err != nil {
return nil, err
}
cacheKey := manifestCacheKey(commitSHA, q)
var res ManifestResponse
err = s.cache.Get(cacheKey, &res)
if err == nil {
log.Infof("manifest cache hit: %s", cacheKey)
@@ -155,23 +148,65 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
log.Infof("manifest cache miss: %s", cacheKey)
}
s.repoLock.Lock(gitClient.Root())
defer s.repoLock.Unlock(gitClient.Root())
commitSHA, err = checkoutRevision(gitClient, commitSHA)
err = checkoutRevision(gitClient, q.Revision)
if err != nil {
return nil, err
}
appPath := path.Join(gitClient.Root(), q.Path)
appPath := path.Join(appRepoPath, q.Path)
ksApp, err := ksutil.NewKsonnetApp(appPath)
if err != nil {
return nil, fmt.Errorf("unable to load application from %s: %v", appPath, err)
}
genRes, err := generateManifests(appPath, q)
params, err := ksApp.ListEnvParams(q.Environment)
if err != nil {
return nil, fmt.Errorf("Failed to list ksonnet app params: %v", err)
}
if q.ComponentParameterOverrides != nil {
for _, override := range q.ComponentParameterOverrides {
err = ksApp.SetComponentParams(q.Environment, override.Component, override.Name, override.Value)
if err != nil {
return nil, err
}
}
}
appSpec := ksApp.App()
env, err := appSpec.Environment(q.Environment)
if err != nil {
return nil, fmt.Errorf("environment '%s' does not exist in ksonnet app", q.Environment)
}
targetObjs, err := ksApp.Show(q.Environment)
if err != nil {
return nil, err
}
res = *genRes
res.Revision = commitSHA
manifests := make([]string, len(targetObjs))
for i, target := range targetObjs {
if q.AppLabel != "" {
err = kube.SetLabel(target, common.LabelApplicationName, q.AppLabel)
if err != nil {
return nil, err
}
}
manifestStr, err := json.Marshal(target.Object)
if err != nil {
return nil, err
}
manifests[i] = string(manifestStr)
}
res = ManifestResponse{
Revision: commitSHA,
Manifests: manifests,
Namespace: env.Destination.Namespace,
Server: env.Destination.Server,
Params: params,
}
err = s.cache.Set(&cache.Item{
Key: manifestCacheKey(commitSHA, q),
Object: res,
Key: cacheKey,
Object: &res,
Expiration: DefaultRepoCacheExpiration,
})
if err != nil {
@@ -180,268 +215,33 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
return &res, nil
}
// generateManifests generates manifests from a path
func generateManifests(appPath string, q *ManifestRequest) (*ManifestResponse, error) {
var targetObjs []*unstructured.Unstructured
var params []*v1alpha1.ComponentParameter
var env *app.EnvironmentSpec
var err error
appSourceType := IdentifyAppSourceTypeByAppDir(appPath)
switch appSourceType {
case AppSourceKsonnet:
targetObjs, params, env, err = ksShow(appPath, q.Environment, q.ComponentParameterOverrides)
case AppSourceHelm:
h := helm.NewHelmApp(appPath)
err = h.DependencyBuild()
if err != nil {
return nil, err
}
targetObjs, err = h.Template(q.AppLabel, q.Namespace, q.ValueFiles, q.ComponentParameterOverrides)
if err != nil {
return nil, err
}
params, err = h.GetParameters(q.ValueFiles)
if err != nil {
return nil, err
}
case AppSourceKustomize:
k := kustomize.NewKustomizeApp(appPath)
targetObjs, err = k.Build()
case AppSourceDirectory:
targetObjs, err = findManifests(appPath)
}
if err != nil {
return nil, err
}
manifests := make([]string, 0)
for _, obj := range targetObjs {
var targets []*unstructured.Unstructured
if obj.IsList() {
err = obj.EachListItem(func(object runtime.Object) error {
unstructuredObj, ok := object.(*unstructured.Unstructured)
if ok {
targets = append(targets, unstructuredObj)
return nil
} else {
return fmt.Errorf("resource list item has unexpected type")
}
})
if err != nil {
return nil, err
}
} else {
targets = []*unstructured.Unstructured{obj}
}
for _, target := range targets {
if q.AppLabel != "" && !kube.IsCRD(target) {
err = kube.SetLabel(target, common.LabelApplicationName, q.AppLabel)
if err != nil {
return nil, err
}
}
manifestStr, err := json.Marshal(target.Object)
if err != nil {
return nil, err
}
manifests = append(manifests, string(manifestStr))
}
}
res := ManifestResponse{
Manifests: manifests,
Params: params,
}
if env != nil {
res.Namespace = env.Destination.Namespace
res.Server = env.Destination.Server
}
return &res, nil
}
// tempRepoPath returns a formulated temporary directory location to clone a repository
func tempRepoPath(repo string) string {
return path.Join(os.TempDir(), strings.Replace(repo, "/", "_", -1))
}
// IdentifyAppSourceTypeByAppDir examines a directory and determines its application source type
func IdentifyAppSourceTypeByAppDir(appDirPath string) AppSourceType {
if pathExists(path.Join(appDirPath, "app.yaml")) {
return AppSourceKsonnet
}
if pathExists(path.Join(appDirPath, "Chart.yaml")) {
return AppSourceHelm
}
if pathExists(path.Join(appDirPath, "kustomization.yaml")) {
return AppSourceKustomize
}
return AppSourceDirectory
}
// IdentifyAppSourceTypeByAppPath determines application source type by app file path
func IdentifyAppSourceTypeByAppPath(appFilePath string) AppSourceType {
if strings.HasSuffix(appFilePath, "app.yaml") {
return AppSourceKsonnet
}
if strings.HasSuffix(appFilePath, "Chart.yaml") {
return AppSourceHelm
}
if strings.HasSuffix(appFilePath, "kustomization.yaml") {
return AppSourceKustomize
}
return AppSourceDirectory
}
// checkoutRevision is a convenience function to initialize a repo, fetch, and checkout a revision
// Returns the 40 character commit SHA after the checkout has been performed
func checkoutRevision(gitClient git.Client, commitSHA string) (string, error) {
err := gitClient.Init()
func checkoutRevision(gitClient git.Client, revision string) error {
err := gitClient.Fetch()
if err != nil {
return "", status.Errorf(codes.Internal, "Failed to initialize git repo: %v", err)
return err
}
err = gitClient.Fetch()
err = gitClient.Reset()
if err != nil {
return "", status.Errorf(codes.Internal, "Failed to fetch git repo: %v", err)
log.Warn(err)
}
err = gitClient.Checkout(commitSHA)
err = gitClient.Checkout(revision)
if err != nil {
return "", status.Errorf(codes.Internal, "Failed to checkout %s: %v", commitSHA, err)
return err
}
return gitClient.CommitSHA()
return nil
}
func manifestCacheKey(commitSHA string, q *ManifestRequest) string {
pStr, _ := json.Marshal(q.ComponentParameterOverrides)
valuesFiles := strings.Join(q.ValueFiles, ",")
return fmt.Sprintf("mfst|%s|%s|%s|%s|%s|%s|%s", q.AppLabel, q.Path, q.Environment, commitSHA, string(pStr), valuesFiles, q.Namespace)
return fmt.Sprintf("mfst|%s|%s|%s|%s", q.Path, q.Environment, commitSHA, string(pStr))
}
func listDirCacheKey(commitSHA string, q *ListDirRequest) string {
return fmt.Sprintf("ldir|%s|%s", q.Path, commitSHA)
}
func getFileCacheKey(commitSHA string, q *GetFileRequest) string {
return fmt.Sprintf("gfile|%s|%s", q.Path, commitSHA)
}
// ksShow runs `ks show` in an app directory after setting any component parameter overrides
func ksShow(appPath, envName string, overrides []*v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, []*v1alpha1.ComponentParameter, *app.EnvironmentSpec, error) {
ksApp, err := ksonnet.NewKsonnetApp(appPath)
if err != nil {
return nil, nil, nil, status.Errorf(codes.FailedPrecondition, "unable to load application from %s: %v", appPath, err)
}
params, err := ksApp.ListEnvParams(envName)
if err != nil {
return nil, nil, nil, status.Errorf(codes.InvalidArgument, "Failed to list ksonnet app params: %v", err)
}
if overrides != nil {
for _, override := range overrides {
err = ksApp.SetComponentParams(envName, override.Component, override.Name, override.Value)
if err != nil {
return nil, nil, nil, err
}
}
}
appSpec := ksApp.App()
env, err := appSpec.Environment(envName)
if err != nil {
return nil, nil, nil, status.Errorf(codes.NotFound, "environment %q does not exist in ksonnet app", envName)
}
targetObjs, err := ksApp.Show(envName)
if err != nil {
return nil, nil, nil, err
}
return targetObjs, params, env, nil
}
var manifestFile = regexp.MustCompile(`^.*\.(yaml|yml|json|jsonnet)$`)
// findManifests looks at all yaml files in a directory and unmarshals them into a list of unstructured objects
func findManifests(appPath string) ([]*unstructured.Unstructured, error) {
files, err := ioutil.ReadDir(appPath)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Failed to read dir %s: %v", appPath, err)
}
var objs []*unstructured.Unstructured
for _, f := range files {
if f.IsDir() || !manifestFile.MatchString(f.Name()) {
continue
}
out, err := ioutil.ReadFile(path.Join(appPath, f.Name()))
if err != nil {
return nil, err
}
if strings.HasSuffix(f.Name(), ".json") {
var obj unstructured.Unstructured
err = json.Unmarshal(out, &obj)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
objs = append(objs, &obj)
} else if strings.HasSuffix(f.Name(), ".jsonnet") {
vm := jsonnet.MakeVM()
vm.Importer(&jsonnet.FileImporter{
JPaths: []string{appPath},
})
jsonStr, err := vm.EvaluateSnippet(f.Name(), string(out))
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Failed to evaluate jsonnet %q: %v", f.Name(), err)
}
// attempt to unmarshal either array or single object
var jsonObjs []*unstructured.Unstructured
err = json.Unmarshal([]byte(jsonStr), &jsonObjs)
if err == nil {
objs = append(objs, jsonObjs...)
} else {
var jsonObj unstructured.Unstructured
err = json.Unmarshal([]byte(jsonStr), &jsonObj)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Failed to unmarshal generated json %q: %v", f.Name(), err)
}
objs = append(objs, &jsonObj)
}
} else {
yamlObjs, err := kube.SplitYAML(string(out))
if err != nil {
if len(yamlObjs) > 0 {
// If we get here, we had a multiple objects in a single YAML file which had some
// valid k8s objects, but errors parsing others (within the same file). It's very
// likely the user messed up a portion of the YAML, so report on that.
return nil, status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, it might be a unrelated YAML file which we will ignore
continue
}
objs = append(objs, yamlObjs...)
}
}
return objs, nil
}
// pathExists reports whether the named file or directory exists.
func pathExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// newClientResolveRevision is a helper to perform the common task of instantiating a git client
// and resolving a revision to a commit SHA
func (s *Service) newClientResolveRevision(repo *v1alpha1.Repository, revision string) (git.Client, string, error) {
appRepoPath := tempRepoPath(repo.Repo)
gitClient, err := s.gitFactory.NewClient(repo.Repo, appRepoPath, repo.Username, repo.Password, repo.SSHPrivateKey)
if err != nil {
return nil, "", err
}
commitSHA, err := gitClient.LsRemote(revision)
if err != nil {
return nil, "", err
}
return gitClient, commitSHA, nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,15 @@
package reposerver
import (
"crypto/tls"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/server/version"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/git"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
tlsutil "github.com/argoproj/argo-cd/util/tls"
"github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
)
@@ -22,51 +18,28 @@ type ArgoCDRepoServer struct {
log *log.Entry
gitFactory git.ClientFactory
cache cache.Cache
opts []grpc.ServerOption
}
// NewServer returns a new instance of the ArgoCD Repo server
func NewServer(gitFactory git.ClientFactory, cache cache.Cache, tlsConfCustomizer tlsutil.ConfigCustomizer) (*ArgoCDRepoServer, error) {
// generate TLS cert
hosts := []string{
"localhost",
"argocd-repo-server",
}
cert, err := tlsutil.GenerateX509KeyPair(tlsutil.CertOptions{
Hosts: hosts,
Organization: "Argo CD",
IsCA: true,
})
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{*cert}}
tlsConfCustomizer(tlsConfig)
opts := []grpc.ServerOption{grpc.Creds(credentials.NewTLS(tlsConfig))}
func NewServer(gitFactory git.ClientFactory, cache cache.Cache) *ArgoCDRepoServer {
return &ArgoCDRepoServer{
log: log.NewEntry(log.New()),
gitFactory: gitFactory,
cache: cache,
opts: opts,
}, nil
}
}
// CreateGRPC creates new configured grpc server
func (a *ArgoCDRepoServer) CreateGRPC() *grpc.Server {
server := grpc.NewServer(
append(a.opts,
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_logrus.StreamServerInterceptor(a.log),
grpc_util.PanicLoggerStreamServerInterceptor(a.log),
)),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_logrus.UnaryServerInterceptor(a.log),
grpc_util.PanicLoggerUnaryServerInterceptor(a.log),
)))...,
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_logrus.StreamServerInterceptor(a.log),
grpc_util.PanicLoggerStreamServerInterceptor(a.log),
)),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_logrus.UnaryServerInterceptor(a.log),
grpc_util.PanicLoggerUnaryServerInterceptor(a.log),
)),
)
version.RegisterVersionServiceServer(server, &version.Server{})
manifestService := repository.NewService(a.gitFactory, a.cache)

View File

@@ -1,14 +1,11 @@
package account
import (
"time"
jwt "github.com/dgrijalva/jwt-go"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/argoproj/argo-cd/common"
jwtutil "github.com/argoproj/argo-cd/util/jwt"
"github.com/argoproj/argo-cd/util/password"
"github.com/argoproj/argo-cd/util/session"
@@ -30,17 +27,16 @@ func NewServer(sessionMgr *session.SessionManager, settingsMgr *settings.Setting
}
// UpdatePassword updates the password of the local admin superuser.
//UpdatePassword is used to Update a User's Passwords
func (s *Server) UpdatePassword(ctx context.Context, q *UpdatePasswordRequest) (*UpdatePasswordResponse, error) {
username := getAuthenticatedUser(ctx)
if username != common.ArgoCDAdminUsername {
return nil, status.Errorf(codes.InvalidArgument, "password can only be changed for local users, not user %q", username)
}
cdSettings, err := s.settingsMgr.GetSettings()
if err != nil {
return nil, err
}
if _, ok := cdSettings.LocalUsers[username]; !ok {
return nil, status.Errorf(codes.InvalidArgument, "password can only be changed for local users")
}
err = s.sessionMgr.VerifyUsernamePassword(username, q.CurrentPassword)
if err != nil {
@@ -52,8 +48,7 @@ func (s *Server) UpdatePassword(ctx context.Context, q *UpdatePasswordRequest) (
return nil, err
}
cdSettings.AdminPasswordHash = hashedPassword
cdSettings.AdminPasswordMtime = time.Now().UTC()
cdSettings.LocalUsers[username] = hashedPassword
err = s.settingsMgr.SaveSettings(cdSettings)
if err != nil {

View File

@@ -1,7 +1,17 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: server/account/account.proto
package account // import "github.com/argoproj/argo-cd/server/account"
/*
Package account is a generated protocol buffer package.
It is generated from these files:
server/account/account.proto
It has these top-level messages:
UpdatePasswordRequest
UpdatePasswordResponse
*/
package account
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
@@ -26,45 +36,14 @@ var _ = math.Inf
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type UpdatePasswordRequest struct {
NewPassword string `protobuf:"bytes,1,opt,name=newPassword,proto3" json:"newPassword,omitempty"`
CurrentPassword string `protobuf:"bytes,2,opt,name=currentPassword,proto3" json:"currentPassword,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
NewPassword string `protobuf:"bytes,1,opt,name=newPassword,proto3" json:"newPassword,omitempty"`
CurrentPassword string `protobuf:"bytes,2,opt,name=currentPassword,proto3" json:"currentPassword,omitempty"`
}
func (m *UpdatePasswordRequest) Reset() { *m = UpdatePasswordRequest{} }
func (m *UpdatePasswordRequest) String() string { return proto.CompactTextString(m) }
func (*UpdatePasswordRequest) ProtoMessage() {}
func (*UpdatePasswordRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_account_d27ff2bbd0f6944b, []int{0}
}
func (m *UpdatePasswordRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *UpdatePasswordRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_UpdatePasswordRequest.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *UpdatePasswordRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_UpdatePasswordRequest.Merge(dst, src)
}
func (m *UpdatePasswordRequest) XXX_Size() int {
return m.Size()
}
func (m *UpdatePasswordRequest) XXX_DiscardUnknown() {
xxx_messageInfo_UpdatePasswordRequest.DiscardUnknown(m)
}
var xxx_messageInfo_UpdatePasswordRequest proto.InternalMessageInfo
func (m *UpdatePasswordRequest) Reset() { *m = UpdatePasswordRequest{} }
func (m *UpdatePasswordRequest) String() string { return proto.CompactTextString(m) }
func (*UpdatePasswordRequest) ProtoMessage() {}
func (*UpdatePasswordRequest) Descriptor() ([]byte, []int) { return fileDescriptorAccount, []int{0} }
func (m *UpdatePasswordRequest) GetNewPassword() string {
if m != nil {
@@ -81,43 +60,12 @@ func (m *UpdatePasswordRequest) GetCurrentPassword() string {
}
type UpdatePasswordResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UpdatePasswordResponse) Reset() { *m = UpdatePasswordResponse{} }
func (m *UpdatePasswordResponse) String() string { return proto.CompactTextString(m) }
func (*UpdatePasswordResponse) ProtoMessage() {}
func (*UpdatePasswordResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_account_d27ff2bbd0f6944b, []int{1}
}
func (m *UpdatePasswordResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *UpdatePasswordResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_UpdatePasswordResponse.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *UpdatePasswordResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_UpdatePasswordResponse.Merge(dst, src)
}
func (m *UpdatePasswordResponse) XXX_Size() int {
return m.Size()
}
func (m *UpdatePasswordResponse) XXX_DiscardUnknown() {
xxx_messageInfo_UpdatePasswordResponse.DiscardUnknown(m)
}
var xxx_messageInfo_UpdatePasswordResponse proto.InternalMessageInfo
func (m *UpdatePasswordResponse) Reset() { *m = UpdatePasswordResponse{} }
func (m *UpdatePasswordResponse) String() string { return proto.CompactTextString(m) }
func (*UpdatePasswordResponse) ProtoMessage() {}
func (*UpdatePasswordResponse) Descriptor() ([]byte, []int) { return fileDescriptorAccount, []int{1} }
func init() {
proto.RegisterType((*UpdatePasswordRequest)(nil), "account.UpdatePasswordRequest")
@@ -149,7 +97,7 @@ func NewAccountServiceClient(cc *grpc.ClientConn) AccountServiceClient {
func (c *accountServiceClient) UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, opts ...grpc.CallOption) (*UpdatePasswordResponse, error) {
out := new(UpdatePasswordResponse)
err := c.cc.Invoke(ctx, "/account.AccountService/UpdatePassword", in, out, opts...)
err := grpc.Invoke(ctx, "/account.AccountService/UpdatePassword", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
@@ -225,9 +173,6 @@ func (m *UpdatePasswordRequest) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintAccount(dAtA, i, uint64(len(m.CurrentPassword)))
i += copy(dAtA[i:], m.CurrentPassword)
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
@@ -246,9 +191,6 @@ func (m *UpdatePasswordResponse) MarshalTo(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
@@ -272,18 +214,12 @@ func (m *UpdatePasswordRequest) Size() (n int) {
if l > 0 {
n += 1 + l + sovAccount(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func (m *UpdatePasswordResponse) Size() (n int) {
var l int
_ = l
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
@@ -399,7 +335,6 @@ func (m *UpdatePasswordRequest) Unmarshal(dAtA []byte) error {
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
@@ -450,7 +385,6 @@ func (m *UpdatePasswordResponse) Unmarshal(dAtA []byte) error {
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
@@ -565,11 +499,9 @@ var (
ErrIntOverflowAccount = fmt.Errorf("proto: integer overflow")
)
func init() {
proto.RegisterFile("server/account/account.proto", fileDescriptor_account_d27ff2bbd0f6944b)
}
func init() { proto.RegisterFile("server/account/account.proto", fileDescriptorAccount) }
var fileDescriptor_account_d27ff2bbd0f6944b = []byte{
var fileDescriptorAccount = []byte{
// 268 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x29, 0x4e, 0x2d, 0x2a,
0x4b, 0x2d, 0xd2, 0x4f, 0x4c, 0x4e, 0xce, 0x2f, 0xcd, 0x2b, 0x81, 0xd1, 0x7a, 0x05, 0x45, 0xf9,

View File

@@ -15,7 +15,6 @@ import (
"k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
@@ -32,9 +31,7 @@ import (
argoutil "github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/rbac"
"github.com/argoproj/argo-cd/util/session"
)
// Server provides a Application service
@@ -43,12 +40,10 @@ type Server struct {
kubeclientset kubernetes.Interface
appclientset appclientset.Interface
repoClientset reposerver.Clientset
kubectl kube.Kubectl
db db.ArgoDB
appComparator controller.AppStateManager
enf *rbac.Enforcer
projectLock *util.KeyLock
auditLogger *argo.AuditLogger
}
// NewServer returns a new instance of the Application service
@@ -57,7 +52,6 @@ func NewServer(
kubeclientset kubernetes.Interface,
appclientset appclientset.Interface,
repoClientset reposerver.Clientset,
kubectl kube.Kubectl,
db db.ArgoDB,
enf *rbac.Enforcer,
projectLock *util.KeyLock,
@@ -69,11 +63,9 @@ func NewServer(
kubeclientset: kubeclientset,
db: db,
repoClientset: repoClientset,
kubectl: kubectl,
appComparator: controller.NewAppStateManager(db, appclientset, repoClientset, namespace, kubectl),
appComparator: controller.NewAppStateManager(db, appclientset, repoClientset, namespace),
enf: enf,
projectLock: projectLock,
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"),
}
}
@@ -82,65 +74,6 @@ func appRBACName(app appv1.Application) string {
return fmt.Sprintf("%s/%s", app.Spec.GetProject(), app.Name)
}
func toString(val interface{}) string {
if val == nil {
return ""
}
return fmt.Sprintf("%s", val)
}
// hideSecretData checks if given object kind is Secret, replaces data keys with stars and returns unchanged data map. The method additionally check if data key if different
// from corresponding key of optional parameter `otherData` and adds extra star to keep information about difference. So if secret data is out of sync user still can see which
// fields are different.
func hideSecretData(state string, otherData map[string]interface{}) (string, map[string]interface{}) {
obj, err := appv1.UnmarshalToUnstructured(state)
if err == nil {
if obj != nil && obj.GetKind() == kube.SecretKind {
if data, ok, err := unstructured.NestedMap(obj.Object, "data"); err == nil && ok {
unchangedData := make(map[string]interface{})
for k, v := range data {
unchangedData[k] = v
}
for k := range data {
replacement := "********"
if otherData != nil {
if val, ok := otherData[k]; ok && toString(val) != toString(data[k]) {
replacement = replacement + "*"
}
}
data[k] = replacement
}
_ = unstructured.SetNestedMap(obj.Object, data, "data")
newState, err := json.Marshal(obj)
if err == nil {
return string(newState), unchangedData
}
}
}
}
return state, nil
}
func hideNodesSecrets(nodes []appv1.ResourceNode) {
for i := range nodes {
node := nodes[i]
node.State, _ = hideSecretData(node.State, nil)
hideNodesSecrets(node.Children)
nodes[i] = node
}
}
func hideAppSecrets(app *appv1.Application) {
for i := range app.Status.ComparisonResult.Resources {
res := app.Status.ComparisonResult.Resources[i]
var data map[string]interface{}
res.LiveState, data = hideSecretData(res.LiveState, nil)
res.TargetState, _ = hideSecretData(res.TargetState, data)
hideNodesSecrets(res.ChildLiveResources)
app.Status.ComparisonResult.Resources[i] = res
}
}
// List returns list of applications
func (s *Server) List(ctx context.Context, q *ApplicationQuery) (*appv1.ApplicationList, error) {
appList, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).List(metav1.ListOptions{})
@@ -154,11 +87,6 @@ func (s *Server) List(ctx context.Context, q *ApplicationQuery) (*appv1.Applicat
}
}
newItems = argoutil.FilterByProjects(newItems, q.Projects)
for i := range newItems {
app := newItems[i]
hideAppSecrets(&app)
newItems[i] = app
}
appList.Items = newItems
return appList, nil
}
@@ -169,8 +97,10 @@ func (s *Server) Create(ctx context.Context, q *ApplicationCreateRequest) (*appv
return nil, grpc.ErrPermissionDenied
}
s.projectLock.Lock(q.Application.Spec.Project)
defer s.projectLock.Unlock(q.Application.Spec.Project)
if !q.Application.Spec.BelongsToDefaultProject() {
s.projectLock.Lock(q.Application.Spec.Project)
defer s.projectLock.Unlock(q.Application.Spec.Project)
}
a := q.Application
err := s.validateApp(ctx, &a.Spec)
@@ -198,11 +128,6 @@ func (s *Server) Create(ctx context.Context, q *ApplicationCreateRequest) (*appv
}
}
}
if err == nil {
s.logEvent(out, ctx, argo.EventReasonResourceCreated, "created application")
}
hideAppSecrets(out)
return out, err
}
@@ -212,7 +137,7 @@ func (s *Server) GetManifests(ctx context.Context, q *ApplicationManifestQuery)
if err != nil {
return nil, err
}
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "get", appRBACName(*a)) {
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications/manifests", "get", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
repo := s.getRepo(ctx, a.Spec.Source.RepoURL)
@@ -241,8 +166,6 @@ func (s *Server) GetManifests(ctx context.Context, q *ApplicationManifestQuery)
Revision: revision,
ComponentParameterOverrides: overrides,
AppLabel: a.Name,
ValueFiles: a.Spec.Source.ValuesFiles,
Namespace: a.Spec.Destination.Namespace,
})
if err != nil {
return nil, err
@@ -271,7 +194,6 @@ func (s *Server) Get(ctx context.Context, q *ApplicationQuery) (*appv1.Applicati
return nil, err
}
}
hideAppSecrets(a)
return a, nil
}
@@ -281,44 +203,27 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *ApplicationResourceE
if err != nil {
return nil, err
}
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "get", appRBACName(*a)) {
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications/events", "get", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
var (
kubeClientset kubernetes.Interface
fieldSelector string
namespace string
)
// There are two places where we get events. If we are getting application events, we query
// our own cluster. If it is events on a resource on an external cluster, then we query the
// external cluster using its rest.Config
if q.ResourceName == "" && q.ResourceUID == "" {
kubeClientset = s.kubeclientset
namespace = a.Namespace
fieldSelector = fields.SelectorFromSet(map[string]string{
"involvedObject.name": a.Name,
"involvedObject.uid": string(a.UID),
"involvedObject.namespace": a.Namespace,
}).String()
} else {
var config *rest.Config
config, namespace, err = s.getApplicationClusterConfig(*q.Name)
if err != nil {
return nil, err
}
kubeClientset, err = kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
fieldSelector = fields.SelectorFromSet(map[string]string{
"involvedObject.name": q.ResourceName,
"involvedObject.uid": q.ResourceUID,
"involvedObject.namespace": namespace,
}).String()
config, namespace, err := s.getApplicationClusterConfig(*q.Name)
if err != nil {
return nil, err
}
kubeClientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
fieldSelector := fields.SelectorFromSet(map[string]string{
"involvedObject.name": q.ResourceName,
"involvedObject.uid": q.ResourceUID,
"involvedObject.namespace": namespace,
}).String()
log.Infof("Querying for resource events with field selector: %s", fieldSelector)
opts := metav1.ListOptions{FieldSelector: fieldSelector}
return kubeClientset.CoreV1().Events(namespace).List(opts)
}
@@ -328,22 +233,17 @@ func (s *Server) Update(ctx context.Context, q *ApplicationUpdateRequest) (*appv
return nil, grpc.ErrPermissionDenied
}
s.projectLock.Lock(q.Application.Spec.Project)
defer s.projectLock.Unlock(q.Application.Spec.Project)
if !q.Application.Spec.BelongsToDefaultProject() {
s.projectLock.Lock(q.Application.Spec.Project)
defer s.projectLock.Unlock(q.Application.Spec.Project)
}
a := q.Application
err := s.validateApp(ctx, &a.Spec)
if err != nil {
return nil, err
}
out, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(a)
if out != nil {
hideAppSecrets(out)
}
if err == nil {
s.logEvent(a, ctx, argo.EventReasonResourceUpdated, "updated application")
}
return out, err
return s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(a)
}
// removeInvalidOverrides removes any parameter overrides that are no longer valid
@@ -351,10 +251,6 @@ func (s *Server) Update(ctx context.Context, q *ApplicationUpdateRequest) (*appv
// throws an error is passed override is invalid
// if passed override and old overrides are invalid, throws error, old overrides not dropped
func (s *Server) removeInvalidOverrides(a *appv1.Application, q *ApplicationUpdateSpecRequest) (*ApplicationUpdateSpecRequest, error) {
if a.Spec.Source.Environment == "" {
// this method is only valid for ksonnet apps
return q, nil
}
oldParams := argo.ParamToMap(a.Spec.Source.ComponentParameterOverrides)
validAppSet := argo.ParamToMap(a.Status.Parameters)
@@ -364,7 +260,7 @@ func (s *Server) removeInvalidOverrides(a *appv1.Application, q *ApplicationUpda
if !argo.CheckValidParam(validAppSet, param) {
alreadySet := argo.CheckValidParam(oldParams, param)
if !alreadySet {
return nil, status.Errorf(codes.InvalidArgument, "Parameter '%s' in '%s' does not exist in ksonnet app", param.Name, param.Component)
return nil, status.Errorf(codes.InvalidArgument, "Parameter '%s' in '%s' does not exist in ksonnet", param.Name, param.Component)
}
} else {
params = append(params, param)
@@ -376,8 +272,11 @@ func (s *Server) removeInvalidOverrides(a *appv1.Application, q *ApplicationUpda
// UpdateSpec updates an application spec and filters out any invalid parameter overrides
func (s *Server) UpdateSpec(ctx context.Context, q *ApplicationUpdateSpecRequest) (*appv1.ApplicationSpec, error) {
s.projectLock.Lock(q.Spec.Project)
defer s.projectLock.Unlock(q.Spec.Project)
if !q.Spec.BelongsToDefaultProject() {
s.projectLock.Lock(q.Spec.Project)
defer s.projectLock.Unlock(q.Spec.Project)
}
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*q.Name, metav1.GetOptions{})
if err != nil {
@@ -394,21 +293,14 @@ func (s *Server) UpdateSpec(ctx context.Context, q *ApplicationUpdateSpecRequest
if err != nil {
return nil, err
}
for {
a.Spec = q.Spec
_, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(a)
if err == nil {
s.logEvent(a, ctx, argo.EventReasonResourceUpdated, "updated application spec")
return &q.Spec, nil
}
if !apierr.IsConflict(err) {
return nil, err
}
a, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*q.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
patch, err := json.Marshal(map[string]appv1.ApplicationSpec{
"spec": q.Spec,
})
if err != nil {
return nil, err
}
_, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Patch(*q.Name, types.MergePatchType, patch)
return &q.Spec, err
}
// Delete removes an application and all associated resources
@@ -418,8 +310,10 @@ func (s *Server) Delete(ctx context.Context, q *ApplicationDeleteRequest) (*Appl
return nil, err
}
s.projectLock.Lock(a.Spec.Project)
defer s.projectLock.Unlock(a.Spec.Project)
if !a.Spec.BelongsToDefaultProject() {
s.projectLock.Lock(a.Spec.Project)
defer s.projectLock.Unlock(a.Spec.Project)
}
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "delete", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
@@ -461,7 +355,6 @@ func (s *Server) Delete(ctx context.Context, q *ApplicationDeleteRequest) (*Appl
return nil, err
}
s.logEvent(a, ctx, argo.EventReasonResourceDeleted, "deleted application")
return &ApplicationResponse{}, nil
}
@@ -480,7 +373,6 @@ func (s *Server) Watch(q *ApplicationQuery, ws ApplicationService_WatchServer) e
// do not emit apps user does not have accessing
continue
}
hideAppSecrets(&a)
err = ws.Send(&appv1.ApplicationWatchEvent{
Type: next.Type,
Application: a,
@@ -496,7 +388,6 @@ func (s *Server) Watch(q *ApplicationQuery, ws ApplicationService_WatchServer) e
case <-ws.Context().Done():
w.Stop()
case <-done:
w.Stop()
}
return nil
}
@@ -517,7 +408,7 @@ func (s *Server) validateApp(ctx context.Context, spec *appv1.ApplicationSpec) e
return err
}
if len(conditions) > 0 {
return status.Errorf(codes.InvalidArgument, "application spec is invalid: %s", argo.FormatAppConditions(conditions))
return status.Errorf(codes.InvalidArgument, "application spec is invalid: \n%s", argo.FormatAppConditions(conditions))
}
return nil
}
@@ -550,76 +441,41 @@ func (s *Server) ensurePodBelongsToApp(applicationName string, podName, namespac
return nil
}
func (s *Server) DeleteResource(ctx context.Context, q *ApplicationDeleteResourceRequest) (*ApplicationResponse, error) {
func (s *Server) DeletePod(ctx context.Context, q *ApplicationDeletePodRequest) (*ApplicationResponse, error) {
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*q.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "delete", appRBACName(*a)) {
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications/pods", "delete", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
found := findResource(a, q)
if found == nil {
return nil, status.Errorf(codes.InvalidArgument, "%s %s %s not found as part of application %s", q.Kind, q.APIVersion, q.ResourceName, *q.Name)
}
config, namespace, err := s.getApplicationClusterConfig(*q.Name)
if err != nil {
return nil, err
}
err = s.kubectl.DeleteResource(config, found, namespace)
kubeClientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
err = s.ensurePodBelongsToApp(*q.Name, *q.PodName, namespace, kubeClientset)
if err != nil {
return nil, err
}
err = kubeClientset.CoreV1().Pods(namespace).Delete(*q.PodName, &metav1.DeleteOptions{})
if err != nil {
return nil, err
}
s.logEvent(a, ctx, argo.EventReasonResourceDeleted, fmt.Sprintf("deleted resource %s/%s '%s'", q.APIVersion, q.Kind, q.ResourceName))
return &ApplicationResponse{}, nil
}
func findResource(a *appv1.Application, q *ApplicationDeleteResourceRequest) *unstructured.Unstructured {
for _, res := range a.Status.ComparisonResult.Resources {
liveObj, err := res.LiveObject()
if err != nil {
log.Warnf("Failed to unmarshal live object: %v", err)
continue
}
if liveObj == nil {
continue
}
if q.ResourceName == liveObj.GetName() && q.APIVersion == liveObj.GetAPIVersion() && q.Kind == liveObj.GetKind() {
return liveObj
}
liveObj = recurseResourceNode(q.ResourceName, q.APIVersion, q.Kind, res.ChildLiveResources)
if liveObj != nil {
return liveObj
}
}
return nil
}
func recurseResourceNode(name, apiVersion, kind string, nodes []appv1.ResourceNode) *unstructured.Unstructured {
for _, node := range nodes {
var childObj unstructured.Unstructured
err := json.Unmarshal([]byte(node.State), &childObj)
if err != nil {
log.Warnf("Failed to unmarshal child live object: %v", err)
continue
}
if name == childObj.GetName() && apiVersion == childObj.GetAPIVersion() && kind == childObj.GetKind() {
return &childObj
}
recurseChildObj := recurseResourceNode(name, apiVersion, kind, node.Children)
if recurseChildObj != nil {
return recurseChildObj
}
}
return nil
}
func (s *Server) PodLogs(q *ApplicationPodLogsQuery, ws ApplicationService_PodLogsServer) error {
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*q.Name, metav1.GetOptions{})
if err != nil {
return err
}
if !s.enf.EnforceClaims(ws.Context().Value("claims"), "applications", "get", appRBACName(*a)) {
if !s.enf.EnforceClaims(ws.Context().Value("claims"), "applications/logs", "get", appRBACName(*a)) {
return grpc.ErrPermissionDenied
}
config, namespace, err := s.getApplicationClusterConfig(*q.Name)
@@ -707,85 +563,67 @@ func (s *Server) getRepo(ctx context.Context, repoURL string) *appv1.Repository
// Sync syncs an application to its target state
func (s *Server) Sync(ctx context.Context, syncReq *ApplicationSyncRequest) (*appv1.Application, error) {
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
a, err := appIf.Get(*syncReq.Name, metav1.GetOptions{})
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*syncReq.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "sync", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
if a.Spec.SyncPolicy != nil && a.Spec.SyncPolicy.Automated != nil {
if syncReq.Revision != "" && syncReq.Revision != a.Spec.Source.TargetRevision {
return nil, status.Errorf(codes.FailedPrecondition, "Cannot sync to %s: auto-sync currently set to %s", syncReq.Revision, a.Spec.Source.TargetRevision)
return s.setAppOperation(ctx, *syncReq.Name, func(app *appv1.Application) (*appv1.Operation, error) {
syncOp := appv1.SyncOperation{
Revision: syncReq.Revision,
Prune: syncReq.Prune,
DryRun: syncReq.DryRun,
SyncStrategy: syncReq.Strategy,
}
}
parameterOverrides := make(appv1.ParameterOverrides, 0)
if syncReq.Parameter != nil {
// If parameter overrides are supplied, the caller explicitly states to use the provided
// list of overrides. NOTE: gogo/protobuf cannot currently distinguish between empty arrays
// vs nil arrays, which is why the wrapping syncReq.Parameter is examined for intent.
// See: https://github.com/gogo/protobuf/issues/181
for _, p := range syncReq.Parameter.Overrides {
parameterOverrides = append(parameterOverrides, appv1.ComponentParameter{
Name: p.Name,
Value: p.Value,
Component: p.Component,
})
}
} else {
// If parameter overrides are omitted completely, we use what is set in the application
if a.Spec.Source.ComponentParameterOverrides != nil {
parameterOverrides = appv1.ParameterOverrides(a.Spec.Source.ComponentParameterOverrides)
}
}
op := appv1.Operation{
Sync: &appv1.SyncOperation{
Revision: syncReq.Revision,
Prune: syncReq.Prune,
DryRun: syncReq.DryRun,
SyncStrategy: syncReq.Strategy,
ParameterOverrides: parameterOverrides,
},
}
a, err = argo.SetAppOperation(ctx, appIf, s.auditLogger, *syncReq.Name, &op)
if err == nil {
rev := syncReq.Revision
if syncReq.Revision == "" {
rev = a.Spec.Source.TargetRevision
}
message := fmt.Sprintf("initiated sync to %s", rev)
s.logEvent(a, ctx, argo.EventReasonOperationStarted, message)
}
return a, err
return &appv1.Operation{
Sync: &syncOp,
}, nil
})
}
func (s *Server) Rollback(ctx context.Context, rollbackReq *ApplicationRollbackRequest) (*appv1.Application, error) {
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
a, err := appIf.Get(*rollbackReq.Name, metav1.GetOptions{})
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*rollbackReq.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "sync", appRBACName(*a)) {
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "rollback", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
if a.Spec.SyncPolicy != nil && a.Spec.SyncPolicy.Automated != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Rollback cannot be initiated when auto-sync is enabled")
return s.setAppOperation(ctx, *rollbackReq.Name, func(app *appv1.Application) (*appv1.Operation, error) {
return &appv1.Operation{
Rollback: &appv1.RollbackOperation{
ID: rollbackReq.ID,
Prune: rollbackReq.Prune,
DryRun: rollbackReq.DryRun,
},
}, nil
})
}
func (s *Server) setAppOperation(ctx context.Context, appName string, operationCreator func(app *appv1.Application) (*appv1.Operation, error)) (*appv1.Application, error) {
for {
a, err := s.Get(ctx, &ApplicationQuery{Name: &appName})
if err != nil {
return nil, err
}
if a.Operation != nil {
return nil, status.Errorf(codes.InvalidArgument, "another operation is already in progress")
}
op, err := operationCreator(a)
if err != nil {
return nil, err
}
a.Operation = op
a.Status.OperationState = nil
_, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(a)
if err != nil && apierr.IsConflict(err) {
log.Warnf("Failed to set operation for app '%s' due to update conflict. Retrying again...", appName)
} else {
return a, err
}
}
op := appv1.Operation{
Rollback: &appv1.RollbackOperation{
ID: rollbackReq.ID,
Prune: rollbackReq.Prune,
DryRun: rollbackReq.DryRun,
},
}
a, err = argo.SetAppOperation(ctx, appIf, s.auditLogger, *rollbackReq.Name, &op)
if err == nil {
s.logEvent(a, ctx, argo.EventReasonOperationStarted, fmt.Sprintf("initiated rollback to %d", rollbackReq.ID))
}
return a, err
}
func (s *Server) TerminateOperation(ctx context.Context, termOpReq *OperationTerminateRequest) (*OperationTerminateResponse, error) {
@@ -793,7 +631,7 @@ func (s *Server) TerminateOperation(ctx context.Context, termOpReq *OperationTer
if err != nil {
return nil, err
}
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "sync", appRBACName(*a)) {
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "terminateop", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
@@ -814,19 +652,7 @@ func (s *Server) TerminateOperation(ctx context.Context, termOpReq *OperationTer
a, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*termOpReq.Name, metav1.GetOptions{})
if err != nil {
return nil, err
} else {
s.logEvent(a, ctx, argo.EventReasonResourceUpdated, "terminated running operation")
}
}
return nil, status.Errorf(codes.Internal, "Failed to terminate app. Too many conflicts")
}
func (s *Server) logEvent(a *appv1.Application, ctx context.Context, reason string, action string) {
eventInfo := argo.EventInfo{Type: v1.EventTypeNormal, Reason: reason}
user := session.Username(ctx)
if user == "" {
user = "Unknown user"
}
message := fmt.Sprintf("%s %s", user, action)
s.auditLogger.LogAppEvent(a, eventInfo, message)
}

File diff suppressed because it is too large Load Diff

View File

@@ -382,12 +382,8 @@ func request_ApplicationService_TerminateOperation_0(ctx context.Context, marsha
}
var (
filter_ApplicationService_DeleteResource_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_ApplicationService_DeleteResource_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ApplicationDeleteResourceRequest
func request_ApplicationService_DeletePod_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ApplicationDeletePodRequest
var metadata runtime.ServerMetadata
var (
@@ -408,11 +404,18 @@ func request_ApplicationService_DeleteResource_0(ctx context.Context, marshaler
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ApplicationService_DeleteResource_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
val, ok = pathParams["podName"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "podName")
}
msg, err := client.DeleteResource(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
protoReq.PodName, err = runtime.StringP(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "podName", err)
}
msg, err := client.DeletePod(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
@@ -857,7 +860,7 @@ func RegisterApplicationServiceHandlerClient(ctx context.Context, mux *runtime.S
})
mux.Handle("DELETE", pattern_ApplicationService_DeleteResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("DELETE", pattern_ApplicationService_DeletePod_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
if cn, ok := w.(http.CloseNotifier); ok {
@@ -875,14 +878,14 @@ func RegisterApplicationServiceHandlerClient(ctx context.Context, mux *runtime.S
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ApplicationService_DeleteResource_0(rctx, inboundMarshaler, client, req, pathParams)
resp, md, err := request_ApplicationService_DeletePod_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_ApplicationService_DeleteResource_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_ApplicationService_DeletePod_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -943,7 +946,7 @@ var (
pattern_ApplicationService_TerminateOperation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applications", "name", "operation"}, ""))
pattern_ApplicationService_DeleteResource_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applications", "name", "resource"}, ""))
pattern_ApplicationService_DeletePod_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"api", "v1", "applications", "name", "pods", "podName"}, ""))
pattern_ApplicationService_PodLogs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"api", "v1", "applications", "name", "pods", "podName", "logs"}, ""))
)
@@ -973,7 +976,7 @@ var (
forward_ApplicationService_TerminateOperation_0 = runtime.ForwardResponseMessage
forward_ApplicationService_DeleteResource_0 = runtime.ForwardResponseMessage
forward_ApplicationService_DeletePod_0 = runtime.ForwardResponseMessage
forward_ApplicationService_PodLogs_0 = runtime.ForwardResponseStream
)

View File

@@ -57,19 +57,6 @@ message ApplicationSyncRequest {
optional bool dryRun = 3 [(gogoproto.nullable) = false];
optional bool prune = 4 [(gogoproto.nullable) = false];
optional github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.SyncStrategy strategy = 5;
optional ParameterOverrides parameter = 6;
}
// ParameterOverrides is a wrapper on a list of parameters. If omitted, the application's overrides
// in the spec will be used. If set, will use the supplied list of overrides
message ParameterOverrides {
repeated Parameter overrides = 1;
}
message Parameter {
required string name = 1 [(gogoproto.nullable) = false];
optional string value = 2 [(gogoproto.nullable) = false];
optional string component = 3 [(gogoproto.nullable) = false];
}
// ApplicationUpdateSpecRequest is a request to update application spec
@@ -85,11 +72,9 @@ message ApplicationRollbackRequest {
optional bool prune = 4 [(gogoproto.nullable) = false];
}
message ApplicationDeleteResourceRequest {
message ApplicationDeletePodRequest {
required string name = 1;
required string resourceName = 2 [(gogoproto.nullable) = false];
required string apiVersion = 3 [(gogoproto.customname) = "APIVersion", (gogoproto.nullable) = false];
required string kind = 4 [(gogoproto.nullable) = false];
required string podName = 2;
}
message ApplicationPodLogsQuery {
@@ -194,9 +179,9 @@ service ApplicationService {
};
}
// DeleteResource deletes a single application resource
rpc DeleteResource(ApplicationDeleteResourceRequest) returns (ApplicationResponse) {
option (google.api.http).delete = "/api/v1/applications/{name}/resource";
// DeletePod returns stream of log entries for the specified pod. Pod
rpc DeletePod(ApplicationDeletePodRequest) returns (ApplicationResponse) {
option (google.api.http).delete = "/api/v1/applications/{name}/pods/{podName}";
}
// PodLogs returns stream of log entries for the specified pod. Pod

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