Compare commits

...

26 Commits

Author SHA1 Message Date
github-actions[bot]
e4a0246c4d Bump version to 2.11.7 (#19175)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pasha-codefresh <39732895+pasha-codefresh@users.noreply.github.com>
2024-07-24 12:28:04 +03:00
pasha-codefresh
05edb2a9ca Merge commit from fork
* feat: verify rbac on each message and not just during handshake

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* feat: verify rbac on each message and not just during handshake

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* fix: linter and e2e tests

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* fix: linter and e2e tests

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* feat: verify rbac on each message and not just during handshake

Signed-off-by: pashakostohrys <pavel@codefresh.io>

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-07-24 12:18:16 +03:00
github-actions[bot]
089247df0f Bump version to 2.11.6 (#19142)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pasha-codefresh <39732895+pasha-codefresh@users.noreply.github.com>
2024-07-22 17:15:09 +03:00
pasha-codefresh
540e3a57b9 Merge commit from fork
* feat: limit payload size

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* git cherry-pick a6841386468365e458a61896cc48ff18163f25c0

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* git cherry-pick a08356a8eb13ced5850ec886f4a3b823de606f65

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* git cherry-pick f0a1f1efe721d486d6fe6cb4f645b269c6781794

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* git cherry-pick 3dd77292073c692f0deb7b4296976a60489bc442

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* git cherry-pick 3bf68adf84c09c44f2c42548b8421df127d61587

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* fix cherry-pick issues

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* fix cherry-pick issues

Signed-off-by: pashakostohrys <pavel@codefresh.io>

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-07-22 17:08:18 +03:00
Blake Pettersson
b980386388 fix: cherry-pick #18761 (v2.11) (#19108)
* fix(applicationset): use requeue after if generate app errors out (#18761)

The `GenerateApplications` can call to external resources like Github
API for instance which might be rate limited or fail. If those requests
somehow fail we should requeue them after some time like (same
reason as e98d3b2a87/applicationset/controllers/applicationset_controller.go (L154)).

For instance, in our environments we were rate limited by Github and the ArgoCD
applicationset controller was logging the following error about every
second or less for every application set using the pull request generator
that we have:
```
time="2024-06-21T14:17:15Z" level=error msg="error generating params" error="error listing repos: error listing pull requests for LedgerHQ/xxx: GET https://api.github.com/repos/LedgerHQ/xxx/pulls?per_page=100: 403 API rate limit exceeded for installation ID xxx. If you reach out to GitHub Support for help, please include the request ID xxx and timestamp 2024-06-xx xxx UTC. [rate reset in 8m18s]" generator="&{0xc000d652c0 0x289a100 {0xc00087bdd0}  [] true}"
time="2024-06-21T14:17:15Z" level=error msg="error generating application from params" applicationset=argocd/xxx error="error listing repos: error listing pull requests for LedgerHQ/xxxx: GET https://api.github.com/repos/LedgerHQ/xxx/pulls?per_page=100: 403 API rate limit exceeded for installation ID xxx. If you reach out to GitHub Support for help, please include the request ID xxx and timestamp 2024-06-xx xxx UTC. [rate reset in 8m18s]" generator="{nil nil nil nil nil &PullRequestGenerator{Github:&PullRequestGeneratorGithub{Owner:LedgerHQ,Repo:xxx,API:,TokenRef:nil,AppSecretName:xxxx,Labels:[argocd/preview],},GitLab:nil,Gitea:nil,BitbucketServer:nil,Filters:[]PullRequestGeneratorFilter{},RequeueAfterSeconds:*1800,Template:ApplicationSetTemplate{ApplicationSetTemplateMeta:ApplicationSetTemplateMeta{Name:,Namespace:,Labels:map[string]string{},Annotations:map[string]string{},Finalizers:[],},Spec:ApplicationSpec{Source:nil,Destination:ApplicationDestination{Server:,Namespace:,Name:,},Project:,SyncPolicy:nil,IgnoreDifferences:[]ResourceIgnoreDifferences{},Info:[]Info{},RevisionHistoryLimit:nil,Sources:[]ApplicationSource{},},},Bitbucket:nil,AzureDevOps:nil,} nil nil nil nil}"
```

Signed-off-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>

* test: cherry-pick fixes

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>

* chore: please the linter

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>

---------

Signed-off-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>
Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>
2024-07-18 22:31:11 -04:00
github-actions[bot]
c4b283ce0c Bump version to 2.11.5 (#19061)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-07-15 13:34:58 -04:00
Alexandre Gaudreault
d1c052d7bf chore: update gitops-engine (2.11) (#19036)
* local dep

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

* dep

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

* use merged deps

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

---------

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-07-15 11:57:34 -04:00
gcp-cherry-pick-bot[bot]
c0f780c380 fix(cli): Get Redis password from secret in loadClusters() (#18951) (#18956)
* Get Redis password from secret in `loadClusters()`



* feat: support redis password in admin stats command



* Simplify code



---------

Signed-off-by: David Wu <155603967+david-wu-octopus@users.noreply.github.com>
Signed-off-by: pashakostohrys <pavel@codefresh.io>
Co-authored-by: david-wu-octopus <155603967+david-wu-octopus@users.noreply.github.com>
Co-authored-by: pashakostohrys <pavel@codefresh.io>
2024-07-05 11:12:37 -04:00
gcp-cherry-pick-bot[bot]
e1284e19e0 remove unwanted updating of source-position in app set command (#18887) (#18896)
Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-07-02 13:14:11 -04:00
github-actions[bot]
9e313e539b Bump version to 2.11.4 (#18894)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-07-02 13:04:34 -04:00
Michael Crenshaw
0d1709f73b fix: update static schemas (#18889)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-02 13:00:41 -04:00
Michael Crenshaw
bfbceff5da fix(controller): bad server-side diffs (#18213) (2.11) (#18868)
* fix(controller): bad server-side diffs (#18213) (2.11)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* hopefully the right hash now

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-01 21:27:36 -04:00
gcp-cherry-pick-bot[bot]
0e71f09990 docs: Fix .path to .path.segments go template (#18872) (#18874)
Signed-off-by: Jaeseok Lee <devsunb@gmail.com>
Co-authored-by: Jaeseok Lee <devsunb@gmail.com>
2024-07-01 10:53:31 -04:00
gcp-cherry-pick-bot[bot]
07880f3c1d fix(webhook): bitbucket and azure not triggering refresh (#18289) (#18765) (#18819)
* fix(webhook): bitbucket and azure webhook not triggering refresh



* update unit test



* fix merge



* adjust logic for reposerver using ls-remote



---------

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-06-26 08:48:45 -04:00
Michael Crenshaw
24b198bf51 fix(appset): revert "keep reconciling even when params error occurred" (#17062) (#18781)
This reverts commit 86369ca71d.

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-25 15:34:37 -04:00
gcp-cherry-pick-bot[bot]
5fd645feac fix: docs site version selector broken (#18378) (#18724)
Signed-off-by: xiaowu.zhu <xiaowu.zhu@daocloud.io>
Signed-off-by: root <root@daocloud.io>
Co-authored-by: yyzxw <34639446+yyzxw@users.noreply.github.com>
2024-06-18 15:45:06 -04:00
gcp-cherry-pick-bot[bot]
b5c13b6139 fix: Update braces package to 3.0.3 (#18459) (#18663)
Signed-off-by: Keith Chong <kykchong@redhat.com>
Co-authored-by: Keith Chong <kykchong@redhat.com>
2024-06-14 09:18:42 -04:00
gcp-cherry-pick-bot[bot]
d75b23bf92 Revert "feat(server): log app Spec along with event (#16416)" (#18458) (#18639)
This reverts commit 820f4d861a.

Signed-off-by: jannfis <jann@mistrust.net>
Co-authored-by: Jann Fischer <jann@mistrust.net>
2024-06-13 14:49:34 -04:00
gcp-cherry-pick-bot[bot]
ac80860eda test: fix e2e tests after GHSA-3cqf-953p-h5cp (#18543) (#18641)
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-13 14:09:28 -04:00
Justin Marquis
c2bd38a11a chore(deps): upgrade redis to 7.2.15-alpine (cherry-pick release-2.11) (#18640)
Signed-off-by: Justin Marquis <justin@akuity.io>
2024-06-13 13:45:24 -04:00
Justin Marquis
13844b90ad chore: bump go version to 1.21.10 (#18540)
Signed-off-by: Justin Marquis <justin@akuity.io>
2024-06-07 08:49:08 -07:00
github-actions[bot]
3f344d54a4 Bump version to 2.11.3 (#18520)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pasha-codefresh <39732895+pasha-codefresh@users.noreply.github.com>
2024-06-06 11:36:25 +03:00
pasha-codefresh
e01bb5303a Merge pull request from GHSA-3cqf-953p-h5cp
* fix: prevent enumerating by cluster name, return exact error for case when cluster exists and not

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* fix: prevent cluster enumeration by name

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* fix: prevent cluster enumeration by name

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* fix linter and add unit test

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* fix linter and add unit test

Signed-off-by: pashakostohrys <pavel@codefresh.io>

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-06-06 11:30:10 +03:00
Blake Pettersson
320abb8d64 Merge pull request from GHSA-87p9-x75h-p4j2
Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-06-06 11:25:55 +03:00
gcp-cherry-pick-bot[bot]
46342a9e82 fix: app names with non-alphanumeric characters in position 63 break syncs (issue #18237) (#18256) (#18439)
* Ensure truncated app label does not end in a special character



* Move regex to global variable and add out of bounds check



* Add test for out-of-bounds check



---------

Signed-off-by: Zack Robinson <robinsoz@arcesium.com>
Co-authored-by: Zack Robinson <zkislakrobinson@gmail.com>
2024-05-28 21:08:52 +03:00
gcp-cherry-pick-bot[bot]
cf17283ebe fix source ordering issue in manifest generation for multi-source app while using manifests and diff commands (#18395) (#18408) 2024-05-24 16:52:42 -04:00
46 changed files with 938 additions and 519 deletions

View File

@@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fca
# 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.21.9@sha256:7d0dcbe5807b1ad7272a598fbf9d7af15b5e2bed4fd6c4c2b5b3684df0b317dd AS builder
FROM docker.io/library/golang:1.21.10@sha256:16438a8e66c0c984f732e815ee5b7d715b8e33e81bac6d6a3750b1067744e7ca AS builder
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
@@ -101,7 +101,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.21.9@sha256:7d0dcbe5807b1ad7272a598fbf9d7af15b5e2bed4fd6c4c2b5b3684df0b317dd AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.10@sha256:16438a8e66c0c984f732e815ee5b7d715b8e33e81bac6d6a3750b1067744e7ca AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd

View File

@@ -1 +1 @@
2.11.2
2.11.7

View File

@@ -127,20 +127,18 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// Log a warning if there are unrecognized generators
_ = utils.CheckInvalidGenerators(&applicationSetInfo)
// desiredApplications is the main list of all expected Applications from all generators in this appset.
desiredApplications, applicationSetReason, generatorsErr := r.generateApplications(logCtx, applicationSetInfo)
if generatorsErr != nil {
desiredApplications, applicationSetReason, err := r.generateApplications(logCtx, applicationSetInfo)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{
Type: argov1alpha1.ApplicationSetConditionErrorOccurred,
Message: generatorsErr.Error(),
Message: err.Error(),
Reason: string(applicationSetReason),
Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, parametersGenerated,
)
if len(desiredApplications) < 1 {
return ctrl.Result{}, generatorsErr
}
return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, err
}
parametersGenerated = true
@@ -314,7 +312,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
if len(validateErrors) == 0 && generatorsErr == nil {
if len(validateErrors) == 0 {
if err := r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{

View File

@@ -9,6 +9,9 @@ import (
"testing"
"time"
"github.com/argoproj/argo-cd/v2/applicationset/generators/mocks"
"github.com/stretchr/testify/require"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -110,7 +113,6 @@ func (r *rendererMock) RenderTemplateParams(tmpl *v1alpha1.Application, syncPoli
}
return args.Get(0).(*v1alpha1.Application), args.Error(1)
}
func (r *rendererMock) Replace(tmpl string, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) {
@@ -179,7 +181,6 @@ func TestExtractApplications(t *testing.T) {
}
t.Run(cc.name, func(t *testing.T) {
appSet := &v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
@@ -206,7 +207,6 @@ func TestExtractApplications(t *testing.T) {
if cc.generateParamsError == nil {
for _, p := range cc.params {
if cc.rendererError != nil {
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)).
Return(nil, cc.rendererError)
@@ -253,10 +253,8 @@ func TestExtractApplications(t *testing.T) {
if cc.generateParamsError == nil {
rendererMock.AssertNumberOfCalls(t, "RenderTemplateParams", len(cc.params))
}
})
}
}
func TestMergeTemplateApplications(t *testing.T) {
@@ -315,7 +313,6 @@ func TestMergeTemplateApplications(t *testing.T) {
cc := c
t.Run(cc.name, func(t *testing.T) {
generatorMock := generatorMock{}
generator := v1alpha1.ApplicationSetGenerator{
List: &v1alpha1.ListGenerator{},
@@ -358,11 +355,9 @@ func TestMergeTemplateApplications(t *testing.T) {
assert.Equal(t, cc.expectedApps, got)
})
}
}
func TestCreateOrUpdateInCluster(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -870,7 +865,8 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
},
{
name: "Ensure that configured preserved annotations are preserved from an existing app",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -937,7 +933,8 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
},
{
name: "Ensure that the app spec is normalized before applying",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -991,7 +988,8 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "Ensure that ignored targetRevision difference doesn't cause an update, even if another field changes",
appSet: v1alpha1.ApplicationSet{
@@ -1082,7 +1080,8 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
},
{
// For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799
name: "ignore parameters added to a multi-source app in the cluster",
appSet: v1alpha1.ApplicationSet{
@@ -1183,7 +1182,8 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
},
{
name: "Demonstrate limitation of MergePatch", // Maybe we can fix this in Argo CD 3.0: https://github.com/argoproj/argo-cd/issues/15975
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -1281,7 +1281,8 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
},
{
name: "Ensure that argocd post-delete finalizers are preserved from an existing app",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -1348,9 +1349,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
} {
t.Run(c.name, func(t *testing.T) {
initObjs := []crtclient.Object{&c.appSet}
for _, a := range c.existingApps {
@@ -1386,7 +1385,6 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
}
func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -1422,7 +1420,6 @@ func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
},
} {
t.Run(c.name, func(t *testing.T) {
appSet := v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
@@ -1480,9 +1477,9 @@ func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
KubeClientset: kubeclientset,
Cache: &fakeCache{},
}
//settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "namespace")
//argoDB := db.NewDB("namespace", settingsMgr, r.KubeClientset)
//clusterList, err := argoDB.ListClusters(context.Background())
// settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "namespace")
// argoDB := db.NewDB("namespace", settingsMgr, r.KubeClientset)
// clusterList, err := argoDB.ListClusters(context.Background())
clusterList, err := utils.ListClusters(context.Background(), kubeclientset, "namespace")
assert.NoError(t, err, "Unexpected error")
@@ -1505,13 +1502,11 @@ func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
bytes, _ := json.MarshalIndent(retrievedApp, "", " ")
t.Log("Contents of app after call:", string(bytes))
})
}
}
func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -1583,9 +1578,7 @@ func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
expectFinalizerRemoved: false,
},
} {
t.Run(c.name, func(t *testing.T) {
appSet := v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
@@ -1665,7 +1658,6 @@ func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
bytes, _ := json.MarshalIndent(retrievedApp, "", " ")
t.Log("Contents of app after call:", string(bytes))
})
}
}
@@ -1746,7 +1738,6 @@ func TestRemoveOwnerReferencesOnDeleteAppSet(t *testing.T) {
}
func TestCreateApplications(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -1950,7 +1941,6 @@ func TestCreateApplications(t *testing.T) {
}
func TestDeleteInCluster(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -2152,8 +2142,59 @@ func TestGetMinRequeueAfter(t *testing.T) {
assert.Equal(t, time.Duration(1)*time.Second, got)
}
func TestValidateGeneratedApplications(t *testing.T) {
func TestRequeueGeneratorFails(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appSet := v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{{
PullRequest: &v1alpha1.PullRequestGenerator{},
}},
},
}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build()
generator := v1alpha1.ApplicationSetGenerator{
PullRequest: &v1alpha1.PullRequestGenerator{},
}
generatorMock := mocks.Generator{}
generatorMock.On("GetTemplate", &generator).
Return(&v1alpha1.ApplicationSetTemplate{})
generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return([]map[string]interface{}{}, fmt.Errorf("Simulated error generating params that could be related to an external service/API call"))
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(0),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"PullRequest": &generatorMock,
},
}
req := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "argocd",
Name: "name",
},
}
res, err := r.Reconcile(context.Background(), req)
require.Error(t, err)
assert.Equal(t, ReconcileRequeueOnValidationError, res.RequeueAfter)
}
func TestValidateGeneratedApplications(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -2313,9 +2354,7 @@ func TestValidateGeneratedApplications(t *testing.T) {
validationErrors: map[int]error{0: fmt.Errorf("application destination spec is invalid: unable to find destination server: there are no clusters with this name: nonexistent-cluster")},
},
} {
t.Run(cc.name, func(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "my-secret",
@@ -2393,7 +2432,6 @@ func TestValidateGeneratedApplications(t *testing.T) {
}
func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -2488,91 +2526,6 @@ func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
assert.Error(t, err)
}
func TestReconcilerCreateAppsRecoveringRenderError(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
err = v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
project := v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
}
appSet := v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true,
Generators: []v1alpha1.ApplicationSetGenerator{
{
List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{
Raw: []byte(`{"name": "very-good-app"}`),
}, {
Raw: []byte(`{"name": "bad-app"}`),
}},
},
},
},
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "{{ index (splitList \"-\" .name ) 2 }}",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
Project: "default",
Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"},
},
},
},
}
kubeclientset := kubefake.NewSimpleClientset()
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{&project}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
KubeClientset: kubeclientset,
Policy: v1alpha1.ApplicationsSyncPolicySync,
ArgoCDNamespace: "argocd",
}
req := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "argocd",
Name: "name",
},
}
// Verify that on generatorsError, no error is returned, but the object is requeued
res, err := r.Reconcile(context.Background(), req)
assert.Nil(t, err)
assert.True(t, res.RequeueAfter == ReconcileRequeueOnValidationError)
var app v1alpha1.Application
// make sure good app got created
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "app"}, &app)
assert.NoError(t, err)
assert.Equal(t, app.Name, "app")
}
func TestSetApplicationSetStatusCondition(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
@@ -2631,7 +2584,6 @@ func TestSetApplicationSetStatusCondition(t *testing.T) {
}
func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.Application {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -2748,7 +2700,6 @@ func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp
}
func TestUpdateNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
@@ -2758,7 +2709,6 @@ func TestUpdateNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
}
func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
@@ -2768,7 +2718,6 @@ func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) {
}
func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
@@ -2779,7 +2728,6 @@ func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) {
}
func TestUpdatePerformedWithSyncPolicySync(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
@@ -2790,7 +2738,6 @@ func TestUpdatePerformedWithSyncPolicySync(t *testing.T) {
}
func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, false)
@@ -2801,7 +2748,6 @@ func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *t
}
func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.ApplicationList {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -2919,7 +2865,6 @@ func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp
}
func TestDeleteNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
@@ -2928,7 +2873,6 @@ func TestDeleteNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
}
func TestDeleteNotPerformedWithSyncPolicyCreateUpdate(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
@@ -2937,7 +2881,6 @@ func TestDeleteNotPerformedWithSyncPolicyCreateUpdate(t *testing.T) {
}
func TestDeletePerformedWithSyncPolicyCreateDelete(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true)
@@ -2946,7 +2889,6 @@ func TestDeletePerformedWithSyncPolicyCreateDelete(t *testing.T) {
}
func TestDeletePerformedWithSyncPolicySync(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true)
@@ -2955,7 +2897,6 @@ func TestDeletePerformedWithSyncPolicySync(t *testing.T) {
}
func TestDeletePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, false)
@@ -2976,16 +2917,18 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
}{
{
name: "Generate an application from a go template application set manifest using a pull request generator",
params: []map[string]interface{}{{
"number": "1",
"branch": "branch1",
"branch_slug": "branchSlug1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"branch_slugify_default": "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"branch_slugify_smarttruncate_disabled": "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature",
"branch_slugify_smarttruncate_enabled": "feat/testwithsmarttruncateenabledramdomlonglistofcharacters",
"labels": []string{"label1"}},
params: []map[string]interface{}{
{
"number": "1",
"branch": "branch1",
"branch_slug": "branchSlug1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"branch_slugify_default": "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"branch_slugify_smarttruncate_disabled": "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature",
"branch_slugify_smarttruncate_enabled": "feat/testwithsmarttruncateenabledramdomlonglistofcharacters",
"labels": []string{"label1"},
},
},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
@@ -3033,9 +2976,7 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
},
},
} {
t.Run(cases.name, func(t *testing.T) {
generatorMock := generatorMock{}
generator := v1alpha1.ApplicationSetGenerator{
PullRequest: &v1alpha1.PullRequestGenerator{},
@@ -3329,9 +3270,7 @@ func TestSetApplicationSetApplicationStatus(t *testing.T) {
expectedAppStatuses: nil,
},
} {
t.Run(cc.name, func(t *testing.T) {
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build()
r := ApplicationSetReconciler{
@@ -3357,7 +3296,6 @@ func TestSetApplicationSetApplicationStatus(t *testing.T) {
}
func TestBuildAppDependencyList(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -4093,9 +4031,7 @@ func TestBuildAppDependencyList(t *testing.T) {
},
},
} {
t.Run(cc.name, func(t *testing.T) {
kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{}
@@ -4120,7 +4056,6 @@ func TestBuildAppDependencyList(t *testing.T) {
}
func TestBuildAppSyncMap(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -4687,9 +4622,7 @@ func TestBuildAppSyncMap(t *testing.T) {
},
},
} {
t.Run(cc.name, func(t *testing.T) {
kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{}
@@ -4713,7 +4646,6 @@ func TestBuildAppSyncMap(t *testing.T) {
}
func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -5345,9 +5277,7 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
},
} {
t.Run(cc.name, func(t *testing.T) {
kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{}
@@ -5379,7 +5309,6 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
}
func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
@@ -6099,9 +6028,7 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
},
},
} {
t.Run(cc.name, func(t *testing.T) {
kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{}
@@ -6165,60 +6092,64 @@ func TestOwnsHandler(t *testing.T) {
ResourceVersion: "bar",
}},
}}, want: false},
{name: "ApplicationHealthStatusDiff", args: args{e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Health: v1alpha1.HealthStatus{
Status: "Unknown",
},
}},
ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Health: v1alpha1.HealthStatus{
Status: "Healthy",
},
}},
},
{name: "ApplicationHealthStatusDiff", args: args{
e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Health: v1alpha1.HealthStatus{
Status: "Unknown",
},
}},
ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Health: v1alpha1.HealthStatus{
Status: "Healthy",
},
}},
},
enableProgressiveSyncs: true,
}, want: true},
{name: "ApplicationSyncStatusDiff", args: args{e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: "OutOfSync",
},
}},
ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: "Synced",
},
}},
},
{name: "ApplicationSyncStatusDiff", args: args{
e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: "OutOfSync",
},
}},
ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: "Synced",
},
}},
},
enableProgressiveSyncs: true,
}, want: true},
{name: "ApplicationOperationStateDiff", args: args{e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
Phase: "foo",
},
}},
ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
Phase: "bar",
},
}},
},
{name: "ApplicationOperationStateDiff", args: args{
e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
Phase: "foo",
},
}},
ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
Phase: "bar",
},
}},
},
enableProgressiveSyncs: true,
}, want: true},
{name: "ApplicationOperationStartedAtDiff", args: args{e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
StartedAt: now,
},
}},
ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
StartedAt: metav1.NewTime(now.Add(time.Minute * 1)),
},
}},
},
{name: "ApplicationOperationStartedAtDiff", args: args{
e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
StartedAt: now,
},
}},
ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
StartedAt: metav1.NewTime(now.Add(time.Minute * 1)),
},
}},
},
enableProgressiveSyncs: true,
}, want: true},
{name: "SameApplicationGeneration", args: args{e: event.UpdateEvent{
@@ -6257,48 +6188,50 @@ func TestOwnsHandler(t *testing.T) {
ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{}}},
ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: nil}},
}}, want: false},
{name: "ApplicationDestinationSame", args: args{e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "server",
Namespace: "ns",
Name: "name",
{name: "ApplicationDestinationSame", args: args{
e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "server",
Namespace: "ns",
Name: "name",
},
},
},
ObjectNew: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "server",
Namespace: "ns",
Name: "name",
},
},
},
},
ObjectNew: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "server",
Namespace: "ns",
Name: "name",
},
},
},
},
enableProgressiveSyncs: true,
}, want: false},
{name: "ApplicationDestinationDiff", args: args{e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "server",
Namespace: "ns",
Name: "name",
{name: "ApplicationDestinationDiff", args: args{
e: event.UpdateEvent{
ObjectOld: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "server",
Namespace: "ns",
Name: "name",
},
},
},
ObjectNew: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "notSameServer",
Namespace: "ns",
Name: "name",
},
},
},
},
ObjectNew: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "notSameServer",
Namespace: "ns",
Name: "name",
},
},
},
},
enableProgressiveSyncs: true,
}, want: true},
{name: "NotAnAppOld", args: args{e: event.UpdateEvent{

View File

@@ -7,6 +7,8 @@ import (
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --name=Generator
// Generator defines the interface implemented by all ApplicationSet generators.
type Generator interface {
// GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template.

View File

@@ -0,0 +1,98 @@
// Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks
import (
time "time"
mock "github.com/stretchr/testify/mock"
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// Generator is an autogenerated mock type for the Generator type
type Generator struct {
mock.Mock
}
// GenerateParams provides a mock function with given fields: appSetGenerator, applicationSetInfo
func (_m *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
ret := _m.Called(appSetGenerator, applicationSetInfo)
if len(ret) == 0 {
panic("no return value specified for GenerateParams")
}
var r0 []map[string]interface{}
var r1 error
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet) ([]map[string]interface{}, error)); ok {
return rf(appSetGenerator, applicationSetInfo)
}
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet) []map[string]interface{}); ok {
r0 = rf(appSetGenerator, applicationSetInfo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]map[string]interface{})
}
}
if rf, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet) error); ok {
r1 = rf(appSetGenerator, applicationSetInfo)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetRequeueAfter provides a mock function with given fields: appSetGenerator
func (_m *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
ret := _m.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetRequeueAfter")
}
var r0 time.Duration
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
r0 = rf(appSetGenerator)
} else {
r0 = ret.Get(0).(time.Duration)
}
return r0
}
// GetTemplate provides a mock function with given fields: appSetGenerator
func (_m *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
ret := _m.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetTemplate")
}
var r0 *v1alpha1.ApplicationSetTemplate
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
r0 = rf(appSetGenerator)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
}
}
return r0
}
// NewGenerator creates a new instance of Generator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewGenerator(t interface {
mock.TestingT
Cleanup(func())
}) *Generator {
mock := &Generator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -104,7 +104,17 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
if err != nil {
return nil, err
}
client := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", port)})
redisOptions := &redis.Options{Addr: fmt.Sprintf("localhost:%d", port)}
secret, err := kubeClient.CoreV1().Secrets(namespace).Get(context.Background(), defaulRedisInitialPasswordSecretName, v1.GetOptions{})
if err == nil {
if _, ok := secret.Data[defaultResisInitialPasswordKey]; ok {
redisOptions.Password = string(secret.Data[defaultResisInitialPasswordKey])
}
}
client := redis.NewClient(redisOptions)
compressionType, err := cacheutil.CompressionTypeFromString(redisCompressionStr)
if err != nil {
return nil, err

View File

@@ -781,8 +781,6 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
}
}
// sourcePosition startes with 1, thus, it needs to be decreased by 1 to find the correct index in the list of sources
sourcePosition = sourcePosition - 1
visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, sourcePosition)
if visited == 0 {
log.Error("Please set at least one option to update")

View File

@@ -1,48 +1,75 @@
setTimeout(function() {
const callbackName = 'callback_' + new Date().getTime();
window[callbackName] = function (response) {
const div = document.createElement('div');
div.innerHTML = response.html;
document.querySelector(".md-header__inner > .md-header__title").appendChild(div);
const container = div.querySelector('.rst-versions');
var caret = document.createElement('div');
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>"
caret.classList.add('dropdown-caret')
div.querySelector('.rst-current-version').appendChild(caret);
const targetNode = document.querySelector('.md-header__inner');
const observerOptions = {
childList: true,
subtree: true
};
const observerCallback = function(mutationsList, observer) {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
const titleElement = document.querySelector('.md-header__inner > .md-header__title');
if (titleElement) {
initializeVersionDropdown();
observer.disconnect();
}
}
}
};
const observer = new MutationObserver(observerCallback);
observer.observe(targetNode, observerOptions);
function initializeVersionDropdown() {
const callbackName = 'callback_' + new Date().getTime();
window[callbackName] = function(response) {
const div = document.createElement('div');
div.innerHTML = response.html;
document.querySelector(".md-header__inner > .md-header__title").appendChild(div);
const container = div.querySelector('.rst-versions');
var caret = document.createElement('div');
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() {
container.classList.toggle('shift-up');
});
};
var CSSLink = document.createElement('link');
CSSLink.rel='stylesheet';
CSSLink.rel = 'stylesheet';
CSSLink.href = '/assets/versions.css';
document.getElementsByTagName('head')[0].appendChild(CSSLink);
var script = document.createElement('script');
script.src = 'https://argo-cd.readthedocs.io/_/api/v2/footer_html/?'+
script.src = 'https://argo-cd.readthedocs.io/_/api/v2/footer_html/?' +
'callback=' + callbackName + '&project=argo-cd&page=&theme=mkdocs&format=jsonp&docroot=docs&source_suffix=.md&version=' + (window['READTHEDOCS_DATA'] || { version: 'latest' }).version;
document.getElementsByTagName('head')[0].appendChild(script);
}, 0);
}
// VERSION WARNINGS
window.addEventListener("DOMContentLoaded", function() {
var rtdData = window['READTHEDOCS_DATA'] || { version: 'latest' };
var currentVersion = window.location.href.match(/\/en\/(release-(?:v\d+|\w+)|latest|stable)\//);
var margin = 30;
var headerHeight = document.getElementsByClassName("md-header")[0].offsetHeight;
if (rtdData.version === "latest") {
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for an unreleased version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>"
var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin
document.querySelector("header.md-header").style.top = bannerHeight +"px";
document.querySelector('style').textContent +=
"@media screen and (min-width: 76.25em){ .md-sidebar { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}"
document.querySelector('style').textContent +=
"@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}"
var headerHeight = document.getElementsByClassName("md-header")[0].offsetHeight;
if (currentVersion && currentVersion.length > 1) {
currentVersion = currentVersion[1];
if (currentVersion === "latest") {
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for an unreleased version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>";
var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin;
document.querySelector("header.md-header").style.top = bannerHeight + "px";
document.querySelector('style').textContent +=
"@media screen and (min-width: 76.25em){ .md-sidebar { height: 0; top:" + (bannerHeight + headerHeight) + "px !important; }}";
document.querySelector('style').textContent +=
"@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:" + (bannerHeight + headerHeight) + "px !important; }}";
} else if (currentVersion !== "stable") {
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for a previous version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>";
var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin;
document.querySelector("header.md-header").style.top = bannerHeight + "px";
document.querySelector('style').textContent +=
"@media screen and (min-width: 76.25em){ .md-sidebar { height: 0; top:" + (bannerHeight + headerHeight) + "px !important; }}";
document.querySelector('style').textContent +=
"@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:" + (bannerHeight + headerHeight) + "px !important; }}";
}
}
else if (rtdData.version !== "stable") {
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for a previous version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>"
var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin
document.querySelector("header.md-header").style.top = bannerHeight +"px";
document.querySelector('style').textContent +=
"@media screen and (min-width: 76.25em){ .md-sidebar { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}"
document.querySelector('style').textContent +=
"@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}"
}
});
});

View File

@@ -326,7 +326,7 @@ As with other generators, clusters *must* already be defined within Argo CD, in
In addition to the flattened key/value pairs from the configuration file, the following generator parameters are provided:
- `{{.path.path}}`: The path to the directory containing matching configuration file within the Git repository. Example: `/clusters/clusterA`, if the config file was `/clusters/clusterA/config.json`
- `{{index .path n}}`: The path to the matching configuration file within the Git repository, split into array elements (`n` - array index). Example: `index .path 0: clusters`, `index .path 1: clusterA`
- `{{index .path.segments n}}`: The path to the matching configuration file within the Git repository, split into array elements (`n` - array index). Example: `index .path.segments 0: clusters`, `index .path.segments 1: clusterA`
- `{{.path.basename}}`: Basename of the path to the directory containing the configuration file (e.g. `clusterA`, with the above example.)
- `{{.path.basenameNormalized}}`: This field is the same as `.path.basename` with unsupported characters replaced with `-` (e.g. a `path` of `/directory/directory_2`, and `.path.basename` of `directory_2` would produce `directory-2` here).
- `{{.path.filename}}`: The matched filename. e.g., `config.json` in the above example.
@@ -360,7 +360,7 @@ spec:
files:
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
values:
base_dir: "{{index .path 0}}/{{index .path 1}}/{{index .path 2}}"
base_dir: "{{index .path.segments 0}}/{{index .path.segments 1}}/{{index .path.segments 2}}"
template:
metadata:
name: '{{.cluster.name}}-guestbook'

View File

@@ -410,3 +410,6 @@ data:
cluster:
name: some-cluster
server: https://some-cluster
# The maximum size of the payload that can be sent to the webhook server.
webhook.maxPayloadSizeMB: 1024

View File

@@ -19,6 +19,7 @@ URL configured in the Git provider should use the `/api/webhook` endpoint of you
(e.g. `https://argocd.example.com/api/webhook`). If you wish to use a shared secret, input an
arbitrary value in the secret. This value will be used when configuring the webhook in the next step.
To prevent DDoS attacks with unauthenticated webhook events (the `/api/webhook` endpoint currently lacks rate limiting protection), it is recommended to limit the payload size. You can achieve this by configuring the `argocd-cm` ConfigMap with the `webhook.maxPayloadSizeMB` attribute. The default value is 1GB.
## Github
![Add Webhook](../assets/webhook-config.png "Add Webhook")

2
go.mod
View File

@@ -13,7 +13,7 @@ require (
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
github.com/alicebob/miniredis/v2 v2.30.4
github.com/antonmedv/expr v1.15.2
github.com/argoproj/gitops-engine v0.7.1-0.20240416142647-fbecbb86e412
github.com/argoproj/gitops-engine v0.7.1-0.20240715141605-18ba62e1f1fb
github.com/argoproj/notifications-engine v0.4.1-0.20240403133627-f48567108f01
github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1
github.com/aws/aws-sdk-go v1.50.8

4
go.sum
View File

@@ -696,8 +696,8 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
github.com/appscode/go v0.0.0-20191119085241-0887d8ec2ecc/go.mod h1:OawnOmAL4ZX3YaPdN+8HTNwBveT1jMsqP74moa9XUbE=
github.com/argoproj/gitops-engine v0.7.1-0.20240416142647-fbecbb86e412 h1:je2wJpWtaoS55mA5MBPCeDnKMeF42pkxO9Oa5KbWrdg=
github.com/argoproj/gitops-engine v0.7.1-0.20240416142647-fbecbb86e412/go.mod h1:gWE8uROi7hIkWGNAVM+8FWkMfo0vZ03SLx/aFw/DBzg=
github.com/argoproj/gitops-engine v0.7.1-0.20240715141605-18ba62e1f1fb h1:PbngWUqmtdVxU5qRR0Dngeo6AXhxY3qZi6RlpfCLbuI=
github.com/argoproj/gitops-engine v0.7.1-0.20240715141605-18ba62e1f1fb/go.mod h1:d4eLldeEFyZIcVySAMhXhnh1tTa4qfvPYfut9B8UClw=
github.com/argoproj/notifications-engine v0.4.1-0.20240403133627-f48567108f01 h1:/V8+HM0VPPTrdjTwUrkIj5a+SjaU//tJwfIXJ1QAOvg=
github.com/argoproj/notifications-engine v0.4.1-0.20240403133627-f48567108f01/go.mod h1:N0A4sEws2soZjEpY4hgZpQS8mRIEw6otzwfkgc3g9uQ=
github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 h1:qsHwwOJ21K2Ao0xPju1sNuqphyMnMYkyB3ZLoLtxWpo=

View File

@@ -5,7 +5,7 @@ kind: Kustomization
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.11.2
newTag: v2.11.7
resources:
- ./application-controller
- ./dex

View File

@@ -40,7 +40,7 @@ spec:
serviceAccountName: argocd-redis
containers:
- name: redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: Always
args:
- "--save"

View File

@@ -21224,7 +21224,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21326,7 +21326,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -21342,7 +21342,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -21583,7 +21583,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -21635,7 +21635,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -21907,7 +21907,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -12,4 +12,4 @@ resources:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.11.2
newTag: v2.11.7

View File

@@ -12,7 +12,7 @@ patches:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.11.2
newTag: v2.11.7
resources:
- ../../base/application-controller
- ../../base/applicationset-controller

View File

@@ -1219,7 +1219,7 @@ spec:
automountServiceAccountToken: false
initContainers:
- name: config-init
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
resources:
{}
@@ -1258,7 +1258,7 @@ spec:
containers:
- name: redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
command:
- redis-server
@@ -1321,7 +1321,7 @@ spec:
- /bin/sh
- /readonly-config/trigger-failover-if-master.sh
- name: sentinel
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
command:
- redis-sentinel
@@ -1378,7 +1378,7 @@ spec:
{}
- name: split-brain-fix
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
command:
- sh

View File

@@ -25,7 +25,7 @@ redis-ha:
enabled: true
image:
repository: redis
tag: 7.0.14-alpine
tag: 7.0.15-alpine
containerSecurityContext: null
sentinel:
bind: "0.0.0.0"

View File

@@ -22565,7 +22565,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -22688,7 +22688,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -22770,7 +22770,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -22889,7 +22889,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -23158,7 +23158,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -23210,7 +23210,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -23534,7 +23534,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -23833,7 +23833,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -23916,7 +23916,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
@@ -23976,7 +23976,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -24034,7 +24034,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
name: split-brain-fix
resources: {}
@@ -24069,7 +24069,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
name: config-init
securityContext:

View File

@@ -1686,7 +1686,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1809,7 +1809,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1891,7 +1891,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2010,7 +2010,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -2279,7 +2279,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2331,7 +2331,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2655,7 +2655,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2954,7 +2954,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -3037,7 +3037,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
@@ -3097,7 +3097,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -3155,7 +3155,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
name: split-brain-fix
resources: {}
@@ -3190,7 +3190,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
name: config-init
securityContext:

View File

@@ -21682,7 +21682,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21805,7 +21805,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -21887,7 +21887,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -21971,7 +21971,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -21987,7 +21987,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -22228,7 +22228,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -22280,7 +22280,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -22602,7 +22602,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -22901,7 +22901,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -803,7 +803,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -926,7 +926,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1008,7 +1008,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1092,7 +1092,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -1108,7 +1108,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -1349,7 +1349,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1401,7 +1401,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1723,7 +1723,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2022,7 +2022,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.11.2
image: quay.io/argoproj/argocd:v2.11.7
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -2731,7 +2731,10 @@ func (s *Service) UpdateRevisionForPaths(_ context.Context, request *apiclient.U
return nil, status.Errorf(codes.Internal, "unable to get changed files for repo %s with revision %s: %v", repo.Repo, revision, err)
}
changed := apppathutil.AppFilesHaveChanged(refreshPaths, files)
changed := false
if len(files) != 0 {
changed = apppathutil.AppFilesHaveChanged(refreshPaths, files)
}
if !changed {
logCtx.Debugf("no changes found for application %s in repo %s from revision %s to revision %s", request.AppName, repo.Repo, syncedRevision, revision)

View File

@@ -469,15 +469,16 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
}
sources := make([]appv1.ApplicationSource, 0)
appSpec := a.Spec.DeepCopy()
if a.Spec.HasMultipleSources() {
numOfSources := int64(len(a.Spec.GetSources()))
for i, pos := range q.SourcePositions {
if pos <= 0 || pos > numOfSources {
return fmt.Errorf("source position is out of range")
}
a.Spec.Sources[pos-1].TargetRevision = q.Revisions[i]
appSpec.Sources[pos-1].TargetRevision = q.Revisions[i]
}
sources = a.Spec.GetSources()
sources = appSpec.GetSources()
} else {
source := a.Spec.GetSource()
if q.GetRevision() != "" {
@@ -487,7 +488,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
}
// Store the map of all sources having ref field into a map for applications with sources field
refSources, err := argo.GetRefSources(context.Background(), a.Spec, s.db)
refSources, err := argo.GetRefSources(context.Background(), *appSpec, s.db)
if err != nil {
return fmt.Errorf("failed to get ref sources: %v", err)
}
@@ -560,7 +561,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
manifestInfo.Manifests[i] = string(data)
}
}
manifests.Manifests = manifestInfo.Manifests
manifests.Manifests = append(manifests.Manifests, manifestInfo.Manifests...)
}
return manifests, nil

View File

@@ -225,7 +225,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fieldLog.Info("terminal session starting")
session, err := newTerminalSession(w, r, nil, s.sessionManager)
session, err := newTerminalSession(ctx, w, r, nil, s.sessionManager, appRBACName, s.enf)
if err != nil {
http.Error(w, "Failed to start terminal session", http.StatusBadRequest)
return

View File

@@ -1,15 +1,19 @@
package application
import (
"context"
"encoding/json"
"fmt"
"github.com/argoproj/argo-cd/v2/common"
httputil "github.com/argoproj/argo-cd/v2/util/http"
util_session "github.com/argoproj/argo-cd/v2/util/session"
"net/http"
"sync"
"time"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
httputil "github.com/argoproj/argo-cd/v2/util/http"
"github.com/argoproj/argo-cd/v2/util/rbac"
util_session "github.com/argoproj/argo-cd/v2/util/session"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/tools/remotecommand"
@@ -31,6 +35,7 @@ var upgrader = func() websocket.Upgrader {
// terminalSession implements PtyHandler
type terminalSession struct {
ctx context.Context
wsConn *websocket.Conn
sizeChan chan remotecommand.TerminalSize
doneChan chan struct{}
@@ -39,6 +44,8 @@ type terminalSession struct {
writeLock sync.Mutex
sessionManager *util_session.SessionManager
token *string
appRBACName string
enf *rbac.Enforcer
}
// getToken get auth token from web socket request
@@ -48,7 +55,7 @@ func getToken(r *http.Request) (string, error) {
}
// newTerminalSession create terminalSession
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager *util_session.SessionManager) (*terminalSession, error) {
func newTerminalSession(ctx context.Context, w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager *util_session.SessionManager, appRBACName string, enf *rbac.Enforcer) (*terminalSession, error) {
token, err := getToken(r)
if err != nil {
return nil, err
@@ -59,12 +66,15 @@ func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader h
return nil, err
}
session := &terminalSession{
ctx: ctx,
wsConn: conn,
tty: true,
sizeChan: make(chan remotecommand.TerminalSize),
doneChan: make(chan struct{}),
sessionManager: sessionManager,
token: &token,
appRBACName: appRBACName,
enf: enf,
}
return session, nil
}
@@ -125,6 +135,29 @@ func (t *terminalSession) reconnect() (int, error) {
return 0, nil
}
func (t *terminalSession) validatePermissions(p []byte) (int, error) {
permissionDeniedMessage, _ := json.Marshal(TerminalMessage{
Operation: "stdout",
Data: "Permission denied",
})
if err := t.enf.EnforceErr(t.ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, t.appRBACName); err != nil {
err = t.wsConn.WriteMessage(websocket.TextMessage, permissionDeniedMessage)
if err != nil {
log.Errorf("permission denied message err: %v", err)
}
return copy(p, EndOfTransmission), permissionDeniedErr
}
if err := t.enf.EnforceErr(t.ctx.Value("claims"), rbacpolicy.ResourceExec, rbacpolicy.ActionCreate, t.appRBACName); err != nil {
err = t.wsConn.WriteMessage(websocket.TextMessage, permissionDeniedMessage)
if err != nil {
log.Errorf("permission denied message err: %v", err)
}
return copy(p, EndOfTransmission), permissionDeniedErr
}
return 0, nil
}
// Read called in a loop from remotecommand as long as the process is running
func (t *terminalSession) Read(p []byte) (int, error) {
// check if token still valid
@@ -135,6 +168,12 @@ func (t *terminalSession) Read(p []byte) (int, error) {
return t.reconnect()
}
// validate permissions
code, err := t.validatePermissions(p)
if err != nil {
return code, err
}
t.readLock.Lock()
_, message, err := t.wsConn.ReadMessage()
t.readLock.Unlock()

View File

@@ -1,23 +1,65 @@
package application
import (
"context"
"encoding/json"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"strings"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/util/assets"
"github.com/argoproj/argo-cd/v2/util/rbac"
"github.com/golang-jwt/jwt/v4"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func reconnect(w http.ResponseWriter, r *http.Request) {
func newTestTerminalSession(w http.ResponseWriter, r *http.Request) terminalSession {
var upgrader = websocket.Upgrader{}
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
return terminalSession{}
}
ts := terminalSession{wsConn: c}
return terminalSession{wsConn: c}
}
func newEnforcer() *rbac.Enforcer {
additionalConfig := make(map[string]string, 0)
kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: additionalConfig,
}, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: testNamespace,
},
Data: map[string][]byte{
"admin.password": []byte("test"),
"server.secretkey": []byte("test"),
},
})
enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
return enforcer
}
func reconnect(w http.ResponseWriter, r *http.Request) {
ts := newTestTerminalSession(w, r)
_, _ = ts.reconnect()
}
@@ -44,3 +86,71 @@ func TestReconnect(t *testing.T) {
assert.Equal(t, message.Data, ReconnectMessage)
}
func TestValidateWithAdminPermissions(t *testing.T) {
validate := func(w http.ResponseWriter, r *http.Request) {
enf := newEnforcer()
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
enf.SetDefaultRole("role:admin")
enf.SetClaimsEnforcerFunc(func(claims jwt.Claims, rvals ...interface{}) bool {
return true
})
ts := newTestTerminalSession(w, r)
ts.enf = enf
ts.appRBACName = "test"
// nolint:staticcheck
ts.ctx = context.WithValue(context.Background(), "claims", &jwt.MapClaims{"groups": []string{"admin"}})
_, err := ts.validatePermissions([]byte{})
require.NoError(t, err)
}
s := httptest.NewServer(http.HandlerFunc(validate))
defer s.Close()
u := "ws" + strings.TrimPrefix(s.URL, "http")
// Connect to the server
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
require.NoError(t, err)
defer ws.Close()
}
func TestValidateWithoutPermissions(t *testing.T) {
validate := func(w http.ResponseWriter, r *http.Request) {
enf := newEnforcer()
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
enf.SetDefaultRole("role:test")
enf.SetClaimsEnforcerFunc(func(claims jwt.Claims, rvals ...interface{}) bool {
return false
})
ts := newTestTerminalSession(w, r)
ts.enf = enf
ts.appRBACName = "test"
// nolint:staticcheck
ts.ctx = context.WithValue(context.Background(), "claims", &jwt.MapClaims{"groups": []string{"test"}})
_, err := ts.validatePermissions([]byte{})
require.Error(t, err)
assert.Equal(t, permissionDeniedErr.Error(), err.Error())
}
s := httptest.NewServer(http.HandlerFunc(validate))
defer s.Close()
u := "ws" + strings.TrimPrefix(s.URL, "http")
// Connect to the server
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
require.NoError(t, err)
defer ws.Close()
_, p, _ := ws.ReadMessage()
var message TerminalMessage
err = json.Unmarshal(p, &message)
require.NoError(t, err)
assert.Equal(t, "Permission denied", message.Data)
}

View File

@@ -187,15 +187,11 @@ func (s *Server) Create(ctx context.Context, q *cluster.ClusterCreateRequest) (*
// Get returns a cluster from a query
func (s *Server) Get(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Cluster, error) {
c, err := s.getClusterWith403IfNotExist(ctx, q)
c, err := s.getClusterAndVerifyAccess(ctx, q, rbacpolicy.ActionGet)
if err != nil {
return nil, err
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionGet, CreateClusterRBACObject(c.Project, q.Server)); err != nil {
return nil, err
}
return s.toAPIResponse(c), nil
}
@@ -207,6 +203,21 @@ func (s *Server) getClusterWith403IfNotExist(ctx context.Context, q *cluster.Clu
return repo, nil
}
func (s *Server) getClusterAndVerifyAccess(ctx context.Context, q *cluster.ClusterQuery, action string) (*appv1.Cluster, error) {
c, err := s.getClusterWith403IfNotExist(ctx, q)
if err != nil {
return nil, err
}
// verify that user can do the specified action inside project where cluster is located
if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, action, CreateClusterRBACObject(c.Project, c.Server)) {
log.WithField("cluster", q.Server).Warnf("encountered permissions issue while processing request: %v", err)
return nil, common.PermissionDeniedAPIError
}
return c, nil
}
func (s *Server) getCluster(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Cluster, error) {
if q.Id != nil {
q.Server = ""
@@ -278,20 +289,16 @@ var clusterFieldsByPath = map[string]func(updated *appv1.Cluster, existing *appv
// Update updates a cluster
func (s *Server) Update(ctx context.Context, q *cluster.ClusterUpdateRequest) (*appv1.Cluster, error) {
c, err := s.getClusterWith403IfNotExist(ctx, &cluster.ClusterQuery{
c, err := s.getClusterAndVerifyAccess(ctx, &cluster.ClusterQuery{
Server: q.Cluster.Server,
Name: q.Cluster.Name,
Id: q.Id,
})
}, rbacpolicy.ActionUpdate)
if err != nil {
return nil, err
}
// verify that user can do update inside project where cluster is located
if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, CreateClusterRBACObject(c.Project, c.Server)) {
return nil, common.PermissionDeniedAPIError
}
if len(q.UpdatedFields) == 0 || sets.NewString(q.UpdatedFields...).Has("project") {
// verify that user can do update inside project where cluster will be located
if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, CreateClusterRBACObject(q.Cluster.Project, c.Server)) {
@@ -341,7 +348,8 @@ func (s *Server) Delete(ctx context.Context, q *cluster.ClusterQuery) (*cluster.
if q.Name != "" {
servers, err := s.db.GetClusterServersByName(ctx, q.Name)
if err != nil {
return nil, err
log.WithField("cluster", q.Name).Warnf("failed to get cluster servers by name: %v", err)
return nil, common.PermissionDeniedAPIError
}
for _, server := range servers {
if err := enforceAndDelete(s, ctx, server, c.Project); err != nil {
@@ -359,7 +367,8 @@ func (s *Server) Delete(ctx context.Context, q *cluster.ClusterQuery) (*cluster.
func enforceAndDelete(s *Server, ctx context.Context, server, project string) error {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionDelete, CreateClusterRBACObject(project, server)); err != nil {
return err
log.WithField("cluster", server).Warnf("encountered permissions issue while processing request: %v", err)
return common.PermissionDeniedAPIError
}
if err := s.db.DeleteCluster(ctx, server); err != nil {
return err
@@ -378,16 +387,19 @@ func (s *Server) RotateAuth(ctx context.Context, q *cluster.ClusterQuery) (*clus
if q.Name != "" {
servers, err = s.db.GetClusterServersByName(ctx, q.Name)
if err != nil {
return nil, status.Errorf(codes.NotFound, "failed to get cluster servers by name: %v", err)
log.WithField("cluster", q.Name).Warnf("failed to get cluster servers by name: %v", err)
return nil, common.PermissionDeniedAPIError
}
for _, server := range servers {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, CreateClusterRBACObject(clust.Project, server)); err != nil {
return nil, status.Errorf(codes.PermissionDenied, "encountered permissions issue while processing request: %v", err)
log.WithField("cluster", server).Warnf("encountered permissions issue while processing request: %v", err)
return nil, common.PermissionDeniedAPIError
}
}
} else {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, CreateClusterRBACObject(clust.Project, q.Server)); err != nil {
return nil, status.Errorf(codes.PermissionDenied, "encountered permissions issue while processing request: %v", err)
log.WithField("cluster", q.Server).Warnf("encountered permissions issue while processing request: %v", err)
return nil, common.PermissionDeniedAPIError
}
servers = append(servers, q.Server)
}
@@ -467,13 +479,10 @@ func (s *Server) toAPIResponse(clust *appv1.Cluster) *appv1.Cluster {
// InvalidateCache invalidates cluster cache
func (s *Server) InvalidateCache(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Cluster, error) {
cls, err := s.getClusterWith403IfNotExist(ctx, q)
cls, err := s.getClusterAndVerifyAccess(ctx, q, rbacpolicy.ActionUpdate)
if err != nil {
return nil, err
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, CreateClusterRBACObject(cls.Project, q.Server)); err != nil {
return nil, err
}
now := v1.Now()
cls.RefreshRequestedAt = &now
cls, err = s.db.UpdateCluster(ctx, cls)

View File

@@ -4,6 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
"github.com/argoproj/argo-cd/v2/util/assets"
"github.com/golang-jwt/jwt/v4"
"reflect"
"testing"
"time"
@@ -51,6 +54,16 @@ func newNoopEnforcer() *rbac.Enforcer {
return enf
}
func newEnforcer() *rbac.Enforcer {
enforcer := rbac.NewEnforcer(fake.NewSimpleClientset(test.NewFakeConfigMap()), test.FakeArgoCDNamespace, common.ArgoCDRBACConfigMapName, nil)
_ = enforcer.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
enforcer.SetDefaultRole("role:test")
enforcer.SetClaimsEnforcerFunc(func(claims jwt.Claims, rvals ...interface{}) bool {
return true
})
return enforcer
}
func TestUpdateCluster_RejectInvalidParams(t *testing.T) {
testCases := []struct {
name string
@@ -604,3 +617,152 @@ func TestListCluster(t *testing.T) {
})
}
}
func TestGetClusterAndVerifyAccess(t *testing.T) {
t.Run("GetClusterAndVerifyAccess - No Cluster", func(t *testing.T) {
db := &dbmocks.ArgoDB{}
mockCluster := v1alpha1.Cluster{
Name: "test/ing",
Server: "https://127.0.0.1",
Namespaces: []string{"default", "kube-system"},
}
mockClusterList := v1alpha1.ClusterList{
ListMeta: v1.ListMeta{},
Items: []v1alpha1.Cluster{
mockCluster,
},
}
db.On("ListClusters", mock.Anything).Return(&mockClusterList, nil)
server := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
cluster, err := server.getClusterAndVerifyAccess(context.Background(), &clusterapi.ClusterQuery{
Name: "test/not-exists",
}, rbacpolicy.ActionGet)
assert.Nil(t, cluster)
assert.ErrorIs(t, err, common.PermissionDeniedAPIError)
})
t.Run("GetClusterAndVerifyAccess - Permissions Denied", func(t *testing.T) {
db := &dbmocks.ArgoDB{}
mockCluster := v1alpha1.Cluster{
Name: "test/ing",
Server: "https://127.0.0.1",
Namespaces: []string{"default", "kube-system"},
}
mockClusterList := v1alpha1.ClusterList{
ListMeta: v1.ListMeta{},
Items: []v1alpha1.Cluster{
mockCluster,
},
}
db.On("ListClusters", mock.Anything).Return(&mockClusterList, nil)
server := NewServer(db, newEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
cluster, err := server.getClusterAndVerifyAccess(context.Background(), &clusterapi.ClusterQuery{
Name: "test/ing",
}, rbacpolicy.ActionGet)
assert.Nil(t, cluster)
assert.ErrorIs(t, err, common.PermissionDeniedAPIError)
})
}
func TestNoClusterEnumeration(t *testing.T) {
db := &dbmocks.ArgoDB{}
mockCluster := v1alpha1.Cluster{
Name: "test/ing",
Server: "https://127.0.0.1",
Namespaces: []string{"default", "kube-system"},
}
mockClusterList := v1alpha1.ClusterList{
ListMeta: v1.ListMeta{},
Items: []v1alpha1.Cluster{
mockCluster,
},
}
db.On("ListClusters", mock.Anything).Return(&mockClusterList, nil)
db.On("GetCluster", mock.Anything, mock.Anything).Return(&mockCluster, nil)
server := NewServer(db, newEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
t.Run("Get", func(t *testing.T) {
_, err := server.Get(context.Background(), &clusterapi.ClusterQuery{
Name: "cluster-not-exists",
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
_, err = server.Get(context.Background(), &clusterapi.ClusterQuery{
Name: "test/ing",
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
})
t.Run("Update", func(t *testing.T) {
_, err := server.Update(context.Background(), &clusterapi.ClusterUpdateRequest{
Cluster: &v1alpha1.Cluster{
Name: "cluster-not-exists",
},
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
_, err = server.Update(context.Background(), &clusterapi.ClusterUpdateRequest{
Cluster: &v1alpha1.Cluster{
Name: "test/ing",
},
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
})
t.Run("Delete", func(t *testing.T) {
_, err := server.Delete(context.Background(), &clusterapi.ClusterQuery{
Server: "https://127.0.0.2",
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
_, err = server.Delete(context.Background(), &clusterapi.ClusterQuery{
Server: "https://127.0.0.1",
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
})
t.Run("RotateAuth", func(t *testing.T) {
_, err := server.RotateAuth(context.Background(), &clusterapi.ClusterQuery{
Server: "https://127.0.0.2",
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
_, err = server.RotateAuth(context.Background(), &clusterapi.ClusterQuery{
Server: "https://127.0.0.1",
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
})
t.Run("InvalidateCache", func(t *testing.T) {
_, err := server.InvalidateCache(context.Background(), &clusterapi.ClusterQuery{
Server: "https://127.0.0.2",
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
_, err = server.InvalidateCache(context.Background(), &clusterapi.ClusterQuery{
Server: "https://127.0.0.1",
})
assert.Error(t, err)
assert.Equal(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about cluster existence")
})
}

View File

@@ -1048,7 +1048,7 @@ 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.ArgoCDServerOpts.ApplicationNamespaces, a.AppClientset, a.settings, a.settingsMgr, a.RepoServerCache, a.Cache, argoDB)
acdWebhookHandler := webhook.NewHandler(a.Namespace, a.ArgoCDServerOpts.ApplicationNamespaces, a.AppClientset, a.settings, a.settingsMgr, a.RepoServerCache, a.Cache, argoDB, a.settingsMgr.GetMaxWebhookPayloadSize())
mux.HandleFunc("/api/webhook", acdWebhookHandler.Handler)

View File

@@ -109,7 +109,6 @@ func (s *Server) Get(ctx context.Context, q *settingspkg.SettingsQuery) (*settin
UserLoginsDisabled: userLoginsDisabled,
KustomizeVersions: kustomizeVersions,
UiCssURL: argoCDSettings.UiCssURL,
PasswordPattern: argoCDSettings.PasswordPattern,
TrackingMethod: trackingMethod,
ExecEnabled: argoCDSettings.ExecEnabled,
AppsInAnyNamespaceEnabled: s.appsInAnyNamespaceEnabled,
@@ -122,6 +121,9 @@ func (s *Server) Get(ctx context.Context, q *settingspkg.SettingsQuery) (*settin
set.UiBannerPosition = argoCDSettings.UiBannerPosition
set.ControllerNamespace = s.mgr.GetNamespace()
}
if sessionmgr.LoggedIn(ctx) {
set.PasswordPattern = argoCDSettings.PasswordPattern
}
if argoCDSettings.DexConfig != "" {
var cfg settingspkg.DexConfig
err = yaml.Unmarshal([]byte(argoCDSettings.DexConfig), &cfg)

View File

@@ -8,7 +8,7 @@ RUN ln -s /usr/lib/$(uname -m)-linux-gnu /usr/lib/linux-gnu
# Please make sure to also check the contained yarn version and update the references below when upgrading this image's version
FROM docker.io/library/node:21.7.1@sha256:b9ccc4aca32eebf124e0ca0fd573dacffba2b9236987a1d4d2625ce3c162ecc8 as node
FROM docker.io/library/golang:1.21.9@sha256:7d0dcbe5807b1ad7272a598fbf9d7af15b5e2bed4fd6c4c2b5b3684df0b317dd as golang
FROM docker.io/library/golang:1.21.10@sha256:16438a8e66c0c984f732e815ee5b7d715b8e33e81bac6d6a3750b1067744e7ca as golang
FROM docker.io/library/registry:2.8@sha256:fb9c9aef62af3955f6014613456551c92e88a67dcf1fc51f5f91bcbd1832813f as registry

View File

@@ -523,100 +523,6 @@ func TestSimpleListGeneratorGoTemplate(t *testing.T) {
}
func TestCreateApplicationDespiteParamsError(t *testing.T) {
expectedErrorMessage := `failed to execute go template {{.cluster}}-guestbook: template: :1:2: executing "" at <.cluster>: map has no entry for key "cluster"`
expectedConditionsParamsError := []v1alpha1.ApplicationSetCondition{
{
Type: v1alpha1.ApplicationSetConditionErrorOccurred,
Status: v1alpha1.ApplicationSetConditionStatusTrue,
Message: expectedErrorMessage,
Reason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
},
{
Type: v1alpha1.ApplicationSetConditionParametersGenerated,
Status: v1alpha1.ApplicationSetConditionStatusFalse,
Message: expectedErrorMessage,
Reason: v1alpha1.ApplicationSetReasonErrorOccurred,
},
{
Type: v1alpha1.ApplicationSetConditionResourcesUpToDate,
Status: v1alpha1.ApplicationSetConditionStatusFalse,
Message: expectedErrorMessage,
Reason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
},
}
expectedApp := argov1alpha1.Application{
TypeMeta: metav1.TypeMeta{
Kind: application.ApplicationKind,
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
Source: &argov1alpha1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: argov1alpha1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
}
Given(t).
// Create a ListGenerator-based ApplicationSet
When().Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{
Name: "simple-list-generator",
},
Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true,
GoTemplateOptions: []string{"missingkey=error"},
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{.cluster}}-guestbook"},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
Source: &argov1alpha1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: argov1alpha1.ApplicationDestination{
Server: "{{.url}}",
Namespace: "guestbook",
},
},
},
Generators: []v1alpha1.ApplicationSetGenerator{
{
List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
},
{
Raw: []byte(`{"invalidCluster": "invalid-cluster","url": "https://kubernetes.default.svc"}`),
}},
},
},
},
},
}).Then().Expect(ApplicationsExist([]argov1alpha1.Application{expectedApp})).
// verify the ApplicationSet status conditions were set correctly
Expect(ApplicationSetHasConditions("simple-list-generator", expectedConditionsParamsError)).
// Delete the ApplicationSet, and verify it deletes the Applications
When().
Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{expectedApp}))
}
func TestRenderHelmValuesObject(t *testing.T) {
expectedApp := argov1alpha1.Application{

View File

@@ -91,7 +91,7 @@ func TestClusterAddPermissionDenied(t *testing.T) {
Create().
Then().
AndCLIOutput(func(output string, err error) {
assert.True(t, strings.Contains(err.Error(), "PermissionDenied desc = permission denied: clusters, create"))
assert.Contains(t, err.Error(), "PermissionDenied desc = permission denied")
})
}
@@ -256,7 +256,7 @@ func TestClusterDeleteDenied(t *testing.T) {
DeleteByName().
Then().
AndCLIOutput(func(output string, err error) {
assert.True(t, strings.Contains(err.Error(), "PermissionDenied desc = permission denied: clusters, delete"))
assert.Contains(t, err.Error(), "PermissionDenied desc = permission denied")
})
// Attempt to remove cluster creds by server
@@ -270,7 +270,7 @@ func TestClusterDeleteDenied(t *testing.T) {
DeleteByServer().
Then().
AndCLIOutput(func(output string, err error) {
assert.True(t, strings.Contains(err.Error(), "PermissionDenied desc = permission denied: clusters, delete"))
assert.Contains(t, err.Error(), "PermissionDenied desc = permission denied")
})
}

View File

@@ -2841,11 +2841,11 @@ braces@^2.3.1:
to-regex "^3.0.1"
braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.0.1"
fill-range "^7.1.1"
browser-process-hrtime@^1.0.0:
version "1.0.0"
@@ -4474,10 +4474,10 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"

View File

@@ -112,12 +112,12 @@ func GetAppRefreshPaths(app *v1alpha1.Application) []string {
}
// AppFilesHaveChanged returns true if any of the changed files are under the given refresh paths
// If refreshPaths is empty, it will always return true
// If refreshPaths or changedFiles are empty, it will always return true
func AppFilesHaveChanged(refreshPaths []string, changedFiles []string) bool {
// empty slice means there was no changes to any files
// so we should not refresh
// an empty slice of changed files means that the payload didn't include a list
// of changed files and we have to assume that a refresh is required
if len(changedFiles) == 0 {
return false
return true
}
if len(refreshPaths) == 0 {

View File

@@ -133,7 +133,7 @@ func Test_AppFilesHaveChanged(t *testing.T) {
changeExpected bool
}{
{"default no path", &v1alpha1.Application{}, []string{"README.md"}, true},
{"no files changed", getApp(".", "source/path"), []string{}, false},
{"no files changed", getApp(".", "source/path"), []string{}, true},
{"relative path - matching", getApp(".", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
{"relative path, multi source - matching #1", getMultiSourceApp(".", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
{"relative path, multi source - matching #2", getMultiSourceApp(".", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},

View File

@@ -2,7 +2,6 @@ package argo
import (
"context"
"encoding/json"
"fmt"
"time"
@@ -46,7 +45,7 @@ const (
EventReasonOperationCompleted = "OperationCompleted"
)
func (l *AuditLogger) logEvent(objMeta ObjectRef, gvk schema.GroupVersionKind, info EventInfo, message string, logFields map[string]interface{}) {
func (l *AuditLogger) logEvent(objMeta ObjectRef, gvk schema.GroupVersionKind, info EventInfo, message string, logFields map[string]string) {
logCtx := log.WithFields(log.Fields{
"type": info.Type,
"reason": info.Reason,
@@ -54,19 +53,6 @@ func (l *AuditLogger) logEvent(objMeta ObjectRef, gvk schema.GroupVersionKind, i
for field, val := range logFields {
logCtx = logCtx.WithField(field, val)
}
logFieldStrings := make(map[string]string)
for field, val := range logFields {
if valStr, ok := val.(string); ok {
logFieldStrings[field] = valStr
continue
}
vJsonStr, err := json.Marshal(val)
if err != nil {
logCtx.Errorf("Unable to marshal audit event field %v: %v", field, err)
continue
}
logFieldStrings[field] = string(vJsonStr)
}
switch gvk.Kind {
case application.ApplicationKind:
@@ -80,7 +66,7 @@ func (l *AuditLogger) logEvent(objMeta ObjectRef, gvk schema.GroupVersionKind, i
event := v1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%v.%x", objMeta.Name, t.UnixNano()),
Annotations: logFieldStrings,
Annotations: logFields,
},
Source: v1.EventSource{
Component: l.component,
@@ -115,14 +101,13 @@ func (l *AuditLogger) LogAppEvent(app *v1alpha1.Application, info EventInfo, mes
ResourceVersion: app.ObjectMeta.ResourceVersion,
UID: app.ObjectMeta.UID,
}
fields := map[string]interface{}{
fields := map[string]string{
"dest-server": app.Spec.Destination.Server,
"dest-namespace": app.Spec.Destination.Namespace,
}
if user != "" {
fields["user"] = user
}
fields["spec"] = app.Spec
l.logEvent(objectMeta, v1alpha1.ApplicationSchemaGroupVersionKind, info, message, fields)
}
@@ -133,7 +118,7 @@ func (l *AuditLogger) LogAppSetEvent(app *v1alpha1.ApplicationSet, info EventInf
ResourceVersion: app.ObjectMeta.ResourceVersion,
UID: app.ObjectMeta.UID,
}
fields := make(map[string]interface{})
fields := map[string]string{}
if user != "" {
fields["user"] = user
}
@@ -147,7 +132,7 @@ func (l *AuditLogger) LogResourceEvent(res *v1alpha1.ResourceNode, info EventInf
ResourceVersion: res.ResourceRef.Version,
UID: types.UID(res.ResourceRef.UID),
}
fields := make(map[string]interface{})
fields := map[string]string{}
if user != "" {
fields["user"] = user
}

View File

@@ -1,7 +1,9 @@
package argo
import (
"errors"
"fmt"
"regexp"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -21,6 +23,7 @@ const (
var WrongResourceTrackingFormat = fmt.Errorf("wrong resource tracking format, should be <application-name>:<group>/<kind>:<namespace>/<name>")
var LabelMaxLength = 63
var OkEndPattern = regexp.MustCompile("[a-zA-Z0-9]$")
// ResourceTracking defines methods which allow setup and retrieve tracking information to resource
type ResourceTracking interface {
@@ -155,6 +158,14 @@ func (rt *resourceTracking) SetAppInstance(un *unstructured.Unstructured, key, v
}
if len(val) > LabelMaxLength {
val = val[:LabelMaxLength]
// Prevent errors if the truncated name ends in a special character.
// See https://github.com/argoproj/argo-cd/issues/18237.
for !OkEndPattern.MatchString(val) {
if len(val) <= 1 {
return errors.New("failed to set app instance label: unable to truncate label to not end with a special character")
}
val = val[:len(val)-1]
}
}
err = argokube.SetAppInstanceLabel(un, key, val)
if err != nil {

View File

@@ -62,6 +62,60 @@ func TestSetAppInstanceAnnotationAndLabel(t *testing.T) {
assert.Equal(t, "my-app", app)
}
func TestSetAppInstanceAnnotationAndLabelLongName(t *testing.T) {
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
assert.Nil(t, err)
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
resourceTracking := NewResourceTracking()
err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters", "", TrackingMethodAnnotationAndLabel)
assert.Nil(t, err)
// the annotation should still work, so the name from GetAppName should not be truncated
app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, TrackingMethodAnnotationAndLabel)
assert.Equal(t, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters", app)
// the label should be truncated to 63 characters
assert.Equal(t, obj.GetLabels()[common.LabelKeyAppInstance], "my-app-with-an-extremely-long-name-that-is-over-sixty-three-cha")
}
func TestSetAppInstanceAnnotationAndLabelLongNameBadEnding(t *testing.T) {
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
assert.Nil(t, err)
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
resourceTracking := NewResourceTracking()
err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "the-very-suspicious-name-with-precisely-sixty-three-characters-with-hyphen", "", TrackingMethodAnnotationAndLabel)
assert.Nil(t, err)
// the annotation should still work, so the name from GetAppName should not be truncated
app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, TrackingMethodAnnotationAndLabel)
assert.Equal(t, "the-very-suspicious-name-with-precisely-sixty-three-characters-with-hyphen", app)
// the label should be truncated to 63 characters, AND the hyphen should be removed
assert.Equal(t, obj.GetLabels()[common.LabelKeyAppInstance], "the-very-suspicious-name-with-precisely-sixty-three-characters")
}
func TestSetAppInstanceAnnotationAndLabelOutOfBounds(t *testing.T) {
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
assert.Nil(t, err)
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
resourceTracking := NewResourceTracking()
err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "----------------------------------------------------------------", "", TrackingMethodAnnotationAndLabel)
// this should error because it can't truncate to a valid value
assert.EqualError(t, err, "failed to set app instance label: unable to truncate label to not end with a special character")
}
func TestSetAppInstanceAnnotationNotFound(t *testing.T) {
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
assert.Nil(t, err)

View File

@@ -431,6 +431,8 @@ const (
settingsWebhookAzureDevOpsUsernameKey = "webhook.azuredevops.username"
// settingsWebhookAzureDevOpsPasswordKey is the key for Azure DevOps webhook password
settingsWebhookAzureDevOpsPasswordKey = "webhook.azuredevops.password"
// settingsWebhookMaxPayloadSize is the key for the maximum payload size for webhooks in MB
settingsWebhookMaxPayloadSizeMB = "webhook.maxPayloadSizeMB"
// settingsApplicationInstanceLabelKey is the key to configure injected app instance label key
settingsApplicationInstanceLabelKey = "application.instanceLabelKey"
// settingsResourceTrackingMethodKey is the key to configure tracking method for application resources
@@ -518,6 +520,11 @@ var (
}
)
const (
// default max webhook payload size is 1GB
defaultMaxWebhookPayloadSize = int64(1) * 1024 * 1024 * 1024
)
// SettingsManager holds config info for a new manager with which to access Kubernetes ConfigMaps.
type SettingsManager struct {
ctx context.Context
@@ -2221,3 +2228,22 @@ func (mgr *SettingsManager) GetResourceCustomLabels() ([]string, error) {
}
return []string{}, nil
}
func (mgr *SettingsManager) GetMaxWebhookPayloadSize() int64 {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return defaultMaxWebhookPayloadSize
}
if argoCDCM.Data[settingsWebhookMaxPayloadSizeMB] == "" {
return defaultMaxWebhookPayloadSize
}
maxPayloadSizeMB, err := strconv.ParseInt(argoCDCM.Data[settingsWebhookMaxPayloadSizeMB], 10, 64)
if err != nil {
log.Warnf("Failed to parse '%s' key: %v", settingsWebhookMaxPayloadSizeMB, err)
return defaultMaxWebhookPayloadSize
}
return maxPayloadSizeMB * 1024 * 1024
}

View File

@@ -41,6 +41,8 @@ type settingsSource interface {
// https://github.com/shadow-maint/shadow/blob/master/libmisc/chkname.c#L36
const usernameRegex = `[a-zA-Z0-9_\.][a-zA-Z0-9_\.-]{0,30}[a-zA-Z0-9_\.\$-]?`
const payloadQueueSize = 50000
var (
_ settingsSource = &settings.SettingsManager{}
errBasicAuthVerificationFailed = errors.New("basic auth verification failed")
@@ -61,9 +63,11 @@ type ArgoCDWebhookHandler struct {
azuredevopsAuthHandler func(r *http.Request) error
gogs *gogs.Webhook
settingsSrc settingsSource
queue chan interface{}
maxWebhookPayloadSizeB int64
}
func NewHandler(namespace string, applicationNamespaces []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, maxWebhookPayloadSizeB int64) *ArgoCDWebhookHandler {
githubWebhook, err := github.New(github.Options.Secret(set.WebhookGitHubSecret))
if err != nil {
log.Warnf("Unable to init the GitHub webhook")
@@ -113,6 +117,8 @@ func NewHandler(namespace string, applicationNamespaces []string, appClientset a
repoCache: repoCache,
serverCache: serverCache,
db: argoDB,
queue: make(chan interface{}, payloadQueueSize),
maxWebhookPayloadSizeB: maxWebhookPayloadSizeB,
}
return &acdWebhook
@@ -388,6 +394,8 @@ func (a *ArgoCDWebhookHandler) Handler(w http.ResponseWriter, r *http.Request) {
var payload interface{}
var err error
r.Body = http.MaxBytesReader(w, r.Body, a.maxWebhookPayloadSizeB)
switch {
case r.Header.Get("X-Vss-Activityid") != "":
if err = a.azuredevopsAuthHandler(r); err != nil {
@@ -430,6 +438,14 @@ func (a *ArgoCDWebhookHandler) Handler(w http.ResponseWriter, r *http.Request) {
}
if err != nil {
// If the error is due to a large payload, return a more user-friendly error message
if err.Error() == "error parsing payload" {
msg := fmt.Sprintf("Webhook processing failed: The payload is either too large or corrupted. Please check the payload size (must be under %v MB) and ensure it is valid JSON", a.maxWebhookPayloadSizeB/1024/1024)
log.WithField(common.SecurityField, common.SecurityHigh).Warn(msg)
http.Error(w, msg, http.StatusBadRequest)
return
}
log.Infof("Webhook processing failed: %s", err)
status := http.StatusBadRequest
if r.Method != http.MethodPost {

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/stretchr/testify/require"
"io"
"net/http"
"net/http/httptest"
@@ -56,6 +57,11 @@ type reactorDef struct {
}
func NewMockHandler(reactor *reactorDef, applicationNamespaces []string, objects ...runtime.Object) *ArgoCDWebhookHandler {
defaultMaxPayloadSize := int64(1) * 1024 * 1024 * 1024
return NewMockHandlerWithPayloadLimit(reactor, applicationNamespaces, defaultMaxPayloadSize, objects...)
}
func NewMockHandlerWithPayloadLimit(reactor *reactorDef, applicationNamespaces []string, maxPayloadSize int64, objects ...runtime.Object) *ArgoCDWebhookHandler {
appClientset := appclientset.NewSimpleClientset(objects...)
if reactor != nil {
defaultReactor := appClientset.ReactionChain[0]
@@ -72,7 +78,7 @@ func NewMockHandler(reactor *reactorDef, applicationNamespaces []string, objects
1*time.Minute,
1*time.Minute,
10*time.Second,
), servercache.NewCache(appstate.NewCache(cacheClient, time.Minute), time.Minute, time.Minute, time.Minute), &mocks.ArgoDB{})
), servercache.NewCache(appstate.NewCache(cacheClient, time.Minute), time.Minute, time.Minute, time.Minute), &mocks.ArgoDB{}, maxPayloadSize)
}
func TestGitHubCommitEvent(t *testing.T) {
@@ -392,8 +398,9 @@ func TestInvalidEvent(t *testing.T) {
req.Header.Set("X-GitHub-Event", "push")
w := httptest.NewRecorder()
h.Handler(w, req)
assert.Equal(t, w.Code, http.StatusBadRequest)
expectedLogResult := "Webhook processing failed: error parsing payload"
close(h.queue)
assert.Equal(t, http.StatusBadRequest, w.Code)
expectedLogResult := "Webhook processing failed: The payload is either too large or corrupted. Please check the payload size (must be under 1024 MB) and ensure it is valid JSON"
assert.Equal(t, expectedLogResult, hook.LastEntry().Message)
assert.Equal(t, expectedLogResult+"\n", w.Body.String())
hook.Reset()
@@ -604,3 +611,21 @@ func Test_getWebUrlRegex(t *testing.T) {
})
}
}
func TestGitHubCommitEventMaxPayloadSize(t *testing.T) {
hook := test.NewGlobal()
maxPayloadSize := int64(100)
h := NewMockHandlerWithPayloadLimit(nil, []string{}, maxPayloadSize)
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
req.Header.Set("X-GitHub-Event", "push")
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
require.NoError(t, err)
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
w := httptest.NewRecorder()
h.Handler(w, req)
close(h.queue)
assert.Equal(t, http.StatusBadRequest, w.Code)
expectedLogResult := "Webhook processing failed: The payload is either too large or corrupted. Please check the payload size (must be under 0 MB) and ensure it is valid JSON"
assert.Equal(t, expectedLogResult, hook.LastEntry().Message)
hook.Reset()
}