Compare commits

..

3 Commits

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

View File

@@ -29,13 +29,7 @@ spec:
- name: cmd
value: "{{item}}"
withItems:
- dep ensure && make cli lint test
- name: test-e2e
template: ci-builder
arguments:
parameters:
- name: cmd
value: "dep ensure && make test-e2e"
- dep ensure && make cli lint test test-e2e
- name: ci-builder
inputs:

View File

@@ -1,119 +1,5 @@
# Changelog
## v0.7.2 (2018-08-21)
- API discovery becomes best effort when partial resource list is returned (issue #524)
## v0.7.1 (2018-08-03)
+ Surface helm parameters to the application level (#485)
+ [UI] Improve application creation wizard (#459)
+ [UI] Show indicator when refresh is still in progress (#493)
* [UI] Improve data loading error notification (#446)
* Infer username from claims during an `argocd relogin` (#475)
* Expand RBAC role to be able to create application events. Fix username claims extraction
- Fix scalability issues with the ListApps API (#494)
- Fix issue where application server was retrieving events from incorrect cluster (#478)
- Fix failure in identifying app source type when path was '.'
- AppProjectSpec SourceRepos mislabeled (#490)
- Failed e2e test was not failing CI workflow
* Fix linux download link in getting_started.md (#487) (@chocopowwwa)
## v0.7.0 (2018-07-27)
+ Support helm charts and yaml directories as an application source
+ Audit trails in the form of API call logs
+ Generate kubernetes events for application state changes
+ Add ksonnet version to version endpoint (#433)
+ Show CLI progress for sync and rollback
+ Make use of dex refresh tokens and store them into local config
+ Expire local superuser tokens when their password changes
+ Add `argocd relogin` command as a convenience around login to current context
- Fix saving default connection status for repos and clusters
- Fix undesired fail-fast behavior of health check
- Fix memory leak in the cluster resource watch
- Health check for StatefulSets, DaemonSet, and ReplicaSets were failing due to use of wrong converters
## v0.6.2 (2018-07-23)
- Health check for StatefulSets, DaemonSet, and ReplicaSets were failing due to use of wrong converters
## v0.6.1 (2018-07-18)
- Fix regression where deployment health check incorrectly reported Healthy
+ Intercept dex SSO errors and present them in Argo login page
## v0.6.0 (2018-07-16)
+ Support PreSync, Sync, PostSync resource hooks
+ Introduce Application Projects for finer grain RBAC controls
+ Swagger Docs & UI
+ Support in-cluster deployments internal kubernetes service name
+ Refactoring & Improvements
* Improved error handling, status and condition reporting
* Remove installer in favor of kubectl apply instructions
* Add validation when setting application parameters
* Cascade deletion is decided during app deletion, instead of app creation
- Fix git authentication implementation when using using SSH key
- app-name label was inadvertently injected into spec.selector if selector was omitted from v1beta1 specs
## v0.5.4 (2018-06-27)
- Refresh flag to sync should be optional, not required
## v0.5.3 (2018-06-20)
+ Support cluster management using the internal k8s API address https://kubernetes.default.svc (#307)
+ Support diffing a local ksonnet app to the live application state (resolves #239) (#298)
+ Add ability to show last operation result in app get. Show path in app list -o wide (#297)
+ Update dependencies: ksonnet v0.11, golang v1.10, debian v9.4 (#296)
+ Add ability to force a refresh of an app during get (resolves #269) (#293)
+ Automatically restart API server upon certificate changes (#292)
## v0.5.2 (2018-06-14)
+ Resource events tab on application details page (#286)
+ Display pod status on application details page (#231)
## v0.5.1 (2018-06-13)
- API server incorrectly compose application fully qualified name for RBAC check (#283)
- UI crash while rendering application operation info if operation failed
## v0.5.0 (2018-06-12)
+ RBAC access control
+ Repository/Cluster state monitoring
+ ArgoCD settings import/export
+ Application creation UI wizard
+ argocd app manifests for printing the application manifests
+ argocd app unset command to unset parameter overrides
+ Fail app sync if prune flag is required (#276)
+ Take into account number of unavailable replicas to decided if deployment is healthy or not #270
+ Add ability to show parameters and overrides in CLI (resolves #240)
- Repo names containing underscores were not being accepted (#258)
- Cookie token was not parsed properly when mixed with other site cookies
## v0.4.7 (2018-06-07)
- Fix argocd app wait health checking logic
## v0.4.6 (2018-06-06)
- Retry argocd app wait connection errors from EOF watch. Show detailed state changes
## v0.4.5 (2018-05-31)
+ Add argocd app unset command to unset parameter overrides
- Cookie token was not parsed properly when mixed with other site cookies
## v0.4.4 (2018-05-30)
+ Add ability to show parameters and overrides in CLI (resolves #240)
+ Add Events API endpoint
+ Issue #238 - add upsert flag to 'argocd app create' command
+ Add repo browsing endpoint (#229)
+ Support subscribing to settings updates and auto-restart of dex and API server
- Issue #233 - Controller does not persist rollback operation result
- App sync frequently fails due to concurrent app modification
## v0.4.3 (2018-05-21)
- Move local branch deletion as part of git Reset() (resolves #185) (#222)
- Fix exit code for app wait (#219)
## v0.4.2 (2018-05-21)
+ Show URL in argocd app get
- Remove interactive context name prompt during login which broke login automation
* Rename force flag to cascade in argocd app delete
## v0.4.1 (2018-05-18)
+ Implemented argocd app wait command
## v0.4.0 (2018-05-17)
+ SSO Integration
+ GitHub Webhook
@@ -126,36 +12,3 @@
* Manifests are memoized in repo server
- Fix connection timeouts to SSH repos
## v0.3.2 (2018-05-03)
+ Application sync should delete 'unexpected' resources #139
+ Update ksonnet to v0.10.1
+ Detect unexpected resources
- Fix: App sync frequently fails due to concurrent app modification #147
- Fix: improve app state comparator: #136, #132
## v0.3.1 (2018-04-24)
+ Add new rollback RPC with numeric identifiers
+ New argo app history and argo app rollback command
+ Switch to gogo/protobuf for golang code generation
- Fix: create .argocd directory during argo login (issue #123)
- Fix: Allow overriding server or namespace separately (issue #110)
## v0.3.0 (2018-04-23)
+ Auth support
+ TLS support
+ DAG-based application view
+ Bulk watch
+ ksonnet v0.10.0-alpha.3
+ kubectl apply deployment strategy
+ CLI improvements for app management
## v0.2.0 (2018-04-03)
+ Rollback UI
+ Override parameters
## v0.1.0 (2018-03-12)
+ Define app in Github with dev and preprod environment using KSonnet
+ Add cluster Diff App with a cluster Deploy app in a cluster
+ Deploy a new version of the app in the cluster
+ App sync based on Github app config change - polling only
+ Basic UI: App diff between Git and k8s cluster for all environments Basic GUI

View File

@@ -1,18 +1,10 @@
## Requirements
Make sure you have following tools installed
* [docker](https://docs.docker.com/install/#supported-platforms)
* [golang](https://golang.org/)
* [dep](https://github.com/golang/dep)
* [protobuf](https://developers.google.com/protocol-buffers/)
* [ksonnet](https://github.com/ksonnet/ksonnet#install)
* [helm](https://github.com/helm/helm/releases)
* [go-swagger](https://github.com/go-swagger/go-swagger/blob/master/docs/install.md)
* [jq](https://stedolan.github.io/jq/)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
Make sure you have following tools installed [docker](https://docs.docker.com/install/#supported-platforms), [golang](https://golang.org/), [dep](https://github.com/golang/dep), [protobuf](https://developers.google.com/protocol-buffers/), [ksonnet](https://github.com/ksonnet/ksonnet#install), [go-swagger](https://github.com/go-swagger/go-swagger/blob/master/docs/install.md), and [jq](https://stedolan.github.io/jq/)
[kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
```
$ brew tap go-swagger/go-swagger
$ brew install go dep protobuf kubectl ksonnet/tap/ks kubernetes-helm jq go-swagger
$ brew install go dep protobuf kubectl ksonnet/tap/ks jq go-swagger
$ go get -u github.com/golang/protobuf/protoc-gen-go
$ go get -u github.com/go-swagger/go-swagger/cmd/swagger
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
@@ -38,18 +30,6 @@ NOTE: The make command can take a while, and we recommend building the specific
* `make codegen` - Builds protobuf and swagger files
* `make argocd-util` - Make the administrator's utility, used for certain tasks such as import/export
## Generating ArgoCD manifests for a specific image repository/tag
During development, the `update-manifests.sh` script, can be used to conveniently regenerate the
ArgoCD installation manifests with a customized image namespace and tag. This enables developers
to easily apply manifests which are using the images that they pushed into their personal container
repository.
```
$ IMAGE_NAMESPACE=jessesuen IMAGE_TAG=latest ./hack/update-manifests.sh
$ kubectl apply -n argocd -f ./manifests/install.yaml
```
## Running locally
You need to have access to kubernetes cluster (including [minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) or [docker edge](https://docs.docker.com/docker-for-mac/install/) ) in order to run Argo CD on your laptop:

View File

@@ -66,10 +66,6 @@ RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION
RUN curl -o /kubectl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
chmod +x /kubectl
env HELM_VERSION=2.9.1
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
mv /tmp/linux-amd64/helm /helm
##############################################################
FROM debian:9.3
@@ -78,7 +74,6 @@ RUN apt-get update && apt-get install -y git && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY --from=cli-tooling /ks /usr/local/bin/ks
COPY --from=cli-tooling /helm /usr/local/bin/helm
COPY --from=cli-tooling /kubectl /usr/local/bin/kubectl
# workaround ksonnet issue https://github.com/ksonnet/ksonnet/issues/298
ENV USER=root

View File

@@ -20,9 +20,3 @@ RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION
tar -C /tmp/ -xf ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
mv /tmp/ks_${KSONNET_VERSION}_linux_amd64/ks /usr/local/bin/ks && \
rm -rf /tmp/ks_${KSONNET_VERSION}
# Install helm
env HELM_VERSION=2.9.1
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
mv /tmp/linux-amd64/helm /usr/local/bin/helm

370
Gopkg.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -81,7 +81,8 @@ argocd-util: clean-debug
.PHONY: install-manifest
install-manifest:
if [ "${IMAGE_NAMESPACE}" = "" ] ; then echo "IMAGE_NAMESPACE must be set to build install manifest" ; exit 1 ; fi
./hack/update-manifests.sh
echo "# This is an auto-generated file. DO NOT EDIT" > manifests/install.yaml
cat manifests/components/*.yaml | sed 's@\( image: argoproj/\(.*\):latest\)@ image: '"${IMAGE_NAMESPACE}"'/\2:'"${IMAGE_TAG}"'@g' >> manifests/install.yaml
.PHONY: server
server: clean-debug
@@ -125,11 +126,11 @@ lint:
.PHONY: test
test:
go test -v `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
go test `go list ./... | grep -v "github.com/argoproj/argo-cd/test/e2e"`
.PHONY: test-e2e
test-e2e:
go test -v -failfast -timeout 20m ./test/e2e
go test ./test/e2e
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in packr boxes
.PHONY: clean-debug

View File

@@ -3,7 +3,7 @@
## What is Argo CD?
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.
Argo CD is a declarative, continuous delivery service based on **ksonnet** for Kubernetes.
![Argo CD UI](docs/argocd-ui.gif)
@@ -19,25 +19,21 @@ is provided for additional features.
## How it works
Argo CD follows the **GitOps** pattern of using git repositories as the source of truth for defining
the desired application state. Kubernetes manifests can be specified in several ways:
* [ksonnet](https://ksonnet.io) applications
* [helm](https://helm.sh) charts
* Simple directory of YAML/json manifests
Argo CD follows the **GitOps** pattern of using git repositories as the source of truth for defining the
desired application state. Kubernetes manifests are specified as [ksonnet](https://ksonnet.io)
applications. Argo CD automates the deployment of the desired
application states in the specified target environments.
![Argo CD Architecture](docs/argocd_architecture.png)
Argo CD automates the deployment of the desired application states in the specified target environments.
Application deployments can track updates to branches, tags, or pinned to a specific version of
manifests at a git commit. See [tracking strategies](docs/tracking_strategies.md) for additional
details about the different tracking strategies available.
## Architecture
![Argo CD Architecture](docs/argocd_architecture.png)
Argo CD is implemented as a kubernetes controller which continuously monitors running applications
and compares the current, live state against the desired target state (as specified in the git repo).
A deployed application whose live state deviates from the target state is considered `OutOfSync`.
Argo CD reports & visualizes the differences, while providing facilities to automatically or
A deployed application whose live state deviates from the target state is considered out-of-sync.
Argo CD reports & visualizes the differences as well as providing facilities to automatically or
manually sync the live state back to the desired target state. Any modifications made to the desired
target state in the git repo can be automatically applied and reflected in the specified target
environments.
@@ -55,15 +51,47 @@ For additional details, see [architecture overview](docs/architecture.md).
* SSO Integration (OIDC, OAuth2, LDAP, SAML 2.0, GitLab, Microsoft, LinkedIn)
* Webhook Integration (GitHub, BitBucket, GitLab)
* PreSync, Sync, PostSync hooks to support complex application rollouts (e.g.blue/green & canary upgrades)
* Audit trails for application events and API calls
* Parameter overrides for overriding ksonnet/helm parameters in git
## What is ksonnet?
* [Jsonnet](http://jsonnet.org), the basis for ksonnet, is a domain specific configuration language,
which provides extreme flexibility for composing and manipulating JSON/YAML specifications.
* [Ksonnet](http://ksonnet.io) goes one step further by applying Jsonnet principles to Kubernetes
manifests. It provides an opinionated file & directory structure to organize applications into
reusable components, parameters, and environments. Environments can be hierarchical, which promotes
both re-use and granular customization of application and environment specifications.
## Why ksonnet?
Application configuration management is a hard problem and grows rapidly in complexity as you deploy
more applications, against more and more environments. Current templating systems, such as Jinja,
and Golang templating, are unnatural ways to maintain kubernetes manifests, and are not well suited to
capture subtle configuration differences between environments. Its ability to compose and re-use
application and environment configurations is also very limited.
Imagine we have a single guestbook application deployed in following environments:
| Environment | K8s Version | Application Image | DB Connection String | Environment Vars | Sidecars |
|---------------|-------------|------------------------|-----------------------|------------------|---------------|
| minikube | 1.10.0 | jesse/guestbook:latest | sql://locahost/db | DEBUG=true | |
| dev | 1.11.0 | app/guestbook:latest | sql://dev-test/db | DEBUG=true | |
| staging | 1.10.0 | app/guestbook:e3c0263 | sql://staging/db | | istio,dnsmasq |
| us-west-1 | 1.9.0 | app/guestbook:abc1234 | sql://prod/db | FOO_FEATURE=true | istio,dnsmasq |
| us-west-2 | 1.10.0 | app/guestbook:abc1234 | sql://prod/db | | istio,dnsmasq |
| us-east-1 | 1.9.0 | app/guestbook:abc1234 | sql://prod/db | BAR_FEATURE=true | istio,dnsmasq |
Ksonnet:
* Enables composition and re-use of common YAML specifications
* Allows overrides, additions, and subtractions of YAML sub-components specific to each environment
* Guarantees proper generation of K8s manifests suitable for the corresponding Kubernetes API version
* Provides [kubernetes-specific jsonnet libraries](https://github.com/ksonnet/ksonnet-lib) to enable
concise definition of kubernetes manifests
## Development Status
* Argo CD is being used in production to deploy SaaS services at Intuit
## Roadmap
* Auto-sync toggle to directly apply git state changes to live state
* Audit trails for application events and API calls
* Service account/access key management for CI pipelines
* Support for additional config management tools (Kustomize?)
* Revamped UI, and feature parity with CLI
* Revamped UI
* Customizable application actions

View File

@@ -1 +1 @@
0.7.2
0.6.2

View File

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

View File

@@ -77,7 +77,6 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
// Perform the login
var tokenString string
var refreshToken string
if !sso {
tokenString = passwordLogin(acdClient, username, password)
} else {
@@ -86,7 +85,15 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
if !ssoConfigured(acdSet) {
log.Fatalf("ArgoCD instance is not configured with SSO")
}
tokenString, refreshToken = oauth2Login(server, clientOpts.PlainText)
tokenString = oauth2Login(server, clientOpts.PlainText)
// The token which we just received from the OAuth2 flow, was from dex. ArgoCD
// currently does not back dex with any kind of persistent storage (it is run
// in-memory). As a result, this token cannot be used in any permanent capacity.
// Restarts of dex will result in a different signing key, and sessions becoming
// invalid. Instead we turn-around and ask ArgoCD to re-sign the token (who *does*
// have persistence of signing keys), and is what we store in the config. Should we
// ever decide to have a database layer for dex, the next line can be removed.
tokenString = tokenLogin(acdClient, tokenString)
}
parser := &jwt.Parser{
@@ -109,9 +116,8 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
Insecure: globalClientOpts.Insecure,
})
localCfg.UpsertUser(localconfig.User{
Name: ctxName,
AuthToken: tokenString,
RefreshToken: refreshToken,
Name: ctxName,
AuthToken: tokenString,
})
if ctxName == "" {
ctxName = server
@@ -157,9 +163,8 @@ func getFreePort() (int, error) {
return ln.Addr().(*net.TCPAddr).Port, ln.Close()
}
// oauth2Login opens a browser, runs a temporary HTTP server to delegate OAuth2 login flow and
// returns the JWT token and a refresh token (if supported)
func oauth2Login(host string, plaintext bool) (string, string) {
// oauth2Login opens a browser, runs a temporary HTTP server to delegate OAuth2 login flow and returns the JWT token
func oauth2Login(host string, plaintext bool) string {
ctx := context.Background()
port, err := getFreePort()
errors.CheckError(err)
@@ -178,7 +183,6 @@ func oauth2Login(host string, plaintext bool) (string, string) {
}
srv := &http.Server{Addr: ":" + strconv.Itoa(port)}
var tokenString string
var refreshToken string
loginCompleted := make(chan struct{})
callbackHandler := func(w http.ResponseWriter, r *http.Request) {
@@ -211,9 +215,8 @@ func oauth2Login(host string, plaintext bool) (string, string) {
log.Fatal(errMsg)
return
}
refreshToken, _ = tok.Extra("refresh_token").(string)
log.Debugf("Token: %s", tokenString)
log.Debugf("Refresh Token: %s", tokenString)
successPage := `
<div style="height:100px; width:100%!; display:flex; flex-direction: column; justify-content: center; align-items:center; background-color:#2ecc71; color:white; font-size:22"><div>Authentication successful!</div></div>
<p style="margin-top:20px; font-size:18; text-align:center">Authentication was successful, you can now return to CLI. This page will close automatically</p>
@@ -245,7 +248,7 @@ func oauth2Login(host string, plaintext bool) (string, string) {
}()
<-loginCompleted
_ = srv.Shutdown(ctx)
return tokenString, refreshToken
return tokenString
}
func passwordLogin(acdClient argocdclient.Client, username, password string) string {
@@ -260,3 +263,14 @@ func passwordLogin(acdClient argocdclient.Client, username, password string) str
errors.CheckError(err)
return createdSession.Token
}
func tokenLogin(acdClient argocdclient.Client, token string) string {
sessConn, sessionIf := acdClient.NewSessionClientOrDie()
defer util.Close(sessConn)
sessionRequest := session.SessionCreateRequest{
Token: token,
}
createdSession, err := sessionIf.Create(context.Background(), &sessionRequest)
errors.CheckError(err)
return createdSession.Token
}

View File

@@ -1,75 +0,0 @@
package commands
import (
"fmt"
"os"
jwt "github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/util/localconfig"
"github.com/argoproj/argo-cd/util/session"
)
// NewReloginCommand returns a new instance of `argocd relogin` command
func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
password string
)
var command = &cobra.Command{
Use: "relogin",
Short: "Refresh an expired authenticate token",
Long: "Refresh an expired authenticate token",
Run: func(c *cobra.Command, args []string) {
if len(args) != 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
errors.CheckError(err)
if localCfg == nil {
log.Fatalf("No context found. Login using `argocd login`")
}
configCtx, err := localCfg.ResolveContext(localCfg.CurrentContext)
errors.CheckError(err)
parser := &jwt.Parser{
SkipClaimsValidation: true,
}
claims := jwt.StandardClaims{}
_, _, err = parser.ParseUnverified(configCtx.User.AuthToken, &claims)
errors.CheckError(err)
var tokenString string
var refreshToken string
if claims.Issuer == session.SessionManagerClaimsIssuer {
clientOpts := argocdclient.ClientOptions{
ConfigPath: "",
ServerAddr: configCtx.Server.Server,
Insecure: configCtx.Server.Insecure,
PlainText: configCtx.Server.PlainText,
}
acdClient := argocdclient.NewClientOrDie(&clientOpts)
fmt.Printf("Relogging in as '%s'\n", claims.Subject)
tokenString = passwordLogin(acdClient, claims.Subject, password)
} else {
fmt.Println("Reinitiating SSO login")
tokenString, refreshToken = oauth2Login(configCtx.Server.Server, configCtx.Server.PlainText)
}
localCfg.UpsertUser(localconfig.User{
Name: localCfg.CurrentContext,
AuthToken: tokenString,
RefreshToken: refreshToken,
})
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
errors.CheckError(err)
fmt.Printf("Context '%s' updated\n", localCfg.CurrentContext)
},
}
command.Flags().StringVar(&password, "password", "", "the password of an account to authenticate")
return command
}

View File

@@ -27,7 +27,6 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewClusterCommand(&clientOpts, pathOpts))
command.AddCommand(NewApplicationCommand(&clientOpts))
command.AddCommand(NewLoginCommand(&clientOpts))
command.AddCommand(NewReloginCommand(&clientOpts))
command.AddCommand(NewRepoCommand(&clientOpts))
command.AddCommand(NewContextCommand(&clientOpts))
command.AddCommand(NewProjectCommand(&clientOpts))

View File

@@ -54,7 +54,6 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
fmt.Printf(" GoVersion: %s\n", serverVers.GoVersion)
fmt.Printf(" Compiler: %s\n", serverVers.Compiler)
fmt.Printf(" Platform: %s\n", serverVers.Platform)
fmt.Printf(" Ksonnet Version: %s\n", serverVers.KsonnetVersion)
}
},

View File

@@ -73,8 +73,6 @@ var (
AnnotationHook = MetadataPrefix + "/hook"
// AnnotationHookDeletePolicy is the policy of deleting a hook
AnnotationHookDeletePolicy = MetadataPrefix + "/hook-delete-policy"
// AnnotationHelmHook is the helm hook annotation
AnnotationHelmHook = "helm.sh/hook"
// LabelKeyApplicationControllerInstanceID is the label which allows to separate application among multiple running application controllers.
LabelKeyApplicationControllerInstanceID = application.ApplicationFullName + "/controller-instanceid"

View File

@@ -10,7 +10,6 @@ import (
"time"
log "github.com/sirupsen/logrus"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -47,7 +46,6 @@ type ApplicationController struct {
namespace string
kubeClientset kubernetes.Interface
applicationClientset appclientset.Interface
auditLogger *argo.AuditLogger
appRefreshQueue workqueue.RateLimitingInterface
appOperationQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
@@ -90,7 +88,6 @@ func NewApplicationController(
statusRefreshTimeout: appResyncPeriod,
forceRefreshApps: make(map[string]bool),
forceRefreshAppsMutex: &sync.Mutex{},
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, "application-controller"),
}
}
@@ -204,7 +201,7 @@ func retryUntilSucceed(action func() error, desc string, ctx context.Context, ti
log.Infof("Stop retrying %s", desc)
return
} else {
log.Warnf("Failed to %s: %+v, retrying in %v", desc, err, timeout)
log.Warnf("Failed to %s: %v, retrying in %v", desc, err, timeout)
time.Sleep(timeout)
}
}
@@ -286,7 +283,6 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
Type: appv1.ApplicationConditionDeletionError,
Message: err.Error(),
})
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonStatusRefreshed, Action: "refresh_status"}, v1.EventTypeWarning)
} else {
log.Infof("Successfully deleted resources for application %s", app.Name)
}
@@ -400,7 +396,6 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
// If operation is completed, clear the operation field to indicate no operation is
// in progress.
patch["operation"] = nil
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonResourceUpdated, Action: "refresh_status"}, v1.EventTypeNormal)
}
if reflect.DeepEqual(app.Status.OperationState, state) {
log.Infof("No operation updates necessary to '%s'. Skipping patch", app.Name)
@@ -558,7 +553,6 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
// setApplicationHealth updates the health statuses of all resources performed in the comparison
func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
var savedErr error
appHealth := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
if comparisonResult.Status == appv1.ComparisonStatusUnknown {
appHealth.Status = appv1.HealthStatusUnknown
@@ -573,8 +567,8 @@ func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.Heal
return nil, err
}
healthState, err := health.GetAppHealth(&obj)
if err != nil && savedErr == nil {
savedErr = err
if err != nil {
return nil, err
}
resource.Health = *healthState
}
@@ -583,7 +577,7 @@ func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.Heal
appHealth.Status = resource.Health.Status
}
}
return &appHealth, savedErr
return &appHealth, nil
}
// updateAppStatus persists updates to application status. Detects if there patch

View File

@@ -120,7 +120,6 @@ func (s *ksonnetAppStateManager) getTargetObjs(app *v1alpha1.Application, revisi
Revision: revision,
ComponentParameterOverrides: mfReqOverrides,
AppLabel: app.Name,
ValueFiles: app.Spec.Source.ValuesFiles,
})
if err != nil {
return nil, nil, err

View File

@@ -431,7 +431,7 @@ func (sc *syncContext) doHookSync(syncTasks []syncTask, hooks []*unstructured.Un
sc.setOperationPhase(appv1.OperationSucceeded, "successfully synced")
}
// getHooks returns all ArgoCD hooks, optionally filtered by ones of the specific type(s)
// getHooks returns all hooks, or ones of the specific type(s)
func (sc *syncContext) getHooks(hookTypes ...appv1.HookType) ([]*unstructured.Unstructured, error) {
var hooks []*unstructured.Unstructured
for _, manifest := range sc.manifestInfo.Manifests {
@@ -440,9 +440,7 @@ func (sc *syncContext) getHooks(hookTypes ...appv1.HookType) ([]*unstructured.Un
if err != nil {
return nil, err
}
if !isArgoHook(&hook) {
// TODO: in the future, if we want to map helm hooks to ArgoCD lifecycles, we should
// include helm hooks in the returned list
if !isHook(&hook) {
continue
}
if len(hookTypes) > 0 {
@@ -616,24 +614,9 @@ func isHookType(hook *unstructured.Unstructured, hookType appv1.HookType) bool {
return false
}
// isHook indicates if the object is either a ArgoCD or Helm hook
// isHook tells whether or not the supplied object is a application lifecycle hook, or a normal,
// synced application resource
func isHook(obj *unstructured.Unstructured) bool {
return isArgoHook(obj) || isHelmHook(obj)
}
// isHelmHook indicates if the supplied object is a helm hook
func isHelmHook(obj *unstructured.Unstructured) bool {
annotations := obj.GetAnnotations()
if annotations == nil {
return false
}
_, ok := annotations[common.AnnotationHook]
return ok
}
// isArgoHook indicates if the supplied object is an ArgoCD application lifecycle hook
// (vs. a normal, synced application resource)
func isArgoHook(obj *unstructured.Unstructured) bool {
annotations := obj.GetAnnotations()
if annotations == nil {
return false

View File

@@ -7,8 +7,6 @@
* [Tracking Strategies](tracking_strategies.md)
## Features
* [Application Sources](application_sources.md)
* [Application Parameters](parameters.md)
* [Resource Health](health.md)
* [Resource Hooks](resource_hooks.md)
* [Single Sign On](sso.md)

View File

@@ -1,102 +0,0 @@
# Application Source Types
ArgoCD supports several different ways in which kubernetes manifests can be defined:
* [ksonnet](https://ksonnet.io) applications
* [helm](https://helm.sh) charts
* Simple directory of YAML/json manifests
Some additional considerations should be made when deploying apps of a particular type:
## Ksonnet
### Environments
Ksonnet has a first class concept of an "environment." To create an application from a ksonnet
app directory, an environment must be specified. For example, the following command creates the
"guestbook-default" app, which points to the `default` environment:
```
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
```
### Parameters
Ksonnet parameters all belong to a component. For example, the following are the parameters
available in the guestbook app, all of which belong to the `guestbook-ui` component:
```
$ ks param list
COMPONENT PARAM VALUE
========= ===== =====
guestbook-ui containerPort 80
guestbook-ui image "gcr.io/heptio-images/ks-guestbook-demo:0.1"
guestbook-ui name "guestbook-ui"
guestbook-ui replicas 1
guestbook-ui servicePort 80
guestbook-ui type "LoadBalancer"
```
When overriding ksonnet parameters in ArgoCD, the component name should also be specified in the
`argocd app set` command, in the form of `-p COMPONENT=PARAM=VALUE`. For example:
```
argocd app set guestbook-default -p guestbook-ui=image=gcr.io/heptio-images/ks-guestbook-demo:0.1
```
## Helm
### Values Files
Helm has the ability to use a different, or even multiple "values.yaml" files to derive its
parameters from. Alternate or multiple values file(s), can be specified using the `--values`
flag. The flag can be repeated to support multiple values files:
```
argocd app set helm-guestbook --values values-production.yaml
```
### Helm Parameters
Helm has the ability to set parameter values, which override any values in
a `values.yaml`. For example, `service.type` is a common parameter which is exposed in a Helm chart:
```
helm template . --set service.type=LoadBalancer
```
Similarly ArgoCD can override values in the `values.yaml` parameters using `argo app set` command,
in the form of `-p PARAM=VALUE`. For example:
```
argocd app set helm-guestbook -p service.type=LoadBalancer
```
### Helm Hooks
Helm hooks are equivalent in concept to [ArgoCD resource hooks](resource_hooks.md). In helm, a hook
is any normal kubernetes resource annotated with the `helm.sh/hook` annotation. When ArgoCD deploys
helm application which contains helm hooks, all helm hook resources are currently ignored during
the `kubectl apply` of the manifests. There is an
[open issue](https://github.com/argoproj/argo-cd/issues/355) to map Helm hooks to ArgoCD's concept
of Pre/Post/Sync hooks.
### Random Data
Helm templating has the ability to generate random data during chart rendering via the
`randAlphaNum` function. Many helm charts from the [charts repository](https://github.com/helm/charts)
make use of this feature. For example, the following is the secret for the
[redis helm chart](https://github.com/helm/charts/blob/master/stable/redis/templates/secrets.yaml):
```
data:
{{- if .Values.password }}
redis-password: {{ .Values.password | b64enc | quote }}
{{- else }}
redis-password: {{ randAlphaNum 10 | b64enc | quote }}
{{- end }}
```
The ArgoCD application controller periodically compares git state against the live state, running
the `helm template <CHART>` command to generate the helm manifests. Because the random value is
regenerated every time the comparison is made, any application which makes use of the `randAlphaNum`
function will always be in an `OutOfSync` state. This can be mitigated by explicitly setting a
value, in the values.yaml such that the value is stable between each comparison. For example:
```
argocd app set redis -p password=abc123
```

View File

@@ -22,31 +22,15 @@ manifests when provided the following inputs:
* repository URL
* git revision (commit, tag, branch)
* application path
* template specific settings: parameters, ksonnet environments, helm values.yaml
* application environment
### Application Controller
The application controller is a Kubernetes controller which continuously monitors running
applications and compares the current, live state against the desired target state (as specified in
the git repo). It detects `OutOfSync` application state and optionally takes corrective action. It
is responsible for invoking any user-defined hooks for lifcecycle events (PreSync, Sync, PostSync)
the git repo). It detects out-of-sync application state and optionally takes corrective action. It
is responsible for invoking any user-defined handlers (argo workflows) for Sync, OutOfSync events
### Application CRD (Custom Resource Definition)
The Application CRD is the Kubernetes resource object representing a deployed application instance
in an environment. It is defined by two key pieces of information:
* `source` reference to the desired state in git (repository, revision, path, environment)
* `destination` reference to the target cluster and namespace.
An example spec is as follows:
```
spec:
project: default
source:
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
path: guestbook
environment: default
destination:
server: https://kubernetes.default.svc
namespace: default
```
in an environment. It holds a reference to the desired target state (repo, revision, app, environment)
of which the application controller will enforce state against.

View File

@@ -1,37 +1,30 @@
# ArgoCD Getting Started
An example guestbook application is provided to demonstrate how ArgoCD works.
An example Ksonnet guestbook application is provided to demonstrates how ArgoCD works.
## Requirements
* Installed [minikube](https://github.com/kubernetes/minikube#installation)
* Installed [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line tool
* Have a [kubeconfig](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) file (default location is `~/.kube/config`).
## 1. Install ArgoCD
```
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v0.7.1/manifests/install.yaml
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/install.yaml
```
This will create a new namespace, `argocd`, where ArgoCD services and application resources will live.
NOTE:
* On GKE with RBAC enabled, you may need to grant your account the ability to create new cluster roles
```
kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
$ kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
```
## 2. Download ArgoCD CLI
Download the latest ArgoCD version:
On Mac:
```
brew install argoproj/tap/argocd
```
On Linux:
```
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.7.1/argocd-linux-amd64
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v0.6.0/argocd-darwin-amd64
chmod +x /usr/local/bin/argocd
```
@@ -60,14 +53,13 @@ argocd login $(minikube service argocd-server -n argocd --url | cut -d'/' -f 3)
```
Other clusters:
```
kubectl get svc -n argocd argocd-server
kubectl get svc argocd-server
argocd login <EXTERNAL-IP>
```
After logging in, change the password using the command:
```
argocd account update-password
argocd relogin
```
@@ -123,7 +115,7 @@ After connecting a git repository, select the guestbook application for creation
Applications can be also be created using the ArgoCD CLI:
```
argocd app create guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
argocd app create --name guestbook-default --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --env default
```
## 7. Sync (deploy) the application
@@ -165,13 +157,17 @@ Service guestbook-ui service "guestbook-ui" created
Deployment guestbook-ui deployment.apps "guestbook-ui" created
```
This command retrieves the manifests from git repository and performs a `kubectl apply` of the
manifests. The guestbook app is now running and you can now view its resource
This command retrieves the manifests from the ksonnet app in the git repository and performs a
`kubectl apply` of the manifests. The guestbook app is now running and you can now view its resource
components, logs, events, and assessed health:
![view app](assets/guestbook-tree.png)
## 8. Next Steps
ArgoCD supports additional features such as SSO, WebHooks, RBAC, Projects. See the rest of
the [documentation](./) for details.
ArgoCD supports additional features such as SSO, WebHooks, RBAC. See the following guides on setting
these up:
* [Configuring SSO](sso.md)
* [Configuring RBAC](rbac.md)
* [Configuring WebHooks](webhook.md)

View File

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

View File

@@ -44,19 +44,15 @@ Kubernetes clusters which can be used by applications belonging to the project.
### 1. Create new project
Following command creates project `myproject` which can deploy applications to namespace `default` of cluster `https://kubernetes.default.svc`. The valid application source is defined in the `https://github.com/argoproj/argocd-example-apps.git` repository.
Following command creates project `myproject` which can deploy applications to namespace `default` of cluster `https://kubernetes.default.svc`. The source ksonnet application
should be defined in `https://github.com/argoproj/argocd-example-apps.git` repository.
```
argocd proj create myproject -d https://kubernetes.default.svc,default -s https://github.com/argoproj/argocd-example-apps.git
```
Project sources and destinations can be managed using commands
```
argocd project add-destination
argocd project remove-destination
argocd project add-source
argocd project remove-source
```
Project sources and destinations can be managed using commands `argocd project add-destination`, `argocd project remove-destination`, `argocd project add-source`
and `argocd project remove-source`.
### 2. Assign application to a project

View File

@@ -1,45 +1,41 @@
# Tracking and Deployment Strategies
An ArgoCD application spec provides several different ways of track kubernetes resource manifests in
git. This document describes the different techniques and the means of deploying those manifests to
the target environment.
An ArgoCD application spec provides several different ways of track kubernetes resource manifests in git. This document describes the different techniques and the means of deploying those manifests to the target environment.
## Branch Tracking
If a branch name is specified, ArgoCD will continually compare live state against the resource
manifests defined at the tip of the specified branch.
If a branch name is specified, ArgoCD will continually compare live state against the resource manifests defined at the tip of the specified branch.
To redeploy an application, a user makes changes to the manifests, and commit/pushes those the
changes to the tracked branch, which will then be detected by ArgoCD controller.
To redeploy an application, a user makes changes to the manifests, and commit/pushes those the changes to the tracked branch, which will then be detected by ArgoCD controller.
## Tag Tracking
If a tag is specified, the manifests at the specified git tag will be used to perform the sync
comparison. This provides some advantages over branch tracking in that a tag is generally considered
more stable, and less frequently updated, with some manual judgement of what constitutes a tag.
If a tag is specified, the manifests at the specified git tag will be used to perform the sync comparison. This provides some advantages over branch tracking in that a tag is generally considered more stable, and less frequently updated, with some manual judgement of what constitutes a tag.
To redeploy an application, the user uses git to change the meaning of a tag by retagging it to a
different commit SHA. ArgoCD will detect the new meaning of the tag when performing the
comparison/sync.
To redeploy an application, the user uses git to change the meaning of a tag by retagging it to a different commit SHA. ArgoCD will detect the new meaning of the tag when performing the comparison/sync.
## Commit Pinning
If a git commit SHA is specified, the application is effectively pinned to the manifests defined at
the specified commit. This is the most restrictive of the techniques and is typically used to
control production environments.
If a git commit SHA is specified, the application is effectively pinned to the manifests defined at the specified commit. This is the most restrictive of the techniques and is typically used to control production environments.
Since commit SHAs cannot change meaning, the only way to change the live state of an application
which is pinned to a commit, is by updating the tracking revision in the application to a different
commit containing the new manifests. Note that [parameter overrides](parameters.md) can still be set
on an application which is pinned to a revision.
## Auto-Sync [(Not Yet Implemented)]((https://github.com/argoproj/argo-cd/issues/79))
In all tracking strategies, the application will have the option to sync automatically. If auto-sync
is configured, the new resources manifests will be applied automatically -- as soon as a difference
is detected between the target state (git) and live state. If auto-sync is disabled, a manual sync
will be needed using the Argo UI, CLI, or API.
Since commit SHAs cannot change meaning, the only way to change the live state of an application which is pinned to a commit, is by updating the tracking revision in the application to a different commit containing the new manifests.
Note that parameter overrides can still be made against a application which is pinned to a revision.
## Parameter Overrides
Note that in all tracking strategies, any [parameter overrides](parameters.md) set in the
application instance take precedence over the git state.
ArgoCD provides means to override the parameters of a ksonnet app. This gives some extra flexibility in having *some* parts of the k8s manifests determined dynamically. It also serves as an alternative way of redeploying an application by changing application parameters via ArgoCD, instead of making the changes to the manifests in git.
The following is an example of where this would be useful: A team maintains a "dev" environment, which needs to be continually updated with the latest version of their guestbook application after every build in the tip of master. To address this use case, the ksonnet application should expose an parameter named `image`, whose value used in the `dev` environment contains a placeholder value (e.g. `example/guestbook:replaceme`), intended to be set externally (outside of git) such as a build systems. As part of the build pipeline, the parameter value of the `image` would be continually updated to the freshly built image (e.g. `example/guestbook:abcd123`). A sync operation would result in the application being redeployed with the new image.
ArgoCD provides these operations conveniently via the CLI, or alternatively via the gRPC/REST API.
```
$ argocd app set guestbook -p guestbook=image=example/guestbook:abcd123
$ argocd app sync guestbook
```
Note that in all tracking strategies, any parameter overrides set in the application instance will be honored.
## [Auto-Sync](https://github.com/argoproj/argo-cd/issues/79) (Not Yet Implemented)
In all tracking strategies, the application will have the option to sync automatically. If auto-sync is configured, the new resources manifests will be applied automatically -- as soon as a difference is detected between the target state (git) and live state. If auto-sync is disabled, a manual sync will be needed using the Argo UI, CLI, or API.

View File

@@ -1,11 +0,0 @@
#!/bin/sh
IMAGE_NAMESPACE=${IMAGE_NAMESPACE:='argoproj'}
IMAGE_TAG=${IMAGE_TAG:='latest'}
for i in "$(ls manifests/components/*.yaml)"; do
sed -i '' 's@\( image: \(.*\)/\(argocd-.*\):.*\)@ image: '"${IMAGE_NAMESPACE}"'/\3:'"${IMAGE_TAG}"'@g' $i
done
echo "# This is an auto-generated file. DO NOT EDIT" > manifests/install.yaml
cat manifests/components/*.yaml >> manifests/install.yaml

View File

@@ -27,11 +27,3 @@ rules:
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list

View File

@@ -14,6 +14,6 @@ spec:
spec:
containers:
- command: [/argocd-application-controller, --repo-server, 'argocd-repo-server:8081']
image: argoproj/argocd-application-controller:v0.7.2
image: argoproj/argocd-application-controller:latest
name: application-controller
serviceAccountName: application-controller

View File

@@ -30,10 +30,3 @@ rules:
- update
- delete
- patch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list

View File

@@ -15,20 +15,20 @@ spec:
serviceAccountName: argocd-server
initContainers:
- name: copyutil
image: argoproj/argocd-server:v0.7.2
image: argoproj/argocd-server:latest
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
- name: ui
image: argoproj/argocd-ui:v0.7.2
image: argoproj/argocd-ui:latest
command: [cp, -r, /app, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
containers:
- name: argocd-server
image: argoproj/argocd-server:v0.7.2
image: argoproj/argocd-server:latest
command: [/argocd-server, --staticassets, /shared/app, --repo-server, 'argocd-repo-server:8081']
volumeMounts:
- mountPath: /shared

View File

@@ -14,7 +14,7 @@ spec:
spec:
containers:
- name: argocd-repo-server
image: argoproj/argocd-repo-server:v0.7.2
image: argoproj/argocd-repo-server:latest
command: [/argocd-repo-server]
ports:
- containerPort: 8081

View File

@@ -126,14 +126,6 @@ rules:
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
@@ -162,7 +154,7 @@ spec:
spec:
containers:
- command: [/argocd-application-controller, --repo-server, 'argocd-repo-server:8081']
image: argoproj/argocd-application-controller:v0.7.2
image: argoproj/argocd-application-controller:v0.6.2
name: application-controller
serviceAccountName: application-controller
---
@@ -202,13 +194,6 @@ rules:
- update
- delete
- patch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
@@ -238,20 +223,20 @@ spec:
serviceAccountName: argocd-server
initContainers:
- name: copyutil
image: argoproj/argocd-server:v0.7.2
image: argoproj/argocd-server:v0.6.2
command: [cp, /argocd-util, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
- name: ui
image: argoproj/argocd-ui:v0.7.2
image: argoproj/argocd-ui:v0.6.2
command: [cp, -r, /app, /shared]
volumeMounts:
- mountPath: /shared
name: static-files
containers:
- name: argocd-server
image: argoproj/argocd-server:v0.7.2
image: argoproj/argocd-server:v0.6.2
command: [/argocd-server, --staticassets, /shared/app, --repo-server, 'argocd-repo-server:8081']
volumeMounts:
- mountPath: /shared
@@ -298,7 +283,7 @@ spec:
spec:
containers:
- name: argocd-repo-server
image: argoproj/argocd-repo-server:v0.7.2
image: argoproj/argocd-repo-server:v0.6.2
command: [/argocd-repo-server]
ports:
- containerPort: 8081

View File

@@ -8,20 +8,9 @@ import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"strings"
"time"
oidc "github.com/coreos/go-oidc"
jwt "github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/server/account"
"github.com/argoproj/argo-cd/server/application"
"github.com/argoproj/argo-cd/server/cluster"
@@ -32,6 +21,9 @@ import (
"github.com/argoproj/argo-cd/server/version"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/localconfig"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
@@ -76,12 +68,11 @@ type ClientOptions struct {
}
type client struct {
ServerAddr string
PlainText bool
Insecure bool
CertPEMData []byte
AuthToken string
RefreshToken string
ServerAddr string
PlainText bool
Insecure bool
CertPEMData []byte
AuthToken string
}
// NewClient creates a new API client from a set of config options.
@@ -91,7 +82,6 @@ func NewClient(opts *ClientOptions) (Client, error) {
if err != nil {
return nil, err
}
var ctxName string
if localCfg != nil {
configCtx, err := localCfg.ResolveContext(opts.Context)
if err != nil {
@@ -108,8 +98,6 @@ func NewClient(opts *ClientOptions) (Client, error) {
c.PlainText = configCtx.Server.PlainText
c.Insecure = configCtx.Server.Insecure
c.AuthToken = configCtx.User.AuthToken
c.RefreshToken = configCtx.User.RefreshToken
ctxName = configCtx.Name
}
}
// Override server address if specified in env or CLI flag
@@ -149,97 +137,9 @@ func NewClient(opts *ClientOptions) (Client, error) {
if opts.Insecure {
c.Insecure = true
}
if localCfg != nil {
err = c.refreshAuthToken(localCfg, ctxName, opts.ConfigPath)
if err != nil {
return nil, err
}
}
return &c, nil
}
// refreshAuthToken refreshes a JWT auth token if it is invalid (e.g. expired)
func (c *client) refreshAuthToken(localCfg *localconfig.LocalConfig, ctxName, configPath string) error {
configCtx, err := localCfg.ResolveContext(ctxName)
if err != nil {
return err
}
if c.RefreshToken == "" {
// If we have no refresh token, there's no point in doing anything
return nil
}
parser := &jwt.Parser{
SkipClaimsValidation: true,
}
var claims jwt.StandardClaims
_, _, err = parser.ParseUnverified(configCtx.User.AuthToken, &claims)
if err != nil {
return err
}
if claims.Valid() == nil {
// token is still valid
return nil
}
log.Debug("Auth token no longer valid. Refreshing")
tlsConfig, err := c.tlsConfig()
if err != nil {
return err
}
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
ctx := oidc.ClientContext(context.Background(), httpClient)
var scheme string
if c.PlainText {
scheme = "http"
} else {
scheme = "https"
}
conf := &oauth2.Config{
ClientID: common.ArgoCDCLIClientAppID,
Scopes: []string{"openid", "profile", "email", "groups", "offline_access"},
Endpoint: oauth2.Endpoint{
AuthURL: fmt.Sprintf("%s://%s%s/auth", scheme, c.ServerAddr, common.DexAPIEndpoint),
TokenURL: fmt.Sprintf("%s://%s%s/token", scheme, c.ServerAddr, common.DexAPIEndpoint),
},
RedirectURL: fmt.Sprintf("%s://%s/auth/callback", scheme, c.ServerAddr),
}
t := &oauth2.Token{
RefreshToken: c.RefreshToken,
}
token, err := conf.TokenSource(ctx, t).Token()
if err != nil {
return err
}
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
return errors.New("no id_token in token response")
}
refreshToken, _ := token.Extra("refresh_token").(string)
c.AuthToken = rawIDToken
c.RefreshToken = refreshToken
localCfg.UpsertUser(localconfig.User{
Name: ctxName,
AuthToken: c.AuthToken,
RefreshToken: c.RefreshToken,
})
err = localconfig.WriteLocalConfig(*localCfg, configPath)
if err != nil {
return err
}
return nil
}
// NewClientOrDie creates a new API client from a set of config options, or fails fatally if the new client creation fails.
func NewClientOrDie(opts *ClientOptions) Client {
client, err := NewClient(opts)
@@ -262,17 +162,25 @@ func (c jwtCredentials) RequireTransportSecurity() bool {
func (c jwtCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
return map[string]string{
MetaDataTokenKey: c.Token,
"tokens": c.Token, // legacy key. delete eventually
}, nil
}
func (c *client) NewConn() (*grpc.ClientConn, error) {
var creds credentials.TransportCredentials
if !c.PlainText {
tlsConfig, err := c.tlsConfig()
if err != nil {
return nil, err
var tlsConfig tls.Config
if len(c.CertPEMData) > 0 {
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(c.CertPEMData) {
return nil, fmt.Errorf("credentials: failed to append certificates")
}
tlsConfig.RootCAs = cp
}
creds = credentials.NewTLS(tlsConfig)
if c.Insecure {
tlsConfig.InsecureSkipVerify = true
}
creds = credentials.NewTLS(&tlsConfig)
}
endpointCredentials := jwtCredentials{
Token: c.AuthToken,
@@ -280,21 +188,6 @@ func (c *client) NewConn() (*grpc.ClientConn, error) {
return grpc_util.BlockingDial(context.Background(), "tcp", c.ServerAddr, creds, grpc.WithPerRPCCredentials(endpointCredentials))
}
func (c *client) tlsConfig() (*tls.Config, error) {
var tlsConfig tls.Config
if len(c.CertPEMData) > 0 {
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(c.CertPEMData) {
return nil, fmt.Errorf("credentials: failed to append certificates")
}
tlsConfig.RootCAs = cp
}
if c.Insecure {
tlsConfig.InsecureSkipVerify = true
}
return &tlsConfig, nil
}
func (c *client) ClientOptions() ClientOptions {
return ClientOptions{
ServerAddr: c.ServerAddr,

View File

@@ -547,21 +547,6 @@ func (m *ApplicationSource) MarshalTo(dAtA []byte) (int, error) {
i += n
}
}
if len(m.ValuesFiles) > 0 {
for _, s := range m.ValuesFiles {
dAtA[i] = 0x32
i++
l = len(s)
for l >= 1<<7 {
dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
l >>= 7
i++
}
dAtA[i] = uint8(l)
i++
i += copy(dAtA[i:], s)
}
}
return i, nil
}
@@ -1773,12 +1758,6 @@ func (m *ApplicationSource) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l))
}
}
if len(m.ValuesFiles) > 0 {
for _, s := range m.ValuesFiles {
l = len(s)
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
@@ -2289,7 +2268,6 @@ func (this *ApplicationSource) String() string {
`Environment:` + fmt.Sprintf("%v", this.Environment) + `,`,
`TargetRevision:` + fmt.Sprintf("%v", this.TargetRevision) + `,`,
`ComponentParameterOverrides:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ComponentParameterOverrides), "ComponentParameter", "ComponentParameter", 1), `&`, ``, 1) + `,`,
`ValuesFiles:` + fmt.Sprintf("%v", this.ValuesFiles) + `,`,
`}`,
}, "")
return s
@@ -3663,35 +3641,6 @@ func (m *ApplicationSource) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ValuesFiles", 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.ValuesFiles = append(m.ValuesFiles, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -7776,158 +7725,157 @@ func init() {
}
var fileDescriptorGenerated = []byte{
// 2446 bytes of a gzipped FileDescriptorProto
// 2425 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0x4b, 0x8c, 0x1c, 0x47,
0x19, 0x76, 0xcf, 0x6b, 0x67, 0xfe, 0xd9, 0x87, 0x5d, 0x79, 0x30, 0x38, 0xd2, 0xee, 0xaa, 0xc3,
0xc3, 0xa0, 0x64, 0x06, 0x1b, 0x02, 0xe6, 0x21, 0x24, 0xcf, 0xae, 0x1d, 0x6f, 0xd6, 0x8f, 0xa5,
0x66, 0x13, 0xa4, 0x10, 0x05, 0xda, 0x3d, 0xb5, 0x33, 0xed, 0x99, 0xe9, 0xee, 0x74, 0xd5, 0x8c,
0x35, 0x12, 0x41, 0x41, 0x08, 0x29, 0xbc, 0x24, 0x10, 0x42, 0x5c, 0x39, 0x70, 0x42, 0x48, 0x48,
0x88, 0x13, 0x12, 0x07, 0x38, 0x20, 0x1f, 0x73, 0x00, 0x11, 0x05, 0xb4, 0xc2, 0x9b, 0x4b, 0x24,
0x0e, 0xdc, 0x73, 0x42, 0xf5, 0xe8, 0xae, 0xea, 0x9e, 0x5d, 0x76, 0xed, 0x69, 0x1b, 0x72, 0xeb,
0xfe, 0xff, 0xbf, 0xff, 0xef, 0xaf, 0xbf, 0xfe, 0xfa, 0x1f, 0xd5, 0xb0, 0xd5, 0xf3, 0x58, 0x7f,
0x7c, 0xab, 0xe9, 0x06, 0xa3, 0x96, 0x13, 0xf5, 0x82, 0x30, 0x0a, 0x6e, 0x8b, 0x87, 0x67, 0xdd,
0x6e, 0x2b, 0x1c, 0xf4, 0x5a, 0x4e, 0xe8, 0xd1, 0x96, 0x13, 0x86, 0x43, 0xcf, 0x75, 0x98, 0x17,
0xf8, 0xad, 0xc9, 0x79, 0x67, 0x18, 0xf6, 0x9d, 0xf3, 0xad, 0x1e, 0xf1, 0x49, 0xe4, 0x30, 0xd2,
0x6d, 0x86, 0x51, 0xc0, 0x02, 0xf4, 0x79, 0xad, 0xaa, 0x19, 0xab, 0x12, 0x0f, 0x5f, 0x77, 0xbb,
0xcd, 0x70, 0xd0, 0x6b, 0x72, 0x55, 0x4d, 0x43, 0x55, 0x33, 0x56, 0x75, 0xf6, 0x59, 0xc3, 0x8a,
0x5e, 0xd0, 0x0b, 0x5a, 0x42, 0xe3, 0xad, 0xf1, 0x9e, 0x78, 0x13, 0x2f, 0xe2, 0x49, 0x22, 0x9d,
0xfd, 0xcc, 0xe0, 0x22, 0x6d, 0x7a, 0x01, 0xb7, 0x6d, 0xe4, 0xb8, 0x7d, 0xcf, 0x27, 0xd1, 0x54,
0x1b, 0x3b, 0x22, 0xcc, 0x69, 0x4d, 0x66, 0xec, 0x3b, 0xdb, 0x3a, 0xea, 0xab, 0x68, 0xec, 0x33,
0x6f, 0x44, 0x66, 0x3e, 0xf8, 0xec, 0x71, 0x1f, 0x50, 0xb7, 0x4f, 0x46, 0xce, 0xcc, 0x77, 0x9f,
0x3e, 0xea, 0xbb, 0x31, 0xf3, 0x86, 0x2d, 0xcf, 0x67, 0x94, 0x45, 0xd9, 0x8f, 0xec, 0xbf, 0x5b,
0x00, 0x97, 0xc2, 0x70, 0x27, 0x0a, 0x6e, 0x13, 0x97, 0xa1, 0x6f, 0x40, 0x95, 0xaf, 0xa3, 0xeb,
0x30, 0xa7, 0x61, 0xad, 0x5b, 0xe7, 0xea, 0x17, 0x3e, 0xd5, 0x94, 0x6a, 0x9b, 0xa6, 0x5a, 0xed,
0x57, 0x2e, 0xdd, 0x9c, 0x9c, 0x6f, 0xde, 0xbc, 0xc5, 0xbf, 0xbf, 0x4e, 0x98, 0xd3, 0x46, 0x77,
0xf7, 0xd7, 0x4e, 0x1d, 0xec, 0xaf, 0x81, 0xa6, 0xe1, 0x44, 0x2b, 0x1a, 0x40, 0x89, 0x86, 0xc4,
0x6d, 0x14, 0x84, 0xf6, 0xad, 0xe6, 0x03, 0xef, 0x5e, 0x53, 0x9b, 0xdd, 0x09, 0x89, 0xdb, 0x5e,
0x54, 0xb0, 0x25, 0xfe, 0x86, 0x05, 0x88, 0xfd, 0x8e, 0x05, 0xcb, 0x5a, 0xec, 0x9a, 0x47, 0x19,
0x7a, 0x65, 0x66, 0x85, 0xcd, 0x93, 0xad, 0x90, 0x7f, 0x2d, 0xd6, 0x77, 0x5a, 0x01, 0x55, 0x63,
0x8a, 0xb1, 0xba, 0xdb, 0x50, 0xf6, 0x18, 0x19, 0xd1, 0x46, 0x61, 0xbd, 0x78, 0xae, 0x7e, 0xe1,
0x72, 0x2e, 0xcb, 0x6b, 0x2f, 0x29, 0xc4, 0xf2, 0x16, 0xd7, 0x8d, 0x25, 0x84, 0xfd, 0x66, 0xc1,
0x5c, 0x1c, 0x5f, 0x35, 0x3a, 0x0f, 0x75, 0x1a, 0x8c, 0x23, 0x97, 0x60, 0x12, 0x06, 0xb4, 0x61,
0xad, 0x17, 0xcf, 0xd5, 0xda, 0x2b, 0x07, 0xfb, 0x6b, 0xf5, 0x8e, 0x26, 0x63, 0x53, 0x06, 0xfd,
0xc0, 0x82, 0xc5, 0x2e, 0xa1, 0xcc, 0xf3, 0x05, 0x7e, 0x6c, 0xf9, 0x57, 0xe6, 0xb3, 0x3c, 0x26,
0x6e, 0x6a, 0xcd, 0xed, 0xc7, 0xd5, 0x2a, 0x16, 0x0d, 0x22, 0xc5, 0x29, 0x70, 0xf4, 0x1c, 0xd4,
0xbb, 0x84, 0xba, 0x91, 0x17, 0xf2, 0xf7, 0x46, 0x71, 0xdd, 0x3a, 0x57, 0x6b, 0x3f, 0xa6, 0x3e,
0xac, 0x6f, 0x6a, 0x16, 0x36, 0xe5, 0xec, 0x3f, 0x17, 0xa1, 0x6e, 0xa0, 0x3e, 0x82, 0x30, 0x1e,
0xa6, 0xc2, 0xf8, 0x85, 0x7c, 0xbc, 0x75, 0x54, 0x1c, 0x23, 0x06, 0x15, 0xca, 0x1c, 0x36, 0xa6,
0xc2, 0x23, 0xf5, 0x0b, 0xd7, 0x72, 0xc2, 0x13, 0x3a, 0xdb, 0xcb, 0x0a, 0xb1, 0x22, 0xdf, 0xb1,
0xc2, 0x42, 0xaf, 0x41, 0x2d, 0x08, 0x79, 0xb6, 0xe0, 0x5b, 0x51, 0x12, 0xc0, 0x9b, 0x73, 0x00,
0xdf, 0x8c, 0x75, 0xb5, 0x97, 0x0e, 0xf6, 0xd7, 0x6a, 0xc9, 0x2b, 0xd6, 0x28, 0xb6, 0x0b, 0x8f,
0x1b, 0xf6, 0x6d, 0x04, 0x7e, 0xd7, 0x13, 0x1b, 0xba, 0x0e, 0x25, 0x36, 0x0d, 0x89, 0xd8, 0xcc,
0x9a, 0x76, 0xd1, 0xee, 0x34, 0x24, 0x58, 0x70, 0xd0, 0x27, 0x60, 0x61, 0x44, 0x28, 0x75, 0x7a,
0x44, 0xec, 0x49, 0xad, 0xbd, 0xa2, 0x84, 0x16, 0xae, 0x4b, 0x32, 0x8e, 0xf9, 0xf6, 0x6b, 0xf0,
0xe4, 0xe1, 0x21, 0x8a, 0x3e, 0x06, 0x15, 0x4a, 0xa2, 0x09, 0x89, 0x14, 0x90, 0xf6, 0x8c, 0xa0,
0x62, 0xc5, 0x45, 0x2d, 0xa8, 0xf9, 0xce, 0x88, 0xd0, 0xd0, 0x71, 0x63, 0xb8, 0x33, 0x4a, 0xb4,
0x76, 0x23, 0x66, 0x60, 0x2d, 0x63, 0xff, 0xc3, 0x82, 0x15, 0x03, 0xf3, 0x11, 0x64, 0xa2, 0x41,
0x3a, 0x13, 0x5d, 0xc9, 0x27, 0x62, 0x8e, 0x48, 0x45, 0x7f, 0x2c, 0xc2, 0x19, 0x33, 0xae, 0x44,
0x7e, 0xe1, 0x5b, 0x12, 0x91, 0x30, 0x78, 0x11, 0x5f, 0x53, 0xee, 0x4c, 0xb6, 0x04, 0x4b, 0x32,
0x8e, 0xf9, 0x7c, 0x7f, 0x43, 0x87, 0xf5, 0x95, 0x2f, 0x93, 0xfd, 0xdd, 0x71, 0x58, 0x1f, 0x0b,
0x0e, 0xcf, 0x0c, 0xc4, 0x9f, 0x78, 0x51, 0xe0, 0x8f, 0x88, 0xcf, 0xb2, 0x99, 0xe1, 0xb2, 0x66,
0x61, 0x53, 0x0e, 0x7d, 0x19, 0x96, 0x99, 0x13, 0xf5, 0x08, 0xc3, 0x64, 0xe2, 0xd1, 0x38, 0x90,
0x6b, 0xed, 0x27, 0xd5, 0x97, 0xcb, 0xbb, 0x29, 0x2e, 0xce, 0x48, 0xa3, 0xdf, 0x59, 0xf0, 0x94,
0x1b, 0x8c, 0xc2, 0xc0, 0x27, 0x3e, 0xdb, 0x71, 0x22, 0x67, 0x44, 0x18, 0x89, 0x6e, 0x4e, 0x48,
0x14, 0x79, 0x5d, 0x42, 0x1b, 0x65, 0xe1, 0xdd, 0xeb, 0x73, 0x78, 0x77, 0x63, 0x46, 0x7b, 0xfb,
0x69, 0x65, 0xdc, 0x53, 0x1b, 0x47, 0x23, 0xe3, 0xff, 0x66, 0x16, 0x2f, 0x04, 0x13, 0x67, 0x38,
0x26, 0xf4, 0x8a, 0x37, 0x24, 0xb4, 0x51, 0xd1, 0x85, 0xe0, 0x25, 0x4d, 0xc6, 0xa6, 0x8c, 0xfd,
0x87, 0x42, 0x2a, 0x44, 0x3b, 0x71, 0xde, 0x11, 0x7b, 0xa9, 0x02, 0x34, 0xaf, 0xbc, 0x23, 0x74,
0x1a, 0xa7, 0x4b, 0xd6, 0x23, 0x85, 0x85, 0xde, 0xb4, 0x44, 0x15, 0x88, 0x4f, 0xa5, 0xca, 0xb1,
0x0f, 0xa1, 0x22, 0x99, 0x85, 0x25, 0x26, 0x62, 0x13, 0x9a, 0x87, 0x70, 0x28, 0xeb, 0xab, 0x8a,
0xb8, 0x24, 0x84, 0x55, 0xd9, 0xc5, 0x31, 0xdf, 0xfe, 0x45, 0x25, 0x7d, 0x06, 0x64, 0x0e, 0xfd,
0x89, 0x05, 0xa7, 0xf9, 0x46, 0x39, 0x91, 0x47, 0x03, 0x1f, 0x13, 0x3a, 0x1e, 0x32, 0xe5, 0xcc,
0xed, 0x39, 0x83, 0xc6, 0x54, 0xd9, 0x6e, 0x28, 0xbb, 0x4e, 0x67, 0x39, 0x78, 0x06, 0x1e, 0x31,
0x58, 0xe8, 0x7b, 0x94, 0x05, 0xd1, 0x54, 0x25, 0x87, 0x79, 0xba, 0xb0, 0x4d, 0x12, 0x0e, 0x83,
0x29, 0x3f, 0x6b, 0x5b, 0xfe, 0x5e, 0xa0, 0xfd, 0x73, 0x55, 0x22, 0xe0, 0x18, 0x0a, 0x7d, 0xdb,
0x02, 0x08, 0xe3, 0x48, 0xe5, 0x85, 0xec, 0x21, 0x1c, 0x9c, 0xa4, 0x66, 0x27, 0x24, 0x8a, 0x0d,
0x50, 0x14, 0x40, 0xa5, 0x4f, 0x9c, 0x21, 0xeb, 0xab, 0x72, 0xf6, 0xfc, 0x1c, 0xf0, 0x57, 0x85,
0xa2, 0x6c, 0x09, 0x95, 0x54, 0xac, 0x60, 0xd0, 0x77, 0x2d, 0x58, 0x4e, 0xaa, 0x1b, 0x97, 0x25,
0x8d, 0xf2, 0xdc, 0x8d, 0xef, 0xcd, 0x94, 0xc2, 0x36, 0xe2, 0x69, 0x2c, 0x4d, 0xc3, 0x19, 0x50,
0xf4, 0x1d, 0x0b, 0xc0, 0x8d, 0xab, 0xa9, 0xcc, 0x07, 0xf5, 0x0b, 0x37, 0xf3, 0x39, 0x51, 0x49,
0x95, 0xd6, 0xee, 0x4f, 0x48, 0x14, 0x1b, 0xb0, 0xf6, 0xbb, 0x16, 0x3c, 0x61, 0x7c, 0xf8, 0x55,
0x87, 0xb9, 0xfd, 0xcb, 0x13, 0x9e, 0xa6, 0xb7, 0x53, 0xf5, 0xfd, 0x73, 0x66, 0x7d, 0x7f, 0x7f,
0x7f, 0xed, 0xe3, 0x47, 0x4d, 0x36, 0x77, 0xb8, 0x86, 0xa6, 0x50, 0x61, 0xb4, 0x02, 0xaf, 0x43,
0xdd, 0xb0, 0x59, 0xa5, 0x8f, 0xbc, 0x0a, 0x60, 0x92, 0x33, 0x0c, 0x22, 0x36, 0xf1, 0xec, 0xbf,
0x16, 0x60, 0x61, 0x63, 0x38, 0xa6, 0x8c, 0x44, 0x27, 0x6e, 0x28, 0xd6, 0xa1, 0xc4, 0x9b, 0x85,
0x6c, 0xfd, 0xe3, 0xbd, 0x04, 0x16, 0x1c, 0x14, 0x42, 0xc5, 0x0d, 0xfc, 0x3d, 0xaf, 0xa7, 0x5a,
0xc0, 0xab, 0xf3, 0x9c, 0x1c, 0x69, 0xdd, 0x86, 0xd0, 0xa7, 0x6d, 0x92, 0xef, 0x58, 0xe1, 0xa0,
0x1f, 0x59, 0xb0, 0xe2, 0x06, 0xbe, 0x4f, 0x5c, 0x1d, 0xbc, 0xa5, 0xb9, 0xdb, 0xdd, 0x8d, 0xb4,
0xc6, 0xf6, 0x87, 0x14, 0xfa, 0x4a, 0x86, 0x81, 0xb3, 0xd8, 0xf6, 0x6f, 0x0b, 0xb0, 0x94, 0xb2,
0x1c, 0x3d, 0x03, 0xd5, 0x31, 0x25, 0x91, 0xf0, 0x9c, 0xf4, 0x6f, 0xd2, 0x11, 0xbd, 0xa8, 0xe8,
0x38, 0x91, 0xe0, 0xd2, 0xa1, 0x43, 0xe9, 0x9d, 0x20, 0xea, 0x2a, 0x3f, 0x27, 0xd2, 0x3b, 0x8a,
0x8e, 0x13, 0x09, 0xde, 0x6f, 0xdc, 0x22, 0x4e, 0x44, 0xa2, 0xdd, 0x60, 0x40, 0x66, 0x26, 0x91,
0xb6, 0x66, 0x61, 0x53, 0x4e, 0x38, 0x8d, 0x0d, 0xe9, 0xc6, 0xd0, 0x23, 0x3e, 0x93, 0x66, 0xe6,
0xe0, 0xb4, 0xdd, 0x6b, 0x1d, 0x53, 0xa3, 0x76, 0x5a, 0x86, 0x81, 0xb3, 0xd8, 0xf6, 0x5f, 0x2c,
0xa8, 0x2b, 0xa7, 0x3d, 0x82, 0xa6, 0xb3, 0x97, 0x6e, 0x3a, 0xdb, 0xf3, 0xc7, 0xe8, 0x11, 0x0d,
0xe7, 0xaf, 0x8b, 0x30, 0x53, 0xe9, 0xd0, 0xab, 0x3c, 0xc7, 0x71, 0x1a, 0xe9, 0x5e, 0x8a, 0x8b,
0xec, 0x27, 0x4f, 0xb6, 0xba, 0x5d, 0x6f, 0x44, 0xcc, 0xf4, 0x15, 0x6b, 0xc1, 0x86, 0x46, 0xf4,
0x86, 0xa5, 0x01, 0x76, 0x03, 0x95, 0x57, 0xf2, 0x6d, 0x89, 0x66, 0x4c, 0xd8, 0x0d, 0xb0, 0x81,
0x89, 0xbe, 0x90, 0x0c, 0x82, 0x65, 0x11, 0x90, 0x76, 0x7a, 0x74, 0x7b, 0x3f, 0xd5, 0x00, 0x64,
0xc6, 0xb9, 0x29, 0xd4, 0x22, 0x22, 0x5b, 0xac, 0xb8, 0x02, 0xcc, 0x93, 0x44, 0xb0, 0xd2, 0x25,
0x8f, 0x71, 0x32, 0xfe, 0xc4, 0x64, 0x8a, 0x35, 0x9a, 0xfd, 0x43, 0x0b, 0xd0, 0x6c, 0xb9, 0xe6,
0x63, 0x54, 0xd2, 0xc4, 0xaa, 0x03, 0x9c, 0xe8, 0x49, 0xc4, 0xb1, 0x96, 0x39, 0x41, 0x9a, 0x7c,
0x1a, 0xca, 0xa2, 0xa9, 0x55, 0x07, 0x36, 0x89, 0x1e, 0xd1, 0xf6, 0x62, 0xc9, 0xb3, 0xff, 0x64,
0x41, 0x36, 0xdd, 0x88, 0x4c, 0x2d, 0x3d, 0x9b, 0xcd, 0xd4, 0x69, 0x2f, 0x9e, 0x7c, 0xce, 0x44,
0xaf, 0x40, 0xdd, 0x61, 0x8c, 0x8c, 0x42, 0x26, 0x02, 0xb2, 0x78, 0xdf, 0x01, 0xb9, 0xcc, 0x23,
0xe1, 0x7a, 0xd0, 0xf5, 0xf6, 0x3c, 0x11, 0x8c, 0xa6, 0x3a, 0xfb, 0xbd, 0x22, 0x2c, 0xa7, 0x9b,
0x2f, 0x34, 0x86, 0x8a, 0x68, 0x76, 0xe4, 0xcd, 0x4f, 0xee, 0xdd, 0x55, 0xe2, 0x12, 0x41, 0xa2,
0x58, 0x81, 0xf1, 0xc4, 0x1a, 0xc5, 0xd3, 0x55, 0x26, 0xb1, 0x26, 0x73, 0x55, 0x22, 0x71, 0xec,
0x44, 0x55, 0xfc, 0xff, 0x9c, 0xa8, 0x5e, 0x05, 0xe8, 0x0a, 0x6f, 0x8b, 0xbd, 0x2c, 0x3d, 0x78,
0x72, 0xd9, 0x4c, 0xb4, 0x60, 0x43, 0x23, 0x3a, 0x0b, 0x05, 0xaf, 0x2b, 0x4e, 0x75, 0xb1, 0x0d,
0x4a, 0xb6, 0xb0, 0xb5, 0x89, 0x0b, 0x5e, 0xd7, 0xa6, 0xb0, 0x68, 0x76, 0x9b, 0x27, 0x8e, 0xd5,
0x2f, 0xc2, 0x92, 0x7c, 0xda, 0x24, 0xcc, 0xf1, 0x86, 0x54, 0xed, 0xce, 0x13, 0x4a, 0x7c, 0xa9,
0x63, 0x32, 0x71, 0x5a, 0xd6, 0xfe, 0x79, 0x01, 0xe0, 0x6a, 0x10, 0x0c, 0x14, 0x66, 0x7c, 0xf4,
0xac, 0x23, 0x8f, 0xde, 0x3a, 0x94, 0x06, 0x9e, 0xdf, 0xcd, 0x1e, 0xce, 0x6d, 0xcf, 0xef, 0x62,
0xc1, 0x41, 0x17, 0x00, 0x9c, 0xd0, 0x7b, 0x89, 0x44, 0x54, 0x5f, 0xee, 0x25, 0x7e, 0xb9, 0xb4,
0xb3, 0xa5, 0x38, 0xd8, 0x90, 0x42, 0xcf, 0xa8, 0xce, 0x50, 0x8e, 0xed, 0x8d, 0x4c, 0x67, 0x58,
0xe5, 0x16, 0x1a, 0xad, 0xdf, 0xc5, 0x4c, 0x7e, 0x5c, 0x9f, 0xc9, 0x8f, 0xba, 0x53, 0xde, 0xe9,
0x3b, 0x94, 0x1c, 0x76, 0xae, 0x2b, 0xc7, 0xdc, 0x1f, 0xfd, 0xcb, 0x02, 0x7d, 0x7b, 0x85, 0xf6,
0xa0, 0x44, 0xa7, 0xbe, 0xab, 0xea, 0xcd, 0x3c, 0x19, 0xb5, 0x33, 0xf5, 0x5d, 0x7d, 0x49, 0x56,
0x15, 0x77, 0x80, 0x53, 0xdf, 0xc5, 0x42, 0x3f, 0x9a, 0x40, 0x35, 0x0a, 0x86, 0xc3, 0x5b, 0x8e,
0x3b, 0xc8, 0xa1, 0xf4, 0x60, 0xa5, 0x4a, 0xe3, 0x2d, 0x8a, 0xf3, 0xaa, 0xc8, 0x38, 0xc1, 0xb2,
0x7f, 0x53, 0x86, 0xcc, 0x74, 0x81, 0xc6, 0xe6, 0xc5, 0xa0, 0x95, 0xe3, 0xc5, 0x60, 0x92, 0xfd,
0x0f, 0xbb, 0x1c, 0x44, 0xcf, 0x41, 0x39, 0xe4, 0x7b, 0xa6, 0x22, 0x6c, 0x2d, 0xce, 0xed, 0x62,
0x23, 0x0f, 0xd9, 0x5a, 0x29, 0x6d, 0xee, 0x6c, 0xf1, 0x98, 0x8c, 0xfd, 0x2d, 0x00, 0xee, 0x6b,
0x35, 0xa6, 0xcb, 0x43, 0x7e, 0x23, 0xaf, 0x1d, 0x55, 0x93, 0xba, 0x48, 0xea, 0x9d, 0x04, 0x05,
0x1b, 0x88, 0xe8, 0xfb, 0x16, 0x2c, 0xc7, 0x8e, 0x57, 0x46, 0x94, 0x1f, 0x8a, 0x11, 0x62, 0x66,
0xc4, 0x29, 0x24, 0x9c, 0x41, 0x46, 0x5f, 0x83, 0x1a, 0x65, 0x4e, 0x24, 0x8b, 0x57, 0xe5, 0xbe,
0x13, 0x5e, 0xb2, 0x97, 0x9d, 0x58, 0x09, 0xd6, 0xfa, 0xd0, 0xcb, 0x00, 0x7b, 0x9e, 0xef, 0xd1,
0xbe, 0xd0, 0xbe, 0xf0, 0x60, 0xa5, 0xf1, 0x4a, 0xa2, 0x01, 0x1b, 0xda, 0xec, 0xbf, 0x15, 0x00,
0xc4, 0xcf, 0x0d, 0x4f, 0x5c, 0x3c, 0xac, 0x43, 0x29, 0x22, 0x61, 0x90, 0xcd, 0x5c, 0x5c, 0x02,
0x0b, 0x4e, 0x6a, 0x8e, 0x28, 0xdc, 0xd7, 0x1c, 0x51, 0x3c, 0x76, 0x8e, 0xe0, 0x39, 0x98, 0xf6,
0x77, 0x22, 0x6f, 0xe2, 0x30, 0xb2, 0x4d, 0xa6, 0x2a, 0x91, 0xe9, 0x1c, 0xdc, 0xb9, 0xaa, 0x99,
0x38, 0x2d, 0x7b, 0xe8, 0x08, 0x56, 0xfe, 0x1f, 0x8e, 0x60, 0xef, 0x58, 0xb0, 0xac, 0x3d, 0xfb,
0xc1, 0xfa, 0x9f, 0xa6, 0xed, 0x3e, 0x62, 0xa6, 0xf8, 0xb7, 0x05, 0x2b, 0x71, 0xf7, 0xaa, 0x8a,
0x60, 0x2e, 0x55, 0x2f, 0xf5, 0xb3, 0xa0, 0x78, 0xfc, 0xcf, 0x02, 0x33, 0x61, 0x95, 0x8e, 0x49,
0x58, 0x5f, 0xca, 0xd4, 0xbb, 0x8f, 0xcc, 0xd4, 0x3b, 0x94, 0xf4, 0xe9, 0x53, 0xdf, 0x4d, 0xf7,
0x07, 0xf6, 0xaf, 0x2c, 0x58, 0x8c, 0xd9, 0x37, 0x82, 0xae, 0xe8, 0x9e, 0xa9, 0x08, 0x32, 0x2b,
0xdd, 0x3d, 0xcb, 0x70, 0x90, 0x3c, 0x34, 0x86, 0xaa, 0xdb, 0xf7, 0x86, 0xdd, 0x88, 0xf8, 0x6a,
0x5b, 0x9e, 0xcf, 0x61, 0x8c, 0xe0, 0xf8, 0x3a, 0x14, 0x36, 0x14, 0x00, 0x4e, 0xa0, 0xec, 0xdf,
0x17, 0x61, 0x29, 0x35, 0x73, 0xf0, 0x11, 0x5d, 0xde, 0xd6, 0x77, 0x0c, 0x9b, 0x93, 0x11, 0x7d,
0x57, 0xb3, 0xb0, 0x29, 0xc7, 0xf7, 0x63, 0xe8, 0x4d, 0xa4, 0x8e, 0xec, 0xcf, 0x9b, 0x6b, 0x31,
0x03, 0x6b, 0x19, 0x63, 0xe8, 0x2a, 0xde, 0xf7, 0xd0, 0xf5, 0x53, 0x0b, 0x90, 0x58, 0x02, 0xd7,
0x9c, 0xcc, 0x46, 0x8d, 0x52, 0xbe, 0x7e, 0x3b, 0xab, 0x2c, 0x42, 0x1b, 0x33, 0x50, 0xf8, 0x10,
0x78, 0xe3, 0x1e, 0xb4, 0xfc, 0x48, 0xee, 0x41, 0xed, 0x6f, 0xc2, 0x99, 0x99, 0x8e, 0x43, 0xb5,
0xbc, 0xd6, 0x61, 0x2d, 0x2f, 0x8f, 0xc4, 0x30, 0x1a, 0xfb, 0x72, 0x83, 0xaa, 0x3a, 0x12, 0x77,
0x38, 0x11, 0x4b, 0x1e, 0xef, 0x83, 0xbb, 0xd1, 0x14, 0x8f, 0x65, 0x2f, 0x59, 0xd5, 0xe8, 0x9b,
0x82, 0x8a, 0x15, 0xd7, 0xfe, 0x5e, 0x01, 0x96, 0x52, 0x55, 0x30, 0x35, 0xb2, 0x58, 0xc7, 0x8e,
0x2c, 0x79, 0x1a, 0x83, 0x5e, 0x87, 0x45, 0x2a, 0x8e, 0x62, 0xe4, 0x30, 0xd2, 0x9b, 0xe6, 0x70,
0x13, 0xdd, 0x31, 0xd4, 0xb5, 0x4f, 0x1f, 0xec, 0xaf, 0x2d, 0x9a, 0x14, 0x9c, 0x82, 0xb3, 0x7f,
0x59, 0x80, 0xc7, 0x0e, 0xe9, 0x08, 0xd0, 0x1d, 0xf3, 0x76, 0x40, 0x8e, 0x8f, 0x2f, 0xe4, 0x10,
0x9e, 0x2a, 0x91, 0xca, 0x5f, 0xbe, 0x87, 0xdd, 0x0d, 0xdc, 0xe7, 0xf4, 0xb8, 0x07, 0xe5, 0x7e,
0x10, 0x0c, 0xe2, 0x31, 0x71, 0x9e, 0x82, 0xa0, 0x87, 0x9b, 0x76, 0x8d, 0xef, 0x26, 0x7f, 0xa7,
0x58, 0xaa, 0xb7, 0xdf, 0xb3, 0x20, 0xe5, 0x45, 0x34, 0x82, 0x32, 0xd7, 0x32, 0xcd, 0xe1, 0x4f,
0x98, 0xa9, 0xf7, 0x12, 0xd7, 0x29, 0xf1, 0xc5, 0x23, 0x96, 0x28, 0xc8, 0x83, 0x12, 0x37, 0x44,
0x75, 0xfa, 0xdb, 0x39, 0xa1, 0xf1, 0x25, 0xca, 0xc1, 0x82, 0x3f, 0x61, 0x01, 0x61, 0x5f, 0x84,
0x33, 0x33, 0x16, 0xf1, 0x90, 0xdf, 0x0b, 0xe2, 0x1f, 0x7f, 0x46, 0xc8, 0x5f, 0xe1, 0x44, 0x2c,
0x79, 0xbc, 0x7e, 0x9c, 0xce, 0xaa, 0x47, 0x3f, 0xb3, 0xe0, 0x0c, 0xcd, 0xea, 0x7b, 0x28, 0x5e,
0xfb, 0xb0, 0x32, 0x6a, 0xd6, 0x7c, 0x3c, 0x6b, 0x01, 0xdf, 0xd1, 0xec, 0x75, 0x29, 0x8f, 0x3d,
0xcf, 0xa7, 0xc4, 0x1d, 0x47, 0xf1, 0x42, 0x93, 0xd8, 0xdb, 0x52, 0x74, 0x9c, 0x48, 0xf0, 0xf1,
0x55, 0x5e, 0xd7, 0xdf, 0xd0, 0x8d, 0x62, 0x32, 0xbe, 0x76, 0x12, 0x0e, 0x36, 0xa4, 0xd0, 0x39,
0xa8, 0xba, 0x24, 0x62, 0x9b, 0xbc, 0x3d, 0xe2, 0x79, 0x61, 0x51, 0xce, 0x59, 0x1b, 0x8a, 0x86,
0x13, 0x2e, 0xfa, 0x28, 0x2c, 0x0c, 0xc8, 0x54, 0x08, 0x96, 0x84, 0x60, 0x9d, 0x57, 0xfc, 0x6d,
0x49, 0xc2, 0x31, 0x0f, 0xd9, 0x50, 0x71, 0x1d, 0x21, 0x55, 0x16, 0x52, 0x20, 0x6e, 0xee, 0x2f,
0x09, 0x21, 0xc5, 0x69, 0x37, 0xef, 0xde, 0x5b, 0x3d, 0xf5, 0xd6, 0xbd, 0xd5, 0x53, 0x6f, 0xdf,
0x5b, 0x3d, 0xf5, 0xc6, 0xc1, 0xaa, 0x75, 0xf7, 0x60, 0xd5, 0x7a, 0xeb, 0x60, 0xd5, 0x7a, 0xfb,
0x60, 0xd5, 0xfa, 0xe7, 0xc1, 0xaa, 0xf5, 0xe3, 0x77, 0x57, 0x4f, 0xbd, 0x5c, 0x8d, 0x5d, 0xfb,
0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf5, 0xd2, 0xa5, 0x7e, 0x8e, 0x27, 0x00, 0x00,
0x66, 0x13, 0xa4, 0x10, 0x01, 0xed, 0x9e, 0xda, 0x99, 0xf6, 0xcc, 0x74, 0x77, 0xba, 0x6a, 0xc6,
0x1a, 0x89, 0x20, 0x23, 0x84, 0xc4, 0x5b, 0x20, 0x84, 0xb8, 0x72, 0xe0, 0x84, 0x90, 0x90, 0x10,
0x27, 0x24, 0x2e, 0x1c, 0x90, 0x8f, 0x39, 0x80, 0x88, 0x02, 0x5a, 0xe1, 0xcd, 0x25, 0x12, 0x07,
0xee, 0x39, 0xa1, 0x7a, 0x74, 0x75, 0x75, 0xcf, 0x2e, 0xbb, 0xf6, 0xb4, 0x0d, 0xb9, 0x75, 0xff,
0xff, 0xdf, 0xff, 0xf7, 0xd7, 0x5f, 0x7f, 0xfd, 0x8f, 0x6a, 0xd8, 0xea, 0x79, 0xac, 0x3f, 0xbe,
0xd5, 0x74, 0x83, 0x51, 0xcb, 0x89, 0x7a, 0x41, 0x18, 0x05, 0xb7, 0xc5, 0xc3, 0xf3, 0x6e, 0xb7,
0x15, 0x0e, 0x7a, 0x2d, 0x27, 0xf4, 0x68, 0xcb, 0x09, 0xc3, 0xa1, 0xe7, 0x3a, 0xcc, 0x0b, 0xfc,
0xd6, 0xe4, 0xbc, 0x33, 0x0c, 0xfb, 0xce, 0xf9, 0x56, 0x8f, 0xf8, 0x24, 0x72, 0x18, 0xe9, 0x36,
0xc3, 0x28, 0x60, 0x01, 0xfa, 0x6c, 0xa2, 0xaa, 0x19, 0xab, 0x12, 0x0f, 0x5f, 0x73, 0xbb, 0xcd,
0x70, 0xd0, 0x6b, 0x72, 0x55, 0x4d, 0x43, 0x55, 0x33, 0x56, 0x75, 0xf6, 0x79, 0xc3, 0x8a, 0x5e,
0xd0, 0x0b, 0x5a, 0x42, 0xe3, 0xad, 0xf1, 0x9e, 0x78, 0x13, 0x2f, 0xe2, 0x49, 0x22, 0x9d, 0xfd,
0xd4, 0xe0, 0x22, 0x6d, 0x7a, 0x01, 0xb7, 0x6d, 0xe4, 0xb8, 0x7d, 0xcf, 0x27, 0xd1, 0x34, 0x31,
0x76, 0x44, 0x98, 0xd3, 0x9a, 0xcc, 0xd8, 0x77, 0xb6, 0x75, 0xd4, 0x57, 0xd1, 0xd8, 0x67, 0xde,
0x88, 0xcc, 0x7c, 0xf0, 0xe9, 0xe3, 0x3e, 0xa0, 0x6e, 0x9f, 0x8c, 0x9c, 0x99, 0xef, 0x3e, 0x79,
0xd4, 0x77, 0x63, 0xe6, 0x0d, 0x5b, 0x9e, 0xcf, 0x28, 0x8b, 0xb2, 0x1f, 0xd9, 0x7f, 0xb7, 0x00,
0x2e, 0x85, 0xe1, 0x4e, 0x14, 0xdc, 0x26, 0x2e, 0x43, 0x5f, 0x87, 0x2a, 0x5f, 0x47, 0xd7, 0x61,
0x4e, 0xc3, 0x5a, 0xb7, 0xce, 0xd5, 0x2f, 0x7c, 0xa2, 0x29, 0xd5, 0x36, 0x4d, 0xb5, 0x89, 0x5f,
0xb9, 0x74, 0x73, 0x72, 0xbe, 0x79, 0xf3, 0x16, 0xff, 0xfe, 0x3a, 0x61, 0x4e, 0x1b, 0xdd, 0xdb,
0x5f, 0x3b, 0x75, 0xb0, 0xbf, 0x06, 0x09, 0x0d, 0x6b, 0xad, 0x68, 0x00, 0x25, 0x1a, 0x12, 0xb7,
0x51, 0x10, 0xda, 0xb7, 0x9a, 0x0f, 0xbd, 0x7b, 0xcd, 0xc4, 0xec, 0x4e, 0x48, 0xdc, 0xf6, 0xa2,
0x82, 0x2d, 0xf1, 0x37, 0x2c, 0x40, 0xec, 0xb7, 0x2d, 0x58, 0x4e, 0xc4, 0xae, 0x79, 0x94, 0xa1,
0xd7, 0x66, 0x56, 0xd8, 0x3c, 0xd9, 0x0a, 0xf9, 0xd7, 0x62, 0x7d, 0xa7, 0x15, 0x50, 0x35, 0xa6,
0x18, 0xab, 0xbb, 0x0d, 0x65, 0x8f, 0x91, 0x11, 0x6d, 0x14, 0xd6, 0x8b, 0xe7, 0xea, 0x17, 0x2e,
0xe7, 0xb2, 0xbc, 0xf6, 0x92, 0x42, 0x2c, 0x6f, 0x71, 0xdd, 0x58, 0x42, 0xd8, 0x77, 0x0b, 0xe6,
0xe2, 0xf8, 0xaa, 0xd1, 0xc7, 0x60, 0x81, 0x06, 0xe3, 0xc8, 0x25, 0xb4, 0x61, 0xad, 0x17, 0xcf,
0xd5, 0xda, 0x2b, 0x07, 0xfb, 0x6b, 0xf5, 0x8e, 0x20, 0x61, 0x12, 0x06, 0x14, 0xc7, 0x7c, 0xf4,
0x03, 0x0b, 0x16, 0xbb, 0x84, 0x32, 0xcf, 0x17, 0xb8, 0xb1, 0xc5, 0x5f, 0x9a, 0xcf, 0xe2, 0x98,
0xb8, 0x99, 0x68, 0x6e, 0x3f, 0xa9, 0xac, 0x5f, 0x34, 0x88, 0x14, 0xa7, 0xc0, 0xd1, 0x0b, 0x50,
0xef, 0x12, 0xea, 0x46, 0x5e, 0xc8, 0xdf, 0x1b, 0xc5, 0x75, 0xeb, 0x5c, 0xad, 0xfd, 0x84, 0xfa,
0xb0, 0xbe, 0x99, 0xb0, 0xb0, 0x29, 0x67, 0xff, 0xb9, 0x08, 0x75, 0x03, 0xf5, 0x31, 0x84, 0xef,
0x30, 0x15, 0xbe, 0x2f, 0xe5, 0xe3, 0xad, 0xa3, 0xe2, 0x17, 0x31, 0xa8, 0x50, 0xe6, 0xb0, 0x31,
0x15, 0x1e, 0xa9, 0x5f, 0xb8, 0x96, 0x13, 0x9e, 0xd0, 0xd9, 0x5e, 0x56, 0x88, 0x15, 0xf9, 0x8e,
0x15, 0x16, 0x7a, 0x1d, 0x6a, 0x41, 0xc8, 0xb3, 0x04, 0xdf, 0x8a, 0x92, 0x00, 0xde, 0x9c, 0x03,
0xf8, 0x66, 0xac, 0xab, 0xbd, 0x74, 0xb0, 0xbf, 0x56, 0xd3, 0xaf, 0x38, 0x41, 0xb1, 0x5d, 0x78,
0xd2, 0xb0, 0x6f, 0x23, 0xf0, 0xbb, 0x9e, 0xd8, 0xd0, 0x75, 0x28, 0xb1, 0x69, 0x48, 0xc4, 0x66,
0xd6, 0x12, 0x17, 0xed, 0x4e, 0x43, 0x82, 0x05, 0x87, 0x87, 0xfc, 0x88, 0x50, 0xea, 0xf4, 0x88,
0xd8, 0x93, 0x5a, 0x7b, 0x45, 0x09, 0x2d, 0x5c, 0x97, 0x64, 0x1c, 0xf3, 0xed, 0xd7, 0xe1, 0xe9,
0xc3, 0x43, 0x14, 0x7d, 0x04, 0x2a, 0x94, 0x44, 0x13, 0x12, 0x29, 0xa0, 0xc4, 0x33, 0x82, 0x8a,
0x15, 0x17, 0xb5, 0xa0, 0xe6, 0x3b, 0x23, 0x42, 0x43, 0xc7, 0x8d, 0xe1, 0xce, 0x28, 0xd1, 0xda,
0x8d, 0x98, 0x81, 0x13, 0x19, 0xfb, 0x1f, 0x16, 0xac, 0x18, 0x98, 0x8f, 0x21, 0x03, 0x0d, 0xd2,
0x19, 0xe8, 0x4a, 0x3e, 0x11, 0x73, 0x44, 0x0a, 0xfa, 0x71, 0x11, 0xce, 0x98, 0x71, 0x25, 0x72,
0x0b, 0xdf, 0x92, 0x88, 0x84, 0xc1, 0xcb, 0xf8, 0x9a, 0x72, 0xa7, 0xde, 0x12, 0x2c, 0xc9, 0x38,
0xe6, 0xf3, 0xfd, 0x0d, 0x1d, 0xd6, 0x57, 0xbe, 0xd4, 0xfb, 0xbb, 0xe3, 0xb0, 0x3e, 0x16, 0x1c,
0x9e, 0x19, 0x88, 0x3f, 0xf1, 0xa2, 0xc0, 0x1f, 0x11, 0x9f, 0x65, 0x33, 0xc3, 0xe5, 0x84, 0x85,
0x4d, 0x39, 0xf4, 0x45, 0x58, 0x66, 0x4e, 0xd4, 0x23, 0x0c, 0x93, 0x89, 0x47, 0xe3, 0x40, 0xae,
0xb5, 0x9f, 0x56, 0x5f, 0x2e, 0xef, 0xa6, 0xb8, 0x38, 0x23, 0x8d, 0x7e, 0x6f, 0xc1, 0x33, 0x6e,
0x30, 0x0a, 0x03, 0x9f, 0xf8, 0x6c, 0xc7, 0x89, 0x9c, 0x11, 0x61, 0x24, 0xba, 0x39, 0x21, 0x51,
0xe4, 0x75, 0x09, 0x6d, 0x94, 0x85, 0x77, 0xaf, 0xcf, 0xe1, 0xdd, 0x8d, 0x19, 0xed, 0xed, 0x67,
0x95, 0x71, 0xcf, 0x6c, 0x1c, 0x8d, 0x8c, 0xff, 0x9b, 0x59, 0xf6, 0x1f, 0x0b, 0xa9, 0x78, 0xeb,
0xc4, 0x49, 0x44, 0x6c, 0x8c, 0x8a, 0xb6, 0xbc, 0x92, 0x88, 0xd0, 0x69, 0x1c, 0x15, 0x59, 0x65,
0x14, 0x16, 0xfa, 0xae, 0x25, 0x52, 0x7a, 0x7c, 0xc4, 0x54, 0xc2, 0x7c, 0x04, 0xe5, 0xc5, 0xac,
0x12, 0x31, 0x11, 0x9b, 0xd0, 0x3c, 0x1e, 0x43, 0x59, 0x24, 0x55, 0xf8, 0xe8, 0x78, 0x54, 0xb5,
0x13, 0xc7, 0x7c, 0xfb, 0x97, 0x95, 0x74, 0x40, 0xcb, 0x84, 0xf8, 0x53, 0x0b, 0x4e, 0x73, 0xaf,
0x3b, 0x91, 0x47, 0x03, 0x1f, 0x13, 0x3a, 0x1e, 0x32, 0xe5, 0xcc, 0xed, 0x39, 0x23, 0xc0, 0x54,
0xd9, 0x6e, 0x28, 0xbb, 0x4e, 0x67, 0x39, 0x78, 0x06, 0x1e, 0x31, 0x58, 0xe8, 0x7b, 0x94, 0x05,
0xd1, 0x54, 0x9d, 0xf4, 0x79, 0x5a, 0xa9, 0x4d, 0x12, 0x0e, 0x83, 0x29, 0x3f, 0x38, 0x5b, 0xfe,
0x5e, 0x90, 0xf8, 0xe7, 0xaa, 0x44, 0xc0, 0x31, 0x14, 0xfa, 0x96, 0x05, 0x10, 0xc6, 0x61, 0xc7,
0xab, 0xd2, 0x23, 0x38, 0x05, 0xba, 0x00, 0x6b, 0x12, 0xc5, 0x06, 0x28, 0x0a, 0xa0, 0xd2, 0x27,
0xce, 0x90, 0xf5, 0x55, 0x6d, 0x7a, 0x71, 0x0e, 0xf8, 0xab, 0x42, 0x51, 0xb6, 0x1e, 0x4a, 0x2a,
0x56, 0x30, 0xe8, 0x3b, 0x16, 0x2c, 0xeb, 0x52, 0xc5, 0x65, 0x49, 0xa3, 0x3c, 0x77, 0xf7, 0x7a,
0x33, 0xa5, 0xb0, 0x8d, 0x78, 0x4e, 0x4a, 0xd3, 0x70, 0x06, 0x14, 0x7d, 0xdb, 0x02, 0x70, 0xe3,
0xd2, 0x48, 0x1b, 0x15, 0xe1, 0xfc, 0x9b, 0xf9, 0x9c, 0x28, 0x5d, 0x72, 0x13, 0xf7, 0x6b, 0x12,
0xc5, 0x06, 0xac, 0xfd, 0x8e, 0x05, 0x4f, 0x19, 0x1f, 0x7e, 0xd9, 0x61, 0x6e, 0xff, 0xf2, 0x84,
0xe7, 0xdc, 0xed, 0x54, 0xb1, 0xfe, 0x8c, 0x59, 0xac, 0xdf, 0xdb, 0x5f, 0xfb, 0xe8, 0x51, 0xe3,
0xc9, 0x1d, 0xae, 0xa1, 0x29, 0x54, 0x18, 0x75, 0xfd, 0x0d, 0xa8, 0x1b, 0x36, 0xab, 0xf4, 0x91,
0x57, 0x35, 0xd3, 0x39, 0xc3, 0x20, 0x62, 0x13, 0xcf, 0xfe, 0x6b, 0x01, 0x16, 0x36, 0x86, 0x63,
0xca, 0x48, 0x74, 0xe2, 0xee, 0x60, 0x1d, 0x4a, 0xbc, 0xf2, 0x67, 0x8b, 0x19, 0x6f, 0x0c, 0xb0,
0xe0, 0xa0, 0x10, 0x2a, 0x6e, 0xe0, 0xef, 0x79, 0x3d, 0xd5, 0xcf, 0x5d, 0x9d, 0xe7, 0xe4, 0x48,
0xeb, 0x36, 0x84, 0xbe, 0xc4, 0x26, 0xf9, 0x8e, 0x15, 0x0e, 0xfa, 0x91, 0x05, 0x2b, 0x6e, 0xe0,
0xfb, 0xc4, 0x4d, 0x82, 0xb7, 0x34, 0x77, 0xef, 0xba, 0x91, 0xd6, 0xd8, 0xfe, 0x80, 0x42, 0x5f,
0xc9, 0x30, 0x70, 0x16, 0xdb, 0xfe, 0x5d, 0x01, 0x96, 0x52, 0x96, 0xa3, 0xe7, 0xa0, 0x3a, 0xa6,
0x24, 0x12, 0x9e, 0x93, 0xfe, 0xd5, 0xed, 0xcd, 0xcb, 0x8a, 0x8e, 0xb5, 0x04, 0x97, 0x0e, 0x1d,
0x4a, 0xef, 0x04, 0x51, 0x57, 0xf9, 0x59, 0x4b, 0xef, 0x28, 0x3a, 0xd6, 0x12, 0xbc, 0x79, 0xb8,
0x45, 0x9c, 0x88, 0x44, 0xbb, 0xc1, 0x80, 0xcc, 0x8c, 0x15, 0xed, 0x84, 0x85, 0x4d, 0x39, 0xe1,
0x34, 0x36, 0xa4, 0x1b, 0x43, 0x8f, 0xf8, 0x4c, 0x9a, 0x99, 0x83, 0xd3, 0x76, 0xaf, 0x75, 0x4c,
0x8d, 0x89, 0xd3, 0x32, 0x0c, 0x9c, 0xc5, 0xb6, 0xff, 0x62, 0x41, 0x5d, 0x39, 0xed, 0x31, 0x74,
0x90, 0xbd, 0x74, 0x07, 0xd9, 0x9e, 0x3f, 0x46, 0x8f, 0xe8, 0x1e, 0x7f, 0x53, 0x84, 0x99, 0x4a,
0x87, 0xbe, 0xca, 0x73, 0x1c, 0xa7, 0x91, 0xee, 0xa5, 0xb8, 0xc8, 0x7e, 0xfc, 0x64, 0xab, 0xdb,
0xf5, 0x46, 0xc4, 0x4c, 0x5f, 0xb1, 0x16, 0x6c, 0x68, 0x44, 0x77, 0xad, 0x04, 0x60, 0x37, 0x50,
0x79, 0x25, 0xdf, 0x96, 0x68, 0xc6, 0x84, 0xdd, 0x00, 0x1b, 0x98, 0xe8, 0x73, 0x7a, 0xaa, 0x2b,
0x8b, 0x80, 0xb4, 0xd3, 0x73, 0xd8, 0x7b, 0xa9, 0x06, 0x20, 0x33, 0x9b, 0x4d, 0xa1, 0x16, 0x91,
0x78, 0xc6, 0x97, 0x15, 0x60, 0x9e, 0x24, 0x82, 0x95, 0x2e, 0x79, 0x8c, 0xf5, 0x2c, 0x13, 0x93,
0x29, 0x4e, 0xd0, 0xec, 0x1f, 0x5a, 0x80, 0x66, 0xcb, 0x35, 0x9f, 0x89, 0x74, 0x47, 0xaa, 0x0e,
0xb0, 0xd6, 0xa3, 0xc5, 0x71, 0x22, 0x73, 0x82, 0x34, 0xf9, 0x2c, 0x94, 0x27, 0xce, 0x70, 0x4c,
0xd4, 0x81, 0xd5, 0xd1, 0xf3, 0x0a, 0x27, 0x62, 0xc9, 0xb3, 0xff, 0x64, 0x41, 0x36, 0xdd, 0x88,
0x4c, 0x2d, 0x3d, 0x9b, 0xcd, 0xd4, 0x69, 0x2f, 0x9e, 0x7c, 0x68, 0x44, 0xaf, 0x41, 0xdd, 0x61,
0x8c, 0x8c, 0x42, 0x26, 0x02, 0xb2, 0xf8, 0xc0, 0x01, 0xb9, 0xcc, 0x23, 0xe1, 0x7a, 0xd0, 0xf5,
0xf6, 0x3c, 0x11, 0x8c, 0xa6, 0x3a, 0xfb, 0xdd, 0x22, 0x2c, 0xa7, 0x9b, 0x2f, 0x34, 0x86, 0x8a,
0x68, 0x76, 0xe4, 0x15, 0x4e, 0xee, 0xdd, 0x95, 0x76, 0x89, 0x20, 0x51, 0xac, 0xc0, 0x78, 0x62,
0x8d, 0xe2, 0x51, 0x29, 0x93, 0x58, 0xf5, 0x90, 0xa4, 0x25, 0x8e, 0x1d, 0x8f, 0x8a, 0xff, 0x97,
0xe3, 0x11, 0x4f, 0x2e, 0x5d, 0xe1, 0x6d, 0xb1, 0x97, 0xa5, 0x87, 0x4f, 0x2e, 0x9b, 0x5a, 0x0b,
0x36, 0x34, 0xa2, 0xb3, 0x50, 0xf0, 0xba, 0xe2, 0x54, 0x17, 0xdb, 0xa0, 0x64, 0x0b, 0x5b, 0x9b,
0xb8, 0xe0, 0x75, 0x6d, 0x0a, 0x8b, 0x66, 0xb7, 0x79, 0xe2, 0x58, 0xfd, 0x3c, 0x2c, 0xc9, 0xa7,
0x4d, 0xc2, 0x1c, 0x6f, 0x48, 0xd5, 0xee, 0x3c, 0xa5, 0xc4, 0x97, 0x3a, 0x26, 0x13, 0xa7, 0x65,
0xed, 0x5f, 0x14, 0x00, 0xae, 0x06, 0xc1, 0x40, 0x61, 0xc6, 0x47, 0xcf, 0x3a, 0xf2, 0xe8, 0xad,
0x43, 0x69, 0xe0, 0xf9, 0xdd, 0xec, 0xe1, 0xdc, 0xf6, 0xfc, 0x2e, 0x16, 0x1c, 0x74, 0x01, 0xc0,
0x09, 0xbd, 0x57, 0x48, 0x44, 0x93, 0x9b, 0x3a, 0xed, 0x97, 0x4b, 0x3b, 0x5b, 0x8a, 0x83, 0x0d,
0x29, 0xf4, 0x9c, 0xea, 0x0c, 0xe5, 0x0c, 0xde, 0xc8, 0x74, 0x86, 0x55, 0x6e, 0xa1, 0xd1, 0xfa,
0x5d, 0xcc, 0xe4, 0xc7, 0xf5, 0x99, 0xfc, 0x98, 0x74, 0xca, 0x3b, 0x7d, 0x87, 0x92, 0xc3, 0xce,
0x75, 0xe5, 0x98, 0xcb, 0xa0, 0x7f, 0x59, 0x90, 0x5c, 0x45, 0xa1, 0x3d, 0x28, 0xd1, 0xa9, 0xef,
0xaa, 0x7a, 0x33, 0x4f, 0x46, 0xed, 0x4c, 0x7d, 0x37, 0xb9, 0xf1, 0xaa, 0x8a, 0x0b, 0xbd, 0xa9,
0xef, 0x62, 0xa1, 0x1f, 0x4d, 0xa0, 0x1a, 0x05, 0xc3, 0xe1, 0x2d, 0xc7, 0x1d, 0xe4, 0x50, 0x7a,
0xb0, 0x52, 0x95, 0xe0, 0x2d, 0x8a, 0xf3, 0xaa, 0xc8, 0x58, 0x63, 0xd9, 0xbf, 0x2d, 0x43, 0x66,
0xba, 0x40, 0x63, 0xf3, 0x96, 0xcf, 0xca, 0xf1, 0x96, 0x4f, 0x67, 0xff, 0xc3, 0x6e, 0xfa, 0xd0,
0x0b, 0x50, 0x0e, 0xf9, 0x9e, 0xa9, 0x08, 0x5b, 0x8b, 0x73, 0xbb, 0xd8, 0xc8, 0x43, 0xb6, 0x56,
0x4a, 0x9b, 0x3b, 0x5b, 0x3c, 0x26, 0x63, 0x7f, 0x13, 0x80, 0xfb, 0x5a, 0x8d, 0xe9, 0xf2, 0x90,
0xdf, 0xc8, 0x6b, 0x47, 0xd5, 0xa4, 0x2e, 0x92, 0x7a, 0x47, 0xa3, 0x60, 0x03, 0x11, 0x7d, 0xdf,
0x82, 0xe5, 0xd8, 0xf1, 0xca, 0x88, 0xf2, 0x23, 0x31, 0x42, 0xcc, 0x8c, 0x38, 0x85, 0x84, 0x33,
0xc8, 0xe8, 0x2b, 0x50, 0xa3, 0xcc, 0x89, 0x64, 0xf1, 0xaa, 0x3c, 0x70, 0xc2, 0xd3, 0x7b, 0xd9,
0x89, 0x95, 0xe0, 0x44, 0x1f, 0x7a, 0x15, 0x60, 0xcf, 0xf3, 0x3d, 0xda, 0x17, 0xda, 0x17, 0x1e,
0xae, 0x34, 0x5e, 0xd1, 0x1a, 0xb0, 0xa1, 0xcd, 0xfe, 0x5b, 0x01, 0x40, 0xfc, 0xb2, 0xf0, 0xc4,
0xc5, 0xc3, 0x3a, 0x94, 0x22, 0x12, 0x06, 0xd9, 0xcc, 0xc5, 0x25, 0xb0, 0xe0, 0xa4, 0xe6, 0x88,
0xc2, 0x03, 0xcd, 0x11, 0xc5, 0x63, 0xe7, 0x08, 0x9e, 0x83, 0x69, 0x7f, 0x27, 0xf2, 0x26, 0x0e,
0x23, 0xdb, 0x64, 0xaa, 0x12, 0x59, 0x92, 0x83, 0x3b, 0x57, 0x13, 0x26, 0x4e, 0xcb, 0x1e, 0x3a,
0x82, 0x95, 0xff, 0x87, 0x23, 0xd8, 0xdb, 0x16, 0x2c, 0x27, 0x9e, 0x7d, 0x7f, 0xfd, 0x14, 0x4b,
0xec, 0x3e, 0x62, 0xa6, 0xf8, 0xb7, 0x05, 0x2b, 0x71, 0xf7, 0xaa, 0x8a, 0x60, 0x2e, 0x55, 0x2f,
0x75, 0xf3, 0x5f, 0x3c, 0xfe, 0xe6, 0xdf, 0x4c, 0x58, 0xa5, 0x63, 0x12, 0xd6, 0x17, 0x32, 0xf5,
0xee, 0x43, 0x33, 0xf5, 0x0e, 0xe9, 0x3e, 0x7d, 0xea, 0xbb, 0xe9, 0xfe, 0xc0, 0xfe, 0xb5, 0x05,
0x8b, 0x31, 0xfb, 0x46, 0xd0, 0x15, 0xdd, 0x33, 0x15, 0x41, 0x66, 0xa5, 0xbb, 0x67, 0x19, 0x0e,
0x92, 0x87, 0xc6, 0x50, 0x75, 0xfb, 0xde, 0xb0, 0x1b, 0x11, 0x5f, 0x6d, 0xcb, 0x8b, 0x39, 0x8c,
0x11, 0x1c, 0x3f, 0x09, 0x85, 0x0d, 0x05, 0x80, 0x35, 0x94, 0xfd, 0x87, 0x22, 0x2c, 0xa5, 0x66,
0x0e, 0x3e, 0xa2, 0xcb, 0xab, 0xf7, 0x8e, 0x61, 0xb3, 0x1e, 0xd1, 0x77, 0x13, 0x16, 0x36, 0xe5,
0xf8, 0x7e, 0x0c, 0xbd, 0x89, 0xd4, 0x91, 0xfd, 0x13, 0x73, 0x2d, 0x66, 0xe0, 0x44, 0xc6, 0x18,
0xba, 0x8a, 0x0f, 0x3c, 0x74, 0xfd, 0xcc, 0x02, 0x24, 0x96, 0xc0, 0x35, 0xeb, 0xd9, 0xa8, 0x51,
0xca, 0xd7, 0x6f, 0x67, 0x95, 0x45, 0x68, 0x63, 0x06, 0x0a, 0x1f, 0x02, 0x6f, 0xdc, 0x83, 0x96,
0x1f, 0xcb, 0x3d, 0xa8, 0xfd, 0x0d, 0x38, 0x33, 0xd3, 0x71, 0xa8, 0x96, 0xd7, 0x3a, 0xac, 0xe5,
0xe5, 0x91, 0x18, 0x46, 0x63, 0x5f, 0x6e, 0x50, 0x35, 0x89, 0xc4, 0x1d, 0x4e, 0xc4, 0x92, 0xc7,
0xfb, 0xe0, 0x6e, 0x34, 0xc5, 0x63, 0xd9, 0x4b, 0x56, 0x13, 0xf4, 0x4d, 0x41, 0xc5, 0x8a, 0x6b,
0x7f, 0xaf, 0x00, 0x4b, 0xa9, 0x2a, 0x98, 0x1a, 0x59, 0xac, 0x63, 0x47, 0x96, 0x3c, 0x8d, 0x41,
0x6f, 0xc0, 0x22, 0x15, 0x47, 0x31, 0x72, 0x18, 0xe9, 0x4d, 0x73, 0xb8, 0x89, 0xee, 0x18, 0xea,
0xda, 0xa7, 0x0f, 0xf6, 0xd7, 0x16, 0x4d, 0x0a, 0x4e, 0xc1, 0xd9, 0xbf, 0x2a, 0xc0, 0x13, 0x87,
0x74, 0x04, 0xe8, 0x8e, 0x79, 0x3b, 0x20, 0xc7, 0xc7, 0x97, 0x72, 0x08, 0x4f, 0x95, 0x48, 0xe5,
0xff, 0xdb, 0xc3, 0xee, 0x06, 0x1e, 0x70, 0x7a, 0xdc, 0x83, 0x72, 0x3f, 0x08, 0x06, 0xf1, 0x98,
0x38, 0x4f, 0x41, 0x48, 0x86, 0x9b, 0x76, 0x8d, 0xef, 0x26, 0x7f, 0xa7, 0x58, 0xaa, 0xb7, 0xdf,
0xb5, 0x20, 0xe5, 0x45, 0x34, 0x82, 0x32, 0xd7, 0x32, 0xcd, 0xe1, 0x4f, 0x98, 0xa9, 0xf7, 0x12,
0xd7, 0x29, 0xf1, 0xc5, 0x23, 0x96, 0x28, 0xc8, 0x83, 0x12, 0x37, 0x44, 0x75, 0xfa, 0xdb, 0x39,
0xa1, 0xf1, 0x25, 0xca, 0xc1, 0x82, 0x3f, 0x61, 0x01, 0x61, 0x5f, 0x84, 0x33, 0x33, 0x16, 0xf1,
0x90, 0xdf, 0x0b, 0xe2, 0x1f, 0x7f, 0x46, 0xc8, 0x5f, 0xe1, 0x44, 0x2c, 0x79, 0xbc, 0x7e, 0x9c,
0xce, 0xaa, 0x47, 0x3f, 0xb7, 0xe0, 0x0c, 0xcd, 0xea, 0x7b, 0x24, 0x5e, 0xfb, 0xa0, 0x32, 0x6a,
0xd6, 0x7c, 0x3c, 0x6b, 0x01, 0xdf, 0xd1, 0xec, 0x75, 0x29, 0x8f, 0x3d, 0xcf, 0xa7, 0xc4, 0x1d,
0x47, 0xf1, 0x42, 0x75, 0xec, 0x6d, 0x29, 0x3a, 0xd6, 0x12, 0x7c, 0x7c, 0x95, 0xd7, 0xf5, 0x37,
0x92, 0x46, 0x51, 0x8f, 0xaf, 0x1d, 0xcd, 0xc1, 0x86, 0x14, 0x3a, 0x07, 0x55, 0x97, 0x44, 0x6c,
0x93, 0xb7, 0x47, 0x3c, 0x2f, 0x2c, 0xca, 0x39, 0x6b, 0x43, 0xd1, 0xb0, 0xe6, 0xa2, 0x0f, 0xc3,
0xc2, 0x80, 0x4c, 0x85, 0x60, 0x49, 0x08, 0xd6, 0x79, 0xc5, 0xdf, 0x96, 0x24, 0x1c, 0xf3, 0x90,
0x0d, 0x15, 0xd7, 0x11, 0x52, 0x65, 0x21, 0x05, 0xe2, 0xe6, 0xfe, 0x92, 0x10, 0x52, 0x9c, 0x76,
0xf3, 0xde, 0xfd, 0xd5, 0x53, 0x6f, 0xde, 0x5f, 0x3d, 0xf5, 0xd6, 0xfd, 0xd5, 0x53, 0x77, 0x0f,
0x56, 0xad, 0x7b, 0x07, 0xab, 0xd6, 0x9b, 0x07, 0xab, 0xd6, 0x5b, 0x07, 0xab, 0xd6, 0x3f, 0x0f,
0x56, 0xad, 0x9f, 0xbc, 0xb3, 0x7a, 0xea, 0xd5, 0x6a, 0xec, 0xda, 0xff, 0x04, 0x00, 0x00, 0xff,
0xff, 0xb1, 0x18, 0x0d, 0xa9, 0x53, 0x27, 0x00, 0x00,
}

View File

@@ -34,7 +34,7 @@ message AppProjectList {
// AppProjectSpec represents
message AppProjectSpec {
// SourceRepos contains list of git repository URLs which can be used for deployment
repeated string sourceRepos = 1;
repeated string sources = 1;
// Destinations contains list of destinations available for deployment
repeated ApplicationDestination destinations = 2;
@@ -85,24 +85,21 @@ message ApplicationList {
// ApplicationSource contains information about github repository, path within repository and target application environment.
message ApplicationSource {
// RepoURL is the git repository URL of the application manifests
// RepoURL is the repository URL containing the ksonnet application.
optional string repoURL = 1;
// Path is a directory path within the repository containing a
// Path is a directory path within repository which contains ksonnet application.
optional string path = 2;
// Environment is a ksonnet application environment name
// Environment is a ksonnet application environment name.
optional string environment = 3;
// TargetRevision defines the commit, tag, or branch in which to sync the application to.
// If omitted, will sync to HEAD
optional string targetRevision = 4;
// ComponentParameterOverrides are a list of parameter override values
// Environment parameter override values
repeated ComponentParameter componentParameterOverrides = 5;
// ValuesFiles is a list of Helm values files to use when generating a template
repeated string valuesFiles = 6;
}
// ApplicationSpec represents desired application state. Contains link to repository with application definition and additional parameters link definition revision.

View File

@@ -2,7 +2,6 @@ package v1alpha1
import (
"encoding/json"
"reflect"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -222,26 +221,24 @@ type ApplicationSpec struct {
// ComponentParameter contains information about component parameter value
type ComponentParameter struct {
Component string `json:"component,omitempty" protobuf:"bytes,1,opt,name=component"`
Component string `json:"component" protobuf:"bytes,1,opt,name=component"`
Name string `json:"name" protobuf:"bytes,2,opt,name=name"`
Value string `json:"value" protobuf:"bytes,3,opt,name=value"`
}
// ApplicationSource contains information about github repository, path within repository and target application environment.
type ApplicationSource struct {
// RepoURL is the git repository URL of the application manifests
// RepoURL is the repository URL containing the ksonnet application.
RepoURL string `json:"repoURL" protobuf:"bytes,1,opt,name=repoURL"`
// Path is a directory path within the repository containing a
// Path is a directory path within repository which contains ksonnet application.
Path string `json:"path" protobuf:"bytes,2,opt,name=path"`
// Environment is a ksonnet application environment name
Environment string `json:"environment,omitempty" protobuf:"bytes,3,opt,name=environment"`
// Environment is a ksonnet application environment name.
Environment string `json:"environment" protobuf:"bytes,3,opt,name=environment"`
// TargetRevision defines the commit, tag, or branch in which to sync the application to.
// If omitted, will sync to HEAD
TargetRevision string `json:"targetRevision,omitempty" protobuf:"bytes,4,opt,name=targetRevision"`
// ComponentParameterOverrides are a list of parameter override values
// Environment parameter override values
ComponentParameterOverrides []ComponentParameter `json:"componentParameterOverrides,omitempty" protobuf:"bytes,5,opt,name=componentParameterOverrides"`
// ValuesFiles is a list of Helm values files to use when generating a template
ValuesFiles []string `json:"valuesFiles,omitempty" protobuf:"bytes,6,opt,name=valuesFiles"`
}
// ApplicationDestination contains deployment destination information
@@ -446,7 +443,7 @@ type AppProject struct {
// AppProjectSpec represents
type AppProjectSpec struct {
// SourceRepos contains list of git repository URLs which can be used for deployment
SourceRepos []string `json:"sourceRepos" protobuf:"bytes,1,name=sourceRepos"`
SourceRepos []string `json:"sources" protobuf:"bytes,1,name=destination"`
// Destinations contains list of destinations available for deployment
Destinations []ApplicationDestination `json:"destinations" protobuf:"bytes,2,name=destination"`
@@ -510,7 +507,10 @@ func (condition *ApplicationCondition) IsError() bool {
// Equals compares two instances of ApplicationSource and return true if instances are equal.
func (source ApplicationSource) Equals(other ApplicationSource) bool {
return reflect.DeepEqual(source, other)
return source.TargetRevision == other.TargetRevision &&
source.RepoURL == other.RepoURL &&
source.Path == other.Path &&
source.Environment == other.Environment
}
func (spec ApplicationSpec) BelongsToDefaultProject() bool {

View File

@@ -205,11 +205,6 @@ func (in *ApplicationSource) DeepCopyInto(out *ApplicationSource) {
*out = make([]ComponentParameter, len(*in))
copy(*out, *in)
}
if in.ValuesFiles != nil {
in, out := &in.ValuesFiles, &out.ValuesFiles
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

View File

@@ -7,20 +7,15 @@ import (
"io/ioutil"
"os"
"path"
"regexp"
"strings"
"time"
"github.com/ksonnet/ksonnet/pkg/app"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/helm"
ksutil "github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/kube"
)
@@ -30,14 +25,6 @@ const (
DefaultRepoCacheExpiration = 24 * time.Hour
)
type AppSourceType string
const (
AppSourceKsonnet AppSourceType = "ksonnet"
AppSourceHelm AppSourceType = "helm"
AppSourceDirectory AppSourceType = "directory"
)
// Service implements ManifestService interface
type Service struct {
repoLock *util.KeyLock
@@ -130,7 +117,7 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
var res ManifestResponse
if git.IsCommitSHA(q.Revision) {
cacheKey := manifestCacheKey(q.Revision, q)
err := s.cache.Get(cacheKey, res)
err := s.cache.Get(cacheKey, &res)
if err == nil {
log.Infof("manifest cache hit: %s", cacheKey)
return &res, nil
@@ -166,53 +153,35 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani
return nil, err
}
appPath := path.Join(appRepoPath, q.Path)
ksApp, err := ksutil.NewKsonnetApp(appPath)
if err != nil {
return nil, fmt.Errorf("unable to load application from %s: %v", appPath, err)
}
genRes, err := generateManifests(appPath, q)
params, err := ksApp.ListEnvParams(q.Environment)
if err != nil {
return nil, fmt.Errorf("Failed to list ksonnet app params: %v", err)
}
if q.ComponentParameterOverrides != nil {
for _, override := range q.ComponentParameterOverrides {
err = ksApp.SetComponentParams(q.Environment, override.Component, override.Name, override.Value)
if err != nil {
return nil, err
}
}
}
appSpec := ksApp.App()
env, err := appSpec.Environment(q.Environment)
if err != nil {
return nil, fmt.Errorf("environment '%s' does not exist in ksonnet app", q.Environment)
}
targetObjs, err := ksApp.Show(q.Environment)
if err != nil {
return nil, err
}
res = *genRes
res.Revision = commitSHA
err = s.cache.Set(&cache.Item{
Key: cacheKey,
Object: res,
Expiration: DefaultRepoCacheExpiration,
})
if err != nil {
log.Warnf("manifest cache set error %s: %v", cacheKey, err)
}
return &res, nil
}
// generateManifests generates manifests from a path
func generateManifests(appPath string, q *ManifestRequest) (*ManifestResponse, error) {
var targetObjs []*unstructured.Unstructured
var params []*v1alpha1.ComponentParameter
var env *app.EnvironmentSpec
var err error
appSourceType := IdentifyAppSourceTypeByAppDir(appPath)
switch appSourceType {
case AppSourceKsonnet:
targetObjs, params, env, err = ksShow(appPath, q.Environment, q.ComponentParameterOverrides)
case AppSourceHelm:
h := helm.NewHelmApp(appPath)
targetObjs, err = h.Template(q.AppLabel, q.ValueFiles, q.ComponentParameterOverrides)
if err != nil {
return nil, err
}
params, err = h.GetParameters(q.ValueFiles)
if err != nil {
return nil, err
}
case AppSourceDirectory:
targetObjs, err = findManifests(appPath)
}
if err != nil {
return nil, err
}
// TODO(jessesuen): we need to sort objects based on their dependency order of creation
manifests := make([]string, len(targetObjs))
for i, target := range targetObjs {
if q.AppLabel != "" {
@@ -228,13 +197,20 @@ func generateManifests(appPath string, q *ManifestRequest) (*ManifestResponse, e
manifests[i] = string(manifestStr)
}
res := ManifestResponse{
res = ManifestResponse{
Revision: commitSHA,
Manifests: manifests,
Namespace: env.Destination.Namespace,
Server: env.Destination.Server,
Params: params,
}
if env != nil {
res.Namespace = env.Destination.Namespace
res.Server = env.Destination.Server
err = s.cache.Set(&cache.Item{
Key: cacheKey,
Object: &res,
Expiration: DefaultRepoCacheExpiration,
})
if err != nil {
log.Warnf("manifest cache set error %s: %v", cacheKey, err)
}
return &res, nil
}
@@ -244,28 +220,6 @@ func tempRepoPath(repo string) string {
return path.Join(os.TempDir(), strings.Replace(repo, "/", "_", -1))
}
// IdentifyAppSourceTypeByAppDir examines a directory and determines its application source type
func IdentifyAppSourceTypeByAppDir(appDirPath string) AppSourceType {
if pathExists(path.Join(appDirPath, "app.yaml")) {
return AppSourceKsonnet
}
if pathExists(path.Join(appDirPath, "Chart.yaml")) {
return AppSourceHelm
}
return AppSourceDirectory
}
// IdentifyAppSourceTypeByAppPath determines application source type by app file path
func IdentifyAppSourceTypeByAppPath(appFilePath string) AppSourceType {
if strings.HasSuffix(appFilePath, "app.yaml") {
return AppSourceKsonnet
}
if strings.HasSuffix(appFilePath, "Chart.yaml") {
return AppSourceHelm
}
return AppSourceDirectory
}
// checkoutRevision is a convenience function to initialize a repo, fetch, and checkout a revision
func checkoutRevision(gitClient git.Client, revision string) error {
err := gitClient.Fetch()
@@ -285,92 +239,9 @@ func checkoutRevision(gitClient git.Client, revision string) error {
func manifestCacheKey(commitSHA string, q *ManifestRequest) string {
pStr, _ := json.Marshal(q.ComponentParameterOverrides)
valuesFiles := strings.Join(q.ValueFiles, ",")
return fmt.Sprintf("mfst|%s|%s|%s|%s|%s|%s", q.AppLabel, q.Path, q.Environment, commitSHA, string(pStr), valuesFiles)
return fmt.Sprintf("mfst|%s|%s|%s|%s", q.Path, q.Environment, commitSHA, string(pStr))
}
func listDirCacheKey(commitSHA string, q *ListDirRequest) string {
return fmt.Sprintf("ldir|%s|%s", q.Path, commitSHA)
}
// ksShow runs `ks show` in an app directory after setting any component parameter overrides
func ksShow(appPath, envName string, overrides []*v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, []*v1alpha1.ComponentParameter, *app.EnvironmentSpec, error) {
ksApp, err := ksutil.NewKsonnetApp(appPath)
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to load application from %s: %v", appPath, err)
}
params, err := ksApp.ListEnvParams(envName)
if err != nil {
return nil, nil, nil, fmt.Errorf("Failed to list ksonnet app params: %v", err)
}
if overrides != nil {
for _, override := range overrides {
err = ksApp.SetComponentParams(envName, override.Component, override.Name, override.Value)
if err != nil {
return nil, nil, nil, err
}
}
}
appSpec := ksApp.App()
env, err := appSpec.Environment(envName)
if err != nil {
return nil, nil, nil, fmt.Errorf("environment '%s' does not exist in ksonnet app", envName)
}
targetObjs, err := ksApp.Show(envName)
if err != nil {
return nil, nil, nil, err
}
return targetObjs, params, env, nil
}
var manifestFile = regexp.MustCompile(`^.*\.(yaml|yml|json)$`)
// findManifests looks at all yaml files in a directory and unmarshals them into a list of unstructured objects
func findManifests(appPath string) ([]*unstructured.Unstructured, error) {
files, err := ioutil.ReadDir(appPath)
if err != nil {
return nil, fmt.Errorf("Failed to read dir %s: %v", appPath, err)
}
var objs []*unstructured.Unstructured
for _, f := range files {
if f.IsDir() || !manifestFile.MatchString(f.Name()) {
continue
}
out, err := ioutil.ReadFile(path.Join(appPath, f.Name()))
if err != nil {
return nil, err
}
if strings.HasSuffix(f.Name(), ".json") {
var obj unstructured.Unstructured
err = json.Unmarshal(out, &obj)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshal '%s': %v", f.Name(), err)
}
objs = append(objs, &obj)
} else {
yamlObjs, err := kube.SplitYAML(string(out))
if err != nil {
if len(yamlObjs) > 0 {
// If we get here, we had a multiple objects in a single YAML file which had some
// valid k8s objects, but errors parsing others (within the same file). It's very
// likely the user messed up a portion of the YAML, so report on that.
return nil, fmt.Errorf("Failed to unmarshal '%s': %v", f.Name(), err)
}
// Otherwise, it might be a unrelated YAML file which we will ignore
continue
}
objs = append(objs, yamlObjs...)
}
}
return objs, nil
}
// pathExists reports whether the named file or directory exists.
func pathExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}

View File

@@ -49,7 +49,6 @@ type ManifestRequest struct {
Environment string `protobuf:"bytes,4,opt,name=environment,proto3" json:"environment,omitempty"`
AppLabel string `protobuf:"bytes,5,opt,name=appLabel,proto3" json:"appLabel,omitempty"`
ComponentParameterOverrides []*github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.ComponentParameter `protobuf:"bytes,6,rep,name=componentParameterOverrides" json:"componentParameterOverrides,omitempty"`
ValueFiles []string `protobuf:"bytes,7,rep,name=valueFiles" json:"valueFiles,omitempty"`
}
func (m *ManifestRequest) Reset() { *m = ManifestRequest{} }
@@ -99,13 +98,6 @@ func (m *ManifestRequest) GetComponentParameterOverrides() []*github_com_argopro
return nil
}
func (m *ManifestRequest) GetValueFiles() []string {
if m != nil {
return m.ValueFiles
}
return nil
}
type ManifestResponse struct {
Manifests []string `protobuf:"bytes,1,rep,name=manifests" json:"manifests,omitempty"`
Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
@@ -468,21 +460,6 @@ func (m *ManifestRequest) MarshalTo(dAtA []byte) (int, error) {
i += n
}
}
if len(m.ValueFiles) > 0 {
for _, s := range m.ValueFiles {
dAtA[i] = 0x3a
i++
l = len(s)
for l >= 1<<7 {
dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
l >>= 7
i++
}
dAtA[i] = uint8(l)
i++
i += copy(dAtA[i:], s)
}
}
return i, nil
}
@@ -724,12 +701,6 @@ func (m *ManifestRequest) Size() (n int) {
n += 1 + l + sovRepository(uint64(l))
}
}
if len(m.ValueFiles) > 0 {
for _, s := range m.ValueFiles {
l = len(s)
n += 1 + l + sovRepository(uint64(l))
}
}
return n
}
@@ -1043,35 +1014,6 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ValueFiles", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ValueFiles = append(m.ValueFiles, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
@@ -1840,41 +1782,40 @@ var (
func init() { proto.RegisterFile("reposerver/repository/repository.proto", fileDescriptorRepository) }
var fileDescriptorRepository = []byte{
// 576 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xae, 0xc9, 0x5f, 0xb3, 0x41, 0xb4, 0xac, 0x22, 0x64, 0x39, 0x51, 0x64, 0x59, 0x02, 0xe5,
0x82, 0xad, 0x84, 0x0b, 0x17, 0x84, 0x04, 0x85, 0x0a, 0xa9, 0x55, 0x91, 0x39, 0xc1, 0x05, 0x6d,
0x9c, 0xc1, 0x59, 0x62, 0xef, 0x2e, 0xbb, 0x1b, 0x4b, 0xbc, 0x03, 0x12, 0x0f, 0xc0, 0x0b, 0x71,
0xe4, 0x11, 0x50, 0x6e, 0x7d, 0x0b, 0xe4, 0x8d, 0x1d, 0x3b, 0x6d, 0xd4, 0x0b, 0xaa, 0xd4, 0xdb,
0xcc, 0x37, 0xb3, 0xf3, 0xcd, 0x7e, 0x33, 0x1a, 0xf4, 0x44, 0x82, 0xe0, 0x0a, 0x64, 0x06, 0x32,
0x30, 0x26, 0xd5, 0x5c, 0x7e, 0xaf, 0x99, 0xbe, 0x90, 0x5c, 0x73, 0x8c, 0x2a, 0xc4, 0xe9, 0xc7,
0x3c, 0xe6, 0x06, 0x0e, 0x72, 0x6b, 0x93, 0xe1, 0x0c, 0x63, 0xce, 0xe3, 0x04, 0x02, 0x22, 0x68,
0x40, 0x18, 0xe3, 0x9a, 0x68, 0xca, 0x99, 0x2a, 0xa2, 0xde, 0xf2, 0xb9, 0xf2, 0x29, 0x37, 0xd1,
0x88, 0x4b, 0x08, 0xb2, 0x49, 0x10, 0x03, 0x03, 0x49, 0x34, 0xcc, 0x8b, 0x9c, 0x77, 0x31, 0xd5,
0x8b, 0xd5, 0xcc, 0x8f, 0x78, 0x1a, 0x10, 0x69, 0x28, 0xbe, 0x1a, 0xe3, 0x69, 0x34, 0x0f, 0xc4,
0x32, 0xce, 0x1f, 0xab, 0x80, 0x08, 0x91, 0xd0, 0xc8, 0x14, 0x0f, 0xb2, 0x09, 0x49, 0xc4, 0x82,
0x5c, 0x2b, 0xe5, 0xfd, 0x68, 0xa0, 0xa3, 0x73, 0xc2, 0xe8, 0x17, 0x50, 0x3a, 0x84, 0x6f, 0x2b,
0x50, 0x1a, 0x7f, 0x44, 0xcd, 0xfc, 0x13, 0xb6, 0xe5, 0x5a, 0xe3, 0xde, 0xf4, 0x8d, 0x5f, 0xb1,
0xf9, 0x25, 0x9b, 0x31, 0x3e, 0x47, 0x73, 0x5f, 0x2c, 0x63, 0x3f, 0x67, 0xf3, 0x6b, 0x6c, 0x7e,
0xc9, 0xe6, 0x87, 0x5b, 0x2d, 0x42, 0x53, 0x12, 0x3b, 0xe8, 0x50, 0x42, 0x46, 0x15, 0xe5, 0xcc,
0xbe, 0xe7, 0x5a, 0xe3, 0x6e, 0xb8, 0xf5, 0x31, 0x46, 0x4d, 0x41, 0xf4, 0xc2, 0x6e, 0x18, 0xdc,
0xd8, 0xd8, 0x45, 0x3d, 0x60, 0x19, 0x95, 0x9c, 0xa5, 0xc0, 0xb4, 0xdd, 0x34, 0xa1, 0x3a, 0x94,
0x57, 0x24, 0x42, 0x9c, 0x91, 0x19, 0x24, 0x76, 0x6b, 0x53, 0xb1, 0xf4, 0xf1, 0x4f, 0x0b, 0x0d,
0x22, 0x9e, 0x0a, 0xce, 0x80, 0xe9, 0xf7, 0x44, 0x92, 0x14, 0x34, 0xc8, 0x8b, 0x0c, 0xa4, 0xa4,
0x73, 0x50, 0x76, 0xdb, 0x6d, 0x8c, 0x7b, 0xd3, 0xf3, 0xff, 0xf8, 0xe0, 0xeb, 0x6b, 0xd5, 0xc3,
0x9b, 0x18, 0xf1, 0x08, 0xa1, 0x8c, 0x24, 0x2b, 0x78, 0x4b, 0x13, 0x50, 0x76, 0xc7, 0x6d, 0x8c,
0xbb, 0x61, 0x0d, 0xf1, 0x2e, 0x2d, 0x74, 0x5c, 0x8d, 0x43, 0x09, 0xce, 0x14, 0xe0, 0x21, 0xea,
0xa6, 0x05, 0xa6, 0x6c, 0xcb, 0xbc, 0xa9, 0x80, 0x3c, 0xca, 0x48, 0x0a, 0x4a, 0x90, 0x08, 0x0a,
0x4d, 0x2b, 0x00, 0x3f, 0x42, 0xed, 0xcd, 0xd2, 0x16, 0xb2, 0x16, 0xde, 0xce, 0x20, 0x9a, 0x57,
0x06, 0x01, 0xa8, 0x2d, 0xf2, 0xd6, 0x95, 0xdd, 0xba, 0x0d, 0x81, 0x8a, 0xe2, 0xde, 0x2f, 0x0b,
0x3d, 0x38, 0xa3, 0x4a, 0x9f, 0x50, 0x79, 0xf7, 0x36, 0xcf, 0x73, 0xd1, 0x61, 0x3e, 0x92, 0xbc,
0x41, 0xdc, 0x47, 0x2d, 0xaa, 0x21, 0x2d, 0xc5, 0xdf, 0x38, 0xa6, 0xff, 0x53, 0xd0, 0x79, 0xd6,
0x1d, 0xec, 0xff, 0x31, 0x3a, 0xda, 0x36, 0x57, 0xec, 0x11, 0x46, 0xcd, 0x39, 0xd1, 0xc4, 0x74,
0x77, 0x3f, 0x34, 0xf6, 0xf4, 0xd2, 0x42, 0x0f, 0x2b, 0xae, 0x0f, 0x20, 0x33, 0x1a, 0x01, 0xbe,
0x40, 0xc7, 0xa7, 0xc5, 0xa1, 0x28, 0xb7, 0x11, 0x0f, 0xfc, 0xda, 0xad, 0xbb, 0x72, 0x32, 0x9c,
0xe1, 0xfe, 0xe0, 0x86, 0xd8, 0x3b, 0xc0, 0x2f, 0x50, 0xa7, 0x18, 0x35, 0x76, 0xea, 0xa9, 0xbb,
0xf3, 0x77, 0xfa, 0xf5, 0x58, 0x29, 0xbf, 0x77, 0x80, 0x4f, 0x50, 0xa7, 0xf8, 0xcc, 0xee, 0xf3,
0x5d, 0xf9, 0x9d, 0xc1, 0xde, 0x58, 0xd9, 0xc4, 0xab, 0x97, 0xbf, 0xd7, 0x23, 0xeb, 0xcf, 0x7a,
0x64, 0xfd, 0x5d, 0x8f, 0xac, 0x4f, 0x93, 0x9b, 0x8e, 0xe8, 0xde, 0x63, 0x3f, 0x6b, 0x9b, 0x9b,
0xf9, 0xec, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x12, 0x81, 0x49, 0x46, 0x0c, 0x06, 0x00, 0x00,
// 560 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0x4d, 0x8b, 0xd3, 0x40,
0x18, 0xde, 0x6c, 0x3f, 0x76, 0x3b, 0x15, 0x77, 0x1d, 0x8a, 0x84, 0xb4, 0x94, 0x10, 0x50, 0x7a,
0x31, 0xa1, 0xf5, 0xe2, 0x45, 0x04, 0x5d, 0x5d, 0x84, 0x5d, 0x56, 0xe2, 0x49, 0x2f, 0x32, 0x4d,
0x5f, 0xd3, 0xb1, 0xcd, 0xcc, 0x38, 0x33, 0x1b, 0xf0, 0x57, 0xf8, 0x03, 0xfc, 0x43, 0x1e, 0xfd,
0x09, 0xd2, 0xdb, 0x82, 0x3f, 0x42, 0x32, 0x9d, 0x34, 0xe9, 0x6e, 0xd9, 0x8b, 0x08, 0x7b, 0x7b,
0xbf, 0xf2, 0x3c, 0xcf, 0x3c, 0x79, 0x79, 0xd1, 0x63, 0x09, 0x82, 0x2b, 0x90, 0x39, 0xc8, 0xc8,
0x84, 0x54, 0x73, 0xf9, 0xad, 0x16, 0x86, 0x42, 0x72, 0xcd, 0x31, 0xaa, 0x2a, 0x5e, 0x2f, 0xe5,
0x29, 0x37, 0xe5, 0xa8, 0x88, 0xd6, 0x13, 0xde, 0x20, 0xe5, 0x3c, 0x5d, 0x42, 0x44, 0x04, 0x8d,
0x08, 0x63, 0x5c, 0x13, 0x4d, 0x39, 0x53, 0xb6, 0x1b, 0x2c, 0x9e, 0xa9, 0x90, 0x72, 0xd3, 0x4d,
0xb8, 0x84, 0x28, 0x1f, 0x47, 0x29, 0x30, 0x90, 0x44, 0xc3, 0xcc, 0xce, 0xbc, 0x4d, 0xa9, 0x9e,
0x5f, 0x4e, 0xc3, 0x84, 0x67, 0x11, 0x91, 0x86, 0xe2, 0x8b, 0x09, 0x9e, 0x24, 0xb3, 0x48, 0x2c,
0xd2, 0xe2, 0x63, 0x15, 0x11, 0x21, 0x96, 0x34, 0x31, 0xe0, 0x51, 0x3e, 0x26, 0x4b, 0x31, 0x27,
0x37, 0xa0, 0x82, 0x3f, 0xfb, 0xe8, 0xe8, 0x9c, 0x30, 0xfa, 0x19, 0x94, 0x8e, 0xe1, 0xeb, 0x25,
0x28, 0x8d, 0x3f, 0xa0, 0x66, 0xf1, 0x08, 0xd7, 0xf1, 0x9d, 0x51, 0x77, 0xf2, 0x3a, 0xac, 0xd8,
0xc2, 0x92, 0xcd, 0x04, 0x9f, 0x92, 0x59, 0x28, 0x16, 0x69, 0x58, 0xb0, 0x85, 0x35, 0xb6, 0xb0,
0x64, 0x0b, 0xe3, 0x8d, 0x17, 0xb1, 0x81, 0xc4, 0x1e, 0x3a, 0x94, 0x90, 0x53, 0x45, 0x39, 0x73,
0xf7, 0x7d, 0x67, 0xd4, 0x89, 0x37, 0x39, 0xc6, 0xa8, 0x29, 0x88, 0x9e, 0xbb, 0x0d, 0x53, 0x37,
0x31, 0xf6, 0x51, 0x17, 0x58, 0x4e, 0x25, 0x67, 0x19, 0x30, 0xed, 0x36, 0x4d, 0xab, 0x5e, 0x2a,
0x10, 0x89, 0x10, 0x67, 0x64, 0x0a, 0x4b, 0xb7, 0xb5, 0x46, 0x2c, 0x73, 0xfc, 0xdd, 0x41, 0xfd,
0x84, 0x67, 0x82, 0x33, 0x60, 0xfa, 0x1d, 0x91, 0x24, 0x03, 0x0d, 0xf2, 0x22, 0x07, 0x29, 0xe9,
0x0c, 0x94, 0xdb, 0xf6, 0x1b, 0xa3, 0xee, 0xe4, 0xfc, 0x1f, 0x1e, 0xf8, 0xea, 0x06, 0x7a, 0x7c,
0x1b, 0x63, 0x70, 0xe5, 0xa0, 0xe3, 0xca, 0x6e, 0x25, 0x38, 0x53, 0x80, 0x07, 0xa8, 0x93, 0xd9,
0x9a, 0x72, 0x1d, 0xbf, 0x31, 0xea, 0xc4, 0x55, 0xa1, 0xe8, 0x32, 0x92, 0x81, 0x12, 0x24, 0x01,
0xeb, 0x59, 0x55, 0xc0, 0x0f, 0x51, 0x7b, 0xbd, 0x94, 0xd6, 0x36, 0x9b, 0x6d, 0x19, 0xdd, 0xbc,
0x66, 0x34, 0xa0, 0xb6, 0x28, 0xa4, 0x29, 0xb7, 0xf5, 0x3f, 0x0c, 0xb0, 0xe0, 0xc1, 0x0f, 0x07,
0xdd, 0x3f, 0xa3, 0x4a, 0x9f, 0x50, 0x79, 0xf7, 0x36, 0x2b, 0xf0, 0xd1, 0xe1, 0x1b, 0xba, 0x84,
0x42, 0x20, 0xee, 0xa1, 0x16, 0xd5, 0x90, 0x95, 0xe6, 0xaf, 0x13, 0xa3, 0xff, 0x14, 0x74, 0x31,
0x75, 0x07, 0xf5, 0x3f, 0x42, 0x47, 0x1b, 0x71, 0x76, 0x8f, 0x30, 0x6a, 0xce, 0x88, 0x26, 0x46,
0xdd, 0xbd, 0xd8, 0xc4, 0x93, 0x2b, 0x07, 0x3d, 0xa8, 0xb8, 0xde, 0x83, 0xcc, 0x69, 0x02, 0xf8,
0x02, 0x1d, 0x9f, 0xda, 0x43, 0x50, 0x6e, 0x23, 0xee, 0x87, 0xb5, 0x5b, 0x76, 0xed, 0x24, 0x78,
0x83, 0xdd, 0xcd, 0x35, 0x71, 0xb0, 0x87, 0x9f, 0xa3, 0x03, 0xfb, 0xab, 0xb1, 0x57, 0x1f, 0xdd,
0xfe, 0xff, 0x5e, 0xaf, 0xde, 0x2b, 0xed, 0x0f, 0xf6, 0xf0, 0x09, 0x3a, 0xb0, 0x8f, 0xd9, 0xfe,
0x7c, 0xdb, 0x7e, 0xaf, 0xbf, 0xb3, 0x57, 0x8a, 0x78, 0xf9, 0xe2, 0xe7, 0x6a, 0xe8, 0xfc, 0x5a,
0x0d, 0x9d, 0xdf, 0xab, 0xa1, 0xf3, 0x71, 0x7c, 0xdb, 0x91, 0xdc, 0x79, 0xcc, 0xa7, 0x6d, 0x73,
0x13, 0x9f, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x01, 0x96, 0x09, 0x12, 0xec, 0x05, 0x00, 0x00,
}

View File

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

View File

@@ -1,19 +0,0 @@
package repository
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGenerateManifestInDir(t *testing.T) {
q := ManifestRequest{}
res1, err := generateManifests("../../manifests/components", &q)
assert.Nil(t, err)
assert.True(t, len(res1.Manifests) == 16) // update this value if we add/remove manifests
// this will test concatenated manifests to verify we split YAMLs correctly
res2, err := generateManifests("../../manifests", &q)
assert.Nil(t, err)
assert.True(t, len(res2.Manifests) == len(res1.Manifests))
}

View File

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

View File

@@ -32,7 +32,6 @@ import (
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/rbac"
"github.com/argoproj/argo-cd/util/session"
)
// Server provides a Application service
@@ -45,7 +44,6 @@ type Server struct {
appComparator controller.AppStateManager
enf *rbac.Enforcer
projectLock *util.KeyLock
auditLogger *argo.AuditLogger
}
// NewServer returns a new instance of the Application service
@@ -68,7 +66,6 @@ func NewServer(
appComparator: controller.NewAppStateManager(db, appclientset, repoClientset, namespace),
enf: enf,
projectLock: projectLock,
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"),
}
}
@@ -131,10 +128,6 @@ func (s *Server) Create(ctx context.Context, q *ApplicationCreateRequest) (*appv
}
}
}
if err == nil {
s.logEvent(out, ctx, argo.EventReasonResourceCreated, "create")
}
return out, err
}
@@ -173,7 +166,6 @@ func (s *Server) GetManifests(ctx context.Context, q *ApplicationManifestQuery)
Revision: revision,
ComponentParameterOverrides: overrides,
AppLabel: a.Name,
ValueFiles: a.Spec.Source.ValuesFiles,
})
if err != nil {
return nil, err
@@ -214,41 +206,24 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *ApplicationResourceE
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications/events", "get", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
var (
kubeClientset kubernetes.Interface
fieldSelector string
namespace string
)
// There are two places where we get events. If we are getting application events, we query
// our own cluster. If it is events on a resource on an external cluster, then we query the
// external cluster using its rest.Config
if q.ResourceName == "" && q.ResourceUID == "" {
kubeClientset = s.kubeclientset
namespace = a.Namespace
fieldSelector = fields.SelectorFromSet(map[string]string{
"involvedObject.name": a.Name,
"involvedObject.uid": string(a.UID),
"involvedObject.namespace": a.Namespace,
}).String()
} else {
var config *rest.Config
config, namespace, err = s.getApplicationClusterConfig(*q.Name)
if err != nil {
return nil, err
}
kubeClientset, err = kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
fieldSelector = fields.SelectorFromSet(map[string]string{
"involvedObject.name": q.ResourceName,
"involvedObject.uid": q.ResourceUID,
"involvedObject.namespace": namespace,
}).String()
config, namespace, err := s.getApplicationClusterConfig(*q.Name)
if err != nil {
return nil, err
}
kubeClientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
fieldSelector := fields.SelectorFromSet(map[string]string{
"involvedObject.name": q.ResourceName,
"involvedObject.uid": q.ResourceUID,
"involvedObject.namespace": namespace,
}).String()
log.Infof("Querying for resource events with field selector: %s", fieldSelector)
opts := metav1.ListOptions{FieldSelector: fieldSelector}
return kubeClientset.CoreV1().Events(namespace).List(opts)
}
@@ -276,10 +251,6 @@ func (s *Server) Update(ctx context.Context, q *ApplicationUpdateRequest) (*appv
// throws an error is passed override is invalid
// if passed override and old overrides are invalid, throws error, old overrides not dropped
func (s *Server) removeInvalidOverrides(a *appv1.Application, q *ApplicationUpdateSpecRequest) (*ApplicationUpdateSpecRequest, error) {
if a.Spec.Source.Environment == "" {
// this method is only valid for ksonnet apps
return q, nil
}
oldParams := argo.ParamToMap(a.Spec.Source.ComponentParameterOverrides)
validAppSet := argo.ParamToMap(a.Status.Parameters)
@@ -289,7 +260,7 @@ func (s *Server) removeInvalidOverrides(a *appv1.Application, q *ApplicationUpda
if !argo.CheckValidParam(validAppSet, param) {
alreadySet := argo.CheckValidParam(oldParams, param)
if !alreadySet {
return nil, status.Errorf(codes.InvalidArgument, "Parameter '%s' in '%s' does not exist in ksonnet app", param.Name, param.Component)
return nil, status.Errorf(codes.InvalidArgument, "Parameter '%s' in '%s' does not exist in ksonnet", param.Name, param.Component)
}
} else {
params = append(params, param)
@@ -322,23 +293,14 @@ func (s *Server) UpdateSpec(ctx context.Context, q *ApplicationUpdateSpecRequest
if err != nil {
return nil, err
}
for {
a.Spec = q.Spec
_, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(a)
if err == nil {
if err != nil {
s.logEvent(a, ctx, argo.EventReasonResourceUpdated, "update")
}
return &q.Spec, nil
}
if !apierr.IsConflict(err) {
return nil, err
}
a, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*q.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
patch, err := json.Marshal(map[string]appv1.ApplicationSpec{
"spec": q.Spec,
})
if err != nil {
return nil, err
}
_, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Patch(*q.Name, types.MergePatchType, patch)
return &q.Spec, err
}
// Delete removes an application and all associated resources
@@ -393,7 +355,6 @@ func (s *Server) Delete(ctx context.Context, q *ApplicationDeleteRequest) (*Appl
return nil, err
}
s.logEvent(a, ctx, argo.EventReasonResourceDeleted, "delete")
return &ApplicationResponse{}, nil
}
@@ -427,7 +388,6 @@ func (s *Server) Watch(q *ApplicationQuery, ws ApplicationService_WatchServer) e
case <-ws.Context().Done():
w.Stop()
case <-done:
w.Stop()
}
return nil
}
@@ -448,7 +408,7 @@ func (s *Server) validateApp(ctx context.Context, spec *appv1.ApplicationSpec) e
return err
}
if len(conditions) > 0 {
return status.Errorf(codes.InvalidArgument, "application spec is invalid: %s", argo.FormatAppConditions(conditions))
return status.Errorf(codes.InvalidArgument, "application spec is invalid: \n%s", argo.FormatAppConditions(conditions))
}
return nil
}
@@ -610,7 +570,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *ApplicationSyncRequest) (*ap
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "sync", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
return s.setAppOperation(ctx, *syncReq.Name, "sync", func(app *appv1.Application) (*appv1.Operation, error) {
return s.setAppOperation(ctx, *syncReq.Name, func(app *appv1.Application) (*appv1.Operation, error) {
syncOp := appv1.SyncOperation{
Revision: syncReq.Revision,
Prune: syncReq.Prune,
@@ -631,7 +591,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *ApplicationRollbackR
if !s.enf.EnforceClaims(ctx.Value("claims"), "applications", "rollback", appRBACName(*a)) {
return nil, grpc.ErrPermissionDenied
}
return s.setAppOperation(ctx, *rollbackReq.Name, "rollback", func(app *appv1.Application) (*appv1.Operation, error) {
return s.setAppOperation(ctx, *rollbackReq.Name, func(app *appv1.Application) (*appv1.Operation, error) {
return &appv1.Operation{
Rollback: &appv1.RollbackOperation{
ID: rollbackReq.ID,
@@ -642,7 +602,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *ApplicationRollbackR
})
}
func (s *Server) setAppOperation(ctx context.Context, appName string, operationName string, operationCreator func(app *appv1.Application) (*appv1.Operation, error)) (*appv1.Application, error) {
func (s *Server) setAppOperation(ctx context.Context, appName string, operationCreator func(app *appv1.Application) (*appv1.Operation, error)) (*appv1.Application, error) {
for {
a, err := s.Get(ctx, &ApplicationQuery{Name: &appName})
if err != nil {
@@ -661,9 +621,6 @@ func (s *Server) setAppOperation(ctx context.Context, appName string, operationN
if err != nil && apierr.IsConflict(err) {
log.Warnf("Failed to set operation for app '%s' due to update conflict. Retrying again...", appName)
} else {
if err == nil {
s.logEvent(a, ctx, argo.EventReasonResourceUpdated, operationName)
}
return a, err
}
}
@@ -695,13 +652,7 @@ func (s *Server) TerminateOperation(ctx context.Context, termOpReq *OperationTer
a, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(*termOpReq.Name, metav1.GetOptions{})
if err != nil {
return nil, err
} else {
s.logEvent(a, ctx, argo.EventReasonResourceUpdated, "terminateop")
}
}
return nil, status.Errorf(codes.Internal, "Failed to terminate app. Too many conflicts")
}
func (s *Server) logEvent(a *appv1.Application, ctx context.Context, reason string, action string) {
s.auditLogger.LogAppEvent(a, argo.EventInfo{Reason: reason, Action: action, Username: session.Username(ctx)}, v1.EventTypeNormal)
}

View File

@@ -64,14 +64,6 @@ version: 0.0.1
}
}
func fakeListDirResponse() *repository.FileList {
return &repository.FileList{
Items: []string{
"some/path/app.yaml",
},
}
}
// return an ApplicationServiceServer which returns fake data
func newTestAppServer() ApplicationServiceServer {
kubeclientset := fake.NewSimpleClientset()
@@ -88,7 +80,6 @@ func newTestAppServer() ApplicationServiceServer {
mockRepoServiceClient := mockreposerver.RepositoryServiceClient{}
mockRepoServiceClient.On("GetFile", mock.Anything, mock.Anything).Return(fakeFileResponse(), nil)
mockRepoServiceClient.On("ListDir", mock.Anything, mock.Anything).Return(fakeListDirResponse(), nil)
mockRepoClient := &mockrepo.Clientset{}
mockRepoClient.On("NewRepositoryClient").Return(&fakeCloser{}, &mockRepoServiceClient, nil)
@@ -111,7 +102,7 @@ func TestCreateApp(t *testing.T) {
Spec: appsv1.ApplicationSpec{
Source: appsv1.ApplicationSource{
RepoURL: fakeRepoURL,
Path: "some/path",
Path: ".",
Environment: "default",
TargetRevision: "HEAD",
},

View File

@@ -14,12 +14,9 @@ import (
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/rbac"
"github.com/argoproj/argo-cd/util/session"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// Server provides a Project service
@@ -27,14 +24,12 @@ type Server struct {
ns string
enf *rbac.Enforcer
appclientset appclientset.Interface
auditLogger *argo.AuditLogger
projectLock *util.KeyLock
}
// NewServer returns a new instance of the Project service
func NewServer(ns string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, enf *rbac.Enforcer, projectLock *util.KeyLock) *Server {
auditLogger := argo.NewAuditLogger(ns, kubeclientset, "argocd-server")
return &Server{enf: enf, appclientset: appclientset, ns: ns, projectLock: projectLock, auditLogger: auditLogger}
func NewServer(ns string, appclientset appclientset.Interface, enf *rbac.Enforcer, projectLock *util.KeyLock) *Server {
return &Server{enf: enf, appclientset: appclientset, ns: ns, projectLock: projectLock}
}
// Create a new project.
@@ -49,11 +44,7 @@ func (s *Server) Create(ctx context.Context, q *ProjectCreateRequest) (*v1alpha1
if err != nil {
return nil, err
}
res, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Create(q.Project)
if err == nil {
s.logEvent(res, ctx, argo.EventReasonResourceCreated, "create")
}
return res, err
return s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Create(q.Project)
}
// List returns list of projects
@@ -196,11 +187,7 @@ func (s *Server) Update(ctx context.Context, q *ProjectUpdateRequest) (*v1alpha1
codes.InvalidArgument, "following source repos are used by one or more application and cannot be removed: %s", strings.Join(removedSrcUsed, ";"))
}
res, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(q.Project)
if err == nil {
s.logEvent(res, ctx, argo.EventReasonResourceUpdated, "update")
}
return res, err
return s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(q.Project)
}
// Delete deletes a project
@@ -212,11 +199,6 @@ func (s *Server) Delete(ctx context.Context, q *ProjectQuery) (*EmptyResponse, e
s.projectLock.Lock(q.Name)
defer s.projectLock.Unlock(q.Name)
p, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(q.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
appsList, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).List(metav1.ListOptions{})
if err != nil {
return nil, err
@@ -225,13 +207,5 @@ func (s *Server) Delete(ctx context.Context, q *ProjectQuery) (*EmptyResponse, e
if len(apps) > 0 {
return nil, status.Errorf(codes.InvalidArgument, "project is referenced by %d applications", len(apps))
}
err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Delete(q.Name, &metav1.DeleteOptions{})
if err == nil {
s.logEvent(p, ctx, argo.EventReasonResourceDeleted, "delete")
}
return &EmptyResponse{}, err
}
func (s *Server) logEvent(p *v1alpha1.AppProject, ctx context.Context, reason string, action string) {
s.auditLogger.LogAppProjEvent(p, argo.EventInfo{Reason: reason, Action: action, Username: session.Username(ctx)}, v1.EventTypeNormal)
return &EmptyResponse{}, s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Delete(q.Name, &metav1.DeleteOptions{})
}

View File

@@ -25,7 +25,6 @@ import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
import _ "google.golang.org/genproto/googleapis/api/annotations"
import _ "k8s.io/api/core/v1"
import _ "k8s.io/apimachinery/pkg/apis/meta/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"
@@ -882,36 +881,36 @@ var (
func init() { proto.RegisterFile("server/project/project.proto", fileDescriptorProject) }
var fileDescriptorProject = []byte{
// 487 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x94, 0xcd, 0x6e, 0x13, 0x31,
0x10, 0xc7, 0x65, 0x28, 0x41, 0x98, 0x4f, 0x59, 0x2d, 0x94, 0xa5, 0x04, 0xb4, 0xa7, 0x2a, 0x52,
0x6d, 0xa5, 0xe5, 0x50, 0x71, 0xe3, 0xa3, 0x42, 0x95, 0x38, 0x40, 0x10, 0x12, 0xe2, 0x52, 0xb9,
0xde, 0x91, 0xe3, 0x26, 0xbb, 0x36, 0xb6, 0xb3, 0x28, 0x42, 0x5c, 0x2a, 0x6e, 0x1c, 0x79, 0x04,
0x6e, 0x3c, 0x09, 0x47, 0x24, 0x5e, 0x00, 0x45, 0x3c, 0x08, 0x5a, 0x67, 0x4d, 0x9a, 0xa6, 0xe1,
0xb4, 0xe2, 0x94, 0xc9, 0xf8, 0xe3, 0xff, 0x9b, 0xf1, 0x7f, 0x07, 0x6f, 0x38, 0xb0, 0x25, 0x58,
0x66, 0xac, 0x3e, 0x02, 0xe1, 0xe3, 0x2f, 0x35, 0x56, 0x7b, 0x4d, 0x2e, 0xd6, 0x7f, 0x93, 0x55,
0xa9, 0xa5, 0x0e, 0x39, 0x56, 0x45, 0xd3, 0xe5, 0x64, 0x43, 0x6a, 0x2d, 0x87, 0xc0, 0xb8, 0x51,
0x8c, 0x17, 0x85, 0xf6, 0xdc, 0x2b, 0x5d, 0xb8, 0x7a, 0x35, 0x1d, 0xec, 0x3a, 0xaa, 0x74, 0x58,
0x15, 0xda, 0x02, 0x2b, 0xbb, 0x4c, 0x42, 0x01, 0x96, 0x7b, 0xc8, 0xea, 0x3d, 0x0f, 0x66, 0x7b,
0x72, 0x2e, 0xfa, 0xaa, 0x00, 0x3b, 0x66, 0x66, 0x20, 0xab, 0x84, 0x63, 0x39, 0x78, 0x7e, 0xd6,
0xa9, 0x7d, 0xa9, 0x7c, 0x7f, 0x74, 0x48, 0x85, 0xce, 0x19, 0xb7, 0x01, 0xec, 0x28, 0x04, 0x5b,
0x22, 0x9b, 0x9d, 0xe6, 0xc6, 0x0c, 0x95, 0x08, 0x48, 0xac, 0xec, 0xf2, 0xa1, 0xe9, 0xf3, 0x85,
0xab, 0xd2, 0xf7, 0x78, 0xf5, 0xc5, 0xb4, 0xc6, 0x27, 0x16, 0xb8, 0x87, 0x1e, 0xbc, 0x1b, 0x81,
0xf3, 0xe4, 0x00, 0xc7, 0xda, 0xd7, 0xd1, 0x7d, 0xb4, 0x79, 0x79, 0x7b, 0x8f, 0xce, 0x44, 0x69,
0x14, 0x0d, 0xc1, 0x81, 0xc8, 0xa8, 0x19, 0x48, 0x5a, 0x89, 0xd2, 0x13, 0xa2, 0x34, 0x8a, 0xd2,
0x47, 0xc6, 0xd4, 0x22, 0xbd, 0x78, 0x6b, 0x9a, 0xe2, 0x2b, 0x75, 0xee, 0xe5, 0x08, 0xec, 0x98,
0x10, 0xbc, 0x52, 0xf0, 0x1c, 0x82, 0xda, 0xa5, 0x5e, 0x88, 0x4f, 0xc0, 0xbd, 0x36, 0xd9, 0xff,
0x84, 0xbb, 0x8e, 0xaf, 0xee, 0xe5, 0xc6, 0x8f, 0x7b, 0xe0, 0x8c, 0x2e, 0x1c, 0x6c, 0x7f, 0xbb,
0x80, 0xaf, 0xd5, 0xbb, 0x5e, 0x81, 0x2d, 0x95, 0x00, 0xf2, 0x19, 0xe1, 0xd6, 0xb4, 0x67, 0xe4,
0x2e, 0x8d, 0xb6, 0x39, 0xab, 0x97, 0x49, 0x33, 0x74, 0xe9, 0x9d, 0xe3, 0x9f, 0xbf, 0xbf, 0x9c,
0x5b, 0x4b, 0x6f, 0x04, 0x47, 0x95, 0xdd, 0xe8, 0x55, 0xf7, 0x10, 0x75, 0xc8, 0x31, 0xc2, 0x2b,
0xcf, 0x95, 0xf3, 0x64, 0xed, 0x34, 0x4b, 0x68, 0x6f, 0xb2, 0xdf, 0x08, 0x43, 0xa5, 0x90, 0xae,
0x07, 0x0e, 0x42, 0x16, 0x38, 0xc8, 0x27, 0x84, 0xcf, 0x3f, 0x83, 0xa5, 0x0c, 0x0d, 0xf5, 0xe1,
0x5e, 0xd0, 0xbf, 0x4d, 0x6e, 0x9d, 0xd6, 0x67, 0x1f, 0x2a, 0xd7, 0x7c, 0x24, 0x5f, 0x11, 0x6e,
0x4d, 0x0d, 0xb3, 0xf8, 0x32, 0x73, 0x46, 0x6a, 0x8a, 0x68, 0x27, 0x10, 0x6d, 0x25, 0x9b, 0x8b,
0x44, 0x51, 0xbe, 0xfa, 0x94, 0x33, 0xee, 0x39, 0x0d, 0x88, 0xd5, 0x8b, 0xbd, 0xc1, 0xad, 0xa7,
0x30, 0x04, 0x0f, 0xcb, 0xda, 0x75, 0xf3, 0x6f, 0x7a, 0xce, 0x8b, 0xb1, 0xfe, 0xce, 0xb2, 0xfa,
0x1f, 0xef, 0x7e, 0x9f, 0xb4, 0xd1, 0x8f, 0x49, 0x1b, 0xfd, 0x9a, 0xb4, 0xd1, 0xdb, 0xce, 0xbf,
0x86, 0xc5, 0xfc, 0xf4, 0x3b, 0x6c, 0x85, 0xa1, 0xb0, 0xf3, 0x27, 0x00, 0x00, 0xff, 0xff, 0x59,
0x47, 0x12, 0x67, 0x16, 0x05, 0x00, 0x00,
// 484 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x94, 0x4d, 0x6b, 0x14, 0x31,
0x18, 0xc7, 0x89, 0xd6, 0x15, 0xe3, 0x2b, 0xa1, 0xd5, 0x3a, 0x6d, 0x57, 0x19, 0x2f, 0xb2, 0x68,
0xc2, 0xd6, 0x83, 0xc5, 0x9b, 0x2f, 0x45, 0x0a, 0x1e, 0x74, 0xc5, 0x8b, 0x08, 0x25, 0x9d, 0x79,
0x48, 0xd3, 0xdd, 0x9d, 0xc4, 0x24, 0x3b, 0x52, 0x8a, 0x97, 0xe2, 0xcd, 0xa3, 0x1f, 0xc2, 0xbb,
0x9f, 0xc2, 0xa3, 0xe0, 0x17, 0x90, 0xc5, 0x0f, 0x22, 0xf3, 0xec, 0x44, 0xbb, 0xdd, 0xae, 0xa7,
0xa1, 0xa7, 0x79, 0x26, 0xc9, 0xe4, 0xf7, 0x9b, 0x27, 0x7f, 0x42, 0x57, 0x3d, 0xb8, 0x12, 0x9c,
0xb0, 0xce, 0xec, 0x41, 0x16, 0xe2, 0x93, 0x5b, 0x67, 0x82, 0x61, 0xe7, 0xeb, 0xd7, 0x64, 0x51,
0x19, 0x65, 0x70, 0x4c, 0x54, 0xd5, 0x64, 0x3a, 0x59, 0x55, 0xc6, 0xa8, 0x01, 0x08, 0x69, 0xb5,
0x90, 0x45, 0x61, 0x82, 0x0c, 0xda, 0x14, 0xbe, 0x9e, 0x4d, 0xfb, 0x1b, 0x9e, 0x6b, 0x83, 0xb3,
0x99, 0x71, 0x20, 0xca, 0xae, 0x50, 0x50, 0x80, 0x93, 0x01, 0xf2, 0x7a, 0xcd, 0x96, 0xd2, 0x61,
0x77, 0xb4, 0xc3, 0x33, 0x33, 0x14, 0xd2, 0x21, 0x62, 0x0f, 0x8b, 0xfb, 0x59, 0x2e, 0x6c, 0x5f,
0x55, 0x1f, 0x7b, 0x21, 0xad, 0x1d, 0xe8, 0x0c, 0x37, 0x17, 0x65, 0x57, 0x0e, 0xec, 0xae, 0x9c,
0xd9, 0x2a, 0xfd, 0x40, 0x17, 0x5f, 0x4e, 0x6c, 0x9f, 0x3a, 0x90, 0x01, 0x7a, 0xf0, 0x7e, 0x04,
0x3e, 0xb0, 0x6d, 0x1a, 0xff, 0x62, 0x99, 0xdc, 0x26, 0x77, 0x2f, 0xae, 0x6f, 0xf2, 0x7f, 0x50,
0x1e, 0xa1, 0x58, 0x6c, 0x67, 0x39, 0xb7, 0x7d, 0xc5, 0x2b, 0x28, 0x3f, 0x02, 0xe5, 0x11, 0xca,
0x1f, 0x5b, 0x5b, 0x43, 0x7a, 0x71, 0xd7, 0x34, 0xa5, 0x97, 0xea, 0xb1, 0x57, 0x23, 0x70, 0xfb,
0x8c, 0xd1, 0x85, 0x42, 0x0e, 0x01, 0x69, 0x17, 0x7a, 0x58, 0x1f, 0x91, 0x7b, 0x63, 0xf3, 0xd3,
0x94, 0xbb, 0x4a, 0x2f, 0x6f, 0x0e, 0x6d, 0xd8, 0xef, 0x81, 0xb7, 0xa6, 0xf0, 0xb0, 0xfe, 0xed,
0x1c, 0xbd, 0x52, 0xaf, 0x7a, 0x0d, 0xae, 0xd4, 0x19, 0xb0, 0xcf, 0x84, 0xb6, 0x26, 0x3d, 0x63,
0x6b, 0x3c, 0x06, 0xe0, 0xa4, 0x5e, 0x26, 0xcd, 0xd8, 0xa5, 0x2b, 0x87, 0x3f, 0x7f, 0x7f, 0x39,
0xb3, 0x94, 0x5e, 0xc3, 0x6c, 0x94, 0xdd, 0x98, 0x3a, 0xff, 0x88, 0x74, 0xd8, 0x21, 0xa1, 0x0b,
0x2f, 0xb4, 0x0f, 0x6c, 0xe9, 0xb8, 0x0b, 0xb6, 0x37, 0xd9, 0x6a, 0xc4, 0xa1, 0x22, 0xa4, 0xcb,
0xe8, 0xc1, 0xd8, 0x8c, 0x07, 0xfb, 0x44, 0xe8, 0xd9, 0xe7, 0x30, 0xd7, 0xa1, 0xa1, 0x3e, 0xdc,
0x42, 0xfe, 0x4d, 0x76, 0xe3, 0x38, 0x5f, 0x1c, 0x54, 0xa9, 0xf9, 0xc8, 0xbe, 0x12, 0xda, 0x9a,
0x04, 0x66, 0xf6, 0x64, 0xa6, 0x82, 0xd4, 0x94, 0xd1, 0x43, 0x34, 0xea, 0x26, 0xf7, 0xa2, 0x91,
0x03, 0x6b, 0xbc, 0x0e, 0xc6, 0x69, 0xf0, 0xe2, 0x20, 0x2a, 0x0c, 0x21, 0xc8, 0x5c, 0x06, 0xc9,
0x51, 0xb3, 0x3a, 0xb5, 0x77, 0xb4, 0xf5, 0x0c, 0x06, 0x10, 0x60, 0x5e, 0xcb, 0xae, 0xff, 0x1d,
0x9e, 0xca, 0x63, 0x7a, 0x07, 0x89, 0x6b, 0x9d, 0x95, 0x93, 0x89, 0x08, 0x78, 0xb2, 0xf1, 0x7d,
0xdc, 0x26, 0x3f, 0xc6, 0x6d, 0xf2, 0x6b, 0xdc, 0x26, 0x6f, 0x3b, 0xff, 0xbb, 0x34, 0xa6, 0xef,
0xb3, 0x9d, 0x16, 0x5e, 0x0e, 0x0f, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0x95, 0xb1, 0xb7, 0x37,
0xe8, 0x04, 0x00, 0x00,
}

View File

@@ -336,9 +336,9 @@ var (
pattern_ProjectService_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "projects", "name"}, ""))
pattern_ProjectService_Update_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "projects", "project.metadata.name"}, ""))
pattern_ProjectService_Update_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "repositories", "project.metadata.name"}, ""))
pattern_ProjectService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "projects", "name"}, ""))
pattern_ProjectService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "repositories", "name"}, ""))
)
var (

View File

@@ -9,7 +9,6 @@ package project;
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";
@@ -53,13 +52,13 @@ service ProjectService {
// Update updates a project
rpc Update(ProjectUpdateRequest) returns (github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.AppProject) {
option (google.api.http) = {
put: "/api/v1/projects/{project.metadata.name}"
put: "/api/v1/repositories/{project.metadata.name}"
body: "*"
};
}
// Delete deletes a project
rpc Delete(ProjectQuery) returns (EmptyResponse) {
option (google.api.http).delete = "/api/v1/projects/{name}";
option (google.api.http).delete = "/api/v1/repositories/{name}";
}
}

View File

@@ -39,7 +39,7 @@ func TestProjectServer(t *testing.T) {
Spec: v1alpha1.ApplicationSpec{Project: "test", Destination: v1alpha1.ApplicationDestination{Namespace: "ns3", Server: "https://server3"}},
}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
projectServer := NewServer("default", apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.Destinations = updatedProj.Spec.Destinations[1:]
@@ -55,7 +55,7 @@ func TestProjectServer(t *testing.T) {
Spec: v1alpha1.ApplicationSpec{Project: "test", Destination: v1alpha1.ApplicationDestination{Namespace: "ns1", Server: "https://server1"}},
}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
projectServer := NewServer("default", apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.Destinations = updatedProj.Spec.Destinations[1:]
@@ -72,7 +72,7 @@ func TestProjectServer(t *testing.T) {
Spec: v1alpha1.ApplicationSpec{Project: "test"},
}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
projectServer := NewServer("default", apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.SourceRepos = []string{}
@@ -88,7 +88,7 @@ func TestProjectServer(t *testing.T) {
Spec: v1alpha1.ApplicationSpec{Project: "test", Source: v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argo-cd.git"}},
}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
projectServer := NewServer("default", apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.SourceRepos = []string{}
@@ -100,7 +100,7 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestDeleteProjectSuccessful", func(t *testing.T) {
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj), enforcer, util.NewKeyLock())
projectServer := NewServer("default", apps.NewSimpleClientset(&existingProj), enforcer, util.NewKeyLock())
_, err := projectServer.Delete(context.Background(), &ProjectQuery{Name: "test"})
@@ -113,7 +113,7 @@ func TestProjectServer(t *testing.T) {
Spec: v1alpha1.ApplicationSpec{Project: "test"},
}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
projectServer := NewServer("default", apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock())
_, err := projectServer.Delete(context.Background(), &ProjectQuery{Name: "test"})

View File

@@ -0,0 +1,150 @@
// Code generated by mockery v1.0.0
package mocks
import context "context"
import mock "github.com/stretchr/testify/mock"
import repository "github.com/argoproj/argo-cd/server/repository"
import v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
// RepositoryServiceServer is an autogenerated mock type for the RepositoryServiceServer type
type RepositoryServiceServer struct {
mock.Mock
}
// Create provides a mock function with given fields: _a0, _a1
func (_m *RepositoryServiceServer) Create(_a0 context.Context, _a1 *repository.RepoCreateRequest) (*v1alpha1.Repository, error) {
ret := _m.Called(_a0, _a1)
var r0 *v1alpha1.Repository
if rf, ok := ret.Get(0).(func(context.Context, *repository.RepoCreateRequest) *v1alpha1.Repository); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *repository.RepoCreateRequest) 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 *RepositoryServiceServer) Delete(_a0 context.Context, _a1 *repository.RepoQuery) (*repository.RepoResponse, error) {
ret := _m.Called(_a0, _a1)
var r0 *repository.RepoResponse
if rf, ok := ret.Get(0).(func(context.Context, *repository.RepoQuery) *repository.RepoResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*repository.RepoResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *repository.RepoQuery) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Get provides a mock function with given fields: _a0, _a1
func (_m *RepositoryServiceServer) Get(_a0 context.Context, _a1 *repository.RepoQuery) (*v1alpha1.Repository, error) {
ret := _m.Called(_a0, _a1)
var r0 *v1alpha1.Repository
if rf, ok := ret.Get(0).(func(context.Context, *repository.RepoQuery) *v1alpha1.Repository); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *repository.RepoQuery) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: _a0, _a1
func (_m *RepositoryServiceServer) List(_a0 context.Context, _a1 *repository.RepoQuery) (*v1alpha1.RepositoryList, error) {
ret := _m.Called(_a0, _a1)
var r0 *v1alpha1.RepositoryList
if rf, ok := ret.Get(0).(func(context.Context, *repository.RepoQuery) *v1alpha1.RepositoryList); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.RepositoryList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *repository.RepoQuery) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListKsonnetApps provides a mock function with given fields: _a0, _a1
func (_m *RepositoryServiceServer) ListKsonnetApps(_a0 context.Context, _a1 *repository.RepoKsonnetQuery) (*repository.RepoKsonnetResponse, error) {
ret := _m.Called(_a0, _a1)
var r0 *repository.RepoKsonnetResponse
if rf, ok := ret.Get(0).(func(context.Context, *repository.RepoKsonnetQuery) *repository.RepoKsonnetResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*repository.RepoKsonnetResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *repository.RepoKsonnetQuery) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: _a0, _a1
func (_m *RepositoryServiceServer) Update(_a0 context.Context, _a1 *repository.RepoUpdateRequest) (*v1alpha1.Repository, error) {
ret := _m.Called(_a0, _a1)
var r0 *v1alpha1.Repository
if rf, ok := ret.Get(0).(func(context.Context, *repository.RepoUpdateRequest) *v1alpha1.Repository); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *repository.RepoUpdateRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@@ -1,7 +1,6 @@
package repository
import (
"path"
"reflect"
"github.com/ghodss/yaml"
@@ -55,7 +54,7 @@ func (s *Server) List(ctx context.Context, q *RepoQuery) (*appsv1.RepositoryList
}
// ListKsonnetApps returns list of Ksonnet apps in the repo
func (s *Server) ListApps(ctx context.Context, q *RepoAppsQuery) (*RepoAppsResponse, error) {
func (s *Server) ListKsonnetApps(ctx context.Context, q *RepoKsonnetQuery) (*RepoKsonnetResponse, error) {
if !s.enf.EnforceClaims(ctx.Value("claims"), "repositories/apps", "get", q.Repo) {
return nil, grpc.ErrPermissionDenied
}
@@ -76,96 +75,39 @@ func (s *Server) ListApps(ctx context.Context, q *RepoAppsQuery) (*RepoAppsRespo
revision = "HEAD"
}
ksonnetRes, err := repoClient.ListDir(ctx, &repository.ListDirRequest{Repo: repo, Revision: revision, Path: "*app.yaml"})
if err != nil {
return nil, err
}
helmRes, err := repoClient.ListDir(ctx, &repository.ListDirRequest{Repo: repo, Revision: revision, Path: "*Chart.yaml"})
if err != nil {
return nil, err
}
items := make([]*AppInfo, 0)
for i := range ksonnetRes.Items {
items = append(items, &AppInfo{Type: string(repository.AppSourceKsonnet), Path: ksonnetRes.Items[i]})
}
for i := range helmRes.Items {
items = append(items, &AppInfo{Type: string(repository.AppSourceHelm), Path: helmRes.Items[i]})
}
return &RepoAppsResponse{Items: items}, nil
}
func (s *Server) GetAppDetails(ctx context.Context, q *RepoAppDetailsQuery) (*RepoAppDetailsResponse, error) {
if !s.enf.EnforceClaims(ctx.Value("claims"), "repositories/apps", "get", q.Repo) {
return nil, grpc.ErrPermissionDenied
}
repo, err := s.db.GetRepository(ctx, q.Repo)
if err != nil {
return nil, err
}
// Test the repo
conn, repoClient, err := s.repoClientset.NewRepositoryClient()
if err != nil {
return nil, err
}
defer util.Close(conn)
revision := q.Revision
if revision == "" {
revision = "HEAD"
}
appSpecRes, err := repoClient.GetFile(ctx, &repository.GetFileRequest{
// Verify app.yaml is functional
req := repository.ListDirRequest{
Repo: repo,
Revision: revision,
Path: q.Path,
})
Path: "*app.yaml",
}
getRes, err := repoClient.ListDir(ctx, &req)
if err != nil {
return nil, err
}
appSourceType := repository.IdentifyAppSourceTypeByAppPath(q.Path)
switch appSourceType {
case repository.AppSourceKsonnet:
var appSpec KsonnetAppSpec
appSpec.Path = q.Path
err = yaml.Unmarshal(appSpecRes.Data, &appSpec)
if err != nil {
return nil, err
}
return &RepoAppDetailsResponse{
Type: string(appSourceType),
Ksonnet: &appSpec,
}, nil
case repository.AppSourceHelm:
var appSpec HelmAppSpec
appSpec.Path = q.Path
err = yaml.Unmarshal(appSpecRes.Data, &appSpec)
if err != nil {
return nil, err
}
valuesFilesRes, err := repoClient.ListDir(ctx, &repository.ListDirRequest{
Revision: revision,
out := make([]*KsonnetAppSpec, 0)
for _, path := range getRes.Items {
getFileRes, err := repoClient.GetFile(ctx, &repository.GetFileRequest{
Repo: repo,
Path: path.Join(path.Dir(q.Path), "*values*.yaml"),
Revision: revision,
Path: path,
})
if err != nil {
return nil, err
}
appSpec.ValueFiles = valuesFilesRes.Items
return &RepoAppDetailsResponse{
Type: string(appSourceType),
Helm: &appSpec,
}, nil
var appSpec KsonnetAppSpec
appSpec.Path = path
err = yaml.Unmarshal(getFileRes.Data, &appSpec)
if err == nil && appSpec.Name != "" && len(appSpec.Environments) > 0 {
out = append(out, &appSpec)
}
}
return nil, status.Errorf(codes.InvalidArgument, "specified application path is not supported")
return &RepoKsonnetResponse{
Items: out,
}, nil
}
// Create creates a repository

File diff suppressed because it is too large Load Diff

View File

@@ -46,11 +46,11 @@ func request_RepositoryService_List_0(ctx context.Context, marshaler runtime.Mar
}
var (
filter_RepositoryService_ListApps_0 = &utilities.DoubleArray{Encoding: map[string]int{"repo": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
filter_RepositoryService_ListKsonnetApps_0 = &utilities.DoubleArray{Encoding: map[string]int{"repo": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_RepositoryService_ListApps_0(ctx context.Context, marshaler runtime.Marshaler, client RepositoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RepoAppsQuery
func request_RepositoryService_ListKsonnetApps_0(ctx context.Context, marshaler runtime.Marshaler, client RepositoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RepoKsonnetQuery
var metadata runtime.ServerMetadata
var (
@@ -71,57 +71,11 @@ func request_RepositoryService_ListApps_0(ctx context.Context, marshaler runtime
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "repo", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_RepositoryService_ListApps_0); err != nil {
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_RepositoryService_ListKsonnetApps_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListApps(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
var (
filter_RepositoryService_GetAppDetails_0 = &utilities.DoubleArray{Encoding: map[string]int{"repo": 0, "path": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_RepositoryService_GetAppDetails_0(ctx context.Context, marshaler runtime.Marshaler, client RepositoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RepoAppDetailsQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["repo"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "repo")
}
protoReq.Repo, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "repo", err)
}
val, ok = pathParams["path"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "path")
}
protoReq.Path, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "path", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_RepositoryService_GetAppDetails_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetAppDetails(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
msg, err := client.ListKsonnetApps(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
@@ -299,7 +253,7 @@ func RegisterRepositoryServiceHandlerClient(ctx context.Context, mux *runtime.Se
})
mux.Handle("GET", pattern_RepositoryService_ListApps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("GET", pattern_RepositoryService_ListKsonnetApps_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 {
@@ -317,43 +271,14 @@ func RegisterRepositoryServiceHandlerClient(ctx context.Context, mux *runtime.Se
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_RepositoryService_ListApps_0(rctx, inboundMarshaler, client, req, pathParams)
resp, md, err := request_RepositoryService_ListKsonnetApps_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_RepositoryService_ListApps_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_RepositoryService_GetAppDetails_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_RepositoryService_GetAppDetails_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_RepositoryService_GetAppDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_RepositoryService_ListKsonnetApps_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -479,9 +404,7 @@ func RegisterRepositoryServiceHandlerClient(ctx context.Context, mux *runtime.Se
var (
pattern_RepositoryService_List_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "repositories"}, ""))
pattern_RepositoryService_ListApps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "repositories", "repo", "apps"}, ""))
pattern_RepositoryService_GetAppDetails_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", "repositories", "repo", "apps", "path"}, ""))
pattern_RepositoryService_ListKsonnetApps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "repositories", "repo", "ksonnet"}, ""))
pattern_RepositoryService_Create_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "repositories"}, ""))
@@ -495,9 +418,7 @@ var (
var (
forward_RepositoryService_List_0 = runtime.ForwardResponseMessage
forward_RepositoryService_ListApps_0 = runtime.ForwardResponseMessage
forward_RepositoryService_GetAppDetails_0 = runtime.ForwardResponseMessage
forward_RepositoryService_ListKsonnetApps_0 = runtime.ForwardResponseMessage
forward_RepositoryService_Create_0 = runtime.ForwardResponseMessage

View File

@@ -11,36 +11,15 @@ 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";
// RepoAppsQuery is a query for Repository apps
message RepoAppsQuery {
// RepoKsonnetQuery is a query for Repository contents matching a particular path
message RepoKsonnetQuery {
string repo = 1;
string revision = 2;
}
// AppInfo contains application type and app file path
message AppInfo {
string type = 1;
string path = 2;
}
// RepoAppDetailsQuery contains query information for app details request
message RepoAppDetailsQuery {
string repo = 1;
string revision = 2;
string path = 3;
}
// RepoAppDetailsResponse application details
message RepoAppDetailsResponse {
string type = 1;
KsonnetAppSpec ksonnet = 2;
HelmAppSpec helm = 3;
}
// RepoAppsResponse contains applications of specified repository
message RepoAppsResponse {
repeated AppInfo items = 1;
// RepoKsonnetResponse is a response for Repository contents matching a particular path
message RepoKsonnetResponse {
repeated KsonnetAppSpec items = 1;
}
// KsonnetAppSpec contains Ksonnet app response
@@ -51,13 +30,6 @@ message KsonnetAppSpec {
map<string, KsonnetEnvironment> environments = 3;
}
// HelmAppSpec contains helm app name and path in source repo
message HelmAppSpec {
string name = 1;
string path = 2;
repeated string valueFiles = 3;
}
message KsonnetEnvironment {
// Name is the user defined name of an environment
string name = 1;
@@ -100,14 +72,9 @@ service RepositoryService {
option (google.api.http).get = "/api/v1/repositories";
}
// ListApps returns list of apps in the repo
rpc ListApps(RepoAppsQuery) returns (RepoAppsResponse) {
option (google.api.http).get = "/api/v1/repositories/{repo}/apps";
}
// GetAppDetails returns application details by given path
rpc GetAppDetails(RepoAppDetailsQuery) returns (RepoAppDetailsResponse) {
option (google.api.http).get = "/api/v1/repositories/{repo}/apps/{path}";
// ListKsonnetApps returns list of Ksonnet apps in the repo
rpc ListKsonnetApps(RepoKsonnetQuery) returns (RepoKsonnetResponse) {
option (google.api.http).get = "/api/v1/repositories/{repo}/ksonnet";
}
// Create creates a repo

View File

@@ -8,15 +8,14 @@ import (
"net/http"
"net/url"
"os"
"regexp"
"strings"
"time"
"github.com/gobuffalo/packr"
golang_proto "github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/go-grpc-middleware/auth"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
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"
@@ -29,9 +28,10 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"github.com/argoproj/argo-cd"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/pkg/apiclient"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
@@ -55,7 +55,6 @@ import (
"github.com/argoproj/argo-cd/util/swagger"
tlsutil "github.com/argoproj/argo-cd/util/tls"
"github.com/argoproj/argo-cd/util/webhook"
netCtx "golang.org/x/net/context"
)
var (
@@ -300,18 +299,11 @@ func (a *ArgoCDServer) useTLS() bool {
func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
var sOpts []grpc.ServerOption
sensitiveMethods := map[string]bool{
"/session.SessionService/Create": true,
"/account.AccountService/UpdatePassword": true,
}
// 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.PayloadStreamServerInterceptor(a.log, true, func(ctx netCtx.Context, fullMethodName string, servingObject interface{}) bool {
return !sensitiveMethods[fullMethodName]
}),
grpc_util.ErrorCodeStreamServerInterceptor(),
grpc_util.PanicLoggerStreamServerInterceptor(a.log),
)))
@@ -319,9 +311,6 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
bug21955WorkaroundInterceptor,
grpc_logrus.UnaryServerInterceptor(a.log),
grpc_auth.UnaryServerInterceptor(a.authenticate),
grpc_util.PayloadUnaryServerInterceptor(a.log, true, func(ctx netCtx.Context, fullMethodName string, servingObject interface{}) bool {
return !sensitiveMethods[fullMethodName]
}),
grpc_util.ErrorCodeUnaryServerInterceptor(),
grpc_util.PanicLoggerUnaryServerInterceptor(a.log),
)))
@@ -333,7 +322,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
sessionService := session.NewServer(a.sessionMgr)
projectLock := util.NewKeyLock()
applicationService := application.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.RepoClientset, db, a.enf, projectLock)
projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock)
projectService := project.NewServer(a.Namespace, a.AppClientset, a.enf, projectLock)
settingsService := settings.NewServer(a.settingsMgr)
accountService := account.NewServer(a.sessionMgr, a.settingsMgr)
version.RegisterVersionServiceServer(grpcS, &version.Server{})
@@ -503,6 +492,11 @@ func getToken(md metadata.MD) string {
if ok && len(tokens) > 0 {
return tokens[0]
}
// check the legacy key (v0.3.2 and below). 'tokens' was renamed to 'token'
tokens, ok = md["tokens"]
if ok && len(tokens) > 0 {
return tokens[0]
}
// check the HTTP cookie
for _, cookieToken := range md["grpcgateway-cookie"] {
header := http.Header{}
@@ -521,17 +515,22 @@ type bug21955Workaround struct {
handler http.Handler
}
var pathPatters = []*regexp.Regexp{
regexp.MustCompile(`/api/v1/clusters/[^/]+`),
regexp.MustCompile(`/api/v1/repositories/[^/]+`),
regexp.MustCompile(`/api/v1/repositories/[^/]+/apps`),
regexp.MustCompile(`/api/v1/repositories/[^/]+/apps/[^/]+`),
}
func (bf *bug21955Workaround) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, pattern := range pathPatters {
if pattern.MatchString(r.URL.RawPath) {
r.URL.Path = r.URL.RawPath
paths := map[string][]string{
"/api/v1/repositories/": {"ksonnet"},
"/api/v1/clusters/": {},
}
for path, subPaths := range paths {
if strings.Index(r.URL.Path, path) > -1 {
postfix := ""
for _, subPath := range subPaths {
if strings.LastIndex(r.URL.Path, subPath) == len(r.URL.Path)-len(subPath) {
postfix = "/" + subPath
r.URL.Path = r.URL.Path[0 : len(r.URL.Path)-len(subPath)-1]
break
}
}
r.URL.Path = path + url.QueryEscape(r.URL.Path[len(path):]) + postfix
break
}
}
@@ -545,23 +544,12 @@ func bug21955WorkaroundInterceptor(ctx context.Context, req interface{}, _ *grpc
return nil, err
}
rq.Repo = repo
} else if rk, ok := req.(*repository.RepoAppsQuery); ok {
} else if rk, ok := req.(*repository.RepoKsonnetQuery); ok {
repo, err := url.QueryUnescape(rk.Repo)
if err != nil {
return nil, err
}
rk.Repo = repo
} else if rdq, ok := req.(*repository.RepoAppDetailsQuery); ok {
repo, err := url.QueryUnescape(rdq.Repo)
if err != nil {
return nil, err
}
path, err := url.QueryUnescape(rdq.Path)
if err != nil {
return nil, err
}
rdq.Repo = repo
rdq.Path = path
} else if ru, ok := req.(*repository.RepoUpdateRequest); ok {
repo, err := url.QueryUnescape(ru.Repo.Repo)
if err != nil {

View File

@@ -2,10 +2,12 @@ package session
import (
"context"
"fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/argoproj/argo-cd/util/jwt"
sessionmgr "github.com/argoproj/argo-cd/util/session"
)
@@ -21,23 +23,40 @@ func NewServer(mgr *sessionmgr.SessionManager) *Server {
}
}
// Create generates a JWT token signed by ArgoCD intended for web/CLI logins of the admin user
// using username/password
// Create generates a non-expiring JWT token signed by ArgoCD. This endpoint is used in two circumstances:
// 1. Web/CLI logins for local users (i.e. admin), for when SSO is not configured. In this case,
// username/password.
// 2. CLI login which completed an OAuth2 login flow but wish to store a permanent token in their config
func (s *Server) Create(ctx context.Context, q *SessionCreateRequest) (*SessionResponse, error) {
if q.Token != "" {
return nil, status.Errorf(codes.Unauthenticated, "token-based session creation no longer supported. please upgrade argocd cli to v0.7+")
}
if q.Username == "" || q.Password == "" {
var tokenString string
var err error
if q.Password != "" {
// first case
err = s.mgr.VerifyUsernamePassword(q.Username, q.Password)
if err != nil {
return nil, err
}
tokenString, err = s.mgr.Create(q.Username)
if err != nil {
return nil, err
}
} else if q.Token != "" {
// second case
claimsIf, err := s.mgr.VerifyToken(q.Token)
if err != nil {
return nil, err
}
claims, err := jwt.MapClaims(claimsIf)
if err != nil {
return nil, err
}
tokenString, err = s.mgr.ReissueClaims(claims)
if err != nil {
return nil, fmt.Errorf("Failed to resign claims: %v", err)
}
} else {
return nil, status.Errorf(codes.Unauthenticated, "no credentials supplied")
}
err := s.mgr.VerifyUsernamePassword(q.Username, q.Password)
if err != nil {
return nil, err
}
tokenString, err := s.mgr.Create(q.Username, 0)
if err != nil {
return nil, err
}
return &SessionResponse{Token: tokenString}, nil
}

View File

@@ -691,62 +691,6 @@
}
}
}
},
"delete": {
"tags": [
"ProjectService"
],
"summary": "Delete deletes a project",
"operationId": "DeleteMixin3",
"parameters": [
{
"type": "string",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/projectEmptyResponse"
}
}
}
}
},
"/api/v1/projects/{project.metadata.name}": {
"put": {
"tags": [
"ProjectService"
],
"summary": "Update updates a project",
"operationId": "UpdateMixin3",
"parameters": [
{
"type": "string",
"name": "project.metadata.name",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/projectProjectUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/v1alpha1AppProject"
}
}
}
}
},
"/api/v1/repositories": {
@@ -798,6 +742,64 @@
}
}
},
"/api/v1/repositories/{name}": {
"delete": {
"tags": [
"ProjectService"
],
"summary": "Delete deletes a project",
"operationId": "DeleteMixin3",
"parameters": [
{
"type": "string",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/projectEmptyResponse"
}
}
}
}
},
"/api/v1/repositories/{project.metadata.name}": {
"put": {
"tags": [
"ProjectService"
],
"summary": "Update updates a project",
"operationId": "UpdateMixin3",
"parameters": [
{
"type": "string",
"name": "project.metadata.name",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/projectProjectUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/v1alpha1AppProject"
}
}
}
}
},
"/api/v1/repositories/{repo.repo}": {
"put": {
"tags": [
@@ -879,13 +881,13 @@
}
}
},
"/api/v1/repositories/{repo}/apps": {
"/api/v1/repositories/{repo}/ksonnet": {
"get": {
"tags": [
"RepositoryService"
],
"summary": "ListApps returns list of apps in the repo",
"operationId": "ListApps",
"summary": "ListKsonnetApps returns list of Ksonnet apps in the repo",
"operationId": "ListKsonnetApps",
"parameters": [
{
"type": "string",
@@ -903,43 +905,7 @@
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/repositoryRepoAppsResponse"
}
}
}
}
},
"/api/v1/repositories/{repo}/apps/{path}": {
"get": {
"tags": [
"RepositoryService"
],
"summary": "GetAppDetails returns application details by given path",
"operationId": "GetAppDetails",
"parameters": [
{
"type": "string",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"name": "path",
"in": "path",
"required": true
},
{
"type": "string",
"name": "revision",
"in": "query"
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/repositoryRepoAppDetailsResponse"
"$ref": "#/definitions/repositoryRepoKsonnetResponse"
}
}
}
@@ -1272,36 +1238,6 @@
}
}
},
"repositoryAppInfo": {
"type": "object",
"title": "AppInfo contains application type and app file path",
"properties": {
"path": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"repositoryHelmAppSpec": {
"type": "object",
"title": "HelmAppSpec contains helm app name and path in source repo",
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"valueFiles": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"repositoryKsonnetAppSpec": {
"type": "object",
"title": "KsonnetAppSpec contains Ksonnet app response\nThis roughly reflects: ksonnet/ksonnet/metadata/app/schema.go",
@@ -1379,29 +1315,14 @@
}
}
},
"repositoryRepoAppDetailsResponse": {
"repositoryRepoKsonnetResponse": {
"type": "object",
"title": "RepoAppDetailsResponse application details",
"properties": {
"helm": {
"$ref": "#/definitions/repositoryHelmAppSpec"
},
"ksonnet": {
"$ref": "#/definitions/repositoryKsonnetAppSpec"
},
"type": {
"type": "string"
}
}
},
"repositoryRepoAppsResponse": {
"type": "object",
"title": "RepoAppsResponse contains applications of specified repository",
"title": "RepoKsonnetResponse is a response for Repository contents matching a particular path",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repositoryAppInfo"
"$ref": "#/definitions/repositoryKsonnetAppSpec"
}
}
}
@@ -1805,7 +1726,7 @@
"$ref": "#/definitions/v1alpha1ApplicationDestination"
}
},
"sourceRepos": {
"sources": {
"type": "array",
"title": "SourceRepos contains list of git repository URLs which can be used for deployment",
"items": {
@@ -1881,33 +1802,26 @@
"properties": {
"componentParameterOverrides": {
"type": "array",
"title": "ComponentParameterOverrides are a list of parameter override values",
"title": "Environment parameter override values",
"items": {
"$ref": "#/definitions/v1alpha1ComponentParameter"
}
},
"environment": {
"type": "string",
"title": "Environment is a ksonnet application environment name"
"description": "Environment is a ksonnet application environment name.",
"type": "string"
},
"path": {
"type": "string",
"title": "Path is a directory path within the repository containing a"
"description": "Path is a directory path within repository which contains ksonnet application.",
"type": "string"
},
"repoURL": {
"type": "string",
"title": "RepoURL is the git repository URL of the application manifests"
"description": "RepoURL is the repository URL containing the ksonnet application.",
"type": "string"
},
"targetRevision": {
"type": "string",
"title": "TargetRevision defines the commit, tag, or branch in which to sync the application to.\nIf omitted, will sync to HEAD"
},
"valuesFiles": {
"type": "array",
"title": "ValuesFiles is a list of Helm values files to use when generating a template",
"items": {
"type": "string"
}
}
}
},
@@ -2431,9 +2345,6 @@
"GoVersion": {
"type": "string"
},
"KsonnetVersion": {
"type": "string"
},
"Platform": {
"type": "string"
},

View File

@@ -2,7 +2,6 @@ package version
import (
argocd "github.com/argoproj/argo-cd"
ksutil "github.com/argoproj/argo-cd/util/ksonnet"
"github.com/golang/protobuf/ptypes/empty"
"golang.org/x/net/context"
)
@@ -12,20 +11,15 @@ type Server struct{}
// Version returns the version of the API server
func (s *Server) Version(context.Context, *empty.Empty) (*VersionMessage, error) {
vers := argocd.GetVersion()
ksonnetVersion, err := ksutil.KsonnetVersion()
if err != nil {
return nil, err
}
return &VersionMessage{
Version: vers.Version,
BuildDate: vers.BuildDate,
GitCommit: vers.GitCommit,
GitTag: vers.GitTag,
GitTreeState: vers.GitTreeState,
GoVersion: vers.GoVersion,
Compiler: vers.Compiler,
Platform: vers.Platform,
KsonnetVersion: ksonnetVersion,
Version: vers.Version,
BuildDate: vers.BuildDate,
GitCommit: vers.GitCommit,
GitTag: vers.GitTag,
GitTreeState: vers.GitTreeState,
GoVersion: vers.GoVersion,
Compiler: vers.Compiler,
Platform: vers.Platform,
}, nil
}

View File

@@ -40,15 +40,14 @@ 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,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"`
KsonnetVersion string `protobuf:"bytes,9,opt,name=KsonnetVersion,proto3" json:"KsonnetVersion,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{} }
@@ -112,13 +111,6 @@ func (m *VersionMessage) GetPlatform() string {
return ""
}
func (m *VersionMessage) GetKsonnetVersion() string {
if m != nil {
return m.KsonnetVersion
}
return ""
}
func init() {
proto.RegisterType((*VersionMessage)(nil), "version.VersionMessage")
}
@@ -260,12 +252,6 @@ func (m *VersionMessage) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintVersion(dAtA, i, uint64(len(m.Platform)))
i += copy(dAtA[i:], m.Platform)
}
if len(m.KsonnetVersion) > 0 {
dAtA[i] = 0x4a
i++
i = encodeVarintVersion(dAtA, i, uint64(len(m.KsonnetVersion)))
i += copy(dAtA[i:], m.KsonnetVersion)
}
return i, nil
}
@@ -313,10 +299,6 @@ func (m *VersionMessage) Size() (n int) {
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
l = len(m.KsonnetVersion)
if l > 0 {
n += 1 + l + sovVersion(uint64(l))
}
return n
}
@@ -594,35 +576,6 @@ func (m *VersionMessage) Unmarshal(dAtA []byte) error {
}
m.Platform = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 9:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field KsonnetVersion", 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.KsonnetVersion = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipVersion(dAtA[iNdEx:])
@@ -752,27 +705,26 @@ var (
func init() { proto.RegisterFile("server/version/version.proto", fileDescriptorVersion) }
var fileDescriptorVersion = []byte{
// 343 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x92, 0xcf, 0x4a, 0xc3, 0x40,
0x10, 0xc6, 0x49, 0xd5, 0xfe, 0x59, 0x4a, 0x0f, 0x8b, 0xd4, 0x25, 0x96, 0x22, 0x3d, 0x88, 0x08,
0x26, 0xa0, 0x17, 0xcf, 0xad, 0xd2, 0x83, 0x08, 0xc5, 0x8a, 0x07, 0x6f, 0x9b, 0x76, 0x1a, 0x57,
0x92, 0x4c, 0xd8, 0x4c, 0x0b, 0x5e, 0x7d, 0x05, 0x5f, 0xc0, 0xc7, 0xf1, 0x28, 0xf8, 0x02, 0x52,
0x7c, 0x10, 0xc9, 0x26, 0x1b, 0x89, 0xa7, 0xcc, 0xf7, 0xfd, 0x86, 0x2f, 0xe1, 0x9b, 0xb0, 0x41,
0x06, 0x7a, 0x03, 0xda, 0xdf, 0x80, 0xce, 0x14, 0x26, 0xf6, 0xe9, 0xa5, 0x1a, 0x09, 0x79, 0xab,
0x94, 0xee, 0x20, 0x44, 0x0c, 0x23, 0xf0, 0x65, 0xaa, 0x7c, 0x99, 0x24, 0x48, 0x92, 0x14, 0x26,
0x59, 0xb1, 0xe6, 0x1e, 0x96, 0xd4, 0xa8, 0x60, 0xbd, 0xf2, 0x21, 0x4e, 0xe9, 0xa5, 0x80, 0xa3,
0xf7, 0x06, 0xeb, 0x3d, 0x14, 0x31, 0xb7, 0x90, 0x65, 0x32, 0x04, 0x2e, 0x58, 0xab, 0x74, 0x84,
0x73, 0xe4, 0x9c, 0x74, 0xee, 0xac, 0xe4, 0x03, 0xd6, 0x19, 0xaf, 0x55, 0xb4, 0xbc, 0x92, 0x04,
0xa2, 0x61, 0xd8, 0x9f, 0x91, 0xd3, 0xa9, 0xa2, 0x09, 0xc6, 0xb1, 0x22, 0xb1, 0x53, 0xd0, 0xca,
0xe0, 0x7d, 0xd6, 0x9c, 0x2a, 0xba, 0x97, 0xa1, 0xd8, 0x35, 0xa8, 0x54, 0x7c, 0xc4, 0xba, 0xf9,
0xa4, 0x01, 0xe6, 0x94, 0xc7, 0xee, 0x19, 0x5a, 0xf3, 0x4c, 0x32, 0xda, 0x6f, 0x6a, 0x96, 0xc9,
0xd6, 0xe0, 0x2e, 0x6b, 0x4f, 0x30, 0x4e, 0x55, 0x04, 0x5a, 0xb4, 0x0c, 0xac, 0x74, 0xce, 0x66,
0x91, 0xa4, 0x15, 0xea, 0x58, 0xb4, 0x0b, 0x66, 0x35, 0x3f, 0x66, 0xbd, 0x9b, 0x0c, 0x93, 0x04,
0xc8, 0x46, 0x77, 0xcc, 0xc6, 0x3f, 0xf7, 0x3c, 0xa8, 0x1a, 0x9a, 0x83, 0xde, 0xa8, 0x05, 0xf0,
0x59, 0xd5, 0x10, 0xef, 0x7b, 0x45, 0xbb, 0x9e, 0x6d, 0xd7, 0xbb, 0xce, 0xdb, 0x75, 0x0f, 0x3c,
0x7b, 0xab, 0x7a, 0xbb, 0xa3, 0xfd, 0xd7, 0xaf, 0x9f, 0xb7, 0x46, 0x8f, 0x77, 0xcd, 0xb5, 0xca,
0xa5, 0xf1, 0xe5, 0xc7, 0x76, 0xe8, 0x7c, 0x6e, 0x87, 0xce, 0xf7, 0x76, 0xe8, 0x3c, 0x9e, 0x86,
0x8a, 0x9e, 0xd6, 0x81, 0xb7, 0xc0, 0xd8, 0x97, 0x3a, 0xc4, 0x54, 0xe3, 0xb3, 0x19, 0xce, 0x16,
0x4b, 0xbf, 0xfe, 0x4b, 0x04, 0x4d, 0xf3, 0xe2, 0x8b, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x82,
0x85, 0x6d, 0x31, 0x2b, 0x02, 0x00, 0x00,
// 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

@@ -19,7 +19,6 @@ message VersionMessage {
string GoVersion = 6;
string Compiler = 7;
string Platform = 8;
string KsonnetVersion = 9;
}
// VersionService returns the version of the API server.

View File

@@ -11,34 +11,12 @@ import (
// load the gcp plugin (required to authenticate against GKE clusters).
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"github.com/argoproj/argo-cd/util/argo"
// load the oidc plugin (required to authenticate with OpenID Connect).
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/fields"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)
func TestAppManagement(t *testing.T) {
assertAppHasEvent := func(a *v1alpha1.Application, action string, reason string) {
list, err := fixture.KubeClient.CoreV1().Events(fixture.Namespace).List(metav1.ListOptions{
FieldSelector: fields.SelectorFromSet(map[string]string{
"involvedObject.name": a.Name,
"involvedObject.uid": string(a.UID),
"involvedObject.namespace": fixture.Namespace,
}).String(),
})
if err != nil {
t.Fatalf("Unable to get app events %v", err)
}
for i := range list.Items {
event := list.Items[i]
if event.Reason == reason && event.Action == action {
return
}
}
t.Errorf("Unable to find event with reason=%s; action=%s", reason, action)
}
testApp := &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Source: v1alpha1.ApplicationSource{
@@ -74,7 +52,6 @@ func TestAppManagement(t *testing.T) {
assert.Equal(t, ".", app.Spec.Source.Path)
assert.Equal(t, fixture.Namespace, app.Spec.Destination.Namespace)
assert.Equal(t, fixture.Config.Host, app.Spec.Destination.Server)
assertAppHasEvent(app, "create", argo.EventReasonResourceCreated)
})
t.Run("TestAppDeletion", func(t *testing.T) {
@@ -85,15 +62,10 @@ func TestAppManagement(t *testing.T) {
t.Fatalf("Unable to delete app %v", err)
}
a, err := fixture.AppClient.ArgoprojV1alpha1().Applications(fixture.Namespace).Get(app.Name, metav1.GetOptions{})
_, err = fixture.AppClient.ArgoprojV1alpha1().Applications(fixture.Namespace).Get(app.Name, metav1.GetOptions{})
if err != nil && !errors.IsNotFound(err) {
t.Fatalf("Unable to get app %v", err)
} else {
assert.NotNil(t, a.DeletionTimestamp)
}
assertAppHasEvent(app, "delete", argo.EventReasonResourceDeleted)
assert.NotNil(t, err)
assert.True(t, errors.IsNotFound(err))
})
t.Run("TestTrackAppStateAndSyncApp", func(t *testing.T) {
@@ -108,7 +80,6 @@ func TestAppManagement(t *testing.T) {
if err != nil {
t.Fatalf("Unable to sync app %v", err)
}
assertAppHasEvent(app, "sync", argo.EventReasonResourceUpdated)
WaitUntil(t, func() (done bool, err error) {
app, err = fixture.AppClient.ArgoprojV1alpha1().Applications(fixture.Namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})
@@ -149,8 +120,6 @@ func TestAppManagement(t *testing.T) {
t.Fatalf("Unable to sync app %v", err)
}
assertAppHasEvent(app, "rollback", argo.EventReasonResourceUpdated)
WaitUntil(t, func() (done bool, err error) {
app, err = fixture.AppClient.ArgoprojV1alpha1().Applications(fixture.Namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})
return err == nil && app.Status.ComparisonResult.Status == v1alpha1.ComparisonStatusSynced, err
@@ -170,7 +139,7 @@ func TestAppManagement(t *testing.T) {
WaitUntil(t, func() (done bool, err error) {
app, err := fixture.AppClient.ArgoprojV1alpha1().Applications(fixture.Namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})
return err == nil && app.Status.ComparisonResult.Status == v1alpha1.ComparisonStatusUnknown && len(app.Status.Conditions) > 0, err
return err == nil && app.Status.ComparisonResult.Status != v1alpha1.ComparisonStatusUnknown && len(app.Status.Conditions) > 0, err
})
app, err := fixture.AppClient.ArgoprojV1alpha1().Applications(fixture.Namespace).Get(app.ObjectMeta.Name, metav1.GetOptions{})

View File

@@ -21,9 +21,6 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"path"
"path/filepath"
"github.com/argoproj/argo-cd/cmd/argocd/commands"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/controller"
@@ -413,14 +410,7 @@ func (c *FakeGitClient) LsRemote(s string) (string, error) {
}
func (c *FakeGitClient) LsFiles(s string) ([]string, error) {
matches, err := filepath.Glob(path.Join(c.root, s))
if err != nil {
return nil, err
}
for i := range matches {
matches[i] = strings.TrimPrefix(matches[i], c.root)
}
return matches, nil
return []string{"abcdef123456890"}, nil
}
func (c *FakeGitClient) CommitSHA() (string, error) {

View File

@@ -17,8 +17,7 @@ func TestMain(m *testing.M) {
println(fmt.Sprintf("Unable to create e2e fixture: %v", err))
os.Exit(-1)
} else {
code := m.Run()
fixture.TearDown()
os.Exit(code)
defer fixture.TearDown()
m.Run()
}
}

View File

@@ -7,34 +7,12 @@ import (
"time"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/argo"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
)
func TestProjectManagement(t *testing.T) {
assertProjHasEvent := func(a *v1alpha1.AppProject, action string, reason string) {
list, err := fixture.KubeClient.CoreV1().Events(fixture.Namespace).List(metav1.ListOptions{
FieldSelector: fields.SelectorFromSet(map[string]string{
"involvedObject.name": a.Name,
"involvedObject.uid": string(a.UID),
"involvedObject.namespace": fixture.Namespace,
}).String(),
})
if err != nil {
t.Fatalf("Unable to get app events %v", err)
}
for i := range list.Items {
event := list.Items[i]
if event.Reason == reason && event.Action == action {
return
}
}
t.Errorf("Unable to find event with reason=%s; action=%s", reason, action)
}
t.Run("TestProjectCreation", func(t *testing.T) {
projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
_, err := fixture.RunCli("proj", "create", projectName,
@@ -61,13 +39,11 @@ func TestProjectManagement(t *testing.T) {
assert.Equal(t, 1, len(proj.Spec.SourceRepos))
assert.Equal(t, "https://github.com/argoproj/argo-cd.git", proj.Spec.SourceRepos[0])
assertProjHasEvent(proj, "create", argo.EventReasonResourceCreated)
})
t.Run("TestProjectDeletion", func(t *testing.T) {
projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
proj, err := fixture.AppClient.ArgoprojV1alpha1().AppProjects(fixture.Namespace).Create(&v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}})
_, err := fixture.AppClient.ArgoprojV1alpha1().AppProjects(fixture.Namespace).Create(&v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}})
if err != nil {
t.Fatalf("Unable to create project %v", err)
}
@@ -79,7 +55,6 @@ func TestProjectManagement(t *testing.T) {
_, err = fixture.AppClient.ArgoprojV1alpha1().AppProjects(fixture.Namespace).Get(projectName, metav1.GetOptions{})
assert.True(t, errors.IsNotFound(err))
assertProjHasEvent(proj, "delete", argo.EventReasonResourceDeleted)
})
t.Run("TestSetProject", func(t *testing.T) {
@@ -109,7 +84,6 @@ func TestProjectManagement(t *testing.T) {
assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.Destinations[1].Server)
assert.Equal(t, "service", proj.Spec.Destinations[1].Namespace)
assertProjHasEvent(proj, "update", argo.EventReasonResourceUpdated)
})
t.Run("TestAddProjectDestination", func(t *testing.T) {
@@ -144,7 +118,6 @@ func TestProjectManagement(t *testing.T) {
assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.Destinations[0].Server)
assert.Equal(t, "test1", proj.Spec.Destinations[0].Namespace)
assertProjHasEvent(proj, "update", argo.EventReasonResourceUpdated)
})
t.Run("TestRemoveProjectDestination", func(t *testing.T) {
@@ -185,7 +158,6 @@ func TestProjectManagement(t *testing.T) {
}
assert.Equal(t, projectName, proj.Name)
assert.Equal(t, 0, len(proj.Spec.Destinations))
assertProjHasEvent(proj, "update", argo.EventReasonResourceUpdated)
})
t.Run("TestAddProjectSource", func(t *testing.T) {
@@ -244,6 +216,5 @@ func TestProjectManagement(t *testing.T) {
}
assert.Equal(t, projectName, proj.Name)
assert.Equal(t, 0, len(proj.Spec.SourceRepos))
assertProjHasEvent(proj, "update", argo.EventReasonResourceUpdated)
})
}

View File

@@ -6,20 +6,17 @@ import (
"errors"
"fmt"
"path"
"strings"
"time"
"github.com/ghodss/yaml"
"github.com/ksonnet/ksonnet/pkg/app"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"strings"
"github.com/argoproj/argo-cd/common"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
@@ -29,10 +26,10 @@ import (
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/git"
)
const (
errDestinationMissing = "Destination server and/or namespace missing from app spec"
"github.com/ghodss/yaml"
"github.com/ksonnet/ksonnet/pkg/app"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// FormatAppConditions returns string representation of give app condition list
@@ -214,31 +211,59 @@ func GetSpecErrors(
}
if repoAccessable {
appSourceType, err := queryAppSourceType(ctx, spec, repoRes, repoClient)
// Verify app.yaml is functional
req := repository.GetFileRequest{
Repo: &argoappv1.Repository{
Repo: spec.Source.RepoURL,
},
Revision: spec.Source.TargetRevision,
Path: path.Join(spec.Source.Path, "app.yaml"),
}
if repoRes != nil {
req.Repo.Username = repoRes.Username
req.Repo.Password = repoRes.Password
req.Repo.SSHPrivateKey = repoRes.SSHPrivateKey
}
getRes, err := repoClient.GetFile(ctx, &req)
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("Unable to determine app source type: %v", err),
Message: fmt.Sprintf("Unable to load app.yaml: %v", err),
})
} else {
switch appSourceType {
case repository.AppSourceKsonnet:
appYamlConditions := verifyAppYAML(ctx, repoRes, spec, repoClient)
if len(appYamlConditions) > 0 {
conditions = append(conditions, appYamlConditions...)
var appSpec app.Spec
err = yaml.Unmarshal(getRes.Data, &appSpec)
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: "app.yaml is not a valid ksonnet app spec",
})
} else {
// Default revision to HEAD if unspecified
if spec.Source.TargetRevision == "" {
spec.Source.TargetRevision = "HEAD"
}
case repository.AppSourceHelm:
helmConditions := verifyHelmChart(ctx, repoRes, spec, repoClient)
if len(helmConditions) > 0 {
conditions = append(conditions, helmConditions...)
}
case repository.AppSourceDirectory:
maniDirConditions := verifyManifestDirectory(ctx, repoRes, spec, repoClient)
if len(maniDirConditions) > 0 {
conditions = append(conditions, maniDirConditions...)
}
}
// Verify the specified environment is defined in it
envSpec, ok := appSpec.Environments[spec.Source.Environment]
if !ok || envSpec == nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("environment '%s' does not exist in ksonnet app", spec.Source.Environment),
})
}
if envSpec != nil {
// If server and namespace are not supplied, pull it from the app.yaml
if spec.Destination.Server == "" {
spec.Destination.Server = envSpec.Destination.Server
}
if spec.Destination.Namespace == "" {
spec.Destination.Namespace = envSpec.Destination.Namespace
}
}
}
}
}
@@ -287,161 +312,3 @@ func GetAppProject(spec *argoappv1.ApplicationSpec, appclientset appclientset.In
}
return proj, err
}
// queryAppSourceType queries repo server for yaml files in a directory, and determines its
// application source type based on the files in the directory.
func queryAppSourceType(ctx context.Context, spec *argoappv1.ApplicationSpec, repoRes *argoappv1.Repository, repoClient repository.RepositoryServiceClient) (repository.AppSourceType, error) {
req := repository.ListDirRequest{
Repo: &argoappv1.Repository{
Repo: spec.Source.RepoURL,
},
Revision: spec.Source.TargetRevision,
Path: fmt.Sprintf("%s/*.yaml", spec.Source.Path),
}
if repoRes != nil {
req.Repo.Username = repoRes.Username
req.Repo.Password = repoRes.Password
req.Repo.SSHPrivateKey = repoRes.SSHPrivateKey
}
getRes, err := repoClient.ListDir(ctx, &req)
if err != nil {
return "", err
}
for _, gitPath := range getRes.Items {
// gitPath may look like: app.yaml, or some/subpath/app.yaml
trimmedPath := strings.TrimPrefix(gitPath, spec.Source.Path)
trimmedPath = strings.TrimPrefix(trimmedPath, "/")
if trimmedPath == "app.yaml" {
return repository.AppSourceKsonnet, nil
}
if trimmedPath == "Chart.yaml" {
return repository.AppSourceHelm, nil
}
}
return repository.AppSourceDirectory, nil
}
// verifyAppYAML verifies that a ksonnet app.yaml is functional
func verifyAppYAML(ctx context.Context, repoRes *argoappv1.Repository, spec *argoappv1.ApplicationSpec, repoClient repository.RepositoryServiceClient) []argoappv1.ApplicationCondition {
req := repository.GetFileRequest{
Repo: &argoappv1.Repository{
Repo: spec.Source.RepoURL,
},
Revision: spec.Source.TargetRevision,
Path: path.Join(spec.Source.Path, "app.yaml"),
}
if repoRes != nil {
req.Repo.Username = repoRes.Username
req.Repo.Password = repoRes.Password
req.Repo.SSHPrivateKey = repoRes.SSHPrivateKey
}
getRes, err := repoClient.GetFile(ctx, &req)
var conditions []argoappv1.ApplicationCondition
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("Unable to load app.yaml: %v", err),
})
} else {
var appSpec app.Spec
err = yaml.Unmarshal(getRes.Data, &appSpec)
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: "app.yaml is not a valid ksonnet app spec",
})
} else {
// Default revision to HEAD if unspecified
if spec.Source.TargetRevision == "" {
spec.Source.TargetRevision = "HEAD"
}
// Verify the specified environment is defined in it
envSpec, ok := appSpec.Environments[spec.Source.Environment]
if !ok || envSpec == nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("environment '%s' does not exist in ksonnet app", spec.Source.Environment),
})
}
if envSpec != nil {
// If server and namespace are not supplied, pull it from the app.yaml
if spec.Destination.Server == "" {
spec.Destination.Server = envSpec.Destination.Server
}
if spec.Destination.Namespace == "" {
spec.Destination.Namespace = envSpec.Destination.Namespace
}
}
}
}
return conditions
}
// verifyHelmChart verifies a helm chart is functional
func verifyHelmChart(ctx context.Context, repoRes *argoappv1.Repository, spec *argoappv1.ApplicationSpec, repoClient repository.RepositoryServiceClient) []argoappv1.ApplicationCondition {
var conditions []argoappv1.ApplicationCondition
if spec.Destination.Server == "" || spec.Destination.Namespace == "" {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: errDestinationMissing,
})
}
req := repository.GetFileRequest{
Repo: &argoappv1.Repository{
Repo: spec.Source.RepoURL,
},
Revision: spec.Source.TargetRevision,
Path: path.Join(spec.Source.Path, "Chart.yaml"),
}
if repoRes != nil {
req.Repo.Username = repoRes.Username
req.Repo.Password = repoRes.Password
req.Repo.SSHPrivateKey = repoRes.SSHPrivateKey
}
_, err := repoClient.GetFile(ctx, &req)
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("Unable to load Chart.yaml: %v", err),
})
}
return conditions
}
// verifyManifestDirectory verifies a repo path contains at least one valid k8s manifest
func verifyManifestDirectory(ctx context.Context, repoRes *argoappv1.Repository, spec *argoappv1.ApplicationSpec, repoClient repository.RepositoryServiceClient) []argoappv1.ApplicationCondition {
var conditions []argoappv1.ApplicationCondition
if spec.Destination.Server == "" || spec.Destination.Namespace == "" {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: errDestinationMissing,
})
}
req := repository.ManifestRequest{
Repo: &argoappv1.Repository{
Repo: spec.Source.RepoURL,
},
Revision: spec.Source.TargetRevision,
Path: spec.Source.Path,
}
if repoRes != nil {
req.Repo.Username = repoRes.Username
req.Repo.Password = repoRes.Password
req.Repo.SSHPrivateKey = repoRes.SSHPrivateKey
}
manRes, err := repoClient.GenerateManifest(ctx, &req)
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("Unable to get manifests in %s: %v", spec.Source.Path, err),
})
} else if len(manRes.Manifests) == 0 {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("Path '%s' contained no kubernetes manifests", spec.Source.Path),
})
}
return conditions
}

View File

@@ -1,85 +0,0 @@
package argo
import (
log "github.com/sirupsen/logrus"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
"fmt"
"time"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
type AuditLogger struct {
kIf kubernetes.Interface
component string
ns string
}
type EventInfo struct {
Action string
Reason string
Username string
}
const (
EventReasonStatusRefreshed = "StatusRefreshed"
EventReasonResourceCreated = "ResourceCreated"
EventReasonResourceUpdated = "ResourceUpdated"
EventReasonResourceDeleted = "ResourceDeleted"
)
func (l *AuditLogger) logEvent(objMeta metav1.ObjectMeta, gvk schema.GroupVersionKind, info EventInfo, eventType string) {
var message string
if info.Username != "" {
message = fmt.Sprintf("User %s executed action %s", info.Username, info.Action)
} else {
message = fmt.Sprintf("Unknown user executed action %s", info.Action)
}
t := metav1.Time{Time: time.Now()}
_, err := l.kIf.CoreV1().Events(l.ns).Create(&v1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%v.%x", objMeta.Name, t.UnixNano()),
},
Source: v1.EventSource{
Component: l.component,
},
InvolvedObject: v1.ObjectReference{
Kind: gvk.Kind,
Name: objMeta.Name,
Namespace: objMeta.Namespace,
ResourceVersion: objMeta.ResourceVersion,
APIVersion: gvk.Version,
UID: objMeta.UID,
},
FirstTimestamp: t,
LastTimestamp: t,
Count: 1,
Message: message,
Type: eventType,
Action: info.Action,
Reason: info.Reason,
})
if err != nil {
log.Errorf("Unable to create audit event: %v", err)
}
}
func (l *AuditLogger) LogAppEvent(app *v1alpha1.Application, info EventInfo, eventType string) {
l.logEvent(app.ObjectMeta, v1alpha1.ApplicationSchemaGroupVersionKind, info, eventType)
}
func (l *AuditLogger) LogAppProjEvent(proj *v1alpha1.AppProject, info EventInfo, eventType string) {
l.logEvent(proj.ObjectMeta, v1alpha1.AppProjectSchemaGroupVersionKind, info, eventType)
}
func NewAuditLogger(ns string, kIf kubernetes.Interface, component string) *AuditLogger {
return &AuditLogger{
ns: ns,
kIf: kIf,
component: component,
}
}

View File

@@ -1,4 +1,4 @@
package config
package cli
import (
"encoding/json"

View File

@@ -1,4 +1,4 @@
package config
package cli
import (
"fmt"

View File

@@ -58,7 +58,6 @@ func (s *db) CreateCluster(ctx context.Context, c *appv1.Cluster) (*appv1.Cluste
},
}
clusterSecret.Data = clusterToData(c)
clusterSecret.Annotations = AnnotationsFromConnectionState(&c.ConnectionState)
clusterSecret, err = s.kubeclientset.CoreV1().Secrets(s.ns).Create(clusterSecret)
if err != nil {
if apierr.IsAlreadyExists(err) {
@@ -89,24 +88,17 @@ func (s *db) WatchClusters(ctx context.Context, callback func(*ClusterEvent)) er
if err != nil {
return err
}
defer w.Stop()
done := make(chan bool)
go func() {
for next := range w.ResultChan() {
secret := next.Object.(*apiv1.Secret)
cluster := SecretToCluster(secret)
callback(&ClusterEvent{
Type: next.Type,
Cluster: cluster,
})
}
done <- true
<-ctx.Done()
w.Stop()
}()
select {
case <-done:
case <-ctx.Done():
for next := range w.ResultChan() {
secret := next.Object.(*apiv1.Secret)
cluster := SecretToCluster(secret)
callback(&ClusterEvent{
Type: next.Type,
Cluster: cluster,
})
}
return nil
}

View File

@@ -57,7 +57,6 @@ func (s *db) CreateRepository(ctx context.Context, r *appsv1.Repository) (*appsv
},
}
repoSecret.Data = repoToData(r)
repoSecret.Annotations = AnnotationsFromConnectionState(&r.ConnectionState)
repoSecret, err := s.kubeclientset.CoreV1().Secrets(s.ns).Create(repoSecret)
if err != nil {
if apierr.IsAlreadyExists(err) {

View File

@@ -13,8 +13,6 @@ import (
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/scheme"
jsonutil "github.com/argoproj/argo-cd/util/json"
)
type DiffResult struct {
@@ -45,7 +43,7 @@ func TwoWayDiff(config, live *unstructured.Unstructured) *DiffResult {
configObj = config.Object
}
if live != nil {
liveObj = jsonutil.RemoveMapFields(configObj, live.Object)
liveObj = RemoveMapFields(configObj, live.Object)
}
gjDiff := gojsondiff.New().CompareObjects(configObj, liveObj)
dr := DiffResult{
@@ -60,7 +58,7 @@ func TwoWayDiff(config, live *unstructured.Unstructured) *DiffResult {
func ThreeWayDiff(orig, config, live *unstructured.Unstructured) *DiffResult {
orig = removeNamespaceAnnotation(orig)
// remove extra fields in the live, that were not in the original object
liveObj := jsonutil.RemoveMapFields(orig.Object, live.Object)
liveObj := RemoveMapFields(orig.Object, live.Object)
// now we have a pruned live object
gjDiff := gojsondiff.New().CompareObjects(config.Object, liveObj)
dr := DiffResult{
@@ -230,3 +228,42 @@ func (d *DiffResult) ASCIIFormat(left *unstructured.Unstructured, formatOpts for
asciiFmt := formatter.NewAsciiFormatter(left.Object, formatOpts)
return asciiFmt.Format(d.Diff)
}
// https://github.com/ksonnet/ksonnet/blob/master/pkg/kubecfg/diff.go
func removeFields(config, live interface{}) interface{} {
switch c := config.(type) {
case map[string]interface{}:
return RemoveMapFields(c, live.(map[string]interface{}))
case []interface{}:
return removeListFields(c, live.([]interface{}))
default:
return live
}
}
// RemoveMapFields remove all non-existent fields in the live that don't exist in the config
func RemoveMapFields(config, live map[string]interface{}) map[string]interface{} {
result := map[string]interface{}{}
for k, v1 := range config {
v2, ok := live[k]
if !ok {
continue
}
result[k] = removeFields(v1, v2)
}
return result
}
func removeListFields(config, live []interface{}) []interface{} {
// If live is longer than config, then the extra elements at the end of the
// list will be returned as-is so they appear in the diff.
result := make([]interface{}, 0, len(live))
for i, v2 := range live {
if len(config) > i {
result = append(result, removeFields(config[i], v2))
} else {
result = append(result, v2)
}
}
return result
}

View File

@@ -1,82 +0,0 @@
package grpc
import (
"bytes"
"encoding/json"
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/gogo/protobuf/proto"
"github.com/grpc-ecosystem/go-grpc-middleware/logging"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
"github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"
"github.com/sirupsen/logrus"
)
func logRequest(entry *logrus.Entry, info string, pbMsg interface{}, ctx context.Context, logClaims bool) {
if logClaims {
if data, err := json.Marshal(ctx.Value("claims")); err == nil {
entry = entry.WithField("grpc.request.claims", string(data))
}
}
if p, ok := pbMsg.(proto.Message); ok {
entry = entry.WithField("grpc.request.content", &jsonpbMarshalleble{p})
}
entry.Info(info)
}
type jsonpbMarshalleble struct {
proto.Message
}
func (j *jsonpbMarshalleble) MarshalJSON() ([]byte, error) {
b := &bytes.Buffer{}
if err := grpc_logrus.JsonPbMarshaller.Marshal(b, j.Message); err != nil {
return nil, fmt.Errorf("jsonpb serializer failed: %v", err)
}
return b.Bytes(), nil
}
type loggingServerStream struct {
grpc.ServerStream
entry *logrus.Entry
logClaims bool
info string
}
func (l *loggingServerStream) SendMsg(m interface{}) error {
return l.ServerStream.SendMsg(m)
}
func (l *loggingServerStream) RecvMsg(m interface{}) error {
err := l.ServerStream.RecvMsg(m)
if err == nil {
logRequest(l.entry, l.info, m, l.ServerStream.Context(), l.logClaims)
}
return err
}
func PayloadStreamServerInterceptor(entry *logrus.Entry, logClaims bool, decider grpc_logging.ServerPayloadLoggingDecider) grpc.StreamServerInterceptor {
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if !decider(stream.Context(), info.FullMethod, srv) {
return handler(srv, stream)
}
logEntry := entry.WithFields(ctx_logrus.Extract(stream.Context()).Data)
newStream := &loggingServerStream{ServerStream: stream, entry: logEntry, logClaims: logClaims, info: fmt.Sprintf("received streaming call %s", info.FullMethod)}
return handler(srv, newStream)
}
}
func PayloadUnaryServerInterceptor(entry *logrus.Entry, logClaims bool, decider grpc_logging.ServerPayloadLoggingDecider) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if !decider(ctx, info.FullMethod, info.Server) {
return handler(ctx, req)
}
logEntry := entry.WithFields(ctx_logrus.Extract(ctx).Data)
logRequest(logEntry, fmt.Sprintf("received unary call %s", info.FullMethod), req, ctx, logClaims)
resp, err := handler(ctx, req)
return resp, err
}
}

View File

@@ -16,32 +16,22 @@ import (
)
func GetAppHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
var err error
var health *appv1.HealthStatus
switch obj.GetKind() {
case kube.DeploymentKind:
health, err = getDeploymentHealth(obj)
return getDeploymentHealth(obj)
case kube.ServiceKind:
health, err = getServiceHealth(obj)
return getServiceHealth(obj)
case kube.IngressKind:
health, err = getIngressHealth(obj)
return getIngressHealth(obj)
case kube.StatefulSetKind:
health, err = getStatefulSetHealth(obj)
return getStatefulSetHealth(obj)
case kube.ReplicaSetKind:
health, err = getReplicaSetHealth(obj)
return getReplicaSetHealth(obj)
case kube.DaemonSetKind:
health, err = getDaemonSetHealth(obj)
return getDaemonSetHealth(obj)
default:
health = &appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
return &appv1.HealthStatus{Status: appv1.HealthStatusHealthy}, nil
}
if err != nil {
health.Status = appv1.HealthStatusUnknown
health.StatusDetails = err.Error()
}
return health, err
}
// healthOrder is a list of health codes in order of most healthy to least healthy

View File

@@ -1,112 +0,0 @@
package helm
import (
"fmt"
"io/ioutil"
"os/exec"
"path"
"strings"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
)
// Helm provides wrapper functionality around the `helm` command.
type Helm interface {
// Template returns a list of unstructured objects from a `helm template` command
Template(name string, valuesFiles []string, overrides []*argoappv1.ComponentParameter) ([]*unstructured.Unstructured, error)
// GetParameters returns a list of chart parameters taking into account values in provided YAML files.
GetParameters(valuesFiles []string) ([]*argoappv1.ComponentParameter, error)
}
// NewHelmApp create a new wrapper to run commands on the `helm` command-line tool.
func NewHelmApp(path string) Helm {
return &helm{path: path}
}
type helm struct {
path string
}
func (h *helm) Template(name string, valuesFiles []string, overrides []*argoappv1.ComponentParameter) ([]*unstructured.Unstructured, error) {
args := []string{
"template", h.path, "--name", name,
}
for _, valuesFile := range valuesFiles {
args = append(args, "-f", path.Join(h.path, valuesFile))
}
for _, p := range overrides {
args = append(args, "--set", fmt.Sprintf("%s=%s", p.Name, p.Value))
}
out, err := helmCmd(args...)
if err != nil {
return nil, err
}
return kube.SplitYAML(out)
}
func (h *helm) GetParameters(valuesFiles []string) ([]*argoappv1.ComponentParameter, error) {
out, err := helmCmd("inspect", "values", h.path)
if err != nil {
return nil, err
}
values := append([]string{out})
for _, file := range valuesFiles {
fileValues, err := ioutil.ReadFile(path.Join(h.path, file))
if err != nil {
return nil, fmt.Errorf("failed to read value file %s: %s", file, err)
}
values = append(values, string(fileValues))
}
output := map[string]string{}
for _, file := range values {
values := map[string]interface{}{}
if err = yaml.Unmarshal([]byte(file), &values); err != nil {
return nil, fmt.Errorf("failed to parse values: %s", err)
}
flatVals(values, output)
}
params := make([]*argoappv1.ComponentParameter, 0)
for key, val := range output {
params = append(params, &argoappv1.ComponentParameter{
Name: key,
Value: val,
})
}
return params, nil
}
func helmCmd(args ...string) (string, error) {
cmd := exec.Command("helm", args...)
cmdStr := strings.Join(cmd.Args, " ")
log.Info(cmdStr)
outBytes, err := cmd.Output()
if err != nil {
exErr, ok := err.(*exec.ExitError)
if !ok {
return "", err
}
errOutput := string(exErr.Stderr)
log.Errorf("`%s` failed: %s", cmdStr, errOutput)
return "", fmt.Errorf(strings.TrimSpace(errOutput))
}
out := string(outBytes)
log.Debug(out)
return out, nil
}
func flatVals(input map[string]interface{}, output map[string]string, prefixes ...string) {
for key, val := range input {
if subMap, ok := val.(map[string]interface{}); ok {
flatVals(subMap, output, append(prefixes, fmt.Sprintf("%v", key))...)
} else {
output[strings.Join(append(prefixes, fmt.Sprintf("%v", key)), ".")] = fmt.Sprintf("%v", val)
}
}
}

View File

@@ -1,86 +0,0 @@
package helm
import (
"testing"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
func findParameter(params []*argoappv1.ComponentParameter, name string) *argoappv1.ComponentParameter {
for _, param := range params {
if param.Name == name {
return param
}
}
return nil
}
func TestHelmTemplateParams(t *testing.T) {
h := NewHelmApp("./testdata/minio")
overrides := []*argoappv1.ComponentParameter{
{
Name: "service.type",
Value: "LoadBalancer",
},
{
Name: "service.port",
Value: "1234",
},
}
objs, err := h.Template("test", nil, overrides)
assert.Nil(t, err)
assert.Equal(t, 5, len(objs))
for _, obj := range objs {
if obj.GetKind() == "Service" && obj.GetName() == "test-minio" {
var svc apiv1.Service
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &svc)
assert.Nil(t, err)
assert.Equal(t, apiv1.ServiceTypeLoadBalancer, svc.Spec.Type)
assert.Equal(t, int32(1234), svc.Spec.Ports[0].TargetPort.IntVal)
}
}
}
func TestHelmTemplateValues(t *testing.T) {
h := NewHelmApp("./testdata/redis")
valuesFiles := []string{"values-production.yaml"}
objs, err := h.Template("test", valuesFiles, nil)
assert.Nil(t, err)
assert.Equal(t, 8, len(objs))
for _, obj := range objs {
if obj.GetKind() == "Deployment" && obj.GetName() == "test-redis-slave" {
var dep appsv1.Deployment
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &dep)
assert.Nil(t, err)
assert.Equal(t, int32(3), *dep.Spec.Replicas)
}
}
}
func TestHelmGetParams(t *testing.T) {
h := NewHelmApp("./testdata/redis")
params, err := h.GetParameters([]string{})
assert.Nil(t, err)
slaveCountParam := findParameter(params, "cluster.slaveCount")
assert.NotNil(t, slaveCountParam)
assert.Equal(t, slaveCountParam.Value, "1")
}
func TestHelmGetParamsValueFiles(t *testing.T) {
h := NewHelmApp("./testdata/redis")
params, err := h.GetParameters([]string{"values-production.yaml"})
assert.Nil(t, err)
slaveCountParam := findParameter(params, "cluster.slaveCount")
assert.NotNil(t, slaveCountParam)
assert.Equal(t, slaveCountParam.Value, "3")
}

View File

@@ -1,21 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj

View File

@@ -1,18 +0,0 @@
apiVersion: v1
description: Minio is a high performance distributed object storage server, designed for large-scale private cloud infrastructure.
name: minio
version: 1.6.0
appVersion: RELEASE.2018-07-10T01-42-11Z
keywords:
- storage
- object-storage
- S3
home: https://minio.io
icon: https://www.minio.io/img/logo_160x160.png
sources:
- https://github.com/minio/minio
maintainers:
- name: Acaleph
email: hello@acale.ph
- name: Minio
email: dev@minio.io

View File

@@ -1,226 +0,0 @@
Minio
=====
[Minio](https://minio.io) is a lightweight, AWS S3 compatible object storage server. It is best suited for storing unstructured data such as photos, videos, log files, backups, VM and container images. Size of an object can range from a few KBs to a maximum of 5TB. Minio server is light enough to be bundled with the application stack, similar to NodeJS, Redis and MySQL.
Minio supports [distributed mode](https://docs.minio.io/docs/distributed-minio-quickstart-guide). In distributed mode, you can pool multiple drives (even on different machines) into a single object storage server.
Introduction
------------
This chart bootstraps Minio deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
Prerequisites
-------------
- Kubernetes 1.4+ with Beta APIs enabled for default standalone mode.
- Kubernetes 1.5+ with Beta APIs enabled to run Minio in [distributed mode](#distributed-minio).
- PV provisioner support in the underlying infrastructure.
Installing the Chart
--------------------
Install this chart using:
```bash
$ helm install stable/minio
```
The command deploys Minio on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
### Release name
An instance of a chart running in a Kubernetes cluster is called a release. Each release is identified by a unique name within the cluster. Helm automatically assigns a unique release name after installing the chart. You can also set your preferred name by:
```bash
$ helm install --name my-release stable/minio
```
### Access and Secret keys
By default a pre-generated access and secret key will be used. To override the default keys, pass the access and secret keys as arguments to helm install.
```bash
$ helm install --set accessKey=myaccesskey,secretKey=mysecretkey \
stable/minio
```
### Updating Minio configuration via Helm
[ConfigMap](https://kubernetes.io/docs/user-guide/configmap/) allows injecting containers with configuration data even while a Helm release is deployed.
To update your Minio server configuration while it is deployed in a release, you need to
1. Check all the configurable values in the Minio chart using `helm inspect values stable/minio`.
2. Override the `minio_server_config` settings in a YAML formatted file, and then pass that file like this `helm upgrade -f config.yaml stable/minio`.
3. Restart the Minio server(s) for the changes to take effect.
You can also check the history of upgrades to a release using `helm history my-release`. Replace `my-release` with the actual release name.
Uninstalling the Chart
----------------------
Assuming your release is named as `my-release`, delete it using the command:
```bash
$ helm delete my-release
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
Configuration
-------------
The following table lists the configurable parameters of the Minio chart and their default values.
| Parameter | Description | Default |
|----------------------------|-------------------------------------|---------------------------------------------------------|
| `image.repository` | Image repository | `minio/minio` |
| `image.tag` | Minio image tag. Possible values listed [here](https://hub.docker.com/r/minio/minio/tags/).| `RELEASE.2018-07-10T01-42-11Z`|
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `mcImage.repository` | Client image repository | `minio/mc` |
| `mcImage.tag` | mc image tag. Possible values listed [here](https://hub.docker.com/r/minio/mc/tags/).| `RELEASE.2018-06-09T02-18-09Z`|
| `mcImage.pullPolicy` | mc Image pull policy | `IfNotPresent` |
| `ingress.enabled` | Enables Ingress | `false` |
| `ingress.annotations` | Ingress annotations | `{}` |
| `ingress.hosts` | Ingress accepted hostnames | `[]` |
| `ingress.tls` | Ingress TLS configuration | `[]` |
| `mode` | Minio server mode (`standalone` or `distributed`)| `standalone` |
| `replicas` | Number of nodes (applicable only for Minio distributed mode). Should be 4 <= x <= 32 | `4` |
| `accessKey` | Default access key (5 to 20 characters) | `AKIAIOSFODNN7EXAMPLE` |
| `secretKey` | Default secret key (8 to 40 characters) | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` |
| `configPath` | Default config file location | `~/.minio` |
| `mountPath` | Default mount location for persistent drive| `/export` |
| `service.type` | Kubernetes service type | `ClusterIP` |
| `service.port` | Kubernetes port where service is exposed| `9000` |
| `service.annotations` | Service annotations | `{}` |
| `persistence.enabled` | Use persistent volume to store data | `true` |
| `persistence.size` | Size of persistent volume claim | `10Gi` |
| `persistence.existingClaim`| Use an existing PVC to persist data | `nil` |
| `persistence.storageClass` | Type of persistent volume claim | `generic` |
| `persistence.accessMode` | ReadWriteOnce or ReadOnly | `ReadWriteOnce` |
| `persistence.subPath` | Mount a sub directory of the persistent volume if set | `""` |
| `resources` | CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `100m` |
| `priorityClassName` | Pod priority settings | `""` |
| `nodeSelector` | Node labels for pod assignment | `{}` |
| `affinity` | Affinity settings for pod assignment | `{}` |
| `tolerations` | Toleration labels for pod assignment | `[]` |
| `defaultBucket.enabled` | If set to true, a bucket will be created after minio install | `false` |
| `defaultBucket.name` | Bucket name | `bucket` |
| `defaultBucket.policy` | Bucket policy | `none` |
| `defaultBucket.purge` | Purge the bucket if already exists | `false` |
| `azuregateway.enabled` | Use minio as an [azure gateway](https://docs.minio.io/docs/minio-gateway-for-azure)| `false` |
| `gcsgateway.enabled` | Use minio as a [Google Cloud Storage gateway](https://docs.minio.io/docs/minio-gateway-for-gcs)| `false` |
| `gcsgateway.gcsKeyJson` | credential json file of service account key | `""` |
| `gcsgateway.projectId` | Google cloud project id | `""` |
| `nasgateway.enabled` | Use minio as a [NAS gateway](https://docs.minio.io/docs/minio-gateway-for-nas) | `false` |
| `nasgateway.replicas` | Number of NAS gateway instances to be run in parallel on a PV | `4` |
Some of the parameters above map to the env variables defined in the [Minio DockerHub image](https://hub.docker.com/r/minio/minio/).
You can specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
```bash
$ helm install --name my-release \
--set persistence.size=100Gi \
stable/minio
```
The above command deploys Minio server with a 100Gi backing persistent volume.
Alternately, you can provide a YAML file that specifies parameter values while installing the chart. For example,
```bash
$ helm install --name my-release -f values.yaml stable/minio
```
> **Tip**: You can use the default [values.yaml](values.yaml)
Distributed Minio
-----------
This chart provisions a Minio server in standalone mode, by default. To provision Minio server in [distributed mode](https://docs.minio.io/docs/distributed-minio-quickstart-guide), set the `mode` field to `distributed`,
```bash
$ helm install --set mode=distributed stable/minio
```
This provisions Minio server in distributed mode with 4 nodes. To change the number of nodes in your distributed Minio server, set the `replicas` field,
```bash
$ helm install --set mode=distributed,replicas=8 stable/minio
```
This provisions Minio server in distributed mode with 8 nodes. Note that the `replicas` value should be an integer between 4 and 16 (inclusive).
### StatefulSet [limitations](http://kubernetes.io/docs/concepts/abstractions/controllers/statefulsets/#limitations) applicable to distributed Minio
1. StatefulSets need persistent storage, so the `persistence.enabled` flag is ignored when `mode` is set to `distributed`.
2. When uninstalling a distributed Minio release, you'll need to manually delete volumes associated with the StatefulSet.
NAS Gateway
-----------
### Prerequisites
Minio in [NAS gateway mode](https://docs.minio.io/docs/minio-gateway-for-nas) can be used to create multiple Minio instances backed by single PV in `ReadWriteMany` mode. Currently few [Kubernetes volume plugins](https://kubernetes.io/docs/user-guide/persistent-volumes/#access-modes) support `ReadWriteMany` mode. To deploy Minio NAS gateway with Helm chart you'll need to have a Persistent Volume running with one of the supported volume plugins. [This document](https://kubernetes.io/docs/user-guide/volumes/#nfs)
outlines steps to create a NFS PV in Kubernetes cluster.
### Provision NAS Gateway Minio instances
To provision Minio servers in [NAS gateway mode](https://docs.minio.io/docs/minio-gateway-for-nas), set the `nasgateway.enabled` field to `true`,
```bash
$ helm install --set nasgateway.enabled=true stable/minio
```
This provisions 4 Minio NAS gateway instances backed by single storage. To change the number of instances in your Minio deployment, set the `replicas` field,
```bash
$ helm install --set nasgateway.enabled=true,nasgateway.replicas=8 stable/minio
```
This provisions Minio NAS gateway with 8 instances.
Persistence
-----------
This chart provisions a PersistentVolumeClaim and mounts corresponding persistent volume to default location `/export`. You'll need physical storage available in the Kubernetes cluster for this to work. If you'd rather use `emptyDir`, disable PersistentVolumeClaim by:
```bash
$ helm install --set persistence.enabled=false stable/minio
```
> *"An emptyDir volume is first created when a Pod is assigned to a Node, and exists as long as that Pod is running on that node. When a Pod is removed from a node for any reason, the data in the emptyDir is deleted forever."*
Existing PersistentVolumeClaim
------------------------------
If a Persistent Volume Claim already exists, specify it during installation.
1. Create the PersistentVolume
1. Create the PersistentVolumeClaim
1. Install the chart
```bash
$ helm install --set persistence.existingClaim=PVC_NAME stable/minio
```
NetworkPolicy
-------------
To enable network policy for Minio,
install [a networking plugin that implements the Kubernetes
NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin),
and set `networkPolicy.enabled` to `true`.
For Kubernetes v1.5 & v1.6, you must also turn on NetworkPolicy by setting
the DefaultDeny namespace annotation. Note: this will enforce policy for _all_ pods in the namespace:
kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}"
With NetworkPolicy enabled, traffic will be limited to just port 9000.
For more precise policy, set `networkPolicy.allowExternal=true`. This will
only allow pods with the generated client label to connect to Minio.
This label will be displayed in the output of a successful install.

View File

@@ -1,44 +0,0 @@
{{- if eq .Values.service.type "ClusterIP" "NodePort" }}
Minio can be accessed via port {{ .Values.service.port }} on the following DNS name from within your cluster:
{{ template "minio.fullname" . }}-svc.{{ .Release.Namespace }}.svc.cluster.local
To access Minio from localhost, run the below commands:
1. export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "release={{ template "minio.fullname" . }}" -o jsonpath="{.items[0].metadata.name}")
2. kubectl port-forward $POD_NAME 9000 --namespace {{ .Release.Namespace }}
Read more about port forwarding here: http://kubernetes.io/docs/user-guide/kubectl/kubectl_port-forward/
You can now access Minio server on http://localhost:9000. Follow the below steps to connect to Minio server with mc client:
1. Download the Minio mc client - https://docs.minio.io/docs/minio-client-quickstart-guide
2. mc config host add {{ template "minio.fullname" . }}-local http://localhost:9000 {{ .Values.accessKey }} {{ .Values.secretKey }} S3v4
3. mc ls {{ template "minio.fullname" . }}-local
Alternately, you can use your browser or the Minio SDK to access the server - https://docs.minio.io/categories/17
{{- end }}
{{- if eq .Values.service.type "LoadBalancer" }}
Minio can be accessed via port {{ .Values.service.port }} on an external IP address. Get the service external IP address by:
kubectl get svc --namespace {{ .Release.Namespace }} -l app={{ template "minio.fullname" . }}
Note that the public IP may take a couple of minutes to be available.
You can now access Minio server on http://<External-IP>:9000. Follow the below steps to connect to Minio server with mc client:
1. Download the Minio mc client - https://docs.minio.io/docs/minio-client-quickstart-guide
2. mc config host add {{ template "minio.fullname" . }}-local http://<External-IP>:{{ .Values.service.port }} {{ .Values.accessKey }} {{ .Values.secretKey }} S3v4
3. mc ls {{ template "minio.fullname" . }}-local
Alternately, you can use your browser or the Minio SDK to access the server - https://docs.minio.io/categories/17
{{- end }}
{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }}
Note: Since NetworkPolicy is enabled, only pods with label
{{ template "minio.fullname" . }}-client=true"
will be able to connect to this minio cluster.
{{- end }}

View File

@@ -1,75 +0,0 @@
#!/bin/sh
set -e ; # Have script exit in the event of a failed command.
# connectToMinio
# Use a check-sleep-check loop to wait for Minio service to be available
connectToMinio() {
ATTEMPTS=0 ; LIMIT=29 ; # Allow 30 attempts
set -e ; # fail if we can't read the keys.
ACCESS=$(cat /config/accesskey) ; SECRET=$(cat /config/secretkey) ;
set +e ; # The connections to minio are allowed to fail.
echo "Connecting to Minio server: http://$MINIO_ENDPOINT:$MINIO_PORT" ;
MC_COMMAND="mc config host add myminio http://$MINIO_ENDPOINT:$MINIO_PORT $ACCESS $SECRET" ;
$MC_COMMAND ;
STATUS=$? ;
until [ $STATUS = 0 ]
do
ATTEMPTS=`expr $ATTEMPTS + 1` ;
echo \"Failed attempts: $ATTEMPTS\" ;
if [ $ATTEMPTS -gt $LIMIT ]; then
exit 1 ;
fi ;
sleep 2 ; # 1 second intervals between attempts
$MC_COMMAND ;
STATUS=$? ;
done ;
set -e ; # reset `e` as active
return 0
}
# checkBucketExists ($bucket)
# Check if the bucket exists, by using the exit code of `mc ls`
checkBucketExists() {
BUCKET=$1
CMD=$(/usr/bin/mc ls myminio/$BUCKET > /dev/null 2>&1)
return $?
}
# createBucket ($bucket, $policy, $purge)
# Ensure bucket exists, purging if asked to
createBucket() {
BUCKET=$1
POLICY=$2
PURGE=$3
# Purge the bucket, if set & exists
# Since PURGE is user input, check explicitly for `true`
if [ $PURGE = true ]; then
if checkBucketExists $BUCKET ; then
echo "Purging bucket '$BUCKET'."
set +e ; # don't exit if this fails
/usr/bin/mc rm -r --force myminio/$BUCKET
set -e ; # reset `e` as active
else
echo "Bucket '$BUCKET' does not exist, skipping purge."
fi
fi
# Create the bucket if it does not exist
if ! checkBucketExists $BUCKET ; then
echo "Creating bucket '$BUCKET'"
/usr/bin/mc mb myminio/$BUCKET
else
echo "Bucket '$BUCKET' already exists."
fi
# At this point, the bucket should exist, skip checking for existence
# Set policy on the bucket
echo "Setting policy of bucket '$BUCKET' to '$POLICY'."
/usr/bin/mc policy $POLICY myminio/$BUCKET
}
# Try connecting to Minio instance
connectToMinio
# Create the bucket
createBucket {{ .Values.defaultBucket.name }} {{ .Values.defaultBucket.policy }} {{ .Values.defaultBucket.purge }}

View File

@@ -1,43 +0,0 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "minio.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "minio.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "minio.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Return the appropriate apiVersion for networkpolicy.
*/}}
{{- define "minio.networkPolicy.apiVersion" -}}
{{- if semverCompare ">=1.4-0, <1.7-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "extensions/v1beta1" -}}
{{- else if semverCompare "^1.7-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "networking.k8s.io/v1" -}}
{{- end -}}
{{- end -}}

View File

@@ -1,140 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
data:
initialize: |-
{{ include (print $.Template.BasePath "/_helper_create_bucket.txt") . | indent 4 }}
config.json: |-
{
"version": "26",
"credential": {
"accessKey": {{ .Values.accessKey | quote }},
"secretKey": {{ .Values.secretKey | quote }}
},
"region": {{ .Values.minioConfig.region | quote }},
"browser": {{ .Values.minioConfig.browser | quote }},
"worm": {{ .Values.minioConfig.worm | quote }},
"domain": {{ .Values.minioConfig.domain | quote }},
"storageclass": {
"standard": {{ .Values.minioConfig.storageClass.standardStorageClass | quote }},
"rrs": {{ .Values.minioConfig.storageClass.reducedRedundancyStorageClass | quote }}
},
"cache": {
"drives": {{ .Values.minioConfig.cache.drives }},
"expiry": {{ .Values.minioConfig.cache.expiry | int }},
"maxuse": {{ .Values.minioConfig.cache.maxuse | int }},
"exclude": {{ .Values.minioConfig.cache.exclude }}
},
"notify": {
"amqp": {
"1": {
"enable": {{ .Values.minioConfig.aqmp.enable }},
"url": {{ .Values.minioConfig.aqmp.url | quote }},
"exchange": {{ .Values.minioConfig.aqmp.exchange | quote }},
"routingKey": {{ .Values.minioConfig.aqmp.routingKey | quote }},
"exchangeType": {{ .Values.minioConfig.aqmp.exchangeType | quote }},
"deliveryMode": {{ .Values.minioConfig.aqmp.deliveryMode }},
"mandatory": {{ .Values.minioConfig.aqmp.mandatory }},
"immediate": {{ .Values.minioConfig.aqmp.immediate }},
"durable": {{ .Values.minioConfig.aqmp.durable }},
"internal": {{ .Values.minioConfig.aqmp.internal }},
"noWait": {{ .Values.minioConfig.aqmp.noWait }},
"autoDeleted": {{ .Values.minioConfig.aqmp.autoDeleted }}
}
},
"nats": {
"1": {
"enable": {{ .Values.minioConfig.nats.enable }},
"address": {{ .Values.minioConfig.nats.address | quote }},
"subject": {{ .Values.minioConfig.nats.subject | quote }},
"username": {{ .Values.minioConfig.nats.username | quote }},
"password": {{ .Values.minioConfig.nats.password | quote }},
"token": {{ .Values.minioConfig.nats.token | quote }},
"secure": {{ .Values.minioConfig.nats.secure }},
"pingInterval": {{ .Values.minioConfig.nats.pingInterval | int64 }},
"streaming": {
"enable": {{ .Values.minioConfig.nats.enableStreaming }},
"clusterID": {{ .Values.minioConfig.nats.clusterID | quote }},
"clientID": {{ .Values.minioConfig.nats.clientID | quote }},
"async": {{ .Values.minioConfig.nats.async }},
"maxPubAcksInflight": {{ .Values.minioConfig.nats.maxPubAcksInflight | int }}
}
}
},
"elasticsearch": {
"1": {
"enable": {{ .Values.minioConfig.elasticsearch.enable }},
"format": {{ .Values.minioConfig.elasticsearch.format | quote }},
"url": {{ .Values.minioConfig.elasticsearch.url | quote }},
"index": {{ .Values.minioConfig.elasticsearch.index | quote }}
}
},
"redis": {
"1": {
"enable": {{ .Values.minioConfig.redis.enable }},
"format": {{ .Values.minioConfig.redis.format | quote }},
"address": {{ .Values.minioConfig.redis.address | quote }},
"password": {{ .Values.minioConfig.redis.password | quote }},
"key": {{ .Values.minioConfig.redis.key | quote }}
}
},
"postgresql": {
"1": {
"enable": {{ .Values.minioConfig.postgresql.enable }},
"format": {{ .Values.minioConfig.postgresql.format | quote }},
"connectionString": {{ .Values.minioConfig.postgresql.connectionString | quote }},
"table": {{ .Values.minioConfig.postgresql.table | quote }},
"host": {{ .Values.minioConfig.postgresql.host | quote }},
"port": {{ .Values.minioConfig.postgresql.port | quote }},
"user": {{ .Values.minioConfig.postgresql.user | quote }},
"password": {{ .Values.minioConfig.postgresql.password | quote }},
"database": {{ .Values.minioConfig.postgresql.database | quote }}
}
},
"kafka": {
"1": {
"enable": {{ .Values.minioConfig.kafka.enable }},
"brokers": {{ .Values.minioConfig.kafka.brokers }},
"topic": {{ .Values.minioConfig.kafka.topic | quote }}
}
},
"webhook": {
"1": {
"enable": {{ .Values.minioConfig.webhook.enable }},
"endpoint": {{ .Values.minioConfig.webhook.endpoint | quote }}
}
},
"mysql": {
"1": {
"enable": {{ .Values.minioConfig.mysql.enable }},
"format": {{ .Values.minioConfig.mysql.format | quote }},
"dsnString": {{ .Values.minioConfig.mysql.dsnString | quote }},
"table": {{ .Values.minioConfig.mysql.table | quote }},
"host": {{ .Values.minioConfig.mysql.host | quote }},
"port": {{ .Values.minioConfig.mysql.port | quote }},
"user": {{ .Values.minioConfig.mysql.user | quote }},
"password": {{ .Values.minioConfig.mysql.password | quote }},
"database": {{ .Values.minioConfig.mysql.database | quote }}
}
},
"mqtt": {
"1": {
"enable": {{ .Values.minioConfig.mqtt.enable }},
"broker": {{ .Values.minioConfig.mqtt.broker | quote }},
"topic": {{ .Values.minioConfig.mqtt.topic | quote }},
"qos": {{ .Values.minioConfig.mqtt.qos | int }},
"clientId": {{ .Values.minioConfig.mqtt.clientId | quote }},
"username": {{ .Values.minioConfig.mqtt.username | quote }},
"password": {{ .Values.minioConfig.mqtt.password | quote }},
"reconnectInterval": {{ .Values.minioConfig.mqtt.reconnectInterval | int }},
"keepAliveInterval": {{ .Values.minioConfig.mqtt.keepAliveInterval | int }}
}
}
}
}

View File

@@ -1,126 +0,0 @@
{{- if eq .Values.mode "standalone" }}
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
{{- if .Values.nasgateway.enabled }}
replicas: {{ .Values.nasgateway.replicas }}
{{- end }}
selector:
matchLabels:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
template:
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
spec:
{{- if .Values.priorityClassName }}
priorityClassName: "{{ .Values.priorityClassName }}"
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if .Values.azuregateway.enabled }}
command: [ "/bin/sh",
"-ce",
"cp /tmp/config.json {{ .Values.configPath }} &&
/usr/bin/docker-entrypoint.sh minio -C {{ .Values.configPath }} gateway azure"]
{{- else }}
{{- if .Values.gcsgateway.enabled }}
command: [ "/bin/sh",
"-ce",
"cp /tmp/config.json {{ .Values.configPath }} &&
/usr/bin/docker-entrypoint.sh minio -C {{ .Values.configPath }} gateway gcs {{ .Values.gcsgateway.projectId }}"]
{{- else }}
{{- if .Values.nasgateway.enabled }}
command: [ "/bin/sh",
"-ce",
"cp /tmp/config.json {{ .Values.configPath }} &&
/usr/bin/docker-entrypoint.sh minio -C {{ .Values.configPath }} gateway nas {{ .Values.mountPath }}"]
{{- else }}
command: [ "/bin/sh",
"-ce",
"cp /tmp/config.json {{ .Values.configPath }} &&
/usr/bin/docker-entrypoint.sh minio -C {{ .Values.configPath }} server {{ .Values.mountPath }}" ]
{{- end }}
{{- end }}
{{- end }}
volumeMounts:
- name: export
mountPath: {{ .Values.mountPath }}
{{- if and .Values.persistence.enabled .Values.persistence.subPath }}
subPath: "{{ .Values.persistence.subPath }}"
{{- end }}
{{- if .Values.gcsgateway.enabled }}
- name: minio-user
mountPath: "/etc/credentials"
readOnly: true
{{- end }}
- name: minio-server-config
mountPath: "/tmp/config.json"
subPath: config.json
- name: minio-config-dir
mountPath: {{ .Values.configPath }}
ports:
- name: service
containerPort: 9000
env:
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ template "minio.fullname" . }}
key: accesskey
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ template "minio.fullname" . }}
key: secretkey
{{- if .Values.gcsgateway.enabled }}
- name: GOOGLE_APPLICATION_CREDENTIALS
value: "/etc/credentials/gcs_key.json"
{{- end }}
livenessProbe:
tcpSocket:
port: 9000
timeoutSeconds: 1
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
volumes:
- name: export
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim | default (include "minio.fullname" .) }}
{{- else }}
emptyDir: {}
{{- end }}
- name: minio-server-config
configMap:
name: {{ template "minio.fullname" . }}
- name: minio-user
secret:
secretName: {{ template "minio.fullname" . }}
- name: minio-config-dir
emptyDir: {}
{{- end }}

View File

@@ -1,39 +0,0 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "minio.fullname" . -}}
{{- $servicePort := .Values.service.port -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- with .Values.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ . }}
http:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}
servicePort: {{ $servicePort }}
{{- end }}
{{- end }}

View File

@@ -1,25 +0,0 @@
{{- if .Values.networkPolicy.enabled }}
kind: NetworkPolicy
apiVersion: {{ template "minio.networkPolicy.apiVersion" . }}
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
podSelector:
matchLabels:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
ingress:
- ports:
- port: {{ .Values.service.port }}
{{- if not .Values.networkPolicy.allowExternal }}
from:
- podSelector:
matchLabels:
{{ template "minio.name" . }}-client: "true"
{{- end }}
{{- end }}

View File

@@ -1,47 +0,0 @@
{{- if .Values.defaultBucket.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ template "minio.fullname" . }}-make-bucket-job
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
metadata:
labels:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
spec:
restartPolicy: OnFailure
{{- if .Values.nodeSelector }}
nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
{{- end }}
volumes:
- name: minio-configuration
projected:
sources:
- configMap:
name: {{ template "minio.fullname" . }}
- secret:
name: {{ template "minio.fullname" . }}
containers:
- name: minio-mc
image: "{{ .Values.mcImage.repository }}:{{ .Values.mcImage.tag }}"
imagePullPolicy: {{ .Values.mcImage.pullPolicy }}
command: ["/bin/sh", "/config/initialize"]
env:
- name: MINIO_ENDPOINT
value: {{ template "minio.fullname" . }}
- name: MINIO_PORT
value: {{ .Values.service.port | quote }}
volumeMounts:
- name: minio-configuration
mountPath: /config
{{- end }}

View File

@@ -1,27 +0,0 @@
{{- if eq .Values.mode "standalone" }}
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
{{- if .Values.nasgateway.enabled }}
selector:
matchLabels:
pv: {{ .Values.nasgateway.pv | quote }}
{{- end }}
accessModes:
- {{ .Values.persistence.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass | quote }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -1,16 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
type: Opaque
data:
accesskey: {{ .Values.accessKey | b64enc }}
secretkey: {{ .Values.secretKey | b64enc }}
{{- if .Values.gcsgateway.enabled }}
gcs_key.json: {{ .Values.gcsgateway.gcsKeyJson | b64enc }}
{{- end }}

View File

@@ -1,42 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.service.annotations }}
annotations:
{{ toYaml .Values.service.annotations | indent 4 }}
{{- end }}
spec:
{{- if (or (eq .Values.service.type "ClusterIP" "") (empty .Values.service.type)) }}
type: ClusterIP
{{- if .Values.service.clusterIP }}
clusterIP: {{ .Values.service.clusterIP }}
{{end}}
{{- else if eq .Values.service.type "LoadBalancer" }}
type: {{ .Values.service.type }}
loadBalancerIP: {{ default "" .Values.service.loadBalancerIP }}
{{- else }}
type: {{ .Values.service.type }}
{{- end }}
ports:
- name: service
port: 9000
targetPort: {{ .Values.service.port }}
protocol: TCP
{{- if (and (eq .Values.service.type "NodePort") ( .Values.service.nodePort)) }}
nodePort: {{ .Values.service.nodePort }}
{{- end}}
{{- if .Values.service.externalIPs }}
externalIPs:
{{- range $i , $ip := .Values.service.externalIPs }}
- {{ $ip }}
{{- end }}
{{- end }}
selector:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}

View File

@@ -1,99 +0,0 @@
{{- if eq .Values.mode "distributed" }}
{{ $nodeCount := .Values.replicas | int }}
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
serviceName: {{ template "minio.fullname" . }}
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
template:
metadata:
name: {{ template "minio.fullname" . }}
labels:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
spec:
{{- if .Values.priorityClassName }}
priorityClassName: "{{ .Values.priorityClassName }}"
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: [ "/bin/sh",
"-ce",
"cp /tmp/config.json {{ .Values.configPath }} &&
/usr/bin/docker-entrypoint.sh minio -C {{ .Values.configPath }} server
{{- range $i := until $nodeCount }}
http://{{ template `minio.fullname` $ }}-{{ $i }}.{{ template `minio.fullname` $ }}.{{ $.Release.Namespace }}.svc.cluster.local{{ $.Values.mountPath }}
{{- end }}" ]
volumeMounts:
- name: export
mountPath: {{ .Values.mountPath }}
{{- if and .Values.persistence.enabled .Values.persistence.subPath }}
subPath: "{{ .Values.persistence.subPath }}"
{{- end }}
- name: minio-server-config
mountPath: "/tmp/config.json"
subPath: config.json
- name: minio-config-dir
mountPath: {{ .Values.configPath }}
ports:
- name: service
containerPort: 9000
env:
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ template "minio.fullname" . }}
key: accesskey
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ template "minio.fullname" . }}
key: secretkey
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
volumes:
- name: minio-user
secret:
secretName: {{ template "minio.fullname" . }}
- name: minio-server-config
configMap:
name: {{ template "minio.fullname" . }}
- name: minio-config-dir
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: export
spec:
accessModes: [ {{ .Values.persistence.accessMode | quote }} ]
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}

View File

@@ -1,237 +0,0 @@
## Set default image, imageTag, and imagePullPolicy. mode is used to indicate the
##
image:
repository: minio/minio
tag: RELEASE.2018-07-10T01-42-11Z
pullPolicy: IfNotPresent
## Set default image, imageTag, and imagePullPolicy for the `mc` (the minio
## client used to create a default bucket).
##
mcImage:
repository: minio/mc
tag: RELEASE.2018-06-09T02-18-09Z
pullPolicy: IfNotPresent
## minio server mode, i.e. standalone or distributed.
## Distributed Minio ref: https://docs.minio.io/docs/distributed-minio-quickstart-guide
##
mode: standalone
## Pod priority settings
## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
##
priorityClassName: ""
## Set default accesskey, secretkey, Minio config file path, volume mount path and
## number of nodes (only used for Minio distributed mode)
## Distributed Minio ref: https://docs.minio.io/docs/distributed-minio-quickstart-guide
##
accessKey: "AKIAIOSFODNN7EXAMPLE"
secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
configPath: "/root/.minio/"
mountPath: "/export"
replicas: 4
## Enable persistence using Persistent Volume Claims
## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
##
persistence:
enabled: true
## A manually managed Persistent Volume and Claim
## Requires persistence.enabled: true
## If defined, PVC must be created manually before volume will be bound
# existingClaim:
## minio data Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
## Storage class of PV to bind. By default it looks for standard storage class.
## If the PV uses a different storage class, specify that here.
storageClass: standard
accessMode: ReadWriteOnce
size: 10Gi
## If subPath is set mount a sub folder of a volume instead of the root of the volume.
## This is especially handy for volume plugins that don't natively support sub mounting (like glusterfs).
##
subPath: ""
## Expose the Minio service to be accessed from outside the cluster (LoadBalancer service).
## or access it from within the cluster (ClusterIP service). Set the service type and the port to serve it.
## ref: http://kubernetes.io/docs/user-guide/services/
##
service:
type: ClusterIP
clusterIP: None
port: 9000
# nodePort: 31311
annotations: {}
# prometheus.io/scrape: 'true'
# prometheus.io/path: '/minio/prometheus/metrics'
# prometheus.io/port: '9000'
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- chart-example.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
## Node labels for pod assignment
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
##
nodeSelector: {}
tolerations: []
affinity: {}
## Configure resource requests and limits
## ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources:
requests:
memory: 256Mi
cpu: 250m
## Create a bucket after minio install
##
defaultBucket:
enabled: false
## If enabled, must be a string with length > 0
name: bucket
## Can be one of none|download|upload|public
policy: none
## Purge if bucket exists already
purge: false
## Use minio as an azure blob gateway, you should disable data persistence so no volume claim are created.
## https://docs.minio.io/docs/minio-gateway-for-azure
azuregateway:
enabled: false
## Use minio as GCS (Google Cloud Storage) gateway, you should disable data persistence so no volume claim are created.
## https://docs.minio.io/docs/minio-gateway-for-gcs
gcsgateway:
enabled: false
# credential json file of service account key
gcsKeyJson: ""
# Google cloud project-id
projectId: ""
## Use minio on NAS backend
## https://docs.minio.io/docs/minio-gateway-for-nas
nasgateway:
enabled: false
# Number of parallel instances
replicas: 4
# Generally for NAS Gateway, you'd like to bind the PVC to a specific PV. To ensure that happens, PV to bind to should have
# a label like "pv: <value>", use value here.
pv: ""
## https://docs.minio.io/docs/minio-bucket-notification-guide
## https://github.com/minio/minio/blob/master/docs/config
minioConfig:
region: "us-east-1"
browser: "on"
domain: ""
worm: "off"
storageClass:
standardStorageClass: ""
reducedRedundancyStorageClass: ""
cache:
drives: []
expiry: 90
maxuse: 80
exclude: []
aqmp:
enable: false
url: ""
exchange: ""
routingKey: ""
exchangeType: ""
deliveryMode: 0
mandatory: false
immediate: false
durable: false
internal: false
noWait: false
autoDeleted: false
nats:
enable: false
address: ""
subject: ""
username: ""
password: ""
token: ""
secure: false
pingInterval: 0
enableStreaming: false
clusterID: ""
clientID: ""
async: false
maxPubAcksInflight: 0
elasticsearch:
enable: false
format: "namespace"
url: ""
index: ""
redis:
enable: false
format: "namespace"
address: ""
password: ""
key: ""
postgresql:
enable: false
format: "namespace"
connectionString: ""
table: ""
host: ""
port: ""
user: ""
password: ""
database: ""
kafka:
enable: false
brokers: "null"
topic: ""
webhook:
enable: false
endpoint: ""
mysql:
enable: false
format: "namespace"
dsnString: ""
table: ""
host: ""
port: ""
user: ""
password: ""
database: ""
mqtt:
enable: false
broker: ""
topic: ""
qos: 0
clientId: ""
username: ""
password: ""
reconnectInterval: 0
keepAliveInterval: 0
networkPolicy:
enabled: false
allowExternal: true

View File

@@ -1,3 +0,0 @@
.git
# OWNERS file for Kubernetes
OWNERS

View File

@@ -1,16 +0,0 @@
name: redis
version: 3.6.5
appVersion: 4.0.10
description: Open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets.
keywords:
- redis
- keyvalue
- database
home: http://redis.io/
icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png
sources:
- https://github.com/bitnami/bitnami-docker-redis
maintainers:
- name: bitnami-bot
email: containers@bitnami.com
engine: gotpl

View File

@@ -1,12 +0,0 @@
approvers:
- prydonius
- tompizmor
- sameersbn
- carrodher
- juan131
reviewers:
- prydonius
- tompizmor
- sameersbn
- carrodher
- juan131

View File

@@ -1,205 +0,0 @@
# Redis
[Redis](http://redis.io/) is an advanced key-value cache and store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps and hyperloglogs.
## TL;DR
```bash
# Testing configuration
$ helm install stable/redis
```
```bash
# Production configuration
$ helm install stable/redis --values values-production.yaml
```
## Introduction
This chart bootstraps a [Redis](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
## Prerequisites
- Kubernetes 1.8+
- PV provisioner support in the underlying infrastructure
## Installing the Chart
To install the chart with the release name `my-release`:
```bash
$ helm install --name my-release stable/redis
```
The command deploys Redis on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
> **Tip**: List all releases using `helm list`
## Uninstalling the Chart
To uninstall/delete the `my-release` deployment:
```bash
$ helm delete my-release
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
## Configuration
The following table lists the configurable parameters of the Redis chart and their default values.
| Parameter | Description | Default |
|--------------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------------------------------|
| `image.registry` | Redis Image registry | `docker.io` |
| `image.repository` | Redis Image name | `bitnami/redis` |
| `image.tag` | Redis Image tag | `{VERSION}` |
| `image.pullPolicy` | Image pull policy | `Always` |
| `image.pullSecrets` | Specify docker-registry secret names as an array | `nil` |
| `cluster.enabled` | Use master-slave topology | `true` |
| `cluster.slaveCount` | Number of slaves | 1 |
| `existingSecret` | Name of existing secret object (for password authentication) | `nil` |
| `usePassword` | Use password | `true` |
| `password` | Redis password (ignored if existingSecret set) | Randomly generated |
| `networkPolicy.enabled` | Enable NetworkPolicy | `false` |
| `networkPolicy.allowExternal` | Don't require client label for connections | `true` |
| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `false` |
| `serviceAccount.name` | The name of the ServiceAccount to create | Generated using the fullname template |
| `rbac.create` | Specifies whether RBAC resources should be created | `false` |
| `rbac.role.rules` | Rules to create | `[]` |
| `metrics.enabled` | Start a side-car prometheus exporter | `false` |
| `metrics.image.registry` | Redis exporter image registry | `docker.io` |
| `metrics.image.repository` | Redis exporter image name | `bitnami/redis` |
| `metrics.image.tag` | Redis exporter image tag | `v0.20.2` |
| `metrics.image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `metrics.image.pullSecrets` | Specify docker-registry secret names as an array | `nil` |
| `metrics.podLabels` | Additional labels for Metrics exporter pod | {} |
| `metrics.podAnnotations` | Additional annotations for Metrics exporter pod | {} |
| `master.service.type` | Kubernetes Service type (redis metrics) | `LoadBalancer` |
| `metrics.service.annotations` | Annotations for the services to monitor (redis master and redis slave service) | {} |
| `metrics.service.loadBalancerIP` | loadBalancerIP if redis metrics service type is `LoadBalancer` | `nil` |
| `metrics.resources` | Exporter resource requests/limit | Memory: `256Mi`, CPU: `100m` |
| `persistence.existingClaim` | Provide an existing PersistentVolumeClaim | `nil` |
| `master.persistence.enabled` | Use a PVC to persist data (master node) | `true` |
| `master.persistence.path` | Path to mount the volume at, to use other images | `/bitnami` |
| `master.persistence.subPath` | Subdirectory of the volume to mount at | `""` |
| `master.persistence.storageClass` | Storage class of backing PVC | `generic` |
| `master.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` |
| `master.persistence.size` | Size of data volume | `8Gi` |
| `master.statefulset.updateStrategy` | Update strategy for StatefulSet | onDelete |
| `master.statefulset.rollingUpdatePartition`| Partition update strategy | `nil` |
| `master.podLabels` | Additional labels for Redis master pod | {} |
| `master.podAnnotations` | Additional annotations for Redis master pod | {} |
| `master.port` | Redis master port | 6379 |
| `master.args` | Redis master command-line args | [] |
| `master.disableCommands` | Comma-separated list of Redis commands to disable (master) | `FLUSHDB,FLUSHALL` |
| `master.extraFlags` | Redis master additional command line flags | [] |
| `master.nodeSelector` | Redis master Node labels for pod assignment | {"beta.kubernetes.io/arch": "amd64"} |
| `master.tolerations` | Toleration labels for Redis master pod assignment | [] |
| `master.affinity ` | Affinity settings for Redis master pod assignment | [] |
| `master.schedulerName` | Name of an alternate scheduler | `nil` |
| `master.service.type` | Kubernetes Service type (redis master) | `ClusterIP` |
| `master.service.annotations` | annotations for redis master service | {} |
| `master.service.loadBalancerIP` | loadBalancerIP if redis master service type is `LoadBalancer` | `nil` |
| `master.securityContext.enabled` | Enable security context (redis master pod) | `true` |
| `master.securityContext.fsGroup` | Group ID for the container (redis master pod) | `1001` |
| `master.securityContext.runAsUser` | User ID for the container (redis master pod) | `1001` |
| `master.resources` | Redis master CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `100m` |
| `master.livenessProbe.enabled` | Turn on and off liveness probe (redis master pod) | `true` |
| `master.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis master pod) | `30` |
| `master.livenessProbe.periodSeconds` | How often to perform the probe (redis master pod) | `30` |
| `master.livenessProbe.timeoutSeconds` | When the probe times out (redis master pod) | `5` |
| `master.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis master pod) | `1` |
| `master.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` |
| `master.readinessProbe.enabled` | Turn on and off readiness probe (redis master pod) | `true` |
| `master.readinessProbe.initialDelaySeconds`| Delay before readiness probe is initiated (redis master pod) | `5` |
| `master.readinessProbe.periodSeconds` | How often to perform the probe (redis master pod) | `10` |
| `master.readinessProbe.timeoutSeconds` | When the probe times out (redis master pod) | `1` |
| `master.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis master pod) | `1` |
| `master.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` |
| `slave.serviceType` | Kubernetes Service type (redis slave) | `LoadBalancer` |
| `slave.service.annotations` | annotations for redis slave service | {} |
| `slave.service.loadBalancerIP` | LoadBalancerIP if Redis slave service type is `LoadBalancer` | `nil` |
| `slave.port` | Redis slave port | `master.port` |
| `slave.args` | Redis slave command-line args | `master.args` |
| `slave.disableCommands` | Comma-separated list of Redis commands to disable (slave) | `master.disableCommands` |
| `slave.extraFlags` | Redis slave additional command line flags | `master.extraFlags` |
| `slave.livenessProbe.enabled` | Turn on and off liveness probe (redis slave pod) | `master.livenessProbe.enabled` |
| `slave.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis slave pod) | `master.livenessProbe.initialDelaySeconds` |
| `slave.livenessProbe.periodSeconds` | How often to perform the probe (redis slave pod) | `master.livenessProbe.periodSeconds` |
| `slave.livenessProbe.timeoutSeconds` | When the probe times out (redis slave pod) | `master.livenessProbe.timeoutSeconds` |
| `slave.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis slave pod) | `master.livenessProbe.successThreshold` |
| `slave.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `master.livenessProbe.failureThreshold` |
| `slave.readinessProbe.enabled` | Turn on and off slave.readiness probe (redis slave pod) | `master.readinessProbe.enabled` |
| `slave.readinessProbe.initialDelaySeconds` | Delay before slave.readiness probe is initiated (redis slave pod) | `master.readinessProbe.initialDelaySeconds` |
| `slave.readinessProbe.periodSeconds` | How often to perform the probe (redis slave pod) | `master.readinessProbe.periodSeconds` |
| `slave.readinessProbe.timeoutSeconds` | When the probe times out (redis slave pod) | `master.readinessProbe.timeoutSeconds` |
| `slave.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis slave pod) | `master.readinessProbe.successThreshold` |
| `slave.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. (redis slave pod) | `master.readinessProbe.failureThreshold` |
| `slave.podLabels` | Additional labels for Redis slave pod | `master.podLabels` |
| `slave.podAnnotations` | Additional annotations for Redis slave pod | `master.podAnnotations` |
| `slave.schedulerName` | Name of an alternate scheduler | `nil` |
| `slave.securityContext.enabled` | Enable security context (redis slave pod) | `master.securityContext.enabled` |
| `slave.securityContext.fsGroup` | Group ID for the container (redis slave pod) | `master.securityContext.fsGroup` |
| `slave.securityContext.runAsUser` | User ID for the container (redis slave pod) | `master.securityContext.runAsUser` |
| `slave.resources` | Redis slave CPU/Memory resource requests/limits | `master.resources` |
| `slave.affinity` | Enable node/pod affinity for slaves | {} |
The above parameters map to the env variables defined in [bitnami/redis](http://github.com/bitnami/bitnami-docker-redis). For more information please refer to the [bitnami/redis](http://github.com/bitnami/bitnami-docker-redis) image documentation.
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
```bash
$ helm install --name my-release \
--set password=secretpassword \
stable/redis
```
The above command sets the Redis server password to `secretpassword`.
Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example,
```bash
$ helm install --name my-release -f values.yaml stable/redis
```
> **Tip**: You can use the default [values.yaml](values.yaml)
> **Note for minikube users**: Current versions of minikube (v0.24.1 at the time of writing) provision `hostPath` persistent volumes that are only writable by root. Using chart defaults cause pod failure for the Redis pod as it attempts to write to the `/bitnami` directory. Consider installing Redis with `--set persistence.enabled=false`. See minikube issue [1990](https://github.com/kubernetes/minikube/issues/1990) for more information.
## NetworkPolicy
To enable network policy for Redis, install
[a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin),
and set `networkPolicy.enabled` to `true`.
For Kubernetes v1.5 & v1.6, you must also turn on NetworkPolicy by setting
the DefaultDeny namespace annotation. Note: this will enforce policy for _all_ pods in the namespace:
kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}"
With NetworkPolicy enabled, only pods with the generated client label will be
able to connect to Redis. This label will be displayed in the output
after a successful install.
## Persistence
The [Bitnami Redis](https://github.com/bitnami/bitnami-docker-redis) image stores the Redis data and configurations at the `/bitnami` path of the container.
By default, the chart mounts a [Persistent Volume](http://kubernetes.io/docs/user-guide/persistent-volumes/) at this location. The volume is created using dynamic volume provisioning. If a Persistent Volume Claim already exists, specify it during installation.
By default, the chart persists both data and configuration. If you wish to persist only the data directory set `persistence.path` to `/bitnami/redis/data` and `persistence.subPath` to `redis/data`.
### Existing PersistentVolumeClaim
1. Create the PersistentVolume
1. Create the PersistentVolumeClaim
1. Install the chart
```bash
$ helm install --set persistence.existingClaim=PVC_NAME stable/redis
```
## Metrics
The chart optionally can start a metrics exporter for [prometheus](https://prometheus.io). The metrics endpoint (port 9121) is exposed in the service. Metrics can be scraped from within the cluster using something similar as the described in the [example Prometheus scrape configuration](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml). If metrics are to be scraped from outside the cluster, the Kubernetes API proxy can be utilized to access the endpoint.

View File

@@ -1,89 +0,0 @@
** Please be patient while the chart is being deployed **
{{- if contains .Values.master.service.type "LoadBalancer" }}
{{- if not .Values.usePassword }}
{{ if and (not .Values.networkPolicy.enabled) (.Values.networkPolicy.allowExternal) }}
-------------------------------------------------------------------------------
WARNING
By specifying "master.service.type=LoadBalancer" and "usePassword=false" you have
most likely exposed the Redis service externally without any authentication
mechanism.
For security reasons, we strongly suggest that you switch to "ClusterIP" or
"NodePort". As alternative, you can also switch to "usePassword=true"
providing a valid password on "password" parameter.
-------------------------------------------------------------------------------
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.cluster.enabled }}
Redis can be accessed via port {{ .Values.master.port }} on the following DNS names from within your cluster:
{{ template "redis.fullname" . }}-master.{{ .Release.Namespace }}.svc.cluster.local for read/write operations
{{ template "redis.fullname" . }}-slave.{{ .Release.Namespace }}.svc.cluster.local for read-only operations
{{- else }}
Redis can be accessed via port {{ .Values.master.port }} on the following DNS name from within your cluster:
{{ template "redis.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local
{{- end }}
{{ if .Values.usePassword }}
To get your password run:
export REDIS_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "redis.fullname" . }} -o jsonpath="{.data.redis-password}" | base64 --decode)
{{- end }}
To connect to your Redis server:
1. Run a Redis pod that you can use as a client:
kubectl run --namespace {{ .Release.Namespace }} {{ template "redis.fullname" . }}-client --rm --tty -i \
{{ if .Values.usePassword }} --env REDIS_PASSWORD=$REDIS_PASSWORD \{{ end }}
{{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }}--labels="{{ template "redis.name" . }}-client=true" \{{- end }}
--image {{ template "redis.image" . }} -- bash
2. Connect using the Redis CLI:
{{- if .Values.cluster.enabled }}
redis-cli -h {{ template "redis.fullname" . }}-master{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }}
redis-cli -h {{ template "redis.fullname" . }}-slave{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }}
{{- else }}
redis-cli -h {{ template "redis.fullname" . }}{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }}
{{- end }}
{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }}
Note: Since NetworkPolicy is enabled, only pods with label
{{ template "redis.fullname" . }}-client=true"
will be able to connect to redis.
{{- else -}}
To connect to your database from outside the cluster execute the following commands:
{{- if contains "NodePort" .Values.master.service.type }}
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "redis.fullname" . }}-master)
redis-cli -h $NODE_IP -p $NODE_PORT {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }}
{{- else if contains "LoadBalancer" .Values.master.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "redis.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "redis.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
redis-cli -h $SERVICE_IP -p {{ .Values.master.service.nodePort }} {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }}
{{- else if contains "ClusterIP" .Values.master.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "redis.name" . }}" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward --namespace {{ .Release.Namespace }} $POD_NAME {{ .Values.master.port }}:{{ .Values.master.port }}
redis-cli -h 127.0.0.1 -p {{ .Values.master.port }} {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }}
{{- end }}
{{- end }}

View File

@@ -1,130 +0,0 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "redis.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Expand the chart plus release name (used by the chart label)
*/}}
{{- define "redis.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "redis.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Return the appropriate apiVersion for networkpolicy.
*/}}
{{- define "networkPolicy.apiVersion" -}}
{{- if semverCompare ">=1.4-0, <1.7-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "extensions/v1beta1" -}}
{{- else -}}
{{- print "networking.k8s.io/v1" -}}
{{- end -}}
{{- end -}}
{{/*
Return the proper image name
*/}}
{{- define "redis.image" -}}
{{- $registryName := .Values.image.registry -}}
{{- $repositoryName := .Values.image.repository -}}
{{- $tag := .Values.image.tag | toString -}}
{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}}
{{- end -}}
{{/*
Return the proper image name (for the metrics image)
*/}}
{{- define "metrics.image" -}}
{{- $registryName := .Values.metrics.image.registry -}}
{{- $repositoryName := .Values.metrics.image.repository -}}
{{- $tag := .Values.metrics.image.tag | toString -}}
{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}}
{{- end -}}
{{/*
Return slave readiness probe
*/}}
{{- define "redis.slave.readinessProbe" -}}
{{- $readinessProbe := .Values.slave.readinessProbe | default .Values.master.readinessProbe -}}
{{- if $readinessProbe }}
{{- if $readinessProbe.enabled }}
readinessProbe:
initialDelaySeconds: {{ $readinessProbe.initialDelaySeconds | default .Values.master.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ $readinessProbe.periodSeconds | default .Values.master.readinessProbe.periodSeconds }}
timeoutSeconds: {{ $readinessProbe.timeoutSeconds | default .Values.master.readinessProbe.timeoutSeconds }}
successThreshold: {{ $readinessProbe.successThreshold | default .Values.master.readinessProbe.successThreshold }}
failureThreshold: {{ $readinessProbe.failureThreshold | default .Values.master.readinessProbe.failureThreshold }}
exec:
command:
- redis-cli
- ping
{{- end }}
{{- end -}}
{{- end -}}
{{/*
Return slave liveness probe
*/}}
{{- define "redis.slave.livenessProbe" -}}
{{- $livenessProbe := .Values.slave.livenessProbe | default .Values.master.livenessProbe -}}
{{- if $livenessProbe }}
{{- if $livenessProbe.enabled }}
livenessProbe:
initialDelaySeconds: {{ $livenessProbe.initialDelaySeconds | default .Values.master.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ $livenessProbe.periodSeconds | default .Values.master.livenessProbe.periodSeconds }}
timeoutSeconds: {{ $livenessProbe.timeoutSeconds | default .Values.master.livenessProbe.timeoutSeconds }}
successThreshold: {{ $livenessProbe.successThreshold | default .Values.master.livenessProbe.successThreshold }}
failureThreshold: {{ $livenessProbe.failureThreshold | default .Values.master.livenessProbe.failureThreshold}}
exec:
command:
- redis-cli
- ping
{{- end }}
{{- end -}}
{{- end -}}
{{/*
Return slave security context
*/}}
{{- define "redis.slave.securityContext" -}}
{{- $securityContext := .Values.slave.securityContext | default .Values.master.securityContext -}}
{{- if $securityContext }}
{{- if $securityContext.enabled }}
securityContext:
fsGroup: {{ $securityContext.fsGroup | default .Values.master.securityContext.fsGroup }}
runAsUser: {{ $securityContext.runAsUser | default .Values.master.securityContext.runAsUser }}
{{- end }}
{{- end }}
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "redis.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "redis.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

View File

@@ -1,70 +0,0 @@
{{- if .Values.metrics.enabled }}
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ template "redis.fullname" . }}-metrics
labels:
app: {{ template "redis.name" . }}
chart: {{ template "redis.chart" . }}
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
spec:
template:
metadata:
labels:
release: "{{ .Release.Name }}"
role: metrics
app: {{ template "redis.name" . }}
{{- if .Values.metrics.podLabels }}
{{ toYaml .Values.metrics.podLabels | indent 8 }}
{{- end }}
{{- if .Values.metrics.podAnnotations }}
annotations:
{{ toYaml .Values.metrics.podAnnotations | indent 8 }}
{{- end }}
spec:
{{- if .Values.metrics.image.pullSecrets }}
imagePullSecrets:
{{- range .Values.metrics.image.pullSecrets }}
- name: {{ . }}
{{- end}}
{{- end}}
{{- if .Values.metrics.nodeSelector }}
serviceAccountName: "{{ template "redis.serviceAccountName" . }}"
nodeSelector:
{{ toYaml .Values.metrics.nodeSelector | indent 8 }}
{{- end }}
{{- if .Values.metrics.tolerations }}
tolerations:
{{ toYaml .Values.metrics.tolerations | indent 8 }}
{{- end }}
containers:
- name: metrics
image: {{ template "metrics.image" . }}
imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }}
env:
- name: REDIS_ADDR
{{- if .Values.cluster.enabled }}
value: {{ printf "%s-master:%d,%s-slave:%d" ( include "redis.fullname" . ) ( int .Values.master.port ) ( include "redis.fullname" . ) ( .Values.slave.port | default .Values.master.port | int ) | quote }}
{{- else }}
value: {{ printf "%s-master:%d" (include "redis.fullname" . ) (int .Values.master.port) | quote }}
{{- end }}
- name: REDIS_ALIAS
value: {{ template "redis.fullname" . }}
{{- if .Values.usePassword }}
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
{{- if .Values.existingSecret }}
name: {{ .Values.existingSecret }}
{{- else }}
name: {{ template "redis.fullname" . }}
{{- end }}
key: redis-password
{{- end }}
ports:
- name: metrics
containerPort: 9121
resources:
{{ toYaml .Values.metrics.resources | indent 10 }}
{{- end }}

View File

@@ -1,27 +0,0 @@
{{- if .Values.metrics.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ template "redis.fullname" . }}-metrics
labels:
app: {{ template "redis.name" . }}
chart: {{ template "redis.chart" . }}
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
annotations:
{{ toYaml .Values.metrics.service.annotations | indent 4 }}
spec:
type: {{ .Values.metrics.service.type }}
{{ if eq .Values.metrics.service.type "LoadBalancer" -}} {{ if .Values.metrics.service.loadBalancerIP -}}
loadBalancerIP: {{ .Values.metrics.service.loadBalancerIP }}
{{ end -}}
{{- end -}}
ports:
- name: metrics
port: 9121
targetPort: metrics
selector:
app: {{ template "redis.name" . }}
release: {{ .Release.Name }}
role: metrics
{{- end }}

View File

@@ -1,30 +0,0 @@
{{- if .Values.networkPolicy.enabled }}
kind: NetworkPolicy
apiVersion: {{ template "networkPolicy.apiVersion" . }}
metadata:
name: "{{ template "redis.fullname" . }}"
labels:
app: {{ template "redis.name" . }}
chart: {{ template "redis.chart" . }}
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
spec:
podSelector:
matchLabels:
app: {{ template "redis.name" . }}
ingress:
# Allow inbound connections
- ports:
- port: 6379
{{- if not .Values.networkPolicy.allowExternal }}
from:
- podSelector:
matchLabels:
{{ template "redis.fullname" . }}-client: "true"
{{- end }}
{{- if .Values.metrics.enabled }}
# Allow prometheus scrapes for metrics
- ports:
- port: 9121
{{- end }}
{{- end }}

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