mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-20 07:18:47 +01:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4a0246c4d | ||
|
|
05edb2a9ca | ||
|
|
089247df0f | ||
|
|
540e3a57b9 | ||
|
|
b980386388 | ||
|
|
c4b283ce0c | ||
|
|
d1c052d7bf | ||
|
|
c0f780c380 | ||
|
|
e1284e19e0 | ||
|
|
9e313e539b | ||
|
|
0d1709f73b | ||
|
|
bfbceff5da | ||
|
|
0e71f09990 | ||
|
|
07880f3c1d | ||
|
|
24b198bf51 | ||
|
|
5fd645feac | ||
|
|
b5c13b6139 | ||
|
|
d75b23bf92 | ||
|
|
ac80860eda | ||
|
|
c2bd38a11a | ||
|
|
13844b90ad | ||
|
|
3f344d54a4 | ||
|
|
e01bb5303a | ||
|
|
320abb8d64 | ||
|
|
46342a9e82 | ||
|
|
cf17283ebe |
@@ -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
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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.
|
||||
|
||||
98
applicationset/generators/mocks/Generator.go
Normal file
98
applicationset/generators/mocks/Generator.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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; }}"
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
2
go.mod
2
go.mod
@@ -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
4
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.11.2
|
||||
newTag: v2.11.7
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
16
ui/yarn.lock
16
ui/yarn.lock
@@ -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"
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user