Compare commits

...

41 Commits

Author SHA1 Message Date
Alex Collins
5fe1447b72 Update manifests to v1.0.1 2019-05-28 10:08:34 -07:00
Alex Collins
539516bd43 Update manifests to v1.0.1 2019-05-28 10:07:32 -07:00
Alex Collins
8a57d544ff Update manifests to v1.0.1 2019-05-28 09:01:21 -07:00
Alex Collins
cd77e2a048 Update manifests to v1.0.1 2019-05-28 08:58:01 -07:00
Alex Collins
a52f766815 removes file which cannot be compiled 2019-05-24 15:03:49 -07:00
Alex Collins
646fd37e16 Public git creds (#1633) 2019-05-24 15:01:03 -07:00
Alexander Matyushentsev
c74ca22023 Update manifests to v1.0.0 2019-05-16 13:00:40 -07:00
Alexander Matyushentsev
2d170be242 Issue #1471 - Support configuring requested OIDC provider scopes and enforced RBAC scopes (#1585)
* Issue #1471 - Support configuring requested OIDC provider scopes and enforced RBAC scopes

* Apply reviewer notes
2019-05-16 07:35:44 -07:00
Alexander Matyushentsev
079101522d Issue #1533 - Prevent reconciliation loop for self-managed apps (#1608) 2019-05-14 08:05:28 -07:00
Jesse Suen
1bea98e01b Supply resourceVersion to watch request to prevent reading of stale cache (#1612) 2019-05-13 15:01:15 -07:00
Alexander Matyushentsev
7c09221f7c Update manifests to v1.0.0-rc3 2019-05-09 09:52:56 -07:00
Alexander Matyushentsev
6355e910d4 Fix flaky TestGetIngressInfo unit test (#1529) 2019-05-09 09:52:16 -07:00
Alexander Matyushentsev
891e0320d7 Issue #1586 - Ignore patch errors during diffing normalization (#1599) 2019-05-09 09:30:48 -07:00
Alexander Matyushentsev
486323ae58 Issue #1596 - SSH URLs support is partially broken (#1597) 2019-05-09 09:30:42 -07:00
Alexander Matyushentsev
4ef875aa0b Issue #1552 - Improve rendering app image information (#1584) 2019-05-09 09:30:31 -07:00
Alexander Matyushentsev
e756b8db7a Fix ingress browsable url formatting if port is not string (#1576) 2019-05-09 09:28:49 -07:00
Alexander Matyushentsev
8023f8ac8d Issue #1579 - Impossible to sync to HEAD from UI if auto-sync is enabled (#1580) 2019-05-09 09:28:42 -07:00
Alexander Matyushentsev
803408904a Issue #1570 - Application controller is unable to delete self-referenced app (#1574) 2019-05-09 09:28:35 -07:00
Alexander Matyushentsev
702f9095da Issue #1546 - Add liveness probe to repo server/api servers (#1560) 2019-05-09 09:28:30 -07:00
Alexander Matyushentsev
0b9ee1ae6d ISsue #1557 - Controller incorrectly report health state of self managed application (#1558) 2019-05-09 09:28:19 -07:00
Alexander Matyushentsev
2f003e08ff Issue #1540 - Fix kustomize manifest generation crash is manifest has image without version (#1559) 2019-05-09 09:28:14 -07:00
Paul Brit
e090857d6b Fix hardcoded 'git' user in util/git.NewClient (#1556)
Closes #1555
2019-05-09 09:28:09 -07:00
dthomson25
4e29fff5a3 Improve Rollout health.lua (#1554) 2019-05-09 09:28:03 -07:00
Alexander Matyushentsev
e279377696 Fix invalid URL for ingress without hostname (#1553) 2019-05-01 15:38:40 -07:00
Alexander Matyushentsev
5e52839ce3 Issue #1533 - Prevent reconciliation loop for self-managed apps (#1547) 2019-05-01 10:22:09 -07:00
Alexander Matyushentsev
3ca632a552 Update manifests to v1.0.0-rc2 2019-04-30 13:19:50 -07:00
Alexander Matyushentsev
cfe55357ac Rollout health checks/actions should support v0.2 and v0.2+ versions (#1543) 2019-04-30 13:18:15 -07:00
Alex Collins
0f6d768eca Fixes bug in normalizer (#1542) 2019-04-30 11:32:54 -07:00
Alexander Matyushentsev
75330da328 Ingress resource might get invalid ExternalURL (#1522) (#1523) 2019-04-30 11:14:18 -07:00
Alexander Matyushentsev
a1bcbab0e5 Issue 1476 - Avoid validating repository in application controller (#1535) 2019-04-30 11:10:06 -07:00
Alexander Matyushentsev
db9272032a Issue #1414 - Load target resource using K8S if conversion fails (#1527) 2019-04-30 11:10:02 -07:00
Alexander Matyushentsev
d6d6c655ff Issue #1476 - Add repo server grpc call timeout (#1528) 2019-04-30 11:09:58 -07:00
Alex Collins
58acc92790 Adds support for configuring repo creds at a domain/org level. Closes… (#1496) 2019-04-30 11:09:53 -07:00
Simon Behar
c3074c0977 Whitelisting of resources (#1509)
* Added whitelisting of resources
2019-04-30 11:09:26 -07:00
Simon Behar
af254f3047 Added ability to sync specific labels from the command line (#1501)
* Finished initial implementation

* Added tests and fix a few bugs
2019-04-30 11:09:16 -07:00
Alex Collins
c140976eeb Updates Makefile 2019-04-24 10:52:53 -07:00
Alex Collins
05c22d4ddc Updates VERSION 2019-04-24 10:51:50 -07:00
Alex Collins
3e08938a20 Updates VERSION 2019-04-24 10:49:30 -07:00
Alex Collins
5937bb574d Update manifests to v1.0.0-rc1 2019-04-24 10:48:24 -07:00
Alex Collins
ded55b26d1 Update manifests to v1.0.0-rc1 2019-04-24 10:46:23 -07:00
Alex Collins
d79ed65de0 Updated CHANGELOG.md 2019-04-24 10:35:04 -07:00
102 changed files with 3747 additions and 1156 deletions

View File

@@ -22,17 +22,11 @@ Argo CD introduces Custom Resource Actions to allow users to provide their own L
* Remove deprecated componentParameterOverrides field #1372
### Changes since v0.12.1
#### New Features
+ Issue #357 - Expose application nodes networking information (#1333)
+ Support for customizable resource actions as Lua scripts #86
+ Surface Service/Ingress external IPs, hostname to application #908
+ Update argocd-util import/export to support proper backup and restore (#1328)
### Changes since v0.12.2
#### Enhancements
* `argocd app wait` should have `--resource` flag like sync #1206
* Adds support for `kustomize edit set image`. Closes #1275 (#1324)
* Allow wait to return on health or suspended (#1392)
* Application warning when a manifest is defined twice #1070
@@ -41,30 +35,32 @@ Argo CD introduces Custom Resource Actions to allow users to provide their own L
* Display number of errors on resource tab #1477
* Displays resources that are being deleted as "Progressing". Closes #1410 (#1426)
* Generate random name for grpc proxy unix socket file instead of time stamp (#1455)
* Issue #357 - Expose application nodes networking information (#1333)
* Issue #1404 - App controller unnecessary set namespace to cluster level resources (#1405)
* Nils health if the resource does not provide it. Closes #1383 (#1408)
* Perform health assessments on all resource nodes in the tree. Closes #1382 (#1422)
* Remove deprecated componentParameterOverrides field #1372
* Shows the health of the application. Closes #1433 (#1434)
* Surface Service/Ingress external IPs, hostname to application #908
* Surface pod status to tree view #1358
* Support for customizable resource actions as Lua scripts #86
* UI / API Errors Truncated, Time Out #1386
* UI Enhancement Proposals Quick Wins #1274
* Update argocd-util import/export to support proper backup and restore (#1328)
* Whitelisting repos/clusters in projects should consider repo/cluster permissions #1432
#### Bug Fixes
- "bind: address already in use" after switching to gRPC-Web #1451
- Annoying warning while using `--grpc-web` flag #1420
- Don't compare secrets in the CLI, since argo-cd doesn't have access to their data (#1459)
- Dropdown menu should not have sync item for unmanaged resources #1357
- Fixes goroutine leak. Closes #1381 (#1457)
- Improve input style #1217
- Issue #1389 - Fix null pointer exception in secret normalization function (#1428)
- Issue #1425 - Argo CD should not delete CRDs (#1428)
- Issue #1446 - Delete helm temp directories (#1449)
- Issue #908 - Surface Service/Ingress external IPs, hostname to application (#1347)
- kustomization fields are all mandatory #1504
- Resource node details is crashing if live resource is missing $1505
- Rollback UI is not showing correct ksonnet parameters in preview #1326
- See details of applications fails with "r.nodes is undefined" #1371
- UI fails to load custom actions is resource is not deployed #1502
- Unable to create app from private repo: x509: certificate signed by unknown authority #1171
## v0.12.2 (2019-04-22)

View File

@@ -11,6 +11,8 @@ GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ;
PACKR_CMD=$(shell if [ "`which packr`" ]; then echo "packr"; else echo "go run vendor/github.com/gobuffalo/packr/packr/main.go"; fi)
TEST_CMD=$(shell [ "`which gotestsum`" != "" ] && echo gotestsum -- || echo go test)
PATH:=$(PATH):$(PWD)/hack
# docker image publishing options
DOCKER_PUSH=false
IMAGE_TAG=latest
@@ -148,6 +150,7 @@ test-e2e: cli
.PHONY: start-e2e
start-e2e: cli
killall goreman || true
kubectl create ns argocd-e2e || true
kubens argocd-e2e
kustomize build test/manifests/base | kubectl apply -f -
@@ -178,4 +181,4 @@ release-precheck: manifests
@if [ "$(GIT_TAG)" != "v`cat VERSION`" ]; then echo 'VERSION does not match git tag'; exit 1; fi
.PHONY: release
release: release-precheck precheckin image release-cli
release: release-precheck pre-commit image release-cli

View File

@@ -2,4 +2,4 @@ controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/a
api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:6379 --disable-auth --insecure --dex-server http://localhost:5556 --repo-server localhost:8081 --staticassets ../argo-cd-ui/dist/app"
repo-server: sh -c "FORCE_LOG_COLORS=1 go run ./cmd/argocd-repo-server/main.go --loglevel debug --redis localhost:6379"
dex: sh -c "go run ./cmd/argocd-util/main.go gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p 5556:5556 -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/dexidp/dex:v2.14.0 serve /dex.yaml"
redis: docker run --rm -i -p 6379:6379 redis:5.0.3-alpine --save "" --appendonly no
redis: docker run --rm --name argocd-redis -i -p 6379:6379 redis:5.0.3-alpine --save ""--appendonly no

View File

@@ -1 +1 @@
1.0.0
1.0.1

View File

@@ -68,6 +68,11 @@
},
"name": "project",
"in": "query"
},
{
"type": "string",
"name": "resourceVersion",
"in": "query"
}
],
"responses": {
@@ -212,6 +217,11 @@
},
"name": "project",
"in": "query"
},
{
"type": "string",
"name": "resourceVersion",
"in": "query"
}
],
"responses": {
@@ -1387,6 +1397,11 @@
},
"name": "project",
"in": "query"
},
{
"type": "string",
"name": "resourceVersion",
"in": "query"
}
],
"responses": {
@@ -1598,6 +1613,12 @@
},
"name": {
"type": "string"
},
"scopes": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@@ -2651,13 +2672,6 @@
"$ref": "#/definitions/v1alpha1ApplicationCondition"
}
},
"externalURLs": {
"description": "ExternalURLs holds all external URLs of application child resources.",
"type": "array",
"items": {
"type": "string"
}
},
"health": {
"$ref": "#/definitions/v1alpha1HealthStatus"
},
@@ -2685,11 +2699,33 @@
"sourceType": {
"type": "string"
},
"summary": {
"$ref": "#/definitions/v1alpha1ApplicationSummary"
},
"sync": {
"$ref": "#/definitions/v1alpha1SyncStatus"
}
}
},
"v1alpha1ApplicationSummary": {
"type": "object",
"properties": {
"externalURLs": {
"description": "ExternalURLs holds all external URLs of application child resources.",
"type": "array",
"items": {
"type": "string"
}
},
"images": {
"description": "Images holds all images of application child resources.",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1alpha1ApplicationTree": {
"type": "object",
"title": "ApplicationTree holds nodes which belongs to the application",

View File

@@ -37,14 +37,15 @@ const (
func newCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
repoServerAddress string
statusProcessors int
operationProcessors int
logLevel string
glogLevel int
cacheSrc func() (*cache.Cache, error)
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
repoServerAddress string
repoServerTimeoutSeconds int
statusProcessors int
operationProcessors int
logLevel string
glogLevel int
cacheSrc func() (*cache.Cache, error)
)
var command = cobra.Command{
Use: cliName,
@@ -54,9 +55,9 @@ func newCommand() *cobra.Command {
cli.SetGLogLevel(glogLevel)
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
config.QPS = common.K8sClientConfigQPS
config.Burst = common.K8sClientConfigBurst
errors.CheckError(err)
kubeClient := kubernetes.NewForConfigOrDie(config)
appClient := appclientset.NewForConfigOrDie(config)
@@ -65,7 +66,7 @@ func newCommand() *cobra.Command {
errors.CheckError(err)
resyncDuration := time.Duration(appResyncPeriod) * time.Second
repoClientset := reposerver.NewRepoServerClientset(repoServerAddress)
repoClientset := reposerver.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -98,6 +99,7 @@ func newCommand() *cobra.Command {
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().Int64Var(&appResyncPeriod, "app-resync", defaultAppResyncPeriod, "Time period in seconds for application resync.")
command.Flags().StringVar(&repoServerAddress, "repo-server", common.DefaultRepoServerAddr, "Repo server address.")
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", 60, "Repo server RPC call timeout seconds.")
command.Flags().IntVar(&statusProcessors, "status-processors", 1, "Number of application status processors")
command.Flags().IntVar(&operationProcessors, "operation-processors", 1, "Number of application operation processors")
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")

View File

@@ -57,7 +57,7 @@ func NewCommand() *cobra.Command {
kubeclientset := kubernetes.NewForConfigOrDie(config)
appclientset := appclientset.NewForConfigOrDie(config)
repoclientset := reposerver.NewRepoServerClientset(repoServerAddress)
repoclientset := reposerver.NewRepoServerClientset(repoServerAddress, 0)
argoCDOpts := server.ArgoCDServerOpts{
Insecure: insecure,

View File

@@ -957,6 +957,7 @@ func formatConditionsSummary(app argoappv1.Application) string {
const (
resourceFieldDelimiter = ":"
resourceFieldCount = 3
labelFieldDelimiter = "="
)
func parseSelectedResources(resources []string) []argoappv1.SyncOperationResource {
@@ -979,6 +980,21 @@ func parseSelectedResources(resources []string) []argoappv1.SyncOperationResourc
return selectedResources
}
func parseLabels(labels []string) (map[string]string, error) {
var selectedLabels map[string]string
if labels != nil {
selectedLabels = map[string]string{}
for _, r := range labels {
fields := strings.Split(r, labelFieldDelimiter)
if len(fields) != 2 {
return nil, fmt.Errorf("labels should have key%svalue, but instead got: %s", labelFieldDelimiter, r)
}
selectedLabels[fields[0]] = fields[1]
}
}
return selectedLabels, nil
}
// NewApplicationWaitCommand returns a new instance of an `argocd app wait` command
func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
@@ -1073,6 +1089,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
var (
revision string
resources []string
labels []string
prune bool
dryRun bool
timeout uint
@@ -1091,8 +1108,51 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
conn, appIf := acdClient.NewApplicationClientOrDie()
defer util.Close(conn)
selectedResources := parseSelectedResources(resources)
appName := args[0]
selectedLabels, parseErr := parseLabels(labels)
if parseErr != nil {
log.Fatal(parseErr)
}
if len(selectedLabels) > 0 {
ctx := context.Background()
if revision == "" {
revision = "HEAD"
}
q := application.ApplicationManifestQuery{
Name: &appName,
Revision: revision,
}
res, err := appIf.GetManifests(ctx, &q)
if err != nil {
log.Fatal(err)
}
for _, mfst := range res.Manifests {
obj, err := argoappv1.UnmarshalToUnstructured(mfst)
errors.CheckError(err)
for key, selectedValue := range selectedLabels {
if objectValue, ok := obj.GetLabels()[key]; ok && selectedValue == objectValue {
gvk := obj.GroupVersionKind()
resources = append(resources, fmt.Sprintf("%s:%s:%s", gvk.Group, gvk.Kind, obj.GetName()))
}
}
}
// If labels are provided and none are found return error only if specific resources were also not
// specified.
if len(resources) == 0 {
log.Fatalf("No matching resources found for labels: %v", labels)
return
}
}
selectedResources := parseSelectedResources(resources)
syncReq := application.ApplicationSyncRequest{
Name: &appName,
DryRun: dryRun,
@@ -1117,18 +1177,21 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
app, err := waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, selectedResources)
errors.CheckError(err)
pruningRequired := 0
for _, resDetails := range app.Status.OperationState.SyncResult.Resources {
if resDetails.Status == argoappv1.ResultCodePruneSkipped {
pruningRequired++
// Only get resources to be pruned if sync was application-wide
if len(selectedResources) == 0 {
pruningRequired := 0
for _, resDetails := range app.Status.OperationState.SyncResult.Resources {
if resDetails.Status == argoappv1.ResultCodePruneSkipped {
pruningRequired++
}
}
if pruningRequired > 0 {
log.Fatalf("%d resources require pruning", pruningRequired)
}
}
if pruningRequired > 0 {
log.Fatalf("%d resources require pruning", pruningRequired)
}
if !app.Status.OperationState.Phase.Successful() && !dryRun {
os.Exit(1)
if !app.Status.OperationState.Phase.Successful() && !dryRun {
os.Exit(1)
}
}
},
}
@@ -1136,6 +1199,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
command.Flags().StringVar(&revision, "revision", "", "Sync to a specific revision. Preserves parameter overrides")
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().StringArrayVar(&labels, "label", []string{}, fmt.Sprintf("Sync only specific resources with a label. This option may be specified repeatedly."))
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
command.Flags().StringVar(&strategy, "strategy", "", "Sync strategy (one of: apply|hook)")
command.Flags().BoolVar(&force, "force", false, "Use a force apply")

View File

@@ -0,0 +1,25 @@
package commands
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseLabels(t *testing.T) {
validLabels := []string{"key=value", "foo=bar", "intuit=inc"}
result, err := parseLabels(validLabels)
assert.NoError(t, err)
assert.Len(t, result, 3)
invalidLabels := []string{"key=value", "too=many=equals"}
_, err = parseLabels(invalidLabels)
assert.Error(t, err)
emptyLabels := []string{}
result, err = parseLabels(emptyLabels)
assert.NoError(t, err)
assert.Len(t, result, 0)
}

View File

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

View File

@@ -10,6 +10,8 @@ import (
"sync"
"time"
"github.com/argoproj/argo-cd/pkg/apis/application"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
@@ -110,21 +112,43 @@ func NewApplicationController(
}
appInformer, appLister := ctrl.newApplicationInformerAndLister()
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, cache.Indexers{})
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settings, kubectlCmd, func(appName string, fullRefresh bool) {
ctrl.requestAppRefresh(appName, fullRefresh)
ctrl.appRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, appName))
})
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd, ctrl.settings, stateCache, projInformer)
metricsAddr := fmt.Sprintf("0.0.0.0:%d", common.PortArgoCDMetrics)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister)
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settings, kubectlCmd, ctrl.metricsServer, ctrl.handleAppUpdated)
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd, ctrl.settings, stateCache, projInformer, ctrl.metricsServer)
ctrl.appInformer = appInformer
ctrl.appLister = appLister
ctrl.projInformer = projInformer
ctrl.appStateManager = appStateManager
ctrl.stateCache = stateCache
metricsAddr := fmt.Sprintf("0.0.0.0:%d", common.PortArgoCDMetrics)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, ctrl.appLister)
return &ctrl, nil
}
func isSelfReferencedApp(app *appv1.Application, ref v1.ObjectReference) bool {
gvk := ref.GroupVersionKind()
return ref.UID == app.UID &&
ref.Name == app.Name &&
ref.Namespace == app.Namespace &&
gvk.Group == application.Group &&
gvk.Kind == application.ApplicationKind
}
func (ctrl *ApplicationController) handleAppUpdated(appName string, fullRefresh bool, ref v1.ObjectReference) {
skipForceRefresh := false
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(ctrl.namespace + "/" + appName)
if app, ok := obj.(*appv1.Application); exists && err == nil && ok && isSelfReferencedApp(app, ref) {
// Don't force refresh app if related resource is application itself. This prevents infinite reconciliation loop.
skipForceRefresh = true
}
if !skipForceRefresh {
ctrl.requestAppRefresh(appName, fullRefresh)
}
ctrl.appRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, appName))
}
func (ctrl *ApplicationController) setAppManagedResources(a *appv1.Application, comparisonResult *comparisonResult) (*appv1.ApplicationTree, error) {
managedResources, err := ctrl.managedResources(a, comparisonResult)
if err != nil {
@@ -327,6 +351,10 @@ func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext b
return
}
func shouldBeDeleted(app *appv1.Application, obj *unstructured.Unstructured) bool {
return !kube.IsCRD(obj) && !isSelfReferencedApp(app, kube.GetObjectRef(obj))
}
func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Application) error {
logCtx := log.WithField("application", app.Name)
logCtx.Infof("Deleting resources")
@@ -345,13 +373,20 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
}
objs := make([]*unstructured.Unstructured, 0)
for k := range objsMap {
if objsMap[k].GetDeletionTimestamp() == nil && !kube.IsCRD(objsMap[k]) {
if objsMap[k].GetDeletionTimestamp() == nil && shouldBeDeleted(app, objsMap[k]) {
objs = append(objs, objsMap[k])
}
}
cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err != nil {
return err
}
config := metrics.AddMetricsTransportWrapper(ctrl.metricsServer, app, cluster.RESTConfig())
err = util.RunAllAsync(len(objs), func(i int) error {
obj := objs[i]
return ctrl.stateCache.Delete(app.Spec.Destination.Server, obj)
return ctrl.kubectl.DeleteResource(config, obj.GroupVersionKind(), obj.GetName(), obj.GetNamespace(), false)
})
if err != nil {
return err
@@ -361,6 +396,11 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
if err != nil {
return err
}
for k, obj := range objsMap {
if !shouldBeDeleted(app, obj) {
delete(objsMap, k)
}
}
if len(objsMap) > 0 {
logCtx.Infof("%d objects remaining for deletion", len(objsMap))
return nil
@@ -589,7 +629,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
if tree, err := ctrl.getResourceTree(app, managedResources); err != nil {
app.Status.Conditions = []appv1.ApplicationCondition{{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()}}
} else {
app.Status.ExternalURLs = tree.GetBrowableURLs()
app.Status.Summary = tree.GetSummary()
if err = ctrl.cache.SetAppResourcesTree(app.Name, tree); err != nil {
logCtx.Errorf("Failed to cache resources tree: %v", err)
return
@@ -621,7 +661,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
if err != nil {
logCtx.Errorf("Failed to cache app resources: %v", err)
} else {
app.Status.ExternalURLs = tree.GetBrowableURLs()
app.Status.Summary = tree.GetSummary()
}
syncErrCond := ctrl.autoSync(app, compareResult.syncStatus)
@@ -688,7 +728,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
})
}
} else {
specConditions, _, err := argo.GetSpecErrors(context.Background(), &app.Spec, proj, ctrl.repoClientset, ctrl.db)
specConditions, err := argo.ValidatePermissions(context.Background(), &app.Spec, proj, ctrl.db)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionUnknownError,
@@ -927,6 +967,7 @@ func (ctrl *ApplicationController) watchSettings(ctx context.Context) {
ctrl.settingsMgr.Subscribe(updateCh)
prevAppLabelKey := ctrl.settings.GetAppInstanceLabelKey()
prevResourceExclusions := ctrl.settings.ResourceExclusions
prevResourceInclusions := ctrl.settings.ResourceInclusions
done := false
for !done {
select {
@@ -939,10 +980,15 @@ func (ctrl *ApplicationController) watchSettings(ctx context.Context) {
prevAppLabelKey = newAppLabelKey
}
if !reflect.DeepEqual(prevResourceExclusions, newSettings.ResourceExclusions) {
log.Infof("resource exclusions modified")
log.WithFields(log.Fields{"prevResourceExclusions": prevResourceExclusions, "newResourceExclusions": newSettings.ResourceExclusions}).Info("resource exclusions modified")
ctrl.stateCache.Invalidate()
prevResourceExclusions = newSettings.ResourceExclusions
}
if !reflect.DeepEqual(prevResourceInclusions, newSettings.ResourceInclusions) {
log.WithFields(log.Fields{"prevResourceInclusions": prevResourceInclusions, "newResourceInclusions": newSettings.ResourceInclusions}).Info("resource inclusions modified")
ctrl.stateCache.Invalidate()
prevResourceInclusions = newSettings.ResourceInclusions
}
case <-ctx.Done():
done = true
}

View File

@@ -5,6 +5,8 @@ import (
"testing"
"time"
"github.com/argoproj/argo-cd/common"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -119,6 +121,7 @@ var fakeApp = `
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
uid: "123"
name: my-app
namespace: ` + test.FakeArgoCDNamespace + `
spec:
@@ -353,20 +356,26 @@ func TestAutoSyncParameterOverrides(t *testing.T) {
// TestFinalizeAppDeletion verifies application deletion
func TestFinalizeAppDeletion(t *testing.T) {
app := newFakeApp()
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
appObj := kube.MustToUnstructured(&app)
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}, managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(appObj): appObj,
}})
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
patched := false
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
defaultReactor := fakeAppCs.ReactionChain[0]
fakeAppCs.ReactionChain = nil
fakeAppCs.AddReactor("get", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
return defaultReactor.React(action)
})
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
patched = true
return true, nil, nil
})
err := ctrl.finalizeApplicationDeletion(app)
// TODO: use an interface to fake out the calls to GetResourcesWithLabel and DeleteResourceWithLabel
// For now just ensure we have an expected error condition
assert.Error(t, err) // Change this to assert.Nil when we stub out GetResourcesWithLabel/DeleteResourceWithLabel
assert.False(t, patched) // Change this to assert.True when we stub out GetResourcesWithLabel/DeleteResourceWithLabel
assert.NoError(t, err)
assert.True(t, patched)
}
// TestNormalizeApplication verifies we normalize an application during reconciliation
@@ -442,3 +451,18 @@ func TestNormalizeApplication(t *testing.T) {
assert.False(t, normalized)
}
}
func TestHandleAppUpdated(t *testing.T) {
app := newFakeApp()
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
app.Spec.Destination.Server = common.KubernetesInternalAPIServerAddr
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
ctrl.handleAppUpdated(app.Name, true, kube.GetObjectRef(kube.MustToUnstructured(app)))
isRequested, _ := ctrl.isRefreshRequested(app.Name)
assert.False(t, isRequested)
ctrl.handleAppUpdated(app.Name, true, corev1.ObjectReference{UID: "test", Kind: kube.DeploymentKind, Name: "test", Namespace: "default"})
isRequested, _ = ctrl.isRefreshRequested(app.Name)
assert.True(t, isRequested)
}

View File

@@ -5,11 +5,13 @@ import (
"sync"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
"github.com/argoproj/argo-cd/controller/metrics"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/db"
@@ -25,12 +27,12 @@ type LiveStateCache interface {
GetManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error)
// Starts watching resources of each controlled cluster.
Run(ctx context.Context)
// Deletes specified resource from cluster.
Delete(server string, obj *unstructured.Unstructured) error
// Invalidate invalidates the entire cluster state cache
Invalidate()
}
type AppUpdatedHandler = func(appName string, fullRefresh bool, ref v1.ObjectReference)
func GetTargetObjKey(a *appv1.Application, un *unstructured.Unstructured, isNamespaced bool) kube.ResourceKey {
key := kube.GetResourceKey(un)
if !isNamespaced {
@@ -42,26 +44,35 @@ func GetTargetObjKey(a *appv1.Application, un *unstructured.Unstructured, isName
return key
}
func NewLiveStateCache(db db.ArgoDB, appInformer cache.SharedIndexInformer, settings *settings.ArgoCDSettings, kubectl kube.Kubectl, onAppUpdated func(appName string, fullRefresh bool)) LiveStateCache {
func NewLiveStateCache(
db db.ArgoDB,
appInformer cache.SharedIndexInformer,
settings *settings.ArgoCDSettings,
kubectl kube.Kubectl,
metricsServer *metrics.MetricsServer,
onAppUpdated AppUpdatedHandler) LiveStateCache {
return &liveStateCache{
appInformer: appInformer,
db: db,
clusters: make(map[string]*clusterInfo),
lock: &sync.Mutex{},
onAppUpdated: onAppUpdated,
kubectl: kubectl,
settings: settings,
appInformer: appInformer,
db: db,
clusters: make(map[string]*clusterInfo),
lock: &sync.Mutex{},
onAppUpdated: onAppUpdated,
kubectl: kubectl,
settings: settings,
metricsServer: metricsServer,
}
}
type liveStateCache struct {
db db.ArgoDB
clusters map[string]*clusterInfo
lock *sync.Mutex
appInformer cache.SharedIndexInformer
onAppUpdated func(appName string, fullRefresh bool)
kubectl kube.Kubectl
settings *settings.ArgoCDSettings
db db.ArgoDB
clusters map[string]*clusterInfo
lock *sync.Mutex
appInformer cache.SharedIndexInformer
onAppUpdated AppUpdatedHandler
kubectl kube.Kubectl
settings *settings.ArgoCDSettings
metricsServer *metrics.MetricsServer
}
func (c *liveStateCache) getCluster(server string) (*clusterInfo, error) {
@@ -116,14 +127,6 @@ func (c *liveStateCache) Invalidate() {
log.Info("live state cache invalidated")
}
func (c *liveStateCache) Delete(server string, obj *unstructured.Unstructured) error {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
return err
}
return clusterInfo.delete(obj)
}
func (c *liveStateCache) IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error) {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
@@ -146,7 +149,7 @@ func (c *liveStateCache) GetManagedLiveObjs(a *appv1.Application, targetObjs []*
if err != nil {
return nil, err
}
return clusterInfo.getManagedLiveObjs(a, targetObjs)
return clusterInfo.getManagedLiveObjs(a, targetObjs, c.metricsServer)
}
func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {

View File

@@ -7,8 +7,9 @@ import (
"sync"
"time"
"github.com/argoproj/argo-cd/controller/metrics"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -44,7 +45,7 @@ type clusterInfo struct {
nodes map[kube.ResourceKey]*node
nsIndex map[string]map[kube.ResourceKey]*node
onAppUpdated func(appName string, fullRefresh bool)
onAppUpdated AppUpdatedHandler
kubectl kube.Kubectl
cluster *appv1.Cluster
log *log.Entry
@@ -93,13 +94,8 @@ func (c *clusterInfo) createObjInfo(un *unstructured.Unstructured, appInstanceLa
}
nodeInfo := &node{
resourceVersion: un.GetResourceVersion(),
ref: v1.ObjectReference{
APIVersion: un.GetAPIVersion(),
Kind: un.GetKind(),
Name: un.GetName(),
Namespace: un.GetNamespace(),
},
ownerRefs: ownerRefs,
ref: kube.GetObjectRef(un),
ownerRefs: ownerRefs,
}
populateNodeInfo(un, nodeInfo)
appName := kube.GetAppInstanceLabel(un, appInstanceLabel)
@@ -351,7 +347,7 @@ func (c *clusterInfo) isNamespaced(obj *unstructured.Unstructured) bool {
return true
}
func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured, metricsServer *metrics.MetricsServer) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
c.lock.Lock()
defer c.lock.Unlock()
@@ -362,6 +358,7 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
managedObjs[key] = o.resource
}
}
config := metrics.AddMetricsTransportWrapper(metricsServer, a, c.cluster.RESTConfig())
// iterate target objects and identify ones that already exist in the cluster,\
// but are simply missing our label
lock := &sync.Mutex{}
@@ -378,7 +375,7 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
managedObj = existingObj.resource
} else {
var err error
managedObj, err = c.kubectl.GetResource(c.cluster.RESTConfig(), targetObj.GroupVersionKind(), existingObj.ref.Name, existingObj.ref.Namespace)
managedObj, err = c.kubectl.GetResource(config, targetObj.GroupVersionKind(), existingObj.ref.Name, existingObj.ref.Namespace)
if err != nil {
if errors.IsNotFound(err) {
return nil
@@ -390,9 +387,19 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
}
if managedObj != nil {
managedObj, err := c.kubectl.ConvertToVersion(managedObj, targetObj.GroupVersionKind().Group, targetObj.GroupVersionKind().Version)
converted, err := c.kubectl.ConvertToVersion(managedObj, targetObj.GroupVersionKind().Group, targetObj.GroupVersionKind().Version)
if err != nil {
return err
// fallback to loading resource from kubernetes if conversion fails
log.Warnf("Failed to convert resource: %v", err)
managedObj, err = c.kubectl.GetResource(config, targetObj.GroupVersionKind(), managedObj.GetName(), managedObj.GetNamespace())
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
} else {
managedObj = converted
}
lock.Lock()
managedObjs[key] = managedObj
@@ -407,10 +414,6 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
return managedObjs, nil
}
func (c *clusterInfo) delete(obj *unstructured.Unstructured) error {
return c.kubectl.DeleteResource(c.cluster.RESTConfig(), obj.GroupVersionKind(), obj.GetName(), obj.GetNamespace(), false)
}
func (c *clusterInfo) processEvent(event watch.EventType, un *unstructured.Unstructured) error {
c.lock.Lock()
defer c.lock.Unlock()
@@ -447,7 +450,7 @@ func (c *clusterInfo) onNodeUpdated(exists bool, existingNode *node, un *unstruc
}
}
for name, full := range toNotify {
c.onAppUpdated(name, full)
c.onAppUpdated(name, full, newObj.ref)
}
}
@@ -459,7 +462,7 @@ func (c *clusterInfo) onNodeRemoved(key kube.ResourceKey, n *node) {
c.removeNode(key)
if appName != "" {
c.onAppUpdated(appName, n.isRootAppNode())
c.onAppUpdated(appName, n.isRootAppNode(), n.ref)
}
}

View File

@@ -107,6 +107,10 @@ var (
serviceName: helm-guestbook
servicePort: 443
path: /
- backend:
serviceName: helm-guestbook
servicePort: https
path: /
status:
loadBalancer:
ingress:
@@ -142,7 +146,7 @@ func newClusterExt(kubectl kube.Kubectl) *clusterInfo {
return &clusterInfo{
lock: &sync.Mutex{},
nodes: make(map[kube.ResourceKey]*node),
onAppUpdated: func(appName string, fullRefresh bool) {},
onAppUpdated: func(appName string, fullRefresh bool, reference corev1.ObjectReference) {},
kubectl: kubectl,
nsIndex: make(map[string]map[kube.ResourceKey]*node),
cluster: &appv1.Cluster{},
@@ -225,7 +229,7 @@ metadata:
Namespace: "default",
},
},
}, []*unstructured.Unstructured{targetDeploy})
}, []*unstructured.Unstructured{targetDeploy}, nil)
assert.Nil(t, err)
assert.Equal(t, managedObjs, map[kube.ResourceKey]*unstructured.Unstructured{
kube.NewResourceKey("apps", "Deployment", "default", "helm-guestbook"): testDeploy,
@@ -351,7 +355,7 @@ func TestUpdateResourceTags(t *testing.T) {
func TestUpdateAppResource(t *testing.T) {
updatesReceived := make([]string, 0)
cluster := newCluster(testPod, testRS, testDeploy)
cluster.onAppUpdated = func(appName string, fullRefresh bool) {
cluster.onAppUpdated = func(appName string, fullRefresh bool, _ corev1.ObjectReference) {
updatesReceived = append(updatesReceived, fmt.Sprintf("%s: %v", appName, fullRefresh))
}

View File

@@ -9,6 +9,7 @@ import (
k8snode "k8s.io/kubernetes/pkg/util/node"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/kube"
)
@@ -63,14 +64,15 @@ func populateServiceInfo(un *unstructured.Unstructured, node *node) {
}
func populateIngressInfo(un *unstructured.Unstructured, node *node) {
targets := make([]v1alpha1.ResourceRef, 0)
ingress := getIngress(un)
targetsMap := make(map[v1alpha1.ResourceRef]bool)
if backend, ok, err := unstructured.NestedMap(un.Object, "spec", "backend"); ok && err == nil {
targets = append(targets, v1alpha1.ResourceRef{
targetsMap[v1alpha1.ResourceRef{
Group: "",
Kind: kube.ServiceKind,
Namespace: un.GetNamespace(),
Name: fmt.Sprintf("%s", backend["serviceName"]),
})
}] = true
}
urlsSet := make(map[string]bool)
if rules, ok, err := unstructured.NestedSlice(un.Object, "spec", "rules"); ok && err == nil {
@@ -80,6 +82,14 @@ func populateIngressInfo(un *unstructured.Unstructured, node *node) {
continue
}
host := rule["host"]
if host == nil || host == "" {
for i := range ingress {
host = util.FirstNonEmpty(ingress[i].Hostname, ingress[i].IP)
if host != "" {
break
}
}
}
paths, ok, err := unstructured.NestedSlice(rule, "http", "paths")
if !ok || err != nil {
continue
@@ -91,32 +101,48 @@ func populateIngressInfo(un *unstructured.Unstructured, node *node) {
}
if serviceName, ok, err := unstructured.NestedString(path, "backend", "serviceName"); ok && err == nil {
targets = append(targets, v1alpha1.ResourceRef{
targetsMap[v1alpha1.ResourceRef{
Group: "",
Kind: kube.ServiceKind,
Namespace: un.GetNamespace(),
Name: serviceName,
})
}] = true
}
if port, ok, err := unstructured.NestedFieldNoCopy(path, "backend", "servicePort"); ok && err == nil && host != "" {
switch fmt.Sprintf("%v", port) {
case "80":
if port, ok, err := unstructured.NestedFieldNoCopy(path, "backend", "servicePort"); ok && err == nil && host != "" && host != nil {
stringPort := ""
switch typedPod := port.(type) {
case int64:
stringPort = fmt.Sprintf("%d", typedPod)
case float64:
stringPort = fmt.Sprintf("%d", int64(typedPod))
case string:
stringPort = typedPod
default:
stringPort = fmt.Sprintf("%v", port)
}
switch stringPort {
case "80", "http":
urlsSet[fmt.Sprintf("http://%s", host)] = true
case "443":
case "443", "https":
urlsSet[fmt.Sprintf("https://%s", host)] = true
default:
urlsSet[fmt.Sprintf("http://%s:%s", host, port)] = true
urlsSet[fmt.Sprintf("http://%s:%s", host, stringPort)] = true
}
}
}
}
}
targets := make([]v1alpha1.ResourceRef, 0)
for target := range targetsMap {
targets = append(targets, target)
}
urls := make([]string, 0)
for url := range urlsSet {
urls = append(urls, url)
}
node.networkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, Ingress: getIngress(un), ExternalURLs: urls}
node.networkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, Ingress: ingress, ExternalURLs: urls}
}
func populatePodInfo(un *unstructured.Unstructured, node *node) {
@@ -135,13 +161,20 @@ func populatePodInfo(un *unstructured.Unstructured, node *node) {
reason = pod.Status.Reason
}
initializing := false
// note that I ignore initContainers
imagesSet := make(map[string]bool)
for _, container := range pod.Spec.InitContainers {
imagesSet[container.Image] = true
}
for _, container := range pod.Spec.Containers {
node.images = append(node.images, container.Image)
imagesSet[container.Image] = true
}
node.images = nil
for image := range imagesSet {
node.images = append(node.images, image)
}
initializing := false
for i := range pod.Status.InitContainerStatuses {
container := pod.Status.InitContainerStatuses[i]
restarts += int(container.RestartCount)

View File

@@ -1,6 +1,8 @@
package cache
import (
"sort"
"strings"
"testing"
v1 "k8s.io/api/core/v1"
@@ -50,6 +52,9 @@ func TestGetIngressInfo(t *testing.T) {
node := &node{}
populateNodeInfo(testIngress, node)
assert.Equal(t, 0, len(node.info))
sort.Slice(node.networkingInfo.TargetRefs, func(i, j int) bool {
return strings.Compare(node.networkingInfo.TargetRefs[j].Name, node.networkingInfo.TargetRefs[i].Name) < 0
})
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
TargetRefs: []v1alpha1.ResourceRef{{
@@ -66,3 +71,38 @@ func TestGetIngressInfo(t *testing.T) {
ExternalURLs: []string{"https://helm-guestbook.com"},
}, node.networkingInfo)
}
func TestGetIngressInfoNoHost(t *testing.T) {
ingress := strToUnstructured(`
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: helm-guestbook
namespace: default
spec:
rules:
- http:
paths:
- backend:
serviceName: helm-guestbook
servicePort: 443
path: /
status:
loadBalancer:
ingress:
- ip: 107.178.210.11`)
node := &node{}
populateNodeInfo(ingress, node)
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
TargetRefs: []v1alpha1.ResourceRef{{
Namespace: "default",
Group: "",
Kind: kube.ServiceKind,
Name: "helm-guestbook",
}},
ExternalURLs: []string{"https://107.178.210.11"},
}, node.networkingInfo)
}

View File

@@ -2,6 +2,7 @@ package metrics
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
@@ -17,6 +18,7 @@ import (
type MetricsServer struct {
*http.Server
syncCounter *prometheus.CounterVec
k8sRequestCounter *prometheus.CounterVec
reconcileHistogram *prometheus.HistogramVec
}
@@ -72,6 +74,14 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister) *Metri
append(descAppDefaultLabels, "phase"),
)
appRegistry.MustRegister(syncCounter)
k8sRequestCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "argocd_app_k8s_request_total",
Help: "Number of kubernetes requests executed during application reconciliation.",
},
append(descAppDefaultLabels, "response_code"),
)
appRegistry.MustRegister(k8sRequestCounter)
reconcileHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
@@ -91,6 +101,7 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister) *Metri
Handler: mux,
},
syncCounter: syncCounter,
k8sRequestCounter: k8sRequestCounter,
reconcileHistogram: reconcileHistogram,
}
}
@@ -103,6 +114,11 @@ func (m *MetricsServer) IncSync(app *argoappv1.Application, state *argoappv1.Ope
m.syncCounter.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject(), string(state.Phase)).Inc()
}
// IncKubernetesRequest increments the kubernetes requests counter for an application
func (m *MetricsServer) IncKubernetesRequest(app *argoappv1.Application, statusCode int) {
m.k8sRequestCounter.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject(), strconv.Itoa(statusCode)).Inc()
}
// IncReconcile increments the reconcile counter for an application
func (m *MetricsServer) IncReconcile(app *argoappv1.Application, duration time.Duration) {
m.reconcileHistogram.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject()).Observe(duration.Seconds())

View File

@@ -0,0 +1,37 @@
package metrics
import (
"net/http"
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
type metricsRoundTripper struct {
roundTripper http.RoundTripper
app *v1alpha1.Application
metricsServer *MetricsServer
}
func (mrt *metricsRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
resp, err := mrt.roundTripper.RoundTrip(r)
statusCode := 0
if resp != nil {
statusCode = resp.StatusCode
}
mrt.metricsServer.IncKubernetesRequest(mrt.app, statusCode)
return resp, err
}
// AddMetricsTransportWrapper adds a transport wrapper which increments 'argocd_app_k8s_request_total' counter on each kubernetes request
func AddMetricsTransportWrapper(server *MetricsServer, app *v1alpha1.Application, config *rest.Config) *rest.Config {
wrap := config.WrapTransport
config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
if wrap != nil {
rt = wrap(rt)
}
return &metricsRoundTripper{roundTripper: rt, metricsServer: server, app: app}
}
return config
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/argoproj/argo-cd/common"
statecache "github.com/argoproj/argo-cd/controller/cache"
"github.com/argoproj/argo-cd/controller/metrics"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
@@ -73,6 +74,7 @@ type comparisonResult struct {
// appStateManager allows to compare applications to git
type appStateManager struct {
metricsServer *metrics.MetricsServer
db db.ArgoDB
settings *settings.ArgoCDSettings
appclientset appclientset.Interface
@@ -88,7 +90,10 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
if err != nil {
return nil, nil, nil, err
}
repo := m.getRepo(source.RepoURL)
repo, err := m.db.GetRepository(context.Background(), source.RepoURL)
if err != nil {
return nil, nil, nil, err
}
conn, repoClient, err := m.repoClientset.NewRepoServerClient()
if err != nil {
return nil, nil, nil, err
@@ -311,7 +316,10 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
syncStatus.Revision = manifestInfo.Revision
}
healthStatus, err := health.SetApplicationHealth(resourceSummaries, GetLiveObjs(managedResources), m.settings.ResourceOverrides)
healthStatus, err := health.SetApplicationHealth(resourceSummaries, GetLiveObjs(managedResources), m.settings.ResourceOverrides, func(obj *unstructured.Unstructured) bool {
return !isSelfReferencedApp(app, kubeutil.GetObjectRef(obj))
})
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
}
@@ -332,15 +340,6 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
return &compRes, nil
}
func (m *appStateManager) getRepo(repoURL string) *v1alpha1.Repository {
repo, err := m.db.GetRepository(context.Background(), repoURL)
if err != nil {
// If we couldn't retrieve from the repo service, assume public repositories
repo = &v1alpha1.Repository{Repo: repoURL}
}
return repo
}
func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource) error {
var nextID int64
if len(app.Status.History) > 0 {
@@ -379,6 +378,7 @@ func NewAppStateManager(
settings *settings.ArgoCDSettings,
liveStateCache statecache.LiveStateCache,
projInformer cache.SharedIndexInformer,
metricsServer *metrics.MetricsServer,
) AppStateManager {
return &appStateManager{
liveStateCache: liveStateCache,
@@ -389,5 +389,6 @@ func NewAppStateManager(
namespace: namespace,
settings: settings,
projInformer: projInformer,
metricsServer: metricsServer,
}
}

View File

@@ -4,6 +4,9 @@ import (
"encoding/json"
"testing"
v1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -178,3 +181,83 @@ func TestCompareAppStateDuplicatedNamespacedResources(t *testing.T) {
})
assert.Equal(t, 2, len(compRes.resources))
}
var defaultProj = argoappv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: test.FakeArgoCDNamespace,
},
Spec: argoappv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []argoappv1.ApplicationDestination{
{
Server: "*",
Namespace: "*",
},
},
},
}
func TestSetHealth(t *testing.T) {
app := newFakeApp()
deployment := kube.MustToUnstructured(&v1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1beta1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: "demo",
Namespace: "default",
},
})
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &repository.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(deployment): deployment,
},
})
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
assert.Equal(t, compRes.healthStatus.Status, argoappv1.HealthStatusHealthy)
}
func TestSetHealthSelfReferencedApp(t *testing.T) {
app := newFakeApp()
unstructuredApp := kube.MustToUnstructured(app)
deployment := kube.MustToUnstructured(&v1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1beta1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: "demo",
Namespace: "default",
},
})
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &repository.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(deployment): deployment,
kube.GetResourceKey(unstructuredApp): unstructuredApp,
},
})
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
assert.Equal(t, compRes.healthStatus.Status, argoappv1.HealthStatusHealthy)
}

View File

@@ -14,6 +14,7 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/controller/metrics"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/util/argo"
@@ -116,7 +117,7 @@ func (m *appStateManager) SyncAppState(app *appv1.Application, state *appv1.Oper
return
}
restConfig := clst.RESTConfig()
restConfig := metrics.AddMetricsTransportWrapper(m.metricsServer, app, clst.RESTConfig())
dynamicIf, err := dynamic.NewForConfig(restConfig)
if err != nil {
state.Phase = appv1.OperationError

View File

@@ -29,6 +29,8 @@ data:
issuer: https://dev-123456.oktapreview.com
clientID: aaaabbbbccccddddeee
clientSecret: $oidc.okta.clientSecret
# Optional set of OIDC scopes to request. If omitted, defaults to: ["openid", "profile", "email", "groups"]
requestedScopes: ["openid", "profile", "email"]
# Git repositories configure Argo CD with (optional).
# This list is updated when configuring/removing repos from the UI/CLI

View File

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

View File

@@ -150,6 +150,88 @@ data:
key: sshPrivateKey
```
!!! tip
The Kubernetes documentation has [instructions for creating a secret containing a private key](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys).
### Repository Credentials (v1.1+)
If you want to use the same credentials for multiple repositories, you can use `repository.credentials`:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
data:
repositories: |
- url: https://github.com/argoproj/private-repo
- url: https://github.com/argoproj/other-private-repo
repository.credentials: |
- url: https://github.com/argoproj
passwordSecret:
name: my-secret
key: password
usernameSecret:
name: my-secret
key: username
```
Argo CD will only use the credentials if you omit `usernameSecret`, `passwordSecret`, and `sshPrivateKeySecret` fields (`insecureIgnoreHostKey` is ignored).
A credential may be match if it's URL is the prefix of the repository's URL. The means that credentials may match, e.g in the above example both [https://github.com/argoproj](https://github.com/argoproj) and [https://github.com](https://github.com) would match. Argo CD selects the first one that matches.
!!! tip
Order your credentials with the most specific at the top and the least specific at the bottom.
A complete example.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
data:
repositories: |
# this has it's own credentials
- url: https://github.com/argoproj/private-repo
passwordSecret:
name: private-repo-secret
key: password
usernameSecret:
name: private-repo-secret
key: username
sshPrivateKeySecret:
name: private-repo-secret
key: sshPrivateKey
- url: https://github.com/argoproj/other-private-repo
- url: https://github.com/otherproj/another-private-repo
repository.credentials: |
# this will be used for the second repo
- url: https://github.com/argoproj
passwordSecret:
name: other-private-repo-secret
key: password
usernameSecret:
name: other-private-repo-secret
key: username
sshPrivateKeySecret:
name: other-private-repo-secret
key: sshPrivateKey
# this will be used for the third repo
- url: https://github.com
passwordSecret:
name: another-private-repo-secret
key: password
usernameSecret:
name: another-private-repo-secret
key: username
sshPrivateKeySecret:
name: another-private-repo-secret
key: sshPrivateKey
```
## Clusters
Cluster credentials are stored in secrets same as repository credentials but does not require entry in `argocd-cm` config map. Each secret must have label

View File

@@ -12,7 +12,7 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v1.0.1
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: latest
newTag: v1.0.1

View File

@@ -32,3 +32,8 @@ spec:
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8081
initialDelaySeconds: 5
periodSeconds: 10

View File

@@ -41,6 +41,12 @@ spec:
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
volumes:
- emptyDir: {}
name: static-files

View File

@@ -479,12 +479,6 @@ spec:
- message
type: object
type: array
externalURLs:
description: ExternalURLs holds all external URLs of application child
resources.
items:
type: string
type: array
health:
properties:
message:
@@ -1144,6 +1138,20 @@ spec:
type: array
sourceType:
type: string
summary:
properties:
externalURLs:
description: ExternalURLs holds all external URLs of application
child resources.
items:
type: string
type: array
images:
description: Images holds all images of application child resources.
items:
type: string
type: array
type: object
sync:
description: SyncStatus is a comparison result of application spec and
deployed application.

View File

@@ -17,7 +17,7 @@ patchesStrategicMerge:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v1.0.1
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: latest
newTag: v1.0.1

View File

@@ -185,10 +185,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -419,10 +415,6 @@ spec:
description: NamePrefix is a prefix appended to resources for
kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository containing
@@ -633,10 +625,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -670,9 +658,6 @@ spec:
- source
type: object
type: array
ingress:
items: {}
type: array
observedAt: {}
operationState:
description: OperationState contains information about state of currently
@@ -853,10 +838,6 @@ spec:
description: NamePrefix is a prefix appended to
resources for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1092,10 +1073,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1162,6 +1139,20 @@ spec:
type: array
sourceType:
type: string
summary:
properties:
externalURLs:
description: ExternalURLs holds all external URLs of application
child resources.
items:
type: string
type: array
images:
description: Images holds all images of application child resources.
items:
type: string
type: array
type: object
sync:
description: SyncStatus is a comparison result of application spec and
deployed application.
@@ -1312,10 +1303,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -2138,7 +2125,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -2185,7 +2172,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2240,8 +2227,13 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
name: argocd-repo-server
ports:
- containerPort: 8081
@@ -2297,8 +2289,14 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
name: argocd-server
ports:
- containerPort: 8080
@@ -2318,7 +2316,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v1.0.1
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -185,10 +185,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -419,10 +415,6 @@ spec:
description: NamePrefix is a prefix appended to resources for
kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository containing
@@ -633,10 +625,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -670,9 +658,6 @@ spec:
- source
type: object
type: array
ingress:
items: {}
type: array
observedAt: {}
operationState:
description: OperationState contains information about state of currently
@@ -853,10 +838,6 @@ spec:
description: NamePrefix is a prefix appended to
resources for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1092,10 +1073,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1162,6 +1139,20 @@ spec:
type: array
sourceType:
type: string
summary:
properties:
externalURLs:
description: ExternalURLs holds all external URLs of application
child resources.
items:
type: string
type: array
images:
description: Images holds all images of application child resources.
items:
type: string
type: array
type: object
sync:
description: SyncStatus is a comparison result of application spec and
deployed application.
@@ -1312,10 +1303,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -2053,7 +2040,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -2100,7 +2087,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2155,8 +2142,13 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
name: argocd-repo-server
ports:
- containerPort: 8081
@@ -2212,8 +2204,14 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
name: argocd-server
ports:
- containerPort: 8080
@@ -2233,7 +2231,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v1.0.1
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -185,10 +185,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -419,10 +415,6 @@ spec:
description: NamePrefix is a prefix appended to resources for
kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository containing
@@ -633,10 +625,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -670,9 +658,6 @@ spec:
- source
type: object
type: array
ingress:
items: {}
type: array
observedAt: {}
operationState:
description: OperationState contains information about state of currently
@@ -853,10 +838,6 @@ spec:
description: NamePrefix is a prefix appended to
resources for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1092,10 +1073,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1162,6 +1139,20 @@ spec:
type: array
sourceType:
type: string
summary:
properties:
externalURLs:
description: ExternalURLs holds all external URLs of application
child resources.
items:
type: string
type: array
images:
description: Images holds all images of application child resources.
items:
type: string
type: array
type: object
sync:
description: SyncStatus is a comparison result of application spec and
deployed application.
@@ -1312,10 +1303,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1902,7 +1889,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -1949,7 +1936,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2012,8 +1999,13 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
name: argocd-repo-server
ports:
- containerPort: 8081
@@ -2046,8 +2038,14 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
name: argocd-server
ports:
- containerPort: 8080
@@ -2067,7 +2065,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v1.0.1
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -185,10 +185,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -419,10 +415,6 @@ spec:
description: NamePrefix is a prefix appended to resources for
kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository containing
@@ -633,10 +625,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -670,9 +658,6 @@ spec:
- source
type: object
type: array
ingress:
items: {}
type: array
observedAt: {}
operationState:
description: OperationState contains information about state of currently
@@ -853,10 +838,6 @@ spec:
description: NamePrefix is a prefix appended to
resources for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1092,10 +1073,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1162,6 +1139,20 @@ spec:
type: array
sourceType:
type: string
summary:
properties:
externalURLs:
description: ExternalURLs holds all external URLs of application
child resources.
items:
type: string
type: array
images:
description: Images holds all images of application child resources.
items:
type: string
type: array
type: object
sync:
description: SyncStatus is a comparison result of application spec and
deployed application.
@@ -1312,10 +1303,6 @@ spec:
description: NamePrefix is a prefix appended to resources
for kustomize apps
type: string
required:
- namePrefix
- imageTags
- images
type: object
path:
description: Path is a directory path within the repository
@@ -1817,7 +1804,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -1864,7 +1851,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -1927,8 +1914,13 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
name: argocd-repo-server
ports:
- containerPort: 8081
@@ -1961,8 +1953,14 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v1.0.1
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
name: argocd-server
ports:
- containerPort: 8080
@@ -1982,7 +1980,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v1.0.1
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -15,8 +15,8 @@ import (
"sync"
"time"
oidc "github.com/coreos/go-oidc"
jwt "github.com/dgrijalva/jwt-go"
"github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"google.golang.org/grpc"
@@ -38,6 +38,7 @@ import (
"github.com/argoproj/argo-cd/server/version"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/localconfig"
oidcutil "github.com/argoproj/argo-cd/util/oidc"
tls_util "github.com/argoproj/argo-cd/util/tls"
)
@@ -51,10 +52,6 @@ const (
MaxGRPCMessageSize = 100 * 1024 * 1024
)
var (
clientScopes = []string{"openid", "profile", "email", "groups", "offline_access"}
)
// Client defines an interface for interaction with an Argo CD server.
type Client interface {
ClientOptions() ClientOptions
@@ -197,16 +194,18 @@ func NewClient(opts *ClientOptions) (Client, error) {
func (c *client) OIDCConfig(ctx context.Context, set *settings.Settings) (*oauth2.Config, *oidc.Provider, error) {
var clientID string
var issuerURL string
if set.DexConfig != nil && len(set.DexConfig.Connectors) > 0 {
clientID = common.ArgoCDCLIClientAppID
issuerURL = fmt.Sprintf("%s%s", set.URL, common.DexAPIEndpoint)
} else if set.OIDCConfig != nil && set.OIDCConfig.Issuer != "" {
var scopes []string
if set.OIDCConfig != nil && set.OIDCConfig.Issuer != "" {
if set.OIDCConfig.CLIClientID != "" {
clientID = set.OIDCConfig.CLIClientID
} else {
clientID = set.OIDCConfig.ClientID
}
issuerURL = set.OIDCConfig.Issuer
scopes = set.OIDCConfig.Scopes
} else if set.DexConfig != nil && len(set.DexConfig.Connectors) > 0 {
clientID = common.ArgoCDCLIClientAppID
issuerURL = fmt.Sprintf("%s%s", set.URL, common.DexAPIEndpoint)
} else {
return nil, nil, fmt.Errorf("%s is not configured with SSO", c.ServerAddr)
}
@@ -214,9 +213,17 @@ func (c *client) OIDCConfig(ctx context.Context, set *settings.Settings) (*oauth
if err != nil {
return nil, nil, fmt.Errorf("Failed to query provider %q: %v", issuerURL, err)
}
oidcConf, err := oidcutil.ParseConfig(provider)
if err != nil {
return nil, nil, fmt.Errorf("Failed to parse provider config: %v", err)
}
scopes = oidcutil.GetScopesOrDefault(scopes)
if oidcutil.OfflineAccess(oidcConf.ScopesSupported) {
scopes = append(scopes, oidc.ScopeOfflineAccess)
}
oauth2conf := oauth2.Config{
ClientID: clientID,
Scopes: clientScopes,
Scopes: scopes,
Endpoint: provider.Endpoint(),
}
return &oauth2conf, provider, nil

File diff suppressed because it is too large Load Diff

View File

@@ -223,8 +223,15 @@ message ApplicationStatus {
optional string sourceType = 9;
optional ApplicationSummary summary = 10;
}
message ApplicationSummary {
// ExternalURLs holds all external URLs of application child resources.
repeated string externalURLs = 10;
repeated string externalURLs = 1;
// Images holds all images of application child resources.
repeated string images = 2;
}
// ApplicationTree holds nodes which belongs to the application

View File

@@ -30,6 +30,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationSourcePlugin": schema_pkg_apis_application_v1alpha1_ApplicationSourcePlugin(ref),
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationSpec": schema_pkg_apis_application_v1alpha1_ApplicationSpec(ref),
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationStatus": schema_pkg_apis_application_v1alpha1_ApplicationStatus(ref),
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationSummary": schema_pkg_apis_application_v1alpha1_ApplicationSummary(ref),
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationTree": schema_pkg_apis_application_v1alpha1_ApplicationTree(ref),
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationWatchEvent": schema_pkg_apis_application_v1alpha1_ApplicationWatchEvent(ref),
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.Cluster": schema_pkg_apis_application_v1alpha1_Cluster(ref),
@@ -840,6 +841,25 @@ func schema_pkg_apis_application_v1alpha1_ApplicationStatus(ref common.Reference
Format: "",
},
},
"summary": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationSummary"),
},
},
},
},
},
Dependencies: []string{
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationCondition", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationSummary", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.HealthStatus", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.OperationState", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ResourceStatus", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.RevisionHistory", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SyncStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationSummary(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"externalURLs": {
SchemaProps: spec.SchemaProps{
Description: "ExternalURLs holds all external URLs of application child resources.",
@@ -854,11 +874,23 @@ func schema_pkg_apis_application_v1alpha1_ApplicationStatus(ref common.Reference
},
},
},
"images": {
SchemaProps: spec.SchemaProps{
Description: "Images holds all images of application child resources.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationCondition", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.HealthStatus", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.OperationState", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ResourceStatus", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.RevisionHistory", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SyncStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
}
}

View File

@@ -225,8 +225,7 @@ type ApplicationStatus struct {
OperationState *OperationState `json:"operationState,omitempty" protobuf:"bytes,7,opt,name=operationState"`
ObservedAt metav1.Time `json:"observedAt,omitempty" protobuf:"bytes,8,opt,name=observedAt"`
SourceType ApplicationSourceType `json:"sourceType,omitempty" protobuf:"bytes,9,opt,name=sourceType"`
// ExternalURLs holds all external URLs of application child resources.
ExternalURLs []string `json:"externalURLs,omitempty" protobuf:"bytes,10,opt,name=externalURLs"`
Summary ApplicationSummary `json:"summary,omitempty" protobuf:"bytes,10,opt,name=summary"`
}
// Operation contains requested operation parameters.
@@ -538,6 +537,13 @@ type ApplicationTree struct {
Nodes []ResourceNode `json:"nodes,omitempty" protobuf:"bytes,1,rep,name=nodes"`
}
type ApplicationSummary struct {
// ExternalURLs holds all external URLs of application child resources.
ExternalURLs []string `json:"externalURLs,omitempty" protobuf:"bytes,1,opt,name=externalURLs"`
// Images holds all images of application child resources.
Images []string `json:"images,omitempty" protobuf:"bytes,2,opt,name=images"`
}
func (t *ApplicationTree) FindNode(group string, kind string, namespace string, name string) *ResourceNode {
for _, n := range t.Nodes {
if n.Group == group && n.Kind == kind && n.Namespace == namespace && n.Name == name {
@@ -547,20 +553,28 @@ func (t *ApplicationTree) FindNode(group string, kind string, namespace string,
return nil
}
func (t *ApplicationTree) GetBrowableURLs() []string {
func (t *ApplicationTree) GetSummary() ApplicationSummary {
urlsSet := make(map[string]bool)
imagesSet := make(map[string]bool)
for _, node := range t.Nodes {
if node.NetworkingInfo != nil {
for _, url := range node.NetworkingInfo.ExternalURLs {
urlsSet[url] = true
}
}
for _, image := range node.Images {
imagesSet[image] = true
}
}
urls := make([]string, 0)
for url := range urlsSet {
urls = append(urls, url)
}
return urls
images := make([]string, 0)
for image := range imagesSet {
images = append(images, image)
}
return ApplicationSummary{ExternalURLs: urls, Images: images}
}
// ResourceRef includes fields which unique identify resource
@@ -756,6 +770,17 @@ type Repository struct {
InsecureIgnoreHostKey bool `json:"insecureIgnoreHostKey,omitempty" protobuf:"bytes,6,opt,name=insecureIgnoreHostKey"`
}
func (m *Repository) HasCredentials() bool {
return m.Username != "" || m.Password != "" || m.SSHPrivateKey != "" || m.InsecureIgnoreHostKey
}
func (m *Repository) CopyCredentialsFrom(source Repository) {
m.Username = source.Username
m.Password = source.Password
m.SSHPrivateKey = source.SSHPrivateKey
m.InsecureIgnoreHostKey = source.InsecureIgnoreHostKey
}
// RepositoryList is a collection of Repositories.
type RepositoryList struct {
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

View File

@@ -199,3 +199,87 @@ func TestAppProjectSpec_DestinationClusters(t *testing.T) {
})
}
}
func TestRepository_HasCredentials(t *testing.T) {
tests := []struct {
name string
repo Repository
want bool
}{
{
name: "TestHasRepo",
repo: Repository{Repo: "foo"},
want: false,
},
{
name: "TestHasUsername",
repo: Repository{Username: "foo"},
want: true,
},
{
name: "TestHasPassword",
repo: Repository{Password: "foo"},
want: true,
},
{
name: "TestHasSSHPrivateKey",
repo: Repository{SSHPrivateKey: "foo"},
want: true,
},
{
name: "TestHasInsecureHostKey",
repo: Repository{InsecureIgnoreHostKey: true},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.repo.HasCredentials(); got != tt.want {
t.Errorf("Repository.HasCredentials() = %v, want %v", got, tt.want)
}
})
}
}
func TestRepository_CopyCredentialsFrom(t *testing.T) {
tests := []struct {
name string
source Repository
want Repository
}{
{
name: "TestHasRepo",
source: Repository{Repo: "foo"},
want: Repository{},
},
{
name: "TestHasUsername",
source: Repository{Username: "foo"},
want: Repository{Username: "foo"},
},
{
name: "TestHasPassword",
source: Repository{Password: "foo"},
want: Repository{Password: "foo"},
},
{
name: "TestHasSSHPrivateKey",
source: Repository{SSHPrivateKey: "foo"},
want: Repository{SSHPrivateKey: "foo"},
},
{
name: "TestHasInsecureHostKey",
source: Repository{InsecureIgnoreHostKey: true},
want: Repository{InsecureIgnoreHostKey: true},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := Repository{}
repo.CopyCredentialsFrom(tt.source)
assert.Equal(t, tt.want, repo)
})
}
}

View File

@@ -461,11 +461,7 @@ func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
(*in).DeepCopyInto(*out)
}
in.ObservedAt.DeepCopyInto(&out.ObservedAt)
if in.ExternalURLs != nil {
in, out := &in.ExternalURLs, &out.ExternalURLs
*out = make([]string, len(*in))
copy(*out, *in)
}
in.Summary.DeepCopyInto(&out.Summary)
return
}
@@ -479,6 +475,32 @@ func (in *ApplicationStatus) DeepCopy() *ApplicationStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSummary) DeepCopyInto(out *ApplicationSummary) {
*out = *in
if in.ExternalURLs != nil {
in, out := &in.ExternalURLs, &out.ExternalURLs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Images != nil {
in, out := &in.Images, &out.Images
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSummary.
func (in *ApplicationSummary) DeepCopy() *ApplicationSummary {
if in == nil {
return nil
}
out := new(ApplicationSummary)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationTree) DeepCopyInto(out *ApplicationTree) {
*out = *in

View File

@@ -5,13 +5,13 @@ import (
"time"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/util"
argogrpc "github.com/argoproj/argo-cd/util/grpc"
)
// Clientset represets repository server api clients
@@ -20,7 +20,8 @@ type Clientset interface {
}
type clientSet struct {
address string
address string
timeoutSeconds int
}
func (c *clientSet) NewRepoServerClient() (util.Closer, repository.RepoServerServiceClient, error) {
@@ -28,10 +29,14 @@ func (c *clientSet) NewRepoServerClient() (util.Closer, repository.RepoServerSer
grpc_retry.WithMax(3),
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(1000 * time.Millisecond)),
}
conn, err := grpc.Dial(c.address,
opts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})),
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...)))
grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...))}
if c.timeoutSeconds > 0 {
opts = append(opts, grpc.WithUnaryInterceptor(argogrpc.WithTimeout(time.Duration(c.timeoutSeconds)*time.Second)))
}
conn, err := grpc.Dial(c.address, opts...)
if err != nil {
log.Errorf("Unable to connect to repository service with address %s", c.address)
return nil, nil, err
@@ -40,6 +45,6 @@ func (c *clientSet) NewRepoServerClient() (util.Closer, repository.RepoServerSer
}
// NewRepoServerClientset creates new instance of repo server Clientset
func NewRepoServerClientset(address string) Clientset {
return &clientSet{address: address}
func NewRepoServerClientset(address string, timeoutSeconds int) Clientset {
return &clientSet{address: address, timeoutSeconds: timeoutSeconds}
}

View File

@@ -490,7 +490,7 @@ func pathExists(ss ...string) bool {
func (s *Service) newClientResolveRevision(repo *v1alpha1.Repository, revision string) (git.Client, string, error) {
repoURL := git.NormalizeGitURL(repo.Repo)
appRepoPath := tempRepoPath(repoURL)
gitClient, err := s.gitFactory.NewClient(repoURL, appRepoPath, repo.Username, repo.Password, repo.SSHPrivateKey, repo.InsecureIgnoreHostKey)
gitClient, err := s.gitFactory.NewClient(repo.Repo, appRepoPath, repo.Username, repo.Password, repo.SSHPrivateKey, repo.InsecureIgnoreHostKey)
if err != nil {
return nil, "", err
}

View File

@@ -2,6 +2,9 @@ discoveryTests:
- inputPath: testdata/paused_rollout.yaml
result:
- name: resume
- inputPath: testdata/v0.2_paused_rollout.yaml
result:
- name: resume
- inputPath: testdata/not_paused_rollout.yaml
result: []
- inputPath: testdata/nil_paused_rollout.yaml
@@ -10,4 +13,6 @@ actionTests:
- action: resume
inputPath: testdata/paused_rollout.yaml
expectedOutputPath: testdata/not_paused_rollout.yaml
- action: resume
inputPath: testdata/v0.2_paused_rollout.yaml
expectedOutputPath: testdata/v0.2_not_paused_rollout.yaml

View File

@@ -1,6 +1,13 @@
actions = {}
if obj.spec.paused ~= nil and obj.spec.paused then
local paused = false
if obj.status.verifyingPreview ~= nil then
paused = obj.status.verifyingPreview
elseif obj.spec.paused ~= nil then
paused = obj.spec.paused
end
if paused then
actions["resume"] = {}
end

View File

@@ -1,2 +1,9 @@
obj.spec.paused = false
if obj.status.verifyingPreview ~= nil and obj.status.verifyingPreview then
obj.status.verifyingPreview = false
end
if obj.spec.paused ~= nil and obj.spec.paused then
obj.spec.paused = false
end
return obj

View File

@@ -0,0 +1,55 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
rollout.argoproj.io/revision: "7"
clusterName: ""
creationTimestamp: 2019-01-22T16:52:54Z
generation: 1
labels:
app.kubernetes.io/instance: guestbook-default
name: ks-guestbook-ui
namespace: default
resourceVersion: "164113"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/ks-guestbook-ui
uid: 29802403-1e66-11e9-a6a4-025000000001
spec:
minReadySeconds: 30
replicas: 3
selector:
matchLabels:
app: ks-guestbook-ui
strategy:
blueGreen:
activeService: ks-guestbook-ui-active
previewService: ks-guestbook-ui-preview
type: BlueGreenUpdate
template:
metadata:
labels:
app: ks-guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.1
name: ks-guestbook-ui
ports:
- containerPort: 83
resources: {}
status:
blueGreen:
activeSelector: 85f9884f5d
previewSelector: 697fb9575c
availableReplicas: 6
conditions:
- lastTransitionTime: 2019-01-25T07:44:26Z
lastUpdateTime: 2019-01-25T07:44:26Z
message: Rollout is serving traffic from the active service.
reason: Available
status: "True"
type: Available
currentPodHash: 697fb9575c
observedGeneration: 767f98959f
readyReplicas: 6
replicas: 6
updatedReplicas: 3
verifyingPreview: false

View File

@@ -0,0 +1,55 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
rollout.argoproj.io/revision: "7"
clusterName: ""
creationTimestamp: 2019-01-22T16:52:54Z
generation: 1
labels:
app.kubernetes.io/instance: guestbook-default
name: ks-guestbook-ui
namespace: default
resourceVersion: "164113"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/ks-guestbook-ui
uid: 29802403-1e66-11e9-a6a4-025000000001
spec:
minReadySeconds: 30
replicas: 3
selector:
matchLabels:
app: ks-guestbook-ui
strategy:
blueGreen:
activeService: ks-guestbook-ui-active
previewService: ks-guestbook-ui-preview
type: BlueGreenUpdate
template:
metadata:
labels:
app: ks-guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.1
name: ks-guestbook-ui
ports:
- containerPort: 83
resources: {}
status:
blueGreen:
activeSelector: 85f9884f5d
previewSelector: 697fb9575c
availableReplicas: 6
conditions:
- lastTransitionTime: 2019-01-25T07:44:26Z
lastUpdateTime: 2019-01-25T07:44:26Z
message: Rollout is serving traffic from the active service.
reason: Available
status: "True"
type: Available
currentPodHash: 697fb9575c
observedGeneration: 767f98959f
readyReplicas: 6
replicas: 6
updatedReplicas: 3
verifyingPreview: true

View File

@@ -1,3 +1,55 @@
function checkReplicasStatus(obj)
hs = {}
if obj.spec.replicas ~= nil and obj.status.updatedReplicas < obj.spec.replicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: More replicas need to be updated"
return hs
end
if obj.status.replicas > obj.status.updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: old replicas are pending termination"
return hs
end
if obj.status.availableReplicas < obj.status.updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: updated replicas are still becoming available"
return hs
end
if obj.spec.replicas ~= nil and obj.status.updatedReplicas < obj.spec.replicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: More replicas need to be updated"
return hs
end
if obj.status.replicas > obj.status.updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: old replicas are pending termination"
return hs
end
if obj.status.availableReplicas < obj.status.updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: updated replicas are still becoming available"
return hs
end
return nil
end
function checkPaused(obj)
hs = {}
local paused = false
if obj.status.verifyingPreview ~= nil then
paused = obj.status.verifyingPreview
elseif obj.spec.paused ~= nil then
paused = obj.spec.paused
end
if paused then
hs.status = "Suspended"
hs.message = "Rollout is paused"
return hs
end
return nil
end
hs = {}
if obj.status ~= nil then
if obj.status.conditions ~= nil then
@@ -7,44 +59,76 @@ if obj.status ~= nil then
hs.message = condition.message
return hs
end
if condition.type == "Progressing" and condition.reason == "ProgressDeadlineExceeded" then
hs.status = "Degraded"
hs.message = condition.message
return hs
end
end
end
if obj.status.currentPodHash ~= nil then
if obj.spec.replicas ~= nil and obj.status.updatedReplicas < obj.spec.replicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: More replicas need to be updated"
return hs
end
if obj.spec.paused then
hs.status = "Suspended"
if obj.status.blueGreen.previewSelector ~= nil and obj.status.blueGreen.previewSelector == obj.status.currentPodHash then
hs.message = "The preview Service is serving traffic to the current pod spec"
elseif obj.status.blueGreen.activeSelector ~= nil and obj.status.blueGreen.activeSelector == obj.status.currentPodHash then
hs.message = "The active Service is serving traffic to the current pod spec"
if obj.spec.strategy.blueGreen ~= nil then
isPaused = checkPaused(obj)
if isPaused ~= nil then
return isPaused
end
replicasHS = checkReplicasStatus(obj)
if replicasHS ~= nil then
return replicasHS
end
if obj.status.blueGreen.activeSelector ~= nil and obj.status.blueGreen.activeSelector == obj.status.currentPodHash then
hs.status = "Healthy"
hs.message = "The active Service is serving traffic to the current pod spec"
return hs
end
return hs
end
if obj.status.replicas > obj.status.updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: old replicas are pending termination"
return hs
end
if obj.status.availableReplicas < obj.status.updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: updated replicas are still becoming available"
hs.message = "The current pod spec is not receiving traffic from the active service"
return hs
end
if obj.spec.strategy.canary ~= nil then
currentRSIsStable = obj.status.canary.stableRS == obj.status.currentPodHash
if obj.spec.strategy.canary.steps ~= nil then
stepCount = table.getn(obj.spec.strategy.canary.steps)
if obj.status.currentStepIndex ~= nil then
currentStepIndex = obj.status.currentStepIndex
isPaused = checkPaused(obj)
if isPaused ~= nil then
return isPaused
end
if paused then
hs.status = "Suspended"
hs.message = "Rollout is paused"
return hs
end
if currentRSIsStable and stepCount == currentStepIndex then
replicasHS = checkReplicasStatus(obj)
if replicasHS ~= nil then
return replicasHS
end
hs.status = "Healthy"
hs.message = "The rollout has completed all steps"
return hs
end
end
hs.status = "Progressing"
hs.message = "Waiting for rollout to finish steps"
return hs
end
if obj.status.blueGreen.activeSelector ~= nil and obj.status.blueGreen.activeSelector == obj.status.currentPodHash then
hs.status = "Healthy"
hs.message = "The active Service is serving traffic to the current pod spec"
return hs
-- The detecting the health of the Canary deployment when there are no steps
replicasHS = checkReplicasStatus(obj)
if replicasHS ~= nil then
return replicasHS
end
if currentRSIsStable then
hs.status = "Healthy"
hs.message = "The rollout has completed canary deployment"
return hs
end
hs.status = "Progressing"
hs.message = "Waiting for rollout to finish canary deployment"
end
hs.status = "Progressing"
hs.message = "The current pod spec is not receiving traffic from the active service"
return hs
end
end
hs.status = "Progressing"

View File

@@ -1,25 +1,55 @@
tests:
- healthStatus:
status: Healthy
message: The active Service is serving traffic to the current pod spec
inputPath: testdata/healthy_servingActiveService.yaml
- healthStatus:
status: Suspended
message: The preview Service is serving traffic to the current pod spec
inputPath: testdata/suspended_servingPreviewService.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: More replicas need to be updated"
inputPath: testdata/progressing_addingMoreReplicas.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: old replicas are pending termination"
inputPath: testdata/progressing_killingOldReplicas.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: updated replicas are still becoming available"
inputPath: testdata/progressing_waitingUntilAvailable.yaml
- healthStatus:
status: Degraded
message: Rollout has missing field '.Spec.Strategy.Type'
inputPath: testdata/degraded_invalidSpec.yaml
inputPath: testdata/degraded_invalidSpec.yaml
- healthStatus:
status: Degraded
message: ReplicaSet "guestbook-bluegreen-helm-guestbook-6b8cf6f7db" has timed out progressing.
inputPath: testdata/degraded_rolloutTimeout.yaml
#BlueGreen
- healthStatus:
status: Healthy
message: The active Service is serving traffic to the current pod spec
inputPath: testdata/bluegreen/healthy_servingActiveService.yaml
- healthStatus:
status: Suspended
message: Rollout is paused
inputPath: testdata/bluegreen/suspended_servingPreviewService.yaml
- healthStatus:
status: Suspended
message: Rollout is paused
inputPath: testdata/v0.2_suspended_servingPreviewService.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: More replicas need to be updated"
inputPath: testdata/bluegreen/progressing_addingMoreReplicas.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: old replicas are pending termination"
inputPath: testdata/bluegreen/progressing_killingOldReplicas.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: updated replicas are still becoming available"
inputPath: testdata/bluegreen/progressing_waitingUntilAvailable.yaml
#Canary
- healthStatus:
status: Progressing
message: Waiting for rollout to finish steps
inputPath: testdata/canary/progressing_setWeightStep.yaml
- healthStatus:
status: Suspended
message: Rollout is paused
inputPath: testdata/canary/suspended_pausedStep.yaml
- healthStatus:
status: Healthy
message: The rollout has completed all steps
inputPath: testdata/canary/healthy_executedAllSteps.yaml
- healthStatus:
status: Progressing
message: 'Waiting for roll out to finish: old replicas are pending termination'
inputPath: testdata/canary/progressing_noSteps.yaml
- healthStatus:
status: Healthy
message: The rollout has completed canary deployment
inputPath: testdata/canary/healthy_noSteps.yaml

View File

@@ -0,0 +1,73 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: >
{"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"guestbook-canary","ksonnet.io/component":"guestbook-ui"},"name":"guestbook-canary","namespace":"default"},"spec":{"minReadySeconds":10,"replicas":5,"selector":{"matchLabels":{"app":"guestbook-canary"}},"strategy":{"canary":{"maxSurge":1,"maxUnavailable":0,"steps":[{"setWeight":20},{"pause":{"duration":30}},{"setWeight":40},{"pause":{}}]}},"template":{"metadata":{"labels":{"app":"guestbook-canary"}},"spec":{"containers":[{"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook-canary","ports":[{"containerPort":80}]}]}}}}
rollout.argoproj.io/revision: '1'
clusterName: ''
creationTimestamp: '2019-05-01T21:55:30Z'
generation: 1
labels:
app.kubernetes.io/instance: guestbook-canary
ksonnet.io/component: guestbook-ui
name: guestbook-canary
namespace: default
resourceVersion: '955764'
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/guestbook-canary
uid: d6105ccd-6c5b-11e9-b8d7-025000000001
spec:
minReadySeconds: 10
replicas: 5
selector:
matchLabels:
app: guestbook-canary
strategy:
canary:
maxSurge: 1
maxUnavailable: 0
steps:
- setWeight: 20
- pause:
duration: 30
- setWeight: 40
- pause: {}
template:
metadata:
creationTimestamp: null
labels:
app: guestbook-canary
spec:
containers:
- image: 'gcr.io/heptio-images/ks-guestbook-demo:0.1'
name: guestbook-canary
ports:
- containerPort: 80
resources: {}
status:
HPAReplicas: 5
availableReplicas: 5
blueGreen: {}
canary:
stableRS: 84ccfddd66
conditions:
- lastTransitionTime: '2019-05-01T21:55:30Z'
lastUpdateTime: '2019-05-01T21:55:58Z'
message: ReplicaSet "guestbook-canary-84ccfddd66" has successfully progressed.
reason: NewReplicaSetAvailable
status: 'True'
type: Progressing
- lastTransitionTime: '2019-05-01T21:55:58Z'
lastUpdateTime: '2019-05-01T21:55:58Z'
message: Rollout has minimum availability
reason: AvailableReason
status: 'True'
type: Available
currentPodHash: 84ccfddd66
currentStepHash: 5f8fbdf7bb
currentStepIndex: 4
observedGeneration: c45557fd9
readyReplicas: 5
replicas: 5
selector: app=guestbook-canary
updatedReplicas: 5

View File

@@ -0,0 +1,66 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: >
{"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"guestbook-canary","ksonnet.io/component":"guestbook-ui"},"name":"guestbook-canary","namespace":"default"},"spec":{"minReadySeconds":10,"replicas":5,"selector":{"matchLabels":{"app":"guestbook-canary"}},"strategy":{"canary":{"maxSurge":1,"maxUnavailable":0,"steps":[{"setWeight":20},{"pause":{"duration":30}},{"setWeight":40},{"pause":{}}]}},"template":{"metadata":{"labels":{"app":"guestbook-canary"}},"spec":{"containers":[{"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook-canary","ports":[{"containerPort":80}]}]}}}}
rollout.argoproj.io/revision: '2'
clusterName: ''
creationTimestamp: '2019-05-01T21:55:30Z'
generation: 1
labels:
app.kubernetes.io/instance: guestbook-canary
ksonnet.io/component: guestbook-ui
name: guestbook-canary
namespace: default
resourceVersion: '956205'
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/guestbook-canary
uid: d6105ccd-6c5b-11e9-b8d7-025000000001
spec:
minReadySeconds: 10
replicas: 5
selector:
matchLabels:
app: guestbook-canary
strategy:
canary:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
creationTimestamp: null
labels:
app: guestbook-canary
spec:
containers:
- image: 'gcr.io/heptio-images/ks-guestbook-demo:0.2'
name: guestbook-canary
ports:
- containerPort: 80
resources: {}
status:
HPAReplicas: 5
availableReplicas: 5
blueGreen: {}
canary:
stableRS: 567dd56d89
conditions:
- lastTransitionTime: '2019-05-01T22:00:16Z'
lastUpdateTime: '2019-05-01T22:00:16Z'
message: Rollout has minimum availability
reason: AvailableReason
status: 'True'
type: Available
- lastTransitionTime: '2019-05-01T21:55:30Z'
lastUpdateTime: '2019-05-01T22:00:16Z'
message: ReplicaSet "guestbook-canary-567dd56d89" has successfully progressed.
reason: NewReplicaSetAvailable
status: 'True'
type: Progressing
currentPodHash: 567dd56d89
currentStepHash: 6c9545789c
observedGeneration: 6886f85bff
readyReplicas: 5
replicas: 5
selector: app=guestbook-canary
updatedReplicas: 5

View File

@@ -0,0 +1,66 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: >
{"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"guestbook-canary","ksonnet.io/component":"guestbook-ui"},"name":"guestbook-canary","namespace":"default"},"spec":{"minReadySeconds":10,"replicas":5,"selector":{"matchLabels":{"app":"guestbook-canary"}},"strategy":{"canary":{"maxSurge":1,"maxUnavailable":0,"steps":[{"setWeight":20},{"pause":{"duration":30}},{"setWeight":40},{"pause":{}}]}},"template":{"metadata":{"labels":{"app":"guestbook-canary"}},"spec":{"containers":[{"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook-canary","ports":[{"containerPort":80}]}]}}}}
rollout.argoproj.io/revision: '2'
clusterName: ''
creationTimestamp: '2019-05-01T21:55:30Z'
generation: 1
labels:
app.kubernetes.io/instance: guestbook-canary
ksonnet.io/component: guestbook-ui
name: guestbook-canary
namespace: default
resourceVersion: '956159'
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/guestbook-canary
uid: d6105ccd-6c5b-11e9-b8d7-025000000001
spec:
minReadySeconds: 10
replicas: 5
selector:
matchLabels:
app: guestbook-canary
strategy:
canary:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
creationTimestamp: null
labels:
app: guestbook-canary
spec:
containers:
- image: 'gcr.io/heptio-images/ks-guestbook-demo:0.2'
name: guestbook-canary
ports:
- containerPort: 80
resources: {}
status:
HPAReplicas: 6
availableReplicas: 2
blueGreen: {}
canary:
stableRS: 567dd56d89
conditions:
- lastTransitionTime: '2019-05-01T21:59:58Z'
lastUpdateTime: '2019-05-01T21:59:58Z'
message: Rollout does not have minimum availability
reason: AvailableReason
status: 'False'
type: Available
- lastTransitionTime: '2019-05-01T21:55:30Z'
lastUpdateTime: '2019-05-01T22:00:05Z'
message: ReplicaSet "guestbook-canary-567dd56d89" is progressing.
reason: ReplicaSetUpdated
status: 'True'
type: Progressing
currentPodHash: 567dd56d89
currentStepHash: 6c9545789c
observedGeneration: 6886f85bff
readyReplicas: 4
replicas: 6
selector: app=guestbook-canary
updatedReplicas: 5

View File

@@ -0,0 +1,69 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"name":"example-rollout-canary","namespace":"default"},"spec":{"minReadySeconds":30,"replicas":5,"revisionHistoryLimit":3,"selector":{"matchLabels":{"app":"guestbook"}},"strategy":{"canary":{"steps":[{"setWeight":20},{"pause":{"duration":20}},{"setWeight":40},{"pause":{}}]}},"template":{"metadata":{"labels":{"app":"guestbook"}},"spec":{"containers":[{"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook","ports":[{"containerPort":80}]}]}}}}
rollout.argoproj.io/revision: "2"
clusterName: ""
creationTimestamp: 2019-04-26T20:17:43Z
generation: 1
name: example-rollout-canary
namespace: default
resourceVersion: "696688"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/example-rollout-canary
uid: 58f6f1bb-6860-11e9-b8d7-025000000001
spec:
minReadySeconds: 30
replicas: 5
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook
strategy:
canary:
steps:
- setWeight: 20
- pause:
duration: 20
- setWeight: 40
- pause: {}
template:
metadata:
creationTimestamp: null
labels:
app: guestbook
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook
ports:
- containerPort: 80
resources: {}
status:
HPAReplicas: 5
availableReplicas: 5
blueGreen: {}
canary:
stableRS: df986d68
conditions:
- lastTransitionTime: 2019-04-26T20:18:05Z
lastUpdateTime: 2019-04-26T20:18:05Z
message: Rollout is not serving traffic from the active service.
reason: Available
status: "False"
type: Available
- lastTransitionTime: 2019-04-26T20:18:58Z
lastUpdateTime: 2019-04-26T20:19:29Z
message: ReplicaSet "example-rollout-canary-6b566f47b7" is progressing.
reason: ReplicaSetUpdated
status: "True"
type: Progressing
currentPodHash: 6b566f47b7
currentStepHash: 6567fc959c
currentStepIndex: 3
observedGeneration: 6df79499bc
readyReplicas: 5
replicas: 5
selector: app=guestbook
updatedReplicas: 2

View File

@@ -0,0 +1,71 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"name":"example-rollout-canary","namespace":"default"},"spec":{"minReadySeconds":30,"replicas":5,"revisionHistoryLimit":3,"selector":{"matchLabels":{"app":"guestbook"}},"strategy":{"canary":{"steps":[{"setWeight":20},{"pause":{"duration":20}},{"setWeight":40},{"pause":{}}]}},"template":{"metadata":{"labels":{"app":"guestbook"}},"spec":{"containers":[{"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook","ports":[{"containerPort":80}]}]}}}}
rollout.argoproj.io/revision: "2"
clusterName: ""
creationTimestamp: 2019-04-26T20:17:43Z
generation: 1
name: example-rollout-canary
namespace: default
resourceVersion: "696597"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/example-rollout-canary
uid: 58f6f1bb-6860-11e9-b8d7-025000000001
spec:
minReadySeconds: 30
paused: true
replicas: 5
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook
strategy:
canary:
steps:
- setWeight: 20
- pause:
duration: 20
- setWeight: 40
- pause: {}
template:
metadata:
creationTimestamp: null
labels:
app: guestbook
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook
ports:
- containerPort: 80
resources: {}
status:
HPAReplicas: 5
availableReplicas: 5
blueGreen: {}
canary:
stableRS: df986d68
conditions:
- lastTransitionTime: 2019-04-26T20:18:05Z
lastUpdateTime: 2019-04-26T20:18:05Z
message: Rollout is not serving traffic from the active service.
reason: Available
status: "False"
type: Available
- lastTransitionTime: 2019-04-26T20:18:38Z
lastUpdateTime: 2019-04-26T20:18:38Z
message: Rollout is paused
reason: RolloutPaused
status: Unknown
type: Progressing
currentPodHash: 6b566f47b7
currentStepHash: 6567fc959c
currentStepIndex: 1
observedGeneration: 5c788f4484
pauseStartTime: 2019-04-26T20:18:38Z
readyReplicas: 5
replicas: 5
selector: app=guestbook
updatedReplicas: 1

View File

@@ -0,0 +1,84 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
rollout.argoproj.io/revision: '4'
clusterName: ''
creationTimestamp: '2019-04-29T21:37:38Z'
generation: 1
labels:
app: helm-guestbook
app.kubernetes.io/instance: guestbook-bluegreen
chart: helm-guestbook-0.1.0
heritage: Tiller
release: guestbook-bluegreen
name: guestbook-bluegreen-helm-guestbook
namespace: default
selfLink: >-
/apis/argoproj.io/v1alpha1/namespaces/default/rollouts/guestbook-bluegreen-helm-guestbook
spec:
minReadySeconds: 0
progressDeadlineSeconds: 32
replicas: 3
revisionHistoryLimit: 3
selector:
matchLabels:
app: helm-guestbook
release: guestbook-bluegreen
strategy:
blueGreen:
activeService: guestbook-bluegreen-helm-guestbook
previewReplicaCount: 1
previewService: guestbook-bluegreen-helm-guestbook-preview
template:
metadata:
creationTimestamp: null
labels:
app: helm-guestbook
release: guestbook-bluegreen
spec:
containers:
- image: 'gcr.io/heptio-images/ks-guestbook-demo:0.3'
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /
port: http
name: helm-guestbook
ports:
- containerPort: 80
name: http
protocol: TCP
readinessProbe:
httpGet:
path: /
port: http
resources: {}
status:
HPAReplicas: 3
availableReplicas: 3
blueGreen:
activeSelector: 8464d8564d
canary: {}
conditions:
- lastTransitionTime: '2019-05-01T17:52:59Z'
lastUpdateTime: '2019-05-01T17:52:59Z'
message: Rollout has minimum availability
reason: AvailableReason
status: 'True'
type: Available
- lastTransitionTime: '2019-05-01T21:36:03Z'
lastUpdateTime: '2019-05-01T21:36:03Z'
message: >-
ReplicaSet "guestbook-bluegreen-helm-guestbook-6b8cf6f7db" has timed out
progressing.
reason: ProgressDeadlineExceeded
status: 'False'
type: Progressing
currentPodHash: 6b8cf6f7db
observedGeneration: 7bcdbf7bd9
readyReplicas: 3
replicas: 4
selector: >-
app=helm-guestbook,release=guestbook-bluegreen,rollouts-pod-template-hash=8464d8564d
updatedReplicas: 1

View File

@@ -0,0 +1,56 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
rollout.argoproj.io/revision: "7"
clusterName: ""
creationTimestamp: 2019-01-22T16:52:54Z
generation: 1
labels:
app.kubernetes.io/instance: guestbook-default
name: ks-guestbook-ui
namespace: default
resourceVersion: "164113"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/ks-guestbook-ui
uid: 29802403-1e66-11e9-a6a4-025000000001
spec:
minReadySeconds: 30
replicas: 3
selector:
matchLabels:
app: ks-guestbook-ui
strategy:
blueGreen:
activeService: ks-guestbook-ui-active
previewService: ks-guestbook-ui-preview
type: BlueGreenUpdate
template:
metadata:
creationTimestamp: null
labels:
app: ks-guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.1
name: ks-guestbook-ui
ports:
- containerPort: 83
resources: {}
status:
blueGreen:
activeSelector: 85f9884f5d
previewSelector: 697fb9575c
availableReplicas: 6
conditions:
- lastTransitionTime: 2019-01-25T07:44:26Z
lastUpdateTime: 2019-01-25T07:44:26Z
message: Rollout is serving traffic from the active service.
reason: Available
status: "True"
type: Available
currentPodHash: 697fb9575c
observedGeneration: 767f98959f
readyReplicas: 6
replicas: 6
updatedReplicas: 3
verifyingPreview: true

View File

@@ -19,11 +19,12 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/reposerver/repository"
@@ -163,8 +164,10 @@ func (s *Server) GetManifests(ctx context.Context, q *ApplicationManifestQuery)
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACName(*a)); err != nil {
return nil, err
}
repo := s.getRepo(ctx, a.Spec.Source.RepoURL)
repo, err := s.db.GetRepository(ctx, a.Spec.Source.RepoURL)
if err != nil {
return nil, err
}
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
if err != nil {
return nil, err
@@ -406,9 +409,9 @@ func (s *Server) Delete(ctx context.Context, q *ApplicationDeleteRequest) (*Appl
}
if patchFinalizer {
// Prior to v0.6, the cascaded deletion finalizer was set during app creation.
// For backward compatibility, we always calculate the patch to see if we need to
// set/unset the finalizer (in case we are dealing with an app created prior to v0.6)
// Although the cascaded deletion finalizer is not set when apps are created via API,
// they will often be set by the user as part of declarative config. As part of a delete
// request, we always calculate the patch to see if we need to set/unset the finalizer.
patch, err := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"finalizers": a.Finalizers,
@@ -433,33 +436,62 @@ func (s *Server) Delete(ctx context.Context, q *ApplicationDeleteRequest) (*Appl
}
func (s *Server) Watch(q *ApplicationQuery, ws ApplicationService_WatchServer) error {
w, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Watch(metav1.ListOptions{})
if err != nil {
return err
}
defer w.Stop()
logCtx := log.NewEntry(log.New())
if q.Name != nil {
logCtx = logCtx.WithField("application", *q.Name)
}
claims := ws.Context().Value("claims")
// sendIfPermitted is a helper to send the application to the client's streaming channel if the
// caller has RBAC privileges permissions to view it
sendIfPermitted := func(a appv1.Application, eventType watch.EventType) error {
if !s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACName(a)) {
// do not emit apps user does not have accessing
return nil
}
err := ws.Send(&appv1.ApplicationWatchEvent{
Type: eventType,
Application: a,
})
if err != nil {
logCtx.Warnf("Unable to send stream message: %v", err)
return err
}
return nil
}
var listOpts metav1.ListOptions
if q.Name != nil && *q.Name != "" {
listOpts.FieldSelector = fmt.Sprintf("metadata.name=%s", *q.Name)
}
listOpts.ResourceVersion = q.ResourceVersion
if listOpts.ResourceVersion == "" {
// If resourceVersion is not supplied, we need to get latest version of the apps by first
// making a list request, which we then supply to the watch request. We always need to
// supply a resourceVersion to watch requests since without it, the return values may return
// stale data. See: https://github.com/argoproj/argo-cd/issues/1605
appsList, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).List(listOpts)
if err != nil {
return err
}
for _, a := range appsList.Items {
err = sendIfPermitted(a, watch.Modified)
if err != nil {
return err
}
}
listOpts.ResourceVersion = appsList.ResourceVersion
}
w, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Watch(listOpts)
if err != nil {
return err
}
defer w.Stop()
done := make(chan bool)
go func() {
for next := range w.ResultChan() {
a := *next.Object.(*appv1.Application)
if q.Name == nil || *q.Name == "" || *q.Name == a.Name {
if !s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACName(a)) {
// do not emit apps user does not have accessing
continue
}
err = ws.Send(&appv1.ApplicationWatchEvent{
Type: next.Type,
Application: a,
})
if err != nil {
logCtx.Warnf("Unable to send stream message: %v", err)
}
}
_ = sendIfPermitted(a, next.Type)
}
logCtx.Info("k8s application watch event channel closed")
close(done)
@@ -501,7 +533,14 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
}
}
conditions, appSourceType, err := argo.GetSpecErrors(ctx, &app.Spec, proj, s.repoClientset, s.db)
conditions, err := argo.ValidatePermissions(ctx, &app.Spec, proj, s.db)
if err != nil {
return err
}
if len(conditions) > 0 {
return status.Errorf(codes.InvalidArgument, "application spec is invalid: %s", argo.FormatAppConditions(conditions))
}
conditions, appSourceType, err := argo.ValidateRepo(ctx, &app.Spec, s.repoClientset, s.db)
if err != nil {
return err
}
@@ -772,15 +811,6 @@ func (s *Server) getApplicationDestination(ctx context.Context, name string) (st
return server, namespace, nil
}
func (s *Server) getRepo(ctx context.Context, repoURL string) *appv1.Repository {
repo, err := s.db.GetRepository(ctx, repoURL)
if err != nil {
// If we couldn't retrieve from the repo service, assume public repositories
repo = &appv1.Repository{Repo: repoURL}
}
return repo
}
// Sync syncs an application to its target state
func (s *Server) Sync(ctx context.Context, syncReq *ApplicationSyncRequest) (*appv1.Application, error) {
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
@@ -795,7 +825,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *ApplicationSyncRequest) (*ap
return nil, status.Errorf(codes.FailedPrecondition, "application is deleting")
}
if a.Spec.SyncPolicy != nil && a.Spec.SyncPolicy.Automated != nil {
if syncReq.Revision != "" && syncReq.Revision != a.Spec.Source.TargetRevision {
if syncReq.Revision != "" && syncReq.Revision != util.FirstNonEmpty(a.Spec.Source.TargetRevision, "HEAD") {
return nil, status.Errorf(codes.FailedPrecondition, "Cannot sync to %s: auto-sync currently set to %s", syncReq.Revision, a.Spec.Source.TargetRevision)
}
}
@@ -887,8 +917,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
}
repo, err := s.db.GetRepository(ctx, app.Spec.Source.RepoURL)
if err != nil {
// If we couldn't retrieve from the repo service, assume public repositories
repo = &appv1.Repository{Repo: app.Spec.Source.RepoURL}
return "", "", err
}
gitClient, err := s.gitFactory.NewClient(repo.Repo, "", repo.Username, repo.Password, repo.SSHPrivateKey, repo.InsecureIgnoreHostKey)
if err != nil {

View File

@@ -42,6 +42,7 @@ type ApplicationQuery struct {
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Refresh *string `protobuf:"bytes,2,opt,name=refresh" json:"refresh,omitempty"`
Projects []string `protobuf:"bytes,3,rep,name=project" json:"project,omitempty"`
ResourceVersion string `protobuf:"bytes,4,opt,name=resourceVersion" json:"resourceVersion"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -51,7 +52,7 @@ func (m *ApplicationQuery) Reset() { *m = ApplicationQuery{} }
func (m *ApplicationQuery) String() string { return proto.CompactTextString(m) }
func (*ApplicationQuery) ProtoMessage() {}
func (*ApplicationQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{0}
return fileDescriptor_application_a312b6d468f6c22a, []int{0}
}
func (m *ApplicationQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -101,6 +102,13 @@ func (m *ApplicationQuery) GetProjects() []string {
return nil
}
func (m *ApplicationQuery) GetResourceVersion() string {
if m != nil {
return m.ResourceVersion
}
return ""
}
// ApplicationEventsQuery is a query for application resource events
type ApplicationResourceEventsQuery struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
@@ -116,7 +124,7 @@ func (m *ApplicationResourceEventsQuery) Reset() { *m = ApplicationResou
func (m *ApplicationResourceEventsQuery) String() string { return proto.CompactTextString(m) }
func (*ApplicationResourceEventsQuery) ProtoMessage() {}
func (*ApplicationResourceEventsQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{1}
return fileDescriptor_application_a312b6d468f6c22a, []int{1}
}
func (m *ApplicationResourceEventsQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -186,7 +194,7 @@ func (m *ApplicationManifestQuery) Reset() { *m = ApplicationManifestQue
func (m *ApplicationManifestQuery) String() string { return proto.CompactTextString(m) }
func (*ApplicationManifestQuery) ProtoMessage() {}
func (*ApplicationManifestQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{2}
return fileDescriptor_application_a312b6d468f6c22a, []int{2}
}
func (m *ApplicationManifestQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -239,7 +247,7 @@ func (m *ApplicationResponse) Reset() { *m = ApplicationResponse{} }
func (m *ApplicationResponse) String() string { return proto.CompactTextString(m) }
func (*ApplicationResponse) ProtoMessage() {}
func (*ApplicationResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{3}
return fileDescriptor_application_a312b6d468f6c22a, []int{3}
}
func (m *ApplicationResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -280,7 +288,7 @@ func (m *ApplicationCreateRequest) Reset() { *m = ApplicationCreateReque
func (m *ApplicationCreateRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationCreateRequest) ProtoMessage() {}
func (*ApplicationCreateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{4}
return fileDescriptor_application_a312b6d468f6c22a, []int{4}
}
func (m *ApplicationCreateRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -334,7 +342,7 @@ func (m *ApplicationUpdateRequest) Reset() { *m = ApplicationUpdateReque
func (m *ApplicationUpdateRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationUpdateRequest) ProtoMessage() {}
func (*ApplicationUpdateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{5}
return fileDescriptor_application_a312b6d468f6c22a, []int{5}
}
func (m *ApplicationUpdateRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -382,7 +390,7 @@ func (m *ApplicationDeleteRequest) Reset() { *m = ApplicationDeleteReque
func (m *ApplicationDeleteRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationDeleteRequest) ProtoMessage() {}
func (*ApplicationDeleteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{6}
return fileDescriptor_application_a312b6d468f6c22a, []int{6}
}
func (m *ApplicationDeleteRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -442,7 +450,7 @@ func (m *ApplicationSyncRequest) Reset() { *m = ApplicationSyncRequest{}
func (m *ApplicationSyncRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationSyncRequest) ProtoMessage() {}
func (*ApplicationSyncRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{7}
return fileDescriptor_application_a312b6d468f6c22a, []int{7}
}
func (m *ApplicationSyncRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -526,7 +534,7 @@ func (m *ApplicationUpdateSpecRequest) Reset() { *m = ApplicationUpdateS
func (m *ApplicationUpdateSpecRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationUpdateSpecRequest) ProtoMessage() {}
func (*ApplicationUpdateSpecRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{8}
return fileDescriptor_application_a312b6d468f6c22a, []int{8}
}
func (m *ApplicationUpdateSpecRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -582,7 +590,7 @@ func (m *ApplicationPatchRequest) Reset() { *m = ApplicationPatchRequest
func (m *ApplicationPatchRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationPatchRequest) ProtoMessage() {}
func (*ApplicationPatchRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{9}
return fileDescriptor_application_a312b6d468f6c22a, []int{9}
}
func (m *ApplicationPatchRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -639,7 +647,7 @@ func (m *ApplicationRollbackRequest) Reset() { *m = ApplicationRollbackR
func (m *ApplicationRollbackRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationRollbackRequest) ProtoMessage() {}
func (*ApplicationRollbackRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{10}
return fileDescriptor_application_a312b6d468f6c22a, []int{10}
}
func (m *ApplicationRollbackRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -712,7 +720,7 @@ func (m *ApplicationResourceRequest) Reset() { *m = ApplicationResourceR
func (m *ApplicationResourceRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationResourceRequest) ProtoMessage() {}
func (*ApplicationResourceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{11}
return fileDescriptor_application_a312b6d468f6c22a, []int{11}
}
func (m *ApplicationResourceRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -801,7 +809,7 @@ func (m *ApplicationResourcePatchRequest) Reset() { *m = ApplicationReso
func (m *ApplicationResourcePatchRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationResourcePatchRequest) ProtoMessage() {}
func (*ApplicationResourcePatchRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{12}
return fileDescriptor_application_a312b6d468f6c22a, []int{12}
}
func (m *ApplicationResourcePatchRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -903,7 +911,7 @@ func (m *ApplicationResourceDeleteRequest) Reset() { *m = ApplicationRes
func (m *ApplicationResourceDeleteRequest) String() string { return proto.CompactTextString(m) }
func (*ApplicationResourceDeleteRequest) ProtoMessage() {}
func (*ApplicationResourceDeleteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{13}
return fileDescriptor_application_a312b6d468f6c22a, []int{13}
}
func (m *ApplicationResourceDeleteRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -998,7 +1006,7 @@ func (m *ResourceActionRunRequest) Reset() { *m = ResourceActionRunReque
func (m *ResourceActionRunRequest) String() string { return proto.CompactTextString(m) }
func (*ResourceActionRunRequest) ProtoMessage() {}
func (*ResourceActionRunRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{14}
return fileDescriptor_application_a312b6d468f6c22a, []int{14}
}
func (m *ResourceActionRunRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1087,7 +1095,7 @@ func (m *ResourceActionsListResponse) Reset() { *m = ResourceActionsList
func (m *ResourceActionsListResponse) String() string { return proto.CompactTextString(m) }
func (*ResourceActionsListResponse) ProtoMessage() {}
func (*ResourceActionsListResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{15}
return fileDescriptor_application_a312b6d468f6c22a, []int{15}
}
func (m *ResourceActionsListResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1134,7 +1142,7 @@ func (m *ApplicationResourceResponse) Reset() { *m = ApplicationResource
func (m *ApplicationResourceResponse) String() string { return proto.CompactTextString(m) }
func (*ApplicationResourceResponse) ProtoMessage() {}
func (*ApplicationResourceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{16}
return fileDescriptor_application_a312b6d468f6c22a, []int{16}
}
func (m *ApplicationResourceResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1188,7 +1196,7 @@ func (m *ApplicationPodLogsQuery) Reset() { *m = ApplicationPodLogsQuery
func (m *ApplicationPodLogsQuery) String() string { return proto.CompactTextString(m) }
func (*ApplicationPodLogsQuery) ProtoMessage() {}
func (*ApplicationPodLogsQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{17}
return fileDescriptor_application_a312b6d468f6c22a, []int{17}
}
func (m *ApplicationPodLogsQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1285,7 +1293,7 @@ func (m *LogEntry) Reset() { *m = LogEntry{} }
func (m *LogEntry) String() string { return proto.CompactTextString(m) }
func (*LogEntry) ProtoMessage() {}
func (*LogEntry) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{18}
return fileDescriptor_application_a312b6d468f6c22a, []int{18}
}
func (m *LogEntry) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1339,7 +1347,7 @@ func (m *OperationTerminateRequest) Reset() { *m = OperationTerminateReq
func (m *OperationTerminateRequest) String() string { return proto.CompactTextString(m) }
func (*OperationTerminateRequest) ProtoMessage() {}
func (*OperationTerminateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{19}
return fileDescriptor_application_a312b6d468f6c22a, []int{19}
}
func (m *OperationTerminateRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1385,7 +1393,7 @@ func (m *OperationTerminateResponse) Reset() { *m = OperationTerminateRe
func (m *OperationTerminateResponse) String() string { return proto.CompactTextString(m) }
func (*OperationTerminateResponse) ProtoMessage() {}
func (*OperationTerminateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{20}
return fileDescriptor_application_a312b6d468f6c22a, []int{20}
}
func (m *OperationTerminateResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1425,7 +1433,7 @@ func (m *ResourcesQuery) Reset() { *m = ResourcesQuery{} }
func (m *ResourcesQuery) String() string { return proto.CompactTextString(m) }
func (*ResourcesQuery) ProtoMessage() {}
func (*ResourcesQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{21}
return fileDescriptor_application_a312b6d468f6c22a, []int{21}
}
func (m *ResourcesQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1472,7 +1480,7 @@ func (m *ManagedResourcesResponse) Reset() { *m = ManagedResourcesRespon
func (m *ManagedResourcesResponse) String() string { return proto.CompactTextString(m) }
func (*ManagedResourcesResponse) ProtoMessage() {}
func (*ManagedResourcesResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_application_c1cee2c3bb672fdc, []int{22}
return fileDescriptor_application_a312b6d468f6c22a, []int{22}
}
func (m *ManagedResourcesResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -2397,6 +2405,10 @@ func (m *ApplicationQuery) MarshalTo(dAtA []byte) (int, error) {
i += copy(dAtA[i:], s)
}
}
dAtA[i] = 0x22
i++
i = encodeVarintApplication(dAtA, i, uint64(len(m.ResourceVersion)))
i += copy(dAtA[i:], m.ResourceVersion)
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
@@ -3314,6 +3326,8 @@ func (m *ApplicationQuery) Size() (n int) {
n += 1 + l + sovApplication(uint64(l))
}
}
l = len(m.ResourceVersion)
n += 1 + l + sovApplication(uint64(l))
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -3828,6 +3842,35 @@ func (m *ApplicationQuery) Unmarshal(dAtA []byte) error {
}
m.Projects = append(m.Projects, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApplication
}
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 ErrInvalidLengthApplication
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ResourceVersion = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipApplication(dAtA[iNdEx:])
@@ -7221,121 +7264,121 @@ var (
)
func init() {
proto.RegisterFile("server/application/application.proto", fileDescriptor_application_c1cee2c3bb672fdc)
proto.RegisterFile("server/application/application.proto", fileDescriptor_application_a312b6d468f6c22a)
}
var fileDescriptor_application_c1cee2c3bb672fdc = []byte{
// 1779 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0xcd, 0x6f, 0xdc, 0xc6,
0x15, 0xef, 0xec, 0xae, 0x76, 0x57, 0x4f, 0xaa, 0x6b, 0x8f, 0x3f, 0x4a, 0x53, 0xb2, 0xb4, 0x18,
0xcb, 0xb2, 0x2c, 0x5b, 0xa4, 0xa5, 0x1a, 0xad, 0x21, 0x18, 0xb0, 0xad, 0xca, 0x95, 0x55, 0xc8,
0xaa, 0xba, 0x92, 0x51, 0xa0, 0x40, 0x51, 0xd0, 0xe4, 0x68, 0xc5, 0x6a, 0x97, 0x64, 0x49, 0xee,
0x16, 0x5b, 0xc3, 0x87, 0x1a, 0x45, 0xd1, 0x43, 0x90, 0x20, 0x48, 0x0e, 0x4e, 0x90, 0x2f, 0xf8,
0x9c, 0x5b, 0x90, 0x4b, 0x0e, 0xb9, 0x05, 0xf0, 0x31, 0x40, 0x72, 0x36, 0x02, 0xc5, 0x7f, 0x40,
0x4e, 0x39, 0x07, 0x33, 0x1c, 0x72, 0x87, 0xd2, 0x2e, 0x57, 0xb2, 0x37, 0x07, 0xdf, 0xb8, 0x6f,
0x86, 0xef, 0xfd, 0xde, 0xc7, 0xbc, 0x79, 0x3f, 0x2e, 0x4c, 0x05, 0xd4, 0x6f, 0x51, 0x5f, 0x37,
0x3c, 0xaf, 0x6e, 0x9b, 0x46, 0x68, 0xbb, 0x8e, 0xfc, 0xac, 0x79, 0xbe, 0x1b, 0xba, 0x78, 0x44,
0x12, 0xa9, 0xa7, 0x6a, 0x6e, 0xcd, 0xe5, 0x72, 0x9d, 0x3d, 0x45, 0x5b, 0xd4, 0xf1, 0x9a, 0xeb,
0xd6, 0xea, 0x54, 0x37, 0x3c, 0x5b, 0x37, 0x1c, 0xc7, 0x0d, 0xf9, 0xe6, 0x40, 0xac, 0x92, 0xdd,
0xeb, 0x81, 0x66, 0xbb, 0x7c, 0xd5, 0x74, 0x7d, 0xaa, 0xb7, 0xe6, 0xf5, 0x1a, 0x75, 0xa8, 0x6f,
0x84, 0xd4, 0x12, 0x7b, 0xae, 0x75, 0xf6, 0x34, 0x0c, 0x73, 0xc7, 0x76, 0xa8, 0xdf, 0xd6, 0xbd,
0xdd, 0x1a, 0x13, 0x04, 0x7a, 0x83, 0x86, 0x46, 0xb7, 0xb7, 0x56, 0x6b, 0x76, 0xb8, 0xd3, 0x7c,
0xa0, 0x99, 0x6e, 0x43, 0x37, 0x7c, 0x0e, 0xec, 0x1f, 0xfc, 0x61, 0xce, 0xb4, 0x3a, 0x6f, 0xcb,
0xee, 0xb5, 0xe6, 0x8d, 0xba, 0xb7, 0x63, 0x1c, 0x54, 0xb5, 0x94, 0xa5, 0xca, 0xa7, 0x9e, 0x2b,
0x62, 0xc5, 0x1f, 0xed, 0xd0, 0xf5, 0xdb, 0xd2, 0x63, 0xa4, 0x83, 0xec, 0xc0, 0xf1, 0xdb, 0x1d,
0x5b, 0x7f, 0x6e, 0x52, 0xbf, 0x8d, 0x31, 0x14, 0x1c, 0xa3, 0x41, 0x15, 0x54, 0x41, 0x33, 0xc3,
0x55, 0xfe, 0x8c, 0x15, 0x28, 0xf9, 0x74, 0xdb, 0xa7, 0xc1, 0x8e, 0x92, 0xe3, 0xe2, 0xf8, 0x27,
0x9e, 0x86, 0x12, 0x33, 0x4c, 0xcd, 0x50, 0xc9, 0x57, 0xf2, 0x33, 0xc3, 0x4b, 0xa3, 0x7b, 0xcf,
0x27, 0xcb, 0x1b, 0x91, 0x28, 0xa8, 0xc6, 0x8b, 0xe4, 0x0b, 0x04, 0x13, 0x92, 0xa9, 0x2a, 0x0d,
0xdc, 0xa6, 0x6f, 0xd2, 0x3b, 0x2d, 0xea, 0x84, 0xc1, 0x7e, 0xc3, 0xb9, 0xc4, 0xf0, 0x02, 0x9c,
0xf0, 0xc5, 0xd6, 0x75, 0xa3, 0x41, 0x03, 0xcf, 0x30, 0xa9, 0x92, 0x63, 0x1b, 0x96, 0x0a, 0xcf,
0x9e, 0x4f, 0xfe, 0xa2, 0x7a, 0x70, 0x19, 0xcf, 0xc0, 0xa8, 0x2c, 0x54, 0xf2, 0xd2, 0xf6, 0xd4,
0x0a, 0x9e, 0x86, 0x91, 0xf8, 0xf7, 0xfd, 0xd5, 0x65, 0xa5, 0x20, 0x6d, 0x94, 0x17, 0xc8, 0x06,
0x28, 0x12, 0xf6, 0x7b, 0x86, 0x63, 0x6f, 0xd3, 0x20, 0xec, 0x8d, 0xba, 0x02, 0x65, 0x9f, 0xb6,
0xec, 0xc0, 0x76, 0x9d, 0x28, 0x5e, 0x42, 0x69, 0x22, 0x25, 0xa7, 0xe1, 0x64, 0x3a, 0x1a, 0x9e,
0xeb, 0x04, 0x94, 0x3c, 0x45, 0x29, 0x4b, 0xbf, 0xf7, 0xa9, 0x11, 0xd2, 0x2a, 0xfd, 0x67, 0x93,
0x06, 0x21, 0x76, 0x40, 0x2e, 0x6c, 0x6e, 0x70, 0x64, 0xe1, 0x0f, 0x5a, 0xa7, 0x0c, 0xb4, 0xb8,
0x0c, 0xf8, 0xc3, 0xdf, 0x4d, 0x4b, 0xf3, 0x76, 0x6b, 0x1a, 0xab, 0x28, 0x4d, 0x3e, 0x24, 0x71,
0x45, 0x69, 0x92, 0xa5, 0xd8, 0x6b, 0x69, 0x1f, 0x3e, 0x03, 0xc5, 0xa6, 0x17, 0x50, 0x3f, 0xe4,
0x3e, 0x94, 0xab, 0xe2, 0x17, 0xf9, 0x6f, 0x1a, 0xe4, 0x7d, 0xcf, 0x92, 0x40, 0xee, 0xfc, 0x8c,
0x20, 0x53, 0xf0, 0xc8, 0xdd, 0x14, 0x8a, 0x65, 0x5a, 0xa7, 0x1d, 0x14, 0xdd, 0x92, 0xa2, 0x40,
0xc9, 0x34, 0x02, 0xd3, 0xb0, 0xa8, 0xf0, 0x27, 0xfe, 0x49, 0xbe, 0xcf, 0xc1, 0x19, 0x49, 0xd5,
0x66, 0xdb, 0x31, 0xb3, 0x14, 0xf5, 0xcd, 0x2e, 0x1e, 0x87, 0xa2, 0xe5, 0xb7, 0xab, 0x4d, 0x47,
0xc9, 0x33, 0x4b, 0x62, 0x5d, 0xc8, 0xb0, 0x0a, 0x43, 0x9e, 0xdf, 0x74, 0xa8, 0x52, 0x90, 0x16,
0x23, 0x11, 0x36, 0xa1, 0x1c, 0x84, 0xec, 0x94, 0xd7, 0xda, 0xca, 0x50, 0x05, 0xcd, 0x8c, 0x2c,
0xac, 0xbc, 0x42, 0xec, 0x98, 0x27, 0x9b, 0x42, 0x5d, 0x35, 0x51, 0x8c, 0x43, 0x18, 0x8e, 0xab,
0x3b, 0x50, 0x4a, 0x95, 0xfc, 0xcc, 0xc8, 0xc2, 0xc6, 0x2b, 0x5a, 0xf9, 0x93, 0xc7, 0x7a, 0x93,
0x74, 0xb0, 0x85, 0x5b, 0x1d, 0x43, 0xe4, 0x09, 0x82, 0xf1, 0x03, 0x65, 0xb3, 0xe9, 0xd1, 0xcc,
0x58, 0x5b, 0x50, 0x08, 0x3c, 0x6a, 0xf2, 0x23, 0x3f, 0xb2, 0xf0, 0xc7, 0xc1, 0xd4, 0x11, 0x33,
0x2a, 0xf0, 0x71, 0xed, 0x64, 0x15, 0x7e, 0x2d, 0x2d, 0x6f, 0x18, 0xa1, 0xb9, 0x93, 0x05, 0x8a,
0x25, 0x90, 0xed, 0x49, 0x35, 0xa2, 0x48, 0x44, 0xfe, 0x87, 0x40, 0x95, 0x4b, 0xd6, 0xad, 0xd7,
0x1f, 0x18, 0xe6, 0x6e, 0xb6, 0xba, 0x9c, 0x6d, 0x71, 0x5d, 0xf9, 0x25, 0x60, 0xba, 0xf6, 0x9e,
0x4f, 0xe6, 0x56, 0x97, 0xab, 0x39, 0xdb, 0x7a, 0xf9, 0x4a, 0x22, 0xdf, 0xee, 0x03, 0x22, 0xf2,
0x90, 0x05, 0x84, 0xc0, 0xb0, 0xd3, 0xb5, 0xc9, 0x76, 0xc4, 0x47, 0x68, 0xae, 0x13, 0x50, 0x6a,
0x51, 0x9f, 0x9f, 0x12, 0xb9, 0xb1, 0xc6, 0x42, 0x06, 0xbe, 0xe6, 0xbb, 0x4d, 0x4f, 0x19, 0x92,
0xa3, 0xc8, 0x45, 0x58, 0x81, 0xc2, 0xae, 0xed, 0x58, 0x4a, 0x51, 0x5a, 0xe2, 0x12, 0xf2, 0x5e,
0x0e, 0x26, 0xbb, 0xb8, 0xd5, 0x37, 0x67, 0xaf, 0x81, 0x6f, 0x9d, 0xba, 0x2a, 0x1d, 0xa8, 0x2b,
0x86, 0x9f, 0x3f, 0x6c, 0xb5, 0x3d, 0xaa, 0x94, 0x65, 0xfc, 0x89, 0x98, 0xfc, 0x88, 0xa0, 0xd2,
0x25, 0x36, 0xfd, 0x5b, 0xe3, 0x6b, 0x12, 0x9c, 0x6d, 0xd7, 0x37, 0xa9, 0x52, 0x4a, 0x6a, 0x1d,
0x55, 0x23, 0x11, 0xf9, 0x01, 0x81, 0x12, 0x7b, 0x7b, 0xdb, 0xe4, 0xbe, 0x37, 0x9d, 0xd7, 0xdd,
0xe1, 0x71, 0x28, 0x1a, 0xdc, 0x97, 0x54, 0x39, 0x08, 0x19, 0xf9, 0x3f, 0x82, 0xb1, 0xb4, 0xcb,
0xc1, 0x9a, 0x1d, 0x84, 0xf1, 0x24, 0x81, 0x6d, 0x28, 0x45, 0x3b, 0x03, 0x05, 0xf1, 0x0e, 0xbf,
0xfa, 0x0a, 0xbd, 0x33, 0x6d, 0x28, 0x76, 0x4f, 0xe8, 0x27, 0x37, 0x61, 0xac, 0x6b, 0xa3, 0x11,
0x48, 0x2a, 0x50, 0x6e, 0x88, 0x89, 0x29, 0xca, 0x41, 0x7c, 0x5d, 0xc6, 0x52, 0xf2, 0x55, 0x2e,
0xdd, 0x7f, 0x5d, 0x6b, 0xcd, 0xad, 0x65, 0x0c, 0x85, 0x87, 0xc9, 0x9e, 0x02, 0x25, 0xcf, 0xb5,
0x3a, 0x89, 0xab, 0xc6, 0x3f, 0xd9, 0xdb, 0xa6, 0xeb, 0x84, 0x06, 0x9b, 0xd8, 0x53, 0xf9, 0xea,
0x88, 0x59, 0xee, 0x03, 0xdb, 0x31, 0xe9, 0x26, 0x35, 0x5d, 0xc7, 0x0a, 0x78, 0xe2, 0xf2, 0x71,
0xee, 0xe5, 0x15, 0x7c, 0x17, 0x86, 0xf9, 0xef, 0x2d, 0xbb, 0x41, 0x95, 0x22, 0xbf, 0xb1, 0x67,
0xb5, 0x88, 0x1a, 0x68, 0x32, 0x35, 0xe8, 0x44, 0x98, 0x51, 0x03, 0xad, 0x35, 0xaf, 0xb1, 0x37,
0xaa, 0x9d, 0x97, 0x19, 0xae, 0xd0, 0xb0, 0xeb, 0x6b, 0xb6, 0xc3, 0x6f, 0xe5, 0x8e, 0xc1, 0x8e,
0x98, 0xd5, 0xc4, 0xb6, 0x5b, 0xaf, 0xbb, 0xff, 0xe2, 0x2d, 0x20, 0xb9, 0x0e, 0x22, 0x19, 0xf9,
0x37, 0x94, 0xd7, 0xdc, 0xda, 0x1d, 0x27, 0xf4, 0xdb, 0xac, 0x26, 0x99, 0x3b, 0xd4, 0x49, 0x07,
0x3d, 0x16, 0xe2, 0x75, 0x18, 0x0e, 0xed, 0x06, 0xdd, 0x0c, 0x8d, 0x86, 0x27, 0x6e, 0xd7, 0x23,
0xe0, 0x4e, 0x90, 0xc5, 0x2a, 0x88, 0x0e, 0x67, 0x93, 0x19, 0x60, 0x8b, 0xfa, 0x0d, 0xdb, 0x31,
0x32, 0x7b, 0x0e, 0x19, 0x07, 0xb5, 0xdb, 0x0b, 0x62, 0x10, 0xbe, 0x05, 0xc7, 0xe2, 0x42, 0x12,
0x85, 0xa0, 0xc1, 0xaf, 0xa4, 0xda, 0x5c, 0x4f, 0xd4, 0x89, 0x4e, 0xb0, 0x7f, 0x91, 0xb4, 0x41,
0xb9, 0x67, 0x38, 0x46, 0x8d, 0x5a, 0x89, 0xa2, 0xa4, 0x24, 0xff, 0x06, 0x43, 0x76, 0x48, 0x1b,
0xf1, 0xd1, 0x58, 0x19, 0xc0, 0xd1, 0x58, 0xb6, 0xb7, 0xb7, 0xab, 0x91, 0xd6, 0x85, 0x17, 0x67,
0x01, 0xcb, 0xe3, 0x06, 0xf5, 0x5b, 0xb6, 0x49, 0xf1, 0x5b, 0x08, 0x0a, 0xec, 0x8c, 0xe2, 0x73,
0x29, 0x55, 0xfb, 0x09, 0x98, 0x3a, 0xa0, 0x29, 0x87, 0x99, 0x22, 0xe3, 0x8f, 0xbf, 0x79, 0xf1,
0x4e, 0xee, 0x0c, 0x3e, 0xc5, 0xb9, 0x6c, 0x6b, 0x5e, 0xa6, 0x96, 0x01, 0x7e, 0x03, 0x01, 0x16,
0x5d, 0x43, 0x62, 0x63, 0xf8, 0x72, 0x2f, 0x7c, 0x5d, 0x58, 0x9b, 0x7a, 0x4e, 0xaa, 0x1a, 0x8d,
0x91, 0x65, 0x56, 0x23, 0x7c, 0x03, 0x07, 0x30, 0xcb, 0x01, 0x4c, 0x61, 0xd2, 0x0d, 0x80, 0xfe,
0x90, 0x95, 0xc2, 0x23, 0x9d, 0x46, 0x76, 0x3f, 0x46, 0x30, 0xf4, 0x17, 0x7e, 0xdb, 0xf5, 0x89,
0xd0, 0xc6, 0x60, 0x22, 0xc4, 0x6d, 0x71, 0xa8, 0xe4, 0x3c, 0x87, 0x79, 0x0e, 0x8f, 0xc5, 0x30,
0x83, 0xd0, 0xa7, 0x46, 0x23, 0x85, 0xf6, 0x2a, 0xc2, 0x4f, 0x11, 0x14, 0x23, 0x52, 0x86, 0x2f,
0xf4, 0x82, 0x98, 0x22, 0x6d, 0xea, 0x80, 0xa8, 0x0f, 0xb9, 0xc4, 0x01, 0x9e, 0x27, 0x5d, 0x13,
0xb9, 0x98, 0xe2, 0x6d, 0x6f, 0x23, 0xc8, 0xaf, 0xd0, 0xbe, 0x65, 0x36, 0x28, 0x64, 0x07, 0x42,
0xd7, 0x25, 0xc3, 0xf8, 0x31, 0x82, 0xd1, 0x15, 0x1a, 0xc6, 0xd4, 0x39, 0xe8, 0x1d, 0xbe, 0x14,
0xbb, 0x56, 0xc7, 0x35, 0xe9, 0x9b, 0x45, 0xbc, 0x94, 0x74, 0x89, 0x39, 0x6e, 0xfa, 0x22, 0xbe,
0x90, 0x55, 0x5c, 0x8d, 0xc4, 0xe6, 0x97, 0x08, 0x8a, 0x11, 0xed, 0xe8, 0x6d, 0x3e, 0xc5, 0x66,
0x07, 0x16, 0xa3, 0x3b, 0x1c, 0xe8, 0x4d, 0xf5, 0x6a, 0x77, 0xa0, 0xf2, 0xfb, 0xac, 0xcd, 0x5a,
0x46, 0x68, 0x68, 0x1c, 0x7d, 0x3a, 0xb3, 0x9f, 0x21, 0x80, 0x0e, 0x6f, 0xc2, 0x97, 0xb2, 0x9d,
0x90, 0xb8, 0x95, 0x3a, 0x40, 0xe6, 0x44, 0x34, 0xee, 0xcc, 0x8c, 0x5a, 0xc9, 0x8a, 0x3a, 0xe3,
0x55, 0x8b, 0x9c, 0x5d, 0xe1, 0x0f, 0x11, 0x0c, 0xf1, 0xf9, 0x1c, 0x4f, 0xf5, 0x02, 0x2c, 0x8f,
0xef, 0x03, 0x0b, 0xfa, 0x34, 0xc7, 0x59, 0x59, 0xc8, 0x2a, 0xcc, 0x45, 0x34, 0x8b, 0x5b, 0x50,
0x8c, 0x46, 0xe4, 0xde, 0x55, 0x91, 0x1a, 0xa1, 0xd5, 0x4a, 0x46, 0x7f, 0x8c, 0x0a, 0x53, 0x9c,
0x89, 0xd9, 0xcc, 0x33, 0xf1, 0x09, 0x82, 0x02, 0xe3, 0xce, 0xf8, 0x7c, 0x2f, 0x7d, 0xd2, 0x97,
0x88, 0x81, 0x45, 0xe5, 0x32, 0x87, 0x76, 0x81, 0x64, 0x67, 0xaf, 0xed, 0x98, 0x2c, 0x34, 0x4f,
0x10, 0x1c, 0xdf, 0x7f, 0x8b, 0xe2, 0xb1, 0x94, 0x91, 0xf4, 0x35, 0xad, 0xa6, 0x43, 0xd8, 0xeb,
0x06, 0x26, 0xb7, 0x38, 0x8a, 0x45, 0x7c, 0xbd, 0xef, 0x81, 0x58, 0x8f, 0x0f, 0x31, 0x53, 0x34,
0x97, 0x7c, 0x4e, 0xc0, 0x9f, 0x23, 0x18, 0x8d, 0xf5, 0x6e, 0xf9, 0x94, 0x66, 0xc3, 0x1a, 0x50,
0xfd, 0x33, 0x43, 0xe4, 0x06, 0xc7, 0xfe, 0x5b, 0x7c, 0xed, 0x90, 0xd8, 0x63, 0xcc, 0x73, 0x21,
0x83, 0xf9, 0x29, 0x82, 0x72, 0xfc, 0x55, 0x00, 0x5f, 0xec, 0x59, 0x49, 0xe9, 0xef, 0x06, 0x03,
0xcb, 0xbe, 0xce, 0xb1, 0x5f, 0x22, 0x53, 0x59, 0xd9, 0xf7, 0x85, 0x71, 0x56, 0x01, 0xef, 0x22,
0xc0, 0xc9, 0x78, 0x96, 0x0c, 0x6c, 0x78, 0x3a, 0x65, 0xaa, 0xe7, 0xe4, 0xa7, 0x5e, 0xec, 0xbb,
0x2f, 0xdd, 0xca, 0x67, 0x33, 0x5b, 0xb9, 0x9b, 0xd8, 0x7f, 0x13, 0xc1, 0xc8, 0x0a, 0x4d, 0x06,
0x97, 0x8c, 0x40, 0xa6, 0xbf, 0x7b, 0xa8, 0x33, 0xfd, 0x37, 0x0a, 0x44, 0x57, 0x38, 0xa2, 0x69,
0x9c, 0x1d, 0xaa, 0x18, 0xc0, 0x07, 0x08, 0x7e, 0x29, 0xba, 0x98, 0x90, 0x5c, 0xe9, 0x67, 0x29,
0xd5, 0xf4, 0x0e, 0x8f, 0xeb, 0x37, 0x1c, 0xd7, 0x1c, 0x39, 0x14, 0xae, 0x45, 0xf1, 0xf9, 0xe0,
0x23, 0x04, 0x27, 0xe5, 0x49, 0x4f, 0x50, 0xc6, 0x97, 0x8d, 0x5b, 0x06, 0xf3, 0x24, 0xd7, 0x38,
0x3e, 0x0d, 0x5f, 0x39, 0x0c, 0x3e, 0x5d, 0x90, 0x48, 0xfc, 0x3e, 0x82, 0x13, 0x9c, 0xb4, 0xcb,
0x8a, 0xf7, 0x35, 0xe4, 0x5e, 0x14, 0xff, 0x10, 0x0d, 0x59, 0x9c, 0x59, 0x72, 0x24, 0x50, 0x8b,
0x82, 0x6c, 0xb3, 0xc9, 0xfd, 0x58, 0x7c, 0x05, 0x88, 0xec, 0xce, 0xf5, 0x0b, 0xdc, 0x51, 0xaf,
0x0c, 0x51, 0x6e, 0xb3, 0x87, 0x2b, 0xb7, 0xff, 0x20, 0x28, 0x09, 0x9e, 0x9c, 0x71, 0xab, 0x4a,
0x44, 0x5a, 0x3d, 0x9d, 0xda, 0x15, 0xf3, 0x44, 0xf2, 0x3b, 0x6e, 0x76, 0x1e, 0xeb, 0x59, 0x66,
0x3d, 0xd7, 0x0a, 0xf4, 0x87, 0x82, 0x40, 0x3f, 0xd2, 0xeb, 0x6e, 0x2d, 0xb8, 0x8a, 0x96, 0x6e,
0x3c, 0xdb, 0x9b, 0x40, 0x5f, 0xef, 0x4d, 0xa0, 0xef, 0xf6, 0x26, 0xd0, 0x5f, 0xb5, 0xac, 0xbf,
0xa3, 0x0e, 0xfe, 0x6d, 0xf7, 0x53, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5c, 0x47, 0xa4, 0xb4, 0xcb,
0x1b, 0x00, 0x00,
var fileDescriptor_application_a312b6d468f6c22a = []byte{
// 1792 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0xcd, 0x6f, 0xdc, 0x5a,
0x15, 0xe7, 0xce, 0x4c, 0x66, 0x26, 0x27, 0xe1, 0x7d, 0xdc, 0xf7, 0x5e, 0xf1, 0x73, 0xd2, 0x64,
0x74, 0x9b, 0xa6, 0x69, 0x5e, 0x63, 0x37, 0xa1, 0x82, 0xa7, 0xe8, 0x49, 0xaf, 0x0d, 0x29, 0x69,
0x50, 0x1a, 0xc2, 0x24, 0x05, 0x09, 0x09, 0x21, 0xd7, 0xbe, 0x99, 0x98, 0xcc, 0xd8, 0xc6, 0xf6,
0x0c, 0x1a, 0xaa, 0x2e, 0xa8, 0x10, 0x62, 0x81, 0x40, 0x08, 0x16, 0x05, 0xf1, 0xa5, 0xae, 0xd9,
0x21, 0x36, 0x2c, 0xd8, 0x21, 0x75, 0x89, 0x04, 0xeb, 0x0a, 0x85, 0xfe, 0x01, 0xac, 0x58, 0xa3,
0x7b, 0x7d, 0xaf, 0x7d, 0x9d, 0xcc, 0x78, 0x92, 0x76, 0x58, 0x74, 0xe7, 0x39, 0xf7, 0xfa, 0x9c,
0xdf, 0xf9, 0xb8, 0xe7, 0x9e, 0x9f, 0x07, 0x16, 0x22, 0x1a, 0xf6, 0x68, 0x68, 0x5a, 0x41, 0xd0,
0x76, 0x6d, 0x2b, 0x76, 0x7d, 0x4f, 0x7d, 0x36, 0x82, 0xd0, 0x8f, 0x7d, 0x3c, 0xa5, 0x88, 0xf4,
0xf7, 0x5b, 0x7e, 0xcb, 0xe7, 0x72, 0x93, 0x3d, 0x25, 0x5b, 0xf4, 0xd9, 0x96, 0xef, 0xb7, 0xda,
0xd4, 0xb4, 0x02, 0xd7, 0xb4, 0x3c, 0xcf, 0x8f, 0xf9, 0xe6, 0x48, 0xac, 0x92, 0xe3, 0x8f, 0x23,
0xc3, 0xf5, 0xf9, 0xaa, 0xed, 0x87, 0xd4, 0xec, 0xad, 0x9a, 0x2d, 0xea, 0xd1, 0xd0, 0x8a, 0xa9,
0x23, 0xf6, 0xdc, 0xca, 0xf6, 0x74, 0x2c, 0xfb, 0xc8, 0xf5, 0x68, 0xd8, 0x37, 0x83, 0xe3, 0x16,
0x13, 0x44, 0x66, 0x87, 0xc6, 0xd6, 0xa0, 0xb7, 0xb6, 0x5b, 0x6e, 0x7c, 0xd4, 0x7d, 0x68, 0xd8,
0x7e, 0xc7, 0xb4, 0x42, 0x0e, 0xec, 0x3b, 0xfc, 0x61, 0xc5, 0x76, 0xb2, 0xb7, 0x55, 0xf7, 0x7a,
0xab, 0x56, 0x3b, 0x38, 0xb2, 0xce, 0xaa, 0xda, 0x28, 0x52, 0x15, 0xd2, 0xc0, 0x17, 0xb1, 0xe2,
0x8f, 0x6e, 0xec, 0x87, 0x7d, 0xe5, 0x31, 0xd1, 0x41, 0x9e, 0x22, 0x78, 0xe7, 0x4e, 0x66, 0xec,
0x6b, 0x5d, 0x1a, 0xf6, 0x31, 0x86, 0x8a, 0x67, 0x75, 0xa8, 0x86, 0x1a, 0x68, 0x69, 0xb2, 0xc9,
0x9f, 0xb1, 0x06, 0xb5, 0x90, 0x1e, 0x86, 0x34, 0x3a, 0xd2, 0x4a, 0x5c, 0x2c, 0x7f, 0xe2, 0x45,
0xa8, 0x31, 0xcb, 0xd4, 0x8e, 0xb5, 0x72, 0xa3, 0xbc, 0x34, 0xb9, 0x31, 0x7d, 0xf2, 0x62, 0xbe,
0xbe, 0x97, 0x88, 0xa2, 0xa6, 0x5c, 0xc4, 0x06, 0xbc, 0x1d, 0xd2, 0xc8, 0xef, 0x86, 0x36, 0xfd,
0x3a, 0x0d, 0x23, 0xd7, 0xf7, 0xb4, 0x0a, 0xd3, 0xb4, 0x51, 0x79, 0xfe, 0x62, 0xfe, 0x33, 0xcd,
0xd3, 0x8b, 0xe4, 0x2f, 0x08, 0xe6, 0x14, 0x68, 0x4d, 0xb1, 0x7c, 0xb7, 0x47, 0xbd, 0x38, 0x3a,
0x0d, 0xb4, 0x94, 0x02, 0x5d, 0x83, 0x77, 0xa5, 0xa6, 0x5d, 0xab, 0x43, 0xa3, 0xc0, 0xb2, 0xa9,
0x56, 0x62, 0x1b, 0x84, 0xa1, 0xb3, 0xcb, 0x78, 0x09, 0xa6, 0x55, 0xa1, 0x56, 0x56, 0xb6, 0xe7,
0x56, 0xf0, 0x22, 0x4c, 0xc9, 0xdf, 0x0f, 0xb6, 0x37, 0xb5, 0x8a, 0xb2, 0x51, 0x5d, 0x20, 0x7b,
0xa0, 0x29, 0xd8, 0xef, 0x5b, 0x9e, 0x7b, 0x48, 0xa3, 0x78, 0x38, 0xea, 0x06, 0xd4, 0x43, 0xda,
0x73, 0x79, 0x54, 0x4a, 0x4a, 0x54, 0x52, 0x29, 0xf9, 0x00, 0xde, 0xcb, 0x47, 0x23, 0xf0, 0xbd,
0x88, 0x92, 0x67, 0x28, 0x67, 0xe9, 0x4b, 0x21, 0xb5, 0x62, 0xda, 0xa4, 0xdf, 0xed, 0xd2, 0x28,
0xc6, 0x1e, 0xa8, 0x27, 0x81, 0x1b, 0x9c, 0x5a, 0xfb, 0xb2, 0x91, 0xd5, 0x8d, 0x21, 0xeb, 0x86,
0x3f, 0x7c, 0xdb, 0x76, 0x8c, 0xe0, 0xb8, 0x65, 0xb0, 0x12, 0x34, 0xd4, 0x53, 0x25, 0x4b, 0xd0,
0x50, 0x2c, 0x49, 0xaf, 0x95, 0x7d, 0xf8, 0x12, 0x54, 0xbb, 0x41, 0x44, 0xc3, 0x98, 0xfb, 0x50,
0x6f, 0x8a, 0x5f, 0xe4, 0x87, 0x79, 0x90, 0x0f, 0x02, 0x47, 0x01, 0x79, 0xf4, 0x7f, 0x04, 0x99,
0x83, 0x47, 0xee, 0xe5, 0x50, 0x6c, 0xd2, 0x36, 0xcd, 0x50, 0x0c, 0x4a, 0x8a, 0x06, 0x35, 0xdb,
0x8a, 0x6c, 0xcb, 0xa1, 0xc2, 0x1f, 0xf9, 0x93, 0xfc, 0xbb, 0x04, 0x97, 0x14, 0x55, 0xfb, 0x7d,
0xcf, 0x2e, 0x52, 0x34, 0x32, 0xbb, 0x78, 0x16, 0xaa, 0x4e, 0xd8, 0x6f, 0x76, 0x3d, 0xad, 0xcc,
0x2c, 0x89, 0x75, 0x21, 0xc3, 0x3a, 0x4c, 0x04, 0x61, 0xd7, 0xa3, 0xfc, 0xc0, 0xc8, 0xc5, 0x44,
0x84, 0x6d, 0xa8, 0x47, 0x31, 0x6b, 0x0b, 0xad, 0xbe, 0x36, 0xd1, 0x40, 0x4b, 0x53, 0x6b, 0x5b,
0xaf, 0x11, 0x3b, 0xe6, 0xc9, 0xbe, 0x50, 0xd7, 0x4c, 0x15, 0xe3, 0x18, 0x26, 0x65, 0x75, 0x47,
0x5a, 0xad, 0x51, 0x5e, 0x9a, 0x5a, 0xdb, 0x7b, 0x4d, 0x2b, 0x5f, 0x0d, 0x58, 0x33, 0x53, 0x0e,
0xb6, 0x70, 0x2b, 0x33, 0xc4, 0x9a, 0xd3, 0xec, 0x99, 0xb2, 0xd9, 0x0f, 0x68, 0x61, 0xac, 0x1d,
0xa8, 0x44, 0x01, 0xb5, 0xf9, 0x91, 0x9f, 0x5a, 0xfb, 0xca, 0x78, 0xea, 0x88, 0x19, 0x15, 0xf8,
0xb8, 0x76, 0xb2, 0x0d, 0x9f, 0x53, 0x96, 0xf7, 0xac, 0xd8, 0x3e, 0x2a, 0x02, 0xc5, 0x12, 0xc8,
0xf6, 0xe4, 0x1a, 0x51, 0x22, 0x22, 0x3f, 0x42, 0xa0, 0xab, 0x25, 0xeb, 0xb7, 0xdb, 0x0f, 0x2d,
0xfb, 0xb8, 0x58, 0x5d, 0xc9, 0x75, 0xb8, 0xae, 0xf2, 0x06, 0x30, 0x5d, 0x27, 0x2f, 0xe6, 0x4b,
0xdb, 0x9b, 0xcd, 0x92, 0xeb, 0xbc, 0x7a, 0x25, 0x91, 0x7f, 0x9e, 0x02, 0x22, 0xf2, 0x50, 0x04,
0x84, 0xc0, 0xa4, 0x37, 0xb0, 0xc9, 0x66, 0xe2, 0x0b, 0x34, 0xd7, 0x39, 0xa8, 0xf5, 0xd2, 0x9b,
0x21, 0xdb, 0x24, 0x85, 0x0c, 0x7c, 0x2b, 0xf4, 0xbb, 0x81, 0x36, 0xa1, 0x46, 0x91, 0x8b, 0xb0,
0x06, 0x95, 0x63, 0xd7, 0x73, 0xb4, 0xaa, 0xb2, 0xc4, 0x25, 0xe4, 0x57, 0x25, 0x98, 0x1f, 0xe0,
0xd6, 0xc8, 0x9c, 0xbd, 0x01, 0xbe, 0x65, 0x75, 0x55, 0x3b, 0x53, 0x57, 0x0c, 0x3f, 0x7f, 0x38,
0xe8, 0x07, 0x54, 0xab, 0xab, 0xf8, 0x53, 0x31, 0xf9, 0x2f, 0x82, 0xc6, 0x80, 0xd8, 0x8c, 0x6e,
0x8d, 0x6f, 0x48, 0x70, 0x0e, 0xfd, 0xd0, 0xa6, 0x5a, 0x2d, 0xad, 0x75, 0xd4, 0x4c, 0x44, 0xe4,
0x3f, 0x08, 0x34, 0xe9, 0xed, 0x1d, 0x9b, 0xfb, 0xde, 0xf5, 0xde, 0x74, 0x87, 0x67, 0xa1, 0x6a,
0x71, 0x5f, 0x72, 0xe5, 0x20, 0x64, 0xe4, 0xc7, 0x08, 0x66, 0xf2, 0x2e, 0x47, 0x3b, 0x6e, 0x14,
0xcb, 0x49, 0x02, 0xbb, 0x50, 0x4b, 0x76, 0x46, 0x1a, 0xe2, 0x1d, 0x7e, 0xfb, 0x35, 0x7a, 0x67,
0xde, 0x90, 0x74, 0x4f, 0xe8, 0x27, 0x9f, 0xc2, 0xcc, 0xc0, 0x46, 0x23, 0x90, 0x34, 0xa0, 0xde,
0x11, 0x13, 0x53, 0x92, 0x03, 0x79, 0x5d, 0x4a, 0x29, 0xf9, 0x5b, 0x29, 0xdf, 0x7f, 0x7d, 0x67,
0xc7, 0x6f, 0x15, 0x0c, 0x85, 0xe7, 0xc9, 0x9e, 0x06, 0xb5, 0xc0, 0x77, 0xb2, 0xc4, 0x35, 0xe5,
0x4f, 0xf6, 0xb6, 0xed, 0x7b, 0xb1, 0xc5, 0x46, 0xfc, 0x5c, 0xbe, 0x32, 0x31, 0xcb, 0x7d, 0xe4,
0x7a, 0x36, 0xdd, 0xa7, 0xb6, 0xef, 0x39, 0x11, 0x4f, 0x5c, 0x59, 0xe6, 0x5e, 0x5d, 0xc1, 0xf7,
0x60, 0x92, 0xff, 0x3e, 0x70, 0x3b, 0x54, 0xab, 0xf2, 0x1b, 0x7b, 0xd9, 0x48, 0xb8, 0x84, 0xa1,
0x72, 0x89, 0x2c, 0xc2, 0x8c, 0x4b, 0x18, 0xbd, 0x55, 0x83, 0xbd, 0xd1, 0xcc, 0x5e, 0x66, 0xb8,
0x62, 0xcb, 0x6d, 0xef, 0xb8, 0x1e, 0xbf, 0x95, 0x33, 0x83, 0x99, 0x98, 0xd5, 0xc4, 0xa1, 0xdf,
0x6e, 0xfb, 0xdf, 0xe3, 0x2d, 0x20, 0xbd, 0x0e, 0x12, 0x19, 0xf9, 0x3e, 0xd4, 0x77, 0xfc, 0xd6,
0x5d, 0x2f, 0x0e, 0xfb, 0xac, 0x26, 0x99, 0x3b, 0xd4, 0xcb, 0x07, 0x5d, 0x0a, 0xf1, 0x2e, 0x4c,
0xc6, 0x6e, 0x87, 0xee, 0xc7, 0x56, 0x27, 0x10, 0xb7, 0xeb, 0x05, 0x70, 0xa7, 0xc8, 0xa4, 0x0a,
0x62, 0xc2, 0x87, 0xe9, 0x0c, 0x70, 0x40, 0xc3, 0x8e, 0xeb, 0x59, 0x85, 0x3d, 0x87, 0xcc, 0x82,
0x3e, 0xe8, 0x05, 0x31, 0x08, 0xdf, 0x86, 0xb7, 0x64, 0x21, 0x89, 0x42, 0x30, 0xe0, 0x6d, 0xa5,
0x36, 0x77, 0x53, 0x75, 0xa2, 0x13, 0x9c, 0x5e, 0x24, 0x7d, 0xd0, 0xee, 0x5b, 0x9e, 0xd5, 0xa2,
0x4e, 0xaa, 0x28, 0x2d, 0xc9, 0x6f, 0xc1, 0x84, 0x1b, 0xd3, 0x8e, 0x3c, 0x1a, 0x5b, 0x63, 0x38,
0x1a, 0x9b, 0xee, 0xe1, 0x61, 0x33, 0xd1, 0xba, 0xf6, 0xf2, 0x43, 0xc0, 0xea, 0xb8, 0x41, 0xc3,
0x9e, 0x6b, 0x53, 0xfc, 0x33, 0x04, 0x15, 0x76, 0x46, 0xf1, 0xe5, 0x9c, 0xaa, 0xd3, 0x84, 0x4d,
0x1f, 0xd3, 0x94, 0xc3, 0x4c, 0x91, 0xd9, 0x27, 0xff, 0x78, 0xf9, 0x8b, 0xd2, 0x25, 0xfc, 0x3e,
0x27, 0xbf, 0xbd, 0x55, 0x95, 0x8b, 0x46, 0xf8, 0x27, 0x08, 0xb0, 0xe8, 0x1a, 0x0a, 0x1b, 0xc3,
0x1f, 0x0d, 0xc3, 0x37, 0x80, 0xb5, 0xe9, 0x97, 0x95, 0xaa, 0x31, 0x18, 0xbb, 0x66, 0x35, 0xc2,
0x37, 0x70, 0x00, 0xcb, 0x1c, 0xc0, 0x02, 0x26, 0x83, 0x00, 0x98, 0x8f, 0x58, 0x29, 0x3c, 0x36,
0x69, 0x62, 0xf7, 0xf7, 0x08, 0x26, 0xbe, 0xc1, 0x6f, 0xbb, 0x11, 0x11, 0xda, 0x1b, 0x4f, 0x84,
0xb8, 0x2d, 0x0e, 0x95, 0x5c, 0xe1, 0x30, 0x2f, 0xe3, 0x19, 0x09, 0x33, 0x8a, 0x43, 0x6a, 0x75,
0x72, 0x68, 0x6f, 0x22, 0xfc, 0x0c, 0x41, 0x35, 0x21, 0x65, 0xf8, 0xea, 0x30, 0x88, 0x39, 0xd2,
0xa6, 0x8f, 0x89, 0xfa, 0x90, 0xeb, 0x1c, 0xe0, 0x15, 0x32, 0x30, 0x91, 0xeb, 0x39, 0xde, 0xf6,
0x73, 0x04, 0xe5, 0x2d, 0x3a, 0xb2, 0xcc, 0xc6, 0x85, 0xec, 0x4c, 0xe8, 0x06, 0x64, 0x18, 0x3f,
0x41, 0x30, 0xbd, 0x45, 0x63, 0x49, 0x9d, 0xa3, 0xe1, 0xe1, 0xcb, 0xb1, 0x6b, 0x7d, 0xd6, 0x50,
0x3e, 0x72, 0xc8, 0xa5, 0xb4, 0x4b, 0xac, 0x70, 0xd3, 0xd7, 0xf0, 0xd5, 0xa2, 0xe2, 0xea, 0xa4,
0x36, 0xff, 0x8a, 0xa0, 0x9a, 0xd0, 0x8e, 0xe1, 0xe6, 0x73, 0x6c, 0x76, 0x6c, 0x31, 0xba, 0xcb,
0x81, 0x7e, 0xaa, 0xdf, 0x1c, 0x0c, 0x54, 0x7d, 0x9f, 0xb5, 0x59, 0xc7, 0x8a, 0x2d, 0x83, 0xa3,
0xcf, 0x67, 0xf6, 0x4f, 0x08, 0x20, 0xe3, 0x4d, 0xf8, 0x7a, 0xb1, 0x13, 0x0a, 0xb7, 0xd2, 0xc7,
0xc8, 0x9c, 0x88, 0xc1, 0x9d, 0x59, 0xd2, 0x1b, 0x45, 0x51, 0x67, 0xbc, 0x6a, 0x9d, 0xb3, 0x2b,
0xfc, 0x5b, 0x04, 0x13, 0x7c, 0x3e, 0xc7, 0x0b, 0xc3, 0x00, 0xab, 0xe3, 0xfb, 0xd8, 0x82, 0xbe,
0xc8, 0x71, 0x36, 0xd6, 0x8a, 0x0a, 0x73, 0x1d, 0x2d, 0xe3, 0x1e, 0x54, 0x93, 0x11, 0x79, 0x78,
0x55, 0xe4, 0x46, 0x68, 0xbd, 0x51, 0xd0, 0x1f, 0x93, 0xc2, 0x14, 0x67, 0x62, 0xb9, 0xf0, 0x4c,
0xfc, 0x01, 0x41, 0x85, 0x71, 0x67, 0x7c, 0x65, 0x98, 0x3e, 0xe5, 0x4b, 0xc4, 0xd8, 0xa2, 0xf2,
0x11, 0x87, 0x76, 0x95, 0x14, 0x67, 0xaf, 0xef, 0xd9, 0x2c, 0x34, 0x4f, 0x11, 0xbc, 0x73, 0xfa,
0x16, 0xc5, 0x33, 0x39, 0x23, 0xf9, 0x6b, 0x5a, 0xcf, 0x87, 0x70, 0xd8, 0x0d, 0x4c, 0x6e, 0x73,
0x14, 0xeb, 0xf8, 0xe3, 0x91, 0x07, 0x62, 0x57, 0x1e, 0x62, 0xa6, 0x68, 0x25, 0xfd, 0x9c, 0x80,
0xff, 0x8c, 0x60, 0x5a, 0xea, 0x3d, 0x08, 0x29, 0x2d, 0x86, 0x35, 0xa6, 0xfa, 0x67, 0x86, 0xc8,
0x27, 0x1c, 0xfb, 0x17, 0xf0, 0xad, 0x73, 0x62, 0x97, 0x98, 0x57, 0x62, 0x06, 0xf3, 0x8f, 0x08,
0xea, 0xf2, 0xab, 0x00, 0xbe, 0x36, 0xb4, 0x92, 0xf2, 0xdf, 0x0d, 0xc6, 0x96, 0x7d, 0x93, 0x63,
0xbf, 0x4e, 0x16, 0x8a, 0xb2, 0x1f, 0x0a, 0xe3, 0xac, 0x02, 0x7e, 0x89, 0x00, 0xa7, 0xe3, 0x59,
0x3a, 0xb0, 0xe1, 0xc5, 0x9c, 0xa9, 0xa1, 0x93, 0x9f, 0x7e, 0x6d, 0xe4, 0xbe, 0x7c, 0x2b, 0x5f,
0x2e, 0x6c, 0xe5, 0x7e, 0x6a, 0xff, 0xa7, 0x08, 0xa6, 0xb6, 0x68, 0x3a, 0xb8, 0x14, 0x04, 0x32,
0xff, 0xdd, 0x43, 0x5f, 0x1a, 0xbd, 0x51, 0x20, 0xba, 0xc1, 0x11, 0x2d, 0xe2, 0xe2, 0x50, 0x49,
0x00, 0xbf, 0x41, 0xf0, 0x59, 0xd1, 0xc5, 0x84, 0xe4, 0xc6, 0x28, 0x4b, 0xb9, 0xa6, 0x77, 0x7e,
0x5c, 0x9f, 0xe7, 0xb8, 0x56, 0xc8, 0xb9, 0x70, 0xad, 0x8b, 0xcf, 0x07, 0xbf, 0x43, 0xf0, 0x9e,
0x3a, 0xe9, 0x09, 0xca, 0xf8, 0xaa, 0x71, 0x2b, 0x60, 0x9e, 0xe4, 0x16, 0xc7, 0x67, 0xe0, 0x1b,
0xe7, 0xc1, 0x67, 0x0a, 0x12, 0x89, 0x7f, 0x8d, 0xe0, 0x5d, 0x4e, 0xda, 0x55, 0xc5, 0xa7, 0x1a,
0xf2, 0x30, 0x8a, 0x7f, 0x8e, 0x86, 0x2c, 0xce, 0x2c, 0xb9, 0x10, 0xa8, 0x75, 0x41, 0xb6, 0xd9,
0xe4, 0xfe, 0x96, 0xbc, 0x02, 0x44, 0x76, 0x57, 0x46, 0x05, 0xee, 0xa2, 0x57, 0x86, 0x28, 0xb7,
0xe5, 0xf3, 0x95, 0xdb, 0x0f, 0x10, 0xd4, 0x04, 0x4f, 0x2e, 0xb8, 0x55, 0x15, 0x22, 0xad, 0x7f,
0x90, 0xdb, 0x25, 0x79, 0x22, 0xf9, 0x22, 0x37, 0xbb, 0x8a, 0xcd, 0x22, 0xb3, 0x81, 0xef, 0x44,
0xe6, 0x23, 0x41, 0xa0, 0x1f, 0x9b, 0x6d, 0xbf, 0x15, 0xdd, 0x44, 0x1b, 0x9f, 0x3c, 0x3f, 0x99,
0x43, 0x7f, 0x3f, 0x99, 0x43, 0xff, 0x3a, 0x99, 0x43, 0xdf, 0x34, 0x8a, 0xfe, 0xbf, 0x3a, 0xfb,
0x3f, 0xdf, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xba, 0x1f, 0x43, 0xcd, 0xfc, 0x1b, 0x00, 0x00,
}

View File

@@ -19,6 +19,7 @@ message ApplicationQuery {
optional string name = 1;
optional string refresh = 2;
repeated string project = 3 [(gogoproto.customname) = "Projects"];
optional string resourceVersion = 4 [(gogoproto.nullable) = false];
}
// ApplicationEventsQuery is a query for application resource events

View File

@@ -5,7 +5,7 @@ import (
"testing"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -166,7 +166,7 @@ metadata:
spec:
source:
path: some/path
repoURL: https://git.com/repo.git
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
ksonnet:
environment: default

View File

@@ -26,12 +26,17 @@ const (
ActionSync = "sync"
)
var (
defaultScopes = []string{"groups"}
)
// RBACPolicyEnforcer provides an RBAC Claims Enforcer which additionally consults AppProject
// roles, jwt tokens, and groups. It is backed by a AppProject informer/lister cache and does not
// make any API calls during enforcement.
type RBACPolicyEnforcer struct {
enf *rbac.Enforcer
projLister applister.AppProjectNamespaceLister
scopes []string
}
// NewRBACPolicyEnforcer returns a new RBAC Enforcer for the Argo CD API Server
@@ -39,9 +44,14 @@ func NewRBACPolicyEnforcer(enf *rbac.Enforcer, projLister applister.AppProjectNa
return &RBACPolicyEnforcer{
enf: enf,
projLister: projLister,
scopes: nil,
}
}
func (p *RBACPolicyEnforcer) SetScopes(scopes []string) {
p.scopes = scopes
}
// EnforceClaims is an RBAC claims enforcer specific to the Argo CD API server
func (p *RBACPolicyEnforcer) EnforceClaims(claims jwt.Claims, rvals ...interface{}) bool {
mapClaims, err := jwtutil.MapClaims(claims)
@@ -68,8 +78,12 @@ func (p *RBACPolicyEnforcer) EnforceClaims(claims jwt.Claims, rvals ...interface
return true
}
scopes := p.scopes
if scopes == nil {
scopes = defaultScopes
}
// Finally check if any of the user's groups grant them permissions
groups := jwtutil.GetGroups(mapClaims)
groups := jwtutil.GetScopeValues(mapClaims, scopes)
for _, group := range groups {
vals := append([]interface{}{group}, rvals[1:]...)
if p.enf.EnforceRuntimePolicy(runtimePolicy, vals...) {

View File

@@ -68,4 +68,9 @@ func TestEnforceAllPolicies(t *testing.T) {
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
claims = jwt.MapClaims{"groups": []string{"my-org:other-group"}}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
// AWS cognito returns its groups in cognito:groups
rbacEnf.SetScopes([]string{"cognito:groups"})
claims = jwt.MapClaims{"cognito:groups": []string{"my-org:my-team"}}
assert.True(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
}

View File

@@ -164,13 +164,7 @@ func (s *Server) ListApps(ctx context.Context, q *RepoAppsQuery) (*RepoAppsRespo
}
repo, err := s.db.GetRepository(ctx, q.Repo)
if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
repo = &appsv1.Repository{
Repo: q.Repo,
}
} else {
return nil, err
}
return nil, err
}
// Test the repo
@@ -202,13 +196,7 @@ func (s *Server) GetAppDetails(ctx context.Context, q *RepoAppDetailsQuery) (*re
}
repo, err := s.db.GetRepository(ctx, q.Repo)
if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
repo = &appsv1.Repository{
Repo: q.Repo,
}
} else {
return nil, err
}
return nil, err
}
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
if err != nil {

View File

@@ -15,6 +15,9 @@ import (
"strings"
"time"
yaml "gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
golang_proto "github.com/golang/protobuf/proto"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
@@ -103,13 +106,14 @@ var (
type ArgoCDServer struct {
ArgoCDServerOpts
ssoClientApp *oidc.ClientApp
settings *settings_util.ArgoCDSettings
log *log.Entry
sessionMgr *util_session.SessionManager
settingsMgr *settings_util.SettingsManager
enf *rbac.Enforcer
projInformer cache.SharedIndexInformer
ssoClientApp *oidc.ClientApp
settings *settings_util.ArgoCDSettings
log *log.Entry
sessionMgr *util_session.SessionManager
settingsMgr *settings_util.SettingsManager
enf *rbac.Enforcer
projInformer cache.SharedIndexInformer
policyEnforcer *rbacpolicy.RBACPolicyEnforcer
// stopCh is the channel which when closed, will shutdown the Argo CD server
stopCh chan struct{}
@@ -177,6 +181,7 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
settingsMgr: settingsMgr,
enf: enf,
projInformer: projInformer,
policyEnforcer: policyEnf,
}
}
@@ -350,7 +355,19 @@ func (a *ArgoCDServer) watchSettings(ctx context.Context) {
}
func (a *ArgoCDServer) rbacPolicyLoader(ctx context.Context) {
err := a.enf.RunPolicyLoader(ctx)
err := a.enf.RunPolicyLoader(ctx, func(cm *v1.ConfigMap) error {
var scopes []string
if scopesStr, ok := cm.Data[rbac.ConfigMapScopesKey]; len(scopesStr) > 0 && ok {
scopes = make([]string, 0)
err := yaml.Unmarshal([]byte(scopesStr), &scopes)
if err != nil {
return err
}
}
a.policyEnforcer.SetScopes(scopes)
return nil
})
errors.CheckError(err)
}

View File

@@ -50,6 +50,7 @@ func (s *Server) Get(ctx context.Context, q *SettingsQuery) (*Settings, error) {
Issuer: oidcConfig.Issuer,
ClientID: oidcConfig.ClientID,
CLIClientID: oidcConfig.CLIClientID,
Scopes: oidcConfig.RequestedScopes,
}
}
return &set, nil

View File

@@ -43,7 +43,7 @@ func (m *SettingsQuery) Reset() { *m = SettingsQuery{} }
func (m *SettingsQuery) String() string { return proto.CompactTextString(m) }
func (*SettingsQuery) ProtoMessage() {}
func (*SettingsQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_settings_f96c7d59ef70e4fa, []int{0}
return fileDescriptor_settings_4373c06dcc35da1b, []int{0}
}
func (m *SettingsQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -87,7 +87,7 @@ func (m *Settings) Reset() { *m = Settings{} }
func (m *Settings) String() string { return proto.CompactTextString(m) }
func (*Settings) ProtoMessage() {}
func (*Settings) Descriptor() ([]byte, []int) {
return fileDescriptor_settings_f96c7d59ef70e4fa, []int{1}
return fileDescriptor_settings_4373c06dcc35da1b, []int{1}
}
func (m *Settings) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -162,7 +162,7 @@ func (m *DexConfig) Reset() { *m = DexConfig{} }
func (m *DexConfig) String() string { return proto.CompactTextString(m) }
func (*DexConfig) ProtoMessage() {}
func (*DexConfig) Descriptor() ([]byte, []int) {
return fileDescriptor_settings_f96c7d59ef70e4fa, []int{2}
return fileDescriptor_settings_4373c06dcc35da1b, []int{2}
}
func (m *DexConfig) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -210,7 +210,7 @@ func (m *Connector) Reset() { *m = Connector{} }
func (m *Connector) String() string { return proto.CompactTextString(m) }
func (*Connector) ProtoMessage() {}
func (*Connector) Descriptor() ([]byte, []int) {
return fileDescriptor_settings_f96c7d59ef70e4fa, []int{3}
return fileDescriptor_settings_4373c06dcc35da1b, []int{3}
}
func (m *Connector) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -258,6 +258,7 @@ type OIDCConfig struct {
Issuer string `protobuf:"bytes,2,opt,name=issuer,proto3" json:"issuer,omitempty"`
ClientID string `protobuf:"bytes,3,opt,name=clientID,proto3" json:"clientID,omitempty"`
CLIClientID string `protobuf:"bytes,4,opt,name=cliClientID,proto3" json:"cliClientID,omitempty"`
Scopes []string `protobuf:"bytes,5,rep,name=scopes" json:"scopes,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -267,7 +268,7 @@ func (m *OIDCConfig) Reset() { *m = OIDCConfig{} }
func (m *OIDCConfig) String() string { return proto.CompactTextString(m) }
func (*OIDCConfig) ProtoMessage() {}
func (*OIDCConfig) Descriptor() ([]byte, []int) {
return fileDescriptor_settings_f96c7d59ef70e4fa, []int{4}
return fileDescriptor_settings_4373c06dcc35da1b, []int{4}
}
func (m *OIDCConfig) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -324,6 +325,13 @@ func (m *OIDCConfig) GetCLIClientID() string {
return ""
}
func (m *OIDCConfig) GetScopes() []string {
if m != nil {
return m.Scopes
}
return nil
}
func init() {
proto.RegisterType((*SettingsQuery)(nil), "cluster.SettingsQuery")
proto.RegisterType((*Settings)(nil), "cluster.Settings")
@@ -614,6 +622,21 @@ func (m *OIDCConfig) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintSettings(dAtA, i, uint64(len(m.CLIClientID)))
i += copy(dAtA[i:], m.CLIClientID)
}
if len(m.Scopes) > 0 {
for _, s := range m.Scopes {
dAtA[i] = 0x2a
i++
l = len(s)
for l >= 1<<7 {
dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
l >>= 7
i++
}
dAtA[i] = uint8(l)
i++
i += copy(dAtA[i:], s)
}
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
@@ -727,6 +750,12 @@ func (m *OIDCConfig) Size() (n int) {
if l > 0 {
n += 1 + l + sovSettings(uint64(l))
}
if len(m.Scopes) > 0 {
for _, s := range m.Scopes {
l = len(s)
n += 1 + l + sovSettings(uint64(l))
}
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -1431,6 +1460,35 @@ func (m *OIDCConfig) Unmarshal(dAtA []byte) error {
}
m.CLIClientID = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Scopes", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSettings
}
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 ErrInvalidLengthSettings
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Scopes = append(m.Scopes, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSettings(dAtA[iNdEx:])
@@ -1559,45 +1617,46 @@ var (
)
func init() {
proto.RegisterFile("server/settings/settings.proto", fileDescriptor_settings_f96c7d59ef70e4fa)
proto.RegisterFile("server/settings/settings.proto", fileDescriptor_settings_4373c06dcc35da1b)
}
var fileDescriptor_settings_f96c7d59ef70e4fa = []byte{
// 566 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x5d, 0x6b, 0x13, 0x41,
0x14, 0x65, 0xbb, 0xfd, 0xca, 0x8d, 0xda, 0x76, 0x94, 0x12, 0x83, 0x24, 0x21, 0x4f, 0x01, 0x71,
0xd7, 0xa4, 0x2f, 0xea, 0x8b, 0x90, 0x54, 0x24, 0xb6, 0x50, 0x9c, 0xa2, 0x0f, 0x82, 0xc8, 0x74,
0x72, 0xdd, 0x8e, 0xd9, 0xee, 0x2c, 0xb3, 0xb3, 0x8b, 0x79, 0xf5, 0x1f, 0x88, 0xf8, 0x27, 0xfc,
0x25, 0x3e, 0x0a, 0xbe, 0x07, 0x59, 0xfc, 0x21, 0xb2, 0xb3, 0x1f, 0x59, 0x9a, 0xe2, 0xdb, 0xd9,
0x73, 0xee, 0x99, 0x9d, 0x7b, 0xcf, 0x5c, 0xe8, 0x44, 0xa8, 0x12, 0x54, 0x6e, 0x84, 0x5a, 0x8b,
0xc0, 0x8b, 0x2a, 0xe0, 0x84, 0x4a, 0x6a, 0x49, 0x76, 0xb8, 0x1f, 0x47, 0x1a, 0x55, 0xfb, 0x9e,
0x27, 0x3d, 0x69, 0x38, 0x37, 0x43, 0xb9, 0xdc, 0x7e, 0xe0, 0x49, 0xe9, 0xf9, 0xe8, 0xb2, 0x50,
0xb8, 0x2c, 0x08, 0xa4, 0x66, 0x5a, 0xc8, 0xa0, 0x30, 0xb7, 0xa7, 0x9e, 0xd0, 0x97, 0xf1, 0x85,
0xc3, 0xe5, 0x95, 0xcb, 0x94, 0xb1, 0x7f, 0x32, 0xe0, 0x11, 0x9f, 0xb9, 0xe1, 0xdc, 0xcb, 0x6c,
0x91, 0xcb, 0xc2, 0xd0, 0x17, 0xdc, 0x18, 0xdd, 0x64, 0xc8, 0xfc, 0xf0, 0x92, 0x0d, 0x5d, 0x0f,
0x03, 0x54, 0x4c, 0xe3, 0x2c, 0x3f, 0xaa, 0xbf, 0x07, 0xb7, 0xcf, 0x8b, 0x9b, 0xbd, 0x8e, 0x51,
0x2d, 0xfa, 0x3f, 0x6c, 0xd8, 0x2d, 0x19, 0x72, 0x1f, 0xec, 0x58, 0xf9, 0x2d, 0xab, 0x67, 0x0d,
0x1a, 0xe3, 0x9d, 0x74, 0xd9, 0xb5, 0xdf, 0xd0, 0x53, 0x9a, 0x71, 0xe4, 0x31, 0x34, 0x66, 0xf8,
0x79, 0x22, 0x83, 0x8f, 0xc2, 0x6b, 0x6d, 0xf4, 0xac, 0x41, 0x73, 0x44, 0x9c, 0xa2, 0x29, 0xe7,
0xb8, 0x54, 0xe8, 0xaa, 0x88, 0x4c, 0x00, 0xa4, 0x98, 0xf1, 0xc2, 0x62, 0x1b, 0xcb, 0xdd, 0xca,
0x72, 0x36, 0x3d, 0x9e, 0xe4, 0xd2, 0xf8, 0x4e, 0xba, 0xec, 0xc2, 0xea, 0x9b, 0xd6, 0x6c, 0xa4,
0x07, 0x4d, 0x16, 0x86, 0xa7, 0xec, 0x02, 0xfd, 0x13, 0x5c, 0xb4, 0x36, 0xb3, 0x9b, 0xd1, 0x3a,
0x45, 0xde, 0xc2, 0x81, 0xc2, 0x48, 0xc6, 0x8a, 0xe3, 0x59, 0x82, 0x4a, 0x89, 0x19, 0x46, 0xad,
0xad, 0x9e, 0x3d, 0x68, 0x8e, 0x06, 0xd5, 0xdf, 0xca, 0x0e, 0x1d, 0x7a, 0xbd, 0xf4, 0x45, 0xa0,
0xd5, 0x82, 0xae, 0x1f, 0xd1, 0xfe, 0x6a, 0xc1, 0xe1, 0xcd, 0xd5, 0x64, 0x1f, 0xec, 0x39, 0x2e,
0xf2, 0x31, 0xd1, 0x0c, 0x12, 0x06, 0x5b, 0x09, 0xf3, 0x63, 0x2c, 0x26, 0x73, 0xe2, 0xac, 0x12,
0x73, 0xca, 0xc4, 0x0c, 0xf8, 0xc0, 0x67, 0x4e, 0x38, 0xf7, 0x9c, 0x2c, 0x31, 0xa7, 0x96, 0x98,
0x53, 0x26, 0xb6, 0x76, 0x43, 0x9a, 0x9f, 0xfc, 0x6c, 0xe3, 0x89, 0xd5, 0x7f, 0x0e, 0x8d, 0x6a,
0xd4, 0x64, 0x04, 0xc0, 0x65, 0x10, 0x20, 0xd7, 0x52, 0x45, 0x2d, 0xcb, 0x74, 0xbc, 0x8a, 0x64,
0x52, 0x4a, 0xb4, 0x56, 0xd5, 0x3f, 0x82, 0x46, 0x25, 0x10, 0x02, 0x9b, 0x01, 0xbb, 0xc2, 0xa2,
0x0f, 0x83, 0x33, 0x4e, 0x2f, 0xc2, 0xbc, 0x8f, 0x06, 0x35, 0xb8, 0xff, 0xdd, 0x82, 0x5a, 0x3c,
0x37, 0xda, 0x0e, 0x61, 0x5b, 0x44, 0x51, 0x8c, 0xaa, 0x30, 0x16, 0x5f, 0x64, 0x00, 0xbb, 0xdc,
0x17, 0x18, 0xe8, 0xe9, 0xb1, 0x79, 0x01, 0x8d, 0xf1, 0xad, 0x74, 0xd9, 0xdd, 0x9d, 0x14, 0x1c,
0xad, 0x54, 0x32, 0x84, 0x26, 0xf7, 0x45, 0x29, 0xe4, 0x41, 0x8f, 0xf7, 0xd2, 0x65, 0xb7, 0x39,
0x39, 0x9d, 0x56, 0xf5, 0xf5, 0x9a, 0xd1, 0x7b, 0xd8, 0x2b, 0x73, 0x3d, 0x47, 0x95, 0x08, 0x8e,
0xe4, 0x15, 0xd8, 0x2f, 0x51, 0x93, 0xc3, 0xb5, 0xe0, 0xcd, 0x63, 0x6f, 0x1f, 0xac, 0xf1, 0xfd,
0xd6, 0x97, 0xdf, 0x7f, 0xbf, 0x6d, 0x10, 0xb2, 0x6f, 0x76, 0x2f, 0x19, 0x56, 0x8b, 0x3b, 0x7e,
0xfa, 0x33, 0xed, 0x58, 0xbf, 0xd2, 0x8e, 0xf5, 0x27, 0xed, 0x58, 0xef, 0x1e, 0xfe, 0x6f, 0x07,
0xaf, 0x2d, 0xff, 0xc5, 0xb6, 0x59, 0xb6, 0xa3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x62, 0x4c,
0x68, 0x20, 0x16, 0x04, 0x00, 0x00,
var fileDescriptor_settings_4373c06dcc35da1b = []byte{
// 577 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0x5d, 0x6b, 0x13, 0x41,
0x14, 0x65, 0xbb, 0xfd, 0xda, 0x1b, 0xb5, 0xed, 0x28, 0x25, 0x06, 0x49, 0x42, 0x9e, 0x02, 0xe2,
0xae, 0x49, 0x5f, 0xd4, 0x17, 0x21, 0xa9, 0x48, 0x6c, 0xa1, 0x38, 0x45, 0x1f, 0x04, 0x91, 0xe9,
0xe4, 0xba, 0x1d, 0xb3, 0xdd, 0x59, 0x66, 0x67, 0x83, 0x79, 0xf5, 0x1f, 0x88, 0xff, 0x42, 0xf0,
0x7f, 0xf8, 0x28, 0xf8, 0x1e, 0x64, 0xf1, 0x87, 0xc8, 0xce, 0x7e, 0x64, 0x69, 0x8a, 0x6f, 0x67,
0xce, 0xb9, 0x67, 0x76, 0xee, 0x9c, 0xb9, 0x0b, 0xed, 0x18, 0xd5, 0x1c, 0x95, 0x17, 0xa3, 0xd6,
0x22, 0xf4, 0xe3, 0x0a, 0xb8, 0x91, 0x92, 0x5a, 0x92, 0x1d, 0x1e, 0x24, 0xb1, 0x46, 0xd5, 0xba,
0xe7, 0x4b, 0x5f, 0x1a, 0xce, 0xcb, 0x50, 0x2e, 0xb7, 0x1e, 0xf8, 0x52, 0xfa, 0x01, 0x7a, 0x2c,
0x12, 0x1e, 0x0b, 0x43, 0xa9, 0x99, 0x16, 0x32, 0x2c, 0xcc, 0xad, 0x89, 0x2f, 0xf4, 0x65, 0x72,
0xe1, 0x72, 0x79, 0xe5, 0x31, 0x65, 0xec, 0x9f, 0x0c, 0x78, 0xc4, 0xa7, 0x5e, 0x34, 0xf3, 0x33,
0x5b, 0xec, 0xb1, 0x28, 0x0a, 0x04, 0x37, 0x46, 0x6f, 0x3e, 0x60, 0x41, 0x74, 0xc9, 0x06, 0x9e,
0x8f, 0x21, 0x2a, 0xa6, 0x71, 0x9a, 0x6f, 0xd5, 0xdb, 0x83, 0xdb, 0xe7, 0xc5, 0xc9, 0x5e, 0x27,
0xa8, 0x16, 0xbd, 0xef, 0x36, 0xec, 0x96, 0x0c, 0xb9, 0x0f, 0x76, 0xa2, 0x82, 0xa6, 0xd5, 0xb5,
0xfa, 0xce, 0x68, 0x27, 0x5d, 0x76, 0xec, 0x37, 0xf4, 0x94, 0x66, 0x1c, 0x79, 0x0c, 0xce, 0x14,
0x3f, 0x8f, 0x65, 0xf8, 0x51, 0xf8, 0xcd, 0x8d, 0xae, 0xd5, 0x6f, 0x0c, 0x89, 0x5b, 0x34, 0xe5,
0x1e, 0x97, 0x0a, 0x5d, 0x15, 0x91, 0x31, 0x80, 0x14, 0x53, 0x5e, 0x58, 0x6c, 0x63, 0xb9, 0x5b,
0x59, 0xce, 0x26, 0xc7, 0xe3, 0x5c, 0x1a, 0xdd, 0x49, 0x97, 0x1d, 0x58, 0xad, 0x69, 0xcd, 0x46,
0xba, 0xd0, 0x60, 0x51, 0x74, 0xca, 0x2e, 0x30, 0x38, 0xc1, 0x45, 0x73, 0x33, 0x3b, 0x19, 0xad,
0x53, 0xe4, 0x2d, 0x1c, 0x28, 0x8c, 0x65, 0xa2, 0x38, 0x9e, 0xcd, 0x51, 0x29, 0x31, 0xc5, 0xb8,
0xb9, 0xd5, 0xb5, 0xfb, 0x8d, 0x61, 0xbf, 0xfa, 0x5a, 0xd9, 0xa1, 0x4b, 0xaf, 0x97, 0xbe, 0x08,
0xb5, 0x5a, 0xd0, 0xf5, 0x2d, 0x5a, 0x5f, 0x2d, 0x38, 0xbc, 0xb9, 0x9a, 0xec, 0x83, 0x3d, 0xc3,
0x45, 0x7e, 0x4d, 0x34, 0x83, 0x84, 0xc1, 0xd6, 0x9c, 0x05, 0x09, 0x16, 0x37, 0x73, 0xe2, 0xae,
0x12, 0x73, 0xcb, 0xc4, 0x0c, 0xf8, 0xc0, 0xa7, 0x6e, 0x34, 0xf3, 0xdd, 0x2c, 0x31, 0xb7, 0x96,
0x98, 0x5b, 0x26, 0xb6, 0x76, 0x42, 0x9a, 0xef, 0xfc, 0x6c, 0xe3, 0x89, 0xd5, 0x7b, 0x0e, 0x4e,
0x75, 0xd5, 0x64, 0x08, 0xc0, 0x65, 0x18, 0x22, 0xd7, 0x52, 0xc5, 0x4d, 0xcb, 0x74, 0xbc, 0x8a,
0x64, 0x5c, 0x4a, 0xb4, 0x56, 0xd5, 0x3b, 0x02, 0xa7, 0x12, 0x08, 0x81, 0xcd, 0x90, 0x5d, 0x61,
0xd1, 0x87, 0xc1, 0x19, 0xa7, 0x17, 0x51, 0xde, 0x87, 0x43, 0x0d, 0xee, 0xfd, 0xb0, 0xa0, 0x16,
0xcf, 0x8d, 0xb6, 0x43, 0xd8, 0x16, 0x71, 0x9c, 0xa0, 0x2a, 0x8c, 0xc5, 0x8a, 0xf4, 0x61, 0x97,
0x07, 0x02, 0x43, 0x3d, 0x39, 0x36, 0x2f, 0xc0, 0x19, 0xdd, 0x4a, 0x97, 0x9d, 0xdd, 0x71, 0xc1,
0xd1, 0x4a, 0x25, 0x03, 0x68, 0xf0, 0x40, 0x94, 0x42, 0x1e, 0xf4, 0x68, 0x2f, 0x5d, 0x76, 0x1a,
0xe3, 0xd3, 0x49, 0x55, 0x5f, 0xaf, 0xc9, 0x3e, 0x1a, 0x73, 0x19, 0x15, 0x71, 0x3b, 0xb4, 0x58,
0x0d, 0xdf, 0xc3, 0x5e, 0x99, 0xf7, 0x39, 0xaa, 0xb9, 0xe0, 0x48, 0x5e, 0x81, 0xfd, 0x12, 0x35,
0x39, 0x5c, 0x7b, 0x10, 0x66, 0x08, 0x5a, 0x07, 0x6b, 0x7c, 0xaf, 0xf9, 0xe5, 0xf7, 0xdf, 0x6f,
0x1b, 0x84, 0xec, 0x9b, 0x99, 0x9c, 0x0f, 0xaa, 0x81, 0x1e, 0x3d, 0xfd, 0x99, 0xb6, 0xad, 0x5f,
0x69, 0xdb, 0xfa, 0x93, 0xb6, 0xad, 0x77, 0x0f, 0xff, 0x37, 0x9b, 0xd7, 0x7e, 0x0a, 0x17, 0xdb,
0x66, 0x08, 0x8f, 0xfe, 0x05, 0x00, 0x00, 0xff, 0xff, 0x0d, 0x66, 0xd0, 0x3d, 0x2e, 0x04, 0x00,
0x00,
}

View File

@@ -36,6 +36,7 @@ message OIDCConfig {
string issuer = 2;
string clientID = 3 [(gogoproto.customname) = "ClientID"];
string cliClientID = 4 [(gogoproto.customname) = "CLIClientID"];
repeated string scopes = 5;
}
// SettingsService

View File

@@ -406,6 +406,7 @@ func TestEdgeCasesApplicationResources(t *testing.T) {
"DeprecatedExtensions": "deprecated-extensions",
"CRDs": "crd-creation",
"DuplicatedResources": "duplicated-resources",
"FailedConversion": "failed-conversion",
}
for name, appPath := range apps {
@@ -516,3 +517,75 @@ func TestResourceAction(t *testing.T) {
assert.Equal(t, "test", deployment.Labels["sample"])
}
func TestSyncResourceByLabel(t *testing.T) {
fixture.EnsureCleanState()
app := createAndSyncDefault(t)
res, _ := fixture.RunCli("app", "sync", app.Name, "--label",
fmt.Sprintf("app.kubernetes.io/instance=test-%s", strings.Split(app.Name, "-")[1]))
assert.Contains(t, res, "guestbook-ui Synced Healthy")
res, _ = fixture.RunCli("app", "sync", app.Name, "--label", "this-label=does-not-exist")
assert.Contains(t, res, "level=fatal")
}
func TestPermissions(t *testing.T) {
fixture.EnsureCleanState()
appName := "test-app"
_, err := fixture.RunCli("proj", "create", "test")
assert.NoError(t, err)
// make sure app cannot be created without permissions in project
output, err := fixture.RunCli("app", "create", appName, "--repo", fixture.RepoURL(),
"--path", guestbookPath, "--project", "test", "--dest-server", common.KubernetesInternalAPIServerAddr, "--dest-namespace", fixture.DeploymentNamespace)
assert.Error(t, err)
sourceError := fmt.Sprintf("application repo %s is not permitted in project 'test'", fixture.RepoURL())
destinationError := fmt.Sprintf("application destination {%s %s} is not permitted in project 'test'", common.KubernetesInternalAPIServerAddr, fixture.DeploymentNamespace)
assert.Contains(t, output, sourceError)
assert.Contains(t, output, destinationError)
proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Get("test", metav1.GetOptions{})
assert.NoError(t, err)
proj.Spec.Destinations = []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}}
proj.Spec.SourceRepos = []string{"*"}
proj, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Update(proj)
assert.NoError(t, err)
// make sure controller report permissions issues in conditions
_, err = fixture.RunCli("app", "create", "test-app", "--repo", fixture.RepoURL(),
"--path", guestbookPath, "--project", "test", "--dest-server", common.KubernetesInternalAPIServerAddr, "--dest-namespace", fixture.DeploymentNamespace)
assert.NoError(t, err)
defer func() {
err = fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.ArgoCDNamespace).Delete(appName, &metav1.DeleteOptions{})
assert.NoError(t, err)
}()
proj.Spec.Destinations = []v1alpha1.ApplicationDestination{}
proj.Spec.SourceRepos = []string{}
_, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Update(proj)
assert.NoError(t, err)
closer, client, err := fixture.ArgoCDClientset.NewApplicationClient()
assert.NoError(t, err)
defer util.Close(closer)
refresh := string(v1alpha1.RefreshTypeNormal)
app, err := client.Get(context.Background(), &application.ApplicationQuery{Name: &appName, Refresh: &refresh})
assert.NoError(t, err)
destinationErrorExist := false
sourceErrorExist := false
for i := range app.Status.Conditions {
if strings.Contains(app.Status.Conditions[i].Message, destinationError) {
destinationErrorExist = true
}
if strings.Contains(app.Status.Conditions[i].Message, sourceError) {
sourceErrorExist = true
}
}
assert.True(t, destinationErrorExist)
assert.True(t, sourceErrorExist)
}

View File

@@ -0,0 +1,16 @@
apiVersion: apiregistration.k8s.io/v1beta1
kind: APIService
metadata:
name: v1beta1.metrics.k8s.io
labels:
app: metrics-server
chart: metrics-server
spec:
service:
name: metrics-server
namespace: kube-system
group: metrics.k8s.io
version: v1beta1
insecureSkipTLSVerify: true
groupPriorityMinimum: 100
versionPriority: 100

View File

@@ -127,28 +127,13 @@ func WaitForRefresh(ctx context.Context, appIf v1alpha1.ApplicationInterface, na
return nil, fmt.Errorf("application refresh deadline exceeded")
}
// GetSpecErrors returns list of conditions which indicates that app spec is invalid. Following is checked:
// ValidateRepo validates the repository specified in application spec. Following is checked:
// * the git repository is accessible
// * the git path contains valid manifests
// * the referenced cluster has been added to Argo CD
// * the app source repo and destination namespace/cluster are permitted in app project
// * there are parameters of only one app source type
// * ksonnet: the specified environment exists
func GetSpecErrors(
ctx context.Context,
spec *argoappv1.ApplicationSpec,
proj *argoappv1.AppProject,
repoClientset reposerver.Clientset,
db db.ArgoDB,
) ([]argoappv1.ApplicationCondition, argoappv1.ApplicationSourceType, error) {
func ValidateRepo(ctx context.Context, spec *argoappv1.ApplicationSpec, repoClientset reposerver.Clientset, db db.ArgoDB) ([]argoappv1.ApplicationCondition, argoappv1.ApplicationSourceType, error) {
conditions := make([]argoappv1.ApplicationCondition, 0)
if spec.Source.RepoURL == "" || spec.Source.Path == "" {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: "spec.source.repoURL and spec.source.path are required",
})
return conditions, "", nil
}
// Test the repo
conn, repoClient, err := repoClientset.NewRepoServerClient()
@@ -158,24 +143,16 @@ func GetSpecErrors(
defer util.Close(conn)
repoAccessable := false
repoRes, err := db.GetRepository(ctx, spec.Source.RepoURL)
if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
// The repo has not been added to Argo CD so we do not have credentials to access it.
// We support the mode where apps can be created from public repositories. Test the
// repo to make sure it is publicly accessible
err = git.TestRepo(spec.Source.RepoURL, "", "", "", false)
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("No credentials available for source repository and repository is not publicly accessible: %v", err),
})
} else {
repoAccessable = true
}
} else {
return nil, "", err
}
return nil, "", err
}
err = git.TestRepo(repoRes.Repo, repoRes.Username, repoRes.Password, repoRes.SSHPrivateKey, repoRes.InsecureIgnoreHostKey)
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("repository not accessible: %v", err),
})
} else {
repoAccessable = true
}
@@ -218,18 +195,31 @@ func GetSpecErrors(
conditions = append(conditions, helmConditions...)
}
case argoappv1.ApplicationSourceTypeDirectory, argoappv1.ApplicationSourceTypeKustomize:
maniDirConditions := verifyGenerateManifests(ctx, repoRes, []*argoappv1.HelmRepository{}, spec, repoClient)
if len(maniDirConditions) > 0 {
conditions = append(conditions, maniDirConditions...)
mainDirConditions := verifyGenerateManifests(ctx, repoRes, []*argoappv1.HelmRepository{}, spec, repoClient)
if len(mainDirConditions) > 0 {
conditions = append(conditions, mainDirConditions...)
}
}
}
}
return conditions, appSourceType, nil
}
// ValidatePermissions ensures that the referenced cluster has been added to Argo CD and the app source repo and destination namespace/cluster are permitted in app project
func ValidatePermissions(ctx context.Context, spec *argoappv1.ApplicationSpec, proj *argoappv1.AppProject, db db.ArgoDB) ([]argoappv1.ApplicationCondition, error) {
conditions := make([]argoappv1.ApplicationCondition, 0)
if spec.Source.RepoURL == "" || spec.Source.Path == "" {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: "spec.source.repoURL and spec.source.path are required",
})
return conditions, nil
}
if !proj.IsSourcePermitted(spec.Source) {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: fmt.Sprintf("application source %v is not permitted in project '%s'", spec.Source, spec.Project),
Message: fmt.Sprintf("application repo %s is not permitted in project '%s'", spec.Source.RepoURL, spec.Project),
})
}
@@ -241,7 +231,7 @@ func GetSpecErrors(
})
}
// Ensure the k8s cluster the app is referencing, is configured in Argo CD
_, err = db.GetCluster(ctx, spec.Destination.Server)
_, err := db.GetCluster(ctx, spec.Destination.Server)
if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
conditions = append(conditions, argoappv1.ApplicationCondition{
@@ -249,11 +239,11 @@ func GetSpecErrors(
Message: fmt.Sprintf("cluster '%s' has not been configured", spec.Destination.Server),
})
} else {
return nil, "", err
return nil, err
}
}
}
return conditions, appSourceType, nil
return conditions, nil
}
// GetAppProject returns a project from an application
@@ -340,6 +330,7 @@ func verifyAppYAML(ctx context.Context, repoRes *argoappv1.Repository, spec *arg
return nil
}
// verifyHelmChart verifies a helm chart is functional
// verifyHelmChart verifies a helm chart is functional
func verifyHelmChart(ctx context.Context, repoRes *argoappv1.Repository, spec *argoappv1.ApplicationSpec, repoClient repository.RepoServerServiceClient) []argoappv1.ApplicationCondition {
var conditions []argoappv1.ApplicationCondition

View File

@@ -4,20 +4,16 @@ import (
"encoding/json"
"strings"
yaml "gopkg.in/yaml.v2"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/diff"
jsonpatch "github.com/evanphx/json-patch"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const (
noMatchingPathError = "Unable to remove nonexistent key: not-matching-path"
)
type normalizerPatch struct {
groupKind schema.GroupKind
namespace string
@@ -103,10 +99,8 @@ func (n *normalizer) Normalize(un *unstructured.Unstructured) error {
for _, patch := range matched {
patchedData, err := patch.patch.Apply(docData)
if err != nil {
if err.Error() == noMatchingPathError {
continue
}
return err
log.Debugf("Failed to apply normalization: %v", err)
continue
}
docData = patchedData
}

View File

@@ -3,6 +3,7 @@ package argo
import (
"testing"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -73,3 +74,45 @@ func TestNormalizeMatchedResourceOverrides(t *testing.T) {
assert.Nil(t, err)
assert.False(t, has)
}
const testCRDYAML = `
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: certificates.certmanager.k8s.io
spec:
group: certmanager.k8s.io
names:
kind: Certificate
listKind: CertificateList
plural: certificates
shortNames:
- cert
- certs
singular: certificate
scope: Namespaced
version: v1alpha1`
func TestNormalizeMissingJsonPointer(t *testing.T) {
normalizer, err := NewDiffNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{
"apps/Deployment": {
IgnoreDifferences: `jsonPointers: ["/garbage"]`,
},
"apiextensions.k8s.io/CustomResourceDefinition": {
IgnoreDifferences: `jsonPointers: ["/spec/additionalPrinterColumns/0/priority"]`,
},
})
assert.NoError(t, err)
deployment := kube.MustToUnstructured(test.DemoDeployment())
err = normalizer.Normalize(deployment)
assert.NoError(t, err)
crd := unstructured.Unstructured{}
err = yaml.Unmarshal([]byte(testCRDYAML), &crd)
assert.NoError(t, err)
err = normalizer.Normalize(&crd)
assert.NoError(t, err)
}

View File

@@ -80,10 +80,14 @@ func TestCreateExistingRepository(t *testing.T) {
assert.Equal(t, codes.AlreadyExists, status.Convert(err).Code())
}
func TestDeleteRepositoryManagedSecrets(t *testing.T) {
func TestGetRepository(t *testing.T) {
config := map[string]string{
"repositories": `
- url: https://github.com/argoproj/argocd-example-apps
- url: https://known/repo
- url: https://secured/repo
`,
"repository.credentials": `
- url: https://secured
usernameSecret:
name: managed-secret
key: username
@@ -91,7 +95,41 @@ func TestDeleteRepositoryManagedSecrets(t *testing.T) {
name: managed-secret
key: password
`}
clientset := getClientset(config, &v1.Secret{
clientset := getClientset(config, newManagedSecret())
db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset)
tests := []struct {
name string
repoURL string
want *v1alpha1.Repository
}{
{
name: "TestUnknownRepo",
repoURL: "https://unknown/repo",
want: &v1alpha1.Repository{Repo: "https://unknown/repo"},
},
{
name: "TestKnownRepo",
repoURL: "https://known/repo",
want: &v1alpha1.Repository{Repo: "https://known/repo"},
},
{
name: "TestSecuredRepo",
repoURL: "https://secured/repo",
want: &v1alpha1.Repository{Repo: "https://secured/repo", Username: "test-username", Password: "test-password"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := db.GetRepository(context.TODO(), tt.repoURL)
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func newManagedSecret() *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "managed-secret",
Namespace: testNamespace,
@@ -103,7 +141,21 @@ func TestDeleteRepositoryManagedSecrets(t *testing.T) {
username: []byte("test-username"),
password: []byte("test-password"),
},
})
}
}
func TestDeleteRepositoryManagedSecrets(t *testing.T) {
config := map[string]string{
"repositories": `
- url: https://github.com/argoproj/argocd-example-apps
usernameSecret:
name: managed-secret
key: username
passwordSecret:
name: managed-secret
key: password
`}
clientset := getClientset(config, newManagedSecret())
db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset)
err := db.DeleteRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps")

262
util/db/mocks/ArgoDB.go Normal file
View File

@@ -0,0 +1,262 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
package mocks
import context "context"
import db "github.com/argoproj/argo-cd/util/db"
import mock "github.com/stretchr/testify/mock"
import v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
// ArgoDB is an autogenerated mock type for the ArgoDB type
type ArgoDB struct {
mock.Mock
}
// CreateCluster provides a mock function with given fields: ctx, c
func (_m *ArgoDB) CreateCluster(ctx context.Context, c *v1alpha1.Cluster) (*v1alpha1.Cluster, error) {
ret := _m.Called(ctx, c)
var r0 *v1alpha1.Cluster
if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.Cluster) *v1alpha1.Cluster); ok {
r0 = rf(ctx, c)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Cluster)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v1alpha1.Cluster) error); ok {
r1 = rf(ctx, c)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateRepository provides a mock function with given fields: ctx, r
func (_m *ArgoDB) CreateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
ret := _m.Called(ctx, r)
var r0 *v1alpha1.Repository
if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.Repository) *v1alpha1.Repository); ok {
r0 = rf(ctx, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v1alpha1.Repository) error); ok {
r1 = rf(ctx, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteCluster provides a mock function with given fields: ctx, name
func (_m *ArgoDB) DeleteCluster(ctx context.Context, name string) error {
ret := _m.Called(ctx, name)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, name)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteRepository provides a mock function with given fields: ctx, name
func (_m *ArgoDB) DeleteRepository(ctx context.Context, name string) error {
ret := _m.Called(ctx, name)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, name)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetCluster provides a mock function with given fields: ctx, name
func (_m *ArgoDB) GetCluster(ctx context.Context, name string) (*v1alpha1.Cluster, error) {
ret := _m.Called(ctx, name)
var r0 *v1alpha1.Cluster
if rf, ok := ret.Get(0).(func(context.Context, string) *v1alpha1.Cluster); ok {
r0 = rf(ctx, name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Cluster)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetRepository provides a mock function with given fields: ctx, repoURL
func (_m *ArgoDB) GetRepository(ctx context.Context, repoURL string) (*v1alpha1.Repository, error) {
ret := _m.Called(ctx, repoURL)
var r0 *v1alpha1.Repository
if rf, ok := ret.Get(0).(func(context.Context, string) *v1alpha1.Repository); ok {
r0 = rf(ctx, repoURL)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, repoURL)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListClusters provides a mock function with given fields: ctx
func (_m *ArgoDB) ListClusters(ctx context.Context) (*v1alpha1.ClusterList, error) {
ret := _m.Called(ctx)
var r0 *v1alpha1.ClusterList
if rf, ok := ret.Get(0).(func(context.Context) *v1alpha1.ClusterList); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ClusterList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListHelmRepos provides a mock function with given fields: ctx
func (_m *ArgoDB) ListHelmRepos(ctx context.Context) ([]*v1alpha1.HelmRepository, error) {
ret := _m.Called(ctx)
var r0 []*v1alpha1.HelmRepository
if rf, ok := ret.Get(0).(func(context.Context) []*v1alpha1.HelmRepository); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*v1alpha1.HelmRepository)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListRepoURLs provides a mock function with given fields: ctx
func (_m *ArgoDB) ListRepoURLs(ctx context.Context) ([]string, error) {
ret := _m.Called(ctx)
var r0 []string
if rf, ok := ret.Get(0).(func(context.Context) []string); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateCluster provides a mock function with given fields: ctx, c
func (_m *ArgoDB) UpdateCluster(ctx context.Context, c *v1alpha1.Cluster) (*v1alpha1.Cluster, error) {
ret := _m.Called(ctx, c)
var r0 *v1alpha1.Cluster
if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.Cluster) *v1alpha1.Cluster); ok {
r0 = rf(ctx, c)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Cluster)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v1alpha1.Cluster) error); ok {
r1 = rf(ctx, c)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateRepository provides a mock function with given fields: ctx, r
func (_m *ArgoDB) UpdateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
ret := _m.Called(ctx, r)
var r0 *v1alpha1.Repository
if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.Repository) *v1alpha1.Repository); ok {
r0 = rf(ctx, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v1alpha1.Repository) error); ok {
r1 = rf(ctx, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WatchClusters provides a mock function with given fields: ctx, callback
func (_m *ArgoDB) WatchClusters(ctx context.Context, callback func(*db.ClusterEvent)) error {
ret := _m.Called(ctx, callback)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, func(*db.ClusterEvent)) error); ok {
r0 = rf(ctx, callback)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -5,17 +5,18 @@ import (
"hash/fnv"
"strings"
"github.com/argoproj/argo-cd/common"
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/settings"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apiv1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/common"
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/settings"
)
const (
@@ -45,7 +46,7 @@ func (db *db) CreateRepository(ctx context.Context, r *appsv1.Repository) (*apps
return nil, err
}
index := getRepoCredIndex(s, r.Repo)
index := getRepositoryIndex(s, r.Repo)
if index > -1 {
return nil, status.Errorf(codes.AlreadyExists, "repository '%s' already exists", r.Repo)
}
@@ -85,25 +86,45 @@ func (db *db) GetRepository(ctx context.Context, repoURL string) (*appsv1.Reposi
return nil, err
}
index := getRepoCredIndex(s, repoURL)
if index < 0 {
return nil, status.Errorf(codes.NotFound, "repo '%s' not found", repoURL)
repo := &appsv1.Repository{Repo: repoURL}
index := getRepositoryIndex(s, repoURL)
if index >= 0 {
repo, err = db.credentialsToRepository(s.Repositories[index])
if err != nil {
return nil, err
}
}
repoInfo := s.Repositories[index]
if !repo.HasCredentials() {
index := getRepositoryCredentialIndex(s, repoURL)
if index >= 0 {
credential, err := db.credentialsToRepository(s.RepositoryCredentials[index])
if err != nil {
return nil, err
} else {
log.WithFields(log.Fields{"repoURL": repo.Repo, "credUrl": credential.Repo}).Info("copying credentials")
repo.CopyCredentialsFrom(*credential)
}
}
}
return repo, err
}
func (db *db) credentialsToRepository(repoInfo settings.RepoCredentials) (*appsv1.Repository, error) {
repo := &appsv1.Repository{
Repo: repoInfo.URL,
InsecureIgnoreHostKey: repoInfo.InsecureIgnoreHostKey,
}
err = db.unmarshalFromSecretsStr(map[*string]*apiv1.SecretKeySelector{
err := db.unmarshalFromSecretsStr(map[*string]*apiv1.SecretKeySelector{
&repo.Username: repoInfo.UsernameSecret,
&repo.Password: repoInfo.PasswordSecret,
&repo.SSHPrivateKey: repoInfo.SSHPrivateKeySecret,
}, make(map[string]*apiv1.Secret))
if err != nil {
return nil, err
}
return repo, nil
return repo, err
}
// UpdateRepository updates a repository
@@ -113,7 +134,7 @@ func (db *db) UpdateRepository(ctx context.Context, r *appsv1.Repository) (*apps
return nil, err
}
index := getRepoCredIndex(s, r.Repo)
index := getRepositoryIndex(s, r.Repo)
if index < 0 {
return nil, status.Errorf(codes.NotFound, "repo '%s' not found", r.Repo)
}
@@ -138,7 +159,7 @@ func (db *db) DeleteRepository(ctx context.Context, repoURL string) error {
return err
}
index := getRepoCredIndex(s, repoURL)
index := getRepositoryIndex(s, repoURL)
if index < 0 {
return status.Errorf(codes.NotFound, "repo '%s' not found", repoURL)
}
@@ -243,9 +264,20 @@ func (db *db) upsertSecret(name string, data map[string][]byte) error {
return nil
}
func getRepoCredIndex(s *settings.ArgoCDSettings, repoURL string) int {
for i, cred := range s.Repositories {
if git.SameURL(cred.URL, repoURL) {
func getRepositoryIndex(s *settings.ArgoCDSettings, repoURL string) int {
for i, repo := range s.Repositories {
if git.SameURL(repo.URL, repoURL) {
return i
}
}
return -1
}
func getRepositoryCredentialIndex(s *settings.ArgoCDSettings, repoURL string) int {
repoURL = git.NormalizeGitURL(repoURL)
for i, cred := range s.RepositoryCredentials {
credUrl := git.NormalizeGitURL(cred.URL)
if strings.HasPrefix(repoURL, credUrl) {
return i
}
}

View File

@@ -1,6 +1,10 @@
package db
import "testing"
import (
"testing"
"github.com/argoproj/argo-cd/util/settings"
)
func TestRepoURLToSecretName(t *testing.T) {
tables := map[string]string{
@@ -18,3 +22,24 @@ func TestRepoURLToSecretName(t *testing.T) {
}
}
}
func Test_getRepositoryCredentialIndex(t *testing.T) {
argoCDSettings := settings.ArgoCDSettings{
RepositoryCredentials: []settings.RepoCredentials{{URL: "http://known"}},
}
tests := []struct {
name string
repoURL string
want int
}{
{"TestNotFound", "", -1},
{"TestFoundFound", "http://known/repo", 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getRepositoryCredentialIndex(&argoCDSettings, tt.repoURL); got != tt.want {
t.Errorf("getRepositoryCredentialIndex() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -8,7 +8,7 @@ import (
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
git "gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/config"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/transport"
@@ -47,9 +47,14 @@ func NewFactory() ClientFactory {
return &factory{}
}
func (f *factory) NewClient(repoURL, path, username, password, sshPrivateKey string, insecureIgnoreHostKey bool) (Client, error) {
func (f *factory) NewClient(rawRepoURL, path, username, password, sshPrivateKey string, insecureIgnoreHostKey bool) (Client, error) {
var sshUser string
if isSSH, user := IsSSHURL(rawRepoURL); isSSH {
sshUser = user
}
clnt := nativeGitClient{
repoURL: repoURL,
repoURL: rawRepoURL,
root: path,
}
if sshPrivateKey != "" {
@@ -57,7 +62,7 @@ func (f *factory) NewClient(repoURL, path, username, password, sshPrivateKey str
if err != nil {
return nil, err
}
auth := &ssh2.PublicKeys{User: "git", Signer: signer}
auth := &ssh2.PublicKeys{User: sshUser, Signer: signer}
if insecureIgnoreHostKey {
auth.HostKeyCallback = ssh.InsecureIgnoreHostKey()
}

View File

@@ -22,7 +22,10 @@ func removeSuffix(s, suffix string) string {
return s
}
var commitSHARegex = regexp.MustCompile("^[0-9A-Fa-f]{40}$")
var (
commitSHARegex = regexp.MustCompile("^[0-9A-Fa-f]{40}$")
sshURLRegex = regexp.MustCompile("^(ssh://)?([^/@:]*?)@.*")
)
// IsCommitSHA returns whether or not a string is a 40 character SHA-1
func IsCommitSHA(sha string) bool {
@@ -47,7 +50,7 @@ func SameURL(leftRepo, rightRepo string) bool {
// and should not be considered stable from release to release
func NormalizeGitURL(repo string) string {
repo = strings.ToLower(strings.TrimSpace(repo))
if IsSSHURL(repo) {
if yes, _ := IsSSHURL(repo); yes {
repo = ensurePrefix(repo, "ssh://")
}
repo = removeSuffix(repo, ".git")
@@ -60,8 +63,12 @@ func NormalizeGitURL(repo string) string {
}
// IsSSHURL returns true if supplied URL is SSH URL
func IsSSHURL(url string) bool {
return strings.HasPrefix(url, "git@") || strings.HasPrefix(url, "ssh://")
func IsSSHURL(url string) (bool, string) {
matches := sshURLRegex.FindStringSubmatch(url)
if len(matches) > 2 {
return true, matches[2]
}
return false, ""
}
// TestRepo tests if a repo exists and is accessible with the given credentials

View File

@@ -73,10 +73,21 @@ func TestIsSSHURL(t *testing.T) {
"ssh://git@github.com:test.git": true,
}
for k, v := range data {
assert.Equal(t, v, IsSSHURL(k))
isSSH, _ := IsSSHURL(k)
assert.Equal(t, v, isSSH)
}
}
func TestIsSSHURLUserName(t *testing.T) {
isSSH, user := IsSSHURL("ssh://john@john-server.org:29418/project")
assert.True(t, isSSH)
assert.Equal(t, "john", user)
isSSH, user = IsSSHURL("john@john-server.org:29418/project")
assert.True(t, isSSH)
assert.Equal(t, "john", user)
}
func TestSameURL(t *testing.T) {
data := map[string]string{
"git@GITHUB.com:argoproj/test": "git@github.com:argoproj/test.git",

View File

@@ -149,3 +149,12 @@ func TestTLS(address string) (*TLSTestResult, error) {
}
return nil, err
}
func WithTimeout(duration time.Duration) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
clientDeadline := time.Now().Add(duration)
ctx, cancel := context.WithDeadline(ctx, clientDeadline)
defer cancel()
return invoker(ctx, method, req, reply, cc, opts...)
}
}

View File

@@ -21,7 +21,7 @@ import (
)
// SetApplicationHealth updates the health statuses of all resources performed in the comparison
func SetApplicationHealth(resStatuses []appv1.ResourceStatus, liveObjs []*unstructured.Unstructured, resourceOverrides map[string]appv1.ResourceOverride) (*appv1.HealthStatus, error) {
func SetApplicationHealth(resStatuses []appv1.ResourceStatus, liveObjs []*unstructured.Unstructured, resourceOverrides map[string]appv1.ResourceOverride, filter func(obj *unstructured.Unstructured) bool) (*appv1.HealthStatus, error) {
var savedErr error
appHealth := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
for i, liveObj := range liveObjs {
@@ -30,9 +30,11 @@ func SetApplicationHealth(resStatuses []appv1.ResourceStatus, liveObjs []*unstru
if liveObj == nil {
resHealth = &appv1.HealthStatus{Status: appv1.HealthStatusMissing}
} else {
resHealth, err = GetResourceHealth(liveObj, resourceOverrides)
if err != nil && savedErr == nil {
savedErr = err
if filter(liveObj) {
resHealth, err = GetResourceHealth(liveObj, resourceOverrides)
if err != nil && savedErr == nil {
savedErr = err
}
}
}
if resHealth != nil {

View File

@@ -118,13 +118,17 @@ func TestSetApplicationHealth(t *testing.T) {
&runningPod,
&failedJob,
}
healthStatus, err := SetApplicationHealth(resources, liveObjs, nil)
healthStatus, err := SetApplicationHealth(resources, liveObjs, nil, func(obj *unstructured.Unstructured) bool {
return true
})
assert.NoError(t, err)
assert.Equal(t, appv1.HealthStatusDegraded, healthStatus.Status)
// now mark the job as a hook and retry. it should ignore the hook and consider the app healthy
failedJob.SetAnnotations(map[string]string{common.AnnotationKeyHook: "PreSync"})
healthStatus, err = SetApplicationHealth(resources, liveObjs, nil)
healthStatus, err = SetApplicationHealth(resources, liveObjs, nil, func(obj *unstructured.Unstructured) bool {
return true
})
assert.NoError(t, err)
assert.Equal(t, appv1.HealthStatusHealthy, healthStatus.Status)

View File

@@ -31,23 +31,30 @@ func GetField(claims jwtgo.MapClaims, fieldName string) string {
return ""
}
// GetGroups extracts the groups from a claims
func GetGroups(claims jwtgo.MapClaims) []string {
// GetScopeValues extracts the values of specified scopes from the claims
func GetScopeValues(claims jwtgo.MapClaims, scopes []string) []string {
groups := make([]string, 0)
groupsIf, ok := claims["groups"]
if !ok {
return groups
}
groupIfList, ok := groupsIf.([]interface{})
if !ok {
return groups
}
for _, groupIf := range groupIfList {
group, ok := groupIf.(string)
if ok {
groups = append(groups, group)
for i := range scopes {
scopeIf, ok := claims[scopes[i]]
if !ok {
continue
}
switch val := scopeIf.(type) {
case []interface{}:
for _, groupIf := range val {
group, ok := groupIf.(string)
if ok {
groups = append(groups, group)
}
}
case []string:
groups = append(groups, val...)
case string:
groups = append(groups, val)
}
}
return groups
}

21
util/jwt/jwt_test.go Normal file
View File

@@ -0,0 +1,21 @@
package jwt
import (
"testing"
jwt "github.com/dgrijalva/jwt-go"
"github.com/stretchr/testify/assert"
)
func TestGetSingleStringScope(t *testing.T) {
claims := jwt.MapClaims{"groups": "my-org:my-team"}
groups := GetScopeValues(claims, []string{"groups"})
assert.Contains(t, groups, "my-org:my-team")
}
func TestGetMultipleListScopes(t *testing.T) {
claims := jwt.MapClaims{"groups1": []string{"my-org:my-team1"}, "groups2": []string{"my-org:my-team2"}}
groups := GetScopeValues(claims, []string{"groups1", "groups2"})
assert.Contains(t, groups, "my-org:my-team1")
assert.Contains(t, groups, "my-org:my-team2")
}

View File

@@ -80,9 +80,11 @@ func filterAPIResources(config *rest.Config, resourceFilter ResourceFilter, filt
gv = schema.GroupVersion{}
}
for _, apiResource := range apiResourcesList.APIResources {
if resourceFilter.IsExcludedResource(gv.Group, apiResource.Kind, config.Host) {
continue
}
if _, ok := isObsoleteExtensionsGroupKind(gv.Group, apiResource.Kind); ok &&
// Edge case for deprecated Ingress kind.
!(gv.Group == "extensions" && apiResource.Kind == IngressKind && version.LessThan(ingressDeprecationVersion)) {

View File

@@ -30,4 +30,4 @@ func TestConvertToVersion(t *testing.T) {
gvk = newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
}
}

View File

@@ -10,6 +10,8 @@ import (
"strings"
"time"
v1 "k8s.io/api/core/v1"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/equality"
@@ -101,6 +103,16 @@ func GetResourceKey(obj *unstructured.Unstructured) ResourceKey {
return NewResourceKey(gvk.Group, gvk.Kind, obj.GetNamespace(), obj.GetName())
}
func GetObjectRef(obj *unstructured.Unstructured) v1.ObjectReference {
return v1.ObjectReference{
UID: obj.GetUID(),
APIVersion: obj.GetAPIVersion(),
Kind: obj.GetKind(),
Name: obj.GetName(),
Namespace: obj.GetNamespace(),
}
}
// TestConfig tests to make sure the REST config is usable
func TestConfig(config *rest.Config) error {
kubeclientset, err := kubernetes.NewForConfig(config)

View File

@@ -26,7 +26,7 @@ type ImageTag struct {
func newImageTag(image Image) ImageTag {
parts := strings.Split(image, ":")
if len(parts) > 0 {
if len(parts) > 1 {
return ImageTag{Name: parts[0], Value: parts[1]}
} else {
return ImageTag{Name: parts[0], Value: "latest"}

View File

@@ -134,3 +134,13 @@ func TestPrivateRemoteBase(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, objs, 2)
}
func TestNewImageTag(t *testing.T) {
tag := newImageTag(Image("busybox"))
assert.Equal(t, tag.Name, "busybox")
assert.Equal(t, tag.Value, "latest")
tag = newImageTag(Image("k8s.gcr.io/nginx-slim:0.8"))
assert.Equal(t, tag.Name, "k8s.gcr.io/nginx-slim")
assert.Equal(t, tag.Value, "0.8")
}

View File

@@ -62,6 +62,13 @@ type ClientApp struct {
cache *cache.Cache
}
func GetScopesOrDefault(scopes []string) []string {
if len(scopes) == 0 {
return []string{"openid", "profile", "email", "groups"}
}
return scopes
}
// NewClientApp will register the Argo CD client app (either via Dex or external OIDC) and return an
// object which has HTTP handlers for handling the HTTP responses for login and callback
func NewClientApp(settings *settings.ArgoCDSettings, cache *cache.Cache, dexServerAddr string) (*ClientApp, error) {
@@ -93,7 +100,7 @@ func NewClientApp(settings *settings.ArgoCDSettings, cache *cache.Cache, dexServ
ExpectContinueTimeout: 1 * time.Second,
},
}
if settings.DexConfig != "" {
if settings.DexConfig != "" && settings.OIDCConfigRAW == "" {
a.client.Transport = dex.NewDexRewriteURLRoundTripper(dexServerAddr, a.client.Transport)
}
if os.Getenv(common.EnvVarSSODebug) == "1" {
@@ -157,8 +164,11 @@ func (a *ClientApp) HandleLogin(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
scopes := []string{"openid", "profile", "email", "groups"}
oauth2Config, err := a.oauth2Config(scopes)
scopes := make([]string, 0)
if config := a.settings.OIDCConfig(); config != nil {
scopes = config.RequestedScopes
}
oauth2Config, err := a.oauth2Config(GetScopesOrDefault(scopes))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@@ -28,6 +28,7 @@ import (
const (
ConfigMapPolicyCSVKey = "policy.csv"
ConfigMapPolicyDefaultKey = "policy.default"
ConfigMapScopesKey = "scopes"
defaultRBACSyncPeriod = 10 * time.Minute
)
@@ -171,29 +172,29 @@ func (e *Enforcer) newInformer() cache.SharedIndexInformer {
}
// RunPolicyLoader runs the policy loader which watches policy updates from the configmap and reloads them
func (e *Enforcer) RunPolicyLoader(ctx context.Context) error {
func (e *Enforcer) RunPolicyLoader(ctx context.Context, onUpdated func(cm *apiv1.ConfigMap) error) error {
cm, err := e.clientset.CoreV1().ConfigMaps(e.namespace).Get(e.configmap, metav1.GetOptions{})
if err != nil {
if !apierr.IsNotFound(err) {
return err
}
} else {
err = e.syncUpdate(cm)
err = e.syncUpdate(cm, onUpdated)
if err != nil {
return err
}
}
e.runInformer(ctx)
e.runInformer(ctx, onUpdated)
return nil
}
func (e *Enforcer) runInformer(ctx context.Context) {
func (e *Enforcer) runInformer(ctx context.Context, onUpdated func(cm *apiv1.ConfigMap) error) {
cmInformer := e.newInformer()
cmInformer.AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if cm, ok := obj.(*apiv1.ConfigMap); ok {
err := e.syncUpdate(cm)
err := e.syncUpdate(cm, onUpdated)
if err != nil {
log.Error(err)
} else {
@@ -207,7 +208,7 @@ func (e *Enforcer) runInformer(ctx context.Context) {
if oldCM.ResourceVersion == newCM.ResourceVersion {
return
}
err := e.syncUpdate(newCM)
err := e.syncUpdate(newCM, onUpdated)
if err != nil {
log.Error(err)
} else {
@@ -222,12 +223,15 @@ func (e *Enforcer) runInformer(ctx context.Context) {
}
// syncUpdate updates the enforcer
func (e *Enforcer) syncUpdate(cm *apiv1.ConfigMap) error {
func (e *Enforcer) syncUpdate(cm *apiv1.ConfigMap, onUpdated func(cm *apiv1.ConfigMap) error) error {
e.SetDefaultRole(cm.Data[ConfigMapPolicyDefaultKey])
policyCSV, ok := cm.Data[ConfigMapPolicyCSVKey]
if !ok {
policyCSV = ""
}
if err := onUpdated(cm); err != nil {
return err
}
return e.SetUserPolicy(policyCSV)
}

View File

@@ -20,6 +20,12 @@ const (
fakeNamespace = "fake-ns"
)
var (
noOpUpdate = func(cm *apiv1.ConfigMap) error {
return nil
}
)
func fakeConfigMap(policy ...string) *apiv1.ConfigMap {
cm := apiv1.ConfigMap{
TypeMeta: metav1.TypeMeta{
@@ -42,7 +48,7 @@ func fakeConfigMap(policy ...string) *apiv1.ConfigMap {
func TestBuiltinPolicyEnforcer(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil)
err := enf.syncUpdate(fakeConfigMap())
err := enf.syncUpdate(fakeConfigMap(), noOpUpdate)
assert.Nil(t, err)
// Without setting builtin policy, this should fail
@@ -85,7 +91,9 @@ func TestPolicyInformer(t *testing.T) {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go enf.runInformer(ctx)
go enf.runInformer(ctx, func(cm *apiv1.ConfigMap) error {
return nil
})
loaded := false
for i := 1; i <= 20; i++ {
@@ -99,7 +107,7 @@ func TestPolicyInformer(t *testing.T) {
// update the configmap and update policy
delete(cm.Data, ConfigMapPolicyCSVKey)
err := enf.syncUpdate(cm)
err := enf.syncUpdate(cm, noOpUpdate)
assert.Nil(t, err)
assert.False(t, enf.Enforce("admin", "applications", "delete", "foo/bar"))
}
@@ -207,7 +215,7 @@ g, alice, role:foo-readonly
func TestDefaultRole(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil)
err := enf.syncUpdate(fakeConfigMap())
err := enf.syncUpdate(fakeConfigMap(), noOpUpdate)
assert.Nil(t, err)
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
@@ -221,7 +229,7 @@ func TestDefaultRole(t *testing.T) {
func TestURLAsObjectName(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil)
err := enf.syncUpdate(fakeConfigMap())
err := enf.syncUpdate(fakeConfigMap(), noOpUpdate)
assert.Nil(t, err)
policy := `
p, alice, repositories, *, foo/*, allow
@@ -321,7 +329,7 @@ func TestClaimsEnforcerFunc(t *testing.T) {
func TestDefaultRoleWithRuntimePolicy(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil)
err := enf.syncUpdate(fakeConfigMap())
err := enf.syncUpdate(fakeConfigMap(), noOpUpdate)
assert.Nil(t, err)
runtimePolicy := assets.BuiltinPolicyCSV
assert.False(t, enf.EnforceRuntimePolicy(runtimePolicy, "bob", "applications", "get", "foo/bar"))
@@ -334,7 +342,7 @@ func TestDefaultRoleWithRuntimePolicy(t *testing.T) {
func TestClaimsEnforcerFuncWithRuntimePolicy(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil)
err := enf.syncUpdate(fakeConfigMap())
err := enf.syncUpdate(fakeConfigMap(), noOpUpdate)
assert.Nil(t, err)
runtimePolicy := assets.BuiltinPolicyCSV
claims := jwt.StandardClaims{
@@ -352,7 +360,7 @@ func TestInvalidRuntimePolicy(t *testing.T) {
cm := fakeConfigMap()
kubeclientset := fake.NewSimpleClientset(cm)
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil)
err := enf.syncUpdate(fakeConfigMap())
err := enf.syncUpdate(fakeConfigMap(), noOpUpdate)
assert.Nil(t, err)
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
assert.True(t, enf.EnforceRuntimePolicy("", "admin", "applications", "update", "foo/bar"))
@@ -383,7 +391,7 @@ func TestValidatePolicy(t *testing.T) {
func TestEnforceErrorMessage(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil)
err := enf.syncUpdate(fakeConfigMap())
err := enf.syncUpdate(fakeConfigMap(), noOpUpdate)
assert.Nil(t, err)
err = enf.EnforceErr("admin", "applications", "get", "foo/bar")

View File

@@ -5,13 +5,13 @@ import (
log "github.com/sirupsen/logrus"
)
type ExcludedResource struct {
type FilteredResource struct {
APIGroups []string `json:"apiGroups,omitempty"`
Kinds []string `json:"kinds,omitempty"`
Clusters []string `json:"clusters,omitempty"`
}
func (r ExcludedResource) matchGroup(apiGroup string) bool {
func (r FilteredResource) matchGroup(apiGroup string) bool {
for _, excludedApiGroup := range r.APIGroups {
if match(excludedApiGroup, apiGroup) {
return true
@@ -29,7 +29,7 @@ func match(pattern, text string) bool {
return compiledGlob.Match(text)
}
func (r ExcludedResource) matchKind(kind string) bool {
func (r FilteredResource) matchKind(kind string) bool {
for _, excludedKind := range r.Kinds {
if excludedKind == "*" || excludedKind == kind {
return true
@@ -38,7 +38,7 @@ func (r ExcludedResource) matchKind(kind string) bool {
return len(r.Kinds) == 0
}
func (r ExcludedResource) matchCluster(cluster string) bool {
func (r FilteredResource) matchCluster(cluster string) bool {
for _, excludedCluster := range r.Clusters {
if match(excludedCluster, cluster) {
return true
@@ -47,6 +47,6 @@ func (r ExcludedResource) matchCluster(cluster string) bool {
return len(r.Clusters) == 0
}
func (r ExcludedResource) Match(apiGroup, kind, cluster string) bool {
func (r FilteredResource) Match(apiGroup, kind, cluster string) bool {
return r.matchGroup(apiGroup) && r.matchKind(kind) && r.matchCluster(cluster)
}

View File

@@ -12,28 +12,28 @@ func TestExcludeResource(t *testing.T) {
cluster := "baz.com"
// matches with missing values
assert.True(t, ExcludedResource{Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, ExcludedResource{APIGroups: []string{apiGroup}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, ExcludedResource{APIGroups: []string{apiGroup}, Kinds: []string{"*"}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{"*"}}.Match(apiGroup, kind, cluster))
// simple matches
assert.True(t, ExcludedResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, ExcludedResource{APIGroups: []string{"*.com"}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, ExcludedResource{APIGroups: []string{apiGroup}, Kinds: []string{"*"}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, ExcludedResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{"*.com"}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{"*.com"}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{"*"}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{"*.com"}}.Match(apiGroup, kind, cluster))
// negative matches
assert.False(t, ExcludedResource{APIGroups: []string{""}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.False(t, ExcludedResource{APIGroups: []string{apiGroup}, Kinds: []string{""}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.False(t, ExcludedResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{""}}.Match(apiGroup, kind, cluster))
assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.False(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{""}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.False(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{""}}.Match(apiGroup, kind, cluster))
// complex matches
assert.True(t, ExcludedResource{APIGroups: []string{apiGroup, apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, ExcludedResource{APIGroups: []string{apiGroup}, Kinds: []string{kind, kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, ExcludedResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster, cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{apiGroup, apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind, kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster))
assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster, cluster}}.Match(apiGroup, kind, cluster))
// rubbish patterns
assert.False(t, ExcludedResource{APIGroups: []string{"["}, Kinds: []string{""}, Clusters: []string{""}}.Match("", "", ""))
assert.False(t, ExcludedResource{APIGroups: []string{""}, Kinds: []string{"["}, Clusters: []string{""}}.Match("", "", ""))
assert.False(t, ExcludedResource{APIGroups: []string{""}, Kinds: []string{""}, Clusters: []string{"["}}.Match("", "", ""))
assert.False(t, FilteredResource{APIGroups: []string{"["}, Kinds: []string{""}, Clusters: []string{""}}.Match("", "", ""))
assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{"["}, Clusters: []string{""}}.Match("", "", ""))
assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{""}, Clusters: []string{"["}}.Match("", "", ""))
}

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