Compare commits

...

71 Commits

Author SHA1 Message Date
Alexander Matyushentsev
1d876c7729 Fix compilation error 2018-05-01 11:09:13 -07:00
Alexander Matyushentsev
70465a0520 Issue #147 - Use patch to update recentDeployments field (#149) 2018-05-01 11:05:56 -07:00
Alexander Matyushentsev
3c9845719f Issue #139 - Application sync should delete 'unexpected' resources (#144)
* Issue #139 - Application sync should delete 'unexpected' resources

* Issue #139 - Add --prune flag to app sync and rollback commands

* Apply reviewer notes: s/skipped/ignored/g; take pruned flag into consideration in dry mode
2018-05-01 10:32:46 -07:00
Alexander Matyushentsev
a36cc8946c Issue #136 - Use custom formatter to get desired state of deployment and service (#145)
* Issue #136 - Use custom formatter to get desired state of deployment and service

* Remove unnecessary break, document default removals
2018-04-30 13:29:35 -07:00
Jesse Suen
9567b539d1 Improve comparator to fall back to looking up a resource by name 2018-04-30 12:03:20 -07:00
Jesse Suen
fdf9515de2 Refactor git library:
* store credentials in files (instead of encoded in URL) to prevent leakage during git errors
* fix issue where HEAD would not track updates from origin/HEAD (resolves #133)
* refactor git library to promote code reuse, and remove shell invocations
2018-04-30 11:01:40 -07:00
Jesse Suen
b320238487 ksonnet util was not locating a ksonnet app dir correctly 2018-04-28 02:57:03 -07:00
Jesse Suen
7872a60499 Update ksonnet to v0.10.1 2018-04-28 01:59:50 -07:00
Jesse Suen
5fea3846d1 Adding clusters should always go through argocd-manager service account creation 2018-04-28 01:27:40 -07:00
Jesse Suen
86a4e0baaa RoleBindings do not need to specify service account namespace in subject 2018-04-28 01:26:46 -07:00
Alexander Matyushentsev
917f1df250 Populated 'unexpected' resources while comparing target and live states (#137)
* Make sure endpoints resources is detected as child resource for correspoinding service

* Issue #104 - Populated 'unexpected' resources while comparing target and live states

* Add comments to comparator CompareAppState method
2018-04-27 13:57:09 -07:00
Alexander Matyushentsev
11260f2476 Don't ask for user credentials if username and password are specified as arguments (#129)
* Don't ask for user credentials if username and password are specified as arguments

* Add cli image make target

* Don't re-prompt username/password in PromptCredentials
2018-04-25 14:18:30 -07:00
Jesse Suen
38d20d0f04 Add argocd ctx command for switching between contexts. Better CLI descriptions (resolves #103) 2018-04-25 01:34:54 -07:00
Jesse Suen
938f40e817 Prompting for repo credentials was accepting newline as username 2018-04-25 00:07:09 -07:00
Jesse Suen
5f9c8b862e Error properly when server address is unspecified (resolves #128) 2018-04-24 23:33:06 -07:00
Jesse Suen
d96d67bb9a Generate a temporary kubeconfig instead of using kubectl flags when applying resources 2018-04-24 19:10:16 -07:00
Jesse Suen
19c3b87676 Bump version to 0.4.0. argocd app sync --dry-run was incorrectly appending items to history (resolves #127) 2018-04-24 17:55:44 -07:00
Jesse Suen
7d08ab4e2b Bump version to v0.3.1 2018-04-24 16:20:23 -07:00
Jesse Suen
efea09d216 Fix linting issue in app rollback 2018-04-24 16:01:26 -07:00
Jesse Suen
2adaef547b Introduce argocd app history and argocd app rollback CLI commands (resolves #125) 2018-04-24 15:58:21 -07:00
Alexander Matyushentsev
d71bbf0d9a Allow overriding server or namespace separately (#126) 2018-04-24 15:26:57 -07:00
Jesse Suen
36b3b2b853 Switch to gogo/protobuf for golang code generation in order to use gogo extensions 2018-04-24 15:20:59 -07:00
Alexander Matyushentsev
63dafa08cc Issue #110 - Rollback ignores parameter overrides (#117)
* Issue #110 - Rollback ignores parameter overrides

* Issue #110 - Move rollback functionality to separate API endpoint

* Use status.Errorf to report invalid rollback id parameter
2018-04-24 13:34:03 -07:00
Alexander Matyushentsev
afddbbe875 Issue #123 - Create .argocd directory before saving config file (#124) 2018-04-24 09:20:28 -07:00
Jesse Suen
34811cafca Update download instructions to getting started 2018-04-23 02:42:02 -07:00
Jesse Suen
8a2851169c Enable auth by default. Decrease app resync period from 10m to 3m 2018-04-23 01:12:58 -07:00
Jesse Suen
1a85a2d805 Bump version file to 0.3.0. Add release target and cli-linux/darwin targets 2018-04-23 00:34:08 -07:00
Jesse Suen
cf2d00e1e0 Add ability to set a parameter override from the CLI (argo app set -p) 2018-04-22 20:36:39 -07:00
Jesse Suen
266c948add Add documentation about ArgoCD tracking strategies 2018-04-22 18:57:22 -07:00
Jesse Suen
dd564ee9dd Introduce app set command for updating an app (resolves #116) 2018-04-22 15:23:16 -07:00
Jesse Suen
b9d48cabb9 Add ability to set the tracking revision during app creation 2018-04-22 13:02:17 -07:00
Jesse Suen
276e0674c3 Deployment of resources is performed using kubectl apply (resolves #106) 2018-04-22 00:57:05 -07:00
Alexander Matyushentsev
f3c4a69327 Add watch verb to controller role 2018-04-21 12:00:45 -07:00
Jesse Suen
1c60a69866 Rename argocd app add/rm to argocd app create/delete (resolves #114) 2018-04-20 17:55:21 -07:00
Jesse Suen
050f937a24 Update ksonnet to v0.10.0-alpha.3 2018-04-20 17:16:03 -07:00
Jesse Suen
b24e478224 Add application validation 2018-04-20 16:37:54 -07:00
Andrew Merenbach
e34380ed76 Expose port 443 to proxy to port 8080 (#113) 2018-04-20 15:43:24 -07:00
Jesse Suen
338a1b826f argo login was not able to properly update boolean connection flags (insecure/plaintext) 2018-04-20 15:39:42 -07:00
Alexander Matyushentsev
b87c63c897 Re-add workaround for ksonnet bug 2018-04-20 14:39:53 -07:00
Alexander Matyushentsev
f6ed150bb7 Issue #108 - App controller incorrectly report that app is out of sync (#109)
* Load/delete app kubernetes resources parallelly to improve comparison performance

* Issue #108 - App controller incorrectly report that app is out of sync
2018-04-20 11:54:52 -07:00
Andrew Merenbach
d5c683bc76 Add syncPolicy field to application CRD (#107)
* Add syncPolicy field to application CRD

* Revert "Add syncPolicy field to application CRD"

This reverts commit da699fcb62fa691fa09a6fdc59fc21b5661f7cea.

* Add SyncPolicy to ApplicationSpec

* Fix description, thanks @jessesuen
2018-04-19 17:58:02 -07:00
Alexander Matyushentsev
3ac95f3f84 Fix null pointer error in controller (#105) 2018-04-19 16:48:32 -07:00
Jesse Suen
3be872ad32 Rework local config to support multiple servers/credentials
* Refactor local ~/.argocd/config to be similar to kube configs
* `argo login` will detect TLS issues and prompt user before writing config
* version server is unauthenticated and `argo version` will report server version
2018-04-19 11:29:56 -07:00
Andrew Merenbach
80964a79b2 Set session cookies, errors appropriately (#100)
* Set session cookies, errors appropriately

* Don't clear token response; set Secure flag only when Insecure disabled
2018-04-19 08:56:07 -07:00
Alexander Matyushentsev
e719035ea5 Allow ignoring recource deletion related errors while deleting application (#98)
* Allow ignoring recource deletion related errors while deleting application

* Rename IgnoreResourceDeletionError to Force
2018-04-18 14:27:37 -07:00
Jesse Suen
f2bcf63b26 Fix linting breakage in session mock from recent changes to session interface 2018-04-18 14:17:16 -07:00
Jesse Suen
2c9843f1a0 Update ksonnet to v0.10.0-alpha.2 2018-04-18 13:54:04 -07:00
Andrew Merenbach
0560406d81 Add server auth cookies (#96)
* Basic fleshing out

* Flesh out cookie setting/deleting some more

* Set/delete cookies and return token

* Slight tweaks

* Update cookie name, validate tokens

* Combine possible tokens into one big list

* Split tokens from cookie key names

* Tweak cookie name, thanks @jessesuen

* Change deprecated grpc.Errorf=>status.Errorf

* Rm unneeded endpoints, thanks @jessesuen

* Update protobuf files
2018-04-18 13:44:57 -07:00
Andrew Merenbach
db8083c657 Lowercase repo names before using in secret (#94) 2018-04-17 17:30:21 -07:00
Jesse Suen
fcc9f50b3f Fix issue preventing uppercased repo and cluster URLs (resolves #81) 2018-04-17 17:15:48 -07:00
Andrew Merenbach
c1ffbad8d8 Support manual token use for CLI commands (#90)
* Support manual token use for CLI commands

* Use a more functional approach to retrieving tokens

* Update Gopkg.lock

* Simplify conditional
2018-04-17 12:12:37 -07:00
Andrew Merenbach
d7cdb1a5af Convert Kubernetes errors to gRPC errors (#89)
* Add rudimentary error conversion interceptors

* Add missing errors.go file

* Move deprecated grpc.Errorf => status.Errorf
2018-04-17 11:35:13 -07:00
Andrew Merenbach
6c41ce5e08 Add session gateway (#84)
* Rebase all changes on to master

* Add additional comments, fix errors, thanks @jessesuen

* Centralize token injection, thanks @jessesuen

* Add REQUIREAUTH=1 env flag to enable authentication
2018-04-13 14:51:23 -07:00
Andrew Merenbach
685a814f38 Add argocd login command (#82)
* Add auth check and bypass for authentication

* Disallow blank passwords

* Mitigate timing attacks

* Factor out authentication/token gen code

* Tweaked token validation code to log claims

* Add missing internal gRPC client endpoints

* Add first draft of login command

* Add login command to root commands

* Get login working

* Generalize command utils for unmarshaling

* Centralize utils for CLI YAML/JSON parsing

* Read/write local config now

* Initialize map

* Revert server files for now

* Fix casing

* Restore commented test, thanks @alexmt

* No need to mitigate timing attacks on blank passwords, thanks @alexmt

* Rm redundant type declaration, thanks @alexmt

* Improve error checks

* Rm unnecessary conversion, thanks @alexmt

* Fix comment

* Don't return error when config doesn't exist
2018-04-11 17:01:58 -07:00
Alexander Matyushentsev
06b64047a4 Issue #69 - Auto-sync option in application CRD instance (#83)
* Issue #69 - Auto-sync option in application CRD instance

* Fix possible NPE error in controller

* Correctly handle situation when cluster is temporary down/unreachable by argocd

* Handle case when argo monitor cluster which go offline and recover
2018-04-11 12:53:33 -07:00
Jesse Suen
8a90b32446 Show more relevant information in argocd cluster add 2018-04-09 18:43:01 -07:00
Jesse Suen
7e47b1ebae TLS support. HTTP/HTTPS/gRPC all serving on single port 2018-04-09 16:30:30 -07:00
Alexander Matyushentsev
150b51a3ac Fix linter warning 2018-04-09 10:44:00 -07:00
Alexander Matyushentsev
0002f8db9e Issue #75 - Implement delete pod API 2018-04-09 10:40:26 -07:00
Alexander Matyushentsev
59ed50d230 Issue #74 - Implement stream logs API 2018-04-06 13:08:29 -07:00
Alexander Matyushentsev
820b4bac1a Remove obsolete pods api 2018-04-06 10:05:25 -07:00
Alexander Matyushentsev
19c5ecdbfa Check app label on client side before deleting app resource 2018-04-06 08:47:02 -07:00
Alexander Matyushentsev
66b0702c24 Issue #65 - Delete all the kube object once app is removed 2018-04-06 08:27:51 -07:00
Alexander Matyushentsev
5b5dc0efc4 Issue #67 - Application controller should persist ksonnet app parameters in app CRD (#73) 2018-04-05 14:08:47 -07:00
Alexander Matyushentsev
0febf05160 Issue #67 - Persist resources tree in application CRD (#68) 2018-04-04 21:11:05 -07:00
Jesse Suen
ee924bda6e Update ksonnet binary in image to ks tip. Begin using ksonnet as library instead of parsing stdout 2018-04-04 16:51:37 -07:00
Jesse Suen
ecfe571e75 update ksonnet dependency to tip. override some of ksonnet's dependencies 2018-04-04 14:07:44 -07:00
Jesse Suen
173ecd9397 Installer and settings management refactoring:
* Re-arrange utilities into more granular packages
* Simplify config manager interface into just Get() and Save()
* Support installation into different namespace
* Combine all secrets into single secret
* Use a hard-wired configmap name
* Admin username is no longer configurable
2018-04-04 11:38:10 -07:00
Andrew Merenbach
ba3db35ba0 Add authentication endpoints (#61)
* Add files for authentication endpoint

* Re-add MakeSessionManager function so we do not need to make key public

* Register session server, thanks @alexmt

* Pass settings to session service

* Add mocks with mockery
2018-04-02 13:55:45 -07:00
Alexander Matyushentsev
074053dac7 Update go-grpc-middleware version (#62) 2018-04-02 13:34:42 -07:00
Andrew Merenbach
6bc98f91b1 Add JWT support (#60)
* Clone cluster => session

* Add sessionmanager and tests to util

* Return token claims type

* Flesh out basic login prototyping

* Update dummy hasher to demonstrate mitigation of timing attacks

* Return error on blank password entry

* Flesh out key retrieval logic some more

* Add server signature key generation

* Don't mess about converting between strings and byte arrays

* Fix Gopkg.toml constraints

* Only update signature if it is not set yet

* Don't convert server signature to string in memory
2018-03-29 14:21:50 -07:00
93 changed files with 12120 additions and 1469 deletions

View File

@@ -32,7 +32,7 @@ spec:
value: "{{item}}"
withItems:
- dep ensure && make lint
- dep ensure && make test
- dep ensure && make test test-e2e
- name: ci-builder
inputs:

View File

@@ -1,5 +1,4 @@
# Prevent vendor directory from being copied to ensure we are not not pulling unexpected cruft from
# a user's workspace, and are only building off of what is locked by dep.
vendor
Dockerfile-argocd
dist

View File

@@ -39,22 +39,46 @@ RUN cd ${GOPATH}/src/dummy && \
mv vendor/* ${GOPATH}/src/ && \
rmdir vendor
ARG MAKE_TARGET
# Perform the build
ARG MAKE_TARGET
WORKDIR /root/go/src/github.com/argoproj/argo-cd
COPY . .
RUN make ${MAKE_TARGET}
RUN wget https://github.com/ksonnet/ksonnet/releases/download/v0.9.1/ks_0.9.1_linux_amd64.tar.gz && tar -C /tmp/ -xf ks_0.9.1_linux_amd64.tar.gz
##############################################################
# 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.10.1
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/*
ARG BINARY
COPY --from=builder /root/go/src/github.com/argoproj/argo-cd/dist/${BINARY} /${BINARY}
COPY --from=builder /tmp/ks_0.9.1_linux_amd64/ks /usr/local/bin/ks
COPY --from=cli-tooling /ks /usr/local/bin/ks
COPY --from=cli-tooling /kubectl /usr/local/bin/kubectl
# workaround ksonnet issue https://github.com/ksonnet/ksonnet/issues/298
ENV USER=root
ENV BINARY=$BINARY
CMD /$BINARY

View File

@@ -10,7 +10,12 @@ RUN curl -O https://get.docker.com/builds/Linux/x86_64/docker-1.13.1.tgz && \
go get -u gopkg.in/alecthomas/gometalinter.v2 && \
gometalinter.v2 --install
# Install kubectl
RUN curl -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
# Install ksonnet
# TODO(jessesuen): pin this once we decide on a stable version
RUN go get -v -u github.com/ksonnet/ksonnet && \
mv ${GOPATH}/bin/ksonnet /usr/local/bin/ks
env KSONNET_VERSION=0.10.1
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}

121
Gopkg.lock generated
View File

@@ -7,6 +7,14 @@
revision = "767c40d6a2e058483c25fa193e963a22da17236d"
version = "v0.18.0"
[[projects]]
name = "github.com/GeertJohan/go.rice"
packages = [
".",
"embedded"
]
revision = "c02ca9a983da5807ddf7d796784928f5be4afd09"
[[projects]]
name = "github.com/PuerkitoBio/purell"
packages = ["."]
@@ -24,12 +32,24 @@
revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f"
version = "v3.5.1"
[[projects]]
branch = "master"
name = "github.com/daaku/go.zipexe"
packages = ["."]
revision = "a5fe2436ffcb3236e175e5149162b41cd28bd27d"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
name = "github.com/emicklei/go-restful"
packages = [
@@ -39,11 +59,6 @@
revision = "26b41036311f2da8242db402557a0dbd09dc83da"
version = "v2.6.0"
[[projects]]
name = "github.com/fatih/color"
packages = ["."]
revision = "5df930a27be2502f99b292b7cc09ebad4d0891f4"
[[projects]]
name = "github.com/ghodss/yaml"
packages = ["."]
@@ -106,6 +121,7 @@
"protoc-gen-gogo/generator",
"protoc-gen-gogo/grpc",
"protoc-gen-gogo/plugin",
"protoc-gen-gogofast",
"sortkeys",
"vanity",
"vanity/command"
@@ -120,6 +136,7 @@
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
[[projects]]
branch = "master"
name = "github.com/golang/protobuf"
packages = [
"jsonpb",
@@ -132,8 +149,7 @@
"ptypes/struct",
"ptypes/timestamp"
]
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
version = "v1.0.0"
revision = "e09c5db296004fbe3f74490e84dcd62c3c5ddb1b"
[[projects]]
branch = "master"
@@ -141,24 +157,14 @@
packages = ["."]
revision = "e89373fe6b4a7413d7acd6da1725b83ef713e6e4"
[[projects]]
name = "github.com/google/go-github"
packages = ["github"]
revision = "996760c56486beb81e91bb7bdb816f8c6f29284e"
[[projects]]
name = "github.com/google/go-jsonnet"
packages = [
".",
"ast",
"parser"
]
revision = "405726fae23ace72b22c410a77b7bd825608f2c8"
[[projects]]
branch = "master"
name = "github.com/google/go-querystring"
packages = ["query"]
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
revision = "dfddf2b4e3aec377b0dcdf247ff92e7d078b8179"
[[projects]]
branch = "master"
@@ -186,15 +192,19 @@
revision = "2bcd89a1743fd4b373f7370ce8ddc14dfbd18229"
[[projects]]
branch = "bugfix/37-panic-on-err"
branch = "master"
name = "github.com/grpc-ecosystem/go-grpc-middleware"
packages = [
".",
"auth",
"logging",
"logging/logrus",
"tags"
"logging/logrus/ctxlogrus",
"tags",
"tags/logrus",
"util/metautils"
]
revision = "b9814e2370c8552db05c796c139b76a6ca45b34f"
revision = "bc372cc64f55abd91995ba3f219b380ffbc59e9d"
[[projects]]
name = "github.com/grpc-ecosystem/grpc-gateway"
@@ -247,21 +257,28 @@
[[projects]]
branch = "master"
name = "github.com/kardianos/osext"
packages = ["."]
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
[[projects]]
name = "github.com/ksonnet/ksonnet"
packages = [
"component",
"env",
"generator",
"metadata",
"metadata/app",
"metadata/lib",
"metadata/params",
"metadata/parts",
"metadata/registry",
"prototype",
"strings"
"pkg/app",
"pkg/component",
"pkg/docparser",
"pkg/lib",
"pkg/node",
"pkg/params",
"pkg/prototype",
"pkg/schema",
"pkg/util/jsonnet",
"pkg/util/kslib",
"pkg/util/strings"
]
revision = "e570bff822d88b12f1841aa6e0069713d7e066c5"
revision = "8c44a5b1545d3d03135f610170ef0167129294bc"
version = "v0.10.1"
[[projects]]
name = "github.com/ksonnet/ksonnet-lib"
@@ -274,7 +291,8 @@
"ksonnet-gen/nodemaker",
"ksonnet-gen/printer"
]
revision = "9f27c242ff65da0620f8324cc0cd50c8ef00bf5b"
revision = "d15220fdcdd07fd377894abff6276d86cb2d776d"
version = "v0.1.3"
[[projects]]
branch = "master"
@@ -286,24 +304,6 @@
]
revision = "32fa128f234d041f196a9f3e0fea5ac9772c08e1"
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
name = "github.com/pborman/uuid"
packages = ["."]
revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53"
version = "v1.1"
[[projects]]
branch = "master"
name = "github.com/petar/GoLLRB"
@@ -337,7 +337,8 @@
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "3d4380f53a34dcdc95f0c1db702615992b38d9a4"
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
name = "github.com/soheilhy/cmux"
@@ -398,7 +399,11 @@
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
packages = [
"bcrypt",
"blowfish",
"ssh/terminal"
]
revision = "432090b8f568c018896cd8a0fb0345872bbac6ce"
[[projects]]
@@ -518,6 +523,8 @@
"metadata",
"naming",
"peer",
"reflection",
"reflection/grpc_reflection_v1alpha",
"resolver",
"resolver/dns",
"resolver/passthrough",
@@ -536,10 +543,9 @@
version = "v0.9.0"
[[projects]]
branch = "v2"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
[[projects]]
branch = "release-1.9"
@@ -624,7 +630,6 @@
"pkg/util/net",
"pkg/util/runtime",
"pkg/util/sets",
"pkg/util/uuid",
"pkg/util/validation",
"pkg/util/validation/field",
"pkg/util/wait",
@@ -760,6 +765,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "683981f86c65c4c2871048e1305c2468fb7dea01ec6de3dbff52c932d8c2f803"
inputs-digest = "8e7142f84554c6f1665ef18e0fb906f82de8cd802b0211c4a46ec1ad228b8b7e"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -1,5 +1,6 @@
required = [
"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",
]
@@ -12,7 +13,8 @@ required = [
name = "github.com/grpc-ecosystem/grpc-gateway"
version = "v1.3.1"
[[constraint]]
# override ksonnet's release-1.8 dependency
[[override]]
branch = "release-1.9"
name = "k8s.io/apimachinery"
@@ -38,8 +40,9 @@ required = [
[[constraint]]
name = "github.com/ksonnet/ksonnet"
branch = "master"
version = "v0.10.1"
[[constraint]]
name = "github.com/pborman/uuid"
version = "1.1.0"
# override ksonnet's logrus dependency
[[override]]
name = "github.com/sirupsen/logrus"
version = "v1.0.3"

View File

@@ -1,6 +1,7 @@
PACKAGE=github.com/argoproj/argo-cd
CURRENT_DIR=$(shell pwd)
DIST_DIR=${CURRENT_DIR}/dist
CLI_NAME=argocd
VERSION=$(shell cat ${CURRENT_DIR}/VERSION)
BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
@@ -57,7 +58,21 @@ codegen: protogen clientgen
# This enables ease of maintenance of the yaml files.
.PHONY: cli
cli:
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/argocd ./cmd/argocd
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS} -extldflags "-static"' -o ${DIST_DIR}/${CLI_NAME} ./cmd/argocd
.PHONY: cli-linux
cli-linux:
docker build --iidfile /tmp/argocd-linux-id --target builder --build-arg MAKE_TARGET="cli IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-linux-amd64" -f Dockerfile-argocd .
docker create --name tmp-argocd-linux `cat /tmp/argocd-linux-id`
docker cp tmp-argocd-linux:/root/go/src/github.com/argoproj/argo-cd/dist/argocd-linux-amd64 dist/
docker rm tmp-argocd-linux
.PHONY: cli-darwin
cli-darwin:
docker build --iidfile /tmp/argocd-darwin-id --target builder --build-arg MAKE_TARGET="cli GOOS=darwin IMAGE_TAG=$(IMAGE_TAG) IMAGE_NAMESPACE=$(IMAGE_NAMESPACE) CLI_NAME=argocd-darwin-amd64" -f Dockerfile-argocd .
docker create --name tmp-argocd-darwin `cat /tmp/argocd-darwin-id`
docker cp tmp-argocd-darwin:/root/go/src/github.com/argoproj/argo-cd/dist/argocd-darwin-amd64 dist/
docker rm tmp-argocd-darwin
.PHONY: server
server:
@@ -86,6 +101,11 @@ controller-image:
docker build --build-arg BINARY=argocd-application-controller --build-arg MAKE_TARGET=controller -t $(IMAGE_PREFIX)argocd-application-controller:$(IMAGE_TAG) -f Dockerfile-argocd .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd-application-controller:$(IMAGE_TAG) ; fi
.PHONY: cli-image
cli-image:
docker build --build-arg BINARY=argocd --build-arg MAKE_TARGET=cli -t $(IMAGE_PREFIX)argocd-cli:$(IMAGE_TAG) -f Dockerfile-argocd .
@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) -f Dockerfile-ci-builder .
@@ -96,7 +116,11 @@ lint:
.PHONY: test
test:
go test ./...
go test `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
.PHONY: test-e2e
test-e2e:
go test ./test/e2e
.PHONY: clean
clean:
@@ -105,3 +129,10 @@ clean:
.PHONY: precheckin
precheckin: test lint
.PHONY: release-precheck
release-precheck:
@if [ "$(GIT_TREE_STATE)" != "clean" ]; then echo 'git tree state is $(GIT_TREE_STATE)' ; exit 1; fi
@if [ -z "$(GIT_TAG)" ]; then echo 'commit must be tagged to perform release' ; exit 1; fi
.PHONY: release
release: release-precheck precheckin cli-darwin cli-linux server-image controller-image repo-server-image cli-image

View File

@@ -1,3 +1,3 @@
controller: go run ./cmd/argocd-application-controller/main.go --app-resync 10
api-server: go run ./cmd/argocd-server/main.go
api-server: go run ./cmd/argocd-server/main.go --insecure
repo-server: go run ./cmd/argocd-repo-server/main.go

View File

@@ -12,7 +12,7 @@ Application deployment and lifecycle management should be automated, auditable,
## Getting Started
Follow our [getting started guide](docs/GETTING_STARTED.md).
Follow our [getting started guide](docs/getting_started.md).
## How it works
@@ -23,6 +23,10 @@ application states in the specified target environments.
![Argo CD Architecture](docs/argocd_architecture.png)
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.
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 out-of-sync.
@@ -31,7 +35,7 @@ manually sync the live state back to the desired target state. Any modifications
target state in the git repo can be automatically applied and reflected in the specified target
environments.
For additional details, see [architecture overview](docs/ARCHITECTURE.md).
For additional details, see [architecture overview](docs/architecture.md).
## Features

View File

@@ -1 +1 @@
0.1.0
0.4.0

View File

@@ -10,6 +10,7 @@ import (
"github.com/argoproj/argo-cd/controller"
"github.com/argoproj/argo-cd/errors"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/server/cluster"
apirepository "github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/util/cli"
@@ -21,7 +22,6 @@ import (
// load the gcp plugin (required to authenticate against GKE clusters).
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
// load the oidc plugin (required to authenticate with OpenID Connect).
"github.com/argoproj/argo-cd/reposerver"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)
@@ -29,7 +29,7 @@ const (
// CLIName is the name of the CLI
cliName = "argocd-application-controller"
// Default time in seconds for application resync period
defaultAppResyncPeriod = 600
defaultAppResyncPeriod = 180
)
func newCommand() *cobra.Command {
@@ -37,11 +37,17 @@ func newCommand() *cobra.Command {
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
repoServerAddress string
workers int
logLevel string
)
var command = cobra.Command{
Use: cliName,
Short: "application-controller is a controller to operate on applications CRD",
RunE: func(c *cobra.Command, args []string) error {
level, err := log.ParseLevel(logLevel)
errors.CheckError(err)
log.SetLevel(level)
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
@@ -58,14 +64,17 @@ func newCommand() *cobra.Command {
}
resyncDuration := time.Duration(appResyncPeriod) * time.Second
apiRepoServer := apirepository.NewServer(namespace, kubeClient, appClient)
apiClusterServer := cluster.NewServer(namespace, kubeClient, appClient)
clusterService := cluster.NewServer(namespace, kubeClient, appClient)
appComparator := controller.NewKsonnetAppComparator(clusterService)
appController := controller.NewApplicationController(
namespace,
kubeClient,
appClient,
reposerver.NewRepositoryServerClientset(repoServerAddress),
apiRepoServer,
apiClusterServer,
appComparator,
resyncDuration,
&controllerConfig)
@@ -74,7 +83,7 @@ func newCommand() *cobra.Command {
defer cancel()
log.Infof("Application Controller (version: %s) starting (namespace: %s)", argocd.GetVersion(), namespace)
go appController.Run(ctx, 1)
go appController.Run(ctx, workers)
// Wait forever
select {}
},
@@ -83,6 +92,8 @@ func newCommand() *cobra.Command {
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().Int64Var(&appResyncPeriod, "app-resync", defaultAppResyncPeriod, "Time period in seconds for application resync.")
command.Flags().StringVar(&repoServerAddress, "repo-server", "localhost:8081", "Repo server address.")
command.Flags().IntVar(&workers, "workers", 1, "Number of application workers")
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
return &command
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/ksonnet"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
@@ -55,7 +56,11 @@ func newCommand() *cobra.Command {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
errors.CheckError(err)
ksVers, err := ksonnet.KsonnetVersion()
errors.CheckError(err)
log.Infof("argocd-repo-server %s serving on %s (namespace: %s)", argocd.GetVersion(), listener.Addr(), namespace)
log.Infof("ksonnet version: %s", ksVers)
err = grpc.Serve(listener)
errors.CheckError(err)
return nil

View File

@@ -15,11 +15,12 @@ import (
// NewCommand returns a new instance of an argocd command
func NewCommand() *cobra.Command {
var (
insecure bool
logLevel string
clientConfig clientcmd.ClientConfig
staticAssetsDir string
repoServerAddress string
configMapName string
disableAuth bool
)
var command = &cobra.Command{
Use: cliName,
@@ -40,16 +41,26 @@ func NewCommand() *cobra.Command {
appclientset := appclientset.NewForConfigOrDie(config)
repoclientset := reposerver.NewRepositoryServerClientset(repoServerAddress)
argocd := server.NewServer(kubeclientset, appclientset, repoclientset, namespace, staticAssetsDir, configMapName)
argoCDOpts := server.ArgoCDServerOpts{
Insecure: insecure,
Namespace: namespace,
StaticAssetsDir: staticAssetsDir,
KubeClientset: kubeclientset,
AppClientset: appclientset,
RepoClientset: repoclientset,
DisableAuth: disableAuth,
}
argocd := server.NewServer(argoCDOpts)
argocd.Run()
},
}
clientConfig = cli.AddKubectlFlagsToCmd(command)
command.Flags().BoolVar(&insecure, "insecure", false, "Run server without TLS")
command.Flags().StringVar(&staticAssetsDir, "staticassets", "", "Static assets directory path")
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
command.Flags().StringVar(&repoServerAddress, "repo-server", "localhost:8081", "Repo server address.")
command.Flags().StringVar(&configMapName, "config-map", "", "Name of a Kubernetes config map to use.")
command.Flags().BoolVar(&disableAuth, "disable-auth", false, "Disable client authentication")
command.AddCommand(cli.NewVersionCmd(cliName))
return command
}

View File

@@ -3,9 +3,10 @@ package commands
import (
"context"
"fmt"
"log"
"net/url"
"os"
"strconv"
"strings"
"text/tabwriter"
"github.com/argoproj/argo-cd/errors"
@@ -13,8 +14,11 @@ import (
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/server/application"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/diff"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/yudai/gojsondiff/formatter"
"golang.org/x/crypto/ssh/terminal"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -24,36 +28,35 @@ import (
func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "app",
Short: fmt.Sprintf("%s app COMMAND", cliName),
Short: "Manage applications",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
}
command.AddCommand(NewApplicationAddCommand(clientOpts))
command.AddCommand(NewApplicationCreateCommand(clientOpts))
command.AddCommand(NewApplicationGetCommand(clientOpts))
command.AddCommand(NewApplicationDiffCommand(clientOpts))
command.AddCommand(NewApplicationSetCommand(clientOpts))
command.AddCommand(NewApplicationSyncCommand(clientOpts))
command.AddCommand(NewApplicationHistoryCommand(clientOpts))
command.AddCommand(NewApplicationRollbackCommand(clientOpts))
command.AddCommand(NewApplicationListCommand(clientOpts))
command.AddCommand(NewApplicationRemoveCommand(clientOpts))
command.AddCommand(NewApplicationDeleteCommand(clientOpts))
return command
}
// NewApplicationAddCommand returns a new instance of an `argocd app add` command
func NewApplicationAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
// NewApplicationCreateCommand returns a new instance of an `argocd app create` command
func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
fileURL string
repoURL string
appPath string
appName string
env string
destServer string
destNamespace string
appOpts appOptions
fileURL string
appName string
syncPolicy string
)
var command = &cobra.Command{
Use: "add",
Short: fmt.Sprintf("%s app add", cliName),
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)
@@ -61,59 +64,58 @@ func NewApplicationAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
}
var app argoappv1.Application
if fileURL != "" {
var (
fileContents []byte
err error
)
_, err = url.ParseRequestURI(fileURL)
_, err := url.ParseRequestURI(fileURL)
if err != nil {
fileContents, err = readLocalFile(fileURL)
err = cli.UnmarshalLocalFile(fileURL, &app)
} else {
fileContents, err = readRemoteFile(fileURL)
err = cli.UnmarshalRemoteFile(fileURL, &app)
}
if err != nil {
log.Fatal(err)
}
unmarshalApplication(fileContents, &app)
} else {
// all these params are required if we're here
if repoURL == "" || appPath == "" || appName == "" {
if syncPolicy != "" && syncPolicy != "Always" {
c.HelpFunc()(c, args)
os.Exit(1)
}
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{
Name: appName,
},
Spec: argoappv1.ApplicationSpec{
Source: argoappv1.ApplicationSource{
RepoURL: repoURL,
Path: appPath,
Environment: env,
RepoURL: appOpts.repoURL,
Path: appOpts.appPath,
Environment: appOpts.env,
TargetRevision: appOpts.revision,
},
SyncPolicy: syncPolicy,
},
}
}
if destServer != "" || destNamespace != "" {
if appOpts.destServer != "" || appOpts.destNamespace != "" {
app.Spec.Destination = &argoappv1.ApplicationDestination{
Server: destServer,
Namespace: destNamespace,
Server: appOpts.destServer,
Namespace: appOpts.destNamespace,
}
}
setParameterOverrides(&app, appOpts.parameters)
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
_, err := appIf.Create(context.Background(), &app)
created, err := appIf.Create(context.Background(), &app)
errors.CheckError(err)
fmt.Printf("application '%s' created\n", created.ObjectMeta.Name)
},
}
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")
command.Flags().StringVar(&repoURL, "repo", "", "Repository URL, ignored if a file is set")
command.Flags().StringVar(&appPath, "path", "", "Path in repository to the ksonnet app directory, ignored if a file is set")
command.Flags().StringVar(&env, "env", "", "Application environment to monitor")
command.Flags().StringVar(&destServer, "dest-server", "", "K8s cluster URL (overrides the server URL specified in the ksonnet app.yaml)")
command.Flags().StringVar(&destNamespace, "dest-namespace", "", "K8s target namespace (overrides the namespace specified in the ksonnet app.yaml)")
addAppFlags(command, &appOpts)
//command.Flags().StringVar(&syncPolicy, "sync-policy", "", "Synchronization policy for application (e.g., Always)")
return command
}
@@ -121,7 +123,7 @@ func NewApplicationAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "get",
Short: fmt.Sprintf("%s app get APPNAME", cliName),
Short: "Get application details",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
@@ -167,11 +169,87 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
return command
}
// NewApplicationSetCommand returns a new instance of an `argocd app set` command
func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
appOpts appOptions
)
var command = &cobra.Command{
Use: "set",
Short: "Set application parameters",
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
app, err := appIf.Get(context.Background(), &application.ApplicationQuery{Name: appName})
errors.CheckError(err)
visited := 0
c.Flags().Visit(func(f *pflag.Flag) {
visited++
switch f.Name {
case "repo":
app.Spec.Source.RepoURL = appOpts.repoURL
case "path":
app.Spec.Source.Path = appOpts.appPath
case "env":
app.Spec.Source.Environment = appOpts.env
case "revision":
app.Spec.Source.TargetRevision = appOpts.revision
case "dest-server":
if app.Spec.Destination == nil {
app.Spec.Destination = &argoappv1.ApplicationDestination{}
}
app.Spec.Destination.Server = appOpts.destServer
case "dest-namespace":
if app.Spec.Destination == nil {
app.Spec.Destination = &argoappv1.ApplicationDestination{}
}
app.Spec.Destination.Namespace = appOpts.destNamespace
}
})
if visited == 0 {
log.Error("Please set at least one option to update")
c.HelpFunc()(c, args)
os.Exit(1)
}
setParameterOverrides(app, appOpts.parameters)
_, err = appIf.Update(context.Background(), app)
errors.CheckError(err)
},
}
addAppFlags(command, &appOpts)
return command
}
type appOptions struct {
repoURL string
appPath string
env string
revision string
destServer string
destNamespace string
parameters []string
}
func addAppFlags(command *cobra.Command, opts *appOptions) {
command.Flags().StringVar(&opts.repoURL, "repo", "", "Repository URL, ignored if a file is set")
command.Flags().StringVar(&opts.appPath, "path", "", "Path in repository to the ksonnet app directory, ignored if a file is set")
command.Flags().StringVar(&opts.env, "env", "", "Application environment to monitor")
command.Flags().StringVar(&opts.revision, "revision", "HEAD", "The tracking source branch, tag, or commit the application will sync to")
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)")
}
// NewApplicationDiffCommand returns a new instance of an `argocd app diff` command
func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "diff",
Short: fmt.Sprintf("%s app diff APPNAME", cliName),
Short: "Perform a diff against the target and live state",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
@@ -206,11 +284,14 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
return command
}
// NewApplicationRemoveCommand returns a new instance of an `argocd app list` command
func NewApplicationRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
// NewApplicationDeleteCommand returns a new instance of an `argocd app delete` command
func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
force bool
)
var command = &cobra.Command{
Use: "rm",
Short: fmt.Sprintf("%s app rm APPNAME", cliName),
Use: "delete",
Short: "Delete an application",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
@@ -219,19 +300,24 @@ func NewApplicationRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
for _, appName := range args {
_, err := appIf.Delete(context.Background(), &application.DeleteApplicationRequest{Name: appName})
appDeleteReq := application.DeleteApplicationRequest{
Name: appName,
Force: force,
}
_, err := appIf.Delete(context.Background(), &appDeleteReq)
errors.CheckError(err)
}
},
}
command.Flags().BoolVar(&force, "force", false, "Force delete application even if cascaded deletion unsuccessful")
return command
}
// NewApplicationListCommand returns a new instance of an `argocd app rm` command
// NewApplicationListCommand returns a new instance of an `argocd app list` command
func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "list",
Short: fmt.Sprintf("%s app list", cliName),
Short: "List applications",
Run: func(c *cobra.Command, args []string) {
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
@@ -262,13 +348,15 @@ func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
// NewApplicationSyncCommand returns a new instance of an `argocd app sync` command
func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
dryRun bool
revision string
prune bool
dryRun bool
)
var command = &cobra.Command{
Use: "sync",
Short: fmt.Sprintf("%s app sync APPNAME", cliName),
Short: "Sync an application to its target state",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
@@ -276,8 +364,10 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
defer util.Close(conn)
appName := args[0]
syncReq := application.ApplicationSyncRequest{
Name: appName,
DryRun: dryRun,
Name: appName,
DryRun: dryRun,
Revision: revision,
Prune: prune,
}
syncRes, err := appIf.Sync(context.Background(), &syncReq)
errors.CheckError(err)
@@ -291,5 +381,132 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
},
}
command.Flags().BoolVar(&dryRun, "dry-run", false, "Preview apply without affecting cluster")
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
command.Flags().StringVar(&revision, "revision", "", "Sync to a specific revision. Preserves parameter overrides")
return command
}
// setParameterOverrides updates an existing or appends a new parameter override in the application
func setParameterOverrides(app *argoappv1.Application, parameters []string) {
if len(parameters) == 0 {
return
}
var newParams []argoappv1.ComponentParameter
if len(app.Spec.Source.ComponentParameterOverrides) > 0 {
newParams = app.Spec.Source.ComponentParameterOverrides
} else {
newParams = make([]argoappv1.ComponentParameter, 0)
}
for _, paramStr := range parameters {
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 {
if cp.Component == newParam.Component && cp.Name == newParam.Name {
index = i
break
}
}
if index == -1 {
newParams = append(newParams, newParam)
} else {
newParams[index] = newParam
}
}
app.Spec.Source.ComponentParameterOverrides = newParams
}
// NewApplicationHistoryCommand returns a new instance of an `argocd app history` command
func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "history",
Short: "Show application deployment history",
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
appName := args[0]
app, err := appIf.Get(context.Background(), &application.ApplicationQuery{Name: appName})
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "ID\tDATE\tCOMMIT\tPARAMETERS\n")
for _, depInfo := range app.Status.RecentDeployments {
paramStr := paramString(depInfo.Params)
fmt.Fprintf(w, "%d\t%s\t%s\t%s\n", depInfo.ID, depInfo.DeployedAt, depInfo.Revision, paramStr)
}
_ = w.Flush()
},
}
return command
}
func paramString(params []argoappv1.ComponentParameter) string {
if len(params) == 0 {
return ""
}
paramNames := []string{}
for _, param := range params {
paramNames = append(paramNames, fmt.Sprintf("%s=%s=%s", param.Component, param.Name, param.Value))
}
return strings.Join(paramNames, ",")
}
// NewApplicationRollbackCommand returns a new instance of an `argocd app rollback` command
func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
prune bool
)
var command = &cobra.Command{
Use: "rollback",
Short: "Rollback application to a previous deployed version",
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
depID, err := strconv.Atoi(args[1])
errors.CheckError(err)
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
ctx := context.Background()
app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: appName})
errors.CheckError(err)
var depInfo *argoappv1.DeploymentInfo
for _, di := range app.Status.RecentDeployments {
if di.ID == int64(depID) {
depInfo = &di
break
}
}
if depInfo == nil {
log.Fatalf("Application '%s' does not have deployment id '%d' in history\n", app.ObjectMeta.Name, depID)
}
syncRes, err := appIf.Rollback(ctx, &application.ApplicationRollbackRequest{
Name: appName,
ID: int64(depID),
Prune: prune,
})
errors.CheckError(err)
fmt.Printf("%s %s\n", appName, syncRes.Message)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "NAME\tKIND\tMESSAGE\n")
for _, resDetails := range syncRes.Resources {
fmt.Fprintf(w, "%s\t%s\t%s\n", resDetails.Name, resDetails.Kind, resDetails.Message)
}
_ = w.Flush()
},
}
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
return command
}

View File

@@ -26,7 +26,7 @@ import (
func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command {
var command = &cobra.Command{
Use: "cluster",
Short: fmt.Sprintf("%s cluster COMMAND", cliName),
Short: "Manage cluster credentials",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
@@ -49,7 +49,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
var configAccess clientcmd.ConfigAccess = pathOpts
if len(args) == 0 {
log.Error("Choose a context name from:")
printContexts(configAccess)
printKubeContexts(configAccess)
os.Exit(1)
}
config, err := configAccess.GetStartingConfig()
@@ -65,14 +65,12 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
if conf.Username == "" || conf.Password == "" {
// Install RBAC resources for managing the cluster if username and password are not specified
conf.BearerToken = common.InstallClusterManagerRBAC(conf)
}
// 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)
clst := NewCluster(args[0], conf, managerBearerToken)
clst, err = clusterIf.Create(context.Background(), clst)
errors.CheckError(err)
fmt.Printf("Cluster '%s' added\n", clst.Name)
@@ -82,12 +80,12 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
return command
}
func printContexts(ca clientcmd.ConfigAccess) {
func printKubeContexts(ca clientcmd.ConfigAccess) {
config, err := ca.GetStartingConfig()
errors.CheckError(err)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
defer func() { _ = w.Flush() }()
columnNames := []string{"CURRENT", "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE"}
columnNames := []string{"CURRENT", "NAME", "CLUSTER", "SERVER"}
_, err = fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t"))
errors.CheckError(err)
@@ -100,16 +98,17 @@ func printContexts(ca clientcmd.ConfigAccess) {
for _, name := range contextNames {
context := config.Contexts[name]
cluster := config.Clusters[context.Cluster]
prefix := " "
if config.CurrentContext == name {
prefix = "*"
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", prefix, name, context.Cluster, context.AuthInfo, context.Namespace)
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", prefix, name, context.Cluster, cluster.Server)
errors.CheckError(err)
}
}
func NewCluster(name string, conf *rest.Config) *argoappv1.Cluster {
func NewCluster(name string, conf *rest.Config, managerBearerToken string) *argoappv1.Cluster {
tlsClientConfig := argoappv1.TLSClientConfig{
Insecure: conf.TLSClientConfig.Insecure,
ServerName: conf.TLSClientConfig.ServerName,
@@ -136,9 +135,7 @@ func NewCluster(name string, conf *rest.Config) *argoappv1.Cluster {
Server: conf.Host,
Name: name,
Config: argoappv1.ClusterConfig{
Username: conf.Username,
Password: conf.Password,
BearerToken: conf.BearerToken,
BearerToken: managerBearerToken,
TLSClientConfig: tlsClientConfig,
},
}
@@ -149,7 +146,7 @@ func NewCluster(name string, conf *rest.Config) *argoappv1.Cluster {
func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "get",
Short: fmt.Sprintf("%s cluster get SERVER", cliName),
Short: "Get cluster information",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
@@ -173,7 +170,7 @@ func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rm",
Short: fmt.Sprintf("%s cluster rm SERVER", cliName),
Short: "Remove cluster credentials",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
@@ -196,7 +193,7 @@ func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "list",
Short: fmt.Sprintf("%s cluster list", cliName),
Short: "List configured clusters",
Run: func(c *cobra.Command, args []string) {
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)

View File

@@ -0,0 +1,84 @@
package commands
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"text/tabwriter"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/util/localconfig"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// NewContextCommand returns a new instance of an `argocd ctx` command
func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "context",
Aliases: []string{"ctx"},
Short: "Switch between contexts",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
printArgoCDContexts(clientOpts.ConfigPath)
return
}
ctxName := args[0]
argoCDDir, err := localconfig.DefaultConfigDir()
errors.CheckError(err)
prevCtxFile := path.Join(argoCDDir, ".prev-ctx")
if ctxName == "-" {
prevCtxBytes, err := ioutil.ReadFile(prevCtxFile)
errors.CheckError(err)
ctxName = string(prevCtxBytes)
}
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)
if localCfg.CurrentContext == ctxName {
fmt.Printf("Already at context '%s'\n", localCfg.CurrentContext)
return
}
if _, err = localCfg.ResolveContext(ctxName); err != nil {
log.Fatal(err)
}
prevCtx := localCfg.CurrentContext
localCfg.CurrentContext = ctxName
err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
errors.CheckError(err)
err = ioutil.WriteFile(prevCtxFile, []byte(prevCtx), 0644)
errors.CheckError(err)
fmt.Printf("Switched to context '%s'\n", localCfg.CurrentContext)
},
}
return command
}
func printArgoCDContexts(configPath string) {
localCfg, err := localconfig.ReadLocalConfig(configPath)
errors.CheckError(err)
if localCfg == nil {
log.Fatalf("No contexts defined in %s", configPath)
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
defer func() { _ = w.Flush() }()
columnNames := []string{"CURRENT", "NAME", "SERVER"}
_, err = fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t"))
errors.CheckError(err)
for _, contextRef := range localCfg.Contexts {
context, err := localCfg.ResolveContext(contextRef.Name)
if err != nil {
log.Warnf("Context '%s' had error: %v", contextRef.Name, err)
}
prefix := " "
if localCfg.CurrentContext == context.Name {
prefix = "*"
}
_, err = fmt.Fprintf(w, "%s\t%s\t%s\n", prefix, context.Name, context.Server.Server)
errors.CheckError(err)
}
}

View File

@@ -21,6 +21,11 @@ func NewInstallCommand() *cobra.Command {
Run: func(c *cobra.Command, args []string) {
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, wasSpecified, err := clientConfig.Namespace()
errors.CheckError(err)
if wasSpecified {
installOpts.Namespace = namespace
}
installer, err := install.NewInstaller(conf, installOpts)
errors.CheckError(err)
installer.Install()
@@ -29,8 +34,8 @@ func NewInstallCommand() *cobra.Command {
command.Flags().BoolVar(&installOpts.Upgrade, "upgrade", false, "upgrade controller/ui deployments and configmap if already installed")
command.Flags().BoolVar(&installOpts.DryRun, "dry-run", false, "print the kubernetes manifests to stdout instead of installing")
command.Flags().BoolVar(&installOpts.ConfigSuperuser, "config-superuser", false, "create or update a superuser username and password")
command.Flags().BoolVar(&installOpts.CreateSignature, "create-signature", false, "create or update the server-side token signing signature")
command.Flags().StringVar(&installOpts.ConfigMap, "config-map", "", "apply settings from a Kubernetes config map")
command.Flags().StringVar(&installOpts.Namespace, "install-namespace", install.DefaultInstallNamespace, "install into a specific namespace")
command.Flags().StringVar(&installOpts.ControllerImage, "controller-image", install.DefaultControllerImage, "use a specified controller image")
command.Flags().StringVar(&installOpts.ServerImage, "server-image", install.DefaultServerImage, "use a specified api server image")
command.Flags().StringVar(&installOpts.UIImage, "ui-image", install.DefaultUIImage, "use a specified ui image")

View File

@@ -0,0 +1,124 @@
package commands
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/server/session"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/localconfig"
"github.com/spf13/cobra"
)
// NewLoginCommand returns a new instance of `argocd login` command
func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
name string
username string
password string
)
var command = &cobra.Command{
Use: "login SERVER",
Short: "Log in to Argo CD",
Long: "Log in to Argo CD",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
server := args[0]
tlsTestResult, err := grpc_util.TestTLS(server)
errors.CheckError(err)
if !tlsTestResult.TLS {
if !globalClientOpts.PlainText {
askToProceed("WARNING: server is not configured with TLS. Proceed (y/n)? ")
globalClientOpts.PlainText = true
}
} else if tlsTestResult.InsecureErr != nil {
if !globalClientOpts.Insecure {
askToProceed(fmt.Sprintf("WARNING: server certificate had error: %s. Proceed insecurely (y/n)? ", tlsTestResult.InsecureErr))
globalClientOpts.Insecure = true
}
}
username, password = cli.PromptCredentials(username, password)
clientOpts := argocdclient.ClientOptions{
ConfigPath: "",
ServerAddr: server,
Insecure: globalClientOpts.Insecure,
PlainText: globalClientOpts.PlainText,
}
conn, sessionIf := argocdclient.NewClientOrDie(&clientOpts).NewSessionClientOrDie()
defer util.Close(conn)
sessionRequest := session.SessionCreateRequest{
Username: username,
Password: password,
}
createdSession, err := sessionIf.Create(context.Background(), &sessionRequest)
errors.CheckError(err)
fmt.Printf("user %q logged in successfully\n", username)
// login successful. Persist the config
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
errors.CheckError(err)
if localCfg == nil {
localCfg = &localconfig.LocalConfig{}
}
localCfg.UpsertServer(localconfig.Server{
Server: server,
PlainText: globalClientOpts.PlainText,
Insecure: globalClientOpts.Insecure,
})
localCfg.UpsertUser(localconfig.User{
Name: server,
AuthToken: createdSession.Token,
})
if name == "" {
name = server
}
localCfg.CurrentContext = name
localCfg.UpsertContext(localconfig.ContextRef{
Name: name,
User: server,
Server: server,
})
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
errors.CheckError(err)
},
}
command.Flags().StringVar(&name, "name", "", "name to use for the context")
command.Flags().StringVar(&username, "username", "", "the username of an account to authenticate")
command.Flags().StringVar(&password, "password", "", "the password of an account to authenticate")
return command
}
func askToProceed(message string) {
proceed := ""
acceptedAnswers := map[string]bool{
"y": true,
"yes": true,
"n": true,
"no": true,
}
for !acceptedAnswers[proceed] {
fmt.Print(message)
reader := bufio.NewReader(os.Stdin)
proceedRaw, err := reader.ReadString('\n')
errors.CheckError(err)
proceed = strings.TrimSpace(proceedRaw)
}
if proceed == "no" || proceed == "n" {
os.Exit(1)
}
}

View File

@@ -1,12 +1,10 @@
package commands
import (
"bufio"
"context"
"fmt"
"io/ioutil"
"os"
"syscall"
"text/tabwriter"
"github.com/argoproj/argo-cd/errors"
@@ -14,17 +12,17 @@ import (
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
// NewRepoCommand returns a new instance of an `argocd repo` command
func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "repo",
Short: fmt.Sprintf("%s repo COMMAND", cliName),
Short: "Manage git repository credentials",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
@@ -45,7 +43,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
)
var command = &cobra.Command{
Use: "add",
Short: fmt.Sprintf("%s repo add REPO", cliName),
Short: "Add git repository credentials",
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
@@ -66,7 +64,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
log.Fatal(err)
}
// If we can't test the repo, it's probably private. Prompt for credentials and try again.
promptCredentials(&repo)
repo.Username, repo.Password = cli.PromptCredentials(repo.Username, repo.Password)
err = git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey)
}
errors.CheckError(err)
@@ -83,25 +81,11 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
return command
}
func promptCredentials(repo *appsv1.Repository) {
reader := bufio.NewReader(os.Stdin)
if repo.Username == "" {
fmt.Print("Username: ")
username, _ := reader.ReadString('\n')
repo.Username = username
}
if repo.Password == "" {
fmt.Print("Password: ")
bytePassword, _ := terminal.ReadPassword(syscall.Stdin)
repo.Password = string(bytePassword)
}
}
// NewRepoRemoveCommand returns a new instance of an `argocd repo list` command
func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rm",
Short: fmt.Sprintf("%s repo rm REPO", cliName),
Short: "Remove git repository credentials",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
@@ -122,7 +106,7 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "list",
Short: fmt.Sprintf("%s repo list", cliName),
Short: "List configured repositories",
Run: func(c *cobra.Command, args []string) {
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)

View File

@@ -1,8 +1,9 @@
package commands
import (
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/localconfig"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
)
@@ -22,15 +23,23 @@ func NewCommand() *cobra.Command {
},
}
command.AddCommand(cli.NewVersionCmd(cliName))
command.AddCommand(NewVersionCmd(&clientOpts))
command.AddCommand(NewClusterCommand(&clientOpts, pathOpts))
command.AddCommand(NewApplicationCommand(&clientOpts))
command.AddCommand(NewLoginCommand(&clientOpts))
command.AddCommand(NewRepoCommand(&clientOpts))
command.AddCommand(NewInstallCommand())
command.AddCommand(NewUninstallCommand())
command.AddCommand(NewContextCommand(&clientOpts))
defaultLocalConfigPath, err := localconfig.DefaultLocalConfigPath()
errors.CheckError(err)
command.PersistentFlags().StringVar(&clientOpts.ConfigPath, "config", defaultLocalConfigPath, "Path to ArgoCD config")
command.PersistentFlags().StringVar(&clientOpts.ServerAddr, "server", "", "ArgoCD server address")
command.PersistentFlags().BoolVar(&clientOpts.Insecure, "insecure", true, "Disable transport security for the client connection")
command.PersistentFlags().BoolVar(&clientOpts.PlainText, "plaintext", false, "Disable TLS")
command.PersistentFlags().BoolVar(&clientOpts.Insecure, "insecure", false, "Skip server certificate and domain verification")
command.PersistentFlags().StringVar(&clientOpts.CertFile, "server-crt", "", "Server certificate file")
command.PersistentFlags().StringVar(&clientOpts.AuthToken, "auth-token", "", "Authentication token")
return command
}

View File

@@ -21,12 +21,16 @@ func NewUninstallCommand() *cobra.Command {
Run: func(c *cobra.Command, args []string) {
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, wasSpecified, err := clientConfig.Namespace()
errors.CheckError(err)
if wasSpecified {
installOpts.Namespace = namespace
}
installer, err := install.NewInstaller(conf, installOpts)
errors.CheckError(err)
installer.Uninstall()
},
}
command.Flags().StringVar(&installOpts.Namespace, "install-namespace", install.DefaultInstallNamespace, "uninstall from a specific namespace")
clientConfig = cli.AddKubectlFlagsToCmd(command)
return command
}

View File

@@ -1,49 +0,0 @@
package commands
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/ghodss/yaml"
)
// unmarshalApplication tries to convert a YAML or JSON byte array into an Application struct.
func unmarshalApplication(data []byte, app *argoappv1.Application) {
// first, try unmarshaling as JSON
// Based on technique from Kubectl, which supports both YAML and JSON:
// https://mlafeldt.github.io/blog/teaching-go-programs-to-love-json-and-yaml/
// http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/
// Short version: JSON unmarshaling won't zero out null fields; YAML unmarshaling will.
// This may have unintended effects or hard-to-catch issues when populating our application object.
data, err := yaml.YAMLToJSON(data)
if err != nil {
log.Fatal("Could not decode valid JSON or YAML Kubernetes manifest")
}
err = json.Unmarshal(data, &app)
if err != nil {
log.Fatalf("Could not unmarshal Kubernetes manifest: %s", string(data))
}
}
// readLocalFile reads a file from disk and returns its contents as a byte array.
// The caller is responsible for checking error return values.
func readLocalFile(path string) (data []byte, err error) {
data, err = ioutil.ReadFile(path)
return
}
// readRemoteFile issues a GET request to retrieve the contents of the specified URL as a byte array.
// The caller is responsible for checking error return values.
func readRemoteFile(url string) (data []byte, err error) {
resp, err := http.Get(url)
if err == nil {
defer func() {
_ = resp.Body.Close()
}()
data, err = ioutil.ReadAll(resp.Body)
}
return
}

View File

@@ -1,64 +0,0 @@
package commands
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"testing"
)
func TestReadLocalFile(t *testing.T) {
sentinel := "Hello, world!"
file, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
panic(err)
}
defer func() {
_ = os.Remove(file.Name())
}()
_, _ = file.WriteString(sentinel)
_ = file.Sync()
data, err := readLocalFile(file.Name())
if string(data) != sentinel {
t.Errorf("Test data did not match (err = %v)! Expected \"%s\" and received \"%s\"", err, sentinel, string(data))
}
}
func TestReadRemoteFile(t *testing.T) {
sentinel := "Hello, world!"
serve := func(c chan<- string) {
// listen on first available dynamic (unprivileged) port
listener, err := net.Listen("tcp", ":0")
if err != nil {
panic(err)
}
// send back the address so that it can be used
c <- listener.Addr().String()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// return the sentinel text at root URL
fmt.Fprint(w, sentinel)
})
panic(http.Serve(listener, nil))
}
c := make(chan string, 1)
// run a local webserver to test data retrieval
go serve(c)
address := <-c
data, err := readRemoteFile("http://" + address)
t.Logf("Listening at address: %s", address)
if string(data) != sentinel {
t.Errorf("Test data did not match (err = %v)! Expected \"%s\" and received \"%s\"", err, sentinel, string(data))
}
}

View File

@@ -0,0 +1,64 @@
package commands
import (
"context"
"fmt"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/util"
"github.com/golang/protobuf/ptypes/empty"
"github.com/spf13/cobra"
)
// NewVersionCmd returns a new `version` command to be used as a sub-command to root
func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var short bool
var client bool
versionCmd := cobra.Command{
Use: "version",
Short: fmt.Sprintf("Print version information"),
Run: func(cmd *cobra.Command, args []string) {
version := argocd.GetVersion()
fmt.Printf("%s: %s\n", cliName, version)
if !short {
fmt.Printf(" BuildDate: %s\n", version.BuildDate)
fmt.Printf(" GitCommit: %s\n", version.GitCommit)
fmt.Printf(" GitTreeState: %s\n", version.GitTreeState)
if version.GitTag != "" {
fmt.Printf(" GitTag: %s\n", version.GitTag)
}
fmt.Printf(" GoVersion: %s\n", version.GoVersion)
fmt.Printf(" Compiler: %s\n", version.Compiler)
fmt.Printf(" Platform: %s\n", version.Platform)
}
if client {
return
}
// Get Server version
conn, versionIf := argocdclient.NewClientOrDie(clientOpts).NewVersionClientOrDie()
defer util.Close(conn)
serverVers, err := versionIf.Version(context.Background(), &empty.Empty{})
errors.CheckError(err)
fmt.Printf("%s: %s\n", "argocd-server", serverVers.Version)
if !short {
fmt.Printf(" BuildDate: %s\n", serverVers.BuildDate)
fmt.Printf(" GitCommit: %s\n", serverVers.GitCommit)
fmt.Printf(" GitTreeState: %s\n", serverVers.GitTreeState)
if version.GitTag != "" {
fmt.Printf(" GitTag: %s\n", serverVers.GitTag)
}
fmt.Printf(" GoVersion: %s\n", serverVers.GoVersion)
fmt.Printf(" Compiler: %s\n", serverVers.Compiler)
fmt.Printf(" Platform: %s\n", serverVers.Platform)
}
},
}
versionCmd.Flags().BoolVar(&short, "short", false, "print just the version number")
versionCmd.Flags().BoolVar(&client, "client", false, "client version only (no server required)")
return &versionCmd
}

View File

@@ -16,6 +16,12 @@ const (
SecretTypeCluster = "cluster"
)
const (
ArgoCDAdminUsername = "admin"
ArgoCDSecretName = "argocd-secret"
ArgoCDConfigMapName = "argocd-cm"
)
var (
// LabelKeyAppInstance refers to the application instance resource name
LabelKeyAppInstance = MetadataPrefix + "/app-instance"
@@ -25,6 +31,7 @@ var (
// LabelKeyApplicationControllerInstanceID is the label which allows to separate application among multiple running application controllers.
LabelKeyApplicationControllerInstanceID = application.ApplicationFullName + "/controller-instanceid"
// LabelApplicationName is the label which indicates that resource belongs to application with the specified name
LabelApplicationName = application.ApplicationFullName + "/app-name"
)
@@ -44,46 +51,3 @@ var ArgoCDManagerPolicyRules = []rbacv1.PolicyRule{
Verbs: []string{"*"},
},
}
const (
ArgoCDServerServiceAccount = "argocd-server"
ArgoCDServerRole = "argocd-server-role"
ArgoCDServerRoleBinding = "argocd-server-role-binding"
)
var ArgoCDServerPolicyRules = []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods", "pods/exec", "pods/log"},
Verbs: []string{"get", "list", "watch"},
},
{
APIGroups: []string{""},
Resources: []string{"secrets"},
Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"},
},
{
APIGroups: []string{"argoproj.io"},
Resources: []string{"applications"},
Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"},
},
}
const (
ApplicationControllerServiceAccount = "application-controller"
ApplicationControllerRole = "application-controller-role"
ApplicationControllerRoleBinding = "application-controller-role-binding"
)
var ApplicationControllerPolicyRules = []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"secrets"},
Verbs: []string{"get"},
},
{
APIGroups: []string{"argoproj.io"},
Resources: []string{"applications"},
Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"},
},
}

View File

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

View File

@@ -6,15 +6,19 @@ import (
"fmt"
"time"
"sync"
"github.com/argoproj/argo-cd/common"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/server/cluster"
apireposerver "github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/util"
argoutil "github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/kube"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -23,21 +27,30 @@ import (
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
const (
watchResourcesRetryTimeout = 10 * time.Second
)
// ApplicationController is the controller for application resources.
type ApplicationController struct {
repoClientset reposerver.Clientset
kubeClientset kubernetes.Interface
applicationClientset appclientset.Interface
appQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
appComparator AppComparator
statusRefreshTimeout time.Duration
apiRepoService apireposerver.RepositoryServiceServer
namespace string
repoClientset reposerver.Clientset
kubeClientset kubernetes.Interface
applicationClientset appclientset.Interface
appQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
appComparator AppComparator
statusRefreshTimeout time.Duration
apiRepoService apireposerver.RepositoryServiceServer
apiClusterService *cluster.Server
forceRefreshApps map[string]bool
forceRefreshAppsMutex *sync.Mutex
}
type ApplicationControllerConfig struct {
@@ -47,24 +60,30 @@ type ApplicationControllerConfig struct {
// NewApplicationController creates new instance of ApplicationController.
func NewApplicationController(
namespace string,
kubeClientset kubernetes.Interface,
applicationClientset appclientset.Interface,
repoClientset reposerver.Clientset,
apiRepoService apireposerver.RepositoryServiceServer,
apiClusterService *cluster.Server,
appComparator AppComparator,
appResyncPeriod time.Duration,
config *ApplicationControllerConfig,
) *ApplicationController {
appQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
return &ApplicationController{
kubeClientset: kubeClientset,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appQueue: appQueue,
apiRepoService: apiRepoService,
appComparator: appComparator,
appInformer: newApplicationInformer(applicationClientset, appQueue, appResyncPeriod, config),
statusRefreshTimeout: appResyncPeriod,
namespace: namespace,
kubeClientset: kubeClientset,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appQueue: appQueue,
apiRepoService: apiRepoService,
apiClusterService: apiClusterService,
appComparator: appComparator,
appInformer: newApplicationInformer(applicationClientset, appQueue, appResyncPeriod, config),
statusRefreshTimeout: appResyncPeriod,
forceRefreshApps: make(map[string]bool),
forceRefreshAppsMutex: &sync.Mutex{},
}
}
@@ -74,6 +93,7 @@ func (ctrl *ApplicationController) Run(ctx context.Context, appWorkers int) {
defer ctrl.appQueue.ShutDown()
go ctrl.appInformer.Run(ctx.Done())
go ctrl.watchAppsResources()
if !cache.WaitForCacheSync(ctx.Done(), ctrl.appInformer.HasSynced) {
log.Error("Timed out waiting for caches to sync")
@@ -87,6 +107,94 @@ func (ctrl *ApplicationController) Run(ctx context.Context, appWorkers int) {
<-ctx.Done()
}
func (ctrl *ApplicationController) forceAppRefresh(appName string) {
ctrl.forceRefreshAppsMutex.Lock()
defer ctrl.forceRefreshAppsMutex.Unlock()
ctrl.forceRefreshApps[appName] = true
}
func (ctrl *ApplicationController) isRefreshForced(appName string) bool {
ctrl.forceRefreshAppsMutex.Lock()
defer ctrl.forceRefreshAppsMutex.Unlock()
_, ok := ctrl.forceRefreshApps[appName]
if ok {
delete(ctrl.forceRefreshApps, appName)
}
return ok
}
// watchClusterResources watches for resource changes annotated with application label on specified cluster and schedule corresponding app refresh.
func (ctrl *ApplicationController) watchClusterResources(ctx context.Context, item appv1.Cluster) {
config := item.RESTConfig()
retryUntilSucceed(func() error {
ch, err := kube.WatchResourcesWithLabel(ctx, config, "", common.LabelApplicationName)
if err != nil {
return err
}
for event := range ch {
eventObj := event.Object.(*unstructured.Unstructured)
objLabels := eventObj.GetLabels()
if objLabels == nil {
objLabels = make(map[string]string)
}
if appName, ok := objLabels[common.LabelApplicationName]; ok {
ctrl.forceAppRefresh(appName)
ctrl.appQueue.Add(ctrl.namespace + "/" + appName)
}
}
return fmt.Errorf("resource updates channel has closed")
}, fmt.Sprintf("watch app resources on %s", config.Host), ctx, watchResourcesRetryTimeout)
}
// watchAppsResources watches for resource changes annotated with application label on all registered clusters and schedule corresponding app refresh.
func (ctrl *ApplicationController) watchAppsResources() {
watchingClusters := make(map[string]context.CancelFunc)
retryUntilSucceed(func() error {
return ctrl.apiClusterService.WatchClusters(context.Background(), func(event *cluster.ClusterEvent) {
cancel, ok := watchingClusters[event.Cluster.Server]
if event.Type == watch.Deleted && ok {
cancel()
delete(watchingClusters, event.Cluster.Server)
} else if event.Type != watch.Deleted && !ok {
ctx, cancel := context.WithCancel(context.Background())
watchingClusters[event.Cluster.Server] = cancel
go ctrl.watchClusterResources(ctx, *event.Cluster)
}
})
}, "watch clusters", context.Background(), watchResourcesRetryTimeout)
<-context.Background().Done()
}
// retryUntilSucceed keep retrying given action with specified timeout until action succeed or specified context is done.
func retryUntilSucceed(action func() error, desc string, ctx context.Context, timeout time.Duration) {
ctxCompleted := false
go func() {
select {
case <-ctx.Done():
ctxCompleted = true
}
}()
for {
err := action()
if err == nil {
return
}
if err != nil {
if ctxCompleted {
log.Infof("Stop retrying %s", desc)
return
} else {
log.Warnf("Failed to %s: %v, retrying in %v", desc, err, timeout)
time.Sleep(timeout)
}
}
}
}
func (ctrl *ApplicationController) processNextItem() bool {
appKey, shutdown := ctrl.appQueue.Get()
if shutdown {
@@ -110,20 +218,21 @@ func (ctrl *ApplicationController) processNextItem() bool {
return true
}
if app.NeedRefreshAppStatus(ctrl.statusRefreshTimeout) {
updatedApp := app.DeepCopy()
status, err := ctrl.tryRefreshAppStatus(updatedApp)
isForceRefreshed := ctrl.isRefreshForced(app.Name)
if isForceRefreshed || app.NeedRefreshAppStatus(ctrl.statusRefreshTimeout) {
log.Infof("Refreshing application '%s' status (force refreshed: %v)", app.Name, isForceRefreshed)
status, err := ctrl.tryRefreshAppStatus(app.DeepCopy())
if err != nil {
updatedApp.Status.ComparisonResult = appv1.ComparisonResult{
status = app.Status.DeepCopy()
status.ComparisonResult = appv1.ComparisonResult{
Status: appv1.ComparisonStatusError,
Error: fmt.Sprintf("Failed to get application status for application '%s': %v", app.Name, err),
ComparedTo: app.Spec.Source,
ComparedAt: metav1.Time{Time: time.Now().UTC()},
}
} else {
updatedApp.Status = *status
}
ctrl.persistApp(updatedApp)
ctrl.updateAppStatus(app.Name, app.Namespace, status)
}
return true
@@ -183,6 +292,20 @@ func (ctrl *ApplicationController) tryRefreshAppStatus(app *appv1.Application) (
log.Infof("App %s comparison result: prev: %s. current: %s", app.Name, app.Status.ComparisonResult.Status, comparisonResult.Status)
newStatus := app.Status
newStatus.ComparisonResult = *comparisonResult
paramsReq := repository.EnvParamsRequest{
Repo: repo,
Revision: revision,
Path: app.Spec.Source.Path,
Environment: app.Spec.Source.Environment,
}
params, err := client.GetEnvParams(context.Background(), &paramsReq)
if err != nil {
return nil, err
}
newStatus.Parameters = make([]appv1.ComponentParameter, len(params.Params))
for i := range params.Params {
newStatus.Parameters[i] = *params.Params[i]
}
return &newStatus, nil
}
@@ -191,13 +314,24 @@ func (ctrl *ApplicationController) runWorker() {
}
}
func (ctrl *ApplicationController) persistApp(app *appv1.Application) {
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.ObjectMeta.Namespace)
_, err := appClient.Update(app)
func (ctrl *ApplicationController) updateAppStatus(appName string, namespace string, status *appv1.ApplicationStatus) {
appKey := fmt.Sprintf("%s/%s", namespace, appName)
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(appKey)
if err != nil {
log.Warnf("Error updating application: %v", err)
log.Warnf("Failed to get application '%s' from informer index: %+v", appKey, err)
} else {
if exists {
app := obj.(*appv1.Application).DeepCopy()
app.Status = *status
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(namespace)
_, err := appClient.Update(app)
if err != nil {
log.Warnf("Error updating application: %v", err)
} else {
log.Info("Application update successful")
}
}
}
log.Info("Application update successful")
}
func newApplicationInformer(

View File

@@ -9,11 +9,16 @@ An example Ksonnet guestbook application is provided to demonstrates how Argo CD
## 1. Download Argo CD
Download the latest Argo CD version from the [releases](https://github.com/argoproj/argo-cd/releases) page.
## 2. Install the Argo CD components
Download the latest Argo CD version
```
$ argocd install
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.3.1/argocd-darwin-amd64
chmod +x /usr/local/bin/argocd
```
## 2. Install Argo CD
```
argocd install
```
This will create a new namespace, `argocd`, where Argo CD services and application resources will live.
@@ -23,24 +28,23 @@ By default, the Argo CD API server is not exposed with an external IP. To expose
change service type to `LoadBalancer`:
```
$ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
```
Export API server URL into `ARGOCD_SERVER` environment variable, which the CLI looks to for
connection information:
# 4. Login to the server from the CLI
```
$ export ARGOCD_SERVER=$(minikube service argocd-server -n argocd --url | cut -d'/' -f 3)
argocd login $(minikube service argocd-server -n argocd --url | cut -d'/' -f 3)
```
Now, Argo CD is able to talk to API server and you can deploy your first application.
Now, the Argo CD cli is configured to talk to API server and you can deploy your first application.
## 4. Connect and deploy the Guestbook application
## 5. Connect and deploy the Guestbook application
1. Register the minikube cluster to Argo CD:
```
$ argocd cluster add minikube
argocd cluster add minikube
```
The `argocd cluster add CONTEXT` command installs an `argocd-manager` ServiceAccount and ClusterRole into
the cluster associated with the supplied kubectl context. Argo CD then uses the associated service account
@@ -49,21 +53,21 @@ token to perform its required management tasks (i.e. deploy/monitoring).
2. Add the guestbook application and github repository containing the Guestbook application
```
$ argocd app add --name guestbook --repo https://github.com/argoproj/argo-cd.git --path examples/guestbook --env minikube --dest-server https://$(minikube ip):8443
argocd app create --name guestbook --repo https://github.com/argoproj/argo-cd.git --path examples/guestbook --env minikube --dest-server https://$(minikube ip):8443
```
Once the application is added, you can now see its status:
```
$ argocd app list
$ argocd app sync guestbook
argocd app list
argocd app get guestbook
```
The application status is initially in an `OutOfSync` state, since the application has yet to be
deployed, and no Kubernetes resouces have been created. To sync (deploy) the application, run:
deployed, and no Kubernetes resources have been created. To sync (deploy) the application, run:
```
$ argocd app sync guestbook
argocd app sync guestbook
```
[![asciicast](https://asciinema.org/a/uYnbFMy5WI2rc9S49oEAyGLb0.png)](https://asciinema.org/a/uYnbFMy5WI2rc9S49oEAyGLb0)
@@ -71,7 +75,7 @@ $ argocd app sync guestbook
Argo CD also allows to view and manager applications using web UI. Get the web UI URL by running:
```
$ minikube service argocd-server -n argocd --url
minikube service argocd-server -n argocd --url
```
![argo cd ui](argocd-ui.png)

View File

@@ -0,0 +1,40 @@
# 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.
## Auto-Sync
In all tracking strategies described below, the application has the option to sync automatically. If auto-sync is configured, the new resources manifests will be applied automatically -- as soon as a difference is detected between the target state (git) and live state. If auto-sync is disabled, a manual sync will be needed using the Argo UI, CLI, or API.
## Branch Tracking
If a branch name is specified, ArgoCD will continually compare live state against the resource manifests defined at the tip of the specified branch.
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.
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.
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.
## Parameter Overrides
ArgoCD provides means to override the parameters of a ksonnet app. This gives some extra flexibility in having *some* parts of the k8s manifests determined dynamically. It also serves as an alternative way of redeploying an application by changing application parameters via ArgoCD, instead of making the changes to the manifests in git.
The following is an example of where this would be useful: A team maintains a "dev" environment, which needs to be continually updated with the latest version of their guestbook application after every build in the tip of master. To solve this, the ksonnet application would expose an parameter named `image`, whose value used in the `dev` environment contains a placeholder value (e.g. `example/guestbook:replaceme`) intended to be set externally (outside of git) such as by build systems. As part of the build pipeline, the parameter value of the `image` would be continually updated to the freshly built image (e.g. `example/guestbook:abcd123`). A sync operation would result in the application being redeployed with the new image.
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.

View File

@@ -39,10 +39,19 @@ go-to-protobuf \
--apimachinery-packages=$(IFS=, ; echo "${APIMACHINERY_PKGS[*]}") \
--proto-import=./vendor
# protoc-gen-go or protoc-gen-gofast is used to build server/*/<service>.pb.go from .proto files
# NOTE: it is possible to use golang/protobuf or gogo/protobuf interchangeably
go build -i -o dist/protoc-gen-gofast ./vendor/github.com/gogo/protobuf/protoc-gen-gofast
# Either protoc-gen-go, protoc-gen-gofast, or protoc-gen-gogofast can be used to build
# server/*/<service>.pb.go from .proto files. golang/protobuf and gogo/protobuf can be used
# interchangeably. The difference in the options are:
# 1. protoc-gen-go - official golang/protobuf
#go build -i -o dist/protoc-gen-go ./vendor/github.com/golang/protobuf/protoc-gen-go
#GOPROTOBINARY=go
# 2. protoc-gen-gofast - fork of golang golang/protobuf. Faster code generation
#go build -i -o dist/protoc-gen-gofast ./vendor/github.com/gogo/protobuf/protoc-gen-gofast
#GOPROTOBINARY=gofast
# 3. protoc-gen-gogofast - faster code generation and gogo extensions and flexibility in controlling
# the generated go code (e.g. customizing field names, nullable fields)
go build -i -o dist/protoc-gen-gogofast ./vendor/github.com/gogo/protobuf/protoc-gen-gogofast
GOPROTOBINARY=gogofast
# protoc-gen-grpc-gateway is used to build <service>.pb.gw.go files from from .proto files
go build -i -o dist/protoc-gen-grpc-gateway ./vendor/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
@@ -50,7 +59,6 @@ go build -i -o dist/protoc-gen-grpc-gateway ./vendor/github.com/grpc-ecosystem/g
# Generate server/<service>/(<service>.pb.go|<service>.pb.gw.go)
PROTO_FILES=$(find $PROJECT_ROOT \( -name "*.proto" -and -path '*/server/*' -or -path '*/reposerver/*' -and -name "*.proto" \))
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
@@ -67,7 +75,7 @@ for i in ${PROTO_FILES}; do
-I$GOPATH/src \
-I${GOOGLE_PROTO_API_PATH} \
-I${GOGO_PROTOBUF_PATH} \
--go_out=plugins=grpc:$GOPATH/src \
--${GOPROTOBINARY}_out=plugins=grpc:$GOPATH/src \
--grpc-gateway_out=logtostderr=true:$GOPATH/src \
$i
done

View File

@@ -1,25 +1,25 @@
package install
import (
"bufio"
"fmt"
"log"
"os"
"strconv"
"strings"
"syscall"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/config"
"github.com/argoproj/argo-cd/util/diff"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/password"
"github.com/argoproj/argo-cd/util/session"
tlsutil "github.com/argoproj/argo-cd/util/tls"
"github.com/ghodss/yaml"
"github.com/gobuffalo/packr"
log "github.com/sirupsen/logrus"
"github.com/yudai/gojsondiff/formatter"
"golang.org/x/crypto/ssh/terminal"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apiv1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apierr "k8s.io/apimachinery/pkg/api/errors"
@@ -50,6 +50,7 @@ type InstallOptions struct {
DryRun bool
Upgrade bool
ConfigSuperuser bool
CreateSignature bool
ConfigMap string
Namespace string
ControllerImage string
@@ -74,6 +75,9 @@ func NewInstaller(config *rest.Config, opts InstallOptions) (*Installer, error)
box: packr.NewBox("./manifests"),
config: &shallowCopy,
}
if opts.Namespace == "" {
inst.Namespace = DefaultInstallNamespace
}
var err error
inst.dynClientPool = dynamic.NewDynamicClientPool(inst.config)
inst.disco, err = discovery.NewDiscoveryClientForConfig(inst.config)
@@ -86,6 +90,7 @@ func NewInstaller(config *rest.Config, opts InstallOptions) (*Installer, error)
func (i *Installer) Install() {
i.InstallNamespace()
i.InstallApplicationCRD()
i.InstallSettings()
i.InstallApplicationController()
i.InstallArgoCDServer()
i.InstallArgoCDRepoServer()
@@ -104,12 +109,6 @@ func (i *Installer) Uninstall() {
i.MustUninstallResource(&obj)
}
}
// i.InstallNamespace()
// i.InstallApplicationCRD()
// i.InstallApplicationController()
// i.InstallArgoCDServer()
// i.InstallArgoCDRepoServer()
}
func (i *Installer) InstallNamespace() {
@@ -129,15 +128,81 @@ func (i *Installer) InstallApplicationCRD() {
i.MustInstallResource(kube.MustToUnstructured(&applicationCRD))
}
func (i *Installer) InstallSettings() {
kubeclientset, err := kubernetes.NewForConfig(i.config)
errors.CheckError(err)
configManager := config.NewConfigManager(kubeclientset, i.Namespace)
_, err = configManager.GetSettings()
if err == nil {
log.Infof("Settings already exists. Skipping creation")
return
}
if !apierr.IsNotFound(err) {
log.Fatal(err)
}
// configmap/secret not yet created
var newSettings config.ArgoCDSettings
// set JWT signature
signature, err := session.MakeSignature(32)
errors.CheckError(err)
newSettings.ServerSignature = signature
// generate admin password
passwordRaw := readAndConfirmPassword()
hashedPassword, err := password.HashPassword(passwordRaw)
errors.CheckError(err)
newSettings.LocalUsers = map[string]string{
common.ArgoCDAdminUsername: hashedPassword,
}
// generate TLS cert
hosts := []string{
"localhost",
"argocd-server",
fmt.Sprintf("argocd-server.%s", i.Namespace),
fmt.Sprintf("argocd-server.%s.svc", i.Namespace),
fmt.Sprintf("argocd-server.%s.svc.cluster.local", i.Namespace),
}
certOpts := tlsutil.CertOptions{
Hosts: hosts,
Organization: "Argo CD",
IsCA: true,
}
cert, err := tlsutil.GenerateX509KeyPair(certOpts)
errors.CheckError(err)
newSettings.Certificate = cert
err = configManager.SaveSettings(&newSettings)
errors.CheckError(err)
}
func readAndConfirmPassword() string {
for {
fmt.Print("*** Enter an admin password: ")
password, err := terminal.ReadPassword(syscall.Stdin)
errors.CheckError(err)
fmt.Print("\n")
fmt.Print("*** Confirm the admin password: ")
confirmPassword, err := terminal.ReadPassword(syscall.Stdin)
errors.CheckError(err)
fmt.Print("\n")
if string(password) == string(confirmPassword) {
return string(password)
}
log.Error("Passwords do not match")
}
}
func (i *Installer) InstallApplicationController() {
var applicationControllerServiceAccount apiv1.ServiceAccount
var applicationControllerRole rbacv1.Role
var applicationControllerRoleBinding rbacv1.RoleBinding
var applicationControllerDeployment appsv1beta2.Deployment
i.unmarshalManifest("02a_application-controller-sa.yaml", &applicationControllerServiceAccount)
i.unmarshalManifest("02b_application-controller-role.yaml", &applicationControllerRole)
i.unmarshalManifest("02c_application-controller-rolebinding.yaml", &applicationControllerRoleBinding)
i.unmarshalManifest("02d_application-controller-deployment.yaml", &applicationControllerDeployment)
i.unmarshalManifest("03a_application-controller-sa.yaml", &applicationControllerServiceAccount)
i.unmarshalManifest("03b_application-controller-role.yaml", &applicationControllerRole)
i.unmarshalManifest("03c_application-controller-rolebinding.yaml", &applicationControllerRoleBinding)
i.unmarshalManifest("03d_application-controller-deployment.yaml", &applicationControllerDeployment)
applicationControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.ControllerImage
applicationControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
i.MustInstallResource(kube.MustToUnstructured(&applicationControllerServiceAccount))
@@ -152,57 +217,28 @@ func (i *Installer) InstallArgoCDServer() {
var argoCDServerControllerRoleBinding rbacv1.RoleBinding
var argoCDServerControllerDeployment appsv1beta2.Deployment
var argoCDServerService apiv1.Service
i.unmarshalManifest("03a_argocd-server-sa.yaml", &argoCDServerServiceAccount)
i.unmarshalManifest("03b_argocd-server-role.yaml", &argoCDServerControllerRole)
i.unmarshalManifest("03c_argocd-server-rolebinding.yaml", &argoCDServerControllerRoleBinding)
i.unmarshalManifest("03d_argocd-server-deployment.yaml", &argoCDServerControllerDeployment)
i.unmarshalManifest("03e_argocd-server-service.yaml", &argoCDServerService)
i.unmarshalManifest("04a_argocd-server-sa.yaml", &argoCDServerServiceAccount)
i.unmarshalManifest("04b_argocd-server-role.yaml", &argoCDServerControllerRole)
i.unmarshalManifest("04c_argocd-server-rolebinding.yaml", &argoCDServerControllerRoleBinding)
i.unmarshalManifest("04d_argocd-server-deployment.yaml", &argoCDServerControllerDeployment)
i.unmarshalManifest("04e_argocd-server-service.yaml", &argoCDServerService)
argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[0].Image = i.UIImage
argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.ServerImage
argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
kubeclientset, err := kubernetes.NewForConfig(i.config)
errors.CheckError(err)
configManager := util.NewConfigManager(kubeclientset, i.Namespace, i.ConfigMap)
errors.CheckError(err)
if i.InstallOptions.ConfigMap != "" {
quotedConfigMapName := strconv.Quote(i.InstallOptions.ConfigMap)
container := &argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0]
container.Command = append(container.Command, "--config-map", quotedConfigMapName)
}
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerServiceAccount))
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerRole))
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerRoleBinding))
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerDeployment))
i.MustInstallResource(kube.MustToUnstructured(&argoCDServerService))
if i.InstallOptions.ConfigSuperuser {
inputReader := bufio.NewReader(os.Stdin)
fmt.Print("*** Please enter a superuser username: ")
rootUsername, err := inputReader.ReadString('\n')
errors.CheckError(err)
rootUsername = strings.Trim(rootUsername, "\n")
fmt.Print("*** Please enter a superuser password: ")
rawPassword, err := terminal.ReadPassword(syscall.Stdin)
errors.CheckError(err)
fmt.Print("\n")
err = configManager.SetRootUserCredentials(rootUsername, string(rawPassword))
errors.CheckError(err)
}
}
func (i *Installer) InstallArgoCDRepoServer() {
var argoCDRepoServerControllerDeployment appsv1beta2.Deployment
var argoCDRepoServerService apiv1.Service
i.unmarshalManifest("04a_argocd-repo-server-deployment.yaml", &argoCDRepoServerControllerDeployment)
i.unmarshalManifest("04b_argocd-repo-server-service.yaml", &argoCDRepoServerService)
i.unmarshalManifest("05a_argocd-repo-server-deployment.yaml", &argoCDRepoServerControllerDeployment)
i.unmarshalManifest("05b_argocd-repo-server-service.yaml", &argoCDRepoServerService)
argoCDRepoServerControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.RepoServerImage
argoCDRepoServerControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy)
i.MustInstallResource(kube.MustToUnstructured(&argoCDRepoServerControllerDeployment))

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ rules:
- secrets
verbs:
- get
- watch
- apiGroups:
- argoproj.io
resources:

View File

@@ -10,4 +10,3 @@ roleRef:
subjects:
- kind: ServiceAccount
name: application-controller
namespace: argocd

View File

@@ -10,4 +10,3 @@ roleRef:
subjects:
- kind: ServiceAccount
name: argocd-server
namespace: argocd

View File

@@ -5,7 +5,13 @@ metadata:
namespace: argocd
spec:
ports:
- port: 80
- name: http
protocol: TCP
port: 80
targetPort: 8080
- name: https
protocol: TCP
port: 443
targetPort: 8080
selector:
app: argocd-server

View File

@@ -6,5 +6,6 @@ metadata:
spec:
ports:
- port: 8081
targetPort: 8081
selector:
app: argocd-repo-server

View File

@@ -1,21 +1,36 @@
package apiclient
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/argoproj/argo-cd/server/application"
"github.com/argoproj/argo-cd/server/cluster"
"github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/server/session"
"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 (
// EnvArgoCDServer is the environment variable to look for an ArgoCD server address
EnvArgoCDServer = "ARGOCD_SERVER"
// EnvArgoCDAuthToken is the environment variable to look for an ArgoCD auth token
EnvArgoCDAuthToken = "ARGOCD_AUTH_TOKEN"
)
// ServerClient defines an interface for interaction with an Argo CD server.
type ServerClient interface {
NewConn() (*grpc.ClientConn, error)
NewRepoClient() (*grpc.ClientConn, repository.RepositoryServiceClient, error)
@@ -24,30 +39,97 @@ type ServerClient interface {
NewClusterClientOrDie() (*grpc.ClientConn, cluster.ClusterServiceClient)
NewApplicationClient() (*grpc.ClientConn, application.ApplicationServiceClient, error)
NewApplicationClientOrDie() (*grpc.ClientConn, application.ApplicationServiceClient)
NewSessionClient() (*grpc.ClientConn, session.SessionServiceClient, error)
NewSessionClientOrDie() (*grpc.ClientConn, session.SessionServiceClient)
NewVersionClient() (*grpc.ClientConn, version.VersionServiceClient, error)
NewVersionClientOrDie() (*grpc.ClientConn, version.VersionServiceClient)
}
// ClientOptions hold address, security, and other settings for the API client.
type ClientOptions struct {
ServerAddr string
PlainText bool
Insecure bool
CertFile string
AuthToken string
ConfigPath string
Context string
}
type client struct {
ClientOptions
ServerAddr string
PlainText bool
Insecure bool
CertPEMData []byte
AuthToken string
}
// NewClient creates a new API client from a set of config options.
func NewClient(opts *ClientOptions) (ServerClient, error) {
clientOpts := *opts
if clientOpts.ServerAddr == "" {
clientOpts.ServerAddr = os.Getenv(EnvArgoCDServer)
var c client
localCfg, err := localconfig.ReadLocalConfig(opts.ConfigPath)
if err != nil {
return nil, err
}
if clientOpts.ServerAddr == "" {
return nil, errors.New("Argo CD server address not supplied")
if localCfg != nil {
configCtx, err := localCfg.ResolveContext(opts.Context)
if err != nil {
return nil, err
}
if configCtx != nil {
c.ServerAddr = configCtx.Server.Server
if configCtx.Server.CACertificateAuthorityData != "" {
c.CertPEMData, err = base64.StdEncoding.DecodeString(configCtx.Server.CACertificateAuthorityData)
if err != nil {
return nil, err
}
}
c.PlainText = configCtx.Server.PlainText
c.Insecure = configCtx.Server.Insecure
c.AuthToken = configCtx.User.AuthToken
}
}
return &client{
ClientOptions: clientOpts,
}, nil
// Override server address if specified in env or CLI flag
if serverFromEnv := os.Getenv(EnvArgoCDServer); serverFromEnv != "" {
c.ServerAddr = serverFromEnv
}
if opts.ServerAddr != "" {
c.ServerAddr = opts.ServerAddr
}
// Make sure we got the server address and auth token from somewhere
if c.ServerAddr == "" {
return nil, errors.New("ArgoCD server address unspecified")
}
if parts := strings.Split(c.ServerAddr, ":"); len(parts) == 1 {
// If port is unspecified, assume the most likely port
c.ServerAddr += ":443"
}
// Override auth-token if specified in env variable or CLI flag
if authFromEnv := os.Getenv(EnvArgoCDAuthToken); authFromEnv != "" {
c.AuthToken = authFromEnv
}
if opts.AuthToken != "" {
c.AuthToken = opts.AuthToken
}
// Override certificate data if specified from CLI flag
if opts.CertFile != "" {
b, err := ioutil.ReadFile(opts.CertFile)
if err != nil {
return nil, err
}
c.CertPEMData = b
}
// Override insecure/plaintext options if specified from CLI
if opts.PlainText {
c.PlainText = true
}
if opts.Insecure {
c.Insecure = true
}
return &c, 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) ServerClient {
client, err := NewClient(opts)
if err != nil {
@@ -56,16 +138,41 @@ func NewClientOrDie(opts *ClientOptions) ServerClient {
return client
}
// JwtCredentials holds a token for authentication.
type jwtCredentials struct {
Token string
}
func (c jwtCredentials) RequireTransportSecurity() bool {
return false
}
func (c jwtCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
return map[string]string{
"tokens": c.Token,
}, nil
}
func (c *client) NewConn() (*grpc.ClientConn, error) {
var dialOpts []grpc.DialOption
if c.Insecure {
dialOpts = append(dialOpts, grpc.WithInsecure())
} else {
return nil, errors.New("secure authentication unsupported")
} // else if opts.Credentials != nil {
// dialOpts = append(dialOpts, grpc.WithTransportCredentials(opts.Credentials))
//}
return grpc.Dial(c.ServerAddr, dialOpts...)
var creds credentials.TransportCredentials
if !c.PlainText {
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
}
creds = credentials.NewTLS(&tlsConfig)
}
endpointCredentials := jwtCredentials{
Token: c.AuthToken,
}
return grpc_util.BlockingDial(context.Background(), "tcp", c.ServerAddr, creds, grpc.WithPerRPCCredentials(endpointCredentials))
}
func (c *client) NewRepoClient() (*grpc.ClientConn, repository.RepositoryServiceClient, error) {
@@ -118,3 +225,37 @@ func (c *client) NewApplicationClientOrDie() (*grpc.ClientConn, application.Appl
}
return conn, repoIf
}
func (c *client) NewSessionClient() (*grpc.ClientConn, session.SessionServiceClient, error) {
conn, err := c.NewConn()
if err != nil {
return nil, nil, err
}
sessionIf := session.NewSessionServiceClient(conn)
return conn, sessionIf, nil
}
func (c *client) NewSessionClientOrDie() (*grpc.ClientConn, session.SessionServiceClient) {
conn, sessionIf, err := c.NewSessionClient()
if err != nil {
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
}
return conn, sessionIf
}
func (c *client) NewVersionClient() (*grpc.ClientConn, version.VersionServiceClient, error) {
conn, err := c.NewConn()
if err != nil {
return nil, nil, err
}
versionIf := version.NewVersionServiceClient(conn)
return conn, versionIf, nil
}
func (c *client) NewVersionClientOrDie() (*grpc.ClientConn, version.VersionServiceClient) {
conn, versionIf, err := c.NewVersionClient()
if err != nil {
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
}
return conn, versionIf
}

View File

@@ -23,6 +23,7 @@
DeploymentInfo
Repository
RepositoryList
ResourceNode
ResourceState
TLSClientConfig
*/
@@ -110,13 +111,17 @@ func (m *RepositoryList) Reset() { *m = RepositoryList{} }
func (*RepositoryList) ProtoMessage() {}
func (*RepositoryList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} }
func (m *ResourceNode) Reset() { *m = ResourceNode{} }
func (*ResourceNode) ProtoMessage() {}
func (*ResourceNode) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} }
func (m *ResourceState) Reset() { *m = ResourceState{} }
func (*ResourceState) ProtoMessage() {}
func (*ResourceState) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} }
func (*ResourceState) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} }
func (m *TLSClientConfig) Reset() { *m = TLSClientConfig{} }
func (*TLSClientConfig) ProtoMessage() {}
func (*TLSClientConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} }
func (*TLSClientConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} }
func init() {
proto.RegisterType((*Application)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.Application")
@@ -134,6 +139,7 @@ func init() {
proto.RegisterType((*DeploymentInfo)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.DeploymentInfo")
proto.RegisterType((*Repository)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.Repository")
proto.RegisterType((*RepositoryList)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.RepositoryList")
proto.RegisterType((*ResourceNode)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ResourceNode")
proto.RegisterType((*ResourceState)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ResourceState")
proto.RegisterType((*TLSClientConfig)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.TLSClientConfig")
}
@@ -322,6 +328,10 @@ func (m *ApplicationSpec) MarshalTo(dAtA []byte) (int, error) {
}
i += n6
}
dAtA[i] = 0x1a
i++
i = encodeVarintGenerated(dAtA, i, uint64(len(m.SyncPolicy)))
i += copy(dAtA[i:], m.SyncPolicy)
return i, nil
}
@@ -360,6 +370,18 @@ func (m *ApplicationStatus) MarshalTo(dAtA []byte) (int, error) {
i += n
}
}
if len(m.Parameters) > 0 {
for _, msg := range m.Parameters {
dAtA[i] = 0x1a
i++
i = encodeVarintGenerated(dAtA, i, uint64(msg.Size()))
n, err := msg.MarshalTo(dAtA[i:])
if err != nil {
return 0, err
}
i += n
}
}
return i, nil
}
@@ -646,6 +668,9 @@ func (m *DeploymentInfo) MarshalTo(dAtA []byte) (int, error) {
return 0, err
}
i += n14
dAtA[i] = 0x28
i++
i = encodeVarintGenerated(dAtA, i, uint64(m.ID))
return i, nil
}
@@ -721,6 +746,40 @@ func (m *RepositoryList) MarshalTo(dAtA []byte) (int, error) {
return i, nil
}
func (m *ResourceNode) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ResourceNode) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
dAtA[i] = 0xa
i++
i = encodeVarintGenerated(dAtA, i, uint64(len(m.State)))
i += copy(dAtA[i:], m.State)
if len(m.Children) > 0 {
for _, msg := range m.Children {
dAtA[i] = 0x12
i++
i = encodeVarintGenerated(dAtA, i, uint64(msg.Size()))
n, err := msg.MarshalTo(dAtA[i:])
if err != nil {
return 0, err
}
i += n
}
}
return i, nil
}
func (m *ResourceState) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
@@ -748,6 +807,18 @@ func (m *ResourceState) MarshalTo(dAtA []byte) (int, error) {
i++
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status)))
i += copy(dAtA[i:], m.Status)
if len(m.ChildLiveResources) > 0 {
for _, msg := range m.ChildLiveResources {
dAtA[i] = 0x22
i++
i = encodeVarintGenerated(dAtA, i, uint64(msg.Size()))
n, err := msg.MarshalTo(dAtA[i:])
if err != nil {
return 0, err
}
i += n
}
}
return i, nil
}
@@ -873,6 +944,8 @@ func (m *ApplicationSpec) Size() (n int) {
l = m.Destination.Size()
n += 1 + l + sovGenerated(uint64(l))
}
l = len(m.SyncPolicy)
n += 1 + l + sovGenerated(uint64(l))
return n
}
@@ -887,6 +960,12 @@ func (m *ApplicationStatus) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l))
}
}
if len(m.Parameters) > 0 {
for _, e := range m.Parameters {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
@@ -995,6 +1074,7 @@ func (m *DeploymentInfo) Size() (n int) {
}
l = m.DeployedAt.Size()
n += 1 + l + sovGenerated(uint64(l))
n += 1 + sovGenerated(uint64(m.ID))
return n
}
@@ -1026,6 +1106,20 @@ func (m *RepositoryList) Size() (n int) {
return n
}
func (m *ResourceNode) Size() (n int) {
var l int
_ = l
l = len(m.State)
n += 1 + l + sovGenerated(uint64(l))
if len(m.Children) > 0 {
for _, e := range m.Children {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
func (m *ResourceState) Size() (n int) {
var l int
_ = l
@@ -1035,6 +1129,12 @@ func (m *ResourceState) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l))
l = len(m.Status)
n += 1 + l + sovGenerated(uint64(l))
if len(m.ChildLiveResources) > 0 {
for _, e := range m.ChildLiveResources {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
@@ -1127,6 +1227,7 @@ func (this *ApplicationSpec) String() string {
s := strings.Join([]string{`&ApplicationSpec{`,
`Source:` + strings.Replace(strings.Replace(this.Source.String(), "ApplicationSource", "ApplicationSource", 1), `&`, ``, 1) + `,`,
`Destination:` + strings.Replace(fmt.Sprintf("%v", this.Destination), "ApplicationDestination", "ApplicationDestination", 1) + `,`,
`SyncPolicy:` + fmt.Sprintf("%v", this.SyncPolicy) + `,`,
`}`,
}, "")
return s
@@ -1138,6 +1239,7 @@ func (this *ApplicationStatus) String() string {
s := strings.Join([]string{`&ApplicationStatus{`,
`ComparisonResult:` + strings.Replace(strings.Replace(this.ComparisonResult.String(), "ComparisonResult", "ComparisonResult", 1), `&`, ``, 1) + `,`,
`RecentDeployments:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.RecentDeployments), "DeploymentInfo", "DeploymentInfo", 1), `&`, ``, 1) + `,`,
`Parameters:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Parameters), "ComponentParameter", "ComponentParameter", 1), `&`, ``, 1) + `,`,
`}`,
}, "")
return s
@@ -1226,6 +1328,7 @@ func (this *DeploymentInfo) String() string {
`Revision:` + fmt.Sprintf("%v", this.Revision) + `,`,
`ComponentParameterOverrides:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ComponentParameterOverrides), "ComponentParameter", "ComponentParameter", 1), `&`, ``, 1) + `,`,
`DeployedAt:` + strings.Replace(strings.Replace(this.DeployedAt.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`,
`ID:` + fmt.Sprintf("%v", this.ID) + `,`,
`}`,
}, "")
return s
@@ -1254,6 +1357,17 @@ func (this *RepositoryList) String() string {
}, "")
return s
}
func (this *ResourceNode) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&ResourceNode{`,
`State:` + fmt.Sprintf("%v", this.State) + `,`,
`Children:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Children), "ResourceNode", "ResourceNode", 1), `&`, ``, 1) + `,`,
`}`,
}, "")
return s
}
func (this *ResourceState) String() string {
if this == nil {
return "nil"
@@ -1262,6 +1376,7 @@ func (this *ResourceState) String() string {
`TargetState:` + fmt.Sprintf("%v", this.TargetState) + `,`,
`LiveState:` + fmt.Sprintf("%v", this.LiveState) + `,`,
`Status:` + fmt.Sprintf("%v", this.Status) + `,`,
`ChildLiveResources:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ChildLiveResources), "ResourceNode", "ResourceNode", 1), `&`, ``, 1) + `,`,
`}`,
}, "")
return s
@@ -1936,6 +2051,35 @@ func (m *ApplicationSpec) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field SyncPolicy", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
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 ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.SyncPolicy = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -2047,6 +2191,37 @@ func (m *ApplicationStatus) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Parameters", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Parameters = append(m.Parameters, ComponentParameter{})
if err := m.Parameters[len(m.Parameters)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -3137,6 +3312,25 @@ func (m *DeploymentInfo) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
}
m.ID = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.ID |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -3435,6 +3629,116 @@ func (m *RepositoryList) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *ResourceNode) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ResourceNode: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ResourceNode: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field State", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
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 ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.State = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Children", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Children = append(m.Children, ResourceNode{})
if err := m.Children[len(m.Children)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *ResourceState) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
@@ -3551,6 +3855,37 @@ func (m *ResourceState) Unmarshal(dAtA []byte) error {
}
m.Status = ComparisonStatus(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ChildLiveResources", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ChildLiveResources = append(m.ChildLiveResources, ResourceNode{})
if err := m.ChildLiveResources[len(m.ChildLiveResources)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -3874,96 +4209,104 @@ func init() {
}
var fileDescriptorGenerated = []byte{
// 1447 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0xcd, 0x6f, 0x1b, 0x45,
0x14, 0xcf, 0xfa, 0x2b, 0xc9, 0xb8, 0xf9, 0xe8, 0xa0, 0x16, 0xd3, 0x4a, 0x6e, 0xb4, 0x15, 0x50,
0x10, 0x5d, 0xd3, 0xf2, 0x0d, 0x12, 0x52, 0xd7, 0x09, 0x6a, 0x48, 0xda, 0x86, 0x89, 0x0b, 0x12,
0x42, 0xc0, 0x64, 0xfd, 0x6a, 0x6f, 0x63, 0xef, 0x2e, 0x33, 0x63, 0x57, 0x3e, 0x20, 0xf5, 0xc0,
0x11, 0xaa, 0xf2, 0x6f, 0x70, 0x04, 0x4e, 0xdc, 0x11, 0x3d, 0x70, 0xa8, 0x04, 0x87, 0x0a, 0xa1,
0x8a, 0xa4, 0x17, 0xfe, 0x86, 0x9e, 0xd0, 0xcc, 0xce, 0xee, 0x8e, 0x6d, 0x85, 0xa4, 0x75, 0xa8,
0xb8, 0x79, 0xe6, 0xfd, 0xe6, 0xf7, 0x7b, 0xfb, 0xe6, 0xcd, 0x7b, 0xcf, 0x68, 0xb5, 0xe5, 0x8b,
0x76, 0x6f, 0xcb, 0xf1, 0xc2, 0x6e, 0x8d, 0xb2, 0x56, 0x18, 0xb1, 0xf0, 0xba, 0xfa, 0x71, 0xd6,
0x6b, 0xd6, 0xa2, 0xed, 0x56, 0x8d, 0x46, 0x3e, 0xaf, 0xd1, 0x28, 0xea, 0xf8, 0x1e, 0x15, 0x7e,
0x18, 0xd4, 0xfa, 0xe7, 0x68, 0x27, 0x6a, 0xd3, 0x73, 0xb5, 0x16, 0x04, 0xc0, 0xa8, 0x80, 0xa6,
0x13, 0xb1, 0x50, 0x84, 0xf8, 0xad, 0x8c, 0xca, 0x49, 0xa8, 0xd4, 0x8f, 0xcf, 0xbc, 0xa6, 0x13,
0x6d, 0xb7, 0x1c, 0x49, 0xe5, 0x18, 0x54, 0x4e, 0x42, 0x75, 0xe2, 0xac, 0xe1, 0x45, 0x2b, 0x6c,
0x85, 0x35, 0xc5, 0xb8, 0xd5, 0xbb, 0xa6, 0x56, 0x6a, 0xa1, 0x7e, 0xc5, 0x4a, 0x27, 0x5e, 0xdd,
0x7e, 0x93, 0x3b, 0x7e, 0x28, 0x7d, 0xeb, 0x52, 0xaf, 0xed, 0x07, 0xc0, 0x06, 0x99, 0xb3, 0x5d,
0x10, 0xb4, 0xd6, 0x1f, 0xf3, 0xef, 0x44, 0x6d, 0xaf, 0x53, 0xac, 0x17, 0x08, 0xbf, 0x0b, 0x63,
0x07, 0x5e, 0xdf, 0xef, 0x00, 0xf7, 0xda, 0xd0, 0xa5, 0x63, 0xe7, 0x5e, 0xd9, 0xeb, 0x5c, 0x4f,
0xf8, 0x9d, 0x9a, 0x1f, 0x08, 0x2e, 0xd8, 0xe8, 0x21, 0xfb, 0xb7, 0x1c, 0x2a, 0x5f, 0xc8, 0x62,
0x83, 0x3f, 0x47, 0x33, 0xf2, 0x43, 0x9a, 0x54, 0xd0, 0x8a, 0xb5, 0x64, 0x9d, 0x29, 0x9f, 0x7f,
0xd9, 0x89, 0x79, 0x1d, 0x93, 0x37, 0x0b, 0xac, 0x44, 0x3b, 0xfd, 0x73, 0xce, 0x95, 0xad, 0xeb,
0xe0, 0x89, 0x4b, 0x20, 0xa8, 0x8b, 0xef, 0xdc, 0x3f, 0x35, 0xb5, 0x7b, 0xff, 0x14, 0xca, 0xf6,
0x48, 0xca, 0x8a, 0x3b, 0xa8, 0xc0, 0x23, 0xf0, 0x2a, 0x39, 0xc5, 0xfe, 0xbe, 0xf3, 0xd8, 0xd7,
0xe7, 0x18, 0x7e, 0x6f, 0x46, 0xe0, 0xb9, 0x47, 0xb4, 0x6e, 0x41, 0xae, 0x88, 0x52, 0xc1, 0x02,
0x95, 0xb8, 0xa0, 0xa2, 0xc7, 0x2b, 0x79, 0xa5, 0xb7, 0x7e, 0x48, 0x7a, 0x8a, 0xd3, 0x9d, 0xd7,
0x8a, 0xa5, 0x78, 0x4d, 0xb4, 0x96, 0xfd, 0x05, 0x3a, 0x6e, 0x80, 0x97, 0x81, 0x0b, 0x3f, 0x88,
0xe3, 0xfb, 0x1c, 0x2a, 0x71, 0x60, 0x7d, 0x60, 0x2a, 0xba, 0xb3, 0x06, 0x83, 0xda, 0x25, 0xda,
0x8a, 0x6b, 0x68, 0x36, 0xa0, 0x5d, 0xe0, 0x11, 0xf5, 0x40, 0x85, 0x6a, 0xd6, 0x3d, 0xaa, 0xa1,
0xb3, 0x97, 0x13, 0x03, 0xc9, 0x30, 0xf6, 0x9f, 0x16, 0x5a, 0x30, 0x34, 0xd7, 0x7d, 0x2e, 0xf0,
0x27, 0x63, 0x97, 0xe9, 0x1c, 0xec, 0x32, 0xe5, 0x69, 0x75, 0x95, 0x8b, 0x5a, 0x73, 0x26, 0xd9,
0x31, 0x2e, 0x72, 0x1b, 0x15, 0x7d, 0x01, 0x5d, 0x5e, 0xc9, 0x2d, 0xe5, 0xcf, 0x94, 0xcf, 0xbf,
0x77, 0x38, 0x91, 0x75, 0xe7, 0xb4, 0x64, 0x71, 0x55, 0x92, 0x93, 0x58, 0xc3, 0xbe, 0x95, 0x47,
0x47, 0xcd, 0xf8, 0x87, 0x3d, 0xe6, 0x01, 0x7e, 0x01, 0x4d, 0x33, 0x88, 0xc2, 0xab, 0x64, 0x5d,
0x87, 0x73, 0x41, 0x1f, 0x9e, 0x26, 0xf1, 0x36, 0x49, 0xec, 0x78, 0x09, 0x15, 0x22, 0x2a, 0xda,
0x3a, 0x96, 0x69, 0xaa, 0x6c, 0x50, 0xd1, 0x26, 0xca, 0x82, 0x5f, 0x43, 0x65, 0x08, 0xfa, 0x3e,
0x0b, 0x83, 0x2e, 0x04, 0x42, 0xe5, 0xcb, 0xac, 0xfb, 0x94, 0x06, 0x96, 0x57, 0x32, 0x13, 0x31,
0x71, 0xf8, 0x5d, 0x34, 0x2f, 0x28, 0x6b, 0x81, 0x20, 0xd0, 0xf7, 0xb9, 0x1f, 0x06, 0x95, 0x82,
0x3a, 0x79, 0x5c, 0x9f, 0x9c, 0x6f, 0x0c, 0x59, 0xc9, 0x08, 0x1a, 0xff, 0x68, 0xa1, 0x93, 0x5e,
0xd8, 0x8d, 0xc2, 0x00, 0x02, 0xb1, 0x41, 0x19, 0xed, 0x82, 0x00, 0x76, 0xa5, 0x0f, 0x8c, 0xf9,
0x4d, 0xe0, 0x95, 0xa2, 0x8a, 0xee, 0xa5, 0x09, 0xa2, 0x5b, 0x1f, 0x63, 0x77, 0x4f, 0x6b, 0xe7,
0x4e, 0xd6, 0xf7, 0x56, 0x26, 0xff, 0xe6, 0x96, 0x7d, 0x2b, 0x37, 0x94, 0x6f, 0x9b, 0xc9, 0x63,
0x53, 0x17, 0xa3, 0xb3, 0xed, 0xb0, 0x1e, 0x9b, 0xe2, 0x34, 0x9e, 0x8a, 0x5a, 0x13, 0xad, 0x85,
0xbf, 0xb2, 0x50, 0xb9, 0x99, 0x3d, 0x31, 0x5d, 0x58, 0x3e, 0x38, 0x1c, 0x6d, 0xe3, 0xed, 0xba,
0x0b, 0x32, 0x0f, 0x8c, 0x0d, 0x62, 0xca, 0xda, 0x3f, 0xe5, 0x86, 0x33, 0x54, 0x55, 0x02, 0xfc,
0xad, 0x85, 0x16, 0x65, 0x18, 0x29, 0xf3, 0x79, 0x18, 0x10, 0xe0, 0xbd, 0x8e, 0xd0, 0xd1, 0x59,
0x9b, 0xf0, 0x4a, 0x4d, 0x4a, 0xb7, 0xa2, 0x83, 0xb3, 0x38, 0x6a, 0x21, 0x63, 0xf2, 0xf8, 0xb6,
0x85, 0x16, 0x19, 0x78, 0x10, 0x88, 0x65, 0x88, 0x3a, 0xe1, 0x40, 0xa5, 0x7b, 0xfc, 0x88, 0x57,
0x27, 0xf0, 0x29, 0x23, 0x5b, 0x0d, 0xae, 0x85, 0xee, 0x33, 0xda, 0xa3, 0xa3, 0x64, 0x44, 0x8a,
0x93, 0x31, 0x75, 0xfb, 0x81, 0x85, 0x8e, 0x19, 0xc1, 0xfb, 0x88, 0x0a, 0xaf, 0xbd, 0xd2, 0x97,
0xcf, 0x6b, 0x0d, 0x15, 0xc4, 0x20, 0x02, 0xfd, 0xbe, 0xdf, 0x48, 0xde, 0x6d, 0x63, 0x10, 0xc1,
0xc3, 0xfb, 0xa7, 0x9e, 0xdf, 0xab, 0xe7, 0xdd, 0x90, 0x0c, 0x8e, 0xa2, 0x90, 0x50, 0xa2, 0x48,
0xf0, 0x97, 0xa8, 0x6c, 0xb8, 0xae, 0x33, 0xe5, 0xb0, 0x0a, 0x57, 0x5a, 0x2a, 0x8c, 0x4d, 0x62,
0xea, 0xd9, 0x3f, 0x5b, 0x68, 0xba, 0xde, 0xe9, 0x71, 0x01, 0xec, 0xc0, 0x8d, 0x60, 0x09, 0x15,
0x64, 0x91, 0x1f, 0xad, 0x5b, 0xb2, 0x07, 0x10, 0x65, 0xc1, 0x11, 0x2a, 0x79, 0x61, 0x70, 0xcd,
0x6f, 0xe9, 0x16, 0x77, 0x71, 0x92, 0xbc, 0x8a, 0xbd, 0xab, 0x2b, 0xbe, 0xcc, 0xa7, 0x78, 0x4d,
0xb4, 0x8e, 0xfd, 0x7d, 0x0e, 0xcd, 0x0d, 0x21, 0xf1, 0x4b, 0x68, 0xa6, 0xc7, 0x81, 0x29, 0x4f,
0xe3, 0xef, 0x49, 0x3b, 0xc7, 0x55, 0xbd, 0x4f, 0x52, 0x84, 0x44, 0x47, 0x94, 0xf3, 0x1b, 0x21,
0x6b, 0xea, 0xef, 0x4a, 0xd1, 0x1b, 0x7a, 0x9f, 0xa4, 0x08, 0x59, 0x97, 0xb7, 0x80, 0x32, 0x60,
0x8d, 0x70, 0x1b, 0x82, 0xd1, 0xba, 0xec, 0x66, 0x26, 0x62, 0xe2, 0xf0, 0x37, 0x16, 0x5a, 0x10,
0x1d, 0x5e, 0xef, 0xf8, 0x10, 0x88, 0xd8, 0x4d, 0x55, 0x99, 0x27, 0x9b, 0x39, 0x1a, 0xeb, 0x9b,
0x26, 0xa3, 0xfb, 0xb4, 0xf6, 0x63, 0x61, 0xc4, 0x40, 0x46, 0xb5, 0xed, 0xdf, 0x2d, 0x54, 0xd6,
0x41, 0x7b, 0x02, 0xcd, 0xb9, 0x35, 0xdc, 0x9c, 0xdd, 0xc9, 0x73, 0x62, 0x8f, 0xc6, 0xfc, 0x5d,
0x01, 0x8d, 0xd5, 0x1c, 0xfc, 0x29, 0x42, 0x71, 0xd5, 0x81, 0xe6, 0x85, 0xa4, 0xdc, 0xbd, 0x78,
0xb0, 0xaf, 0x6b, 0xf8, 0x5d, 0xc8, 0x26, 0xc8, 0x7a, 0xca, 0x42, 0x0c, 0x46, 0x7c, 0xd3, 0xca,
0x04, 0x1a, 0xa1, 0x7e, 0xc7, 0x87, 0xdb, 0x6d, 0xc6, 0x5c, 0x68, 0x84, 0xc4, 0xd0, 0x34, 0xde,
0x6f, 0xfe, 0xe0, 0x83, 0x5c, 0x61, 0xff, 0x41, 0x0e, 0xbf, 0x9d, 0x4e, 0xac, 0x45, 0x85, 0xb6,
0x87, 0x67, 0xcc, 0x87, 0x43, 0x35, 0x7e, 0x78, 0xee, 0xc4, 0x03, 0x34, 0xcb, 0x20, 0x6e, 0x8b,
0xbc, 0x52, 0x52, 0x37, 0x3f, 0x49, 0x35, 0x20, 0x9a, 0x4b, 0xaa, 0x40, 0xe6, 0x76, 0xb2, 0xcd,
0x49, 0xa6, 0x86, 0x4f, 0xa3, 0x22, 0x30, 0x16, 0xb2, 0xca, 0xb4, 0xf2, 0x3a, 0x4d, 0x96, 0x15,
0xb9, 0x49, 0x62, 0x9b, 0xfd, 0xb5, 0x85, 0xf0, 0xf8, 0xc4, 0x21, 0x63, 0x94, 0x8e, 0x1a, 0xba,
0x7c, 0xa4, 0x62, 0x29, 0x9c, 0x64, 0x98, 0x03, 0x14, 0xc5, 0xd3, 0xa8, 0xd8, 0xa7, 0x9d, 0x1e,
0xe8, 0xdb, 0x49, 0xdd, 0xf9, 0x50, 0x6e, 0x92, 0xd8, 0x66, 0xff, 0x9a, 0x47, 0xf3, 0xc3, 0x5d,
0x0b, 0xf7, 0x50, 0x29, 0x92, 0x7e, 0xf1, 0x8a, 0xf5, 0x5f, 0xcc, 0x5d, 0x69, 0x96, 0xa8, 0x2d,
0x4e, 0xb4, 0x98, 0xac, 0x88, 0x2c, 0x19, 0x1f, 0x47, 0x2a, 0x62, 0x3a, 0x38, 0xa6, 0x88, 0x7d,
0x47, 0xc6, 0xfc, 0xff, 0x72, 0x64, 0x94, 0x55, 0xa1, 0xa9, 0xa2, 0xad, 0xaa, 0x42, 0xe1, 0xf1,
0xab, 0xc2, 0x72, 0xca, 0x42, 0x0c, 0x46, 0xfb, 0x17, 0x0b, 0x21, 0x39, 0xf7, 0x73, 0x5f, 0x84,
0x6c, 0x20, 0x93, 0x44, 0x0e, 0xff, 0x3a, 0xa1, 0xd2, 0x24, 0x91, 0x08, 0xa2, 0x2c, 0x43, 0x5d,
0x2b, 0xf7, 0x48, 0x5d, 0x2b, 0xbf, 0x6f, 0xd7, 0x7a, 0x07, 0xcd, 0x71, 0xde, 0xde, 0x60, 0x7e,
0x9f, 0x0a, 0x58, 0x83, 0x81, 0x7e, 0xfb, 0xc7, 0xf4, 0x91, 0xb9, 0xcd, 0xcd, 0x8b, 0x99, 0x91,
0x0c, 0x63, 0xed, 0x3f, 0x2c, 0x34, 0x9f, 0x7d, 0xc9, 0x13, 0x68, 0x17, 0xd7, 0x87, 0xdb, 0xc5,
0xca, 0x44, 0x45, 0x23, 0xf1, 0x7b, 0x8f, 0x8e, 0xf1, 0x83, 0x85, 0xe6, 0x86, 0x2a, 0x8b, 0xec,
0xf0, 0xf1, 0x9f, 0x22, 0xb5, 0xd4, 0x17, 0x96, 0x76, 0xf8, 0x46, 0x66, 0x22, 0x26, 0x4e, 0x96,
0x8d, 0x8e, 0xdf, 0x8f, 0x39, 0x46, 0xff, 0x23, 0xaf, 0x27, 0x06, 0x92, 0x61, 0x8c, 0xd2, 0x9a,
0x7f, 0xd4, 0xd2, 0x6a, 0xff, 0x6d, 0xa1, 0xd1, 0x1e, 0x2f, 0x33, 0xc2, 0x0f, 0x38, 0x78, 0x3d,
0x16, 0x3b, 0x3d, 0x93, 0xc5, 0x78, 0x55, 0xef, 0x93, 0x14, 0x81, 0xcf, 0x23, 0x14, 0xf7, 0x84,
0xcb, 0x59, 0xbe, 0xa5, 0x29, 0xbd, 0x99, 0x5a, 0x88, 0x81, 0xc2, 0x67, 0xd0, 0x8c, 0x07, 0x4c,
0x2c, 0xcb, 0x5b, 0x97, 0x3e, 0x1f, 0x71, 0x8f, 0x48, 0xf6, 0xba, 0xde, 0x23, 0xa9, 0x15, 0x3f,
0x8b, 0xa6, 0xb7, 0x61, 0xa0, 0x80, 0x05, 0x05, 0x2c, 0xcb, 0xbf, 0xc1, 0x6b, 0xf1, 0x16, 0x49,
0x6c, 0xd8, 0x46, 0x25, 0x8f, 0x2a, 0x54, 0x51, 0xa1, 0x90, 0x1a, 0xef, 0x2e, 0x28, 0x90, 0xb6,
0xb8, 0xce, 0x9d, 0x9d, 0xea, 0xd4, 0xdd, 0x9d, 0xea, 0xd4, 0xbd, 0x9d, 0xea, 0xd4, 0xcd, 0xdd,
0xaa, 0x75, 0x67, 0xb7, 0x6a, 0xdd, 0xdd, 0xad, 0x5a, 0xf7, 0x76, 0xab, 0xd6, 0x5f, 0xbb, 0x55,
0xeb, 0xf6, 0x83, 0xea, 0xd4, 0xc7, 0x33, 0xc9, 0x8d, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0xbf,
0x57, 0x11, 0xb6, 0xcd, 0x13, 0x00, 0x00,
// 1579 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcd, 0x8f, 0x1b, 0x45,
0x16, 0x9f, 0xb6, 0x3d, 0x5f, 0xcf, 0xf3, 0x95, 0x5a, 0x25, 0xeb, 0x9d, 0x48, 0x9e, 0x51, 0x47,
0xbb, 0x9b, 0x5d, 0x6d, 0xda, 0x9b, 0xec, 0x07, 0x5f, 0x12, 0x52, 0xda, 0x33, 0x90, 0x61, 0x26,
0xc9, 0x50, 0xe3, 0x80, 0x84, 0x10, 0xd0, 0xd3, 0xae, 0xd8, 0x9d, 0xb1, 0xbb, 0x9b, 0xaa, 0xb2,
0x23, 0x1f, 0x90, 0x82, 0xc4, 0x11, 0x50, 0x10, 0xff, 0x05, 0x47, 0xc4, 0x99, 0x03, 0x12, 0x22,
0xdc, 0x22, 0xc1, 0x21, 0x42, 0x68, 0x44, 0x26, 0x97, 0xfc, 0x0d, 0x39, 0xa1, 0xaa, 0xae, 0xae,
0x2e, 0xdb, 0x0c, 0x33, 0x89, 0x9d, 0x88, 0x9b, 0xfb, 0xbd, 0x5f, 0xbd, 0xdf, 0xeb, 0x57, 0xef,
0xab, 0x0d, 0x1b, 0x8d, 0x80, 0x37, 0x3b, 0xbb, 0x8e, 0x1f, 0xb5, 0x2b, 0x1e, 0x6d, 0x44, 0x31,
0x8d, 0x6e, 0xc8, 0x1f, 0xe7, 0xfc, 0x7a, 0x25, 0xde, 0x6b, 0x54, 0xbc, 0x38, 0x60, 0x15, 0x2f,
0x8e, 0x5b, 0x81, 0xef, 0xf1, 0x20, 0x0a, 0x2b, 0xdd, 0xf3, 0x5e, 0x2b, 0x6e, 0x7a, 0xe7, 0x2b,
0x0d, 0x12, 0x12, 0xea, 0x71, 0x52, 0x77, 0x62, 0x1a, 0xf1, 0x08, 0xbd, 0x90, 0x99, 0x72, 0x52,
0x53, 0xf2, 0xc7, 0xbb, 0x7e, 0xdd, 0x89, 0xf7, 0x1a, 0x8e, 0x30, 0xe5, 0x18, 0xa6, 0x9c, 0xd4,
0xd4, 0xf2, 0x39, 0xc3, 0x8b, 0x46, 0xd4, 0x88, 0x2a, 0xd2, 0xe2, 0x6e, 0xe7, 0xba, 0x7c, 0x92,
0x0f, 0xf2, 0x57, 0xc2, 0xb4, 0xfc, 0xdf, 0xbd, 0xe7, 0x99, 0x13, 0x44, 0xc2, 0xb7, 0xb6, 0xe7,
0x37, 0x83, 0x90, 0xd0, 0x5e, 0xe6, 0x6c, 0x9b, 0x70, 0xaf, 0xd2, 0x1d, 0xf2, 0x6f, 0xb9, 0x72,
0xd8, 0x29, 0xda, 0x09, 0x79, 0xd0, 0x26, 0x43, 0x07, 0xfe, 0x7f, 0xd4, 0x01, 0xe6, 0x37, 0x49,
0xdb, 0x1b, 0x3a, 0xf7, 0x9f, 0xc3, 0xce, 0x75, 0x78, 0xd0, 0xaa, 0x04, 0x21, 0x67, 0x9c, 0x0e,
0x1e, 0xb2, 0x7f, 0xc8, 0x41, 0xf1, 0x62, 0x16, 0x1b, 0xf4, 0x1e, 0xcc, 0x88, 0x17, 0xa9, 0x7b,
0xdc, 0x2b, 0x59, 0xab, 0xd6, 0xd9, 0xe2, 0x85, 0x7f, 0x3b, 0x89, 0x5d, 0xc7, 0xb4, 0x9b, 0x05,
0x56, 0xa0, 0x9d, 0xee, 0x79, 0xe7, 0xea, 0xee, 0x0d, 0xe2, 0xf3, 0xcb, 0x84, 0x7b, 0x2e, 0xba,
0xb3, 0xbf, 0x32, 0x71, 0xb0, 0xbf, 0x02, 0x99, 0x0c, 0x6b, 0xab, 0xa8, 0x05, 0x05, 0x16, 0x13,
0xbf, 0x94, 0x93, 0xd6, 0x5f, 0x73, 0x9e, 0xf8, 0xfa, 0x1c, 0xc3, 0xef, 0x9d, 0x98, 0xf8, 0xee,
0x9c, 0xe2, 0x2d, 0x88, 0x27, 0x2c, 0x59, 0x10, 0x87, 0x29, 0xc6, 0x3d, 0xde, 0x61, 0xa5, 0xbc,
0xe4, 0xdb, 0x1a, 0x13, 0x9f, 0xb4, 0xe9, 0x2e, 0x28, 0xc6, 0xa9, 0xe4, 0x19, 0x2b, 0x2e, 0xfb,
0x7d, 0x38, 0x65, 0x80, 0xd7, 0x08, 0xe3, 0x41, 0x98, 0xc4, 0xf7, 0x6f, 0x30, 0xc5, 0x08, 0xed,
0x12, 0x2a, 0xa3, 0x3b, 0x6b, 0x58, 0x90, 0x52, 0xac, 0xb4, 0xa8, 0x02, 0xb3, 0xa1, 0xd7, 0x26,
0x2c, 0xf6, 0x7c, 0x22, 0x43, 0x35, 0xeb, 0x9e, 0x50, 0xd0, 0xd9, 0x2b, 0xa9, 0x02, 0x67, 0x18,
0xfb, 0x67, 0x0b, 0x16, 0x0d, 0xce, 0xad, 0x80, 0x71, 0xf4, 0xf6, 0xd0, 0x65, 0x3a, 0xc7, 0xbb,
0x4c, 0x71, 0x5a, 0x5e, 0xe5, 0x92, 0xe2, 0x9c, 0x49, 0x25, 0xc6, 0x45, 0xee, 0xc1, 0x64, 0xc0,
0x49, 0x9b, 0x95, 0x72, 0xab, 0xf9, 0xb3, 0xc5, 0x0b, 0xaf, 0x8c, 0x27, 0xb2, 0xee, 0xbc, 0xa2,
0x9c, 0xdc, 0x10, 0xc6, 0x71, 0xc2, 0x61, 0x7f, 0x9a, 0x87, 0x13, 0x66, 0xfc, 0xa3, 0x0e, 0xf5,
0x09, 0xfa, 0x07, 0x4c, 0x53, 0x12, 0x47, 0xd7, 0xf0, 0x96, 0x0a, 0xe7, 0xa2, 0x3a, 0x3c, 0x8d,
0x13, 0x31, 0x4e, 0xf5, 0x68, 0x15, 0x0a, 0xb1, 0xc7, 0x9b, 0x2a, 0x96, 0x3a, 0x55, 0xb6, 0x3d,
0xde, 0xc4, 0x52, 0x83, 0xfe, 0x07, 0x45, 0x12, 0x76, 0x03, 0x1a, 0x85, 0x6d, 0x12, 0x72, 0x99,
0x2f, 0xb3, 0xee, 0x9f, 0x14, 0xb0, 0xb8, 0x9e, 0xa9, 0xb0, 0x89, 0x43, 0x2f, 0xc3, 0x02, 0xf7,
0x68, 0x83, 0x70, 0x4c, 0xba, 0x01, 0x0b, 0xa2, 0xb0, 0x54, 0x90, 0x27, 0x4f, 0xa9, 0x93, 0x0b,
0xb5, 0x3e, 0x2d, 0x1e, 0x40, 0xa3, 0xaf, 0x2c, 0x38, 0xed, 0x47, 0xed, 0x38, 0x0a, 0x49, 0xc8,
0xb7, 0x3d, 0xea, 0xb5, 0x09, 0x27, 0xf4, 0x6a, 0x97, 0x50, 0x1a, 0xd4, 0x09, 0x2b, 0x4d, 0xca,
0xe8, 0x5e, 0x1e, 0x21, 0xba, 0xd5, 0x21, 0xeb, 0xee, 0x19, 0xe5, 0xdc, 0xe9, 0xea, 0xe1, 0xcc,
0xf8, 0xf7, 0xdc, 0xb2, 0xbf, 0xc9, 0xf5, 0xe5, 0xdb, 0x4e, 0x5a, 0x6c, 0xf2, 0x62, 0x54, 0xb6,
0x8d, 0xab, 0xd8, 0xa4, 0x4d, 0xa3, 0x54, 0xe4, 0x33, 0x56, 0x5c, 0xe8, 0x23, 0x0b, 0x8a, 0xf5,
0xac, 0xc4, 0x54, 0x63, 0x79, 0x7d, 0x3c, 0xdc, 0x46, 0xed, 0xba, 0x8b, 0x22, 0x0f, 0x0c, 0x01,
0x36, 0x69, 0xd1, 0x05, 0x00, 0xd6, 0x0b, 0xfd, 0xed, 0xa8, 0x15, 0xf8, 0x3d, 0x95, 0x3d, 0xba,
0x13, 0xee, 0x68, 0x0d, 0x36, 0x50, 0xf6, 0xd7, 0x03, 0x59, 0x2d, 0xbb, 0x07, 0xfa, 0xcc, 0x82,
0x25, 0x11, 0x7a, 0x8f, 0x06, 0x2c, 0x0a, 0x31, 0x61, 0x9d, 0x16, 0x57, 0x11, 0xdd, 0x1c, 0x31,
0x0d, 0x4c, 0x93, 0x6e, 0x49, 0x79, 0xb7, 0x34, 0xa8, 0xc1, 0x43, 0xf4, 0xe8, 0xb6, 0x05, 0x4b,
0x94, 0xf8, 0x24, 0xe4, 0x6b, 0x24, 0x6e, 0x45, 0x3d, 0x59, 0x22, 0x49, 0xe1, 0x6f, 0x8c, 0xe0,
0x53, 0x66, 0x6c, 0x23, 0xbc, 0x1e, 0xb9, 0x7f, 0x51, 0x1e, 0x9d, 0xc0, 0x03, 0x54, 0x0c, 0x0f,
0xb1, 0xa3, 0x0f, 0x2d, 0x80, 0x38, 0x4d, 0x4c, 0xd1, 0xdf, 0x9f, 0x42, 0x9d, 0xe8, 0x0b, 0xd4,
0x22, 0x86, 0x0d, 0x52, 0xfb, 0x81, 0x05, 0x27, 0x8d, 0x0b, 0x7c, 0xd3, 0xe3, 0x7e, 0x73, 0xbd,
0x2b, 0xbc, 0xdb, 0x84, 0x02, 0xef, 0xc5, 0x44, 0xf5, 0xa5, 0xe7, 0xd2, 0x7e, 0x53, 0xeb, 0xc5,
0xe4, 0xd1, 0xfe, 0xca, 0xdf, 0x0f, 0x9b, 0xd5, 0x37, 0x85, 0x05, 0x47, 0x9a, 0x10, 0x50, 0x2c,
0x8d, 0xa0, 0x0f, 0xa0, 0x68, 0x78, 0xac, 0x32, 0x7c, 0x5c, 0x0d, 0x57, 0xb7, 0x38, 0x43, 0x88,
0x4d, 0x3e, 0xfb, 0x5b, 0x0b, 0xa6, 0xab, 0xad, 0x0e, 0xe3, 0x84, 0x1e, 0x7b, 0x80, 0xad, 0x42,
0x41, 0x0c, 0xa7, 0xc1, 0x7e, 0x2b, 0x66, 0x17, 0x96, 0x1a, 0x14, 0xc3, 0x94, 0x1f, 0x85, 0xd7,
0x83, 0x86, 0x1a, 0xcd, 0x97, 0x46, 0xb9, 0xba, 0xc4, 0xbb, 0xaa, 0xb4, 0x97, 0xf9, 0x94, 0x3c,
0x63, 0xc5, 0x63, 0x7f, 0x99, 0x83, 0xf9, 0x3e, 0x24, 0xfa, 0x17, 0xcc, 0x74, 0x18, 0xa1, 0xd2,
0xd3, 0xe4, 0x7d, 0xf4, 0xc4, 0xbb, 0xa6, 0xe4, 0x58, 0x23, 0x04, 0x3a, 0xf6, 0x18, 0xbb, 0x19,
0xd1, 0xba, 0x7a, 0x2f, 0x8d, 0xde, 0x56, 0x72, 0xac, 0x11, 0x62, 0x9e, 0xec, 0x12, 0x8f, 0x12,
0x5a, 0x8b, 0xf6, 0x48, 0x38, 0x38, 0x4f, 0xdc, 0x4c, 0x85, 0x4d, 0x1c, 0xfa, 0xc4, 0x82, 0x45,
0xde, 0x62, 0xd5, 0x56, 0x40, 0x42, 0x9e, 0xb8, 0x29, 0x27, 0xca, 0x68, 0xbb, 0x52, 0x6d, 0x6b,
0xc7, 0xb4, 0xe8, 0xfe, 0x59, 0xf9, 0xb1, 0x38, 0xa0, 0xc0, 0x83, 0xdc, 0xf6, 0x8f, 0x16, 0x14,
0x55, 0xd0, 0x9e, 0xc1, 0x52, 0xd1, 0xe8, 0x5f, 0x2a, 0xdc, 0xd1, 0x73, 0xe2, 0x90, 0x85, 0xe2,
0x8b, 0x02, 0x0c, 0xf5, 0x3d, 0xf4, 0x0e, 0x40, 0xd2, 0xf9, 0x48, 0xfd, 0x62, 0xda, 0x72, 0xff,
0x79, 0xbc, 0xb7, 0xab, 0x05, 0x6d, 0x92, 0xb5, 0x8b, 0xaa, 0xb6, 0x82, 0x0d, 0x8b, 0xe8, 0x96,
0x95, 0x11, 0xd4, 0x22, 0x55, 0xc7, 0xe3, 0x9d, 0x92, 0x43, 0x2e, 0xd4, 0x22, 0x6c, 0x70, 0x1a,
0xf5, 0x9b, 0x3f, 0xfe, 0x02, 0x5a, 0x38, 0x7a, 0x01, 0x45, 0x2f, 0xea, 0x4d, 0x7b, 0x52, 0xa2,
0xed, 0xfe, 0xdd, 0xf8, 0x51, 0xdf, 0x9c, 0xe9, 0xdf, 0x97, 0x51, 0x0f, 0x66, 0x29, 0x49, 0xc6,
0x39, 0x2b, 0x4d, 0xc9, 0x9b, 0x1f, 0xa5, 0x1b, 0x60, 0x65, 0x4b, 0xb0, 0x90, 0xcc, 0xed, 0x54,
0xcc, 0x70, 0xc6, 0x86, 0xce, 0xc0, 0x24, 0xa1, 0x34, 0xa2, 0xa5, 0x69, 0xe9, 0xb5, 0x4e, 0x96,
0x75, 0x21, 0xc4, 0x89, 0xce, 0xfe, 0xd8, 0x02, 0x34, 0x3c, 0x1d, 0x44, 0x8c, 0xf4, 0x8a, 0xa4,
0xda, 0x87, 0x26, 0xd3, 0x70, 0x9c, 0x61, 0x8e, 0xd1, 0x14, 0xcf, 0xc0, 0x64, 0xd7, 0x6b, 0x75,
0x88, 0xba, 0x1d, 0xed, 0xce, 0x1b, 0x42, 0x88, 0x13, 0x9d, 0xfd, 0x30, 0x0f, 0x0b, 0xfd, 0x93,
0x13, 0x75, 0x60, 0x4a, 0x8e, 0x25, 0x56, 0xb2, 0x9e, 0xc6, 0x1c, 0xd4, 0x59, 0x22, 0x45, 0x0c,
0x2b, 0x32, 0xd1, 0x11, 0x69, 0xba, 0xf6, 0x0e, 0x74, 0x44, 0xbd, 0xf0, 0x6a, 0xc4, 0x91, 0xab,
0x6e, 0xfe, 0x0f, 0xb9, 0xea, 0x8a, 0xae, 0x50, 0x97, 0xd1, 0x96, 0x5d, 0xa1, 0xf0, 0xe4, 0x5d,
0x61, 0x4d, 0x5b, 0xc1, 0x86, 0x45, 0xb4, 0x0c, 0xb9, 0xa0, 0x2e, 0xab, 0x26, 0xef, 0x82, 0xc2,
0xe6, 0x36, 0xd6, 0x70, 0x2e, 0xa8, 0xdb, 0xdf, 0x59, 0x00, 0xe2, 0x5b, 0x86, 0x05, 0x3c, 0xa2,
0x3d, 0x91, 0x40, 0xe2, 0x83, 0x46, 0x25, 0x9b, 0x4e, 0x20, 0x81, 0xc0, 0x52, 0xd3, 0x37, 0xd1,
0x72, 0x8f, 0x35, 0xd1, 0xf2, 0x47, 0x4e, 0xb4, 0x97, 0x60, 0x9e, 0xb1, 0xe6, 0x36, 0x0d, 0xba,
0x1e, 0x27, 0x9b, 0xa4, 0xa7, 0xfa, 0xc2, 0x49, 0x75, 0x64, 0x7e, 0x67, 0xe7, 0x52, 0xa6, 0xc4,
0xfd, 0x58, 0xfb, 0x27, 0x0b, 0x16, 0xb2, 0x37, 0x79, 0x06, 0xa3, 0xe4, 0x46, 0xff, 0x28, 0x59,
0x1f, 0xa9, 0xa1, 0xa4, 0x7e, 0x1f, 0x36, 0x4d, 0x2c, 0x98, 0x4b, 0xdb, 0xcb, 0x95, 0xa8, 0x2e,
0xeb, 0x58, 0xf4, 0xb6, 0x74, 0xab, 0xd0, 0xa7, 0x64, 0x4b, 0xc2, 0x89, 0x0e, 0x75, 0x60, 0xc6,
0x6f, 0x06, 0xad, 0x3a, 0x25, 0xa1, 0x72, 0xf2, 0xd5, 0x31, 0x74, 0x3d, 0xc1, 0x9f, 0x05, 0xa6,
0xaa, 0x08, 0xb0, 0xa6, 0xb2, 0xbf, 0xcf, 0xc1, 0x7c, 0x5f, 0x8b, 0x14, 0xab, 0x4a, 0xf2, 0x55,
0xba, 0x63, 0xf8, 0xac, 0x57, 0x95, 0x5a, 0xa6, 0xc2, 0x26, 0x4e, 0xf4, 0xbf, 0x56, 0xd0, 0x4d,
0x6c, 0x0c, 0xfe, 0x49, 0xb1, 0x95, 0x2a, 0x70, 0x86, 0x31, 0x66, 0x44, 0xfe, 0xb1, 0x67, 0xc4,
0xe7, 0x16, 0x20, 0xf9, 0x0a, 0xc2, 0xb2, 0x6e, 0xe5, 0xa5, 0xc2, 0x78, 0xe3, 0xb6, 0xac, 0x3c,
0x42, 0xd5, 0x21, 0x2a, 0xfc, 0x1b, 0xf4, 0xf6, 0x43, 0x0b, 0x06, 0x57, 0x28, 0x51, 0x54, 0x41,
0xc8, 0x88, 0xdf, 0xa1, 0x49, 0x28, 0x67, 0xb2, 0xdb, 0xd8, 0x50, 0x72, 0xac, 0x11, 0xf2, 0xbb,
0x51, 0x8e, 0xdc, 0x2b, 0x59, 0xc9, 0x66, 0xdf, 0x8d, 0x5a, 0x83, 0x0d, 0x14, 0x3a, 0x0b, 0x33,
0x3e, 0xa1, 0x7c, 0x4d, 0x14, 0x8e, 0x88, 0xe4, 0x9c, 0x3b, 0x27, 0xef, 0x5a, 0xc9, 0xb0, 0xd6,
0xa2, 0xbf, 0xc2, 0xf4, 0x1e, 0xe9, 0x49, 0x60, 0x41, 0x02, 0x8b, 0x07, 0xfb, 0x2b, 0xd3, 0x9b,
0x89, 0x08, 0xa7, 0x3a, 0x64, 0xc3, 0x94, 0xef, 0x49, 0xd4, 0xa4, 0x44, 0x81, 0xdc, 0x9e, 0x2f,
0x4a, 0x90, 0xd2, 0xb8, 0xce, 0x9d, 0xfb, 0xe5, 0x89, 0xbb, 0xf7, 0xcb, 0x13, 0xf7, 0xee, 0x97,
0x27, 0x6e, 0x1d, 0x94, 0xad, 0x3b, 0x07, 0x65, 0xeb, 0xee, 0x41, 0xd9, 0xba, 0x77, 0x50, 0xb6,
0x7e, 0x39, 0x28, 0x5b, 0xb7, 0x1f, 0x94, 0x27, 0xde, 0x9a, 0x49, 0xe3, 0xfa, 0x6b, 0x00, 0x00,
0x00, 0xff, 0xff, 0xdc, 0x24, 0x80, 0x9e, 0xe4, 0x15, 0x00, 0x00,
}

View File

@@ -69,6 +69,9 @@ message ApplicationSpec {
// Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml
// This field is optional. If omitted, uses the server and namespace defined in the environment
optional ApplicationDestination destination = 2;
// SyncPolicy dictates whether we auto-sync based on the delta between the tracked branch and live state
optional string syncPolicy = 3;
}
// ApplicationStatus contains information about application status in target environment.
@@ -76,6 +79,8 @@ message ApplicationStatus {
optional ComparisonResult comparisonResult = 1;
repeated DeploymentInfo recentDeployment = 2;
repeated ComponentParameter parameters = 3;
}
// ApplicationWatchEvent contains information about application change.
@@ -161,6 +166,8 @@ message DeploymentInfo {
repeated ComponentParameter componentParameterOverrides = 3;
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time deployedAt = 4;
optional int64 id = 5;
}
// Repository is a Git repository holding application configurations
@@ -181,6 +188,13 @@ message RepositoryList {
repeated Repository items = 2;
}
// ResourceNode contains information about live resource and its children
message ResourceNode {
optional string state = 1;
repeated ResourceNode children = 2;
}
// ResourceState holds the target state of a resource and live state of a resource
message ResourceState {
optional string targetState = 1;
@@ -188,6 +202,8 @@ message ResourceState {
optional string liveState = 2;
optional string status = 3;
repeated ResourceNode childLiveResources = 4;
}
// TLSClientConfig contains settings to enable transport layer security

View File

@@ -17,6 +17,7 @@ type DeploymentInfo struct {
Revision string `json:"revision" protobuf:"bytes,2,opt,name=revision"`
ComponentParameterOverrides []ComponentParameter `json:"componentParameterOverrides,omitempty" protobuf:"bytes,3,opt,name=componentParameterOverrides"`
DeployedAt metav1.Time `json:"deployedAt" protobuf:"bytes,4,opt,name=deployedAt"`
ID int64 `json:"id" protobuf:"bytes,5,opt,name=id"`
}
// Application is a definition of Application resource.
@@ -57,6 +58,8 @@ type ApplicationSpec struct {
// Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml
// This field is optional. If omitted, uses the server and namespace defined in the environment
Destination *ApplicationDestination `json:"destination,omitempty" protobuf:"bytes,2,opt,name=destination"`
// SyncPolicy dictates whether we auto-sync based on the delta between the tracked branch and live state
SyncPolicy string `json:"syncPolicy,omitempty" protobuf:"bytes,3,opt,name=syncPolicy"`
}
// ComponentParameter contains information about component parameter value
@@ -102,8 +105,9 @@ const (
// ApplicationStatus contains information about application status in target environment.
type ApplicationStatus struct {
ComparisonResult ComparisonResult `json:"comparisonResult" protobuf:"bytes,1,opt,name=comparisonResult"`
RecentDeployments []DeploymentInfo `json:"recentDeployments" protobuf:"bytes,2,opt,name=recentDeployment"`
ComparisonResult ComparisonResult `json:"comparisonResult" protobuf:"bytes,1,opt,name=comparisonResult"`
RecentDeployments []DeploymentInfo `json:"recentDeployments" protobuf:"bytes,2,opt,name=recentDeployment"`
Parameters []ComponentParameter `json:"parameters,omitempty" protobuf:"bytes,3,opt,name=parameters"`
}
// ComparisonResult is a comparison result of application spec and deployed application.
@@ -117,11 +121,18 @@ type ComparisonResult struct {
Error string `json:"error,omitempty" protobuf:"bytes,7,opt,name=error"`
}
// ResourceNode contains information about live resource and its children
type ResourceNode struct {
State string `json:"state,omitempty" protobuf:"bytes,1,opt,name=state"`
Children []ResourceNode `json:"children,omitempty" protobuf:"bytes,2,opt,name=children"`
}
// ResourceState holds the target state of a resource and live state of a resource
type ResourceState struct {
TargetState string `json:"targetState,omitempty" protobuf:"bytes,1,opt,name=targetState"`
LiveState string `json:"liveState,omitempty" protobuf:"bytes,2,opt,name=liveState"`
Status ComparisonStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
TargetState string `json:"targetState,omitempty" protobuf:"bytes,1,opt,name=targetState"`
LiveState string `json:"liveState,omitempty" protobuf:"bytes,2,opt,name=liveState"`
Status ComparisonStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
ChildLiveResources []ResourceNode `json:"childLiveResources,omitempty" protobuf:"bytes,4,opt,name=childLiveResources"`
}
// Cluster is the definition of a cluster resource
@@ -181,9 +192,9 @@ type TLSClientConfig struct {
// Repository is a Git repository holding application configurations
type Repository struct {
Repo string `json:"repo" protobuf:"bytes,1,opt,name=repo"`
Username string `json:"username" protobuf:"bytes,2,opt,name=username"`
Password string `json:"password" protobuf:"bytes,3,opt,name=password"`
SSHPrivateKey string `json:"sshPrivateKey" protobuf:"bytes,4,opt,name=sshPrivateKey"`
Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
}
// RepositoryList is a collection of Repositories.

View File

@@ -143,6 +143,11 @@ func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Parameters != nil {
in, out := &in.Parameters, &out.Parameters
*out = make([]ComponentParameter, len(*in))
copy(*out, *in)
}
return
}
@@ -239,7 +244,9 @@ func (in *ComparisonResult) DeepCopyInto(out *ComparisonResult) {
if in.Resources != nil {
in, out := &in.Resources, &out.Resources
*out = make([]ResourceState, len(*in))
copy(*out, *in)
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
@@ -335,9 +342,39 @@ func (in *RepositoryList) DeepCopy() *RepositoryList {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceNode) DeepCopyInto(out *ResourceNode) {
*out = *in
if in.Children != nil {
in, out := &in.Children, &out.Children
*out = make([]ResourceNode, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceNode.
func (in *ResourceNode) DeepCopy() *ResourceNode {
if in == nil {
return nil
}
out := new(ResourceNode)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceState) DeepCopyInto(out *ResourceState) {
*out = *in
if in.ChildLiveResources != nil {
in, out := &in.ChildLiveResources, &out.ChildLiveResources
*out = make([]ResourceNode, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}

View File

@@ -8,11 +8,13 @@ import (
"strings"
"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/git"
ksutil "github.com/argoproj/argo-cd/util/ksonnet"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/kubernetes"
)
@@ -34,16 +36,43 @@ func NewService(namespace string, kubeClient kubernetes.Interface, gitClient git
}
}
func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*ManifestResponse, error) {
appRepoPath := path.Join(os.TempDir(), strings.Replace(q.Repo.Repo, "/", "_", -1))
func (s *Service) GetKsonnetApp(ctx context.Context, in *KsonnetAppRequest) (*KsonnetAppResponse, error) {
appRepoPath := tempRepoPath(in.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer func() {
err := s.gitClient.Reset(appRepoPath)
if err != nil {
log.Warn(err)
defer s.unlockAndResetRepoPath(appRepoPath)
ksApp, err := s.getAppSpec(*in.Repo, appRepoPath, in.Revision, in.Path)
if err != nil {
return nil, err
}
return ksAppToResponse(ksApp)
}
// ksAppToResponse converts a Ksonnet app instance to a API response object
func ksAppToResponse(ksApp ksutil.KsonnetApp) (*KsonnetAppResponse, error) {
var appRes KsonnetAppResponse
appRes.Environments = make(map[string]*KsonnetEnvironment)
for envName, env := range ksApp.Spec().Environments {
if env.Destination == nil {
return nil, fmt.Errorf("Environment '%s' has no destination defined", envName)
}
s.repoLock.Unlock(appRepoPath)
}()
envRes := KsonnetEnvironment{
Name: envName,
K8SVersion: env.KubernetesVersion,
Path: env.Path,
Destination: &KsonnetEnvironmentDestination{
Server: env.Destination.Server,
Namespace: env.Destination.Namespace,
},
}
appRes.Environments[envName] = &envRes
}
return &appRes, nil
}
func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*ManifestResponse, error) {
appRepoPath := tempRepoPath(q.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer s.unlockAndResetRepoPath(appRepoPath)
err := s.gitClient.CloneOrFetch(q.Repo.Repo, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey, appRepoPath)
if err != nil {
@@ -82,12 +111,10 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
manifests := make([]string, len(targetObjs))
for i, target := range targetObjs {
if q.AppLabel != "" {
labels := target.GetLabels()
if labels == nil {
labels = make(map[string]string)
err = s.setAppLabels(target, q.AppLabel)
if err != nil {
return nil, err
}
labels[common.LabelApplicationName] = q.AppLabel
target.SetLabels(labels)
}
manifestStr, err := json.Marshal(target.Object)
if err != nil {
@@ -103,9 +130,48 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
}, nil
}
func (s *Service) getAppSpec(repo v1alpha1.Repository, appRepoPath, revision, subPath string) (ksutil.KsonnetApp, error) {
err := s.gitClient.CloneOrFetch(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey, appRepoPath)
if err != nil {
return nil, err
}
_, err = s.gitClient.Checkout(appRepoPath, revision)
if err != nil {
return nil, err
}
appPath := path.Join(appRepoPath, subPath)
ksApp, err := ksutil.NewKsonnetApp(appPath)
if err != nil {
return nil, err
}
return ksApp, nil
}
func (s *Service) setAppLabels(target *unstructured.Unstructured, appName string) error {
labels := target.GetLabels()
if labels == nil {
labels = make(map[string]string)
}
labels[common.LabelApplicationName] = appName
target.SetLabels(labels)
// special case for deployment: make sure that derived replicaset and pod has application label
if target.GetKind() == "Deployment" {
labels, ok := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
if ok {
if labels == nil {
labels = make(map[string]interface{})
}
labels[common.LabelApplicationName] = appName
}
unstructured.SetNestedMap(target.UnstructuredContent(), labels, "spec", "template", "metadata", "labels")
}
return nil
}
// GetEnvParams retrieves Ksonnet environment params in specified repo name and revision
func (s *Service) GetEnvParams(c context.Context, q *EnvParamsRequest) (*EnvParamsResponse, error) {
appRepoPath := path.Join(os.TempDir(), strings.Replace(q.Repo.Repo, "/", "_", -1))
appRepoPath := tempRepoPath(q.Repo.Repo)
s.repoLock.Lock(appRepoPath)
defer s.repoLock.Unlock(appRepoPath)
@@ -133,3 +199,18 @@ func (s *Service) GetEnvParams(c context.Context, q *EnvParamsRequest) (*EnvPara
Params: target,
}, 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))
}
// unlockAndResetRepoPath will reset any local changes in a local git repo and unlock the path
// so that other workers can use the local repo
func (s *Service) unlockAndResetRepoPath(appRepoPath string) {
err := s.gitClient.Reset(appRepoPath)
if err != nil {
log.Warn(err)
}
s.repoLock.Unlock(appRepoPath)
}

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,45 @@ message EnvParamsResponse {
repeated github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ComponentParameter params = 1;
}
// KsonnetAppRequest is a query for ksonnet app
message KsonnetAppRequest {
github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.Repository repo = 1;
string revision = 2;
string path = 3;
}
// KsonnetAppResponse contains Ksonnet app response
// This roughly reflects: ksonnet/ksonnet/metadata/app/schema.go
// NOTE: we may expose ksonnet apps from API server, in which case these definitions will move to
// a more public place. For now, these types are only used internally.
message KsonnetAppResponse {
string name = 1;
map<string, KsonnetEnvironment> environments = 2;
}
message KsonnetEnvironment {
// Name is the user defined name of an environment
string name = 1;
// KubernetesVersion is the kubernetes version the targetted cluster is running on.
string k8sVersion = 2;
// Path is the relative project path containing metadata for this environment.
string path = 3;
// Destination stores the cluster address that this environment points to.
KsonnetEnvironmentDestination destination = 4;
// Targets contain the relative component paths that this environment
//repeated string targets = X;
}
message KsonnetEnvironmentDestination {
// Server is the Kubernetes server that the cluster is running on.
string server = 1;
// Namespace is the namespace of the Kubernetes server that targets should be deployed to
string namespace = 2;
}
// ManifestService
service RepositoryService {
@@ -46,4 +85,9 @@ service RepositoryService {
// Retrieve Ksonnet environment params in specified repo name and revision
rpc GetEnvParams(EnvParamsRequest) returns (EnvParamsResponse) {
}
// Retrieve Ksonnet environment params in specified repo name and revision
rpc GetKsonnetApp(KsonnetAppRequest) returns (KsonnetAppResponse) {
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"k8s.io/client-go/kubernetes"
)
@@ -44,5 +45,8 @@ func (a *ArgoCDRepoServer) CreateGRPC(gitClient git.Client) *grpc.Server {
manifestService := repository.NewService(a.ns, a.kubeclientset, gitClient)
repository.RegisterRepositoryServiceServer(server, manifestService)
// Register reflection service on gRPC server.
reflection.Register(server)
return server
}

View File

@@ -1,11 +1,14 @@
package application
import (
"bufio"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/controller"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
@@ -14,15 +17,19 @@ import (
apirepository "github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/util"
argoutil "github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/diff"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/kube"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
apiv1 "k8s.io/api/core/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"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/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
const (
@@ -38,6 +45,7 @@ type Server struct {
// TODO(jessesuen): move common cluster code to shared libraries
clusterService cluster.ClusterServiceServer
repoService apirepository.RepositoryServiceServer
appComparator controller.AppComparator
}
// NewServer returns a new instance of the Application service
@@ -56,6 +64,7 @@ func NewServer(
clusterService: clusterService,
repoClientset: repoClientset,
repoService: repoService,
appComparator: controller.NewKsonnetAppComparator(clusterService),
}
}
@@ -64,48 +73,63 @@ func (s *Server) List(ctx context.Context, q *ApplicationQuery) (*appv1.Applicat
return s.appclientset.ArgoprojV1alpha1().Applications(s.ns).List(metav1.ListOptions{})
}
// Create creates a application
// Create creates an application
func (s *Server) Create(ctx context.Context, a *appv1.Application) (*appv1.Application, error) {
err := s.validateApp(ctx, a)
if err != nil {
return nil, err
}
return s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Create(a)
}
// Get returns a application by name
// Get returns an application by name
func (s *Server) Get(ctx context.Context, q *ApplicationQuery) (*appv1.Application, error) {
return s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(q.Name, metav1.GetOptions{})
}
// Update updates a application
// Update updates an application
func (s *Server) Update(ctx context.Context, a *appv1.Application) (*appv1.Application, error) {
err := s.validateApp(ctx, a)
if err != nil {
return nil, err
}
return s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(a)
}
// Delete removes an application and all associated resources
func (s *Server) Delete(ctx context.Context, q *DeleteApplicationRequest) (*ApplicationResponse, error) {
err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Delete(q.Name, &metav1.DeleteOptions{})
var err error
server := q.Server
namespace := q.Namespace
if server == "" || namespace == "" {
server, namespace, err = s.getApplicationDestination(ctx, q.Name)
if err != nil && !apierr.IsNotFound(err) && !q.Force {
return nil, err
}
}
if server != "" && namespace != "" {
clst, err := s.clusterService.Get(ctx, &cluster.ClusterQuery{Server: server})
if err != nil && !q.Force {
return nil, err
}
if clst != nil {
config := clst.RESTConfig()
err = kube.DeleteResourceWithLabel(config, namespace, common.LabelApplicationName, q.Name)
if err != nil && !q.Force {
return nil, err
}
}
}
err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Delete(q.Name, &metav1.DeleteOptions{})
if err != nil && !apierr.IsNotFound(err) {
return nil, err
}
if q.Server != "" && q.Namespace != "" {
clst, err := s.clusterService.Get(ctx, &cluster.ClusterQuery{Server: q.Server})
if err != nil {
return nil, err
}
config := clst.RESTConfig()
err = kube.DeleteResourceWithLabel(config, q.Namespace, fmt.Sprintf("%s=%s", common.LabelApplicationName, q.Name))
if err != nil {
return nil, err
}
}
return &ApplicationResponse{}, nil
}
// ListPods returns pods in a application
func (s *Server) ListPods(ctx context.Context, q *ApplicationQuery) (*apiv1.PodList, error) {
// TODO: filter by the app label
return s.kubeclientset.CoreV1().Pods(s.ns).List(metav1.ListOptions{})
}
func (s *Server) Watch(q *ApplicationQuery, ws ApplicationService_WatchServer) error {
w, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Watch(metav1.ListOptions{})
if err != nil {
@@ -135,26 +159,250 @@ func (s *Server) Watch(q *ApplicationQuery, ws ApplicationService_WatchServer) e
return nil
}
// Sync syncs an application to its target state
func (s *Server) Sync(ctx context.Context, syncReq *ApplicationSyncRequest) (*ApplicationSyncResult, error) {
log.Infof("Syncing application %s", syncReq.Name)
app, err := s.Get(ctx, &ApplicationQuery{Name: syncReq.Name})
// validateApp will ensure:
// * the git repository is accessible
// * the git path contains a valid app.yaml
// * the specified environment exists
// * the referenced cluster has been added to ArgoCD
func (s *Server) validateApp(ctx context.Context, a *appv1.Application) error {
// Test the repo
conn, repoClient, err := s.repoClientset.NewRepositoryClient()
if err != nil {
return err
}
defer util.Close(conn)
repoRes, err := s.repoService.Get(ctx, &apirepository.RepoQuery{Repo: a.Spec.Source.RepoURL})
if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
// The repo has not been added to ArgoCD so we do not have credentials to access it.
// We support the mode where apps can be created from public repositories. Test the
// repo to make sure it is publically accessible
err = git.TestRepo(a.Spec.Source.RepoURL, "", "", "")
if err != nil {
return err
}
} else {
return err
}
}
// Verify app.yaml is functional
req := repository.KsonnetAppRequest{
Repo: &appv1.Repository{
Repo: a.Spec.Source.RepoURL,
},
Revision: a.Spec.Source.TargetRevision,
Path: a.Spec.Source.Path,
}
if repoRes != nil {
req.Repo.Username = repoRes.Username
req.Repo.Password = repoRes.Password
req.Repo.SSHPrivateKey = repoRes.SSHPrivateKey
}
ksAppRes, err := repoClient.GetKsonnetApp(ctx, &req)
if err != nil {
return err
}
// Verify the specified environment is defined in it
envSpec, ok := ksAppRes.Environments[a.Spec.Source.Environment]
if !ok {
return status.Errorf(codes.InvalidArgument, "environment '%s' does not exist in app", a.Spec.Source.Environment)
}
// Ensure the k8s cluster the app is referencing, is configured in ArgoCD
// NOTE: need to check if it was overridden in the destination spec
clusterURL := envSpec.Destination.Server
if a.Spec.Destination != nil && a.Spec.Destination.Server != "" {
clusterURL = a.Spec.Destination.Server
}
_, err = s.clusterService.Get(ctx, &cluster.ClusterQuery{Server: clusterURL})
if err != nil {
return err
}
return nil
}
func (s *Server) getApplicationClusterConfig(applicationName string) (*rest.Config, string, error) {
server, namespace, err := s.getApplicationDestination(context.Background(), applicationName)
if err != nil {
return nil, "", err
}
clst, err := s.clusterService.Get(context.Background(), &cluster.ClusterQuery{Server: server})
if err != nil {
return nil, "", err
}
config := clst.RESTConfig()
return config, namespace, err
}
func (s *Server) ensurePodBelongsToApp(applicationName string, podName, namespace string, kubeClientset *kubernetes.Clientset) error {
pod, err := kubeClientset.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
if err != nil {
return err
}
wrongPodError := fmt.Errorf("pod %s does not belong to application %s", podName, applicationName)
if pod.Labels == nil {
return wrongPodError
}
if value, ok := pod.Labels[common.LabelApplicationName]; !ok || value != applicationName {
return wrongPodError
}
return nil
}
func (s *Server) DeletePod(ctx context.Context, q *DeletePodQuery) (*ApplicationResponse, error) {
config, namespace, err := s.getApplicationClusterConfig(q.ApplicationName)
if err != nil {
return nil, err
}
revision := syncReq.Revision
if revision == "" {
kubeClientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
err = s.ensurePodBelongsToApp(q.ApplicationName, 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
}
return &ApplicationResponse{}, nil
}
func (s *Server) PodLogs(q *PodLogsQuery, ws ApplicationService_PodLogsServer) error {
config, namespace, err := s.getApplicationClusterConfig(q.ApplicationName)
if err != nil {
return err
}
kubeClientset, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
err = s.ensurePodBelongsToApp(q.ApplicationName, q.PodName, namespace, kubeClientset)
if err != nil {
return err
}
var sinceSeconds, tailLines *int64
if q.SinceSeconds > 0 {
sinceSeconds = &q.SinceSeconds
}
if q.TailLines > 0 {
tailLines = &q.TailLines
}
stream, err := kubeClientset.CoreV1().Pods(namespace).GetLogs(q.PodName, &v1.PodLogOptions{
Container: q.Container,
Follow: q.Follow,
Timestamps: true,
SinceSeconds: sinceSeconds,
SinceTime: q.SinceTime,
TailLines: tailLines,
}).Stream()
if err != nil {
return err
}
done := make(chan bool)
go func() {
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, " ")
logTime, err := time.Parse(time.RFC3339, parts[0])
metaLogTime := metav1.NewTime(logTime)
if err == nil {
lines := strings.Join(parts[1:], " ")
for _, line := range strings.Split(lines, "\r") {
if line != "" {
err = ws.Send(&LogEntry{
Content: line,
TimeStamp: &metaLogTime,
})
if err != nil {
log.Warnf("Unable to send stream message: %v", err)
}
}
}
}
}
done <- true
}()
select {
case <-ws.Context().Done():
util.Close(stream)
case <-done:
}
return nil
}
// Sync syncs an application to its target state
func (s *Server) Sync(ctx context.Context, syncReq *ApplicationSyncRequest) (*ApplicationSyncResult, error) {
return s.deployAndPersistDeploymentInfo(ctx, syncReq.Name, syncReq.Revision, nil, syncReq.DryRun, syncReq.Prune)
}
func (s *Server) Rollback(ctx context.Context, rollbackReq *ApplicationRollbackRequest) (*ApplicationSyncResult, error) {
app, err := s.Get(ctx, &ApplicationQuery{Name: rollbackReq.Name})
if err != nil {
return nil, err
}
var deploymentInfo *appv1.DeploymentInfo
for _, info := range app.Status.RecentDeployments {
if info.ID == rollbackReq.ID {
deploymentInfo = &info
break
}
}
if deploymentInfo == nil {
return nil, status.Errorf(codes.InvalidArgument, "application %s does not have deployment with id %v", rollbackReq.Name, rollbackReq.ID)
}
return s.deployAndPersistDeploymentInfo(ctx, rollbackReq.Name, deploymentInfo.Revision, &deploymentInfo.ComponentParameterOverrides, rollbackReq.DryRun, rollbackReq.Prune)
}
func (s *Server) deployAndPersistDeploymentInfo(
ctx context.Context, appName string, revision string, overrides *[]appv1.ComponentParameter, dryRun bool, prune bool) (*ApplicationSyncResult, error) {
log.Infof("Syncing application %s", appName)
app, err := s.Get(ctx, &ApplicationQuery{Name: appName})
if err != nil {
return nil, err
}
if revision != "" {
app.Spec.Source.TargetRevision = revision
}
if overrides != nil {
app.Spec.Source.ComponentParameterOverrides = *overrides
}
res, manifest, err := s.deploy(ctx, app, dryRun, prune)
if err != nil {
return nil, err
}
if !dryRun {
err = s.persistDeploymentInfo(ctx, appName, manifest.Revision, nil)
if err != nil {
return nil, err
}
}
return res, err
}
func (s *Server) persistDeploymentInfo(ctx context.Context, appName string, revision string, overrides *[]appv1.ComponentParameter) error {
app, err := s.Get(ctx, &ApplicationQuery{Name: appName})
if err != nil {
return err
}
repo := s.getRepo(ctx, app.Spec.Source.RepoURL)
conn, repoClient, err := s.repoClientset.NewRepositoryClient()
if err != nil {
return nil, err
return err
}
defer util.Close(conn)
// set fields in v1alpha/types.go
log.Infof("Retrieving deployment params for application %s", syncReq.Name)
log.Infof("Retrieving deployment params for application %s", appName)
envParams, err := repoClient.GetEnvParams(ctx, &repository.EnvParamsRequest{
Repo: repo,
Environment: app.Spec.Source.Environment,
@@ -163,37 +411,65 @@ func (s *Server) Sync(ctx context.Context, syncReq *ApplicationSyncRequest) (*Ap
})
if err != nil {
return nil, err
return err
}
log.Infof("Received deployment params: %s", envParams.Params)
res, manifest, err := s.deploy(ctx, app.Spec.Source, app.Spec.Destination, app.Name, syncReq.DryRun)
if err == nil {
// Persist app deployment info
params := make([]appv1.ComponentParameter, len(envParams.Params))
for i := range envParams.Params {
param := *envParams.Params[i]
params[i] = param
}
app, err = s.Get(ctx, &ApplicationQuery{Name: syncReq.Name})
if err != nil {
return nil, err
}
app.Status.RecentDeployments = append(app.Status.RecentDeployments, appv1.DeploymentInfo{
ComponentParameterOverrides: app.Spec.Source.ComponentParameterOverrides,
Revision: manifest.Revision,
Params: params,
DeployedAt: metav1.NewTime(time.Now()),
})
if len(app.Status.RecentDeployments) > maxRecentDeploymentsCnt {
app.Status.RecentDeployments = app.Status.RecentDeployments[:maxRecentDeploymentsCnt]
}
_, err = s.Update(ctx, app)
if err != nil {
return nil, err
}
params := make([]appv1.ComponentParameter, len(envParams.Params))
for i := range envParams.Params {
param := *envParams.Params[i]
params[i] = param
}
var nextId int64 = 0
if len(app.Status.RecentDeployments) > 0 {
nextId = app.Status.RecentDeployments[len(app.Status.RecentDeployments)-1].ID + 1
}
recentDeployments := append(app.Status.RecentDeployments, appv1.DeploymentInfo{
ComponentParameterOverrides: app.Spec.Source.ComponentParameterOverrides,
Revision: revision,
Params: params,
DeployedAt: metav1.NewTime(time.Now()),
ID: nextId,
})
if len(recentDeployments) > maxRecentDeploymentsCnt {
recentDeployments = recentDeployments[1 : maxRecentDeploymentsCnt+1]
}
patch, err := json.Marshal(map[string]map[string][]appv1.DeploymentInfo{
"status": {
"recentDeployments": recentDeployments,
},
})
if err != nil {
return err
}
_, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Patch(app.Name, types.MergePatchType, patch)
return err
}
func (s *Server) getApplicationDestination(ctx context.Context, name string) (string, string, error) {
app, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(name, metav1.GetOptions{})
if err != nil {
return "", "", err
} else {
repo := s.getRepo(ctx, app.Spec.Source.RepoURL)
conn, repoClient, err := s.repoClientset.NewRepositoryClient()
if err != nil {
return "", "", err
}
defer util.Close(conn)
manifestInfo, err := repoClient.GenerateManifest(ctx, &repository.ManifestRequest{
Repo: repo,
Environment: app.Spec.Source.Environment,
Path: app.Spec.Source.Path,
Revision: app.Spec.Source.TargetRevision,
AppLabel: app.Name,
})
if err != nil {
return "", "", err
}
server, namespace := argoutil.ResolveServerNamespace(app.Spec.Destination, manifestInfo)
return server, namespace, nil
}
return res, err
}
func (s *Server) getRepo(ctx context.Context, repoURL string) *appv1.Repository {
@@ -207,43 +483,35 @@ func (s *Server) getRepo(ctx context.Context, repoURL string) *appv1.Repository
func (s *Server) deploy(
ctx context.Context,
source appv1.ApplicationSource,
destination *appv1.ApplicationDestination,
appLabel string,
dryRun bool) (*ApplicationSyncResult, *repository.ManifestResponse, error) {
app *appv1.Application,
dryRun bool,
prune bool) (*ApplicationSyncResult, *repository.ManifestResponse, error) {
repo := s.getRepo(ctx, source.RepoURL)
repo := s.getRepo(ctx, app.Spec.Source.RepoURL)
conn, repoClient, err := s.repoClientset.NewRepositoryClient()
if err != nil {
return nil, nil, err
}
defer util.Close(conn)
overrides := make([]*appv1.ComponentParameter, len(source.ComponentParameterOverrides))
if source.ComponentParameterOverrides != nil {
for i := range source.ComponentParameterOverrides {
item := source.ComponentParameterOverrides[i]
overrides := make([]*appv1.ComponentParameter, len(app.Spec.Source.ComponentParameterOverrides))
if app.Spec.Source.ComponentParameterOverrides != nil {
for i := range app.Spec.Source.ComponentParameterOverrides {
item := app.Spec.Source.ComponentParameterOverrides[i]
overrides[i] = &item
}
}
manifestInfo, err := repoClient.GenerateManifest(ctx, &repository.ManifestRequest{
Repo: repo,
Environment: source.Environment,
Path: source.Path,
Revision: source.TargetRevision,
Environment: app.Spec.Source.Environment,
Path: app.Spec.Source.Path,
Revision: app.Spec.Source.TargetRevision,
ComponentParameterOverrides: overrides,
AppLabel: appLabel,
AppLabel: app.Name,
})
if err != nil {
return nil, nil, err
}
server, namespace := argoutil.ResolveServerNamespace(destination, manifestInfo)
clst, err := s.clusterService.Get(ctx, &cluster.ClusterQuery{Server: server})
if err != nil {
return nil, nil, err
}
config := clst.RESTConfig()
targetObjs := make([]*unstructured.Unstructured, len(manifestInfo.Manifests))
for i, manifest := range manifestInfo.Manifests {
@@ -254,40 +522,89 @@ func (s *Server) deploy(
targetObjs[i] = obj
}
liveObjs, err := kube.GetLiveResources(config, targetObjs, namespace)
server, namespace := argoutil.ResolveServerNamespace(app.Spec.Destination, manifestInfo)
comparison, err := s.appComparator.CompareAppState(server, namespace, targetObjs, app)
if err != nil {
return nil, nil, err
}
diffResList, err := diff.DiffArray(targetObjs, liveObjs)
clst, err := s.clusterService.Get(ctx, &cluster.ClusterQuery{Server: server})
if err != nil {
return nil, nil, err
}
config := clst.RESTConfig()
var syncRes ApplicationSyncResult
syncRes.Resources = make([]*ResourceDetails, 0)
for i, diffRes := range diffResList.Diffs {
for _, resourceState := range comparison.Resources {
var liveObj, targetObj *unstructured.Unstructured
if resourceState.LiveState != "null" {
liveObj = &unstructured.Unstructured{}
err = json.Unmarshal([]byte(resourceState.LiveState), liveObj)
if err != nil {
return nil, nil, err
}
}
if resourceState.TargetState != "null" {
targetObj = &unstructured.Unstructured{}
err = json.Unmarshal([]byte(resourceState.TargetState), targetObj)
if err != nil {
return nil, nil, err
}
}
needsCreate := liveObj == nil
needsDelete := targetObj == nil
obj := targetObj
if obj == nil {
obj = liveObj
}
resDetails := ResourceDetails{
Name: targetObjs[i].GetName(),
Kind: targetObjs[i].GetKind(),
Name: obj.GetName(),
Kind: obj.GetKind(),
Namespace: namespace,
}
needsCreate := bool(liveObjs[i] == nil)
if !diffRes.Modified {
if resourceState.Status == appv1.ComparisonStatusSynced {
resDetails.Message = fmt.Sprintf("already synced")
} else if dryRun {
if needsCreate {
resDetails.Message = fmt.Sprintf("will create")
} else if needsDelete {
if prune {
resDetails.Message = fmt.Sprintf("will delete")
} else {
resDetails.Message = fmt.Sprintf("will be ignored (should be deleted)")
}
} else {
resDetails.Message = fmt.Sprintf("will update")
}
} else {
_, err := kube.ApplyResource(config, targetObjs[i], namespace)
if err != nil {
return nil, nil, err
}
if needsCreate {
resDetails.Message = fmt.Sprintf("created")
if needsDelete {
if prune {
err = kube.DeleteResource(config, liveObj, namespace)
if err != nil {
return nil, nil, err
}
resDetails.Message = fmt.Sprintf("deleted")
} else {
resDetails.Message = fmt.Sprintf("ignored (should be deleted)")
}
} else {
resDetails.Message = fmt.Sprintf("updated")
_, err := kube.ApplyResource(config, targetObj, namespace)
if err != nil {
return nil, nil, err
}
if needsCreate {
resDetails.Message = fmt.Sprintf("created")
} else {
resDetails.Message = fmt.Sprintf("updated")
}
}
}
syncRes.Resources = append(syncRes.Resources, &resDetails)

File diff suppressed because it is too large Load Diff

View File

@@ -177,33 +177,6 @@ func request_ApplicationService_Delete_0(ctx context.Context, marshaler runtime.
}
func request_ApplicationService_ListPods_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ApplicationQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
msg, err := client.ListPods(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func request_ApplicationService_Sync_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ApplicationSyncRequest
var metadata runtime.ServerMetadata
@@ -235,6 +208,129 @@ func request_ApplicationService_Sync_0(ctx context.Context, marshaler runtime.Ma
}
func request_ApplicationService_Rollback_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ApplicationRollbackRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
msg, err := client.Rollback(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
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 DeletePodQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["applicationName"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "applicationName")
}
protoReq.ApplicationName, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "applicationName", err)
}
val, ok = pathParams["podName"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "podName")
}
protoReq.PodName, err = runtime.String(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
}
var (
filter_ApplicationService_PodLogs_0 = &utilities.DoubleArray{Encoding: map[string]int{"applicationName": 0, "podName": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_ApplicationService_PodLogs_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (ApplicationService_PodLogsClient, runtime.ServerMetadata, error) {
var protoReq PodLogsQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["applicationName"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "applicationName")
}
protoReq.ApplicationName, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "applicationName", err)
}
val, ok = pathParams["podName"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "podName")
}
protoReq.PodName, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "podName", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ApplicationService_PodLogs_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.PodLogs(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
// RegisterApplicationServiceHandlerFromEndpoint is same as RegisterApplicationServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterApplicationServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
@@ -447,35 +543,6 @@ func RegisterApplicationServiceHandlerClient(ctx context.Context, mux *runtime.S
})
mux.Handle("GET", pattern_ApplicationService_ListPods_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 {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ApplicationService_ListPods_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_ListPods_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ApplicationService_Sync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -505,6 +572,93 @@ func RegisterApplicationServiceHandlerClient(ctx context.Context, mux *runtime.S
})
mux.Handle("POST", pattern_ApplicationService_Rollback_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 {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ApplicationService_Rollback_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_Rollback_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
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 {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
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_DeletePod_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ApplicationService_PodLogs_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 {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ApplicationService_PodLogs_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_PodLogs_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -521,9 +675,13 @@ var (
pattern_ApplicationService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "applications", "name"}, ""))
pattern_ApplicationService_ListPods_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", "pods"}, ""))
pattern_ApplicationService_Sync_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", "sync"}, ""))
pattern_ApplicationService_Rollback_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", "rollback"}, ""))
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", "applicationName", "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", "applicationName", "pods", "podName", "logs"}, ""))
)
var (
@@ -539,7 +697,11 @@ var (
forward_ApplicationService_Delete_0 = runtime.ForwardResponseMessage
forward_ApplicationService_ListPods_0 = runtime.ForwardResponseMessage
forward_ApplicationService_Sync_0 = runtime.ForwardResponseMessage
forward_ApplicationService_Rollback_0 = runtime.ForwardResponseMessage
forward_ApplicationService_DeletePod_0 = runtime.ForwardResponseMessage
forward_ApplicationService_PodLogs_0 = runtime.ForwardResponseStream
)

View File

@@ -9,6 +9,7 @@ package application;
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "k8s.io/api/core/v1/generated.proto";
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1/generated.proto";
@@ -23,6 +24,7 @@ message DeleteApplicationRequest {
string name = 1;
string namespace = 2;
string server = 3;
bool force = 4;
}
// ApplicationSyncRequest is a request to apply the config state to live state
@@ -30,6 +32,7 @@ message ApplicationSyncRequest {
string name = 1;
string revision = 2;
bool dryRun = 3;
bool prune = 4;
}
// ApplicationSyncResult is a result of a sync requeswt
@@ -38,6 +41,13 @@ message ApplicationSyncResult {
repeated ResourceDetails resources = 2;
}
message ApplicationRollbackRequest {
string name = 1;
int64 id = 2 [(gogoproto.customname) = "ID"];
bool dryRun = 3;
bool prune = 4;
}
message ResourceDetails {
string name = 1;
string kind = 2;
@@ -45,6 +55,26 @@ message ResourceDetails {
string message = 4;
}
message DeletePodQuery {
string applicationName = 1;
string podName = 2;
}
message PodLogsQuery {
string applicationName = 1;
string podName = 2;
string container = 3;
int64 sinceSeconds = 4;
k8s.io.apimachinery.pkg.apis.meta.v1.Time sinceTime = 5;
int64 tailLines = 6;
bool follow = 7;
}
message LogEntry {
string content = 1;
k8s.io.apimachinery.pkg.apis.meta.v1.Time timeStamp = 2;
}
// ApplicationService
service ApplicationService {
@@ -84,11 +114,6 @@ service ApplicationService {
option (google.api.http).delete = "/api/v1/applications/{name}";
}
// ListPods returns pods in an application
rpc ListPods(ApplicationQuery) returns (k8s.io.api.core.v1.PodList) {
option (google.api.http).get = "/api/v1/applications/{name}/pods";
}
// Sync syncs an application to its target state
rpc Sync(ApplicationSyncRequest) returns (ApplicationSyncResult) {
option (google.api.http) = {
@@ -97,4 +122,21 @@ service ApplicationService {
};
}
// Sync syncs an application to its target state
rpc Rollback(ApplicationRollbackRequest) returns (ApplicationSyncResult) {
option (google.api.http) = {
post: "/api/v1/applications/{name}/rollback"
body: "*"
};
}
// PodLogs returns stream of log entries for the specified pod. Pod
rpc DeletePod(DeletePodQuery) returns (ApplicationResponse) {
option (google.api.http).delete = "/api/v1/applications/{applicationName}/pods/{podName}";
}
// PodLogs returns stream of log entries for the specified pod. Pod
rpc PodLogs(PodLogsQuery) returns (stream LogEntry) {
option (google.api.http).get = "/api/v1/applications/{applicationName}/pods/{podName}/logs";
}
}

View File

@@ -45,7 +45,7 @@ var (
)
func init() {
forward_ApplicationService_Watch_0 = func(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, req *http.Request, recv func() (proto.Message, error), opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
sseOverrider := func(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, req *http.Request, recv func() (proto.Message, error), opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
if req.Header.Get("Accept") == "text/event-stream" {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Transfer-Encoding", "chunked")
@@ -55,4 +55,6 @@ func init() {
runtime.ForwardResponseStream(ctx, mux, marshaler, w, req, recv, opts...)
}
}
forward_ApplicationService_Watch_0 = sseOverrider
forward_ApplicationService_PodLogs_0 = sseOverrider
}

View File

@@ -19,6 +19,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
)
@@ -30,7 +31,7 @@ type Server struct {
}
// NewServer returns a new instance of the Cluster service
func NewServer(namespace string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface) ClusterServiceServer {
func NewServer(namespace string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface) *Server {
return &Server{
ns: namespace,
appclientset: appclientset,
@@ -93,6 +94,41 @@ func (s *Server) Create(ctx context.Context, c *appv1.Cluster) (*appv1.Cluster,
return secretToCluster(clusterSecret), nil
}
// ClusterEvent contains information about cluster event
type ClusterEvent struct {
Type watch.EventType
Cluster *appv1.Cluster
}
// WatchClusters allow watching for cluster events
func (s *Server) WatchClusters(ctx context.Context, callback func(*ClusterEvent)) error {
listOpts := metav1.ListOptions{}
labelSelector := labels.NewSelector()
req, err := labels.NewRequirement(common.LabelKeySecretType, selection.Equals, []string{common.SecretTypeCluster})
if err != nil {
return err
}
labelSelector = labelSelector.Add(*req)
listOpts.LabelSelector = labelSelector.String()
w, err := s.kubeclientset.CoreV1().Secrets(s.ns).Watch(listOpts)
if err != nil {
return err
}
go func() {
<-ctx.Done()
w.Stop()
}()
for next := range w.ResultChan() {
secret := next.Object.(*apiv1.Secret)
cluster := secretToCluster(secret)
callback(&ClusterEvent{
Type: next.Type,
Cluster: cluster,
})
}
return nil
}
func (s *Server) getClusterSecret(server string) (*apiv1.Secret, error) {
secName := serverToSecretName(server)
clusterSecret, err := s.kubeclientset.CoreV1().Secrets(s.ns).Get(secName, metav1.GetOptions{})
@@ -153,7 +189,7 @@ func serverToSecretName(server string) string {
}
h := fnv.New32a()
_, _ = h.Write([]byte(server))
host := strings.Split(serverURL.Host, ":")[0]
host := strings.ToLower(strings.Split(serverURL.Host, ":")[0])
return fmt.Sprintf("cluster-%s-%v", host, h.Sum32())
}

View File

@@ -1,24 +1,24 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: server/cluster/cluster.proto
/*
Package cluster is a generated protocol buffer package.
Package cluster is a generated protocol buffer package.
Cluster Service
Cluster Service
Cluster Service API performs CRUD actions against cluster resources
Cluster Service API performs CRUD actions against cluster resources
It is generated from these files:
server/cluster/cluster.proto
It is generated from these files:
server/cluster/cluster.proto
It has these top-level messages:
ClusterQuery
ClusterResponse
ClusterUpdateRequest
It has these top-level messages:
ClusterQuery
ClusterResponse
ClusterUpdateRequest
*/
package cluster
import proto "github.com/golang/protobuf/proto"
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
@@ -26,10 +26,10 @@ import _ "google.golang.org/genproto/googleapis/api/annotations"
import k8s_io_api_core_v1 "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"
grpc "google.golang.org/grpc"
)
import context "golang.org/x/net/context"
import grpc "google.golang.org/grpc"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -40,17 +40,17 @@ var _ = math.Inf
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
// ClusterQuery is a query for cluster resources
type ClusterQuery struct {
Server string `protobuf:"bytes,1,opt,name=server" json:"server,omitempty"`
Server string `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
}
func (m *ClusterQuery) Reset() { *m = ClusterQuery{} }
func (m *ClusterQuery) String() string { return proto.CompactTextString(m) }
func (*ClusterQuery) ProtoMessage() {}
func (*ClusterQuery) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (*ClusterQuery) Descriptor() ([]byte, []int) { return fileDescriptorCluster, []int{0} }
func (m *ClusterQuery) GetServer() string {
if m != nil {
@@ -65,17 +65,17 @@ type ClusterResponse struct {
func (m *ClusterResponse) Reset() { *m = ClusterResponse{} }
func (m *ClusterResponse) String() string { return proto.CompactTextString(m) }
func (*ClusterResponse) ProtoMessage() {}
func (*ClusterResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (*ClusterResponse) Descriptor() ([]byte, []int) { return fileDescriptorCluster, []int{1} }
type ClusterUpdateRequest struct {
Server string `protobuf:"bytes,1,opt,name=server" json:"server,omitempty"`
Server string `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
Cluster *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Cluster `protobuf:"bytes,2,opt,name=cluster" json:"cluster,omitempty"`
}
func (m *ClusterUpdateRequest) Reset() { *m = ClusterUpdateRequest{} }
func (m *ClusterUpdateRequest) String() string { return proto.CompactTextString(m) }
func (*ClusterUpdateRequest) ProtoMessage() {}
func (*ClusterUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (*ClusterUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptorCluster, []int{2} }
func (m *ClusterUpdateRequest) GetServer() string {
if m != nil {
@@ -381,39 +381,514 @@ var _ClusterService_serviceDesc = grpc.ServiceDesc{
Metadata: "server/cluster/cluster.proto",
}
func init() { proto.RegisterFile("server/cluster/cluster.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 496 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0xcd, 0x6e, 0xd4, 0x30,
0x10, 0xc6, 0x05, 0xa5, 0x60, 0x10, 0x3f, 0xa3, 0x16, 0x2d, 0xd9, 0x82, 0x96, 0x08, 0x55, 0xd5,
0x0a, 0x6c, 0x6d, 0xb9, 0xa0, 0x1e, 0x5b, 0x7e, 0x84, 0xc4, 0xa1, 0x6c, 0xe1, 0x82, 0x2a, 0x21,
0x37, 0x19, 0xd2, 0xb0, 0x21, 0x36, 0xb6, 0x37, 0x12, 0x42, 0x08, 0x09, 0xae, 0x9c, 0xe0, 0xc8,
0x33, 0xf0, 0x0e, 0xbc, 0x03, 0xaf, 0xc0, 0x83, 0xa0, 0x38, 0x36, 0x6d, 0xb7, 0x4a, 0x2f, 0xec,
0x81, 0x53, 0xec, 0xb1, 0xf3, 0x7d, 0xf3, 0xcd, 0x7c, 0x1e, 0xba, 0x62, 0x50, 0xd7, 0xa8, 0x79,
0x5a, 0x4e, 0x8d, 0x3d, 0xf8, 0x32, 0xa5, 0xa5, 0x95, 0xb0, 0xe8, 0xb7, 0xf1, 0x52, 0x2e, 0x73,
0xe9, 0x62, 0xbc, 0x59, 0xb5, 0xc7, 0xf1, 0x4a, 0x2e, 0x65, 0x5e, 0x22, 0x17, 0xaa, 0xe0, 0xa2,
0xaa, 0xa4, 0x15, 0xb6, 0x90, 0x95, 0xf1, 0xa7, 0xc9, 0xe4, 0x9e, 0x61, 0x85, 0x74, 0xa7, 0xa9,
0xd4, 0xc8, 0xeb, 0x11, 0xcf, 0xb1, 0x42, 0x2d, 0x2c, 0x66, 0xfe, 0xce, 0xe3, 0xbc, 0xb0, 0xfb,
0xd3, 0x3d, 0x96, 0xca, 0x37, 0x5c, 0x68, 0x47, 0xf1, 0xda, 0x2d, 0xee, 0xa4, 0x19, 0x57, 0x93,
0xbc, 0xf9, 0xd9, 0x70, 0xa1, 0x54, 0x59, 0xa4, 0x0e, 0x9c, 0xd7, 0x23, 0x51, 0xaa, 0x7d, 0x71,
0x0c, 0x2a, 0x59, 0xa5, 0x17, 0xb6, 0xda, 0x6c, 0x9f, 0x4e, 0x51, 0xbf, 0x83, 0xab, 0x34, 0x6a,
0xb5, 0xf5, 0xc8, 0x80, 0xac, 0x9d, 0x1b, 0xfb, 0x5d, 0x72, 0x85, 0x5e, 0xf2, 0xf7, 0xc6, 0x68,
0x94, 0xac, 0x0c, 0x26, 0x5f, 0x08, 0x5d, 0xf2, 0xb1, 0xe7, 0x2a, 0x13, 0x16, 0xc7, 0xf8, 0x76,
0x8a, 0xc6, 0x76, 0x61, 0xc0, 0x2e, 0x0d, 0x95, 0xe9, 0x2d, 0x0c, 0xc8, 0xda, 0xf9, 0xf5, 0x4d,
0x76, 0x20, 0x84, 0x05, 0x21, 0x6e, 0xf1, 0x32, 0xcd, 0x98, 0x9a, 0xe4, 0xac, 0x11, 0xc2, 0x0e,
0x09, 0x61, 0x41, 0x08, 0x0b, 0xd9, 0x04, 0xc8, 0xf5, 0x9f, 0x8b, 0xf4, 0xa2, 0x0f, 0xee, 0xa0,
0xae, 0x8b, 0x14, 0xe1, 0x23, 0x3d, 0xf3, 0xa4, 0x30, 0x16, 0x96, 0x59, 0x68, 0xd0, 0x61, 0xad,
0xf1, 0xc3, 0x7f, 0xa7, 0x6f, 0xe0, 0x93, 0xde, 0xa7, 0x5f, 0xbf, 0xbf, 0x2d, 0x00, 0x5c, 0x76,
0x4d, 0xab, 0x47, 0xc1, 0x0e, 0x06, 0x7e, 0x10, 0x1a, 0x6d, 0x69, 0x14, 0x16, 0x61, 0x0e, 0x5a,
0xe3, 0x39, 0x60, 0x24, 0x7d, 0x97, 0xec, 0x72, 0x72, 0x2c, 0xd9, 0x0d, 0x32, 0x84, 0xcf, 0x84,
0x9e, 0x7e, 0x84, 0x9d, 0x05, 0x9b, 0x07, 0xff, 0x4d, 0xc7, 0xdf, 0x87, 0x6b, 0xb3, 0xfc, 0xfc,
0x7d, 0x6b, 0x93, 0x0f, 0xf0, 0x95, 0xd0, 0xa8, 0x75, 0xd4, 0x7f, 0x53, 0xb5, 0x53, 0xf0, 0x9d,
0x50, 0xea, 0x6d, 0xfe, 0x60, 0xe7, 0x19, 0x5c, 0x9f, 0xad, 0xd0, 0x91, 0x27, 0x30, 0x17, 0xce,
0xa1, 0xab, 0xd4, 0xad, 0xb8, 0xbb, 0x52, 0x1b, 0xc1, 0xfc, 0xb0, 0x4b, 0xa3, 0xfb, 0x58, 0xa2,
0xc5, 0xae, 0xd6, 0xf5, 0x66, 0xc3, 0x7f, 0x9f, 0xb1, 0x6f, 0xc8, 0xf0, 0x84, 0x86, 0xbc, 0xa2,
0x67, 0x1b, 0xa3, 0x6f, 0xcb, 0xcc, 0x74, 0xe1, 0xf7, 0x59, 0x3b, 0xb7, 0x1a, 0x5d, 0xac, 0x99,
0x5b, 0xac, 0x1e, 0xb1, 0x6d, 0x99, 0xb9, 0x07, 0xb2, 0xea, 0x28, 0x06, 0x70, 0xa3, 0x93, 0x82,
0x2b, 0x99, 0x99, 0xcd, 0xdb, 0x2f, 0x86, 0x27, 0x4d, 0xb6, 0xa3, 0x43, 0x77, 0x2f, 0x72, 0x13,
0xec, 0xee, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x95, 0xa3, 0x36, 0x8d, 0x05, 0x00, 0x00,
func (m *ClusterQuery) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ClusterQuery) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Server) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintCluster(dAtA, i, uint64(len(m.Server)))
i += copy(dAtA[i:], m.Server)
}
return i, nil
}
func (m *ClusterResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ClusterResponse) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
return i, nil
}
func (m *ClusterUpdateRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ClusterUpdateRequest) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Server) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintCluster(dAtA, i, uint64(len(m.Server)))
i += copy(dAtA[i:], m.Server)
}
if m.Cluster != nil {
dAtA[i] = 0x12
i++
i = encodeVarintCluster(dAtA, i, uint64(m.Cluster.Size()))
n1, err := m.Cluster.MarshalTo(dAtA[i:])
if err != nil {
return 0, err
}
i += n1
}
return i, nil
}
func encodeVarintCluster(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *ClusterQuery) Size() (n int) {
var l int
_ = l
l = len(m.Server)
if l > 0 {
n += 1 + l + sovCluster(uint64(l))
}
return n
}
func (m *ClusterResponse) Size() (n int) {
var l int
_ = l
return n
}
func (m *ClusterUpdateRequest) Size() (n int) {
var l int
_ = l
l = len(m.Server)
if l > 0 {
n += 1 + l + sovCluster(uint64(l))
}
if m.Cluster != nil {
l = m.Cluster.Size()
n += 1 + l + sovCluster(uint64(l))
}
return n
}
func sovCluster(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozCluster(x uint64) (n int) {
return sovCluster(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *ClusterQuery) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCluster
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ClusterQuery: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ClusterQuery: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Server", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCluster
}
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 ErrInvalidLengthCluster
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Server = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipCluster(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthCluster
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *ClusterResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCluster
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ClusterResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ClusterResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
default:
iNdEx = preIndex
skippy, err := skipCluster(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthCluster
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *ClusterUpdateRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCluster
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ClusterUpdateRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ClusterUpdateRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Server", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCluster
}
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 ErrInvalidLengthCluster
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Server = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Cluster", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCluster
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthCluster
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Cluster == nil {
m.Cluster = &github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Cluster{}
}
if err := m.Cluster.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipCluster(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthCluster
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipCluster(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowCluster
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowCluster
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowCluster
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthCluster
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowCluster
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipCluster(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthCluster = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowCluster = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("server/cluster/cluster.proto", fileDescriptorCluster) }
var fileDescriptorCluster = []byte{
// 510 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0x4f, 0x6b, 0x14, 0x31,
0x14, 0x37, 0x55, 0xa6, 0x1a, 0xc5, 0x3f, 0xa1, 0x95, 0x75, 0xb6, 0x2e, 0xeb, 0x20, 0xa5, 0x2c,
0x98, 0xb0, 0xf5, 0x52, 0x7a, 0x6c, 0xfd, 0x83, 0xe0, 0xa1, 0x6e, 0xf5, 0x22, 0x05, 0x49, 0x67,
0x9e, 0xd3, 0x71, 0xc7, 0x49, 0x4c, 0xb2, 0x03, 0x22, 0x22, 0xe8, 0xd5, 0x93, 0x1e, 0xfd, 0x0c,
0x7e, 0x07, 0x8f, 0x1e, 0x05, 0xbf, 0x80, 0x2c, 0x7e, 0x10, 0x99, 0x4c, 0x62, 0xdb, 0x2d, 0xb3,
0x17, 0xe7, 0xe0, 0x69, 0x92, 0x97, 0xcc, 0xef, 0xf7, 0x7e, 0xef, 0xfd, 0xf2, 0xf0, 0x8a, 0x06,
0x55, 0x82, 0x62, 0x71, 0x3e, 0xd1, 0xe6, 0xf0, 0x4b, 0xa5, 0x12, 0x46, 0x90, 0x45, 0xb7, 0x0d,
0x97, 0x52, 0x91, 0x0a, 0x1b, 0x63, 0xd5, 0xaa, 0x3e, 0x0e, 0x57, 0x52, 0x21, 0xd2, 0x1c, 0x18,
0x97, 0x19, 0xe3, 0x45, 0x21, 0x0c, 0x37, 0x99, 0x28, 0xb4, 0x3b, 0x8d, 0xc6, 0x1b, 0x9a, 0x66,
0xc2, 0x9e, 0xc6, 0x42, 0x01, 0x2b, 0x87, 0x2c, 0x85, 0x02, 0x14, 0x37, 0x90, 0xb8, 0x3b, 0x0f,
0xd2, 0xcc, 0x1c, 0x4c, 0xf6, 0x69, 0x2c, 0x5e, 0x32, 0xae, 0x2c, 0xc5, 0x0b, 0xbb, 0xb8, 0x15,
0x27, 0x4c, 0x8e, 0xd3, 0xea, 0x67, 0xcd, 0xb8, 0x94, 0x79, 0x16, 0x5b, 0x70, 0x56, 0x0e, 0x79,
0x2e, 0x0f, 0xf8, 0x09, 0xa8, 0x68, 0x15, 0x5f, 0xd8, 0xae, 0xb3, 0x7d, 0x34, 0x01, 0xf5, 0x9a,
0x5c, 0xc5, 0x41, 0xad, 0xad, 0x83, 0xfa, 0x68, 0xed, 0xdc, 0xc8, 0xed, 0xa2, 0x2b, 0xf8, 0x92,
0xbb, 0x37, 0x02, 0x2d, 0x45, 0xa1, 0x21, 0xfa, 0x88, 0xf0, 0x92, 0x8b, 0x3d, 0x91, 0x09, 0x37,
0x30, 0x82, 0x57, 0x13, 0xd0, 0xa6, 0x09, 0x83, 0xec, 0x61, 0x5f, 0x99, 0xce, 0x42, 0x1f, 0xad,
0x9d, 0x5f, 0xdf, 0xa2, 0x87, 0x42, 0xa8, 0x17, 0x62, 0x17, 0xcf, 0xe2, 0x84, 0xca, 0x71, 0x4a,
0x2b, 0x21, 0xf4, 0x88, 0x10, 0xea, 0x85, 0x50, 0x9f, 0x8d, 0x87, 0x5c, 0xff, 0xb6, 0x88, 0x2f,
0xba, 0xe0, 0x2e, 0xa8, 0x32, 0x8b, 0x81, 0xbc, 0xc3, 0x67, 0x1e, 0x66, 0xda, 0x90, 0x65, 0xea,
0x1b, 0x74, 0x54, 0x6b, 0x78, 0xef, 0xdf, 0xe9, 0x2b, 0xf8, 0xa8, 0xf3, 0xfe, 0xe7, 0xef, 0xcf,
0x0b, 0x84, 0x5c, 0xb6, 0x4d, 0x2b, 0x87, 0xde, 0x0e, 0x9a, 0x7c, 0x45, 0x38, 0xd8, 0x56, 0xc0,
0x0d, 0x90, 0x16, 0xb4, 0x86, 0x2d, 0x60, 0x44, 0x5d, 0x9b, 0xec, 0x72, 0x74, 0x22, 0xd9, 0x4d,
0x34, 0x20, 0x1f, 0x10, 0x3e, 0x7d, 0x1f, 0x1a, 0x0b, 0xd6, 0x06, 0xff, 0x0d, 0xcb, 0xdf, 0x25,
0xd7, 0x66, 0xf9, 0xd9, 0x9b, 0xda, 0x26, 0x6f, 0xc9, 0x27, 0x84, 0x83, 0xda, 0x51, 0xff, 0x4d,
0xd5, 0x4e, 0x91, 0x2f, 0x08, 0x63, 0x67, 0xf3, 0xbb, 0xbb, 0x8f, 0xc9, 0xf5, 0xd9, 0x0a, 0x1d,
0x7b, 0x02, 0xad, 0x70, 0x0e, 0x6c, 0xa5, 0x6e, 0x86, 0xcd, 0x95, 0xda, 0xf4, 0xe6, 0x27, 0x7b,
0x38, 0xb8, 0x03, 0x39, 0x18, 0x68, 0x6a, 0x5d, 0x67, 0x36, 0xfc, 0xf7, 0x19, 0xbb, 0x86, 0x0c,
0xe6, 0x34, 0xe4, 0x39, 0x3e, 0x5b, 0x19, 0x7d, 0x47, 0x24, 0xba, 0x09, 0xbf, 0x4b, 0xeb, 0xb9,
0x55, 0xe9, 0xa2, 0xd5, 0xdc, 0xa2, 0xe5, 0x90, 0xee, 0x88, 0xc4, 0x3e, 0x90, 0x55, 0x4b, 0xd1,
0x27, 0xbd, 0x46, 0x0a, 0x26, 0x45, 0xa2, 0xb7, 0x36, 0xbe, 0x4f, 0x7b, 0xe8, 0xc7, 0xb4, 0x87,
0x7e, 0x4d, 0x7b, 0xe8, 0xe9, 0x60, 0xde, 0x94, 0x3b, 0x3e, 0x80, 0xf7, 0x03, 0x3b, 0xcd, 0x6e,
0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0x25, 0x28, 0x85, 0xfb, 0x99, 0x05, 0x00, 0x00,
}

View File

@@ -146,7 +146,7 @@ func repoURLToSecretName(repo string) string {
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())
return fmt.Sprintf("repo-%s-%v", strings.ToLower(parts[len(parts)-1]), h.Sum32())
}
// repoToStringData converts a repository object to string data for serialization to a secret

View File

@@ -1,24 +1,24 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: server/repository/repository.proto
/*
Package repository is a generated protocol buffer package.
Package repository is a generated protocol buffer package.
Repository Service
Repository Service
Repository Service API performs CRUD actions against repository resources
Repository Service API performs CRUD actions against repository resources
It is generated from these files:
server/repository/repository.proto
It is generated from these files:
server/repository/repository.proto
It has these top-level messages:
RepoQuery
RepoResponse
RepoUpdateRequest
It has these top-level messages:
RepoQuery
RepoResponse
RepoUpdateRequest
*/
package repository
import proto "github.com/golang/protobuf/proto"
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
@@ -26,10 +26,10 @@ 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"
grpc "google.golang.org/grpc"
)
import context "golang.org/x/net/context"
import grpc "google.golang.org/grpc"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -40,17 +40,17 @@ var _ = math.Inf
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
// RepoQuery is a query for Repository resources
type RepoQuery struct {
Repo string `protobuf:"bytes,1,opt,name=repo" json:"repo,omitempty"`
Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
}
func (m *RepoQuery) Reset() { *m = RepoQuery{} }
func (m *RepoQuery) String() string { return proto.CompactTextString(m) }
func (*RepoQuery) ProtoMessage() {}
func (*RepoQuery) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (*RepoQuery) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{0} }
func (m *RepoQuery) GetRepo() string {
if m != nil {
@@ -65,17 +65,17 @@ type RepoResponse struct {
func (m *RepoResponse) Reset() { *m = RepoResponse{} }
func (m *RepoResponse) String() string { return proto.CompactTextString(m) }
func (*RepoResponse) ProtoMessage() {}
func (*RepoResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (*RepoResponse) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{1} }
type RepoUpdateRequest struct {
Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"`
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Repo *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository `protobuf:"bytes,2,opt,name=repo" json:"repo,omitempty"`
}
func (m *RepoUpdateRequest) Reset() { *m = RepoUpdateRequest{} }
func (m *RepoUpdateRequest) String() string { return proto.CompactTextString(m) }
func (*RepoUpdateRequest) ProtoMessage() {}
func (*RepoUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (*RepoUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptorRepository, []int{2} }
func (m *RepoUpdateRequest) GetUrl() string {
if m != nil {
@@ -346,38 +346,513 @@ var _RepositoryService_serviceDesc = grpc.ServiceDesc{
Metadata: "server/repository/repository.proto",
}
func init() { proto.RegisterFile("server/repository/repository.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 470 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x94, 0x41, 0x6b, 0x14, 0x31,
0x14, 0xc7, 0x4d, 0x5b, 0x06, 0x1a, 0x45, 0xf4, 0x51, 0xa5, 0x8e, 0x2d, 0x2d, 0xf1, 0xb2, 0x14,
0x9a, 0xb0, 0xf5, 0x22, 0x3d, 0xaa, 0x45, 0x0a, 0x5e, 0x9c, 0xea, 0x41, 0x0f, 0x4a, 0x3a, 0xfb,
0x98, 0xc6, 0x1d, 0x27, 0x31, 0xc9, 0x0c, 0x14, 0x29, 0x88, 0x07, 0xf1, 0xee, 0xc5, 0x83, 0xdf,
0xc3, 0x0f, 0xe2, 0x57, 0xf0, 0x5b, 0x78, 0x91, 0x64, 0xa6, 0xbb, 0x6b, 0xbb, 0xed, 0x69, 0x0e,
0xbd, 0xfd, 0xe7, 0x4d, 0xf2, 0xf2, 0xcb, 0xff, 0xe5, 0x3d, 0xca, 0x1c, 0xda, 0x06, 0xad, 0xb0,
0x68, 0xb4, 0x53, 0x5e, 0xdb, 0xe3, 0x19, 0xc9, 0x8d, 0xd5, 0x5e, 0x03, 0x9d, 0x46, 0xd2, 0x95,
0x42, 0x17, 0x3a, 0x86, 0x45, 0x50, 0xed, 0x8a, 0x74, 0xad, 0xd0, 0xba, 0x28, 0x51, 0x48, 0xa3,
0x84, 0xac, 0x2a, 0xed, 0xa5, 0x57, 0xba, 0x72, 0xdd, 0x5f, 0x36, 0x7e, 0xe4, 0xb8, 0xd2, 0xf1,
0x6f, 0xae, 0x2d, 0x8a, 0x66, 0x28, 0x0a, 0xac, 0xd0, 0x4a, 0x8f, 0xa3, 0x6e, 0xcd, 0x7e, 0xa1,
0xfc, 0x51, 0x7d, 0xc8, 0x73, 0xfd, 0x41, 0x48, 0x1b, 0x8f, 0x78, 0x1f, 0xc5, 0x76, 0x3e, 0x12,
0x66, 0x5c, 0x84, 0xcd, 0x4e, 0x48, 0x63, 0x4a, 0x95, 0xc7, 0xe4, 0xa2, 0x19, 0xca, 0xd2, 0x1c,
0xc9, 0x73, 0xa9, 0xd8, 0x06, 0x5d, 0xce, 0xd0, 0xe8, 0x17, 0x35, 0xda, 0x63, 0x00, 0xba, 0x14,
0xe8, 0x57, 0xc9, 0x26, 0x19, 0x2c, 0x67, 0x51, 0xb3, 0x9b, 0xf4, 0x46, 0x58, 0x90, 0xa1, 0x33,
0xba, 0x72, 0xc8, 0x3e, 0x13, 0x7a, 0x3b, 0x04, 0x5e, 0x99, 0x91, 0xf4, 0x98, 0xe1, 0xc7, 0x1a,
0x9d, 0x87, 0x5b, 0x74, 0xb1, 0xb6, 0x65, 0xb7, 0x31, 0x48, 0x78, 0xdd, 0xe5, 0x5a, 0xd8, 0x24,
0x83, 0xeb, 0x3b, 0x7b, 0x7c, 0x8a, 0xcc, 0x4f, 0x91, 0xa3, 0x78, 0x97, 0x8f, 0xb8, 0x19, 0x17,
0x3c, 0x20, 0xf3, 0x19, 0x64, 0x7e, 0x8a, 0xcc, 0xb3, 0x89, 0xa1, 0x2d, 0xd2, 0xce, 0xdf, 0xa4,
0x45, 0x68, 0x83, 0x07, 0x68, 0x1b, 0x95, 0x23, 0x7c, 0x25, 0x74, 0xe9, 0xb9, 0x72, 0x1e, 0xee,
0xf0, 0x99, 0xa2, 0x4c, 0x2e, 0x97, 0xee, 0xf7, 0x82, 0x10, 0x4e, 0x60, 0x6b, 0x5f, 0x7e, 0xff,
0xf9, 0xbe, 0x70, 0x17, 0x56, 0x62, 0x95, 0x9a, 0xe1, 0xf4, 0x15, 0x28, 0x74, 0xf0, 0x8b, 0xd0,
0xe4, 0x89, 0x45, 0xe9, 0x11, 0xfa, 0xb9, 0x76, 0xda, 0x4f, 0x1a, 0xb6, 0x11, 0xb1, 0xef, 0xb1,
0xb9, 0xd8, 0xbb, 0x64, 0x0b, 0xbe, 0x11, 0xba, 0xf8, 0x0c, 0x2f, 0x74, 0xb0, 0x27, 0x8c, 0x07,
0x11, 0x63, 0x1d, 0xee, 0xcf, 0xc3, 0x10, 0x9f, 0xc2, 0xd7, 0x09, 0xfc, 0x20, 0x34, 0x69, 0x9f,
0xd8, 0x15, 0x33, 0xf1, 0x1a, 0xfc, 0x24, 0x94, 0x76, 0xaf, 0x7f, 0xef, 0xe0, 0x25, 0xac, 0x9f,
0x35, 0xeb, 0xbf, 0xce, 0xe8, 0xeb, 0xd8, 0x41, 0x34, 0x8d, 0xa5, 0xe9, 0x7c, 0xd3, 0x6a, 0x5b,
0x9e, 0xec, 0xc6, 0xee, 0x80, 0xb7, 0x34, 0x79, 0x8a, 0x25, 0x7a, 0xbc, 0xa8, 0x8c, 0xab, 0x67,
0xc3, 0x93, 0xde, 0xee, 0x2a, 0xb3, 0x75, 0x59, 0x65, 0x1e, 0x8b, 0x37, 0xdb, 0x97, 0x8d, 0x9f,
0x73, 0x23, 0xf2, 0x30, 0x89, 0x93, 0xe6, 0xe1, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x0a,
0x51, 0x7f, 0x3e, 0x05, 0x00, 0x00,
func (m *RepoQuery) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *RepoQuery) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Repo) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintRepository(dAtA, i, uint64(len(m.Repo)))
i += copy(dAtA[i:], m.Repo)
}
return i, nil
}
func (m *RepoResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *RepoResponse) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
return i, nil
}
func (m *RepoUpdateRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *RepoUpdateRequest) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Url) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintRepository(dAtA, i, uint64(len(m.Url)))
i += copy(dAtA[i:], m.Url)
}
if m.Repo != nil {
dAtA[i] = 0x12
i++
i = encodeVarintRepository(dAtA, i, uint64(m.Repo.Size()))
n1, err := m.Repo.MarshalTo(dAtA[i:])
if err != nil {
return 0, err
}
i += n1
}
return i, nil
}
func encodeVarintRepository(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *RepoQuery) Size() (n int) {
var l int
_ = l
l = len(m.Repo)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
return n
}
func (m *RepoResponse) Size() (n int) {
var l int
_ = l
return n
}
func (m *RepoUpdateRequest) Size() (n int) {
var l int
_ = l
l = len(m.Url)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.Repo != nil {
l = m.Repo.Size()
n += 1 + l + sovRepository(uint64(l))
}
return n
}
func sovRepository(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozRepository(x uint64) (n int) {
return sovRepository(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *RepoQuery) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: RepoQuery: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: RepoQuery: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Repo", 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.Repo = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthRepository
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *RepoResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: RepoResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: RepoResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthRepository
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *RepoUpdateRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: RepoUpdateRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: RepoUpdateRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Url", 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.Url = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Repo", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Repo == nil {
m.Repo = &github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.Repository{}
}
if err := m.Repo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthRepository
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipRepository(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowRepository
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowRepository
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowRepository
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthRepository
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowRepository
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipRepository(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthRepository = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowRepository = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("server/repository/repository.proto", fileDescriptorRepository) }
var fileDescriptorRepository = []byte{
// 486 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x94, 0x41, 0x6b, 0x14, 0x31,
0x14, 0xc7, 0x4d, 0x5b, 0x06, 0x1a, 0x45, 0x34, 0x54, 0xa9, 0x63, 0xbb, 0x2d, 0xf1, 0xb2, 0x14,
0x9a, 0xb0, 0xf5, 0x22, 0xf5, 0xa6, 0x16, 0x29, 0x78, 0x71, 0xaa, 0x07, 0x3d, 0x28, 0xe9, 0xcc,
0x63, 0x1a, 0x77, 0x9c, 0xc4, 0x24, 0x33, 0x50, 0xa4, 0x20, 0x1e, 0xc4, 0xbb, 0x17, 0x0f, 0x7e,
0x0f, 0xbf, 0x82, 0x47, 0xc1, 0x2f, 0x20, 0x8b, 0xdf, 0xc2, 0x8b, 0x24, 0x33, 0xdd, 0x5d, 0xdb,
0x6d, 0x4f, 0x73, 0xe8, 0xed, 0x3f, 0x6f, 0x92, 0x97, 0x5f, 0xfe, 0x2f, 0xef, 0x61, 0x6a, 0xc1,
0xd4, 0x60, 0xb8, 0x01, 0xad, 0xac, 0x74, 0xca, 0x1c, 0x4e, 0x49, 0xa6, 0x8d, 0x72, 0x8a, 0xe0,
0x49, 0x24, 0x5e, 0xca, 0x55, 0xae, 0x42, 0x98, 0x7b, 0xd5, 0xac, 0x88, 0x57, 0x72, 0xa5, 0xf2,
0x02, 0xb8, 0xd0, 0x92, 0x8b, 0xb2, 0x54, 0x4e, 0x38, 0xa9, 0x4a, 0xdb, 0xfe, 0xa5, 0xc3, 0x7b,
0x96, 0x49, 0x15, 0xfe, 0xa6, 0xca, 0x00, 0xaf, 0x07, 0x3c, 0x87, 0x12, 0x8c, 0x70, 0x90, 0xb5,
0x6b, 0x76, 0x73, 0xe9, 0x0e, 0xaa, 0x7d, 0x96, 0xaa, 0xb7, 0x5c, 0x98, 0x70, 0xc4, 0x9b, 0x20,
0x36, 0xd3, 0x8c, 0xeb, 0x61, 0xee, 0x37, 0x5b, 0x2e, 0xb4, 0x2e, 0x64, 0x1a, 0x92, 0xf3, 0x7a,
0x20, 0x0a, 0x7d, 0x20, 0x4e, 0xa5, 0xa2, 0x6b, 0x78, 0x31, 0x01, 0xad, 0x9e, 0x56, 0x60, 0x0e,
0x09, 0xc1, 0x0b, 0x9e, 0x7e, 0x19, 0xad, 0xa3, 0xfe, 0x62, 0x12, 0x34, 0xbd, 0x8a, 0xaf, 0xf8,
0x05, 0x09, 0x58, 0xad, 0x4a, 0x0b, 0xf4, 0x03, 0xc2, 0xd7, 0x7d, 0xe0, 0xb9, 0xce, 0x84, 0x83,
0x04, 0xde, 0x55, 0x60, 0x1d, 0xb9, 0x86, 0xe7, 0x2b, 0x53, 0xb4, 0x1b, 0xbd, 0x24, 0x2f, 0xda,
0x5c, 0x73, 0xeb, 0xa8, 0x7f, 0x79, 0x6b, 0x87, 0x4d, 0x90, 0xd9, 0x31, 0x72, 0x10, 0xaf, 0xd3,
0x8c, 0xe9, 0x61, 0xce, 0x3c, 0x32, 0x9b, 0x42, 0x66, 0xc7, 0xc8, 0x2c, 0x19, 0x1b, 0xda, 0x20,
0x6d, 0xfd, 0x8d, 0x1a, 0x84, 0x26, 0xb8, 0x07, 0xa6, 0x96, 0x29, 0x90, 0x4f, 0x08, 0x2f, 0x3c,
0x91, 0xd6, 0x91, 0x1b, 0x6c, 0xaa, 0x28, 0xe3, 0xcb, 0xc5, 0xbb, 0x9d, 0x20, 0xf8, 0x13, 0xe8,
0xca, 0xc7, 0x5f, 0x7f, 0xbe, 0xcc, 0xdd, 0x24, 0x4b, 0xa1, 0x4a, 0xf5, 0x60, 0xf2, 0x0a, 0x24,
0x58, 0xf2, 0x1d, 0xe1, 0xe8, 0xa1, 0x01, 0xe1, 0x80, 0x74, 0x73, 0xed, 0xb8, 0x9b, 0x34, 0x74,
0x2d, 0x60, 0xdf, 0xa2, 0x33, 0xb1, 0xb7, 0xd1, 0x06, 0xf9, 0x8c, 0xf0, 0xfc, 0x63, 0x38, 0xd3,
0xc1, 0x8e, 0x30, 0xee, 0x04, 0x8c, 0x55, 0x72, 0x7b, 0x16, 0x06, 0x7f, 0xef, 0xbf, 0x8e, 0xc8,
0x57, 0x84, 0xa3, 0xe6, 0x89, 0x5d, 0x30, 0x13, 0x2f, 0x91, 0x6f, 0x08, 0xe3, 0xf6, 0xf5, 0xef,
0xec, 0x3d, 0x23, 0xab, 0x27, 0xcd, 0xfa, 0xaf, 0x33, 0xba, 0x3a, 0xb6, 0x1f, 0x4c, 0xa3, 0x71,
0x3c, 0xdb, 0xb4, 0xca, 0x14, 0x47, 0xdb, 0xa1, 0x3b, 0xc8, 0x2b, 0x1c, 0x3d, 0x82, 0x02, 0x1c,
0x9c, 0x55, 0xc6, 0xe5, 0x93, 0xe1, 0x71, 0x6f, 0xb7, 0x95, 0xd9, 0x38, 0xaf, 0x32, 0x0f, 0xee,
0xff, 0x18, 0xf5, 0xd0, 0xcf, 0x51, 0x0f, 0xfd, 0x1e, 0xf5, 0xd0, 0xcb, 0xcd, 0xf3, 0x46, 0xd1,
0xa9, 0x71, 0xb9, 0x1f, 0x85, 0xa9, 0x73, 0xf7, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd4, 0xa0,
0x05, 0x80, 0x4a, 0x05, 0x00, 0x00,
}

View File

@@ -0,0 +1,18 @@
package repository
import "testing"
func TestRepoURLToSecretName(t *testing.T) {
tables := map[string]string{
"git://git@github.com:argoproj/ARGO-cd.git": "repo-argo-cd-83273445",
"https://github.com/argoproj/ARGO-cd": "repo-argo-cd-1890113693",
"https://github.com/argoproj/argo-cd": "repo-argo-cd-42374749",
"ssh://git@github.com/argoproj/argo-cd.git": "repo-argo-cd-3569564120",
}
for k, v := range tables {
if sn := repoURLToSecretName(k); sn != v {
t.Errorf("Expected secret name %q for repo %q; instead, got %q", v, k, sn)
}
}
}

View File

@@ -2,33 +2,46 @@ package server
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
"strings"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/errors"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/server/application"
"github.com/argoproj/argo-cd/server/cluster"
"github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/server/session"
"github.com/argoproj/argo-cd/server/version"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/config"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
jsonutil "github.com/argoproj/argo-cd/util/json"
util_session "github.com/argoproj/argo-cd/util/session"
tlsutil "github.com/argoproj/argo-cd/util/tls"
golang_proto "github.com/golang/protobuf/proto"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
log "github.com/sirupsen/logrus"
"github.com/soheilhy/cmux"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
"k8s.io/client-go/kubernetes"
)
const (
port = 8080
port = 8080
authCookieName = "argocd.argoproj.io/auth-token"
)
var (
@@ -37,31 +50,33 @@ var (
// ArgoCDServer is the API server for ArgoCD
type ArgoCDServer struct {
ns string
staticAssetsDir string
kubeclientset kubernetes.Interface
appclientset appclientset.Interface
repoclientset reposerver.Clientset
settings util.ArgoCDSettings
log *log.Entry
ArgoCDServerOpts
settings config.ArgoCDSettings
log *log.Entry
}
type ArgoCDServerOpts struct {
DisableAuth bool
Insecure bool
Namespace string
StaticAssetsDir string
KubeClientset kubernetes.Interface
AppClientset appclientset.Interface
RepoClientset reposerver.Clientset
}
// NewServer returns a new instance of the ArgoCD API server
func NewServer(
kubeclientset kubernetes.Interface, appclientset appclientset.Interface, repoclientset reposerver.Clientset, namespace, staticAssetsDir, configMapName string) *ArgoCDServer {
configManager := util.NewConfigManager(kubeclientset, namespace, configMapName)
func NewServer(opts ArgoCDServerOpts) *ArgoCDServer {
configManager := config.NewConfigManager(opts.KubeClientset, opts.Namespace)
settings, err := configManager.GetSettings()
if err != nil {
log.Fatal(err)
}
return &ArgoCDServer{
ns: namespace,
kubeclientset: kubeclientset,
appclientset: appclientset,
repoclientset: repoclientset,
log: log.NewEntry(log.New()),
staticAssetsDir: staticAssetsDir,
settings: settings,
ArgoCDServerOpts: opts,
log: log.NewEntry(log.New()),
settings: *settings,
}
}
@@ -74,82 +89,196 @@ func (a *ArgoCDServer) Run() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
conn, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
panic(err)
grpcS := a.newGRPCServer()
var httpS *http.Server
var httpsS *http.Server
if a.useTLS() {
httpS = newRedirectServer()
httpsS = a.newHTTPServer(ctx)
} else {
httpS = a.newHTTPServer(ctx)
}
// Cmux is used to support servicing gRPC and HTTP1.1+JSON on the same port
m := cmux.New(conn)
grpcL := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
httpL := m.Match(cmux.HTTP1Fast())
conn, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
errors.CheckError(err)
// gRPC Server
grpcS := grpc.NewServer(
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),
)),
)
tcpm := cmux.New(conn)
var tlsm cmux.CMux
var grpcL net.Listener
var httpL net.Listener
var httpsL net.Listener
if !a.useTLS() {
httpL = tcpm.Match(cmux.HTTP1Fast())
grpcL = tcpm.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
} else {
// We first match on HTTP 1.1 methods.
httpL = tcpm.Match(cmux.HTTP1Fast())
// If not matched, we assume that its TLS.
tlsl := tcpm.Match(cmux.Any())
tlsConfig := tls.Config{
Certificates: []tls.Certificate{*a.settings.Certificate},
}
tlsl = tls.NewListener(tlsl, &tlsConfig)
// Now, we build another mux recursively to match HTTPS and GoRPC.
tlsm = cmux.New(tlsl)
httpsL = tlsm.Match(cmux.HTTP1Fast())
grpcL = tlsm.Match(cmux.Any())
}
// Start the muxed listeners for our servers
log.Infof("argocd %s serving on port %d (tls: %v, namespace: %s)", argocd.GetVersion(), port, a.useTLS(), a.Namespace)
go func() { errors.CheckError(grpcS.Serve(grpcL)) }()
go func() { errors.CheckError(httpS.Serve(httpL)) }()
if a.useTLS() {
go func() { errors.CheckError(httpsS.Serve(httpsL)) }()
go func() { errors.CheckError(tlsm.Serve()) }()
}
err = tcpm.Serve()
errors.CheckError(err)
}
func (a *ArgoCDServer) useTLS() bool {
if a.Insecure || a.settings.Certificate == nil {
return false
}
return true
}
func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
var sOpts []grpc.ServerOption
// NOTE: notice we do not configure the gRPC server here with TLS (e.g. grpc.Creds(creds))
// This is because TLS handshaking occurs in cmux handling
sOpts = append(sOpts, grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_logrus.StreamServerInterceptor(a.log),
grpc_auth.StreamServerInterceptor(a.authenticate),
grpc_util.ErrorCodeStreamServerInterceptor(),
grpc_util.PanicLoggerStreamServerInterceptor(a.log),
)))
sOpts = append(sOpts, grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_logrus.UnaryServerInterceptor(a.log),
grpc_auth.UnaryServerInterceptor(a.authenticate),
grpc_util.ErrorCodeUnaryServerInterceptor(),
grpc_util.PanicLoggerUnaryServerInterceptor(a.log),
)))
grpcS := grpc.NewServer(sOpts...)
clusterService := cluster.NewServer(a.Namespace, a.KubeClientset, a.AppClientset)
repoService := repository.NewServer(a.Namespace, a.KubeClientset, a.AppClientset)
sessionService := session.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.settings)
applicationService := application.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.RepoClientset, repoService, clusterService)
version.RegisterVersionServiceServer(grpcS, &version.Server{})
clusterService := cluster.NewServer(a.ns, a.kubeclientset, a.appclientset)
repoService := repository.NewServer(a.ns, a.kubeclientset, a.appclientset)
cluster.RegisterClusterServiceServer(grpcS, clusterService)
application.RegisterApplicationServiceServer(grpcS, application.NewServer(a.ns, a.kubeclientset, a.appclientset, a.repoclientset, repoService, clusterService))
application.RegisterApplicationServiceServer(grpcS, applicationService)
repository.RegisterRepositoryServiceServer(grpcS, repoService)
session.RegisterSessionServiceServer(grpcS, sessionService)
// Register reflection service on gRPC server.
reflection.Register(grpcS)
return grpcS
}
// MakeCookieMetadata generates a string representing a Web cookie. Yum!
func (a *ArgoCDServer) makeCookieMetadata(key, value string, flags ...string) string {
components := []string{
fmt.Sprintf("%s=%s", key, value),
}
if a.ArgoCDServerOpts.Insecure == false {
components = append(components, "Secure")
}
components = append(components, flags...)
return strings.Join(components, "; ")
}
// TranslateGrpcCookieHeader conditionally sets a cookie on the response.
func (a *ArgoCDServer) translateGrpcCookieHeader(ctx context.Context, w http.ResponseWriter, resp golang_proto.Message) error {
if sessionResp, ok := resp.(*session.SessionResponse); ok {
cookie := a.makeCookieMetadata(authCookieName, sessionResp.Token, "path=/")
w.Header().Set("Set-Cookie", cookie)
}
return nil
}
// newHTTPServer returns the HTTP server to serve HTTP/HTTPS requests. This is implemented
// using grpc-gateway as a proxy to the gRPC server.
func (a *ArgoCDServer) newHTTPServer(ctx context.Context) *http.Server {
mux := http.NewServeMux()
httpS := http.Server{
Addr: endpoint,
Handler: mux,
}
var dOpts []grpc.DialOption
if a.useTLS() {
// The following sets up the dial Options for grpc-gateway to talk to gRPC server over TLS.
// grpc-gateway is just translating HTTP/HTTPS requests as gRPC requests over localhost,
// so we need to supply the same certificates to establish the connections that a normal,
// external gRPC client would need.
certPool := x509.NewCertPool()
pemCertBytes, _ := tlsutil.EncodeX509KeyPair(*a.settings.Certificate)
ok := certPool.AppendCertsFromPEM(pemCertBytes)
if !ok {
panic("bad certs")
}
dCreds := credentials.NewTLS(&tls.Config{
RootCAs: certPool,
InsecureSkipVerify: true,
})
dOpts = append(dOpts, grpc.WithTransportCredentials(dCreds))
} else {
dOpts = append(dOpts, grpc.WithInsecure())
}
// HTTP 1.1+JSON Server
// grpc-ecosystem/grpc-gateway is used to proxy HTTP requests to the corresponding gRPC call
mux := http.NewServeMux()
// NOTE: if a marshaller option is not supplied, grpc-gateway will default to the jsonpb from
// golang/protobuf. Which does not support types such as time.Time. gogo/protobuf does support
// time.Time, but does not support custom UnmarshalJSON() and MarshalJSON() methods. Therefore
// we use our own Marshaler
gwMuxOpts := runtime.WithMarshalerOption(runtime.MIMEWildcard, new(jsonutil.JSONMarshaler))
gwmux := runtime.NewServeMux(gwMuxOpts)
gwCookieOpts := runtime.WithForwardResponseOption(a.translateGrpcCookieHeader)
gwmux := runtime.NewServeMux(gwMuxOpts, gwCookieOpts)
mux.Handle("/api/", gwmux)
dOpts := []grpc.DialOption{grpc.WithInsecure()}
mustRegisterGWHandler(version.RegisterVersionServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(cluster.RegisterClusterServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(application.RegisterApplicationServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(repository.RegisterRepositoryServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(session.RegisterSessionServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
if a.staticAssetsDir != "" {
if a.StaticAssetsDir != "" {
mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
acceptHtml := false
acceptHTML := false
for _, acceptType := range strings.Split(request.Header.Get("Accept"), ",") {
if acceptType == "text/html" || acceptType == "html" {
acceptHtml = true
acceptHTML = true
break
}
}
fileRequest := request.URL.Path != "/index.html" && strings.Contains(request.URL.Path, ".")
// serve index.html for non file requests to support HTML5 History API
if acceptHtml && !fileRequest && (request.Method == "GET" || request.Method == "HEAD") {
http.ServeFile(writer, request, a.staticAssetsDir+"/index.html")
if acceptHTML && !fileRequest && (request.Method == "GET" || request.Method == "HEAD") {
http.ServeFile(writer, request, a.StaticAssetsDir+"/index.html")
} else {
http.ServeFile(writer, request, a.staticAssetsDir+request.URL.Path)
http.ServeFile(writer, request, a.StaticAssetsDir+request.URL.Path)
}
})
}
return &httpS
}
httpS := &http.Server{
Addr: endpoint,
Handler: mux,
}
// Start the muxed listeners for our servers
log.Infof("argocd %s serving on port %d (namespace: %s)", argocd.GetVersion(), port, a.ns)
go func() { _ = grpcS.Serve(grpcL) }()
go func() { _ = httpS.Serve(httpL) }()
err = m.Serve()
if err != nil {
panic(err)
// newRedirectServer returns an HTTP server which does a 307 redirect to the HTTPS server
func newRedirectServer() *http.Server {
return &http.Server{
Addr: endpoint,
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
target := "https://" + req.Host + req.URL.Path
if len(req.URL.RawQuery) > 0 {
target += "?" + req.URL.RawQuery
}
http.Redirect(w, req, target, http.StatusTemporaryRedirect)
}),
}
}
@@ -162,3 +291,42 @@ func mustRegisterGWHandler(register registerFunc, ctx context.Context, mux *runt
panic(err)
}
}
// parseTokens tests a slice of strings and returns `true` only if any of them are valid.
func (a *ArgoCDServer) parseTokens(tokens []string) bool {
mgr := util_session.MakeSessionManager(a.settings.ServerSignature)
for _, token := range tokens {
_, err := mgr.Parse(token)
if err == nil {
return true
}
}
return false
}
// Authenticate checks for the presence of a token when accessing server-side resources.
func (a *ArgoCDServer) authenticate(ctx context.Context) (context.Context, error) {
if a.DisableAuth {
return ctx, nil
}
if md, ok := metadata.FromIncomingContext(ctx); ok {
tokens := md["tokens"]
// Extract only the value portion of cookie-stored tokens
for _, cookieToken := range md["grpcgateway-cookie"] {
tokenPair := strings.SplitN(cookieToken, "=", 2)
if len(tokenPair) == 2 {
tokens = append(tokens, tokenPair[1])
}
}
// Check both gRPC-provided tokens and Web-provided (cookie-based) ones
if a.parseTokens(tokens) {
return ctx, nil
}
return ctx, status.Errorf(codes.Unauthenticated, "user is not allowed access")
}
return ctx, status.Errorf(codes.Unauthenticated, "empty metadata")
}

View File

@@ -0,0 +1,72 @@
// Code generated by mockery v1.0.0
package mocks
import context "context"
import grpc "google.golang.org/grpc"
import mock "github.com/stretchr/testify/mock"
import session "github.com/argoproj/argo-cd/server/session"
// SessionServiceClient is an autogenerated mock type for the SessionServiceClient type
type SessionServiceClient struct {
mock.Mock
}
// Create provides a mock function with given fields: ctx, in, opts
func (_m *SessionServiceClient) Create(ctx context.Context, in *session.SessionCreateRequest, opts ...grpc.CallOption) (*session.SessionResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *session.SessionResponse
if rf, ok := ret.Get(0).(func(context.Context, *session.SessionCreateRequest, ...grpc.CallOption) *session.SessionResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*session.SessionResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *session.SessionCreateRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: ctx, in, opts
func (_m *SessionServiceClient) Delete(ctx context.Context, in *session.SessionDeleteRequest, opts ...grpc.CallOption) (*session.SessionResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *session.SessionResponse
if rf, ok := ret.Get(0).(func(context.Context, *session.SessionDeleteRequest, ...grpc.CallOption) *session.SessionResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*session.SessionResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *session.SessionDeleteRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@@ -0,0 +1,57 @@
// Code generated by mockery v1.0.0
package mocks
import context "context"
import mock "github.com/stretchr/testify/mock"
import session "github.com/argoproj/argo-cd/server/session"
// SessionServiceServer is an autogenerated mock type for the SessionServiceServer type
type SessionServiceServer struct {
mock.Mock
}
// Create provides a mock function with given fields: _a0, _a1
func (_m *SessionServiceServer) Create(_a0 context.Context, _a1 *session.SessionCreateRequest) (*session.SessionResponse, error) {
ret := _m.Called(_a0, _a1)
var r0 *session.SessionResponse
if rf, ok := ret.Get(0).(func(context.Context, *session.SessionCreateRequest) *session.SessionResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*session.SessionResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *session.SessionCreateRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: _a0, _a1
func (_m *SessionServiceServer) Delete(_a0 context.Context, _a1 *session.SessionDeleteRequest) (*session.SessionResponse, error) {
ret := _m.Called(_a0, _a1)
var r0 *session.SessionResponse
if rf, ok := ret.Get(0).(func(context.Context, *session.SessionDeleteRequest) *session.SessionResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*session.SessionResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *session.SessionDeleteRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

81
server/session/session.go Normal file
View File

@@ -0,0 +1,81 @@
package session
import (
"context"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/util/config"
"github.com/argoproj/argo-cd/util/password"
"github.com/argoproj/argo-cd/util/session"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/client-go/kubernetes"
)
// Server provides a Session service
type Server struct {
ns string
kubeclientset kubernetes.Interface
appclientset appclientset.Interface
serversettings config.ArgoCDSettings
}
// NewServer returns a new instance of the Session service
func NewServer(namespace string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, serversettings config.ArgoCDSettings) *Server {
return &Server{
ns: namespace,
appclientset: appclientset,
kubeclientset: kubeclientset,
serversettings: serversettings,
}
}
// invalidLoginMessage, for security purposes, doesn't say whether the username or password was invalid. This does not mitigate the potential for timing attacks to determine which is which.
const (
invalidLoginError = "Invalid username or password"
blankPasswordError = "Blank passwords are not allowed"
)
// Create an authentication cookie for the client.
func (s *Server) Create(ctx context.Context, q *SessionCreateRequest) (*SessionResponse, error) {
if q.Password == "" {
err := status.Errorf(codes.Unauthenticated, blankPasswordError)
return nil, err
}
passwordHash, ok := s.serversettings.LocalUsers[q.Username]
if !ok {
// Username was not found in local user store.
// Ensure we still send password to hashing algorithm for comparison.
// This mitigates potential for timing attacks that benefit from short-circuiting,
// provided the hashing library/algorithm in use doesn't itself short-circuit.
passwordHash = ""
}
valid, _ := password.VerifyPassword(q.Password, passwordHash)
if !valid {
err := status.Errorf(codes.Unauthenticated, invalidLoginError)
return nil, err
}
sessionManager := session.MakeSessionManager(s.serversettings.ServerSignature)
token, err := sessionManager.Create(q.Username)
if err != nil {
token = ""
}
return &SessionResponse{token}, err
}
// Delete an authentication cookie from the client. This makes sense only for the Web client.
func (s *Server) Delete(ctx context.Context, q *SessionDeleteRequest) (*SessionResponse, error) {
return &SessionResponse{""}, nil
}
// AuthFuncOverride overrides the authentication function and let us not require auth to receive auth.
// Without this function here, ArgoCDServer.authenticate would be invoked and credentials checked.
// Since this service is generally invoked when the user has _no_ credentials, that would create a
// chicken-and-egg situation if we didn't place this here to allow traffic to pass through.
func (s *Server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
return ctx, nil
}

View File

@@ -0,0 +1,703 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: server/session/session.proto
/*
Package session is a generated protocol buffer package.
Session Service
Session Service API performs CRUD actions against session resources
It is generated from these files:
server/session/session.proto
It has these top-level messages:
SessionCreateRequest
SessionDeleteRequest
SessionResponse
*/
package session
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
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"
import context "golang.org/x/net/context"
import grpc "google.golang.org/grpc"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
// SessionCreateRequest is for logging in.
type SessionCreateRequest struct {
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
}
func (m *SessionCreateRequest) Reset() { *m = SessionCreateRequest{} }
func (m *SessionCreateRequest) String() string { return proto.CompactTextString(m) }
func (*SessionCreateRequest) ProtoMessage() {}
func (*SessionCreateRequest) Descriptor() ([]byte, []int) { return fileDescriptorSession, []int{0} }
func (m *SessionCreateRequest) GetUsername() string {
if m != nil {
return m.Username
}
return ""
}
func (m *SessionCreateRequest) GetPassword() string {
if m != nil {
return m.Password
}
return ""
}
// SessionDeleteRequest is for logging out.
type SessionDeleteRequest struct {
}
func (m *SessionDeleteRequest) Reset() { *m = SessionDeleteRequest{} }
func (m *SessionDeleteRequest) String() string { return proto.CompactTextString(m) }
func (*SessionDeleteRequest) ProtoMessage() {}
func (*SessionDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptorSession, []int{1} }
// SessionResponse wraps the created token or returns an empty string if deleted.
type SessionResponse struct {
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
}
func (m *SessionResponse) Reset() { *m = SessionResponse{} }
func (m *SessionResponse) String() string { return proto.CompactTextString(m) }
func (*SessionResponse) ProtoMessage() {}
func (*SessionResponse) Descriptor() ([]byte, []int) { return fileDescriptorSession, []int{2} }
func (m *SessionResponse) GetToken() string {
if m != nil {
return m.Token
}
return ""
}
func init() {
proto.RegisterType((*SessionCreateRequest)(nil), "session.SessionCreateRequest")
proto.RegisterType((*SessionDeleteRequest)(nil), "session.SessionDeleteRequest")
proto.RegisterType((*SessionResponse)(nil), "session.SessionResponse")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for SessionService service
type SessionServiceClient interface {
// Create a new JWT for authentication.
Create(ctx context.Context, in *SessionCreateRequest, opts ...grpc.CallOption) (*SessionResponse, error)
// Create a new JWT for authentication.
Delete(ctx context.Context, in *SessionDeleteRequest, opts ...grpc.CallOption) (*SessionResponse, error)
}
type sessionServiceClient struct {
cc *grpc.ClientConn
}
func NewSessionServiceClient(cc *grpc.ClientConn) SessionServiceClient {
return &sessionServiceClient{cc}
}
func (c *sessionServiceClient) Create(ctx context.Context, in *SessionCreateRequest, opts ...grpc.CallOption) (*SessionResponse, error) {
out := new(SessionResponse)
err := grpc.Invoke(ctx, "/session.SessionService/Create", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sessionServiceClient) Delete(ctx context.Context, in *SessionDeleteRequest, opts ...grpc.CallOption) (*SessionResponse, error) {
out := new(SessionResponse)
err := grpc.Invoke(ctx, "/session.SessionService/Delete", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for SessionService service
type SessionServiceServer interface {
// Create a new JWT for authentication.
Create(context.Context, *SessionCreateRequest) (*SessionResponse, error)
// Create a new JWT for authentication.
Delete(context.Context, *SessionDeleteRequest) (*SessionResponse, error)
}
func RegisterSessionServiceServer(s *grpc.Server, srv SessionServiceServer) {
s.RegisterService(&_SessionService_serviceDesc, srv)
}
func _SessionService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SessionCreateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SessionServiceServer).Create(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/session.SessionService/Create",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SessionServiceServer).Create(ctx, req.(*SessionCreateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SessionService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SessionDeleteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SessionServiceServer).Delete(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/session.SessionService/Delete",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SessionServiceServer).Delete(ctx, req.(*SessionDeleteRequest))
}
return interceptor(ctx, in, info, handler)
}
var _SessionService_serviceDesc = grpc.ServiceDesc{
ServiceName: "session.SessionService",
HandlerType: (*SessionServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Create",
Handler: _SessionService_Create_Handler,
},
{
MethodName: "Delete",
Handler: _SessionService_Delete_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "server/session/session.proto",
}
func (m *SessionCreateRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *SessionCreateRequest) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Username) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintSession(dAtA, i, uint64(len(m.Username)))
i += copy(dAtA[i:], m.Username)
}
if len(m.Password) > 0 {
dAtA[i] = 0x12
i++
i = encodeVarintSession(dAtA, i, uint64(len(m.Password)))
i += copy(dAtA[i:], m.Password)
}
return i, nil
}
func (m *SessionDeleteRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *SessionDeleteRequest) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
return i, nil
}
func (m *SessionResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *SessionResponse) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Token) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintSession(dAtA, i, uint64(len(m.Token)))
i += copy(dAtA[i:], m.Token)
}
return i, nil
}
func encodeVarintSession(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *SessionCreateRequest) Size() (n int) {
var l int
_ = l
l = len(m.Username)
if l > 0 {
n += 1 + l + sovSession(uint64(l))
}
l = len(m.Password)
if l > 0 {
n += 1 + l + sovSession(uint64(l))
}
return n
}
func (m *SessionDeleteRequest) Size() (n int) {
var l int
_ = l
return n
}
func (m *SessionResponse) Size() (n int) {
var l int
_ = l
l = len(m.Token)
if l > 0 {
n += 1 + l + sovSession(uint64(l))
}
return n
}
func sovSession(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozSession(x uint64) (n int) {
return sovSession(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *SessionCreateRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSession
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: SessionCreateRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: SessionCreateRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Username", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSession
}
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 ErrInvalidLengthSession
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Username = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Password", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSession
}
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 ErrInvalidLengthSession
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Password = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSession(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthSession
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *SessionDeleteRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSession
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: SessionDeleteRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: SessionDeleteRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
default:
iNdEx = preIndex
skippy, err := skipSession(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthSession
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *SessionResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSession
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: SessionResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: SessionResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSession
}
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 ErrInvalidLengthSession
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Token = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSession(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthSession
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipSession(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSession
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSession
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSession
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthSession
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSession
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipSession(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthSession = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowSession = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("server/session/session.proto", fileDescriptorSession) }
var fileDescriptorSession = []byte{
// 349 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0xc1, 0x4a, 0xfb, 0x40,
0x10, 0xc6, 0xd9, 0xc2, 0xbf, 0x7f, 0xdd, 0x83, 0xc5, 0x10, 0xb4, 0x84, 0x5a, 0x24, 0x17, 0xa5,
0x60, 0x96, 0xea, 0xa5, 0x78, 0x54, 0x2f, 0x5e, 0x3c, 0xb4, 0xb7, 0x82, 0x87, 0x6d, 0x3a, 0x6c,
0xd7, 0xa6, 0x3b, 0xeb, 0xee, 0x36, 0xde, 0x7d, 0x05, 0x5f, 0x4a, 0xf0, 0x22, 0xf8, 0x02, 0x52,
0x7c, 0x10, 0xe9, 0x26, 0x29, 0xb4, 0x95, 0x9e, 0xb2, 0xdf, 0x7e, 0x93, 0xdf, 0x7c, 0x99, 0x09,
0x6d, 0x59, 0x30, 0x39, 0x18, 0x66, 0xc1, 0x5a, 0x89, 0xaa, 0x7a, 0x26, 0xda, 0xa0, 0xc3, 0xe0,
0x7f, 0x29, 0xa3, 0x50, 0xa0, 0x40, 0x7f, 0xc7, 0x96, 0xa7, 0xc2, 0x8e, 0x5a, 0x02, 0x51, 0x64,
0xc0, 0xb8, 0x96, 0x8c, 0x2b, 0x85, 0x8e, 0x3b, 0x89, 0xca, 0x96, 0x6e, 0x3c, 0xed, 0xd9, 0x44,
0xa2, 0x77, 0x53, 0x34, 0xc0, 0xf2, 0x2e, 0x13, 0xa0, 0xc0, 0x70, 0x07, 0xe3, 0xb2, 0xe6, 0x5e,
0x48, 0x37, 0x99, 0x8f, 0x92, 0x14, 0x67, 0x8c, 0x1b, 0xdf, 0xe2, 0xc9, 0x1f, 0x2e, 0xd2, 0x31,
0xd3, 0x53, 0xb1, 0x7c, 0xd9, 0x32, 0xae, 0x75, 0x26, 0x53, 0x0f, 0x67, 0x79, 0x97, 0x67, 0x7a,
0xc2, 0xb7, 0x50, 0xf1, 0x03, 0x0d, 0x07, 0x45, 0xda, 0x5b, 0x03, 0xdc, 0x41, 0x1f, 0x9e, 0xe7,
0x60, 0x5d, 0x10, 0xd1, 0xbd, 0xb9, 0x05, 0xa3, 0xf8, 0x0c, 0x9a, 0xe4, 0x94, 0x9c, 0xef, 0xf7,
0x57, 0x7a, 0xe9, 0x69, 0x6e, 0xed, 0x0b, 0x9a, 0x71, 0xb3, 0x56, 0x78, 0x95, 0x8e, 0x8f, 0x56,
0xbc, 0x3b, 0xc8, 0x60, 0xc5, 0x8b, 0xcf, 0x68, 0xa3, 0xbc, 0xef, 0x83, 0xd5, 0xa8, 0x2c, 0x04,
0x21, 0xfd, 0xe7, 0x70, 0x0a, 0xaa, 0xe4, 0x17, 0xe2, 0xf2, 0x83, 0xd0, 0x83, 0xb2, 0x72, 0x00,
0x26, 0x97, 0x29, 0x04, 0x8f, 0xb4, 0x5e, 0x84, 0x0b, 0x4e, 0x92, 0x6a, 0xd2, 0x7f, 0x85, 0x8e,
0x9a, 0x9b, 0x76, 0xd5, 0x2b, 0x8e, 0x5e, 0xbf, 0x7e, 0xde, 0x6a, 0x61, 0xdc, 0xf0, 0x73, 0xcd,
0xbb, 0xd5, 0xc6, 0xae, 0x49, 0x27, 0x18, 0xd2, 0x7a, 0x91, 0x75, 0x1b, 0xbf, 0xf6, 0x0d, 0x3b,
0xf0, 0xc7, 0x1e, 0x7f, 0xd8, 0xd9, 0xc4, 0xdf, 0xf4, 0xde, 0x17, 0x6d, 0xf2, 0xb9, 0x68, 0x93,
0xef, 0x45, 0x9b, 0x0c, 0x3b, 0xbb, 0xf6, 0xb6, 0xfe, 0x4b, 0x8d, 0xea, 0x7e, 0x3f, 0x57, 0xbf,
0x01, 0x00, 0x00, 0xff, 0xff, 0xac, 0xad, 0xb8, 0xb8, 0x6b, 0x02, 0x00, 0x00,
}

View File

@@ -0,0 +1,162 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: server/session/session.proto
/*
Package session is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package session
import (
"io"
"net/http"
"github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/status"
)
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
func request_SessionService_Create_0(ctx context.Context, marshaler runtime.Marshaler, client SessionServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SessionCreateRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.Create(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func request_SessionService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, client SessionServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SessionDeleteRequest
var metadata runtime.ServerMetadata
msg, err := client.Delete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
// RegisterSessionServiceHandlerFromEndpoint is same as RegisterSessionServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterSessionServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterSessionServiceHandler(ctx, mux, conn)
}
// RegisterSessionServiceHandler registers the http handlers for service SessionService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterSessionServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterSessionServiceHandlerClient(ctx, mux, NewSessionServiceClient(conn))
}
// RegisterSessionServiceHandler registers the http handlers for service SessionService to "mux".
// The handlers forward requests to the grpc endpoint over the given implementation of "SessionServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "SessionServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "SessionServiceClient" to call the correct interceptors.
func RegisterSessionServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client SessionServiceClient) error {
mux.Handle("POST", pattern_SessionService_Create_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 {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_SessionService_Create_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_SessionService_Create_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_SessionService_Delete_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 {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_SessionService_Delete_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_SessionService_Delete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_SessionService_Create_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "session"}, ""))
pattern_SessionService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "session"}, ""))
)
var (
forward_SessionService_Create_0 = runtime.ForwardResponseMessage
forward_SessionService_Delete_0 = runtime.ForwardResponseMessage
)

View File

@@ -0,0 +1,46 @@
syntax = "proto3";
option go_package = "github.com/argoproj/argo-cd/server/session";
// Session Service
//
// Session Service API performs CRUD actions against session resources
package session;
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "k8s.io/api/core/v1/generated.proto";
import "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1/generated.proto";
// SessionCreateRequest is for logging in.
message SessionCreateRequest {
string username = 1;
string password = 2;
}
// SessionDeleteRequest is for logging out.
message SessionDeleteRequest {}
// SessionResponse wraps the created token or returns an empty string if deleted.
message SessionResponse {
string token = 1;
}
// SessionService
service SessionService {
// Create a new JWT for authentication.
rpc Create(SessionCreateRequest) returns (SessionResponse) {
option (google.api.http) = {
post: "/api/v1/session"
body: "*"
};
}
// Create a new JWT for authentication.
rpc Delete(SessionDeleteRequest) returns (SessionResponse) {
option (google.api.http) = {
delete: "/api/v1/session"
};
}
}

View File

@@ -22,3 +22,8 @@ func (s *Server) Version(context.Context, *empty.Empty) (*VersionMessage, error)
Platform: vers.Platform,
}, nil
}
// AuthFuncOverride allows the version to be returned without auth
func (s *Server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
return ctx, nil
}

View File

@@ -1,31 +1,31 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: server/version/version.proto
/*
Package version is a generated protocol buffer package.
Package version is a generated protocol buffer package.
Version Service
Version Service
Version Service API returns the version of the API server.
Version Service API returns the version of the API server.
It is generated from these files:
server/version/version.proto
It is generated from these files:
server/version/version.proto
It has these top-level messages:
VersionMessage
It has these top-level messages:
VersionMessage
*/
package version
import proto "github.com/golang/protobuf/proto"
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "google.golang.org/genproto/googleapis/api/annotations"
import google_protobuf1 "github.com/golang/protobuf/ptypes/empty"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
import context "golang.org/x/net/context"
import grpc "google.golang.org/grpc"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -36,24 +36,24 @@ var _ = math.Inf
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
// VersionMessage represents version of the ArgoCD API server
type VersionMessage struct {
Version string `protobuf:"bytes,1,opt,name=Version" json:"Version,omitempty"`
BuildDate string `protobuf:"bytes,2,opt,name=BuildDate" json:"BuildDate,omitempty"`
GitCommit string `protobuf:"bytes,3,opt,name=GitCommit" json:"GitCommit,omitempty"`
GitTag string `protobuf:"bytes,4,opt,name=GitTag" json:"GitTag,omitempty"`
GitTreeState string `protobuf:"bytes,5,opt,name=GitTreeState" json:"GitTreeState,omitempty"`
GoVersion string `protobuf:"bytes,6,opt,name=GoVersion" json:"GoVersion,omitempty"`
Compiler string `protobuf:"bytes,7,opt,name=Compiler" json:"Compiler,omitempty"`
Platform string `protobuf:"bytes,8,opt,name=Platform" json:"Platform,omitempty"`
Version string `protobuf:"bytes,1,opt,name=Version,proto3" json:"Version,omitempty"`
BuildDate string `protobuf:"bytes,2,opt,name=BuildDate,proto3" json:"BuildDate,omitempty"`
GitCommit string `protobuf:"bytes,3,opt,name=GitCommit,proto3" json:"GitCommit,omitempty"`
GitTag string `protobuf:"bytes,4,opt,name=GitTag,proto3" json:"GitTag,omitempty"`
GitTreeState string `protobuf:"bytes,5,opt,name=GitTreeState,proto3" json:"GitTreeState,omitempty"`
GoVersion string `protobuf:"bytes,6,opt,name=GoVersion,proto3" json:"GoVersion,omitempty"`
Compiler string `protobuf:"bytes,7,opt,name=Compiler,proto3" json:"Compiler,omitempty"`
Platform string `protobuf:"bytes,8,opt,name=Platform,proto3" json:"Platform,omitempty"`
}
func (m *VersionMessage) Reset() { *m = VersionMessage{} }
func (m *VersionMessage) String() string { return proto.CompactTextString(m) }
func (*VersionMessage) ProtoMessage() {}
func (*VersionMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (*VersionMessage) Descriptor() ([]byte, []int) { return fileDescriptorVersion, []int{0} }
func (m *VersionMessage) GetVersion() string {
if m != nil {
@@ -189,28 +189,542 @@ var _VersionService_serviceDesc = grpc.ServiceDesc{
Metadata: "server/version/version.proto",
}
func init() { proto.RegisterFile("server/version/version.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 310 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x91, 0xcd, 0x4a, 0x03, 0x31,
0x14, 0x85, 0x69, 0xd5, 0xfe, 0x84, 0xd2, 0x45, 0x90, 0x1a, 0xc6, 0x2e, 0xa4, 0x2b, 0x11, 0x9d,
0x80, 0xbe, 0x41, 0xab, 0x74, 0x25, 0x14, 0x2b, 0x2e, 0xdc, 0x65, 0xda, 0xdb, 0x18, 0x99, 0xcc,
0x1d, 0x32, 0x69, 0xc1, 0xad, 0xaf, 0xe0, 0xa3, 0xf9, 0x0a, 0x3e, 0x84, 0x4b, 0x99, 0xfc, 0x8c,
0xcc, 0x6a, 0x72, 0xce, 0x17, 0xce, 0xe4, 0x9e, 0x4b, 0xa6, 0x15, 0x98, 0x03, 0x18, 0x7e, 0x00,
0x53, 0x29, 0x2c, 0xe2, 0x37, 0x2d, 0x0d, 0x5a, 0xa4, 0xfd, 0x20, 0x93, 0xa9, 0x44, 0x94, 0x39,
0x70, 0x51, 0x2a, 0x2e, 0x8a, 0x02, 0xad, 0xb0, 0x0a, 0x8b, 0xca, 0x5f, 0x4b, 0xce, 0x03, 0x75,
0x2a, 0xdb, 0xef, 0x38, 0xe8, 0xd2, 0x7e, 0x78, 0x38, 0xfb, 0xed, 0x90, 0xf1, 0x8b, 0x8f, 0x79,
0x84, 0xaa, 0x12, 0x12, 0x28, 0x23, 0xfd, 0xe0, 0xb0, 0xce, 0x45, 0xe7, 0x72, 0xf8, 0x14, 0x25,
0x9d, 0x92, 0xe1, 0x7c, 0xaf, 0xf2, 0xed, 0xbd, 0xb0, 0xc0, 0xba, 0x8e, 0xfd, 0x1b, 0x35, 0x5d,
0x2a, 0xbb, 0x40, 0xad, 0x95, 0x65, 0x47, 0x9e, 0x36, 0x06, 0x9d, 0x90, 0xde, 0x52, 0xd9, 0x67,
0x21, 0xd9, 0xb1, 0x43, 0x41, 0xd1, 0x19, 0x19, 0xd5, 0x27, 0x03, 0xb0, 0xb6, 0x75, 0xec, 0x89,
0xa3, 0x2d, 0xcf, 0x25, 0x63, 0x7c, 0x53, 0x2f, 0x24, 0x47, 0x83, 0x26, 0x64, 0xb0, 0x40, 0x5d,
0xaa, 0x1c, 0x0c, 0xeb, 0x3b, 0xd8, 0xe8, 0x9a, 0xad, 0x72, 0x61, 0x77, 0x68, 0x34, 0x1b, 0x78,
0x16, 0xf5, 0x6d, 0xd6, 0x4c, 0xbe, 0x06, 0x73, 0x50, 0x1b, 0xa0, 0xab, 0x66, 0x72, 0x3a, 0x49,
0x7d, 0x6b, 0x69, 0x6c, 0x2d, 0x7d, 0xa8, 0x5b, 0x4b, 0xce, 0xd2, 0xb8, 0x83, 0x76, 0x6b, 0xb3,
0xd3, 0xcf, 0xef, 0x9f, 0xaf, 0xee, 0x98, 0x8e, 0xdc, 0x16, 0xc2, 0xa5, 0xf9, 0xf5, 0xeb, 0x95,
0x54, 0xf6, 0x6d, 0x9f, 0xa5, 0x1b, 0xd4, 0x5c, 0x18, 0x89, 0xa5, 0xc1, 0x77, 0x77, 0xb8, 0xd9,
0x6c, 0x79, 0x7b, 0xbd, 0x59, 0xcf, 0xfd, 0xec, 0xee, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x45, 0x81,
0x09, 0xaf, 0xf7, 0x01, 0x00, 0x00,
func (m *VersionMessage) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *VersionMessage) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Version) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.Version)))
i += copy(dAtA[i:], m.Version)
}
if len(m.BuildDate) > 0 {
dAtA[i] = 0x12
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.BuildDate)))
i += copy(dAtA[i:], m.BuildDate)
}
if len(m.GitCommit) > 0 {
dAtA[i] = 0x1a
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.GitCommit)))
i += copy(dAtA[i:], m.GitCommit)
}
if len(m.GitTag) > 0 {
dAtA[i] = 0x22
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.GitTag)))
i += copy(dAtA[i:], m.GitTag)
}
if len(m.GitTreeState) > 0 {
dAtA[i] = 0x2a
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.GitTreeState)))
i += copy(dAtA[i:], m.GitTreeState)
}
if len(m.GoVersion) > 0 {
dAtA[i] = 0x32
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.GoVersion)))
i += copy(dAtA[i:], m.GoVersion)
}
if len(m.Compiler) > 0 {
dAtA[i] = 0x3a
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.Compiler)))
i += copy(dAtA[i:], m.Compiler)
}
if len(m.Platform) > 0 {
dAtA[i] = 0x42
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.Platform)))
i += copy(dAtA[i:], m.Platform)
}
return i, nil
}
func encodeVarintVersion(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *VersionMessage) Size() (n int) {
var l int
_ = l
l = len(m.Version)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
l = len(m.BuildDate)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
l = len(m.GitCommit)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
l = len(m.GitTag)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
l = len(m.GitTreeState)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
l = len(m.GoVersion)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
l = len(m.Compiler)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
l = len(m.Platform)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
return n
}
func sovVersion(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozVersion(x uint64) (n int) {
return sovVersion(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *VersionMessage) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: VersionMessage: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: VersionMessage: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
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 ErrInvalidLengthVersion
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Version = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field BuildDate", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
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 ErrInvalidLengthVersion
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.BuildDate = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field GitCommit", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
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 ErrInvalidLengthVersion
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.GitCommit = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field GitTag", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
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 ErrInvalidLengthVersion
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.GitTag = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field GitTreeState", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
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 ErrInvalidLengthVersion
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.GitTreeState = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field GoVersion", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
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 ErrInvalidLengthVersion
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.GoVersion = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Compiler", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
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 ErrInvalidLengthVersion
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Compiler = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Platform", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVersion
}
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 ErrInvalidLengthVersion
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Platform = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipVersion(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthVersion
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipVersion(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowVersion
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowVersion
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowVersion
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthVersion
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowVersion
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipVersion(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthVersion = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowVersion = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("server/version/version.proto", fileDescriptorVersion) }
var fileDescriptorVersion = []byte{
// 327 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x91, 0xcd, 0x4a, 0xfb, 0x40,
0x14, 0xc5, 0x49, 0xff, 0x7f, 0xfb, 0x31, 0x94, 0x2e, 0x06, 0xa9, 0x43, 0x2c, 0x45, 0xba, 0x12,
0xc1, 0x04, 0x74, 0xe3, 0xba, 0x55, 0xba, 0x12, 0x8a, 0x15, 0x17, 0xee, 0x26, 0xed, 0x6d, 0x1c,
0xc9, 0xe4, 0x86, 0xc9, 0xb4, 0xe0, 0xd6, 0x57, 0xf0, 0xa5, 0x5c, 0x0a, 0xbe, 0x80, 0x14, 0x1f,
0xc2, 0xa5, 0x64, 0x3e, 0x22, 0x59, 0x65, 0xce, 0xf9, 0x0d, 0x27, 0x73, 0xcf, 0x25, 0xa3, 0x12,
0xd4, 0x0e, 0x54, 0xbc, 0x03, 0x55, 0x0a, 0xcc, 0xfd, 0x37, 0x2a, 0x14, 0x6a, 0xa4, 0x1d, 0x27,
0xc3, 0x51, 0x8a, 0x98, 0x66, 0x10, 0xf3, 0x42, 0xc4, 0x3c, 0xcf, 0x51, 0x73, 0x2d, 0x30, 0x2f,
0xed, 0xb5, 0xf0, 0xd8, 0x51, 0xa3, 0x92, 0xed, 0x26, 0x06, 0x59, 0xe8, 0x17, 0x0b, 0x27, 0x3f,
0x01, 0x19, 0x3c, 0xd8, 0x98, 0x5b, 0x28, 0x4b, 0x9e, 0x02, 0x65, 0xa4, 0xe3, 0x1c, 0x16, 0x9c,
0x04, 0xa7, 0xbd, 0x3b, 0x2f, 0xe9, 0x88, 0xf4, 0xa6, 0x5b, 0x91, 0xad, 0xaf, 0xb9, 0x06, 0xd6,
0x32, 0xec, 0xcf, 0xa8, 0xe8, 0x5c, 0xe8, 0x19, 0x4a, 0x29, 0x34, 0xfb, 0x67, 0x69, 0x6d, 0xd0,
0x21, 0x69, 0xcf, 0x85, 0xbe, 0xe7, 0x29, 0xfb, 0x6f, 0x90, 0x53, 0x74, 0x42, 0xfa, 0xd5, 0x49,
0x01, 0x2c, 0x75, 0x15, 0x7b, 0x60, 0x68, 0xc3, 0x33, 0xc9, 0xe8, 0xdf, 0xd4, 0x76, 0xc9, 0xde,
0xa0, 0x21, 0xe9, 0xce, 0x50, 0x16, 0x22, 0x03, 0xc5, 0x3a, 0x06, 0xd6, 0xba, 0x62, 0x8b, 0x8c,
0xeb, 0x0d, 0x2a, 0xc9, 0xba, 0x96, 0x79, 0x7d, 0x91, 0xd4, 0x93, 0x2f, 0x41, 0xed, 0xc4, 0x0a,
0xe8, 0xa2, 0x9e, 0x9c, 0x0e, 0x23, 0xdb, 0x5a, 0xe4, 0x5b, 0x8b, 0x6e, 0xaa, 0xd6, 0xc2, 0xa3,
0xc8, 0xef, 0xa0, 0xd9, 0xda, 0xe4, 0xf0, 0xf5, 0xf3, 0xfb, 0xad, 0x35, 0xa0, 0x7d, 0xb3, 0x05,
0x77, 0x69, 0x7a, 0xf5, 0xbe, 0x1f, 0x07, 0x1f, 0xfb, 0x71, 0xf0, 0xb5, 0x1f, 0x07, 0x8f, 0x67,
0xa9, 0xd0, 0x4f, 0xdb, 0x24, 0x5a, 0xa1, 0x8c, 0xb9, 0x4a, 0xb1, 0x50, 0xf8, 0x6c, 0x0e, 0xe7,
0xab, 0x75, 0xdc, 0x5c, 0x75, 0xd2, 0x36, 0x3f, 0xbe, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x19,
0x01, 0x2c, 0x30, 0x03, 0x02, 0x00, 0x00,
}

View File

@@ -151,10 +151,12 @@ func (f *Fixture) CreateApp(t *testing.T, application *v1alpha1.Application) *v1
// CreateController creates new controller instance
func (f *Fixture) CreateController() *controller.ApplicationController {
return controller.NewApplicationController(
f.Namespace,
f.KubeClient,
f.AppClient,
reposerver.NewRepositoryServerClientset(f.repoServerListener.Addr().String()),
f.ApiRepoService,
cluster.NewServer(f.Namespace, f.KubeClient, f.AppClient),
f.AppComparator,
time.Second,
&controller.ApplicationControllerConfig{Namespace: f.Namespace, InstanceID: f.InstanceID})
@@ -203,3 +205,7 @@ func (c *FakeGitClient) Reset(repoPath string) error {
// do nothing
return nil
}
func (c *FakeGitClient) CommitSHA(repoPath string) (string, error) {
return "abcdef123456890", nil
}

View File

@@ -3,11 +3,16 @@
package cli
import (
"bufio"
"fmt"
"os"
"strings"
"syscall"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/errors"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/client-go/tools/clientcmd"
)
@@ -49,3 +54,25 @@ func AddKubectlFlagsToCmd(cmd *cobra.Command) clientcmd.ClientConfig {
clientcmd.BindOverrideFlags(&overrides, cmd.PersistentFlags(), kflags)
return clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
}
// PromptCredentials is a helper to prompt the user for a username and password
func PromptCredentials(username, password string) (string, string) {
for username == "" {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Username: ")
usernameRaw, err := reader.ReadString('\n')
errors.CheckError(err)
username = strings.TrimSpace(usernameRaw)
}
for password == "" {
fmt.Print("Password: ")
passwordRaw, err := terminal.ReadPassword(syscall.Stdin)
errors.CheckError(err)
password = string(passwordRaw)
if password == "" {
fmt.Print("\n")
}
}
fmt.Print("\n")
return username, password
}

74
util/cli/config.go Normal file
View File

@@ -0,0 +1,74 @@
package cli
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/ghodss/yaml"
)
// unmarshalObject tries to convert a YAML or JSON byte array into the provided type.
func unmarshalObject(data []byte, obj interface{}) error {
// first, try unmarshaling as JSON
// Based on technique from Kubectl, which supports both YAML and JSON:
// https://mlafeldt.github.io/blog/teaching-go-programs-to-love-json-and-yaml/
// http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/
// Short version: JSON unmarshaling won't zero out null fields; YAML unmarshaling will.
// This may have unintended effects or hard-to-catch issues when populating our application object.
jsonData, err := yaml.YAMLToJSON(data)
if err != nil {
return err
}
err = json.Unmarshal(jsonData, &obj)
if err != nil {
return err
}
return err
}
// MarshalLocalYAMLFile writes JSON or YAML to a file on disk.
// The caller is responsible for checking error return values.
func MarshalLocalYAMLFile(path string, obj interface{}) error {
yamlData, err := yaml.Marshal(obj)
if err == nil {
err = ioutil.WriteFile(path, yamlData, 0600)
}
return err
}
// UnmarshalLocalFile retrieves JSON or YAML from a file on disk.
// The caller is responsible for checking error return values.
func UnmarshalLocalFile(path string, obj interface{}) error {
data, err := ioutil.ReadFile(path)
if err == nil {
err = unmarshalObject(data, obj)
}
return err
}
// UnmarshalRemoteFile retrieves JSON or YAML through a GET request.
// The caller is responsible for checking error return values.
func UnmarshalRemoteFile(url string, obj interface{}) error {
data, err := readRemoteFile(url)
if err == nil {
err = unmarshalObject(data, obj)
}
return err
}
// ReadRemoteFile issues a GET request to retrieve the contents of the specified URL as a byte array.
// The caller is responsible for checking error return values.
func readRemoteFile(url string) ([]byte, error) {
var data []byte
resp, err := http.Get(url)
if err == nil {
defer func() {
_ = resp.Body.Close()
}()
data, err = ioutil.ReadAll(resp.Body)
}
return data, err
}

94
util/cli/config_test.go Normal file
View File

@@ -0,0 +1,94 @@
package cli
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"testing"
)
func TestUnmarshalLocalFile(t *testing.T) {
const (
field1 = "Hello, world!"
field2 = 42
)
sentinel := fmt.Sprintf("---\nfield1: %q\nfield2: %d", field1, field2)
file, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
panic(err)
}
defer func() {
_ = os.Remove(file.Name())
}()
_, _ = file.WriteString(sentinel)
_ = file.Sync()
var testStruct struct {
Field1 string
Field2 int
}
err = UnmarshalLocalFile(file.Name(), &testStruct)
if err != nil {
t.Errorf("Could not unmarshal test data: %s", err)
}
if testStruct.Field1 != field1 || testStruct.Field2 != field2 {
t.Errorf("Test data did not match! Expected {%s %d} but got: %v", field1, field2, testStruct)
}
}
func TestUnmarshalRemoteFile(t *testing.T) {
const (
field1 = "Hello, world!"
field2 = 42
)
sentinel := fmt.Sprintf("---\nfield1: %q\nfield2: %d", field1, field2)
serve := func(c chan<- string) {
// listen on first available dynamic (unprivileged) port
listener, err := net.Listen("tcp", ":0")
if err != nil {
panic(err)
}
// send back the address so that it can be used
c <- listener.Addr().String()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// return the sentinel text at root URL
fmt.Fprint(w, sentinel)
})
panic(http.Serve(listener, nil))
}
c := make(chan string, 1)
// run a local webserver to test data retrieval
go serve(c)
address := <-c
t.Logf("Listening at address: %s", address)
data, err := readRemoteFile("http://" + address)
if string(data) != sentinel {
t.Errorf("Test data did not match (err = %v)! Expected %q and received %q", err, sentinel, string(data))
}
var testStruct struct {
Field1 string
Field2 int
}
err = UnmarshalRemoteFile("http://"+address, &testStruct)
if err != nil {
t.Errorf("Could not unmarshal test data: %s", err)
}
if testStruct.Field1 != field1 || testStruct.Field2 != field2 {
t.Errorf("Test data did not match! Expected {%s %d} but got: %v", field1, field2, testStruct)
}
}

View File

@@ -0,0 +1,150 @@
package config
import (
"crypto/tls"
"fmt"
"github.com/argoproj/argo-cd/common"
tlsutil "github.com/argoproj/argo-cd/util/tls"
apiv1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// ArgoCDSettings holds in-memory runtime configuration options.
type ArgoCDSettings struct {
// LocalUsers holds users local to (stored on) the server. This is to be distinguished from any potential alternative future login providers (LDAP, SAML, etc.) that might ever be added.
LocalUsers map[string]string
// ServerSignature holds the key used to generate JWT tokens.
ServerSignature []byte
// Certificate holds the certificate/private key for the ArgoCD API server.
// If nil, will run insecure without TLS.
Certificate *tls.Certificate
}
const (
// configManagerAdminPasswordKey designates the key for a root password inside a Kubernetes secret.
configManagerAdminPasswordKey = "admin.password"
// configManagerServerSignatureKey designates the key for a server secret key inside a Kubernetes secret.
configManagerServerSignatureKey = "server.secretkey"
// configManagerServerCertificate designates the key for the public cert used in TLS
configManagerServerCertificate = "server.crt"
// configManagerServerPrivateKey designates the key for the private key used in TLS
configManagerServerPrivateKey = "server.key"
)
// ConfigManager holds config info for a new manager with which to access Kubernetes ConfigMaps.
type ConfigManager struct {
clientset kubernetes.Interface
namespace string
}
// GetSettings retrieves settings from the ConfigManager.
func (mgr *ConfigManager) GetSettings() (*ArgoCDSettings, error) {
// TODO: we currently do not store anything in configmaps, yet. We eventually will (e.g.
// tuning parameters). Future settings/tunables should be stored here
_, err := mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Get(common.ArgoCDConfigMapName, metav1.GetOptions{})
if err != nil {
return nil, err
}
argoCDSecret, err := mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(common.ArgoCDSecretName, metav1.GetOptions{})
if err != nil {
return nil, err
}
var settings ArgoCDSettings
adminPasswordHash, ok := argoCDSecret.Data[configManagerAdminPasswordKey]
if !ok {
return nil, fmt.Errorf("admin user not found")
}
settings.LocalUsers = map[string]string{
common.ArgoCDAdminUsername: string(adminPasswordHash),
}
secretKey, ok := argoCDSecret.Data[configManagerServerSignatureKey]
if !ok {
return nil, fmt.Errorf("server secret key not found")
}
settings.ServerSignature = secretKey
serverCert, certOk := argoCDSecret.Data[configManagerServerCertificate]
serverKey, keyOk := argoCDSecret.Data[configManagerServerPrivateKey]
if certOk && keyOk {
cert, err := tls.X509KeyPair(serverCert, serverKey)
if err != nil {
return nil, fmt.Errorf("invalid x509 key pair %s/%s in secret: %s", configManagerServerCertificate, configManagerServerPrivateKey, err)
}
settings.Certificate = &cert
}
return &settings, nil
}
// SaveSettings serializes ArgoCD settings and upserts it into K8s secret/configmap
func (mgr *ConfigManager) SaveSettings(settings *ArgoCDSettings) error {
configMapData := make(map[string]string)
_, err := mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Get(common.ArgoCDConfigMapName, metav1.GetOptions{})
if err != nil {
if !apierr.IsNotFound(err) {
return err
}
newConfigMap := &apiv1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDConfigMapName,
},
Data: configMapData,
}
_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Create(newConfigMap)
if err != nil {
return err
}
} else {
// mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update()
}
secretStringData := map[string]string{
configManagerServerSignatureKey: string(settings.ServerSignature),
configManagerAdminPasswordKey: settings.LocalUsers[common.ArgoCDAdminUsername],
}
if settings.Certificate != nil {
certBytes, keyBytes := tlsutil.EncodeX509KeyPair(*settings.Certificate)
secretStringData[configManagerServerCertificate] = string(certBytes)
secretStringData[configManagerServerPrivateKey] = string(keyBytes)
}
argoCDSecret, err := mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(common.ArgoCDSecretName, metav1.GetOptions{})
if err != nil {
if !apierr.IsNotFound(err) {
return err
}
newSecret := &apiv1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDSecretName,
},
StringData: secretStringData,
}
_, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Create(newSecret)
if err != nil {
return err
}
} else {
argoCDSecret.Data = nil
argoCDSecret.StringData = secretStringData
_, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Update(argoCDSecret)
if err != nil {
return err
}
}
return nil
}
// NewConfigManager generates a new ConfigManager pointer and returns it
func NewConfigManager(clientset kubernetes.Interface, namespace string) *ConfigManager {
return &ConfigManager{
clientset: clientset,
namespace: namespace,
}
}

View File

@@ -1,148 +0,0 @@
package util
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/kubernetes"
)
// ArgoCDSettings holds in-memory runtime configuration options.
type ArgoCDSettings struct {
// LocalUsers holds users local to (stored on) the server. This is to be distinguished from any potential alternative future login providers (LDAP, SAML, etc.) that might ever be added.
LocalUsers map[string]string
}
type configMapData struct {
rootCredentialsSecretName string
}
const (
// defaultConfigMapName default name of config map with argo-cd settings
defaultConfigMapName = "argo-cd-cm"
// defaultRootCredentialsSecretName contains default name of secret with root user credentials
defaultRootCredentialsSecretName = "argo-cd-root-credentials"
// configManagerRootUsernameKey designates the root username inside a Kubernetes secret.
configManagerRootUsernameKey = "root.username"
// configManagerRootPasswordKey designates the root password inside a Kubernetes secret.
configManagerRootPasswordKey = "root.password"
)
// ConfigManager holds config info for a new manager with which to access Kubernetes ConfigMaps.
type ConfigManager struct {
clientset kubernetes.Interface
namespace string
configMapName string
}
// GetSettings retrieves settings from the ConfigManager.
func (mgr *ConfigManager) GetSettings() (ArgoCDSettings, error) {
settings := ArgoCDSettings{}
settings.LocalUsers = make(map[string]string)
data, err := mgr.getConfigMapData()
if err != nil {
return settings, err
}
// Try to retrieve the secret
rootCredentials, err := mgr.readSecret(data.rootCredentialsSecretName)
if err != nil {
if errors.IsNotFound(err) {
return settings, nil
} else {
return settings, err
}
}
// Retrieve credential info from the secret
rootUsername, okUsername := rootCredentials.Data[configManagerRootUsernameKey]
rootPassword, okPassword := rootCredentials.Data[configManagerRootPasswordKey]
if okUsername && okPassword {
// Store credential info inside LocalUsers
settings.LocalUsers[string(rootUsername)] = string(rootPassword)
}
return settings, nil
}
func (mgr *ConfigManager) SetRootUserCredentials(username string, password string) error {
data, err := mgr.getConfigMapData()
if err != nil {
return err
}
// Don't commit plaintext passwords
passwordHash, err := HashPassword(password)
if err != nil {
return err
}
credentials := map[string]string{
configManagerRootUsernameKey: username,
configManagerRootPasswordKey: passwordHash,
}
// See if we've already written this secret
secret, err := mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(data.rootCredentialsSecretName, metav1.GetOptions{})
if err != nil {
newSecret := &apiv1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: data.rootCredentialsSecretName,
},
}
newSecret.StringData = credentials
_, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Create(newSecret)
} else {
secret.StringData = credentials
_, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Update(secret)
}
return err
}
// NewConfigManager generates a new ConfigManager pointer and returns it
func NewConfigManager(clientset kubernetes.Interface, namespace, configMapName string) (mgr *ConfigManager) {
if configMapName == "" {
configMapName = defaultConfigMapName
}
mgr = &ConfigManager{
clientset: clientset,
namespace: namespace,
configMapName: configMapName,
}
return
}
func (mgr *ConfigManager) getConfigMapData() (configMapData, error) {
data := configMapData{}
configMap, err := mgr.readConfigMap(mgr.configMapName)
if err != nil {
if errors.IsNotFound(err) {
data.rootCredentialsSecretName = defaultRootCredentialsSecretName
return data, nil
} else {
return data, err
}
}
rootCredentialsSecretName, ok := configMap.Data[defaultRootCredentialsSecretName]
if !ok {
rootCredentialsSecretName = defaultRootCredentialsSecretName
}
data.rootCredentialsSecretName = rootCredentialsSecretName
return data, nil
}
// ReadConfigMap retrieves a config map from Kubernetes.
func (mgr *ConfigManager) readConfigMap(name string) (configMap *apiv1.ConfigMap, err error) {
configMap, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Get(name, metav1.GetOptions{})
return
}
// ReadSecret retrieves a secret from Kubernetes.
func (mgr *ConfigManager) readSecret(name string) (secret *apiv1.Secret, err error) {
secret, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(name, metav1.GetOptions{})
return
}

View File

@@ -26,7 +26,7 @@ func Diff(left, right *unstructured.Unstructured) *DiffResult {
leftObj = left.Object
}
if right != nil {
rightObj = removeMapFields(leftObj, right.Object)
rightObj = RemoveMapFields(leftObj, right.Object)
}
gjDiff := gojsondiff.New().CompareObjects(leftObj, rightObj)
dr := DiffResult{
@@ -75,7 +75,7 @@ func (d *DiffResult) ASCIIFormat(left *unstructured.Unstructured, formatOpts for
func removeFields(config, live interface{}) interface{} {
switch c := config.(type) {
case map[string]interface{}:
return removeMapFields(c, live.(map[string]interface{}))
return RemoveMapFields(c, live.(map[string]interface{}))
case []interface{}:
return removeListFields(c, live.([]interface{}))
default:
@@ -83,7 +83,7 @@ func removeFields(config, live interface{}) interface{} {
}
}
func removeMapFields(config, live map[string]interface{}) map[string]interface{} {
func RemoveMapFields(config, live map[string]interface{}) map[string]interface{} {
result := map[string]interface{}{}
for k, v1 := range config {
v2, ok := live[k]

View File

@@ -3,8 +3,12 @@ package git
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path"
"strings"
log "github.com/sirupsen/logrus"
)
@@ -13,16 +17,66 @@ import (
type Client interface {
CloneOrFetch(url string, username string, password string, sshPrivateKey string, repoPath string) error
Checkout(repoPath string, sha string) (string, error)
CommitSHA(repoPath string) (string, error)
Reset(repoPath string) error
}
// NativeGitClient implements Client interface using git CLI
type NativeGitClient struct {
rootDirectoryPath string
type NativeGitClient struct{}
// Init initializes a local git repository and sets the remote origin
func (m *NativeGitClient) Init(repo string, repoPath string) error {
log.Infof("Initializing %s to %s", repo, repoPath)
err := os.MkdirAll(repoPath, 0755)
if err != nil {
return err
}
if _, err := runCmd(repoPath, "git", "init"); err != nil {
return err
}
if _, err := runCmd(repoPath, "git", "remote", "add", "origin", repo); err != nil {
return err
}
return nil
}
// SetCredentials sets a local credentials file to connect to a remote git repository
func (m *NativeGitClient) SetCredentials(repo string, username string, password string, sshPrivateKey string, repoPath string) error {
if password != "" {
gitCredentialsFile := path.Join(repoPath, ".git", "credentials")
repoURL, err := url.ParseRequestURI(repo)
if err != nil {
return err
}
repoURL.User = url.UserPassword(username, password)
cmdURL := repoURL.String()
err = ioutil.WriteFile(gitCredentialsFile, []byte(cmdURL), 0600)
if err != nil {
return fmt.Errorf("failed to set git credentials: %v", err)
}
_, err = runCmd(repoPath, "git", "config", "--local", "credential.helper", fmt.Sprintf("store --file=%s", gitCredentialsFile))
if err != nil {
return err
}
}
if sshPrivateKey != "" {
sshPrivateKeyFile := path.Join(repoPath, ".git", "ssh-private-key")
err := ioutil.WriteFile(sshPrivateKeyFile, []byte(sshPrivateKey), 0600)
if err != nil {
return fmt.Errorf("failed to set git credentials: %v", err)
}
sshCmd := fmt.Sprintf("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i %s", sshPrivateKeyFile)
_, err = runCmd(repoPath, "git", "config", "--local", "core.sshCommand", sshCmd)
if err != nil {
return err
}
}
return nil
}
// CloneOrFetch either clone or fetch repository into specified directory path.
func (m *NativeGitClient) CloneOrFetch(repo string, username string, password string, sshPrivateKey string, repoPath string) error {
log.Debugf("Cloning/Fetching repo %s at %s", repo, repoPath)
var needClone bool
if _, err := os.Stat(repoPath); os.IsNotExist(err) {
needClone = true
@@ -32,88 +86,104 @@ func (m *NativeGitClient) CloneOrFetch(repo string, username string, password st
_, err = cmd.Output()
needClone = err != nil
}
repoURL, env, err := GetGitCommandEnvAndURL(repo, username, password, sshPrivateKey)
if err != nil {
return err
}
if needClone {
_, err := exec.Command("rm", "-rf", repoPath).Output()
if err != nil {
return fmt.Errorf("unable to clean repo cache at %s: %v", repoPath, err)
}
log.Infof("Cloning %s to %s", repo, repoPath)
cmd := exec.Command("git", "clone", repoURL, repoPath)
cmd.Env = env
_, err = cmd.Output()
err = m.Init(repo, repoPath)
if err != nil {
return fmt.Errorf("unable to clone repository %s: %v", repo, err)
}
} else {
log.Infof("Fetching %s", repo)
// Fetch remote changes and delete all local branches
cmd := exec.Command("sh", "-c", "git fetch --all && git checkout --detach HEAD")
cmd.Env = env
cmd.Dir = repoPath
_, err := cmd.Output()
if err != nil {
return fmt.Errorf("unable to fetch repo %s: %v", repoPath, err)
}
}
cmd = exec.Command("sh", "-c", "for i in $(git branch --merged | grep -v \\*); do git branch -D $i; done")
cmd.Dir = repoPath
_, err = cmd.Output()
if err != nil {
return fmt.Errorf("unable to delete local branches for %s: %v", repoPath, err)
err := m.SetCredentials(repo, username, password, sshPrivateKey, repoPath)
if err != nil {
return err
}
// Fetch remote changes
if _, err = runCmd(repoPath, "git", "fetch", "origin"); err != nil {
return err
}
// git fetch does not update the HEAD reference. The following command will update the local
// knowledge of what remote considers the “default branch”
// See: https://stackoverflow.com/questions/8839958/how-does-origin-head-get-set
if _, err := runCmd(repoPath, "git", "remote", "set-head", "origin", "-a"); err != nil {
return err
}
// Delete all local branches (we must first detach so we are not checked out a branch we are about to delete)
if _, err = runCmd(repoPath, "git", "checkout", "--detach", "origin/HEAD"); err != nil {
return err
}
branchesOut, err := runCmd(repoPath, "git", "for-each-ref", "--format=%(refname:short)", "refs/heads/")
if err != nil {
return err
}
branchesOut = strings.TrimSpace(branchesOut)
if branchesOut != "" {
branches := strings.Split(branchesOut, "\n")
args := []string{"branch", "-D"}
args = append(args, branches...)
if _, err = runCmd(repoPath, "git", args...); err != nil {
return err
}
}
return nil
}
// Reset resets local changes
// Reset resets local changes in a repository
func (m *NativeGitClient) Reset(repoPath string) error {
cmd := exec.Command("sh", "-c", "git reset --hard HEAD && git clean -f")
cmd.Dir = repoPath
_, err := cmd.Output()
if err != nil {
return fmt.Errorf("unable to reset repository %s: %v", repoPath, err)
if _, err := runCmd(repoPath, "git", "reset", "--hard", "origin/HEAD"); err != nil {
return err
}
if _, err := runCmd(repoPath, "git", "clean", "-f"); err != nil {
return err
}
return nil
}
// Checkout checkout specified git sha
func (m *NativeGitClient) Checkout(repoPath string, sha string) (string, error) {
if sha == "" {
sha = "origin/HEAD"
func (m *NativeGitClient) Checkout(repoPath string, revision string) (string, error) {
if revision == "" || revision == "HEAD" {
revision = "origin/HEAD"
}
checkoutCmd := exec.Command("git", "checkout", sha)
checkoutCmd.Dir = repoPath
_, err := checkoutCmd.Output()
if err != nil {
return "", fmt.Errorf("unable to checkout revision %s: %v", sha, err)
if _, err := runCmd(repoPath, "git", "checkout", revision); err != nil {
return "", err
}
revisionCmd := exec.Command("git", "rev-parse", "HEAD")
revisionCmd.Dir = repoPath
output, err := revisionCmd.Output()
return m.CommitSHA(repoPath)
}
// CommitSHA returns current commit sha from `git rev-parse HEAD`
func (m *NativeGitClient) CommitSHA(repoPath string) (string, error) {
out, err := runCmd(repoPath, "git", "rev-parse", "HEAD")
if err != nil {
return "", err
}
return string(output), nil
return strings.TrimSpace(out), nil
}
// NewNativeGitClient creates new instance of NativeGitClient
func NewNativeGitClient() (Client, error) {
rootDirPath, err := ioutil.TempDir("", "argo-git")
if err != nil {
return nil, err
}
return &NativeGitClient{
rootDirectoryPath: rootDirPath,
}, nil
return &NativeGitClient{}, nil
}
// runCmd is a convenience function to run a command in a given directory and return its output
func runCmd(cwd string, command string, args ...string) (string, error) {
cmd := exec.Command(command, args...)
log.Debug(strings.Join(cmd.Args, " "))
cmd.Dir = cwd
out, err := cmd.Output()
if len(out) > 0 {
log.Debug(string(out))
}
if err != nil {
exErr, ok := err.(*exec.ExitError)
if ok {
errOutput := strings.Split(string(exErr.Stderr), "\n")[0]
log.Debug(errOutput)
return string(out), fmt.Errorf("'%s' failed: %v", strings.Join(cmd.Args, " "), errOutput)
}
return string(out), fmt.Errorf("'%s' failed: %v", strings.Join(cmd.Args, " "), err)
}
return string(out), nil
}

View File

@@ -71,7 +71,7 @@ func TestRepo(repo, username, password string, sshPrivateKey string) error {
exErr := err.(*exec.ExitError)
errOutput := strings.Split(string(exErr.Stderr), "\n")[0]
errOutput = redactPassword(errOutput, password)
return fmt.Errorf("failed to test %s: %s", repo, errOutput)
return fmt.Errorf("%s: %s", repo, errOutput)
}
return nil
}

View File

@@ -9,17 +9,24 @@ type Client struct {
}
// Checkout provides a mock function with given fields: repoPath, sha
func (_m *Client) Checkout(repoPath string, sha string) error {
func (_m *Client) Checkout(repoPath string, sha string) (string, error) {
ret := _m.Called(repoPath, sha)
var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok {
var r0 string
if rf, ok := ret.Get(0).(func(string, string) string); ok {
r0 = rf(repoPath, sha)
} else {
r0 = ret.Error(0)
r0 = ret.Get(0).(string)
}
return r0
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(repoPath, sha)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CloneOrFetch provides a mock function with given fields: url, username, password, sshPrivateKey, repoPath
@@ -35,3 +42,38 @@ func (_m *Client) CloneOrFetch(url string, username string, password string, ssh
return r0
}
// CommitSHA provides a mock function with given fields: repoPath
func (_m *Client) CommitSHA(repoPath string) (string, error) {
ret := _m.Called(repoPath)
var r0 string
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(repoPath)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(repoPath)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Reset provides a mock function with given fields: repoPath
func (_m *Client) Reset(repoPath string) error {
ret := _m.Called(repoPath)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(repoPath)
} else {
r0 = ret.Error(0)
}
return r0
}

75
util/grpc/errors.go Normal file
View File

@@ -0,0 +1,75 @@
package grpc
import (
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apierr "k8s.io/apimachinery/pkg/api/errors"
)
func kubeErrToGRPC(err error) error {
/*
Unmapped source Kubernetes API errors as of 2018-04-16:
* IsConflict => 409
* IsGone => 410
* IsResourceExpired => 410
* IsServerTimeout => 500
* IsTooManyRequests => 429
* IsUnexpectedServerError => should probably be a panic
* IsUnexpectedObjectError => should probably be a panic
Unmapped target gRPC codes as of 2018-04-16:
* Canceled Code = 1
* Unknown Code = 2
* ResourceExhausted Code = 8
* Aborted Code = 10
* OutOfRange Code = 11
* DataLoss Code = 15
*/
rewrapError := func(err error, code codes.Code) error {
return status.Errorf(code, err.Error())
}
switch {
case apierr.IsNotFound(err):
err = rewrapError(err, codes.NotFound)
case apierr.IsAlreadyExists(err):
err = rewrapError(err, codes.AlreadyExists)
case apierr.IsInvalid(err):
err = rewrapError(err, codes.InvalidArgument)
case apierr.IsMethodNotSupported(err):
err = rewrapError(err, codes.Unimplemented)
case apierr.IsServiceUnavailable(err):
err = rewrapError(err, codes.Unavailable)
case apierr.IsBadRequest(err):
err = rewrapError(err, codes.FailedPrecondition)
case apierr.IsUnauthorized(err):
err = rewrapError(err, codes.Unauthenticated)
case apierr.IsForbidden(err):
err = rewrapError(err, codes.PermissionDenied)
case apierr.IsTimeout(err):
err = rewrapError(err, codes.DeadlineExceeded)
case apierr.IsInternalError(err):
err = rewrapError(err, codes.Internal)
}
return err
}
// ErrorCodeUnaryServerInterceptor replaces Kubernetes errors with relevant gRPC equivalents, if any.
func ErrorCodeUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
resp, err = handler(ctx, req)
return resp, kubeErrToGRPC(err)
}
}
// ErrorCodeStreamServerInterceptor replaces Kubernetes errors with relevant gRPC equivalents, if any.
func ErrorCodeStreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
err := handler(srv, ss)
return kubeErrToGRPC(err)
}
}

View File

@@ -1,12 +1,18 @@
package grpc
import (
"crypto/tls"
"net"
"runtime/debug"
"strings"
"time"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/status"
)
// PanicLoggerUnaryServerInterceptor returns a new unary server interceptor for recovering from panics and returning error
@@ -15,7 +21,7 @@ func PanicLoggerUnaryServerInterceptor(log *logrus.Entry) grpc.UnaryServerInterc
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
err = grpc.Errorf(codes.Internal, "%s", r)
err = status.Errorf(codes.Internal, "%s", r)
}
}()
return handler(ctx, req)
@@ -28,9 +34,118 @@ func PanicLoggerStreamServerInterceptor(log *logrus.Entry) grpc.StreamServerInte
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
err = grpc.Errorf(codes.Internal, "%s", r)
err = status.Errorf(codes.Internal, "%s", r)
}
}()
return handler(srv, stream)
}
}
// BlockingDial is a helper method to dial the given address, using optional TLS credentials,
// and blocking until the returned connection is ready. If the given credentials are nil, the
// connection will be insecure (plain-text).
// Lifted from: https://github.com/fullstorydev/grpcurl/blob/master/grpcurl.go
func BlockingDial(ctx context.Context, network, address string, creds credentials.TransportCredentials, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
// grpc.Dial doesn't provide any information on permanent connection errors (like
// TLS handshake failures). So in order to provide good error messages, we need a
// custom dialer that can provide that info. That means we manage the TLS handshake.
result := make(chan interface{}, 1)
writeResult := func(res interface{}) {
// non-blocking write: we only need the first result
select {
case result <- res:
default:
}
}
dialer := func(address string, timeout time.Duration) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
conn, err := (&net.Dialer{Cancel: ctx.Done()}).Dial(network, address)
if err != nil {
writeResult(err)
return nil, err
}
if creds != nil {
conn, _, err = creds.ClientHandshake(ctx, address, conn)
if err != nil {
writeResult(err)
return nil, err
}
}
return conn, nil
}
// Even with grpc.FailOnNonTempDialError, this call will usually timeout in
// the face of TLS handshake errors. So we can't rely on grpc.WithBlock() to
// know when we're done. So we run it in a goroutine and then use result
// channel to either get the channel or fail-fast.
go func() {
opts = append(opts,
grpc.WithBlock(),
grpc.FailOnNonTempDialError(true),
grpc.WithDialer(dialer),
grpc.WithInsecure(), // we are handling TLS, so tell grpc not to
)
conn, err := grpc.DialContext(ctx, address, opts...)
var res interface{}
if err != nil {
res = err
} else {
res = conn
}
writeResult(res)
}()
select {
case res := <-result:
if conn, ok := res.(*grpc.ClientConn); ok {
return conn, nil
}
return nil, res.(error)
case <-ctx.Done():
return nil, ctx.Err()
}
}
type TLSTestResult struct {
TLS bool
InsecureErr error
}
func TestTLS(address string) (*TLSTestResult, error) {
if parts := strings.Split(address, ":"); len(parts) == 1 {
// If port is unspecified, assume the most likely port
address += ":443"
}
var testResult TLSTestResult
var tlsConfig tls.Config
tlsConfig.InsecureSkipVerify = true
creds := credentials.NewTLS(&tlsConfig)
conn, err := BlockingDial(context.Background(), "tcp", address, creds)
if err == nil {
_ = conn.Close()
testResult.TLS = true
creds := credentials.NewTLS(&tls.Config{})
conn, err := BlockingDial(context.Background(), "tcp", address, creds)
if err == nil {
_ = conn.Close()
} else {
// if connection was successful with InsecureSkipVerify true, but unsuccessful with
// InsecureSkipVerify false, it means server is not configured securely
testResult.InsecureErr = err
}
return &testResult, nil
}
// If we get here, we were unable to connect via TLS (even with InsecureSkipVerify: true)
// It may be because server is running without TLS, or because of real issues (e.g. connection
// refused). Test if server accepts plain-text connections
conn, err = BlockingDial(context.Background(), "tcp", address, nil)
if err == nil {
_ = conn.Close()
testResult.TLS = false
return &testResult, nil
}
return nil, err
}

View File

@@ -3,21 +3,30 @@ package ksonnet
import (
"fmt"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"encoding/json"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/diff"
"github.com/ghodss/yaml"
"github.com/ksonnet/ksonnet/metadata"
"github.com/ksonnet/ksonnet/metadata/app"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/component"
log "github.com/sirupsen/logrus"
"github.com/spf13/afero"
"k8s.io/api/apps/v1beta1"
"k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
v1ExtBeta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var (
diffSeparator = regexp.MustCompile(`\n---`)
lineSeparator = regexp.MustCompile(`\n`)
)
// KsonnetApp represents a ksonnet application directory and provides wrapper functionality around
@@ -29,6 +38,9 @@ type KsonnetApp interface {
// App is the Ksonnet application
App() app.App
// Spec is the Ksonnet application spec
Spec() *app.Spec
// Show returns a list of unstructured objects that would be applied to an environment
Show(environment string) ([]*unstructured.Unstructured, error)
@@ -39,24 +51,41 @@ type KsonnetApp interface {
SetComponentParams(environment string, component string, param string, value string) error
}
// KsonnetVersion returns the version of ksonnet used when running ksonnet commands
func KsonnetVersion() (string, error) {
cmd := exec.Command("ks", "version")
out, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("unable to determine ksonnet version: %v", err)
}
ksonnetVersionStr := strings.Split(string(out), "\n")[0]
parts := strings.SplitN(ksonnetVersionStr, ":", 2)
if len(parts) != 2 {
return "", fmt.Errorf("unexpected version string format: %s", ksonnetVersionStr)
}
return strings.TrimSpace(parts[1]), nil
}
type ksonnetApp struct {
manager metadata.Manager
app app.App
app app.App
spec app.Spec
}
// NewKsonnetApp tries to create a new wrapper to run commands on the `ks` command-line tool.
func NewKsonnetApp(path string) (KsonnetApp, error) {
ksApp := ksonnetApp{}
mgr, err := metadata.Find(path)
a, err := app.Load(afero.NewOsFs(), path, false)
if err != nil {
return nil, err
}
ksApp.manager = mgr
app, err := ksApp.manager.App()
ksApp.app = a
var spec app.Spec
err = cli.UnmarshalLocalFile(filepath.Join(a.Root(), "app.yaml"), &spec)
if err != nil {
return nil, err
}
ksApp.app = app
ksApp.spec = spec
return &ksApp, nil
}
@@ -80,14 +109,19 @@ func (k *ksonnetApp) ksCmd(args ...string) (string, error) {
}
func (k *ksonnetApp) Root() string {
return k.manager.Root()
return k.app.Root()
}
// Spec is the Ksonnet application spec (app.yaml)
// App is the Ksonnet application
func (k *ksonnetApp) App() app.App {
return k.app
}
// Spec is the Ksonnet application spec
func (k *ksonnetApp) Spec() *app.Spec {
return &k.spec
}
// Show generates a concatenated list of Kubernetes manifests in the given environment.
func (k *ksonnetApp) Show(environment string) ([]*unstructured.Unstructured, error) {
out, err := k.ksCmd("show", environment)
@@ -105,35 +139,77 @@ func (k *ksonnetApp) Show(environment string) ([]*unstructured.Unstructured, err
if err != nil {
return nil, fmt.Errorf("Failed to unmarshal manifest from `ks show`")
}
err = remarshal(&obj)
if err != nil {
return nil, err
}
objs = append(objs, &obj)
}
// TODO(jessesuen): we need to sort objects based on their dependency order of creation
return objs, nil
}
// remarshal checks resource kind and version and re-marshal using corresponding struct custom marshaller. This ensures that expected resource state is formatter same as actual
// resource state in kubernetes and allows to find differences between actual and target states more accurate.
func remarshal(obj *unstructured.Unstructured) error {
var newObj interface{}
switch obj.GetAPIVersion() + ":" + obj.GetKind() {
case "apps/v1beta1:Deployment":
newObj = &v1beta1.Deployment{}
case "apps/v1beta2:Deployment":
newObj = &v1beta2.Deployment{}
case "extensions/v1beta1":
newObj = &v1ExtBeta1.Deployment{}
case "apps/v1beta1:StatefulSet":
newObj = &v1beta1.StatefulSet{}
case "apps/v1beta2:StatefulSet":
newObj = &v1beta2.StatefulSet{}
case "v1:Service":
newObj = &corev1.Service{}
}
if newObj != nil {
oldObj := obj.Object
data, err := json.Marshal(obj)
if err != nil {
return err
}
err = json.Unmarshal(data, newObj)
if err != nil {
return err
}
data, err = json.Marshal(newObj)
if err != nil {
return err
}
err = json.Unmarshal(data, obj)
if err != nil {
return err
}
// remove all default values specified by custom formatter
obj.Object = diff.RemoveMapFields(oldObj, obj.Object)
}
return nil
}
// ListEnvParams returns list of environment parameters
func (k *ksonnetApp) ListEnvParams(environment string) ([]*v1alpha1.ComponentParameter, error) {
// count of rows to skip in command-line output
const skipRows = 2
out, err := k.ksCmd("param", "list", "--env", environment)
mod, err := component.DefaultManager.Module(k.app, "")
if err != nil {
return nil, err
}
ksParams, err := mod.Params(environment)
if err != nil {
return nil, err
}
var params []*v1alpha1.ComponentParameter
rows := lineSeparator.Split(out, -1)
for _, row := range rows[skipRows:] {
if strings.TrimSpace(row) == "" {
continue
}
fields := strings.Fields(row)
component, param, rawValue := fields[0], fields[1], fields[2]
value, err := strconv.Unquote(rawValue)
for _, ksParam := range ksParams {
value, err := strconv.Unquote(ksParam.Value)
if err != nil {
value = rawValue
value = ksParam.Value
}
componentParam := v1alpha1.ComponentParameter{
Component: component,
Name: param,
Component: ksParam.Component,
Name: ksParam.Key,
Value: value,
}
params = append(params, &componentParam)

View File

@@ -1,10 +1,15 @@
// Package kube provides helper utilities common for kubernetes
package kube
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"sync"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@@ -14,12 +19,38 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
const (
listVerb = "list"
deleteVerb = "delete"
deleteCollectionVerb = "deletecollection"
)
const (
ServiceKind = "Service"
EndpointsKind = "Endpoints"
)
var (
// location to use for generating temporary files, such as the ca.crt needed by kubectl
kubectlTempDir string
)
func init() {
fileInfo, err := os.Stat("/dev/shm")
if err == nil && fileInfo.IsDir() {
kubectlTempDir = "/dev/shm"
}
}
// TestConfig tests to make sure the REST config is usable
func TestConfig(config *rest.Config) error {
kubeclientset, err := kubernetes.NewForConfig(config)
@@ -84,8 +115,135 @@ func GetLiveResource(dclient dynamic.Interface, obj *unstructured.Unstructured,
return liveObj, nil
}
func WatchResourcesWithLabel(ctx context.Context, config *rest.Config, namespace string, labelName string) (chan watch.Event, error) {
log.Infof("Start watching for resources changes with label %s in cluster %s", labelName, config.Host)
dynClientPool := dynamic.NewDynamicClientPool(config)
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
}
serverResources, err := disco.ServerResources()
if err != nil {
return nil, err
}
resources := make([]dynamic.ResourceInterface, 0)
for _, apiResourcesList := range serverResources {
for i := range apiResourcesList.APIResources {
apiResource := apiResourcesList.APIResources[i]
watchSupported := false
for _, verb := range apiResource.Verbs {
if verb == "watch" {
watchSupported = true
break
}
}
if watchSupported {
dclient, err := dynClientPool.ClientForGroupVersionKind(schema.FromAPIVersionAndKind(apiResourcesList.GroupVersion, apiResource.Kind))
if err != nil {
return nil, err
}
resources = append(resources, dclient.Resource(&apiResource, namespace))
}
}
}
ch := make(chan watch.Event)
go func() {
var wg sync.WaitGroup
wg.Add(len(resources))
for i := 0; i < len(resources); i++ {
resource := resources[i]
go func() {
defer wg.Done()
watch, err := resource.Watch(metav1.ListOptions{LabelSelector: labelName})
go func() {
select {
case <-ctx.Done():
watch.Stop()
}
}()
if err == nil {
for event := range watch.ResultChan() {
ch <- event
}
}
}()
}
wg.Wait()
close(ch)
log.Infof("Stop watching for resources changes with label %s in cluster %s", labelName, config.ServerName)
}()
return ch, nil
}
// GetResourcesWithLabel returns all kubernetes resources with specified label
func GetResourcesWithLabel(config *rest.Config, namespace string, labelName string, labelValue string) ([]*unstructured.Unstructured, error) {
dynClientPool := dynamic.NewDynamicClientPool(config)
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
}
resources, err := disco.ServerResources()
if err != nil {
return nil, err
}
var resourceInterfaces []dynamic.ResourceInterface
for _, apiResourcesList := range resources {
for i := range apiResourcesList.APIResources {
apiResource := apiResourcesList.APIResources[i]
listSupported := false
for _, verb := range apiResource.Verbs {
if verb == listVerb {
listSupported = true
break
}
}
if listSupported {
dclient, err := dynClientPool.ClientForGroupVersionKind(schema.FromAPIVersionAndKind(apiResourcesList.GroupVersion, apiResource.Kind))
if err != nil {
return nil, err
}
resourceInterfaces = append(resourceInterfaces, dclient.Resource(&apiResource, namespace))
}
}
}
var asyncErr error
var result []*unstructured.Unstructured
var wg sync.WaitGroup
wg.Add(len(resourceInterfaces))
for i := range resourceInterfaces {
client := resourceInterfaces[i]
go func() {
defer wg.Done()
list, err := client.List(metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", labelName, labelValue),
})
if err != nil {
asyncErr = err
return
}
// apply client side filtering since not every kubernetes API supports label filtering
for i := range list.(*unstructured.UnstructuredList).Items {
item := list.(*unstructured.UnstructuredList).Items[i]
labels := item.GetLabels()
if labels != nil {
if value, ok := labels[labelName]; ok && value == labelValue {
result = append(result, &item)
}
}
}
}()
}
wg.Wait()
return result, asyncErr
}
// DeleteResourceWithLabel delete all resources which match to specified label selector
func DeleteResourceWithLabel(config *rest.Config, namespace string, labelSelector string) error {
func DeleteResourceWithLabel(config *rest.Config, namespace string, labelName string, labelValue string) error {
dynClientPool := dynamic.NewDynamicClientPool(config)
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
@@ -95,14 +253,21 @@ func DeleteResourceWithLabel(config *rest.Config, namespace string, labelSelecto
if err != nil {
return err
}
var resourceInterfaces []struct {
dynamic.ResourceInterface
bool
}
for _, apiResourcesList := range resources {
for _, apiResource := range apiResourcesList.APIResources {
for i := range apiResourcesList.APIResources {
apiResource := apiResourcesList.APIResources[i]
deleteCollectionSupported := false
deleteSupported := false
for _, verb := range apiResource.Verbs {
if verb == "deletecollection" {
if verb == deleteCollectionVerb {
deleteCollectionSupported = true
} else if verb == "delete" {
} else if verb == deleteVerb {
deleteSupported = true
}
}
@@ -110,31 +275,61 @@ func DeleteResourceWithLabel(config *rest.Config, namespace string, labelSelecto
if err != nil {
return err
}
propagationPolicy := metav1.DeletePropagationForeground
if deleteCollectionSupported {
err = dclient.Resource(&apiResource, namespace).DeleteCollection(&metav1.DeleteOptions{
PropagationPolicy: &propagationPolicy,
}, metav1.ListOptions{LabelSelector: labelSelector})
if err != nil && !apierr.IsNotFound(err) {
return err
}
} else if deleteSupported {
items, err := dclient.Resource(&apiResource, namespace).List(metav1.ListOptions{LabelSelector: labelSelector})
if err != nil {
return err
}
for _, item := range items.(*unstructured.UnstructuredList).Items {
err = dclient.Resource(&apiResource, namespace).Delete(item.GetName(), &metav1.DeleteOptions{
PropagationPolicy: &propagationPolicy,
})
if err != nil {
return err
}
}
if deleteCollectionSupported || deleteSupported {
resourceInterfaces = append(resourceInterfaces, struct {
dynamic.ResourceInterface
bool
}{dclient.Resource(&apiResource, namespace), deleteCollectionSupported})
}
}
}
return nil
var asyncErr error
propagationPolicy := metav1.DeletePropagationForeground
var wg sync.WaitGroup
wg.Add(len(resourceInterfaces))
for i := range resourceInterfaces {
client := resourceInterfaces[i].ResourceInterface
deleteCollectionSupported := resourceInterfaces[i].bool
go func() {
defer wg.Done()
if deleteCollectionSupported {
err = client.DeleteCollection(&metav1.DeleteOptions{
PropagationPolicy: &propagationPolicy,
}, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelName, labelValue)})
if err != nil && !apierr.IsNotFound(err) {
asyncErr = err
}
} else {
items, err := client.List(metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelName, labelValue)})
if err != nil {
asyncErr = err
return
}
for _, item := range items.(*unstructured.UnstructuredList).Items {
// apply client side filtering since not every kubernetes API supports label filtering
labels := item.GetLabels()
if labels != nil {
if value, ok := labels[labelName]; ok && value == labelValue {
err = client.Delete(item.GetName(), &metav1.DeleteOptions{
PropagationPolicy: &propagationPolicy,
})
if err != nil && !apierr.IsNotFound(err) {
asyncErr = err
return
}
}
}
}
}
}()
}
wg.Wait()
return asyncErr
}
// GetLiveResources returns the corresponding live resource from a list of resources
@@ -236,32 +431,120 @@ func ListAllResources(config *rest.Config, apiResources []metav1.APIResource, na
return resources, nil
}
// ApplyResource performs an apply of a unstructured resource
func ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string) (*unstructured.Unstructured, error) {
// deleteFile is best effort deletion of a file
func deleteFile(path string) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return
}
_ = os.Remove(path)
}
// DeleteResource deletes resource
func DeleteResource(config *rest.Config, obj *unstructured.Unstructured, namespace string) error {
dynClientPool := dynamic.NewDynamicClientPool(config)
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
return err
}
gvk := obj.GroupVersionKind()
dclient, err := dynClientPool.ClientForGroupVersionKind(gvk)
if err != nil {
return nil, err
return err
}
apiResource, err := ServerResourceForGroupVersionKind(disco, gvk)
if err != nil {
return nil, err
return err
}
reIf := dclient.Resource(apiResource, namespace)
liveObj, err := reIf.Update(obj)
if err != nil {
if !apierr.IsNotFound(err) {
return nil, errors.WithStack(err)
}
liveObj, err = reIf.Create(obj)
if err != nil {
return nil, errors.WithStack(err)
}
}
return liveObj, nil
propagationPolicy := metav1.DeletePropagationForeground
return reIf.Delete(obj.GetName(), &metav1.DeleteOptions{PropagationPolicy: &propagationPolicy})
}
// ApplyResource performs an apply of a unstructured resource
func ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string) (*unstructured.Unstructured, error) {
log.Infof("Applying resource %s/%s in cluster: %s, namespace: %s", obj.GetKind(), obj.GetName(), config.Host, namespace)
f, err := ioutil.TempFile(kubectlTempDir, "")
if err != nil {
return nil, fmt.Errorf("Failed to generate temp file for kubeconfig: %v", err)
}
_ = f.Close()
err = WriteKubeConfig(config, namespace, f.Name())
if err != nil {
return nil, fmt.Errorf("Failed to write kubeconfig: %v", err)
}
defer deleteFile(f.Name())
manifestBytes, err := json.Marshal(obj)
if err != nil {
return nil, err
}
cmd := exec.Command("kubectl", "--kubeconfig", f.Name(), "-n", namespace, "apply", "-o", "json", "-f", "-")
log.Info(cmd.Args)
cmd.Stdin = bytes.NewReader(manifestBytes)
out, err := cmd.Output()
if err != nil {
exErr := err.(*exec.ExitError)
return nil, fmt.Errorf("failed to apply '%s': %s", obj.GetName(), exErr.Stderr)
}
var liveObj unstructured.Unstructured
err = json.Unmarshal(out, &liveObj)
if err != nil {
return nil, fmt.Errorf("failed to apply '%s': %s", obj.GetName(), err)
}
return &liveObj, nil
}
// WriteKubeConfig takes a rest.Config and writes it as a kubeconfig at the specified path
func WriteKubeConfig(restConfig *rest.Config, namespace, filename string) error {
var kubeConfig = clientcmdapi.Config{
CurrentContext: restConfig.Host,
Contexts: map[string]*clientcmdapi.Context{
restConfig.Host: {
Cluster: restConfig.Host,
AuthInfo: restConfig.Host,
Namespace: namespace,
},
},
Clusters: map[string]*clientcmdapi.Cluster{
restConfig.Host: {
Server: restConfig.Host,
},
},
AuthInfos: map[string]*clientcmdapi.AuthInfo{
restConfig.Host: {},
},
}
// Set Cluster info
if restConfig.TLSClientConfig.Insecure {
kubeConfig.Clusters[restConfig.Host].InsecureSkipTLSVerify = true
}
if restConfig.TLSClientConfig.CAFile != "" {
kubeConfig.Clusters[restConfig.Host].CertificateAuthority = restConfig.TLSClientConfig.CAFile
}
// Set AuthInfo
if len(restConfig.TLSClientConfig.CAData) > 0 {
kubeConfig.Clusters[restConfig.Host].CertificateAuthorityData = restConfig.TLSClientConfig.CAData
}
if restConfig.TLSClientConfig.CertFile != "" {
kubeConfig.AuthInfos[restConfig.Host].ClientCertificate = restConfig.TLSClientConfig.CertFile
}
if len(restConfig.TLSClientConfig.CertData) > 0 {
kubeConfig.AuthInfos[restConfig.Host].ClientCertificateData = restConfig.TLSClientConfig.CertData
}
if restConfig.TLSClientConfig.KeyFile != "" {
kubeConfig.AuthInfos[restConfig.Host].ClientKey = restConfig.TLSClientConfig.KeyFile
}
if len(restConfig.TLSClientConfig.KeyData) > 0 {
kubeConfig.AuthInfos[restConfig.Host].ClientKeyData = restConfig.TLSClientConfig.KeyData
}
if restConfig.Username != "" {
kubeConfig.AuthInfos[restConfig.Host].Username = restConfig.Username
}
if restConfig.Password != "" {
kubeConfig.AuthInfos[restConfig.Host].Password = restConfig.Password
}
if restConfig.BearerToken != "" {
kubeConfig.AuthInfos[restConfig.Host].Token = restConfig.BearerToken
}
return clientcmd.WriteToFile(kubeConfig, filename)
}

View File

@@ -0,0 +1,176 @@
package localconfig
import (
"fmt"
"os"
"os/user"
"path"
"github.com/argoproj/argo-cd/util/cli"
)
// LocalConfig is a local ArgoCD config file
type LocalConfig struct {
CurrentContext string `json:"current-context"`
Contexts []ContextRef `json:"contexts"`
Servers []Server `json:"servers"`
Users []User `json:"users"`
}
// ContextRef is a reference to a Server and User for an API client
type ContextRef struct {
Name string `json:"name"`
Server string `json:"server"`
User string `json:"user"`
}
// Context is the resolved Server and User objects resolved
type Context struct {
Name string
Server Server
User User
}
// Server contains ArgoCD server information
type Server struct {
// Server is the ArgoCD server address
Server string `json:"server"`
// Insecure indicates to connect to the server over TLS insecurely
Insecure bool `json:"insecure,omitempty"`
// CACertificateAuthorityData is the base64 string of a PEM encoded certificate
// TODO: not yet implemented
CACertificateAuthorityData string `json:"certificate-authority-data,omitempty"`
// PlainText indicates to connect with TLS disabled
PlainText bool `json:"plain-text,omitempty"`
}
// User contains user authentication information
type User struct {
Name string `json:"name"`
AuthToken string `json:"auth-token,omitempty"`
}
// ReadLocalConfig loads up the local configuration file. Returns nil if config does not exist
func ReadLocalConfig(path string) (*LocalConfig, error) {
var err error
var config LocalConfig
err = cli.UnmarshalLocalFile(path, &config)
if os.IsNotExist(err) {
return nil, nil
}
err = ValidateLocalConfig(config)
if err != nil {
return nil, err
}
return &config, nil
}
func ValidateLocalConfig(config LocalConfig) error {
if config.CurrentContext == "" {
return fmt.Errorf("Local config invalid: current-context unset")
}
if _, err := config.ResolveContext(config.CurrentContext); err != nil {
return fmt.Errorf("Local config invalid: %s", err)
}
return nil
}
// WriteLocalConfig writes a new local configuration file.
func WriteLocalConfig(config LocalConfig, configPath string) error {
err := os.MkdirAll(path.Dir(configPath), os.ModePerm)
if err != nil {
return err
}
return cli.MarshalLocalYAMLFile(configPath, config)
}
// ResolveContext resolves the specified context. If unspecified, resolves the current context
func (l *LocalConfig) ResolveContext(name string) (*Context, error) {
if name == "" {
name = l.CurrentContext
}
for _, ctx := range l.Contexts {
if ctx.Name == name {
server, err := l.GetServer(ctx.Server)
if err != nil {
return nil, err
}
user, err := l.GetUser(ctx.User)
if err != nil {
return nil, err
}
return &Context{
Name: ctx.Name,
Server: *server,
User: *user,
}, nil
}
}
return nil, fmt.Errorf("Context '%s' undefined", name)
}
func (l *LocalConfig) GetServer(name string) (*Server, error) {
for _, s := range l.Servers {
if s.Server == name {
return &s, nil
}
}
return nil, fmt.Errorf("Server '%s' undefined", name)
}
func (l *LocalConfig) UpsertServer(server Server) {
for i, s := range l.Servers {
if s.Server == server.Server {
l.Servers[i] = server
return
}
}
l.Servers = append(l.Servers, server)
}
func (l *LocalConfig) GetUser(name string) (*User, error) {
for _, u := range l.Users {
if u.Name == name {
return &u, nil
}
}
return nil, fmt.Errorf("User '%s' undefined", name)
}
func (l *LocalConfig) UpsertUser(user User) {
for i, u := range l.Users {
if u.Name == user.Name {
l.Users[i] = user
return
}
}
l.Users = append(l.Users, user)
}
func (l *LocalConfig) UpsertContext(context ContextRef) {
for i, c := range l.Contexts {
if c.Name == context.Name {
l.Contexts[i] = context
return
}
}
l.Contexts = append(l.Contexts, context)
}
// DefaultConfigDir returns the local configuration path for settings such as cached authentication tokens.
func DefaultConfigDir() (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
return path.Join(usr.HomeDir, ".argocd"), nil
}
// DefaultLocalConfigPath returns the local configuration path for settings such as cached authentication tokens.
func DefaultLocalConfigPath() (string, error) {
dir, err := DefaultConfigDir()
if err != nil {
return "", err
}
return path.Join(dir, "config"), nil
}

View File

@@ -1,6 +1,9 @@
package util
package password
import (
"crypto/subtle"
"fmt"
"golang.org/x/crypto/bcrypt"
)
@@ -28,14 +31,22 @@ var preferredHashers = []PasswordHasher{
// HashPasswordWithHashers hashes an entered password using the first hasher in the provided list of hashers.
func hashPasswordWithHashers(password string, hashers []PasswordHasher) (string, error) {
// Even though good hashers will disallow blank passwords, let's be explicit that ALL BLANK PASSWORDS ARE INVALID. Full stop.
if password == "" {
return "", fmt.Errorf("blank passwords are not allowed")
}
return hashers[0].HashPassword(password)
}
// VerifyPasswordWithHashers verifies an entered password against a hashed password using one or more algorithms. It returns whether the hash is "stale" (i.e., was verified using something other than the FIRST hasher specified).
func verifyPasswordWithHashers(password, hashedPassword string, hashers []PasswordHasher) (valid, stale bool) {
// Be explicit here for security, even though zero values can be assumed.
valid = false
stale = false
func verifyPasswordWithHashers(password, hashedPassword string, hashers []PasswordHasher) (bool, bool) {
// Even though good hashers will disallow blank passwords, let's be explicit that ALL BLANK PASSWORDS ARE INVALID. Full stop.
if password == "" {
return false, false
}
valid, stale := false, false
for idx, hasher := range hashers {
if hasher.VerifyPassword(password, hashedPassword) {
valid = true
@@ -45,7 +56,8 @@ func verifyPasswordWithHashers(password, hashedPassword string, hashers []Passwo
break
}
}
return
return valid, stale
}
// HashPassword hashes against the current preferred hasher.
@@ -66,7 +78,7 @@ func (h DummyPasswordHasher) HashPassword(password string) (string, error) {
// VerifyPassword validates whether a one-way digest ("hash") of a password was created from a given plaintext password.
func (h DummyPasswordHasher) VerifyPassword(password, hashedPassword string) bool {
return password == hashedPassword
return 1 == subtle.ConstantTimeCompare([]byte(password), []byte(hashedPassword))
}
// HashPassword creates a one-way digest ("hash") of a password. In the case of Bcrypt, a pseudorandom salt is included automatically by the underlying library. For security reasons, the work factor is always at _least_ bcrypt.DefaultCost.

View File

@@ -1,4 +1,4 @@
package util
package password
import (
"testing"
@@ -12,10 +12,10 @@ func testPasswordHasher(t *testing.T, h PasswordHasher) {
)
hashedPassword, _ := h.HashPassword(defaultPassword)
if !h.VerifyPassword(defaultPassword, hashedPassword) {
t.Errorf("Password \"%s\" should have validated against hash \"%s\"", defaultPassword, hashedPassword)
t.Errorf("Password %q should have validated against hash %q", defaultPassword, hashedPassword)
}
if h.VerifyPassword(defaultPassword, pollution+hashedPassword) {
t.Errorf("Password \"%s\" should NOT have validated against hash \"%s\"", defaultPassword, pollution+hashedPassword)
t.Errorf("Password %q should NOT have validated against hash %q", defaultPassword, pollution+hashedPassword)
}
}
@@ -33,6 +33,7 @@ func TestDummyPasswordHasher(t *testing.T) {
func TestPasswordHashing(t *testing.T) {
const (
defaultPassword = "Hello, world!"
blankPassword = ""
)
hashers := []PasswordHasher{
BcryptPasswordHasher{0},
@@ -42,17 +43,26 @@ func TestPasswordHashing(t *testing.T) {
hashedPassword, _ := hashPasswordWithHashers(defaultPassword, hashers)
valid, stale := verifyPasswordWithHashers(defaultPassword, hashedPassword, hashers)
if !valid {
t.Errorf("Password \"%s\" should have validated against hash \"%s\"", defaultPassword, hashedPassword)
t.Errorf("Password %q should have validated against hash %q", defaultPassword, hashedPassword)
}
if stale {
t.Errorf("Password \"%s\" should not have been marked stale against hash \"%s\"", defaultPassword, hashedPassword)
t.Errorf("Password %q should not have been marked stale against hash %q", defaultPassword, hashedPassword)
}
valid, stale = verifyPasswordWithHashers(defaultPassword, defaultPassword, hashers)
if !valid {
t.Errorf("Password \"%s\" should have validated against itself with dummy hasher", defaultPassword)
t.Errorf("Password %q should have validated against itself with dummy hasher", defaultPassword)
}
if !stale {
t.Errorf("Password \"%s\" should have been acknowledged stale against itself with dummy hasher", defaultPassword)
t.Errorf("Password %q should have been acknowledged stale against itself with dummy hasher", defaultPassword)
}
hashedPassword, err := hashPasswordWithHashers(blankPassword, hashers)
if err == nil {
t.Errorf("Blank password should have produced error, rather than hash %q", hashedPassword)
}
valid, _ = verifyPasswordWithHashers(blankPassword, "", hashers)
if valid != false {
t.Errorf("Blank password should have failed verification")
}
}

View File

@@ -0,0 +1,118 @@
package session
import (
"crypto/rand"
"fmt"
"time"
util_password "github.com/argoproj/argo-cd/util/password"
jwt "github.com/dgrijalva/jwt-go"
)
// SessionManager generates and validates JWT tokens for login sessions.
type SessionManager struct {
serverSecretKey []byte
}
const (
// sessionManagerClaimsIssuer fills the "iss" field of the token.
sessionManagerClaimsIssuer = "argocd"
// invalidLoginError, for security purposes, doesn't say whether the username or password was invalid. This does not mitigate the potential for timing attacks to determine which is which.
invalidLoginError = "Invalid username or password"
blankPasswordError = "Blank passwords are not allowed"
)
// SessionManagerTokenClaims holds claim metadata for a token.
type SessionManagerTokenClaims struct {
//Foo string `json:"foo"`
jwt.StandardClaims
}
// MakeSessionManager creates a new session manager with the given secret key.
func MakeSessionManager(secretKey []byte) SessionManager {
return SessionManager{
serverSecretKey: secretKey,
}
}
// Create creates a new token for a given subject (user) and returns it as a string.
func (mgr SessionManager) Create(subject string) (string, error) {
// Create a new token object, specifying signing method and the claims
// you would like it to contain.
now := time.Now().Unix()
claims := SessionManagerTokenClaims{
//"bar",
jwt.StandardClaims{
//ExpiresAt: time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
IssuedAt: now,
Issuer: sessionManagerClaimsIssuer,
NotBefore: now,
Subject: subject,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Unix and get the complete encoded token as a string using the secret
return token.SignedString(mgr.serverSecretKey)
}
// Parse tries to parse the provided string and returns the token claims.
func (mgr SessionManager) Parse(tokenString string) (*SessionManagerTokenClaims, error) {
// Parse takes the token string and a function for looking up the key. The latter is especially
// useful if you use multiple keys for your application. The standard is to use 'kid' in the
// head of the token to identify which key to use, but the parsed token (head and claims) is provided
// to the callback, providing flexibility.
token, err := jwt.ParseWithClaims(tokenString, &SessionManagerTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return mgr.serverSecretKey, nil
})
if token != nil {
if claims, ok := token.Claims.(*SessionManagerTokenClaims); ok && token.Valid {
return claims, nil
}
}
return nil, err
}
// MakeSignature generates a cryptographically-secure pseudo-random token, based on a given number of random bytes, for signing purposes.
func MakeSignature(size int) ([]byte, error) {
b := make([]byte, size)
_, err := rand.Read(b)
if err != nil {
b = nil
}
return b, err
}
// LoginLocalUser checks if a username/password combo is correct and creates a new token if so.
// [TODO] This may belong elsewhere.
func (mgr SessionManager) LoginLocalUser(username, password string, users map[string]string) (string, error) {
if password == "" {
err := fmt.Errorf(blankPasswordError)
return "", err
}
passwordHash, ok := users[username]
if !ok {
// Username was not found in local user store.
// Ensure we still send password to hashing algorithm for comparison.
// This mitigates potential for timing attacks that benefit from short-circuiting,
// provided the hashing library/algorithm in use doesn't itself short-circuit.
passwordHash = ""
}
if valid, _ := util_password.VerifyPassword(password, passwordHash); valid {
token, err := mgr.Create(username)
if err == nil {
return token, nil
}
}
return "", fmt.Errorf(invalidLoginError)
}

View File

@@ -0,0 +1,38 @@
package session
import (
"testing"
)
func TestSessionManager(t *testing.T) {
const (
defaultSecretKey = "Hello, world!"
defaultSubject = "argo"
)
mgr := SessionManager{[]byte(defaultSecretKey)}
token, err := mgr.Create(defaultSubject)
if err != nil {
t.Errorf("Could not create token: %v", err)
}
claims, err := mgr.Parse(token)
if err != nil {
t.Errorf("Could not parse token: %v", err)
}
subject := claims.Subject
if subject != "argo" {
t.Errorf("Token claim subject \"%s\" does not match expected subject \"%s\".", subject, defaultSubject)
}
}
func TestMakeSignature(t *testing.T) {
for size := 1; size <= 64; size++ {
s, err := MakeSignature(size)
if err != nil {
t.Errorf("Could not generate signature of size %d: %v", size, err)
}
t.Logf("Generated token: %v", s)
}
}

180
util/tls/tls.go Normal file
View File

@@ -0,0 +1,180 @@
package tls
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"os"
"time"
)
const (
DefaultRSABits = 2048
)
type CertOptions struct {
// Hostnames and IPs to generate a certificate for
Hosts []string
// Name of organization in certificate
Organization string
// Creation date
ValidFrom time.Time
// Duration that certificate is valid for
ValidFor time.Duration
// whether this cert should be its own Certificate Authority
IsCA bool
// Size of RSA key to generate. Ignored if --ecdsa-curve is set
RSABits int
// ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521
ECDSACurve string
}
func publicKey(priv interface{}) interface{} {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
func pemBlockForKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
os.Exit(2)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}
func generate(opts CertOptions) ([]byte, crypto.PrivateKey, error) {
if len(opts.Hosts) == 0 {
return nil, nil, fmt.Errorf("hosts not supplied")
}
var privateKey crypto.PrivateKey
var err error
switch opts.ECDSACurve {
case "":
rsaBits := DefaultRSABits
if opts.RSABits != 0 {
rsaBits = opts.RSABits
}
privateKey, err = rsa.GenerateKey(rand.Reader, rsaBits)
case "P224":
privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case "P256":
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case "P384":
privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case "P521":
privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
return nil, nil, fmt.Errorf("Unrecognized elliptic curve: %q", opts.ECDSACurve)
}
if err != nil {
return nil, nil, fmt.Errorf("failed to generate private key: %s", err)
}
var notBefore time.Time
if opts.ValidFrom.IsZero() {
notBefore = time.Now()
} else {
notBefore = opts.ValidFrom
}
var validFor time.Duration
if opts.ValidFor == 0 {
validFor = 365 * 24 * time.Hour
}
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate serial number: %s", err)
}
if opts.Organization == "" {
return nil, nil, fmt.Errorf("organization not supplied")
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{opts.Organization},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
for _, h := range opts.Hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
if opts.IsCA {
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
}
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(privateKey), privateKey)
if err != nil {
return nil, nil, fmt.Errorf("Failed to create certificate: %s", err)
}
return certBytes, privateKey, nil
}
// generatePEM generates a new certificate and key and returns it as PEM encoded bytes
func generatePEM(opts CertOptions) ([]byte, []byte, error) {
certBytes, privateKey, err := generate(opts)
if err != nil {
return nil, nil, err
}
certpem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
keypem := pem.EncodeToMemory(pemBlockForKey(privateKey))
return certpem, keypem, nil
}
// GenerateX509KeyPair generates a X509 key pair
func GenerateX509KeyPair(opts CertOptions) (*tls.Certificate, error) {
certpem, keypem, err := generatePEM(opts)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(certpem, keypem)
if err != nil {
return nil, err
}
return &cert, nil
}
// EncodeX509KeyPair encodes a TLS Certificate into its pem encoded for storage
func EncodeX509KeyPair(cert tls.Certificate) ([]byte, []byte) {
certpem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]})
keypem := pem.EncodeToMemory(pemBlockForKey(cert.PrivateKey))
return certpem, keypem
}