mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-18 06:18:57 +01:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
674cf8d6e2 | ||
|
|
c1dba1c764 | ||
|
|
d383379b0c | ||
|
|
adbb1f50c8 | ||
|
|
419165a296 | ||
|
|
19f5d43235 | ||
|
|
2a433f168a | ||
|
|
494e9eeb51 | ||
|
|
cc75f42a10 | ||
|
|
876ff3035e | ||
|
|
4d63d279d6 | ||
|
|
62ae79ab5e | ||
|
|
6f5eaff91f | ||
|
|
c8b4707c55 | ||
|
|
09143f26a4 | ||
|
|
f08375665b | ||
|
|
0901195cd9 | ||
|
|
463e62ff80 | ||
|
|
71e523e776 | ||
|
|
61d0ef7614 | ||
|
|
d54d931f3e | ||
|
|
0c31efd158 | ||
|
|
a325c35320 | ||
|
|
9d56d4fa26 | ||
|
|
707342d707 | ||
|
|
de600c0222 | ||
|
|
937e88a164 | ||
|
|
4f3e5080a5 | ||
|
|
cf74364052 | ||
|
|
5bcd846fa1 | ||
|
|
7af7aaa08f | ||
|
|
ccb64f1c7e |
6
.github/workflows/ci-build.yaml
vendored
6
.github/workflows/ci-build.yaml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
|
||||
env:
|
||||
# Golang version to use across CI steps
|
||||
GOLANG_VERSION: '1.18'
|
||||
GOLANG_VERSION: '1.19.7'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -425,9 +425,9 @@ jobs:
|
||||
git config --global user.email "john.doe@example.com"
|
||||
- name: Pull Docker image required for tests
|
||||
run: |
|
||||
docker pull ghcr.io/dexidp/dex:v2.35.3
|
||||
docker pull ghcr.io/dexidp/dex:v2.36.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:7.0.8-alpine
|
||||
docker pull redis:7.0.11-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
1
.github/workflows/codeql.yml
vendored
1
.github/workflows/codeql.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
# Secrets aren't available for dependabot on push. https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/troubleshooting-the-codeql-workflow#error-403-resource-not-accessible-by-integration-when-using-dependabot
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
- 'cherry-pick-*'
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 19 * * 0'
|
||||
|
||||
2
.github/workflows/image.yaml
vendored
2
.github/workflows/image.yaml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
types: [ labeled, unlabeled, opened, synchronize, reopened ]
|
||||
|
||||
env:
|
||||
GOLANG_VERSION: '1.18'
|
||||
GOLANG_VERSION: '1.19.7'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -12,7 +12,7 @@ on:
|
||||
- "!release-v0*"
|
||||
|
||||
env:
|
||||
GOLANG_VERSION: '1.18'
|
||||
GOLANG_VERSION: '1.19.7'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04
|
||||
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
|
||||
# Also used as the image in CI jobs so needs all dependencies
|
||||
####################################################################################################
|
||||
FROM docker.io/library/golang:1.18 AS builder
|
||||
FROM docker.io/library/golang:1.19.7 AS builder
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
|
||||
@@ -99,7 +99,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
|
||||
####################################################################################################
|
||||
# Argo CD Build stage which performs the actual build of Argo CD binaries
|
||||
####################################################################################################
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.18 AS argocd-build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.19.7 AS argocd-build
|
||||
|
||||
WORKDIR /go/src/github.com/argoproj/argo-cd
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@@ -46,7 +47,6 @@ type addRateLimitingInterface interface {
|
||||
}
|
||||
|
||||
func (h *clusterSecretEventHandler) queueRelatedAppGenerators(q addRateLimitingInterface, object client.Object) {
|
||||
|
||||
// Check for label, lookup all ApplicationSets that might match the cluster, queue them all
|
||||
if object.GetLabels()[generators.ArgoCDSecretTypeLabel] != generators.ArgoCDSecretTypeCluster {
|
||||
return
|
||||
@@ -73,6 +73,40 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(q addRateLimitingI
|
||||
foundClusterGenerator = true
|
||||
break
|
||||
}
|
||||
|
||||
if generator.Matrix != nil {
|
||||
ok, err := nestedGeneratorsHaveClusterGenerator(generator.Matrix.Generators)
|
||||
if err != nil {
|
||||
h.Log.
|
||||
WithFields(log.Fields{
|
||||
"namespace": appSet.GetNamespace(),
|
||||
"name": appSet.GetName(),
|
||||
}).
|
||||
WithError(err).
|
||||
Error("Unable to check if ApplicationSet matrix generators have cluster generator")
|
||||
}
|
||||
if ok {
|
||||
foundClusterGenerator = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if generator.Merge != nil {
|
||||
ok, err := nestedGeneratorsHaveClusterGenerator(generator.Merge.Generators)
|
||||
if err != nil {
|
||||
h.Log.
|
||||
WithFields(log.Fields{
|
||||
"namespace": appSet.GetNamespace(),
|
||||
"name": appSet.GetName(),
|
||||
}).
|
||||
WithError(err).
|
||||
Error("Unable to check if ApplicationSet merge generators have cluster generator")
|
||||
}
|
||||
if ok {
|
||||
foundClusterGenerator = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if foundClusterGenerator {
|
||||
|
||||
@@ -82,3 +116,42 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(q addRateLimitingI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nestedGeneratorsHaveClusterGenerator iterate over provided nested generators to check if they have a cluster generator.
|
||||
func nestedGeneratorsHaveClusterGenerator(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) (bool, error) {
|
||||
for _, generator := range generators {
|
||||
if ok, err := nestedGeneratorHasClusterGenerator(generator); ok || err != nil {
|
||||
return ok, err
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// nestedGeneratorHasClusterGenerator checks if the provided generator has a cluster generator.
|
||||
func nestedGeneratorHasClusterGenerator(nested argoprojiov1alpha1.ApplicationSetNestedGenerator) (bool, error) {
|
||||
if nested.Clusters != nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if nested.Matrix != nil {
|
||||
nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(nested.Matrix)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to get nested matrix generator: %w", err)
|
||||
}
|
||||
if nestedMatrix != nil {
|
||||
return nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
|
||||
}
|
||||
}
|
||||
|
||||
if nested.Merge != nil {
|
||||
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(nested.Merge)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to get nested merge generator: %w", err)
|
||||
}
|
||||
if nestedMerge != nil {
|
||||
return nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -163,7 +164,6 @@ func TestClusterEventHandler(t *testing.T) {
|
||||
{NamespacedName: types.NamespacedName{Namespace: "another-namespace", Name: "my-app-set"}},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "non-argo cd secret should not match",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
@@ -189,6 +189,348 @@ func TestClusterEventHandler(t *testing.T) {
|
||||
},
|
||||
expectedRequests: []reconcile.Request{},
|
||||
},
|
||||
{
|
||||
name: "a matrix generator with a cluster generator should produce a request",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Matrix: &argov1alpha1.MatrixGenerator{
|
||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Clusters: &argov1alpha1.ClusterGenerator{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{{
|
||||
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "a matrix generator with non cluster generator should not match",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Matrix: &argov1alpha1.MatrixGenerator{
|
||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
List: &argov1alpha1.ListGenerator{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{},
|
||||
},
|
||||
{
|
||||
name: "a matrix generator with a nested matrix generator containing a cluster generator should produce a request",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Matrix: &argov1alpha1.MatrixGenerator{
|
||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Matrix: &apiextensionsv1.JSON{
|
||||
Raw: []byte(
|
||||
`{
|
||||
"generators": [
|
||||
{
|
||||
"clusters": {
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"argocd.argoproj.io/secret-type": "cluster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{{
|
||||
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "a matrix generator with a nested matrix generator containing non cluster generator should not match",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Matrix: &argov1alpha1.MatrixGenerator{
|
||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Matrix: &apiextensionsv1.JSON{
|
||||
Raw: []byte(
|
||||
`{
|
||||
"generators": [
|
||||
{
|
||||
"list": {
|
||||
"elements": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{},
|
||||
},
|
||||
{
|
||||
name: "a merge generator with a cluster generator should produce a request",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Merge: &argov1alpha1.MergeGenerator{
|
||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Clusters: &argov1alpha1.ClusterGenerator{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{{
|
||||
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "a matrix generator with non cluster generator should not match",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Merge: &argov1alpha1.MergeGenerator{
|
||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
List: &argov1alpha1.ListGenerator{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{},
|
||||
},
|
||||
{
|
||||
name: "a merge generator with a nested merge generator containing a cluster generator should produce a request",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Merge: &argov1alpha1.MergeGenerator{
|
||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Merge: &apiextensionsv1.JSON{
|
||||
Raw: []byte(
|
||||
`{
|
||||
"generators": [
|
||||
{
|
||||
"clusters": {
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"argocd.argoproj.io/secret-type": "cluster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{{
|
||||
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "a merge generator with a nested merge generator containing non cluster generator should not match",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Merge: &argov1alpha1.MergeGenerator{
|
||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Merge: &apiextensionsv1.JSON{
|
||||
Raw: []byte(
|
||||
`{
|
||||
"generators": [
|
||||
{
|
||||
"list": {
|
||||
"elements": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestMatrixGenerate(t *testing.T) {
|
||||
}
|
||||
|
||||
listGenerator := &argoprojiov1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
|
||||
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url", "templated": "test-{{path.basenameNormalized}}"}`)}},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -50,8 +50,8 @@ func TestMatrixGenerate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url"},
|
||||
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url"},
|
||||
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url", "templated": "test-app1"},
|
||||
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url", "templated": "test-app2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -96,6 +96,25 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
|
||||
// specific case time
|
||||
if currentType == "time.Time" {
|
||||
copy.Field(i).Set(original.Field(i))
|
||||
} else if currentType == "Raw.k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" {
|
||||
var unmarshaled interface{}
|
||||
originalBytes := original.Field(i).Bytes()
|
||||
err := json.Unmarshal(originalBytes, &unmarshaled)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON field: %w", err)
|
||||
}
|
||||
jsonOriginal := reflect.ValueOf(&unmarshaled)
|
||||
jsonCopy := reflect.New(jsonOriginal.Type()).Elem()
|
||||
err = r.deeplyReplace(jsonCopy, jsonOriginal, replaceMap, useGoTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to deeply replace JSON field contents: %w", err)
|
||||
}
|
||||
jsonCopyInterface := jsonCopy.Interface().(*interface{})
|
||||
data, err := json.Marshal(jsonCopyInterface)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal templated JSON field: %w", err)
|
||||
}
|
||||
copy.Field(i).Set(reflect.ValueOf(data))
|
||||
} else if err := r.deeplyReplace(copy.Field(i), original.Field(i), replaceMap, useGoTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,13 +9,16 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/cache"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
)
|
||||
|
||||
func NewDashboardCommand() *cobra.Command {
|
||||
var (
|
||||
port int
|
||||
address string
|
||||
port int
|
||||
address string
|
||||
compressionStr string
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "dashboard",
|
||||
@@ -23,7 +26,9 @@ func NewDashboardCommand() *cobra.Command {
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := cmd.Context()
|
||||
|
||||
errors.CheckError(headless.StartLocalServer(ctx, &argocdclient.ClientOptions{Core: true}, initialize.RetrieveContextIfChanged(cmd.Flag("context")), &port, &address))
|
||||
compression, err := cache.CompressionTypeFromString(compressionStr)
|
||||
errors.CheckError(err)
|
||||
errors.CheckError(headless.StartLocalServer(ctx, &argocdclient.ClientOptions{Core: true}, initialize.RetrieveContextIfChanged(cmd.Flag("context")), &port, &address, compression))
|
||||
println(fmt.Sprintf("Argo CD UI is available at http://%s:%d", address, port))
|
||||
<-ctx.Done()
|
||||
},
|
||||
@@ -31,5 +36,6 @@ func NewDashboardCommand() *cobra.Command {
|
||||
initialize.InitCommand(cmd)
|
||||
cmd.Flags().IntVar(&port, "port", common.DefaultPortAPIServer, "Listen on given port")
|
||||
cmd.Flags().StringVar(&address, "address", common.DefaultAddressAPIServer, "Listen on given address")
|
||||
cmd.Flags().StringVar(&compressionStr, "redis-compress", env.StringFromEnv("REDIS_COMPRESSION", string(cache.RedisCompressionNone)), "Enable this if the application controller is configured with redis compression enabled. (possible values: none, gzip)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -165,7 +165,9 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
|
||||
// Get app before creating to see if it is being updated or no change
|
||||
existing, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &app.Name})
|
||||
if grpc.UnwrapGRPCStatus(err).Code() != codes.NotFound {
|
||||
unwrappedError := grpc.UnwrapGRPCStatus(err).Code()
|
||||
// As part of the fix for CVE-2022-41354, the API will return Permission Denied when an app does not exist.
|
||||
if unwrappedError != codes.NotFound && unwrappedError != codes.PermissionDenied {
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,12 @@ import (
|
||||
)
|
||||
|
||||
type forwardCacheClient struct {
|
||||
namespace string
|
||||
context string
|
||||
init sync.Once
|
||||
client cache.CacheClient
|
||||
err error
|
||||
namespace string
|
||||
context string
|
||||
init sync.Once
|
||||
client cache.CacheClient
|
||||
compression cache.RedisCompressionType
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error) error {
|
||||
@@ -58,7 +59,7 @@ func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error)
|
||||
}
|
||||
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort)})
|
||||
c.client = cache.NewRedisCache(redisClient, time.Hour, cache.RedisCompressionNone)
|
||||
c.client = cache.NewRedisCache(redisClient, time.Hour, c.compression)
|
||||
})
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
@@ -139,7 +140,7 @@ func testAPI(ctx context.Context, clientOpts *apiclient.ClientOptions) error {
|
||||
|
||||
// StartLocalServer allows executing command in a headless mode: on the fly starts Argo CD API server and
|
||||
// changes provided client options to use started API server port
|
||||
func StartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOptions, ctxStr string, port *int, address *string) error {
|
||||
func StartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOptions, ctxStr string, port *int, address *string, compression cache.RedisCompressionType) error {
|
||||
flags := pflag.NewFlagSet("tmp", pflag.ContinueOnError)
|
||||
clientConfig := cli.AddKubectlFlagsToSet(flags)
|
||||
startInProcessAPI := clientOpts.Core
|
||||
@@ -200,7 +201,7 @@ func StartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOptions,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr}), time.Hour)
|
||||
appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression}), time.Hour)
|
||||
srv := server.NewServer(ctx, server.ArgoCDServerOpts{
|
||||
EnableGZip: false,
|
||||
Namespace: namespace,
|
||||
@@ -243,7 +244,7 @@ func NewClientOrDie(opts *apiclient.ClientOptions, c *cobra.Command) apiclient.C
|
||||
ctx := c.Context()
|
||||
|
||||
ctxStr := initialize.RetrieveContextIfChanged(c.Flag("context"))
|
||||
err := StartLocalServer(ctx, opts, ctxStr, nil, nil)
|
||||
err := StartLocalServer(ctx, opts, ctxStr, nil, nil, cache.RedisCompressionNone)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -138,7 +138,10 @@ func readProjFromURI(fileURL string, proj *v1alpha1.AppProject) error {
|
||||
} else {
|
||||
err = config.UnmarshalRemoteFile(fileURL, &proj)
|
||||
}
|
||||
return fmt.Errorf("error reading proj from uri: %w", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading proj from uri: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetProjSpecOptions(flags *pflag.FlagSet, spec *v1alpha1.AppProjectSpec, projOpts *ProjectOpts) int {
|
||||
|
||||
@@ -378,7 +378,7 @@ func (s *Service) GetParametersAnnouncement(stream apiclient.ConfigManagementPlu
|
||||
return fmt.Errorf("illegal appPath: out of workDir bound")
|
||||
}
|
||||
|
||||
repoResponse, err := getParametersAnnouncement(bufferedCtx, appPath, s.initConstants.PluginConfig.Spec.Parameters.Static, s.initConstants.PluginConfig.Spec.Parameters.Dynamic)
|
||||
repoResponse, err := getParametersAnnouncement(bufferedCtx, appPath, s.initConstants.PluginConfig.Spec.Parameters.Static, s.initConstants.PluginConfig.Spec.Parameters.Dynamic, metadata.GetEnv())
|
||||
if err != nil {
|
||||
return fmt.Errorf("get parameters announcement error: %w", err)
|
||||
}
|
||||
@@ -390,11 +390,12 @@ func (s *Service) GetParametersAnnouncement(stream apiclient.ConfigManagementPlu
|
||||
return nil
|
||||
}
|
||||
|
||||
func getParametersAnnouncement(ctx context.Context, appDir string, announcements []*repoclient.ParameterAnnouncement, command Command) (*apiclient.ParametersAnnouncementResponse, error) {
|
||||
func getParametersAnnouncement(ctx context.Context, appDir string, announcements []*repoclient.ParameterAnnouncement, command Command, envEntries []*apiclient.EnvEntry) (*apiclient.ParametersAnnouncementResponse, error) {
|
||||
augmentedAnnouncements := announcements
|
||||
|
||||
if len(command.Command) > 0 {
|
||||
stdout, err := runCommand(ctx, command, appDir, os.Environ())
|
||||
env := append(os.Environ(), environ(envEntries)...)
|
||||
stdout, err := runCommand(ctx, command, appDir, env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error executing dynamic parameter output command: %w", err)
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ func Test_getParametersAnnouncement_empty_command(t *testing.T) {
|
||||
Command: []string{"echo"},
|
||||
Args: []string{`[]`},
|
||||
}
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command)
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command, []*apiclient.EnvEntry{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*repoclient.ParameterAnnouncement{{Name: "static-a"}, {Name: "static-b"}}, res.ParameterAnnouncements)
|
||||
}
|
||||
@@ -395,7 +395,7 @@ func Test_getParametersAnnouncement_no_command(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(staticYAML), static)
|
||||
require.NoError(t, err)
|
||||
command := Command{}
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command)
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command, []*apiclient.EnvEntry{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*repoclient.ParameterAnnouncement{{Name: "static-a"}, {Name: "static-b"}}, res.ParameterAnnouncements)
|
||||
}
|
||||
@@ -412,7 +412,7 @@ func Test_getParametersAnnouncement_static_and_dynamic(t *testing.T) {
|
||||
Command: []string{"echo"},
|
||||
Args: []string{`[{"name": "dynamic-a"}, {"name": "dynamic-b"}]`},
|
||||
}
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command)
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command, []*apiclient.EnvEntry{})
|
||||
require.NoError(t, err)
|
||||
expected := []*repoclient.ParameterAnnouncement{
|
||||
{Name: "dynamic-a"},
|
||||
@@ -428,7 +428,7 @@ func Test_getParametersAnnouncement_invalid_json(t *testing.T) {
|
||||
Command: []string{"echo"},
|
||||
Args: []string{`[`},
|
||||
}
|
||||
_, err := getParametersAnnouncement(context.Background(), "", []*repoclient.ParameterAnnouncement{}, command)
|
||||
_, err := getParametersAnnouncement(context.Background(), "", []*repoclient.ParameterAnnouncement{}, command, []*apiclient.EnvEntry{})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unexpected end of JSON input")
|
||||
}
|
||||
@@ -438,7 +438,7 @@ func Test_getParametersAnnouncement_bad_command(t *testing.T) {
|
||||
Command: []string{"exit"},
|
||||
Args: []string{"1"},
|
||||
}
|
||||
_, err := getParametersAnnouncement(context.Background(), "", []*repoclient.ParameterAnnouncement{}, command)
|
||||
_, err := getParametersAnnouncement(context.Background(), "", []*repoclient.ParameterAnnouncement{}, command, []*apiclient.EnvEntry{})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "error executing dynamic parameter output command")
|
||||
}
|
||||
@@ -730,16 +730,17 @@ func TestService_GetParametersAnnouncement(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("successful response", func(t *testing.T) {
|
||||
s, err := NewMockParametersAnnouncementStream("./testdata/kustomize", "./testdata/kustomize", nil)
|
||||
s, err := NewMockParametersAnnouncementStream("./testdata/kustomize", "./testdata/kustomize", []string{"MUST_BE_SET=yep"})
|
||||
require.NoError(t, err)
|
||||
err = service.GetParametersAnnouncement(s)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s.response)
|
||||
require.Len(t, s.response.ParameterAnnouncements, 1)
|
||||
assert.Equal(t, repoclient.ParameterAnnouncement{Name: "test-param", String_: "test-value"}, *s.response.ParameterAnnouncements[0])
|
||||
require.Len(t, s.response.ParameterAnnouncements, 2)
|
||||
assert.Equal(t, repoclient.ParameterAnnouncement{Name: "dynamic-test-param", String_: "yep"}, *s.response.ParameterAnnouncements[0])
|
||||
assert.Equal(t, repoclient.ParameterAnnouncement{Name: "test-param", String_: "test-value"}, *s.response.ParameterAnnouncements[1])
|
||||
})
|
||||
t.Run("out of bounds app", func(t *testing.T) {
|
||||
s, err := NewMockParametersAnnouncementStream("./testdata/kustomize", "./testdata/kustomize", nil)
|
||||
s, err := NewMockParametersAnnouncementStream("./testdata/kustomize", "./testdata/kustomize", []string{"MUST_BE_SET=yep"})
|
||||
require.NoError(t, err)
|
||||
// set a malicious app path on the metadata
|
||||
s.metadataRequest.Request.(*apiclient.AppStreamRequest_Metadata).Metadata.AppRelPath = "../out-of-bounds"
|
||||
@@ -747,6 +748,13 @@ func TestService_GetParametersAnnouncement(t *testing.T) {
|
||||
require.ErrorContains(t, err, "illegal appPath")
|
||||
require.Nil(t, s.response)
|
||||
})
|
||||
t.Run("fails when script fails", func(t *testing.T) {
|
||||
s, err := NewMockParametersAnnouncementStream("./testdata/kustomize", "./testdata/kustomize", []string{"WRONG_ENV_VAR=oops"})
|
||||
require.NoError(t, err)
|
||||
err = service.GetParametersAnnouncement(s)
|
||||
require.ErrorContains(t, err, "error executing dynamic parameter output command")
|
||||
require.Nil(t, s.response)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getCommandArgsToLog(t *testing.T) {
|
||||
|
||||
@@ -5,9 +5,15 @@ metadata:
|
||||
spec:
|
||||
version: v1.0
|
||||
init:
|
||||
command: [kustomize, version]
|
||||
command: [sh, -c]
|
||||
args:
|
||||
- |
|
||||
kustomize version
|
||||
generate:
|
||||
command: [sh, -c, "kustomize build"]
|
||||
command: [sh, -c]
|
||||
args:
|
||||
- |
|
||||
kustomize build
|
||||
discover:
|
||||
find:
|
||||
command: [sh, -c, find . -name kustomization.yaml]
|
||||
@@ -16,3 +22,12 @@ spec:
|
||||
static:
|
||||
- name: test-param
|
||||
string: test-value
|
||||
dynamic:
|
||||
command: [sh, -c]
|
||||
args:
|
||||
- |
|
||||
# Make sure env vars are making it to the plugin.
|
||||
if [ -z "$MUST_BE_SET" ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "[{\"name\": \"dynamic-test-param\", \"string\": \"$MUST_BE_SET\"}]"
|
||||
|
||||
7
controller/cache/cache.go
vendored
7
controller/cache/cache.go
vendored
@@ -488,10 +488,11 @@ func (c *liveStateCache) getSyncedCluster(server string) (clustercache.ClusterCa
|
||||
func (c *liveStateCache) invalidate(cacheSettings cacheSettings) {
|
||||
log.Info("invalidating live state cache")
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.cacheSettings = cacheSettings
|
||||
for _, clust := range c.clusters {
|
||||
clusters := c.clusters
|
||||
c.lock.Unlock()
|
||||
|
||||
for _, clust := range clusters {
|
||||
clust.Invalidate(clustercache.SetSettings(cacheSettings.clusterSettings))
|
||||
}
|
||||
log.Info("live state cache invalidated")
|
||||
|
||||
BIN
docs/assets/extra_info-1.png
Normal file
BIN
docs/assets/extra_info-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/assets/extra_info-2.png
Normal file
BIN
docs/assets/extra_info-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
BIN
docs/assets/extra_info.png
Normal file
BIN
docs/assets/extra_info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
@@ -9,16 +9,6 @@ setTimeout(function() {
|
||||
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>"
|
||||
caret.classList.add('dropdown-caret')
|
||||
div.querySelector('.rst-current-version').appendChild(caret);
|
||||
div.querySelector('.rst-current-version').addEventListener('click', function() {
|
||||
const classes = container.className.split(' ');
|
||||
const index = classes.indexOf('shift-up');
|
||||
if (index === -1) {
|
||||
classes.push('shift-up');
|
||||
} else {
|
||||
classes.splice(index, 1);
|
||||
}
|
||||
container.className = classes.join(' ');
|
||||
});
|
||||
}
|
||||
|
||||
var CSSLink = document.createElement('link');
|
||||
|
||||
@@ -146,7 +146,12 @@ spec:
|
||||
# name: in-cluster
|
||||
# The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace
|
||||
namespace: guestbook
|
||||
|
||||
|
||||
# Extra information to show in the Argo CD Application details tab
|
||||
info:
|
||||
- name: 'Example:'
|
||||
value: 'https://example.com'
|
||||
|
||||
# Sync policy
|
||||
syncPolicy:
|
||||
automated: # automated sync by default retries failed attempts 5 times with following delays between attempts ( 5s, 10s, 20s, 40s, 80s ); retry controlled using `retry` field.
|
||||
|
||||
@@ -74,7 +74,7 @@ kind: Kustomization
|
||||
|
||||
namespace: argocd
|
||||
resources:
|
||||
- github.com/argoproj/argo-cd/manifests/ha?ref=v2.6.2
|
||||
- github.com/argoproj/argo-cd/manifests/ha/base?ref=v2.6.2
|
||||
```
|
||||
|
||||
## Helm
|
||||
|
||||
@@ -64,7 +64,7 @@ See [Web-based Terminal](web_based_terminal.md) for more info.
|
||||
|
||||
#### The `applicationsets` resource
|
||||
|
||||
[ApplicationSets](applicationset) provide a declarative way to automatically create/update/delete Applications.
|
||||
[ApplicationSets](applicationset/index.md) provide a declarative way to automatically create/update/delete Applications.
|
||||
|
||||
Granting `applicationsets, create` effectively grants the ability to create Applications. While it doesn't allow the
|
||||
user to create Applications directly, they can create Applications via an ApplicationSet.
|
||||
|
||||
@@ -184,3 +184,16 @@ This note is just for clarity. No action is required.
|
||||
|
||||
We [expected](../upgrading/2.3-2.4.md#enable-logs-rbac-enforcement) to enable logs RBAC enforcement by default in 2.5.
|
||||
We have decided not to do that in the 2.x series due to disruption for users of [Project Roles](../../user-guide/projects.md#project-roles).
|
||||
|
||||
## `argocd app create` for old CLI versions fails with API version >=2.5.16
|
||||
|
||||
Starting with Argo CD 2.5.16, the API returns `PermissionDenied` instead of `NotFound` for Application `GET` requests if
|
||||
the Application does not exist.
|
||||
|
||||
The Argo CD CLI before versions starting with version 2.5.0-rc1 and before versions 2.5.16 and 2.6.7 does a `GET`
|
||||
request before the `POST` request in `argocd app create`. The command does not gracefully handle the `PermissionDenied`
|
||||
response and will therefore fail to create/update the Application.
|
||||
|
||||
To solve the issue, upgrade the CLI to at least 2.5.16, or 2.6.7.
|
||||
|
||||
CLIs older than 2.5.0-rc1 are unaffected.
|
||||
|
||||
@@ -81,3 +81,17 @@ name. Argo CD v2.6 introduces support for specifying sidecar plugins by name.
|
||||
|
||||
Removal of argocd-cm plugin support has been delayed until 2.7 to provide a transition time for users who need to
|
||||
specify plugins by name.
|
||||
|
||||
## `argocd app create` for old CLI versions fails with API version >=2.6.7
|
||||
|
||||
Starting with Argo CD 2.6.7, the API returns `PermissionDenied` instead of `NotFound` for Application `GET` requests if
|
||||
the Application does not exist.
|
||||
|
||||
The Argo CD CLI before versions starting with version 2.5.0-rc1 and before versions 2.5.16 and 2.6.7 does a `GET`
|
||||
request before the `POST` request in `argocd app create`. The command does not gracefully handle the `PermissionDenied`
|
||||
response and will therefore fail to create/update the Application.
|
||||
|
||||
To solve the issue, upgrade the CLI to at least 2.5.16, or 2.6.7.
|
||||
|
||||
CLIs older than 2.5.0-rc1 are unaffected.
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ argocd admin dashboard [flags]
|
||||
--password string Password for basic authentication to the API server
|
||||
--port int Listen on given port (default 8080)
|
||||
--proxy-url string If provided, this URL will be used to connect via proxy
|
||||
--redis-compress string Enable this if the application controller is configured with redis compression enabled. (possible values: none, gzip) (default "none")
|
||||
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
|
||||
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
|
||||
--token string Bearer token for authentication to the API server
|
||||
|
||||
28
docs/user-guide/extra_info.md
Normal file
28
docs/user-guide/extra_info.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Add extra Application info
|
||||
|
||||
You can add additional information to an Application on your ArgoCD dashboard.
|
||||
If you wish to add clickable links, see [Add external URL](https://argo-cd.readthedocs.io/en/stable/user-guide/external-url/).
|
||||
|
||||
This is done by providing the 'info' field a key-value in your Application manifest.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
project: argo-demo
|
||||
source:
|
||||
repoURL: 'https://demo'
|
||||
path: argo-demo
|
||||
destination:
|
||||
server: https://demo
|
||||
namespace: argo-demo
|
||||
info:
|
||||
- name: Example:
|
||||
value: >-
|
||||
https://example.com
|
||||
```
|
||||

|
||||
|
||||
The additional information will be visible on the ArgoCD Application details page.
|
||||
|
||||

|
||||
|
||||

|
||||
@@ -115,7 +115,7 @@ Argo CD supports many (most?) Helm hooks by mapping the Helm annotations onto Ar
|
||||
| `helm.sh/hook: test-success` | Not supported. No equivalent in Argo CD. |
|
||||
| `helm.sh/hook: test-failure` | Not supported. No equivalent in Argo CD. |
|
||||
| `helm.sh/hook-delete-policy` | Supported. See also `argocd.argoproj.io/hook-delete-policy`). |
|
||||
| `helm.sh/hook-delete-timeout` | No supported. Never used in Helm stable |
|
||||
| `helm.sh/hook-delete-timeout` | Not supported. Never used in Helm stable |
|
||||
| `helm.sh/hook-weight` | Supported as equivalent to `argocd.argoproj.io/sync-wave`. |
|
||||
|
||||
Unsupported hooks are ignored. In Argo CD, hooks are created by using `kubectl apply`, rather than `kubectl create`. This means that if the hook is named and already exists, it will not change unless you have annotated it with `before-hook-creation`.
|
||||
|
||||
@@ -321,7 +321,7 @@ stringData:
|
||||
|
||||
All the examples above talk about Git repositories, but the same principles apply to clusters as well.
|
||||
|
||||
With cluster-scoped clusters we can also restrict projects to only allow applications whose destinations belong to the
|
||||
With project-scoped clusters we can also restrict projects to only allow applications whose destinations belong to the
|
||||
same project. The default behavior allows for applications to be installed onto clusters which are not a part of the same
|
||||
project, as the example below demonstrates:
|
||||
|
||||
|
||||
16
go.mod
16
go.mod
@@ -8,8 +8,9 @@ require (
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
|
||||
github.com/alicebob/miniredis/v2 v2.23.1
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20221208230615-917f5a0f16d5
|
||||
github.com/argoproj/notifications-engine v0.3.1-0.20221203221941-490d98afd1d6
|
||||
github.com/antonmedv/expr v1.9.0
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20230512020822-b4dd8b8c3976
|
||||
github.com/argoproj/notifications-engine v0.4.1-0.20230228182525-f754726f03da
|
||||
github.com/argoproj/pkg v0.13.7-0.20221221191914-44694015343d
|
||||
github.com/aws/aws-sdk-go v1.44.164
|
||||
github.com/bombsimon/logrusr/v2 v2.0.1
|
||||
@@ -73,7 +74,7 @@ require (
|
||||
github.com/whilp/git-urls v0.0.0-20191001220047-6db9661140c0
|
||||
github.com/xanzy/go-gitlab v0.60.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
golang.org/x/crypto v0.3.0
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
@@ -106,8 +107,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/antonmedv/expr v1.9.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/gosimple/slug v1.13.1
|
||||
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
@@ -136,8 +136,6 @@ require (
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Microsoft/go-winio v0.4.17 // indirect
|
||||
github.com/PagerDuty/go-pagerduty v1.6.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
|
||||
@@ -181,7 +179,7 @@ require (
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-version v1.2.1 // indirect
|
||||
github.com/huandu/xstrings v1.3.1 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.4 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
@@ -217,7 +215,7 @@ require (
|
||||
github.com/russross/blackfriday v1.5.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/slack-go/slack v0.10.1 // indirect
|
||||
github.com/slack-go/slack v0.12.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/vmihailenco/go-tinylfu v0.2.1 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
|
||||
|
||||
32
go.sum
32
go.sum
@@ -84,15 +84,10 @@ github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
@@ -137,10 +132,10 @@ github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/appscode/go v0.0.0-20191119085241-0887d8ec2ecc/go.mod h1:OawnOmAL4ZX3YaPdN+8HTNwBveT1jMsqP74moa9XUbE=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20221208230615-917f5a0f16d5 h1:iRpHi7X3q9G55KTaMjxKicgNnS2blFHaEfOOgsmP8lE=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20221208230615-917f5a0f16d5/go.mod h1:WpA/B7tgwfz+sdNE3LqrTrb7ArEY1FOPI2pAGI0hfPc=
|
||||
github.com/argoproj/notifications-engine v0.3.1-0.20221203221941-490d98afd1d6 h1:b92Xft7MQv/SP56FW08zt5CMTE1rySH8UPDKOAgSzOM=
|
||||
github.com/argoproj/notifications-engine v0.3.1-0.20221203221941-490d98afd1d6/go.mod h1:pgPU59KCsBOMhyw9amRWPoSuBmUWvx3Xsc5r0mUriLg=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20230512020822-b4dd8b8c3976 h1:8i12dOcimhwrJxUznzZR/NW4JpIL5DXZjkI3Bl3yh38=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20230512020822-b4dd8b8c3976/go.mod h1:WpA/B7tgwfz+sdNE3LqrTrb7ArEY1FOPI2pAGI0hfPc=
|
||||
github.com/argoproj/notifications-engine v0.4.1-0.20230228182525-f754726f03da h1:Vf9xvHcXn4TP/nLIfWn+TaC521V9fpz/DwRP6uEeVR8=
|
||||
github.com/argoproj/notifications-engine v0.4.1-0.20230228182525-f754726f03da/go.mod h1:05koR0gE/O0i5YDbidg1dpr76XitK4DJveh+dIAq6e8=
|
||||
github.com/argoproj/pkg v0.13.7-0.20221221191914-44694015343d h1:7fXEKF3OQ9i1PrgieA6FLrXOL3UAKyiotomn0RHevds=
|
||||
github.com/argoproj/pkg v0.13.7-0.20221221191914-44694015343d/go.mod h1:RKjj5FJ6KxtktOY49GJSG49qO6Z4lH7RnrVCaS3tf18=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
@@ -642,8 +637,8 @@ github.com/heketi/heketi v10.3.0+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva
|
||||
github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4=
|
||||
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@@ -1011,8 +1006,8 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c h1:fyKiXKO1/I/B6Y2U8T7WdQGWzwehOuGIrljPtt7YTTI=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/slack-go/slack v0.10.1 h1:BGbxa0kMsGEvLOEoZmYs8T1wWfoZXwmQFBb6FgYCXUA=
|
||||
github.com/slack-go/slack v0.10.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ=
|
||||
github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw=
|
||||
github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
@@ -1217,7 +1212,6 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
@@ -1226,8 +1220,9 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1340,6 +1335,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -1487,11 +1483,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
@@ -37,7 +37,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: dex
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
command: [/shared/argocd-dex, rundex]
|
||||
env:
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.6.6
|
||||
newTag: v2.6.8
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
serviceAccountName: argocd-redis
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "--save"
|
||||
|
||||
@@ -15557,7 +15557,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -15639,7 +15639,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -15821,7 +15821,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -15873,7 +15873,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -16080,7 +16080,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.6.6
|
||||
newTag: v2.6.8
|
||||
|
||||
@@ -11,7 +11,7 @@ patchesStrategicMerge:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.6.6
|
||||
newTag: v2.6.8
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -1071,7 +1071,7 @@ spec:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
initContainers:
|
||||
- name: config-init
|
||||
image: haproxy:2.6.9-alpine
|
||||
image: haproxy:2.6.12-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
{}
|
||||
@@ -1089,7 +1089,7 @@ spec:
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: haproxy
|
||||
image: haproxy:2.6.9-alpine
|
||||
image: haproxy:2.6.12-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
null
|
||||
@@ -1179,7 +1179,7 @@ spec:
|
||||
automountServiceAccountToken: false
|
||||
initContainers:
|
||||
- name: config-init
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
{}
|
||||
@@ -1206,7 +1206,7 @@ spec:
|
||||
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-server
|
||||
@@ -1256,7 +1256,7 @@ spec:
|
||||
- /bin/sh
|
||||
- /readonly-config/trigger-failover-if-master.sh
|
||||
- name: sentinel
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-sentinel
|
||||
@@ -1300,7 +1300,7 @@ spec:
|
||||
{}
|
||||
|
||||
- name: split-brain-fix
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- sh
|
||||
|
||||
@@ -11,14 +11,14 @@ redis-ha:
|
||||
IPv6:
|
||||
enabled: false
|
||||
image:
|
||||
tag: 2.6.9-alpine
|
||||
tag: 2.6.12-alpine
|
||||
containerSecurityContext: null
|
||||
timeout:
|
||||
server: 6m
|
||||
client: 6m
|
||||
checkInterval: 3s
|
||||
image:
|
||||
tag: 7.0.8-alpine
|
||||
tag: 7.0.11-alpine
|
||||
containerSecurityContext: null
|
||||
sentinel:
|
||||
bind: "0.0.0.0"
|
||||
|
||||
@@ -16758,7 +16758,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -16839,7 +16839,7 @@ spec:
|
||||
key: dexserver.disable.tls
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
name: dex
|
||||
ports:
|
||||
@@ -16868,7 +16868,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -16921,7 +16921,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -16992,7 +16992,7 @@ spec:
|
||||
app.kubernetes.io/name: argocd-redis-ha-haproxy
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- image: haproxy:2.6.9-alpine
|
||||
- image: haproxy:2.6.12-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -17028,7 +17028,7 @@ spec:
|
||||
- /readonly/haproxy_init.sh
|
||||
command:
|
||||
- sh
|
||||
image: haproxy:2.6.9-alpine
|
||||
image: haproxy:2.6.12-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
securityContext:
|
||||
@@ -17224,7 +17224,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -17276,7 +17276,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -17555,7 +17555,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -17791,7 +17791,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
@@ -17868,7 +17868,7 @@ spec:
|
||||
- /data/conf/redis.conf
|
||||
command:
|
||||
- redis-server
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle:
|
||||
preStop:
|
||||
@@ -17921,7 +17921,7 @@ spec:
|
||||
- /data/conf/sentinel.conf
|
||||
command:
|
||||
- redis-sentinel
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -17973,7 +17973,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: split-brain-fix
|
||||
resources: {}
|
||||
@@ -18002,7 +18002,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
securityContext:
|
||||
|
||||
@@ -1562,7 +1562,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1643,7 +1643,7 @@ spec:
|
||||
key: dexserver.disable.tls
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
name: dex
|
||||
ports:
|
||||
@@ -1672,7 +1672,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1725,7 +1725,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1796,7 +1796,7 @@ spec:
|
||||
app.kubernetes.io/name: argocd-redis-ha-haproxy
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- image: haproxy:2.6.9-alpine
|
||||
- image: haproxy:2.6.12-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -1832,7 +1832,7 @@ spec:
|
||||
- /readonly/haproxy_init.sh
|
||||
command:
|
||||
- sh
|
||||
image: haproxy:2.6.9-alpine
|
||||
image: haproxy:2.6.12-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
securityContext:
|
||||
@@ -2028,7 +2028,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2080,7 +2080,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2359,7 +2359,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2595,7 +2595,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
@@ -2672,7 +2672,7 @@ spec:
|
||||
- /data/conf/redis.conf
|
||||
command:
|
||||
- redis-server
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle:
|
||||
preStop:
|
||||
@@ -2725,7 +2725,7 @@ spec:
|
||||
- /data/conf/sentinel.conf
|
||||
command:
|
||||
- redis-sentinel
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -2777,7 +2777,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: split-brain-fix
|
||||
resources: {}
|
||||
@@ -2806,7 +2806,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
securityContext:
|
||||
|
||||
@@ -15877,7 +15877,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -15958,7 +15958,7 @@ spec:
|
||||
key: dexserver.disable.tls
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
name: dex
|
||||
ports:
|
||||
@@ -15987,7 +15987,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -16040,7 +16040,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -16117,7 +16117,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -16299,7 +16299,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -16351,7 +16351,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -16626,7 +16626,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -16860,7 +16860,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -681,7 +681,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -762,7 +762,7 @@ spec:
|
||||
key: dexserver.disable.tls
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
name: dex
|
||||
ports:
|
||||
@@ -791,7 +791,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -844,7 +844,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -921,7 +921,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.8-alpine
|
||||
image: redis:7.0.11-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -1103,7 +1103,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1155,7 +1155,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1430,7 +1430,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1664,7 +1664,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.6
|
||||
image: quay.io/argoproj/argocd:v2.6.8
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -161,6 +161,7 @@ nav:
|
||||
- user-guide/best_practices.md
|
||||
- user-guide/status-badge.md
|
||||
- user-guide/external-url.md
|
||||
- user-guide/extra_info.md
|
||||
- Notification subscriptions: user-guide/subscriptions.md
|
||||
- Command Reference: user-guide/commands/argocd.md
|
||||
- Developer Guide:
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
// Code generated by mockery v2.13.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
apiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
|
||||
metadata "google.golang.org/grpc/metadata"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// RepoServerService_GenerateManifestWithFilesClient is an autogenerated mock type for the RepoServerService_GenerateManifestWithFilesClient type
|
||||
type RepoServerService_GenerateManifestWithFilesClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CloseAndRecv provides a mock function with given fields:
|
||||
func (_m *RepoServerService_GenerateManifestWithFilesClient) CloseAndRecv() (*apiclient.ManifestResponse, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *apiclient.ManifestResponse
|
||||
if rf, ok := ret.Get(0).(func() *apiclient.ManifestResponse); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*apiclient.ManifestResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CloseSend provides a mock function with given fields:
|
||||
func (_m *RepoServerService_GenerateManifestWithFilesClient) CloseSend() error {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Context provides a mock function with given fields:
|
||||
func (_m *RepoServerService_GenerateManifestWithFilesClient) Context() context.Context {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 context.Context
|
||||
if rf, ok := ret.Get(0).(func() context.Context); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(context.Context)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Header provides a mock function with given fields:
|
||||
func (_m *RepoServerService_GenerateManifestWithFilesClient) Header() (metadata.MD, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 metadata.MD
|
||||
if rf, ok := ret.Get(0).(func() metadata.MD); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(metadata.MD)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RecvMsg provides a mock function with given fields: m
|
||||
func (_m *RepoServerService_GenerateManifestWithFilesClient) RecvMsg(m interface{}) error {
|
||||
ret := _m.Called(m)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(interface{}) error); ok {
|
||||
r0 = rf(m)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Send provides a mock function with given fields: _a0
|
||||
func (_m *RepoServerService_GenerateManifestWithFilesClient) Send(_a0 *apiclient.ManifestRequestWithFiles) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*apiclient.ManifestRequestWithFiles) error); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendMsg provides a mock function with given fields: m
|
||||
func (_m *RepoServerService_GenerateManifestWithFilesClient) SendMsg(m interface{}) error {
|
||||
ret := _m.Called(m)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(interface{}) error); ok {
|
||||
r0 = rf(m)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Trailer provides a mock function with given fields:
|
||||
func (_m *RepoServerService_GenerateManifestWithFilesClient) Trailer() metadata.MD {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 metadata.MD
|
||||
if rf, ok := ret.Get(0).(func() metadata.MD); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(metadata.MD)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewRepoServerService_GenerateManifestWithFilesClient interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewRepoServerService_GenerateManifestWithFilesClient creates a new instance of RepoServerService_GenerateManifestWithFilesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewRepoServerService_GenerateManifestWithFilesClient(t mockConstructorTestingTNewRepoServerService_GenerateManifestWithFilesClient) *RepoServerService_GenerateManifestWithFilesClient {
|
||||
mock := &RepoServerService_GenerateManifestWithFilesClient{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -221,7 +221,7 @@ func (s *Service) ListApps(ctx context.Context, q *apiclient.ListAppsRequest) (*
|
||||
}
|
||||
|
||||
defer io.Close(closer)
|
||||
apps, err := discovery.Discover(ctx, gitClient.Root(), q.EnabledSourceTypes, s.initConstants.CMPTarExcludedGlobs)
|
||||
apps, err := discovery.Discover(ctx, gitClient.Root(), gitClient.Root(), q.EnabledSourceTypes, s.initConstants.CMPTarExcludedGlobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -474,6 +474,7 @@ func resolveReferencedSources(hasMultipleSources bool, source *v1alpha1.Applicat
|
||||
if !ok {
|
||||
_, referencedCommitSHA, err := newClientResolveRevision(&refSourceMapping.Repo, refSourceMapping.TargetRevision)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get git client for repo %s: %v", refSourceMapping.Repo.Repo, err)
|
||||
return nil, fmt.Errorf("failed to get git client for repo %s", refSourceMapping.Repo.Repo)
|
||||
}
|
||||
|
||||
@@ -706,6 +707,7 @@ func (s *Service) runManifestGenAsync(ctx context.Context, repoRoot, commitSHA,
|
||||
} else {
|
||||
gitClient, referencedCommitSHA, err := s.newClientResolveRevision(&refSourceMapping.Repo, refSourceMapping.TargetRevision)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get git client for repo %s: %v", refSourceMapping.Repo.Repo, err)
|
||||
ch.errCh <- fmt.Errorf("failed to get git client for repo %s", refSourceMapping.Repo.Repo)
|
||||
return
|
||||
}
|
||||
@@ -1292,7 +1294,7 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
|
||||
|
||||
resourceTracking := argo.NewResourceTracking()
|
||||
|
||||
appSourceType, err := GetAppSourceType(ctx, q.ApplicationSource, appPath, q.AppName, q.EnabledSourceTypes, opt.cmpTarExcludedGlobs)
|
||||
appSourceType, err := GetAppSourceType(ctx, q.ApplicationSource, appPath, repoRoot, q.AppName, q.EnabledSourceTypes, opt.cmpTarExcludedGlobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1469,8 +1471,8 @@ func mergeSourceParameters(source *v1alpha1.ApplicationSource, path, appName str
|
||||
}
|
||||
|
||||
// GetAppSourceType returns explicit application source type or examines a directory and determines its application source type
|
||||
func GetAppSourceType(ctx context.Context, source *v1alpha1.ApplicationSource, path, appName string, enableGenerateManifests map[string]bool, tarExcludedGlobs []string) (v1alpha1.ApplicationSourceType, error) {
|
||||
err := mergeSourceParameters(source, path, appName)
|
||||
func GetAppSourceType(ctx context.Context, source *v1alpha1.ApplicationSource, appPath, repoPath, appName string, enableGenerateManifests map[string]bool, tarExcludedGlobs []string) (v1alpha1.ApplicationSourceType, error) {
|
||||
err := mergeSourceParameters(source, appPath, appName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error while parsing source parameters: %v", err)
|
||||
}
|
||||
@@ -1486,7 +1488,7 @@ func GetAppSourceType(ctx context.Context, source *v1alpha1.ApplicationSource, p
|
||||
}
|
||||
return *appSourceType, nil
|
||||
}
|
||||
appType, err := discovery.AppType(ctx, path, enableGenerateManifests, tarExcludedGlobs)
|
||||
appType, err := discovery.AppType(ctx, appPath, repoPath, enableGenerateManifests, tarExcludedGlobs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1906,7 +1908,7 @@ func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath, p
|
||||
}
|
||||
|
||||
// detect config management plugin server (sidecar)
|
||||
conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, pluginName, env, tarExcludedGlobs)
|
||||
conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, repoPath, pluginName, env, tarExcludedGlobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1963,7 +1965,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
|
||||
return err
|
||||
}
|
||||
|
||||
appSourceType, err := GetAppSourceType(ctx, q.Source, opContext.appPath, q.AppName, q.EnabledSourceTypes, s.initConstants.CMPTarExcludedGlobs)
|
||||
appSourceType, err := GetAppSourceType(ctx, q.Source, opContext.appPath, repoRoot, q.AppName, q.EnabledSourceTypes, s.initConstants.CMPTarExcludedGlobs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2157,7 +2159,7 @@ func populatePluginAppDetails(ctx context.Context, res *apiclient.RepoAppDetails
|
||||
pluginName = q.Source.Plugin.Name
|
||||
}
|
||||
// detect config management plugin server (sidecar)
|
||||
conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, pluginName, env, tarExcludedGlobs)
|
||||
conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, repoPath, pluginName, env, tarExcludedGlobs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to detect CMP for app: %w", err)
|
||||
}
|
||||
|
||||
@@ -1188,15 +1188,15 @@ func TestGenerateNullList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIdentifyAppSourceTypeByAppDirWithKustomizations(t *testing.T) {
|
||||
sourceType, err := GetAppSourceType(context.Background(), &argoappv1.ApplicationSource{}, "./testdata/kustomization_yaml", "testapp", map[string]bool{}, []string{})
|
||||
sourceType, err := GetAppSourceType(context.Background(), &argoappv1.ApplicationSource{}, "./testdata/kustomization_yaml", "./testdata", "testapp", map[string]bool{}, []string{})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, argoappv1.ApplicationSourceTypeKustomize, sourceType)
|
||||
|
||||
sourceType, err = GetAppSourceType(context.Background(), &argoappv1.ApplicationSource{}, "./testdata/kustomization_yml", "testapp", map[string]bool{}, []string{})
|
||||
sourceType, err = GetAppSourceType(context.Background(), &argoappv1.ApplicationSource{}, "./testdata/kustomization_yml", "./testdata", "testapp", map[string]bool{}, []string{})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, argoappv1.ApplicationSourceTypeKustomize, sourceType)
|
||||
|
||||
sourceType, err = GetAppSourceType(context.Background(), &argoappv1.ApplicationSource{}, "./testdata/Kustomization", "testapp", map[string]bool{}, []string{})
|
||||
sourceType, err = GetAppSourceType(context.Background(), &argoappv1.ApplicationSource{}, "./testdata/Kustomization", "./testdata", "testapp", map[string]bool{}, []string{})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, argoappv1.ApplicationSourceTypeKustomize, sourceType)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
watchAPIBufferSize = env.ParseNumFromEnv(argocommon.EnvWatchAPIBufferSize, 1000, 0, math.MaxInt32)
|
||||
watchAPIBufferSize = env.ParseNumFromEnv(argocommon.EnvWatchAPIBufferSize, 1000, 0, math.MaxInt32)
|
||||
permissionDeniedErr = status.Error(codes.PermissionDenied, "permission denied")
|
||||
)
|
||||
|
||||
// Server provides an Application service
|
||||
@@ -78,7 +79,7 @@ type Server struct {
|
||||
appclientset appclientset.Interface
|
||||
appLister applisters.ApplicationLister
|
||||
appInformer cache.SharedIndexInformer
|
||||
appBroadcaster *broadcasterHandler
|
||||
appBroadcaster Broadcaster
|
||||
repoClientset apiclient.Clientset
|
||||
kubectl kube.Kubectl
|
||||
db db.ArgoDB
|
||||
@@ -98,6 +99,7 @@ func NewServer(
|
||||
appclientset appclientset.Interface,
|
||||
appLister applisters.ApplicationLister,
|
||||
appInformer cache.SharedIndexInformer,
|
||||
appBroadcaster Broadcaster,
|
||||
repoClientset apiclient.Clientset,
|
||||
cache *servercache.Cache,
|
||||
kubectl kube.Kubectl,
|
||||
@@ -108,7 +110,9 @@ func NewServer(
|
||||
projInformer cache.SharedIndexInformer,
|
||||
enabledNamespaces []string,
|
||||
) (application.ApplicationServiceServer, AppResourceTreeFn) {
|
||||
appBroadcaster := &broadcasterHandler{}
|
||||
if appBroadcaster == nil {
|
||||
appBroadcaster = &broadcasterHandler{}
|
||||
}
|
||||
appInformer.AddEventHandler(appBroadcaster)
|
||||
s := &Server{
|
||||
ns: namespace,
|
||||
@@ -131,6 +135,61 @@ func NewServer(
|
||||
return s, s.getAppResources
|
||||
}
|
||||
|
||||
// getAppEnforceRBAC gets the Application with the given name in the given namespace. If no namespace is
|
||||
// specified, the Application is fetched from the default namespace (the one in which the API server is running).
|
||||
//
|
||||
// If the Application does not exist, then we have no way of determining if the user would have had access to get that
|
||||
// Application. Verifying access requires knowing the Application's name, namespace, and project. The user may specify,
|
||||
// at minimum, the Application name.
|
||||
//
|
||||
// So to prevent a malicious user from inferring the existence or absense of the Application or namespace, we respond
|
||||
// "permission denied" if the Application does not exist.
|
||||
func (s *Server) getAppEnforceRBAC(ctx context.Context, action, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, error) {
|
||||
logCtx := log.WithFields(map[string]interface{}{
|
||||
"application": name,
|
||||
"namespace": namespace,
|
||||
})
|
||||
a, err := getApp()
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
logCtx.Warn("application does not exist")
|
||||
return nil, permissionDeniedErr
|
||||
}
|
||||
logCtx.Errorf("failed to get application: %s", err)
|
||||
return nil, permissionDeniedErr
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, action, a.RBACName(s.ns)); err != nil {
|
||||
logCtx.WithFields(map[string]interface{}{
|
||||
"project": a.Spec.Project,
|
||||
argocommon.SecurityField: argocommon.SecurityMedium,
|
||||
}).Warnf("user tried to %s application which they do not have access to: %s", action, err)
|
||||
return nil, permissionDeniedErr
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// getApplicationEnforceRBACInformer uses an informer to get an Application. If the app does not exist, permission is
|
||||
// denied, or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
|
||||
// information.
|
||||
func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, namespace, name string) (*appv1.Application, error) {
|
||||
namespaceOrDefault := s.appNamespaceOrDefault(namespace)
|
||||
return s.getAppEnforceRBAC(ctx, action, namespaceOrDefault, name, func() (*appv1.Application, error) {
|
||||
return s.appLister.Applications(namespaceOrDefault).Get(name)
|
||||
})
|
||||
}
|
||||
|
||||
// getApplicationEnforceRBACClient uses a client to get an Application. If the app does not exist, permission is denied,
|
||||
// or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
|
||||
// information.
|
||||
func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, namespace, name, resourceVersion string) (*appv1.Application, error) {
|
||||
namespaceOrDefault := s.appNamespaceOrDefault(namespace)
|
||||
return s.getAppEnforceRBAC(ctx, action, namespaceOrDefault, name, func() (*appv1.Application, error) {
|
||||
return s.appclientset.ArgoprojV1alpha1().Applications(namespaceOrDefault).Get(ctx, name, metav1.GetOptions{
|
||||
ResourceVersion: resourceVersion,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// List returns list of applications
|
||||
func (s *Server) List(ctx context.Context, q *application.ApplicationQuery) (*appv1.ApplicationList, error) {
|
||||
selector, err := labels.Parse(q.GetSelector())
|
||||
@@ -318,13 +377,8 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
|
||||
if q.Name == nil || *q.Name == "" {
|
||||
return nil, fmt.Errorf("invalid request: application name is missing")
|
||||
}
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -426,14 +480,8 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get
|
||||
return fmt.Errorf("invalid request: application name is missing")
|
||||
}
|
||||
|
||||
appName := query.GetName()
|
||||
appNs := s.appNamespaceOrDefault(query.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetAppNamespace(), query.GetName())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -538,14 +586,8 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
|
||||
// We must use a client Get instead of an informer Get, because it's common to call Get immediately
|
||||
// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
|
||||
// previously seen by the client.
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{
|
||||
ResourceVersion: q.GetResourceVersion(),
|
||||
})
|
||||
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, appNs, appName, q.GetResourceVersion())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -627,13 +669,8 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
|
||||
|
||||
// ListResourceEvents returns a list of event resources
|
||||
func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -694,13 +731,13 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *application.Applicat
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Application, merge bool, validate bool) (*appv1.Application, error) {
|
||||
func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Application, merge bool, validate bool, action string) (*appv1.Application, error) {
|
||||
s.projectLock.RLock(newApp.Spec.GetProject())
|
||||
defer s.projectLock.RUnlock(newApp.Spec.GetProject())
|
||||
|
||||
app, err := s.appclientset.ArgoprojV1alpha1().Applications(newApp.Namespace).Get(ctx, newApp.Name, metav1.GetOptions{})
|
||||
app, err := s.getApplicationEnforceRBACClient(ctx, action, newApp.Namespace, newApp.Name, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.validateAndNormalizeApp(ctx, newApp, validate)
|
||||
@@ -807,7 +844,7 @@ func (s *Server) Update(ctx context.Context, q *application.ApplicationUpdateReq
|
||||
if q.Validate != nil {
|
||||
validate = *q.Validate
|
||||
}
|
||||
return s.validateAndUpdateApp(ctx, q.Application, false, validate)
|
||||
return s.validateAndUpdateApp(ctx, q.Application, false, validate, rbacpolicy.ActionUpdate)
|
||||
}
|
||||
|
||||
// UpdateSpec updates an application spec and filters out any invalid parameter overrides
|
||||
@@ -815,13 +852,8 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
|
||||
if q.GetSpec() == nil {
|
||||
return nil, fmt.Errorf("error updating application spec: spec is nil in request")
|
||||
}
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetAppNamespace(), q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -830,7 +862,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
|
||||
if q.Validate != nil {
|
||||
validate = *q.Validate
|
||||
}
|
||||
a, err = s.validateAndUpdateApp(ctx, a, false, validate)
|
||||
a, err = s.validateAndUpdateApp(ctx, a, false, validate, rbacpolicy.ActionUpdate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error validating and updating app: %w", err)
|
||||
}
|
||||
@@ -839,11 +871,9 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
|
||||
|
||||
// Patch patches an application
|
||||
func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchRequest) (*appv1.Application, error) {
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
app, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
|
||||
app, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, app.RBACName(s.ns)); err != nil {
|
||||
@@ -881,16 +911,16 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling patched app: %w", err)
|
||||
}
|
||||
return s.validateAndUpdateApp(ctx, newApp, false, true)
|
||||
return s.validateAndUpdateApp(ctx, newApp, false, true, rbacpolicy.ActionUpdate)
|
||||
}
|
||||
|
||||
// Delete removes an application and all associated resources
|
||||
func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteRequest) (*application.ApplicationResponse, error) {
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, appNs, appName, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.projectLock.RLock(a.Spec.Project)
|
||||
@@ -1034,7 +1064,9 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
|
||||
proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", app.Spec.Project)
|
||||
// Offer no hint that the project does not exist.
|
||||
log.Warnf("User attempted to create/update application in non-existent project %q", app.Spec.Project)
|
||||
return permissionDeniedErr
|
||||
}
|
||||
return fmt.Errorf("error getting application's project: %w", err)
|
||||
}
|
||||
@@ -1138,22 +1170,16 @@ func (s *Server) getAppResources(ctx context.Context, a *appv1.Application) (*ap
|
||||
return s.cache.GetAppResourcesTree(a.InstanceName(s.ns), &tree)
|
||||
})
|
||||
if err != nil {
|
||||
return &tree, fmt.Errorf("error getting cached app state: %w", err)
|
||||
return &tree, fmt.Errorf("error getting cached app resource tree: %w", err)
|
||||
}
|
||||
return &tree, nil
|
||||
}
|
||||
|
||||
func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error getting app by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, action, a.RBACName(s.ns)); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
tree, err := s.getAppResources(ctx, a)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error getting app resources: %w", err)
|
||||
@@ -1173,7 +1199,7 @@ func (s *Server) getAppLiveResource(ctx context.Context, action string, q *appli
|
||||
func (s *Server) GetResource(ctx context.Context, q *application.ApplicationResourceRequest) (*application.ApplicationResourceResponse, error) {
|
||||
res, config, _, err := s.getAppLiveResource(ctx, rbacpolicy.ActionGet, q)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app live resource: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make sure to use specified resource version if provided
|
||||
@@ -1220,9 +1246,6 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
|
||||
}
|
||||
res, config, a, err := s.getAppLiveResource(ctx, rbacpolicy.ActionUpdate, resourceRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app live resource: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1234,6 +1257,9 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
|
||||
}
|
||||
return nil, fmt.Errorf("error patching resource: %w", err)
|
||||
}
|
||||
if manifest == nil {
|
||||
return nil, fmt.Errorf("failed to patch resource: manifest was nil")
|
||||
}
|
||||
manifest, err = replaceSecretValues(manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error replacing secret values: %w", err)
|
||||
@@ -1262,9 +1288,6 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
|
||||
}
|
||||
res, config, a, err := s.getAppLiveResource(ctx, rbacpolicy.ActionDelete, resourceRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting live resource for delete: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionDelete, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var deleteOption metav1.DeleteOptions
|
||||
@@ -1288,13 +1311,8 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
|
||||
}
|
||||
|
||||
func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery) (*appv1.ApplicationTree, error) {
|
||||
appName := q.GetApplicationName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetApplicationName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1302,14 +1320,8 @@ func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery
|
||||
}
|
||||
|
||||
func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
|
||||
appName := q.GetApplicationName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
_, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetApplicationName())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
|
||||
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1324,13 +1336,8 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
|
||||
}
|
||||
|
||||
func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.RevisionMetadata, error) {
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1365,14 +1372,9 @@ func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) boo
|
||||
}
|
||||
|
||||
func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQuery) (*application.ManagedResourcesResponse, error) {
|
||||
appName := q.GetApplicationName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetApplicationName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return nil, fmt.Errorf("error verifying rbac: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]*appv1.ResourceDiff, 0)
|
||||
@@ -1380,7 +1382,7 @@ func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQ
|
||||
return s.cache.GetAppManagedResources(a.InstanceName(s.ns), &items)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting cached app state: %w", err)
|
||||
return nil, fmt.Errorf("error getting cached app managed resources: %w", err)
|
||||
}
|
||||
res := &application.ManagedResourcesResponse{}
|
||||
for i := range items {
|
||||
@@ -1427,14 +1429,8 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
|
||||
}
|
||||
}
|
||||
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.appLister.Applications(appNs).Get(appName)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
|
||||
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1625,12 +1621,9 @@ func isTheSelectedOne(currentNode *appv1.ResourceNode, q *application.Applicatio
|
||||
|
||||
// Sync syncs an application to its target state
|
||||
func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncRequest) (*appv1.Application, error) {
|
||||
appName := syncReq.GetName()
|
||||
appNs := s.appNamespaceOrDefault(syncReq.GetAppNamespace())
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
|
||||
a, err := appIf.Get(ctx, appName, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetAppNamespace(), syncReq.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
@@ -1717,6 +1710,9 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
|
||||
op.Retry = *retry
|
||||
}
|
||||
|
||||
appName := syncReq.GetName()
|
||||
appNs := s.appNamespaceOrDefault(syncReq.GetAppNamespace())
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
|
||||
a, err = argo.SetAppOperation(appIf, appName, &op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting app operation: %w", err)
|
||||
@@ -1734,14 +1730,8 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
|
||||
}
|
||||
|
||||
func (s *Server) Rollback(ctx context.Context, rollbackReq *application.ApplicationRollbackRequest) (*appv1.Application, error) {
|
||||
appName := rollbackReq.GetName()
|
||||
appNs := s.appNamespaceOrDefault(rollbackReq.GetAppNamespace())
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
|
||||
a, err := appIf.Get(ctx, appName, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1787,6 +1777,9 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
|
||||
},
|
||||
InitiatedBy: appv1.OperationInitiator{Username: session.Username(ctx)},
|
||||
}
|
||||
appName := rollbackReq.GetName()
|
||||
appNs := s.appNamespaceOrDefault(rollbackReq.GetAppNamespace())
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
|
||||
a, err = argo.SetAppOperation(appIf, appName, &op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting app operation: %w", err)
|
||||
@@ -1796,24 +1789,9 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
|
||||
}
|
||||
|
||||
func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksRequest) (*application.LinksResponse, error) {
|
||||
appName := req.GetName()
|
||||
appNs := s.appNamespaceOrDefault(req.GetNamespace())
|
||||
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, req.GetNamespace(), req.GetName(), "")
|
||||
if err != nil {
|
||||
log.WithFields(map[string]interface{}{
|
||||
"application": appName,
|
||||
"ns": appNs,
|
||||
}).Errorf("failed to get application, error=%v", err.Error())
|
||||
return nil, fmt.Errorf("error getting application")
|
||||
}
|
||||
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
log.WithFields(map[string]interface{}{
|
||||
"application": appName,
|
||||
"ns": appNs,
|
||||
}).Warnf("unauthorized access to app, error=%v", err.Error())
|
||||
return nil, fmt.Errorf("error getting application")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := kube.ToUnstructured(a)
|
||||
@@ -1895,11 +1873,8 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
|
||||
func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {
|
||||
appName := termOpReq.GetName()
|
||||
appNs := s.appNamespaceOrDefault(termOpReq.GetAppNamespace())
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, appNs, appName, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1971,10 +1946,9 @@ func (s *Server) ListResourceActions(ctx context.Context, q *application.Applica
|
||||
|
||||
func (s *Server) getUnstructuredLiveResourceOrApp(ctx context.Context, rbacRequest string, q *application.ApplicationResourceRequest) (obj *unstructured.Unstructured, res *appv1.ResourceNode, app *appv1.Application, config *rest.Config, err error) {
|
||||
if q.GetKind() == "Application" && q.GetGroup() == "argoproj.io" && q.GetName() == q.GetResourceName() {
|
||||
namespace := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
app, err = s.appLister.Applications(namespace).Get(q.GetName())
|
||||
app, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("error getting app by name: %w", err)
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacRequest, app.RBACName(s.ns)); err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
@@ -1987,7 +1961,7 @@ func (s *Server) getUnstructuredLiveResourceOrApp(ctx context.Context, rbacReque
|
||||
} else {
|
||||
res, config, app, err = s.getAppLiveResource(ctx, rbacRequest, q)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("error getting app live resource: %w", err)
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
obj, err = s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
|
||||
}
|
||||
@@ -2157,15 +2131,8 @@ func (s *Server) plugins() ([]*appv1.ConfigManagementPlugin, error) {
|
||||
}
|
||||
|
||||
func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.ApplicationSyncWindowsQuery) (*application.ApplicationSyncWindowsResponse, error) {
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
|
||||
a, err := appIf.Get(ctx, appName, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetAppNamespace(), q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"context"
|
||||
coreerrors "errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
|
||||
"github.com/argoproj/pkg/sync"
|
||||
"github.com/ghodss/yaml"
|
||||
@@ -18,13 +21,17 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
k8sappsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/rest"
|
||||
kubetesting "k8s.io/client-go/testing"
|
||||
k8scache "k8s.io/client-go/tools/cache"
|
||||
"k8s.io/utils/pointer"
|
||||
@@ -36,6 +43,7 @@ import (
|
||||
appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
appmocks "github.com/argoproj/argo-cd/v2/server/application/mocks"
|
||||
servercache "github.com/argoproj/argo-cd/v2/server/cache"
|
||||
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
@@ -98,6 +106,11 @@ func fakeRepoServerClient(isHelm bool) *mocks.RepoServerServiceClient {
|
||||
mockRepoServiceClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(&apiclient.ManifestResponse{}, nil)
|
||||
mockRepoServiceClient.On("GetAppDetails", mock.Anything, mock.Anything).Return(&apiclient.RepoAppDetailsResponse{}, nil)
|
||||
mockRepoServiceClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
|
||||
mockRepoServiceClient.On("GetRevisionMetadata", mock.Anything, mock.Anything).Return(&appsv1.RevisionMetadata{}, nil)
|
||||
mockWithFilesClient := &mocks.RepoServerService_GenerateManifestWithFilesClient{}
|
||||
mockWithFilesClient.On("Send", mock.Anything).Return(nil)
|
||||
mockWithFilesClient.On("CloseAndRecv").Return(&apiclient.ManifestResponse{}, nil)
|
||||
mockRepoServiceClient.On("GenerateManifestWithFiles", mock.Anything, mock.Anything).Return(mockWithFilesClient, nil)
|
||||
|
||||
if isHelm {
|
||||
mockRepoServiceClient.On("ResolveRevision", mock.Anything, mock.Anything).Return(fakeResolveRevesionResponseHelm(), nil)
|
||||
@@ -109,15 +122,15 @@ func fakeRepoServerClient(isHelm bool) *mocks.RepoServerServiceClient {
|
||||
}
|
||||
|
||||
// return an ApplicationServiceServer which returns fake data
|
||||
func newTestAppServer(objects ...runtime.Object) *Server {
|
||||
func newTestAppServer(t *testing.T, objects ...runtime.Object) *Server {
|
||||
f := func(enf *rbac.Enforcer) {
|
||||
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
|
||||
enf.SetDefaultRole("role:admin")
|
||||
}
|
||||
return newTestAppServerWithEnforcerConfigure(f, objects...)
|
||||
return newTestAppServerWithEnforcerConfigure(f, t, objects...)
|
||||
}
|
||||
|
||||
func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ...runtime.Object) *Server {
|
||||
func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), t *testing.T, objects ...runtime.Object) *Server {
|
||||
kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: testNamespace,
|
||||
@@ -202,15 +215,83 @@ func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ...ru
|
||||
panic("Timed out waiting for caches to sync")
|
||||
}
|
||||
|
||||
broadcaster := new(appmocks.Broadcaster)
|
||||
broadcaster.On("Subscribe", mock.Anything, mock.Anything).Return(func() {}).Run(func(args mock.Arguments) {
|
||||
// Simulate the broadcaster notifying the subscriber of an application update.
|
||||
// The second parameter to Subscribe is filters. For the purposes of tests, we ignore the filters. Future tests
|
||||
// might require implementing those.
|
||||
go func() {
|
||||
events := args.Get(0).(chan *appsv1.ApplicationWatchEvent)
|
||||
for _, obj := range objects {
|
||||
app, ok := obj.(*appsv1.Application)
|
||||
if ok {
|
||||
oldVersion, err := strconv.Atoi(app.ResourceVersion)
|
||||
if err != nil {
|
||||
oldVersion = 0
|
||||
}
|
||||
clonedApp := app.DeepCopy()
|
||||
clonedApp.ResourceVersion = fmt.Sprintf("%d", oldVersion+1)
|
||||
events <- &appsv1.ApplicationWatchEvent{Type: watch.Added, Application: *clonedApp}
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
broadcaster.On("OnAdd", mock.Anything).Return()
|
||||
broadcaster.On("OnUpdate", mock.Anything, mock.Anything).Return()
|
||||
broadcaster.On("OnDelete", mock.Anything).Return()
|
||||
|
||||
appStateCache := appstate.NewCache(cache.NewCache(cache.NewInMemoryCache(time.Hour)), time.Hour)
|
||||
// pre-populate the app cache
|
||||
for _, obj := range objects {
|
||||
app, ok := obj.(*appsv1.Application)
|
||||
if ok {
|
||||
err := appStateCache.SetAppManagedResources(app.Name, []*appsv1.ResourceDiff{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Pre-populate the resource tree based on the app's resources.
|
||||
nodes := make([]appsv1.ResourceNode, len(app.Status.Resources))
|
||||
for i, res := range app.Status.Resources {
|
||||
nodes[i] = appsv1.ResourceNode{
|
||||
ResourceRef: appsv1.ResourceRef{
|
||||
Group: res.Group,
|
||||
Kind: res.Kind,
|
||||
Version: res.Version,
|
||||
Name: res.Name,
|
||||
Namespace: res.Namespace,
|
||||
UID: "fake",
|
||||
},
|
||||
}
|
||||
}
|
||||
err = appStateCache.SetAppResourcesTree(app.Name, &appsv1.ApplicationTree{
|
||||
Nodes: nodes,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
appCache := servercache.NewCache(appStateCache, time.Hour, time.Hour, time.Hour)
|
||||
|
||||
kubectl := &kubetest.MockKubectlCmd{}
|
||||
kubectl = kubectl.WithGetResourceFunc(func(_ context.Context, _ *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error) {
|
||||
for _, obj := range objects {
|
||||
if obj.GetObjectKind().GroupVersionKind().GroupKind() == gvk.GroupKind() {
|
||||
if obj, ok := obj.(*unstructured.Unstructured); ok && obj.GetName() == name && obj.GetNamespace() == namespace {
|
||||
return obj, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
server, _ := NewServer(
|
||||
testNamespace,
|
||||
kubeclientset,
|
||||
fakeAppsClientset,
|
||||
factory.Argoproj().V1alpha1().Applications().Lister(),
|
||||
appInformer,
|
||||
broadcaster,
|
||||
mockRepoClient,
|
||||
nil,
|
||||
&kubetest.MockKubectlCmd{},
|
||||
appCache,
|
||||
kubectl,
|
||||
db,
|
||||
enforcer,
|
||||
sync.NewKeyLock(),
|
||||
@@ -301,8 +382,423 @@ func createTestApp(testApp string, opts ...func(app *appsv1.Application)) *appsv
|
||||
return &app
|
||||
}
|
||||
|
||||
type TestServerStream struct {
|
||||
ctx context.Context
|
||||
appName string
|
||||
headerSent bool
|
||||
}
|
||||
|
||||
func (t *TestServerStream) SetHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestServerStream) SendHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestServerStream) SetTrailer(metadata.MD) {}
|
||||
|
||||
func (t *TestServerStream) Context() context.Context {
|
||||
return t.ctx
|
||||
}
|
||||
|
||||
func (t *TestServerStream) SendMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestServerStream) RecvMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestServerStream) SendAndClose(r *apiclient.ManifestResponse) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestServerStream) Recv() (*application.ApplicationManifestQueryWithFilesWrapper, error) {
|
||||
if !t.headerSent {
|
||||
t.headerSent = true
|
||||
return &application.ApplicationManifestQueryWithFilesWrapper{Part: &application.ApplicationManifestQueryWithFilesWrapper_Query{
|
||||
Query: &application.ApplicationManifestQueryWithFiles{
|
||||
Name: pointer.String(t.appName),
|
||||
Checksum: pointer.String(""),
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func (t *TestServerStream) ServerStream() TestServerStream {
|
||||
return TestServerStream{}
|
||||
}
|
||||
|
||||
type TestResourceTreeServer struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) Send(tree *appsv1.ApplicationTree) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) SetHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) SendHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) SetTrailer(metadata.MD) {}
|
||||
|
||||
func (t *TestResourceTreeServer) Context() context.Context {
|
||||
return t.ctx
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) SendMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) RecvMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type TestPodLogsServer struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) Send(log *application.LogEntry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) SetHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) SendHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) SetTrailer(metadata.MD) {}
|
||||
|
||||
func (t *TestPodLogsServer) Context() context.Context {
|
||||
return t.ctx
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) SendMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) RecvMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNoAppEnumeration(t *testing.T) {
|
||||
// This test ensures that malicious users can't infer the existence or non-existence of Applications by inspecting
|
||||
// error messages. The errors for "app does not exist" must be the same as errors for "you aren't allowed to
|
||||
// interact with this app."
|
||||
|
||||
// These tests are only important on API calls where the full app RBAC name (project, namespace, and name) is _not_
|
||||
// known based on the query parameters. For example, the Create call cannot leak existence of Applications, because
|
||||
// the Application's project, namespace, and name are all specified in the API call. The call can be rejected
|
||||
// immediately if the user does not have access. But the Delete endpoint may be called with just the Application
|
||||
// name. So we cannot return a different error message for "does not exist" and "you don't have delete permissions,"
|
||||
// because the user could infer that the Application exists if they do not get the "does not exist" message. For
|
||||
// endpoints that do not require the full RBAC name, we must return a generic "permission denied" for both "does not
|
||||
// exist" and "no access."
|
||||
|
||||
f := func(enf *rbac.Enforcer) {
|
||||
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
|
||||
enf.SetDefaultRole("role:none")
|
||||
}
|
||||
deployment := k8sappsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "test",
|
||||
},
|
||||
}
|
||||
testApp := newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "test"
|
||||
app.Status.Resources = []appsv1.ResourceStatus{
|
||||
{
|
||||
Group: deployment.GroupVersionKind().Group,
|
||||
Kind: deployment.GroupVersionKind().Kind,
|
||||
Version: deployment.GroupVersionKind().Version,
|
||||
Name: deployment.Name,
|
||||
Namespace: deployment.Namespace,
|
||||
Status: "Synced",
|
||||
},
|
||||
}
|
||||
app.Status.History = []appsv1.RevisionHistory{
|
||||
{
|
||||
ID: 0,
|
||||
Source: appsv1.ApplicationSource{
|
||||
TargetRevision: "something-old",
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
testDeployment := kube.MustToUnstructured(&deployment)
|
||||
appServer := newTestAppServerWithEnforcerConfigure(f, t, testApp, testDeployment)
|
||||
|
||||
noRoleCtx := context.Background()
|
||||
// nolint:staticcheck
|
||||
adminCtx := context.WithValue(noRoleCtx, "claims", &jwt.MapClaims{"groups": []string{"admin"}})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
_, err := appServer.Get(adminCtx, &application.ApplicationQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Get(noRoleCtx, &application.ApplicationQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Get(adminCtx, &application.ApplicationQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("GetManifests", func(t *testing.T) {
|
||||
_, err := appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.GetManifests(noRoleCtx, &application.ApplicationManifestQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ListResourceEvents", func(t *testing.T) {
|
||||
_, err := appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ListResourceEvents(noRoleCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("UpdateSpec", func(t *testing.T) {
|
||||
_, err := appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("test"), Spec: &appsv1.ApplicationSpec{
|
||||
Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.com"},
|
||||
Source: &appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
|
||||
}})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.UpdateSpec(noRoleCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("test"), Spec: &appsv1.ApplicationSpec{
|
||||
Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.com"},
|
||||
Source: &appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
|
||||
}})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("doest-not-exist"), Spec: &appsv1.ApplicationSpec{
|
||||
Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.com"},
|
||||
Source: &appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
|
||||
}})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("Patch", func(t *testing.T) {
|
||||
_, err := appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Patch(noRoleCtx, &application.ApplicationPatchRequest{Name: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("GetResource", func(t *testing.T) {
|
||||
_, err := appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.GetResource(noRoleCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("PatchResource", func(t *testing.T) {
|
||||
_, err := appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
|
||||
// This will always throw an error, because the kubectl mock for PatchResource is hard-coded to return nil.
|
||||
// The best we can do is to confirm we get past the permission check.
|
||||
assert.NotEqual(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.PatchResource(noRoleCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("DeleteResource", func(t *testing.T) {
|
||||
_, err := appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.DeleteResource(noRoleCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ResourceTree", func(t *testing.T) {
|
||||
_, err := appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ResourceTree(noRoleCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("RevisionMetadata", func(t *testing.T) {
|
||||
_, err := appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.RevisionMetadata(noRoleCtx, &application.RevisionMetadataQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ManagedResources", func(t *testing.T) {
|
||||
_, err := appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ManagedResources(noRoleCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("Sync", func(t *testing.T) {
|
||||
_, err := appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Sync(noRoleCtx, &application.ApplicationSyncRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("TerminateOperation", func(t *testing.T) {
|
||||
// The sync operation is already started from the previous test. We just need to set the field that the
|
||||
// controller would set if this were an actual Argo CD environment.
|
||||
setSyncRunningOperationState(t, appServer)
|
||||
_, err := appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.TerminateOperation(noRoleCtx, &application.OperationTerminateRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("Rollback", func(t *testing.T) {
|
||||
unsetSyncRunningOperationState(t, appServer)
|
||||
_, err := appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Rollback(noRoleCtx, &application.ApplicationRollbackRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ListResourceActions", func(t *testing.T) {
|
||||
_, err := appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ListResourceActions(noRoleCtx, &application.ApplicationResourceRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ListResourceActions(noRoleCtx, &application.ApplicationResourceRequest{Group: pointer.String("argoproj.io"), Kind: pointer.String("Application"), Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("RunResourceAction", func(t *testing.T) {
|
||||
_, err := appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Action: pointer.String("restart")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.RunResourceAction(noRoleCtx, &application.ResourceActionRunRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.RunResourceAction(noRoleCtx, &application.ResourceActionRunRequest{Group: pointer.String("argoproj.io"), Kind: pointer.String("Application"), Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("GetApplicationSyncWindows", func(t *testing.T) {
|
||||
_, err := appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.GetApplicationSyncWindows(noRoleCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("GetManifestsWithFiles", func(t *testing.T) {
|
||||
err := appServer.GetManifestsWithFiles(&TestServerStream{ctx: adminCtx, appName: "test"})
|
||||
assert.NoError(t, err)
|
||||
err = appServer.GetManifestsWithFiles(&TestServerStream{ctx: noRoleCtx, appName: "test"})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
err = appServer.GetManifestsWithFiles(&TestServerStream{ctx: adminCtx, appName: "does-not-exist"})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("WatchResourceTree", func(t *testing.T) {
|
||||
err := appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("test")}, &TestResourceTreeServer{ctx: adminCtx})
|
||||
assert.NoError(t, err)
|
||||
err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("test")}, &TestResourceTreeServer{ctx: noRoleCtx})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("does-not-exist")}, &TestResourceTreeServer{ctx: adminCtx})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("PodLogs", func(t *testing.T) {
|
||||
err := appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("test")}, &TestPodLogsServer{ctx: adminCtx})
|
||||
assert.NoError(t, err)
|
||||
err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("test")}, &TestPodLogsServer{ctx: noRoleCtx})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("does-not-exist")}, &TestPodLogsServer{ctx: adminCtx})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ListLinks", func(t *testing.T) {
|
||||
_, err := appServer.ListLinks(adminCtx, &application.ListAppLinksRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ListLinks(noRoleCtx, &application.ListAppLinksRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ListLinks(adminCtx, &application.ListAppLinksRequest{Name: pointer.String("does-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ListResourceLinks", func(t *testing.T) {
|
||||
_, err := appServer.ListResourceLinks(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ListResourceLinks(noRoleCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ListResourceLinks(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("does-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
// Do this last so other stuff doesn't fail.
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
_, err := appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Delete(noRoleCtx, &application.ApplicationDeleteRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
}
|
||||
|
||||
// setSyncRunningOperationState simulates starting a sync operation on the given app.
|
||||
func setSyncRunningOperationState(t *testing.T, appServer *Server) {
|
||||
appIf := appServer.appclientset.ArgoprojV1alpha1().Applications("default")
|
||||
app, err := appIf.Get(context.Background(), "test", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
// This sets the status that would be set by the controller usually.
|
||||
app.Status.OperationState = &appsv1.OperationState{Phase: synccommon.OperationRunning, Operation: appsv1.Operation{Sync: &appsv1.SyncOperation{}}}
|
||||
_, err = appIf.Update(context.Background(), app, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// unsetSyncRunningOperationState simulates finishing a sync operation on the given app.
|
||||
func unsetSyncRunningOperationState(t *testing.T, appServer *Server) {
|
||||
appIf := appServer.appclientset.ArgoprojV1alpha1().Applications("default")
|
||||
app, err := appIf.Get(context.Background(), "test", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
app.Operation = nil
|
||||
app.Status.OperationState = nil
|
||||
_, err = appIf.Update(context.Background(), app, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestListAppsInNamespaceWithLabels(t *testing.T) {
|
||||
appServer := newTestAppServer(newTestApp(func(app *appsv1.Application) {
|
||||
appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "App1"
|
||||
app.ObjectMeta.Namespace = "test-namespace"
|
||||
app.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
|
||||
@@ -323,7 +819,7 @@ func TestListAppsInNamespaceWithLabels(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListAppsInDefaultNSWithLabels(t *testing.T) {
|
||||
appServer := newTestAppServer(newTestApp(func(app *appsv1.Application) {
|
||||
appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "App1"
|
||||
app.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
|
||||
}), newTestApp(func(app *appsv1.Application) {
|
||||
@@ -402,7 +898,7 @@ func testListAppsWithLabels(t *testing.T, appQuery application.ApplicationQuery,
|
||||
}
|
||||
|
||||
func TestListAppWithProjects(t *testing.T) {
|
||||
appServer := newTestAppServer(newTestApp(func(app *appsv1.Application) {
|
||||
appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "App1"
|
||||
app.Spec.Project = "test-project1"
|
||||
}), newTestApp(func(app *appsv1.Application) {
|
||||
@@ -453,7 +949,7 @@ func TestListAppWithProjects(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListApps(t *testing.T) {
|
||||
appServer := newTestAppServer(newTestApp(func(app *appsv1.Application) {
|
||||
appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "bcd"
|
||||
}), newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "abc"
|
||||
@@ -501,7 +997,7 @@ g, group-49, role:test3
|
||||
`
|
||||
_ = enf.SetUserPolicy(policy)
|
||||
}
|
||||
appServer := newTestAppServerWithEnforcerConfigure(f, objects...)
|
||||
appServer := newTestAppServerWithEnforcerConfigure(f, t, objects...)
|
||||
|
||||
res, err := appServer.List(ctx, &application.ApplicationQuery{})
|
||||
|
||||
@@ -515,7 +1011,7 @@ g, group-49, role:test3
|
||||
|
||||
func TestCreateApp(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp.Spec.Project = ""
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
Application: testApp,
|
||||
@@ -528,7 +1024,7 @@ func TestCreateApp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateAppWithDestName(t *testing.T) {
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestAppWithDestName()
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
Application: testApp,
|
||||
@@ -541,7 +1037,7 @@ func TestCreateAppWithDestName(t *testing.T) {
|
||||
|
||||
func TestUpdateApp(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
testApp.Spec.Project = ""
|
||||
app, err := appServer.Update(context.Background(), &application.ApplicationUpdateRequest{
|
||||
Application: testApp,
|
||||
@@ -552,7 +1048,7 @@ func TestUpdateApp(t *testing.T) {
|
||||
|
||||
func TestUpdateAppSpec(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
testApp.Spec.Project = ""
|
||||
spec, err := appServer.UpdateSpec(context.Background(), &application.ApplicationUpdateSpecRequest{
|
||||
Name: &testApp.Name,
|
||||
@@ -567,7 +1063,7 @@ func TestUpdateAppSpec(t *testing.T) {
|
||||
|
||||
func TestDeleteApp(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
Application: newTestApp(),
|
||||
}
|
||||
@@ -655,20 +1151,9 @@ func TestDeleteApp(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteApp_InvalidName(t *testing.T) {
|
||||
appServer := newTestAppServer()
|
||||
_, err := appServer.Delete(context.Background(), &application.ApplicationDeleteRequest{
|
||||
Name: pointer.StringPtr("foo"),
|
||||
})
|
||||
if !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
assert.True(t, apierrors.IsNotFound(err))
|
||||
}
|
||||
|
||||
func TestSyncAndTerminate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Source.RepoURL = "https://github.com/argoproj/argo-cd.git"
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
@@ -708,7 +1193,7 @@ func TestSyncAndTerminate(t *testing.T) {
|
||||
|
||||
func TestSyncHelm(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Source.RepoURL = "https://argoproj.github.io/argo-helm"
|
||||
testApp.Spec.Source.Path = ""
|
||||
@@ -732,7 +1217,7 @@ func TestSyncHelm(t *testing.T) {
|
||||
|
||||
func TestSyncGit(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Source.RepoURL = "https://github.com/org/test"
|
||||
testApp.Spec.Source.Path = "deploy"
|
||||
@@ -765,7 +1250,7 @@ func TestRollbackApp(t *testing.T) {
|
||||
Revision: "abc",
|
||||
Source: *testApp.Spec.Source.DeepCopy(),
|
||||
}}
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
updatedApp, err := appServer.Rollback(context.Background(), &application.ApplicationRollbackRequest{
|
||||
Name: &testApp.Name,
|
||||
@@ -785,56 +1270,63 @@ func TestUpdateAppProject(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
appServer.enf.SetDefaultRole("")
|
||||
|
||||
// Verify normal update works (without changing project)
|
||||
_ = appServer.enf.SetBuiltinPolicy(`p, admin, applications, update, default/test-app, allow`)
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.NoError(t, err)
|
||||
t.Run("update without changing project", func(t *testing.T) {
|
||||
_ = appServer.enf.SetBuiltinPolicy(`p, admin, applications, update, default/test-app, allow`)
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Verify caller cannot update to another project
|
||||
testApp.Spec.Project = "my-proj"
|
||||
_, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.Equal(t, status.Code(err), codes.PermissionDenied)
|
||||
t.Run("cannot update to another project", func(t *testing.T) {
|
||||
testApp.Spec.Project = "my-proj"
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.Equal(t, status.Code(err), codes.PermissionDenied)
|
||||
})
|
||||
|
||||
// Verify inability to change projects without create privileges in new project
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
t.Run("cannot change projects without create privileges", func(t *testing.T) {
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
p, admin, applications, update, default/test-app, allow
|
||||
p, admin, applications, update, my-proj/test-app, allow
|
||||
`)
|
||||
_, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
statusErr := grpc.UnwrapGRPCStatus(err)
|
||||
assert.NotNil(t, statusErr)
|
||||
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
statusErr := grpc.UnwrapGRPCStatus(err)
|
||||
assert.NotNil(t, statusErr)
|
||||
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
|
||||
})
|
||||
|
||||
// Verify inability to change projects without update privileges in new project
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
t.Run("cannot change projects without update privileges in new project", func(t *testing.T) {
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
p, admin, applications, update, default/test-app, allow
|
||||
p, admin, applications, create, my-proj/test-app, allow
|
||||
`)
|
||||
_, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.Equal(t, status.Code(err), codes.PermissionDenied)
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.Equal(t, status.Code(err), codes.PermissionDenied)
|
||||
})
|
||||
|
||||
// Verify inability to change projects without update privileges in old project
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
t.Run("cannot change projects without update privileges in old project", func(t *testing.T) {
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
p, admin, applications, create, my-proj/test-app, allow
|
||||
p, admin, applications, update, my-proj/test-app, allow
|
||||
`)
|
||||
_, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
statusErr = grpc.UnwrapGRPCStatus(err)
|
||||
assert.NotNil(t, statusErr)
|
||||
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
statusErr := grpc.UnwrapGRPCStatus(err)
|
||||
assert.NotNil(t, statusErr)
|
||||
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
|
||||
})
|
||||
|
||||
// Verify can update project with proper permissions
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
t.Run("can update project with proper permissions", func(t *testing.T) {
|
||||
// Verify can update project with proper permissions
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
p, admin, applications, update, default/test-app, allow
|
||||
p, admin, applications, create, my-proj/test-app, allow
|
||||
p, admin, applications, update, my-proj/test-app, allow
|
||||
`)
|
||||
updatedApp, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "my-proj", updatedApp.Spec.Project)
|
||||
updatedApp, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "my-proj", updatedApp.Spec.Project)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppJsonPatch(t *testing.T) {
|
||||
@@ -842,7 +1334,7 @@ func TestAppJsonPatch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
appServer.enf.SetDefaultRole("")
|
||||
|
||||
app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: pointer.String("garbage")})
|
||||
@@ -867,7 +1359,7 @@ func TestAppMergePatch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
appServer.enf.SetDefaultRole("")
|
||||
|
||||
app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{
|
||||
@@ -880,7 +1372,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
|
||||
t.Run("Active", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Project = "proj-maint"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
|
||||
assert.NoError(t, err)
|
||||
@@ -889,7 +1381,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
|
||||
t.Run("Inactive", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Project = "default"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
|
||||
assert.NoError(t, err)
|
||||
@@ -898,7 +1390,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
|
||||
t.Run("ProjectDoesNotExist", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Project = "none"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
@@ -916,7 +1408,7 @@ func TestGetCachedAppState(t *testing.T) {
|
||||
Namespace: testNamespace,
|
||||
},
|
||||
}
|
||||
appServer := newTestAppServer(testApp, testProj)
|
||||
appServer := newTestAppServer(t, testApp, testProj)
|
||||
fakeClientSet := appServer.appclientset.(*apps.Clientset)
|
||||
fakeClientSet.AddReactor("get", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, &appsv1.Application{Spec: appsv1.ApplicationSpec{Source: &appsv1.ApplicationSource{}}}, nil
|
||||
@@ -1095,7 +1587,7 @@ func TestGetAppRefresh_NormalRefresh(t *testing.T) {
|
||||
defer cancel()
|
||||
testApp := newTestApp()
|
||||
testApp.ObjectMeta.ResourceVersion = "1"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
var patched int32
|
||||
|
||||
@@ -1123,7 +1615,7 @@ func TestGetAppRefresh_HardRefresh(t *testing.T) {
|
||||
defer cancel()
|
||||
testApp := newTestApp()
|
||||
testApp.ObjectMeta.ResourceVersion = "1"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
var getAppDetailsQuery *apiclient.RepoServerAppDetailsQuery
|
||||
mockRepoServiceClient := mocks.RepoServerServiceClient{}
|
||||
@@ -1173,7 +1665,7 @@ func TestInferResourcesStatusHealth(t *testing.T) {
|
||||
Name: "guestbook-stateful",
|
||||
Namespace: "default",
|
||||
}}
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
appStateCache := appstate.NewCache(cacheClient, time.Minute)
|
||||
err := appStateCache.SetAppResourcesTree(testApp.Name, &appsv1.ApplicationTree{Nodes: []appsv1.ResourceNode{{
|
||||
ResourceRef: appsv1.ResourceRef{
|
||||
|
||||
@@ -23,6 +23,14 @@ func (s *subscriber) matches(event *appv1.ApplicationWatchEvent) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Broadcaster is an interface for broadcasting application informer watch events to multiple subscribers.
|
||||
type Broadcaster interface {
|
||||
Subscribe(ch chan *appv1.ApplicationWatchEvent, filters ...func(event *appv1.ApplicationWatchEvent) bool) func()
|
||||
OnAdd(interface{})
|
||||
OnUpdate(interface{}, interface{})
|
||||
OnDelete(interface{})
|
||||
}
|
||||
|
||||
type broadcasterHandler struct {
|
||||
lock sync.Mutex
|
||||
subscribers []*subscriber
|
||||
|
||||
66
server/application/mocks/Broadcaster.go
Normal file
66
server/application/mocks/Broadcaster.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Code generated by mockery v2.13.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Broadcaster is an autogenerated mock type for the Broadcaster type
|
||||
type Broadcaster struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// OnAdd provides a mock function with given fields: _a0
|
||||
func (_m *Broadcaster) OnAdd(_a0 interface{}) {
|
||||
_m.Called(_a0)
|
||||
}
|
||||
|
||||
// OnDelete provides a mock function with given fields: _a0
|
||||
func (_m *Broadcaster) OnDelete(_a0 interface{}) {
|
||||
_m.Called(_a0)
|
||||
}
|
||||
|
||||
// OnUpdate provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Broadcaster) OnUpdate(_a0 interface{}, _a1 interface{}) {
|
||||
_m.Called(_a0, _a1)
|
||||
}
|
||||
|
||||
// Subscribe provides a mock function with given fields: ch, filters
|
||||
func (_m *Broadcaster) Subscribe(ch chan *v1alpha1.ApplicationWatchEvent, filters ...func(*v1alpha1.ApplicationWatchEvent) bool) func() {
|
||||
_va := make([]interface{}, len(filters))
|
||||
for _i := range filters {
|
||||
_va[_i] = filters[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ch)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 func()
|
||||
if rf, ok := ret.Get(0).(func(chan *v1alpha1.ApplicationWatchEvent, ...func(*v1alpha1.ApplicationWatchEvent) bool) func()); ok {
|
||||
r0 = rf(ch, filters...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(func())
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewBroadcaster interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewBroadcaster creates a new instance of Broadcaster. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewBroadcaster(t mockConstructorTestingTNewBroadcaster) *Broadcaster {
|
||||
mock := &Broadcaster{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -769,6 +769,7 @@ func newArgoCDServiceSet(a *ArgoCDServer) *ArgoCDServiceSet {
|
||||
a.AppClientset,
|
||||
a.appLister,
|
||||
a.appInformer,
|
||||
nil,
|
||||
a.RepoClientset,
|
||||
a.Cache,
|
||||
kubectl,
|
||||
@@ -946,7 +947,8 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
|
||||
|
||||
// Webhook handler for git events (Note: cache timeouts are hardcoded because API server does not write to cache and not really using them)
|
||||
argoDB := db.NewDB(a.Namespace, a.settingsMgr, a.KubeClientset)
|
||||
acdWebhookHandler := webhook.NewHandler(a.Namespace, a.AppClientset, a.settings, a.settingsMgr, repocache.NewCache(a.Cache.GetCache(), 24*time.Hour, 3*time.Minute), a.Cache, argoDB)
|
||||
acdWebhookHandler := webhook.NewHandler(a.Namespace, a.ArgoCDServerOpts.ApplicationNamespaces, a.AppClientset, a.settings, a.settingsMgr, repocache.NewCache(a.Cache.GetCache(), 24*time.Hour, 3*time.Minute), a.Cache, argoDB)
|
||||
|
||||
mux.HandleFunc("/api/webhook", acdWebhookHandler.Handler)
|
||||
|
||||
// Serve cli binaries directly from API server
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/redis:7.0.8-alpine as redis
|
||||
FROM docker.io/library/redis:7.0.11-alpine as redis
|
||||
|
||||
# There are libraries we will want to copy from here in the final stage of the
|
||||
# build, but the COPY directive does not have a way to determine system
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
|
||||
api-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} "
|
||||
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.35.3 serve /dex.yaml"
|
||||
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
|
||||
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} "
|
||||
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.36.0 serve /dex.yaml"
|
||||
redis: sh -c "/usr/local/bin/redis-server --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
repo-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_BINARY_NAME=argocd-repo-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
ui: sh -c "test $ARGOCD_IN_CI = true && exit 0; cd ui && ARGOCD_E2E_YARN_HOST=0.0.0.0 ${ARGOCD_E2E_YARN_CMD:-yarn} start"
|
||||
|
||||
@@ -429,7 +429,9 @@ func TestNamespacedInvalidAppProject(t *testing.T) {
|
||||
IgnoreErrors().
|
||||
CreateApp().
|
||||
Then().
|
||||
Expect(Error("", "application references project does-not-exist which does not exist"))
|
||||
// We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic
|
||||
// permission denied error.
|
||||
Expect(Error("", "permission denied"))
|
||||
}
|
||||
|
||||
func TestNamespacedAppDeletion(t *testing.T) {
|
||||
|
||||
@@ -412,7 +412,9 @@ func TestInvalidAppProject(t *testing.T) {
|
||||
IgnoreErrors().
|
||||
CreateApp().
|
||||
Then().
|
||||
Expect(Error("", "application references project does-not-exist which does not exist"))
|
||||
// We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic
|
||||
// permission denied error.
|
||||
Expect(Error("", "permission denied"))
|
||||
}
|
||||
|
||||
func TestAppDeletion(t *testing.T) {
|
||||
|
||||
@@ -156,7 +156,7 @@ func TestCustomToolWithEnv(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
//make sure we can sync and diff with --local
|
||||
// make sure we can sync and diff with --local
|
||||
func TestCustomToolSyncAndDiffLocal(t *testing.T) {
|
||||
ctx := Given(t)
|
||||
ctx.
|
||||
@@ -203,7 +203,7 @@ func startCMPServer(configFile string) {
|
||||
FailOnErr(RunWithStdin("", "", "../../dist/argocd", "--config-dir-path", configFile))
|
||||
}
|
||||
|
||||
//Discover by fileName
|
||||
// Discover by fileName
|
||||
func TestCMPDiscoverWithFileName(t *testing.T) {
|
||||
pluginName := "cmp-fileName"
|
||||
Given(t).
|
||||
@@ -212,7 +212,7 @@ func TestCMPDiscoverWithFileName(t *testing.T) {
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Setenv("ARGOCD_BINARY_NAME", "argocd")
|
||||
}).
|
||||
Path(pluginName).
|
||||
Path(pluginName + "/subdir").
|
||||
When().
|
||||
CreateApp().
|
||||
Sync().
|
||||
@@ -222,7 +222,7 @@ func TestCMPDiscoverWithFileName(t *testing.T) {
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
//Discover by Find glob
|
||||
// Discover by Find glob
|
||||
func TestCMPDiscoverWithFindGlob(t *testing.T) {
|
||||
Given(t).
|
||||
And(func() {
|
||||
@@ -240,7 +240,7 @@ func TestCMPDiscoverWithFindGlob(t *testing.T) {
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
//Discover by Plugin Name
|
||||
// Discover by Plugin Name
|
||||
func TestCMPDiscoverWithPluginName(t *testing.T) {
|
||||
Given(t).
|
||||
And(func() {
|
||||
@@ -261,7 +261,7 @@ func TestCMPDiscoverWithPluginName(t *testing.T) {
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
//Discover by Find command
|
||||
// Discover by Find command
|
||||
func TestCMPDiscoverWithFindCommandWithEnv(t *testing.T) {
|
||||
pluginName := "cmp-find-command"
|
||||
ctx := Given(t)
|
||||
@@ -329,3 +329,54 @@ func TestPruneResourceFromCMP(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCMPWithSymlinkPartialFiles(t *testing.T) {
|
||||
Given(t, WithTestData("testdata2")).
|
||||
And(func() {
|
||||
go startCMPServer("./testdata2/cmp-symlink")
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Setenv("ARGOCD_BINARY_NAME", "argocd")
|
||||
}).
|
||||
Path("guestbook-partial-symlink-files").
|
||||
When().
|
||||
CreateApp().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
func TestCMPWithSymlinkFiles(t *testing.T) {
|
||||
Given(t, WithTestData("testdata2")).
|
||||
And(func() {
|
||||
go startCMPServer("./testdata2/cmp-symlink")
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Setenv("ARGOCD_BINARY_NAME", "argocd")
|
||||
}).
|
||||
Path("guestbook-symlink-files").
|
||||
When().
|
||||
CreateApp().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
func TestCMPWithSymlinkFolder(t *testing.T) {
|
||||
Given(t, WithTestData("testdata2")).
|
||||
And(func() {
|
||||
go startCMPServer("./testdata2/cmp-symlink")
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Setenv("ARGOCD_BINARY_NAME", "argocd")
|
||||
}).
|
||||
Path("guestbook-symlink-folder").
|
||||
When().
|
||||
CreateApp().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ type ContextArgs struct {
|
||||
AppNamespace string
|
||||
}
|
||||
|
||||
func Given(t *testing.T) *Context {
|
||||
fixture.EnsureCleanState(t)
|
||||
func Given(t *testing.T, opts ...fixture.TestOption) *Context {
|
||||
fixture.EnsureCleanState(t, opts...)
|
||||
return GivenWithSameState(t)
|
||||
}
|
||||
|
||||
|
||||
@@ -514,7 +514,30 @@ func SetParamInNotificationsConfigMap(key, value string) {
|
||||
})
|
||||
}
|
||||
|
||||
func EnsureCleanState(t *testing.T) {
|
||||
type TestOption func(option *testOption)
|
||||
|
||||
type testOption struct {
|
||||
testdata string
|
||||
}
|
||||
|
||||
func newTestOption(opts ...TestOption) *testOption {
|
||||
to := &testOption{
|
||||
testdata: "testdata",
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(to)
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
func WithTestData(testdata string) TestOption {
|
||||
return func(option *testOption) {
|
||||
option.testdata = testdata
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureCleanState(t *testing.T, opts ...TestOption) {
|
||||
opt := newTestOption(opts...)
|
||||
// In large scenarios, we can skip tests that already run
|
||||
SkipIfAlreadyRun(t)
|
||||
// Register this test after it has been run & was successfull
|
||||
@@ -632,7 +655,7 @@ func EnsureCleanState(t *testing.T) {
|
||||
}
|
||||
|
||||
// set-up tmp repo, must have unique name
|
||||
FailOnErr(Run("", "cp", "-Rf", "testdata", repoDirectory()))
|
||||
FailOnErr(Run("", "cp", "-Rf", opt.testdata, repoDirectory()))
|
||||
FailOnErr(Run(repoDirectory(), "chmod", "777", "."))
|
||||
FailOnErr(Run(repoDirectory(), "git", "init"))
|
||||
FailOnErr(Run(repoDirectory(), "git", "add", "."))
|
||||
|
||||
2
test/e2e/testdata/cmp-fileName/plugin.yaml
vendored
2
test/e2e/testdata/cmp-fileName/plugin.yaml
vendored
@@ -7,4 +7,4 @@ spec:
|
||||
generate:
|
||||
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"']
|
||||
discover:
|
||||
fileName: "subdir/s*.yaml"
|
||||
fileName: "cmp-fileName/subdir/s*.yaml"
|
||||
|
||||
13
test/e2e/testdata2/cmp-symlink/plugin.yaml
Normal file
13
test/e2e/testdata2/cmp-symlink/plugin.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: cmp-symlink
|
||||
spec:
|
||||
version: v1.0
|
||||
init:
|
||||
command: [kustomize, version]
|
||||
generate:
|
||||
command: [sh, -c, 'kustomize edit set image test=quay.io/argoprojlabs/argocd-e2e-container:0.2 && kustomize build --load-restrictor LoadRestrictionsNone']
|
||||
discover:
|
||||
find:
|
||||
glob: "**/kustomization.yaml"
|
||||
@@ -0,0 +1 @@
|
||||
../guestbook/guestbook-ui-deployment.yaml
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: guestbook-ui
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: guestbook-ui
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ./guestbook-ui-deployment.yaml
|
||||
- ./guestbook-ui-svc.yaml
|
||||
@@ -0,0 +1 @@
|
||||
../guestbook/guestbook-ui-deployment.yaml
|
||||
1
test/e2e/testdata2/guestbook-symlink-files/guestbook-ui-svc.yaml
Symbolic link
1
test/e2e/testdata2/guestbook-symlink-files/guestbook-ui-svc.yaml
Symbolic link
@@ -0,0 +1 @@
|
||||
../guestbook/guestbook-ui-svc.yaml
|
||||
1
test/e2e/testdata2/guestbook-symlink-files/kustomization.yaml
Symbolic link
1
test/e2e/testdata2/guestbook-symlink-files/kustomization.yaml
Symbolic link
@@ -0,0 +1 @@
|
||||
../guestbook/kustomization.yaml
|
||||
1
test/e2e/testdata2/guestbook-symlink-folder
Symbolic link
1
test/e2e/testdata2/guestbook-symlink-folder
Symbolic link
@@ -0,0 +1 @@
|
||||
guestbook
|
||||
23
test/e2e/testdata2/guestbook/guestbook-ui-deployment.yaml
Normal file
23
test/e2e/testdata2/guestbook/guestbook-ui-deployment.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: guestbook-ui
|
||||
labels:
|
||||
test: "true"
|
||||
spec:
|
||||
replicas: 0
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guestbook-ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: test
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
10
test/e2e/testdata2/guestbook/guestbook-ui-svc.yaml
Normal file
10
test/e2e/testdata2/guestbook/guestbook-ui-svc.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: guestbook-ui
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: guestbook-ui
|
||||
6
test/e2e/testdata2/guestbook/kustomization.yaml
Normal file
6
test/e2e/testdata2/guestbook/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ./guestbook-ui-deployment.yaml
|
||||
- ./guestbook-ui-svc.yaml
|
||||
@@ -31,11 +31,11 @@ func IsManifestGenerationEnabled(sourceType v1alpha1.ApplicationSourceType, enab
|
||||
return enabled
|
||||
}
|
||||
|
||||
func Discover(ctx context.Context, repoPath string, enableGenerateManifests map[string]bool, tarExcludedGlobs []string) (map[string]string, error) {
|
||||
func Discover(ctx context.Context, appPath, repoPath string, enableGenerateManifests map[string]bool, tarExcludedGlobs []string) (map[string]string, error) {
|
||||
apps := make(map[string]string)
|
||||
|
||||
// Check if it is CMP
|
||||
conn, _, err := DetectConfigManagementPlugin(ctx, repoPath, "", []string{}, tarExcludedGlobs)
|
||||
conn, _, err := DetectConfigManagementPlugin(ctx, appPath, repoPath, "", []string{}, tarExcludedGlobs)
|
||||
if err == nil {
|
||||
// Found CMP
|
||||
io.Close(conn)
|
||||
@@ -44,14 +44,14 @@ func Discover(ctx context.Context, repoPath string, enableGenerateManifests map[
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
err = filepath.Walk(repoPath, func(path string, info os.FileInfo, err error) error {
|
||||
err = filepath.Walk(appPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
dir, err := filepath.Rel(repoPath, filepath.Dir(path))
|
||||
dir, err := filepath.Rel(appPath, filepath.Dir(path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -67,8 +67,8 @@ func Discover(ctx context.Context, repoPath string, enableGenerateManifests map[
|
||||
return apps, err
|
||||
}
|
||||
|
||||
func AppType(ctx context.Context, path string, enableGenerateManifests map[string]bool, tarExcludedGlobs []string) (string, error) {
|
||||
apps, err := Discover(ctx, path, enableGenerateManifests, tarExcludedGlobs)
|
||||
func AppType(ctx context.Context, appPath, repoPath string, enableGenerateManifests map[string]bool, tarExcludedGlobs []string) (string, error) {
|
||||
apps, err := Discover(ctx, appPath, repoPath, enableGenerateManifests, tarExcludedGlobs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func AppType(ctx context.Context, path string, enableGenerateManifests map[strin
|
||||
// check cmpSupports()
|
||||
// if supported return conn for the cmp-server
|
||||
|
||||
func DetectConfigManagementPlugin(ctx context.Context, repoPath, pluginName string, env []string, tarExcludedGlobs []string) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, error) {
|
||||
func DetectConfigManagementPlugin(ctx context.Context, appPath, repoPath, pluginName string, env []string, tarExcludedGlobs []string) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, error) {
|
||||
var conn io.Closer
|
||||
var cmpClient pluginclient.ConfigManagementPluginServiceClient
|
||||
var connFound bool
|
||||
@@ -98,7 +98,7 @@ func DetectConfigManagementPlugin(ctx context.Context, repoPath, pluginName stri
|
||||
|
||||
if pluginName != "" {
|
||||
// check if the given plugin supports the repo
|
||||
conn, cmpClient, connFound = cmpSupports(ctx, pluginSockFilePath, repoPath, fmt.Sprintf("%v.sock", pluginName), env, tarExcludedGlobs, true)
|
||||
conn, cmpClient, connFound = cmpSupports(ctx, pluginSockFilePath, appPath, repoPath, fmt.Sprintf("%v.sock", pluginName), env, tarExcludedGlobs, true)
|
||||
if !connFound {
|
||||
return nil, nil, fmt.Errorf("couldn't find cmp-server plugin with name %v supporting the given repository", pluginName)
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func DetectConfigManagementPlugin(ctx context.Context, repoPath, pluginName stri
|
||||
}
|
||||
for _, file := range fileList {
|
||||
if file.Type() == os.ModeSocket {
|
||||
conn, cmpClient, connFound = cmpSupports(ctx, pluginSockFilePath, repoPath, file.Name(), env, tarExcludedGlobs, false)
|
||||
conn, cmpClient, connFound = cmpSupports(ctx, pluginSockFilePath, appPath, repoPath, file.Name(), env, tarExcludedGlobs, false)
|
||||
if connFound {
|
||||
break
|
||||
}
|
||||
@@ -125,13 +125,13 @@ func DetectConfigManagementPlugin(ctx context.Context, repoPath, pluginName stri
|
||||
// matchRepositoryCMP will send the repoPath to the cmp-server. The cmp-server will
|
||||
// inspect the files and return true if the repo is supported for manifest generation.
|
||||
// Will return false otherwise.
|
||||
func matchRepositoryCMP(ctx context.Context, repoPath string, client pluginclient.ConfigManagementPluginServiceClient, env []string, tarExcludedGlobs []string) (bool, bool, error) {
|
||||
func matchRepositoryCMP(ctx context.Context, appPath, repoPath string, client pluginclient.ConfigManagementPluginServiceClient, env []string, tarExcludedGlobs []string) (bool, bool, error) {
|
||||
matchRepoStream, err := client.MatchRepository(ctx, grpc_retry.Disable())
|
||||
if err != nil {
|
||||
return false, false, fmt.Errorf("error getting stream client: %s", err)
|
||||
}
|
||||
|
||||
err = cmp.SendRepoStream(ctx, repoPath, repoPath, matchRepoStream, env, tarExcludedGlobs)
|
||||
err = cmp.SendRepoStream(ctx, appPath, repoPath, matchRepoStream, env, tarExcludedGlobs)
|
||||
if err != nil {
|
||||
return false, false, fmt.Errorf("error sending stream: %s", err)
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func matchRepositoryCMP(ctx context.Context, repoPath string, client pluginclien
|
||||
return resp.GetIsSupported(), resp.GetIsDiscoveryEnabled(), nil
|
||||
}
|
||||
|
||||
func cmpSupports(ctx context.Context, pluginSockFilePath, repoPath, fileName string, env []string, tarExcludedGlobs []string, namedPlugin bool) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, bool) {
|
||||
func cmpSupports(ctx context.Context, pluginSockFilePath, appPath, repoPath, fileName string, env []string, tarExcludedGlobs []string, namedPlugin bool) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, bool) {
|
||||
address := filepath.Join(pluginSockFilePath, fileName)
|
||||
if !files.Inbound(address, pluginSockFilePath) {
|
||||
log.Errorf("invalid socket file path, %v is outside plugin socket dir %v", fileName, pluginSockFilePath)
|
||||
@@ -160,7 +160,7 @@ func cmpSupports(ctx context.Context, pluginSockFilePath, repoPath, fileName str
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
isSupported, isDiscoveryEnabled, err := matchRepositoryCMP(ctx, repoPath, cmpClient, env, tarExcludedGlobs)
|
||||
isSupported, isDiscoveryEnabled, err := matchRepositoryCMP(ctx, appPath, repoPath, cmpClient, env, tarExcludedGlobs)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
common.SecurityField: common.SecurityMedium,
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestDiscover(t *testing.T) {
|
||||
apps, err := Discover(context.Background(), "./testdata", map[string]bool{}, []string{})
|
||||
apps, err := Discover(context.Background(), "./testdata", "./testdata", map[string]bool{}, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{
|
||||
"foo": "Kustomize",
|
||||
@@ -19,15 +19,15 @@ func TestDiscover(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAppType(t *testing.T) {
|
||||
appType, err := AppType(context.Background(), "./testdata/foo", map[string]bool{}, []string{})
|
||||
appType, err := AppType(context.Background(), "./testdata/foo", "./testdata", map[string]bool{}, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Kustomize", appType)
|
||||
|
||||
appType, err = AppType(context.Background(), "./testdata/baz", map[string]bool{}, []string{})
|
||||
appType, err = AppType(context.Background(), "./testdata/baz", "./testdata", map[string]bool{}, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Helm", appType)
|
||||
|
||||
appType, err = AppType(context.Background(), "./testdata", map[string]bool{}, []string{})
|
||||
appType, err = AppType(context.Background(), "./testdata", "./testdata", map[string]bool{}, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Directory", appType)
|
||||
}
|
||||
@@ -37,15 +37,15 @@ func TestAppType_Disabled(t *testing.T) {
|
||||
string(v1alpha1.ApplicationSourceTypeKustomize): false,
|
||||
string(v1alpha1.ApplicationSourceTypeHelm): false,
|
||||
}
|
||||
appType, err := AppType(context.Background(), "./testdata/foo", enableManifestGeneration, []string{})
|
||||
appType, err := AppType(context.Background(), "./testdata/foo", "./testdata", enableManifestGeneration, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Directory", appType)
|
||||
|
||||
appType, err = AppType(context.Background(), "./testdata/baz", enableManifestGeneration, []string{})
|
||||
appType, err = AppType(context.Background(), "./testdata/baz", "./testdata", enableManifestGeneration, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Directory", appType)
|
||||
|
||||
appType, err = AppType(context.Background(), "./testdata", enableManifestGeneration, []string{})
|
||||
appType, err = AppType(context.Background(), "./testdata", "./testdata", enableManifestGeneration, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Directory", appType)
|
||||
}
|
||||
|
||||
@@ -106,14 +106,14 @@ func SendRepoStream(ctx context.Context, appPath, repoPath string, sender Stream
|
||||
}
|
||||
|
||||
func GetCompressedRepoAndMetadata(repoPath string, appPath string, env []string, excludedGlobs []string, opt *senderOption) (*os.File, *pluginclient.AppStreamRequest, error) {
|
||||
// compress all files in appPath in tgz
|
||||
// compress all files in repoPath in tgz
|
||||
tgz, filesWritten, checksum, err := tgzstream.CompressFiles(repoPath, nil, excludedGlobs)
|
||||
if filesWritten == 0 {
|
||||
return nil, nil, fmt.Errorf("no files to send")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error compressing repo files: %w", err)
|
||||
}
|
||||
if filesWritten == 0 {
|
||||
return nil, nil, fmt.Errorf("no files to send")
|
||||
}
|
||||
if opt != nil && opt.tarDoneChan != nil {
|
||||
opt.tarDoneChan <- true
|
||||
close(opt.tarDoneChan)
|
||||
|
||||
@@ -39,12 +39,12 @@ type RepoStreamReceiver interface {
|
||||
// SendApplicationManifestQueryWithFiles compresses a folder and sends it over the stream
|
||||
func SendApplicationManifestQueryWithFiles(ctx context.Context, stream ApplicationStreamSender, appName string, appNs string, dir string, inclusions []string) error {
|
||||
f, filesWritten, checksum, err := tgzstream.CompressFiles(dir, inclusions, nil)
|
||||
if filesWritten == 0 {
|
||||
return fmt.Errorf("no files to send")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compress files: %w", err)
|
||||
}
|
||||
if filesWritten == 0 {
|
||||
return fmt.Errorf("no files to send")
|
||||
}
|
||||
|
||||
err = stream.Send(&applicationpkg.ApplicationManifestQueryWithFilesWrapper{
|
||||
Part: &applicationpkg.ApplicationManifestQueryWithFilesWrapper_Query{
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/argoproj/argo-cd/v2/util/glob"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -47,6 +48,7 @@ type ArgoCDWebhookHandler struct {
|
||||
serverCache *servercache.Cache
|
||||
db db.ArgoDB
|
||||
ns string
|
||||
appNs []string
|
||||
appClientset appclientset.Interface
|
||||
github *github.Webhook
|
||||
gitlab *gitlab.Webhook
|
||||
@@ -56,7 +58,7 @@ type ArgoCDWebhookHandler struct {
|
||||
settingsSrc settingsSource
|
||||
}
|
||||
|
||||
func NewHandler(namespace string, appClientset appclientset.Interface, set *settings.ArgoCDSettings, settingsSrc settingsSource, repoCache *cache.Cache, serverCache *servercache.Cache, argoDB db.ArgoDB) *ArgoCDWebhookHandler {
|
||||
func NewHandler(namespace string, applicationNamespaces []string, appClientset appclientset.Interface, set *settings.ArgoCDSettings, settingsSrc settingsSource, repoCache *cache.Cache, serverCache *servercache.Cache, argoDB db.ArgoDB) *ArgoCDWebhookHandler {
|
||||
githubWebhook, err := github.New(github.Options.Secret(set.WebhookGitHubSecret))
|
||||
if err != nil {
|
||||
log.Warnf("Unable to init the GitHub webhook")
|
||||
@@ -80,6 +82,7 @@ func NewHandler(namespace string, appClientset appclientset.Interface, set *sett
|
||||
|
||||
acdWebhook := ArgoCDWebhookHandler{
|
||||
ns: namespace,
|
||||
appNs: applicationNamespaces,
|
||||
appClientset: appClientset,
|
||||
github: githubWebhook,
|
||||
gitlab: gitlabWebhook,
|
||||
@@ -216,7 +219,14 @@ func (a *ArgoCDWebhookHandler) HandleEvent(payload interface{}) {
|
||||
for _, webURL := range webURLs {
|
||||
log.Infof("Received push event repo: %s, revision: %s, touchedHead: %v", webURL, revision, touchedHead)
|
||||
}
|
||||
appIf := a.appClientset.ArgoprojV1alpha1().Applications(a.ns)
|
||||
|
||||
nsFilter := a.ns
|
||||
if len(a.appNs) > 0 {
|
||||
// Retrieve app from all namespaces
|
||||
nsFilter = ""
|
||||
}
|
||||
|
||||
appIf := a.appClientset.ArgoprojV1alpha1().Applications(nsFilter)
|
||||
apps, err := appIf.List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Warnf("Failed to list applications: %v", err)
|
||||
@@ -234,13 +244,23 @@ func (a *ArgoCDWebhookHandler) HandleEvent(payload interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip any application that is neither in the control plane's namespace
|
||||
// nor in the list of enabled namespaces.
|
||||
var filteredApps []v1alpha1.Application
|
||||
for _, app := range apps.Items {
|
||||
if app.Namespace == a.ns || glob.MatchStringInList(a.appNs, app.Namespace, false) {
|
||||
filteredApps = append(filteredApps, app)
|
||||
}
|
||||
}
|
||||
|
||||
for _, webURL := range webURLs {
|
||||
repoRegexp, err := getWebUrlRegex(webURL)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to get repoRegexp: %s", err)
|
||||
continue
|
||||
}
|
||||
for _, app := range apps.Items {
|
||||
for _, app := range filteredApps {
|
||||
|
||||
for _, source := range app.Spec.GetSources() {
|
||||
if sourceRevisionHasChanged(source, revision, touchedHead) && sourceUsesURL(source, webURL, repoRegexp) {
|
||||
if appFilesHaveChanged(&app, changedFiles) {
|
||||
|
||||
@@ -53,7 +53,7 @@ type reactorDef struct {
|
||||
reaction kubetesting.ReactionFunc
|
||||
}
|
||||
|
||||
func NewMockHandler(reactor *reactorDef, objects ...runtime.Object) *ArgoCDWebhookHandler {
|
||||
func NewMockHandler(reactor *reactorDef, applicationNamespaces []string, objects ...runtime.Object) *ArgoCDWebhookHandler {
|
||||
appClientset := appclientset.NewSimpleClientset(objects...)
|
||||
if reactor != nil {
|
||||
defaultReactor := appClientset.ReactionChain[0]
|
||||
@@ -64,7 +64,8 @@ func NewMockHandler(reactor *reactorDef, objects ...runtime.Object) *ArgoCDWebho
|
||||
appClientset.AddReactor(reactor.verb, reactor.resource, reactor.reaction)
|
||||
}
|
||||
cacheClient := cacheutil.NewCache(cacheutil.NewInMemoryCache(1 * time.Hour))
|
||||
return NewHandler("", appClientset, &settings.ArgoCDSettings{}, &fakeSettingsSrc{}, cache.NewCache(
|
||||
|
||||
return NewHandler("argocd", applicationNamespaces, appClientset, &settings.ArgoCDSettings{}, &fakeSettingsSrc{}, cache.NewCache(
|
||||
cacheClient,
|
||||
1*time.Minute,
|
||||
1*time.Minute,
|
||||
@@ -73,8 +74,8 @@ func NewMockHandler(reactor *reactorDef, objects ...runtime.Object) *ArgoCDWebho
|
||||
|
||||
func TestGitHubCommitEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
|
||||
assert.NoError(t, err)
|
||||
@@ -98,9 +99,10 @@ func TestGitHubCommitEvent_MultiSource_Refresh(t *testing.T) {
|
||||
patched = true
|
||||
return true, nil, nil
|
||||
}
|
||||
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, &v1alpha1.Application{
|
||||
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, []string{}, &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-refresh",
|
||||
Name: "app-to-refresh",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
@@ -126,8 +128,9 @@ func TestGitHubCommitEvent_MultiSource_Refresh(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
},
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
|
||||
assert.NoError(t, err)
|
||||
@@ -141,10 +144,106 @@ func TestGitHubCommitEvent_MultiSource_Refresh(t *testing.T) {
|
||||
hook.Reset()
|
||||
}
|
||||
|
||||
// TestGitHubCommitEvent_AppsInOtherNamespaces makes sure that webhooks properly find apps in the configured set of
|
||||
// allowed namespaces when Apps are allowed in any namespace
|
||||
func TestGitHubCommitEvent_AppsInOtherNamespaces(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
|
||||
patchedApps := make([]string, 0, 3)
|
||||
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
patchAction := action.(kubetesting.PatchAction)
|
||||
patchedApps = append(patchedApps, patchAction.GetName())
|
||||
return true, nil, nil
|
||||
}
|
||||
|
||||
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, []string{"end-to-end-tests", "app-team-*"},
|
||||
&v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-refresh-in-default-namespace",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-ignore",
|
||||
Namespace: "kube-system",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-refresh-in-exact-match-namespace",
|
||||
Namespace: "end-to-end-tests",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-refresh-in-globbed-namespace",
|
||||
Namespace: "app-team-two",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
|
||||
assert.NoError(t, err)
|
||||
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
|
||||
w := httptest.NewRecorder()
|
||||
h.Handler(w, req)
|
||||
assert.Equal(t, w.Code, http.StatusOK)
|
||||
|
||||
logMessages := make([]string, 0, len(hook.Entries))
|
||||
|
||||
for _, entry := range hook.Entries {
|
||||
logMessages = append(logMessages, entry.Message)
|
||||
}
|
||||
|
||||
assert.Contains(t, logMessages, "Requested app 'app-to-refresh-in-default-namespace' refresh")
|
||||
assert.Contains(t, logMessages, "Requested app 'app-to-refresh-in-exact-match-namespace' refresh")
|
||||
assert.Contains(t, logMessages, "Requested app 'app-to-refresh-in-globbed-namespace' refresh")
|
||||
assert.NotContains(t, logMessages, "Requested app 'app-to-ignore' refresh")
|
||||
|
||||
assert.Contains(t, patchedApps, "app-to-refresh-in-default-namespace")
|
||||
assert.Contains(t, patchedApps, "app-to-refresh-in-exact-match-namespace")
|
||||
assert.Contains(t, patchedApps, "app-to-refresh-in-globbed-namespace")
|
||||
assert.NotContains(t, patchedApps, "app-to-ignore")
|
||||
assert.Len(t, patchedApps, 3)
|
||||
|
||||
hook.Reset()
|
||||
}
|
||||
|
||||
func TestGitHubTagEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
eventJSON, err := os.ReadFile("testdata/github-tag-event.json")
|
||||
assert.NoError(t, err)
|
||||
@@ -159,8 +258,8 @@ func TestGitHubTagEvent(t *testing.T) {
|
||||
|
||||
func TestGitHubPingEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-GitHub-Event", "ping")
|
||||
eventJSON, err := os.ReadFile("testdata/github-ping-event.json")
|
||||
assert.NoError(t, err)
|
||||
@@ -175,8 +274,8 @@ func TestGitHubPingEvent(t *testing.T) {
|
||||
|
||||
func TestBitbucketServerRepositoryReferenceChangedEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-Event-Key", "repo:refs_changed")
|
||||
eventJSON, err := os.ReadFile("testdata/bitbucket-server-event.json")
|
||||
assert.NoError(t, err)
|
||||
@@ -193,7 +292,7 @@ func TestBitbucketServerRepositoryReferenceChangedEvent(t *testing.T) {
|
||||
|
||||
func TestBitbucketServerRepositoryDiagnosticPingEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
eventJSON := "{\"test\": true}"
|
||||
req := httptest.NewRequest("POST", "/api/webhook", bytes.NewBufferString(eventJSON))
|
||||
req.Header.Set("X-Event-Key", "diagnostics:ping")
|
||||
@@ -207,8 +306,8 @@ func TestBitbucketServerRepositoryDiagnosticPingEvent(t *testing.T) {
|
||||
|
||||
func TestGogsPushEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-Gogs-Event", "push")
|
||||
eventJSON, err := os.ReadFile("testdata/gogs-event.json")
|
||||
assert.NoError(t, err)
|
||||
@@ -223,8 +322,8 @@ func TestGogsPushEvent(t *testing.T) {
|
||||
|
||||
func TestGitLabPushEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-Gitlab-Event", "Push Hook")
|
||||
eventJSON, err := os.ReadFile("testdata/gitlab-event.json")
|
||||
assert.NoError(t, err)
|
||||
@@ -239,8 +338,8 @@ func TestGitLabPushEvent(t *testing.T) {
|
||||
|
||||
func TestInvalidMethod(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("GET", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/webhook", nil)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
w := httptest.NewRecorder()
|
||||
h.Handler(w, req)
|
||||
@@ -253,8 +352,8 @@ func TestInvalidMethod(t *testing.T) {
|
||||
|
||||
func TestInvalidEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
w := httptest.NewRecorder()
|
||||
h.Handler(w, req)
|
||||
@@ -267,8 +366,8 @@ func TestInvalidEvent(t *testing.T) {
|
||||
|
||||
func TestUnknownEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil)
|
||||
req := httptest.NewRequest("POST", "/api/webhook", nil)
|
||||
h := NewMockHandler(nil, []string{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
req.Header.Set("X-Unknown-Event", "push")
|
||||
w := httptest.NewRecorder()
|
||||
h.Handler(w, req)
|
||||
|
||||
Reference in New Issue
Block a user