mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 17:48:47 +01:00
Compare commits
40 Commits
crenshaw-d
...
v2.12.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b9cd828c6 | ||
|
|
cafd35cea7 | ||
|
|
343dec049a | ||
|
|
560953c37b | ||
|
|
7244c2d5e5 | ||
|
|
b068220503 | ||
|
|
c873d5c68a | ||
|
|
88f85daf52 | ||
|
|
26b2039a55 | ||
|
|
952838cdde | ||
|
|
7af4526666 | ||
|
|
b156b61e22 | ||
|
|
fd478450e6 | ||
|
|
ec30a48bce | ||
|
|
57e61b2d8a | ||
|
|
6f2ae0dd46 | ||
|
|
3e31ce9470 | ||
|
|
dee59f3002 | ||
|
|
d6c37aab88 | ||
|
|
eaa1972e69 | ||
|
|
004cabba47 | ||
|
|
d9263101fc | ||
|
|
cec8504df2 | ||
|
|
0704aa6506 | ||
|
|
511d6e371c | ||
|
|
065f8494cf | ||
|
|
beacacc43d | ||
|
|
81d454215d | ||
|
|
1237d4ea17 | ||
|
|
b211d3e038 | ||
|
|
50c32b53f0 | ||
|
|
444d332d0a | ||
|
|
573c771d2b | ||
|
|
e616dff2d1 | ||
|
|
e36e19de4e | ||
|
|
bbfa79ad9e | ||
|
|
00fc93b8b2 | ||
|
|
16c20a84b8 | ||
|
|
2a9a62eeb7 | ||
|
|
fe60670885 |
@@ -31,11 +31,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
k8scache "k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
@@ -88,7 +86,6 @@ type ApplicationSetReconciler struct {
|
||||
SCMRootCAPath string
|
||||
GlobalPreservedAnnotations []string
|
||||
GlobalPreservedLabels []string
|
||||
Cache cache.Cache
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -126,23 +123,26 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if err := r.migrateStatus(ctx, &applicationSetInfo); err != nil {
|
||||
logCtx.Errorf("failed to migrate status subresource %v", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -320,7 +320,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{
|
||||
@@ -580,7 +580,7 @@ func (r *ApplicationSetReconciler) applyTemplatePatch(app *argov1alpha1.Applicat
|
||||
func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
|
||||
return predicate.Funcs{
|
||||
CreateFunc: func(e event.CreateEvent) bool {
|
||||
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false)
|
||||
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), glob.REGEXP)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -623,25 +623,6 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *ApplicationSetReconciler) updateCache(ctx context.Context, obj client.Object, logger *log.Entry) {
|
||||
informer, err := r.Cache.GetInformer(ctx, obj)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to get informer: %v", err)
|
||||
return
|
||||
}
|
||||
// The controller runtime abstract away informers creation
|
||||
// so unfortunately could not find any other way to access informer store.
|
||||
k8sInformer, ok := informer.(k8scache.SharedInformer)
|
||||
if !ok {
|
||||
logger.Error("informer is not a kubernetes informer")
|
||||
return
|
||||
}
|
||||
if err := k8sInformer.GetStore().Update(obj); err != nil {
|
||||
logger.Errorf("failed to update cache: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// createOrUpdateInCluster will create / update application resources in the cluster.
|
||||
// - For new applications, it will call create
|
||||
// - For existing application, it will call update
|
||||
@@ -743,7 +724,6 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
}
|
||||
continue
|
||||
}
|
||||
r.updateCache(ctx, found, appLog)
|
||||
|
||||
if action != controllerutil.OperationResultNone {
|
||||
// Don't pollute etcd with "unchanged Application" events
|
||||
@@ -910,7 +890,6 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
|
||||
if err := r.Client.Patch(ctx, updated, patch); err != nil {
|
||||
return fmt.Errorf("error updating finalizers: %w", err)
|
||||
}
|
||||
r.updateCache(ctx, updated, appLog)
|
||||
// Application must have updated list of finalizers
|
||||
updated.DeepCopyInto(app)
|
||||
|
||||
@@ -1153,6 +1132,13 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
|
||||
} else {
|
||||
// we have an existing AppStatus
|
||||
currentAppStatus = applicationSet.Status.ApplicationStatus[idx]
|
||||
|
||||
// upgrade any existing AppStatus that might have been set by an older argo-cd version
|
||||
// note: currentAppStatus.TargetRevisions may be set to empty list earlier during migrations,
|
||||
// to prevent other usage of r.Client.Status().Update to fail before reaching here.
|
||||
if currentAppStatus.TargetRevisions == nil || len(currentAppStatus.TargetRevisions) == 0 {
|
||||
currentAppStatus.TargetRevisions = app.Status.GetRevisions()
|
||||
}
|
||||
}
|
||||
|
||||
appOutdated := false
|
||||
@@ -1350,6 +1336,27 @@ func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplica
|
||||
return -1
|
||||
}
|
||||
|
||||
// migrateStatus run migrations on the status subresource of ApplicationSet early during the run of ApplicationSetReconciler.Reconcile
|
||||
// this handles any defaulting of values - which would otherwise cause the references to r.Client.Status().Update to fail given missing required fields.
|
||||
func (r *ApplicationSetReconciler) migrateStatus(ctx context.Context, appset *argov1alpha1.ApplicationSet) error {
|
||||
update := false
|
||||
if statusList := appset.Status.ApplicationStatus; statusList != nil {
|
||||
for idx := range statusList {
|
||||
if statusList[idx].TargetRevisions == nil {
|
||||
statusList[idx].TargetRevisions = []string{}
|
||||
update = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if update {
|
||||
if err := r.Client.Status().Update(ctx, appset); err != nil {
|
||||
return fmt.Errorf("unable to set application set status: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, logCtx *log.Entry, appset *argov1alpha1.ApplicationSet, apps []argov1alpha1.Application) error {
|
||||
statusMap := getResourceStatusMap(appset)
|
||||
statusMap = buildResourceStatus(statusMap, apps)
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/generators/mocks"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -20,11 +22,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
k8scache "k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
crtcache "sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
crtclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
@@ -45,34 +44,6 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
|
||||
)
|
||||
|
||||
type fakeStore struct {
|
||||
k8scache.Store
|
||||
}
|
||||
|
||||
func (f *fakeStore) Update(obj interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeInformer struct {
|
||||
k8scache.SharedInformer
|
||||
}
|
||||
|
||||
func (f *fakeInformer) AddIndexers(indexers k8scache.Indexers) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeInformer) GetStore() k8scache.Store {
|
||||
return &fakeStore{}
|
||||
}
|
||||
|
||||
type fakeCache struct {
|
||||
cache.Cache
|
||||
}
|
||||
|
||||
func (f *fakeCache) GetInformer(ctx context.Context, obj crtclient.Object, opt ...crtcache.InformerGetOption) (cache.Informer, error) {
|
||||
return &fakeInformer{}, nil
|
||||
}
|
||||
|
||||
type generatorMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
@@ -224,7 +195,6 @@ func TestExtractApplications(t *testing.T) {
|
||||
},
|
||||
Renderer: &rendererMock,
|
||||
KubeClientset: kubefake.NewSimpleClientset(),
|
||||
Cache: &fakeCache{},
|
||||
}
|
||||
|
||||
got, reason, err := r.generateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
|
||||
@@ -1361,7 +1331,6 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
|
||||
Cache: &fakeCache{},
|
||||
}
|
||||
|
||||
err = r.createOrUpdateInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps)
|
||||
@@ -1472,7 +1441,6 @@ func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(10),
|
||||
KubeClientset: kubeclientset,
|
||||
Cache: &fakeCache{},
|
||||
}
|
||||
// settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "namespace")
|
||||
// argoDB := db.NewDB("namespace", settingsMgr, r.KubeClientset)
|
||||
@@ -1630,7 +1598,6 @@ func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(10),
|
||||
KubeClientset: kubeclientset,
|
||||
Cache: &fakeCache{},
|
||||
}
|
||||
// settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "argocd")
|
||||
// argoDB := db.NewDB("argocd", settingsMgr, r.KubeClientset)
|
||||
@@ -1718,7 +1685,6 @@ func TestRemoveOwnerReferencesOnDeleteAppSet(t *testing.T) {
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(10),
|
||||
KubeClientset: nil,
|
||||
Cache: &fakeCache{},
|
||||
}
|
||||
|
||||
err = r.removeOwnerReferencesOnDeleteAppSet(context.Background(), appSet)
|
||||
@@ -1915,7 +1881,6 @@ func TestCreateApplications(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
|
||||
Cache: &fakeCache{},
|
||||
}
|
||||
|
||||
err = r.createInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.apps)
|
||||
@@ -2122,7 +2087,6 @@ func TestGetMinRequeueAfter(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(0),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": &generatorMock10,
|
||||
"Git": &generatorMock1,
|
||||
@@ -2139,6 +2103,57 @@ func TestGetMinRequeueAfter(t *testing.T) {
|
||||
assert.Equal(t, time.Duration(1)*time.Second, got)
|
||||
}
|
||||
|
||||
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),
|
||||
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)
|
||||
@@ -2333,7 +2348,6 @@ func TestValidateGeneratedApplications(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoCDNamespace: "namespace",
|
||||
@@ -2436,7 +2450,6 @@ func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
@@ -2471,90 +2484,6 @@ func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestReconcilerCreateAppsRecoveringRenderError(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
require.NoError(t, err)
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
require.NoError(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).WithStatusSubresource(&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)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ReconcileRequeueOnValidationError, res.RequeueAfter)
|
||||
|
||||
var app v1alpha1.Application
|
||||
|
||||
// make sure good app got created
|
||||
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "app"}, &app)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "app", app.Name)
|
||||
}
|
||||
|
||||
func TestSetApplicationSetStatusCondition(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
@@ -2597,7 +2526,6 @@ func TestSetApplicationSetStatusCondition(t *testing.T) {
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
@@ -2671,7 +2599,6 @@ func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(recordBuffer),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
@@ -2835,7 +2762,6 @@ func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(recordBuffer),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
@@ -3021,7 +2947,6 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"PullRequest": &generatorMock,
|
||||
},
|
||||
@@ -3146,7 +3071,6 @@ func TestPolicies(t *testing.T) {
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(10),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
@@ -3307,7 +3231,6 @@ func TestSetApplicationSetApplicationStatus(t *testing.T) {
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
@@ -4069,7 +3992,6 @@ func TestBuildAppDependencyList(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
@@ -4660,7 +4582,6 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
@@ -4791,6 +4712,58 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handles an outdated list of statuses with a healthy application, setting required variables",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apps: []v1alpha1.Application{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app1",
|
||||
},
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
Health: v1alpha1.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
},
|
||||
OperationState: &v1alpha1.OperationState{
|
||||
Phase: common.OperationSucceeded,
|
||||
},
|
||||
Sync: v1alpha1.SyncStatus{
|
||||
Status: v1alpha1.SyncStatusCodeSynced,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "progresses an OutOfSync RollingSync application to waiting",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
@@ -4880,10 +4853,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "",
|
||||
Status: "Pending",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "",
|
||||
Status: "Pending",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Next"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4902,15 +4876,16 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource became Progressing, updating status from Pending to Progressing.",
|
||||
Status: "Progressing",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "Application resource became Progressing, updating status from Pending to Progressing.",
|
||||
Status: "Progressing",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Next"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "progresses a pending syncing application to progressing",
|
||||
name: "progresses a pending synced application to progressing",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
@@ -4925,10 +4900,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "",
|
||||
Status: "Pending",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "",
|
||||
Status: "Pending",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4953,10 +4929,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource became Progressing, updating status from Pending to Progressing.",
|
||||
Status: "Progressing",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "Application resource became Progressing, updating status from Pending to Progressing.",
|
||||
Status: "Progressing",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4976,10 +4953,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "",
|
||||
Status: "Progressing",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "",
|
||||
Status: "Progressing",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Next"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -5004,10 +4982,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource became Healthy, updating status from Progressing to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "Application resource became Healthy, updating status from Progressing to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Next"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -5027,10 +5006,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "",
|
||||
Status: "Waiting",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "",
|
||||
Status: "Waiting",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -5055,10 +5035,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -5334,16 +5315,18 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application has pending changes, setting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "Application has pending changes, setting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
{
|
||||
Application: "app2",
|
||||
Message: "Application has pending changes, setting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
Step: "1",
|
||||
Application: "app2",
|
||||
Message: "Application has pending changes, setting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -5368,10 +5351,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
Application: "app1",
|
||||
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -5387,7 +5371,6 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
@@ -5533,9 +5516,10 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application is out of date with the current AppSet generation, setting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
Application: "app1",
|
||||
Message: "Application is out of date with the current AppSet generation, setting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
TargetRevisions: []string{"Next"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -5553,6 +5537,7 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
|
||||
Message: "Application moved to Pending status, watching for the Application resource to start Progressing.",
|
||||
Status: "Pending",
|
||||
Step: "1",
|
||||
TargetRevisions: []string{"Next"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -6138,7 +6123,6 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
@@ -6353,7 +6337,6 @@ func TestUpdateResourceStatus(t *testing.T) {
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
@@ -6559,3 +6542,74 @@ func TestOwnsHandler(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateStatus(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
appset v1alpha1.ApplicationSet
|
||||
expectedStatus v1alpha1.ApplicationSetStatus
|
||||
}{
|
||||
{
|
||||
name: "status without applicationstatus target revisions set will default to empty list",
|
||||
appset: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "test",
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
TargetRevisions: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "status with applicationstatus target revisions set will do nothing",
|
||||
appset: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "test",
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
TargetRevisions: []string{"Current"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&tc.appset).WithObjects(&tc.appset).Build()
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
}
|
||||
|
||||
err := r.migrateStatus(context.Background(), &tc.appset)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedStatus, tc.appset.Status)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestRequeueAfter(t *testing.T) {
|
||||
terminalGenerators := map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
|
||||
"Git": generators.NewGitGenerator(mockServer),
|
||||
"Git": generators.NewGitGenerator(mockServer, "namespace"),
|
||||
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}, "", []string{""}, true),
|
||||
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
|
||||
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}, "", []string{""}, true),
|
||||
|
||||
@@ -346,7 +346,7 @@ func getMockClusterGenerator() Generator {
|
||||
func getMockGitGenerator() Generator {
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock)
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
|
||||
return gitGenerator
|
||||
}
|
||||
|
||||
|
||||
@@ -24,13 +24,16 @@ import (
|
||||
var _ Generator = (*GitGenerator)(nil)
|
||||
|
||||
type GitGenerator struct {
|
||||
repos services.Repos
|
||||
repos services.Repos
|
||||
namespace string
|
||||
}
|
||||
|
||||
func NewGitGenerator(repos services.Repos) Generator {
|
||||
func NewGitGenerator(repos services.Repos, namespace string) Generator {
|
||||
g := &GitGenerator{
|
||||
repos: repos,
|
||||
repos: repos,
|
||||
namespace: namespace,
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
@@ -59,21 +62,25 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
|
||||
|
||||
noRevisionCache := appSet.RefreshRequired()
|
||||
|
||||
var project string
|
||||
if strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
|
||||
project = appSetGenerator.Git.Template.Spec.Project
|
||||
} else {
|
||||
project = appSet.Spec.Template.Spec.Project
|
||||
}
|
||||
verifyCommit := false
|
||||
|
||||
appProject := &argoprojiov1alpha1.AppProject{}
|
||||
if err := client.Get(context.TODO(), types.NamespacedName{Name: appSet.Spec.Template.Spec.Project, Namespace: appSet.Namespace}, appProject); err != nil {
|
||||
return nil, fmt.Errorf("error getting project %s: %w", project, err)
|
||||
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
|
||||
// but git generator cannot be called without verifying the commit signature.
|
||||
// In this case, we skip the signature verification.
|
||||
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
|
||||
project := appSet.Spec.Template.Spec.Project
|
||||
appProject := &argoprojiov1alpha1.AppProject{}
|
||||
namespace := g.namespace
|
||||
if namespace == "" {
|
||||
namespace = appSet.Namespace
|
||||
}
|
||||
if err := client.Get(context.TODO(), types.NamespacedName{Name: project, Namespace: namespace}, appProject); err != nil {
|
||||
return nil, fmt.Errorf("error getting project %s: %w", project, err)
|
||||
}
|
||||
// we need to verify the signature on the Git revision if GPG is enabled
|
||||
verifyCommit = appProject.Spec.SignatureKeys != nil && len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
|
||||
}
|
||||
|
||||
// we need to verify the signature on the Git revision if GPG is enabled
|
||||
verifyCommit := appProject.Spec.SignatureKeys != nil && len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
|
||||
|
||||
var err error
|
||||
var res []map[string]interface{}
|
||||
if len(appSetGenerator.Git.Directories) != 0 {
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
|
||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock)
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
@@ -624,7 +624,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
||||
|
||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock)
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
@@ -989,7 +989,7 @@ cluster:
|
||||
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
||||
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock)
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
@@ -1345,7 +1345,7 @@ cluster:
|
||||
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
||||
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock)
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
@@ -1383,3 +1383,114 @@ cluster:
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitGenerator_GenerateParams(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
|
||||
pathParamPrefix string
|
||||
repoApps []string
|
||||
repoPathsError error
|
||||
repoFileContents map[string][]byte
|
||||
values map[string]string
|
||||
expected []map[string]interface{}
|
||||
expectedError error
|
||||
appset argoprojiov1alpha1.ApplicationSet
|
||||
callGetDirectories bool
|
||||
}{
|
||||
{
|
||||
name: "Signature Verification - ignores templated project field",
|
||||
repoApps: []string{
|
||||
"app1",
|
||||
},
|
||||
repoPathsError: nil,
|
||||
appset: argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
||||
Git: &argoprojiov1alpha1.GitGenerator{
|
||||
RepoURL: "RepoURL",
|
||||
Revision: "Revision",
|
||||
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
|
||||
PathParamPrefix: "",
|
||||
Values: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
}},
|
||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{
|
||||
Spec: argoprojiov1alpha1.ApplicationSpec{
|
||||
Project: "{{.project}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
callGetDirectories: true,
|
||||
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Signature Verification - Checks for non-templated project field",
|
||||
repoApps: []string{
|
||||
"app1",
|
||||
},
|
||||
repoPathsError: nil,
|
||||
appset: argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
||||
Git: &argoprojiov1alpha1.GitGenerator{
|
||||
RepoURL: "RepoURL",
|
||||
Revision: "Revision",
|
||||
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
|
||||
PathParamPrefix: "",
|
||||
Values: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
}},
|
||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{
|
||||
Spec: argoprojiov1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
callGetDirectories: false,
|
||||
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
|
||||
expectedError: fmt.Errorf("error getting project project: appprojects.argoproj.io \"project\" not found"),
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
|
||||
if testCase.callGetDirectories {
|
||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.repoApps, testCase.repoPathsError)
|
||||
}
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
require.NoError(t, err)
|
||||
appProject := argoprojiov1alpha1.AppProject{}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
||||
|
||||
got, err := gitGenerator.GenerateParams(&testCase.appset.Spec.Generators[0], &testCase.appset, client)
|
||||
|
||||
if testCase.expectedError != nil {
|
||||
require.EqualError(t, err, testCase.expectedError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.expected, got)
|
||||
}
|
||||
|
||||
argoCDServiceMock.AssertExpectations(t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,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.
|
||||
|
||||
@@ -1089,7 +1089,7 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
|
||||
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
|
||||
"some/path.json": []byte("test: content"),
|
||||
}, nil)
|
||||
gitGenerator := NewGitGenerator(repoServiceMock)
|
||||
gitGenerator := NewGitGenerator(repoServiceMock, "")
|
||||
|
||||
matrixGenerator := NewMatrixGenerator(map[string]Generator{
|
||||
"List": listGeneratorMock,
|
||||
|
||||
100
applicationset/generators/mocks/Generator.go
Normal file
100
applicationset/generators/mocks/Generator.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Code generated by mockery v2.40.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
client "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
time "time"
|
||||
|
||||
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, _a2
|
||||
func (_m *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, _a2 client.Client) ([]map[string]interface{}, error) {
|
||||
ret := _m.Called(appSetGenerator, applicationSetInfo, _a2)
|
||||
|
||||
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, client.Client) ([]map[string]interface{}, error)); ok {
|
||||
return rf(appSetGenerator, applicationSetInfo, _a2)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]interface{}); ok {
|
||||
r0 = rf(appSetGenerator, applicationSetInfo, _a2)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]map[string]interface{})
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
|
||||
r1 = rf(appSetGenerator, applicationSetInfo, _a2)
|
||||
} 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
|
||||
}
|
||||
@@ -177,7 +177,7 @@ func NewCommand() *cobra.Command {
|
||||
terminalGenerators := map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
"Clusters": generators.NewClusterGenerator(mgr.GetClient(), ctx, k8sClient, namespace),
|
||||
"Git": generators.NewGitGenerator(argoCDService),
|
||||
"Git": generators.NewGitGenerator(argoCDService, namespace),
|
||||
"SCMProvider": generators.NewSCMProviderGenerator(mgr.GetClient(), scmAuth, scmRootCAPath, allowedScmProviders, enableScmProviders),
|
||||
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
|
||||
"PullRequest": generators.NewPullRequestGenerator(mgr.GetClient(), scmAuth, scmRootCAPath, allowedScmProviders, enableScmProviders),
|
||||
@@ -234,7 +234,6 @@ func NewCommand() *cobra.Command {
|
||||
SCMRootCAPath: scmRootCAPath,
|
||||
GlobalPreservedAnnotations: globalPreservedAnnotations,
|
||||
GlobalPreservedLabels: globalPreservedLabels,
|
||||
Cache: mgr.GetCache(),
|
||||
}).SetupWithManager(mgr, enableProgressiveSyncs, maxConcurrentReconciliations); err != nil {
|
||||
log.Error(err, "unable to create controller", "controller", "ApplicationSet")
|
||||
os.Exit(1)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -776,8 +776,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")
|
||||
|
||||
@@ -1767,6 +1767,22 @@ func (ctrl *ApplicationController) normalizeApplication(orig, app *appv1.Applica
|
||||
}
|
||||
}
|
||||
|
||||
func createMergePatch(orig, new interface{}) ([]byte, bool, error) {
|
||||
origBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
newBytes, err := json.Marshal(new)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
patch, err := jsonpatch.CreateMergePatch(origBytes, newBytes)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return patch, string(patch) != "{}", nil
|
||||
}
|
||||
|
||||
// persistAppStatus persists updates to application status. If no changes were made, it is a no-op
|
||||
func (ctrl *ApplicationController) persistAppStatus(orig *appv1.Application, newStatus *appv1.ApplicationStatus) (patchMs time.Duration) {
|
||||
logCtx := getAppLog(orig)
|
||||
@@ -1786,9 +1802,9 @@ func (ctrl *ApplicationController) persistAppStatus(orig *appv1.Application, new
|
||||
}
|
||||
delete(newAnnotations, appv1.AnnotationKeyRefresh)
|
||||
}
|
||||
patch, modified, err := diff.CreateTwoWayMergePatch(
|
||||
patch, modified, err := createMergePatch(
|
||||
&appv1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: orig.GetAnnotations()}, Status: orig.Status},
|
||||
&appv1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: newAnnotations}, Status: *newStatus}, appv1.Application{})
|
||||
&appv1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: newAnnotations}, Status: *newStatus})
|
||||
if err != nil {
|
||||
logCtx.Errorf("Error constructing app status patch: %v", err)
|
||||
return
|
||||
@@ -1995,7 +2011,7 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application) (bool,
|
||||
// isAppNamespaceAllowed returns whether the application is allowed in the
|
||||
// namespace it's residing in.
|
||||
func (ctrl *ApplicationController) isAppNamespaceAllowed(app *appv1.Application) bool {
|
||||
return app.Namespace == ctrl.namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, false)
|
||||
return app.Namespace == ctrl.namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, glob.REGEXP)
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
|
||||
|
||||
@@ -374,8 +374,8 @@ data:
|
||||
|
||||
var fakePostDeleteHook = `
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"apiVersion": "batch/v1",
|
||||
"kind": "Job",
|
||||
"metadata": {
|
||||
"name": "post-delete-hook",
|
||||
"namespace": "default",
|
||||
@@ -388,22 +388,93 @@ var fakePostDeleteHook = `
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "post-delete-hook",
|
||||
"image": "busybox",
|
||||
"restartPolicy": "Never",
|
||||
"command": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"sleep 5 && echo hello from the post-delete-hook pod"
|
||||
]
|
||||
"template": {
|
||||
"metadata": {
|
||||
"name": "post-delete-hook"
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "post-delete-hook",
|
||||
"image": "busybox",
|
||||
"command": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"sleep 5 && echo hello from the post-delete-hook job"
|
||||
]
|
||||
}
|
||||
],
|
||||
"restartPolicy": "Never"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var fakeServiceAccount = `
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": {
|
||||
"name": "hook-serviceaccount",
|
||||
"namespace": "default",
|
||||
"annotations": {
|
||||
"argocd.argoproj.io/hook": "PostDelete",
|
||||
"argocd.argoproj.io/hook-delete-policy": "BeforeHookCreation,HookSucceeded"
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var fakeRole = `
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "Role",
|
||||
"metadata": {
|
||||
"name": "hook-role",
|
||||
"namespace": "default",
|
||||
"annotations": {
|
||||
"argocd.argoproj.io/hook": "PostDelete",
|
||||
"argocd.argoproj.io/hook-delete-policy": "BeforeHookCreation,HookSucceeded"
|
||||
}
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [""],
|
||||
"resources": ["secrets"],
|
||||
"verbs": ["get", "delete", "list"]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
var fakeRoleBinding = `
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "RoleBinding",
|
||||
"metadata": {
|
||||
"name": "hook-rolebinding",
|
||||
"namespace": "default",
|
||||
"annotations": {
|
||||
"argocd.argoproj.io/hook": "PostDelete",
|
||||
"argocd.argoproj.io/hook-delete-policy": "BeforeHookCreation,HookSucceeded"
|
||||
}
|
||||
},
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "Role",
|
||||
"name": "hook-role"
|
||||
},
|
||||
"subjects": [
|
||||
{
|
||||
"kind": "ServiceAccount",
|
||||
"name": "hook-serviceaccount",
|
||||
"namespace": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
func newFakeApp() *v1alpha1.Application {
|
||||
return createFakeApp(fakeApp)
|
||||
}
|
||||
@@ -439,12 +510,39 @@ func newFakeCM() map[string]interface{} {
|
||||
}
|
||||
|
||||
func newFakePostDeleteHook() map[string]interface{} {
|
||||
var cm map[string]interface{}
|
||||
err := yaml.Unmarshal([]byte(fakePostDeleteHook), &cm)
|
||||
var hook map[string]interface{}
|
||||
err := yaml.Unmarshal([]byte(fakePostDeleteHook), &hook)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cm
|
||||
return hook
|
||||
}
|
||||
|
||||
func newFakeRoleBinding() map[string]interface{} {
|
||||
var roleBinding map[string]interface{}
|
||||
err := yaml.Unmarshal([]byte(fakeRoleBinding), &roleBinding)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return roleBinding
|
||||
}
|
||||
|
||||
func newFakeRole() map[string]interface{} {
|
||||
var role map[string]interface{}
|
||||
err := yaml.Unmarshal([]byte(fakeRole), &role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return role
|
||||
}
|
||||
|
||||
func newFakeServiceAccount() map[string]interface{} {
|
||||
var serviceAccount map[string]interface{}
|
||||
err := yaml.Unmarshal([]byte(fakeServiceAccount), &serviceAccount)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return serviceAccount
|
||||
}
|
||||
|
||||
func TestAutoSync(t *testing.T) {
|
||||
@@ -721,7 +819,7 @@ func TestFinalizeAppDeletion(t *testing.T) {
|
||||
|
||||
// Ensure any stray resources irregularly labeled with instance label of app are not deleted upon deleting,
|
||||
// when app project restriction is in place
|
||||
t.Run("ProjectRestrictionEnforced", func(*testing.T) {
|
||||
t.Run("ProjectRestrictionEnforced", func(t *testing.T) {
|
||||
restrictedProj := v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "restricted",
|
||||
@@ -882,7 +980,13 @@ func TestFinalizeAppDeletion(t *testing.T) {
|
||||
app.SetPostDeleteFinalizer()
|
||||
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
|
||||
liveHook := &unstructured.Unstructured{Object: newFakePostDeleteHook()}
|
||||
require.NoError(t, unstructured.SetNestedField(liveHook.Object, "Succeeded", "status", "phase"))
|
||||
conditions := []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Complete",
|
||||
"status": "True",
|
||||
},
|
||||
}
|
||||
require.NoError(t, unstructured.SetNestedField(liveHook.Object, conditions, "status", "conditions"))
|
||||
ctrl := newFakeController(&fakeData{
|
||||
manifestResponses: []*apiclient.ManifestResponse{{
|
||||
Manifests: []string{fakePostDeleteHook},
|
||||
@@ -916,15 +1020,27 @@ func TestFinalizeAppDeletion(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
app.SetPostDeleteFinalizer("cleanup")
|
||||
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
|
||||
liveRoleBinding := &unstructured.Unstructured{Object: newFakeRoleBinding()}
|
||||
liveRole := &unstructured.Unstructured{Object: newFakeRole()}
|
||||
liveServiceAccount := &unstructured.Unstructured{Object: newFakeServiceAccount()}
|
||||
liveHook := &unstructured.Unstructured{Object: newFakePostDeleteHook()}
|
||||
require.NoError(t, unstructured.SetNestedField(liveHook.Object, "Succeeded", "status", "phase"))
|
||||
conditions := []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Complete",
|
||||
"status": "True",
|
||||
},
|
||||
}
|
||||
require.NoError(t, unstructured.SetNestedField(liveHook.Object, conditions, "status", "conditions"))
|
||||
ctrl := newFakeController(&fakeData{
|
||||
manifestResponses: []*apiclient.ManifestResponse{{
|
||||
Manifests: []string{fakePostDeleteHook},
|
||||
Manifests: []string{fakeRoleBinding, fakeRole, fakeServiceAccount, fakePostDeleteHook},
|
||||
}},
|
||||
apps: []runtime.Object{app, &defaultProj},
|
||||
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
|
||||
kube.GetResourceKey(liveHook): liveHook,
|
||||
kube.GetResourceKey(liveRoleBinding): liveRoleBinding,
|
||||
kube.GetResourceKey(liveRole): liveRole,
|
||||
kube.GetResourceKey(liveServiceAccount): liveServiceAccount,
|
||||
kube.GetResourceKey(liveHook): liveHook,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
@@ -943,9 +1059,14 @@ func TestFinalizeAppDeletion(t *testing.T) {
|
||||
return []*v1alpha1.Cluster{}, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// post-delete hook is deleted
|
||||
require.Len(t, ctrl.kubectl.(*MockKubectl).DeletedResources, 1)
|
||||
require.Equal(t, "post-delete-hook", ctrl.kubectl.(*MockKubectl).DeletedResources[0].Name)
|
||||
// post-delete hooks are deleted
|
||||
require.Len(t, ctrl.kubectl.(*MockKubectl).DeletedResources, 4)
|
||||
deletedResources := []string{}
|
||||
for _, res := range ctrl.kubectl.(*MockKubectl).DeletedResources {
|
||||
deletedResources = append(deletedResources, res.Name)
|
||||
}
|
||||
expectedNames := []string{"hook-rolebinding", "hook-role", "hook-serviceaccount", "post-delete-hook"}
|
||||
require.ElementsMatch(t, expectedNames, deletedResources, "Deleted resources should match expected names")
|
||||
// finalizer is not removed
|
||||
assert.False(t, patched)
|
||||
})
|
||||
@@ -992,7 +1113,7 @@ func TestNormalizeApplication(t *testing.T) {
|
||||
normalized := false
|
||||
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if patchAction, ok := action.(kubetesting.PatchAction); ok {
|
||||
if string(patchAction.GetPatch()) == `{"spec":{"project":"default"},"status":{"sync":{"comparedTo":{"destination":{},"source":{"repoURL":""}}}}}` {
|
||||
if string(patchAction.GetPatch()) == `{"spec":{"project":"default"}}` {
|
||||
normalized = true
|
||||
}
|
||||
}
|
||||
@@ -1949,3 +2070,65 @@ func TestAddControllerNamespace(t *testing.T) {
|
||||
assert.Equal(t, test.FakeArgoCDNamespace, updatedApp.Status.ControllerNamespace)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHelmValuesObjectHasReplaceStrategy(t *testing.T) {
|
||||
app := v1alpha1.Application{
|
||||
Status: v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{ComparedTo: v1alpha1.ComparedTo{
|
||||
Source: v1alpha1.ApplicationSource{
|
||||
Helm: &v1alpha1.ApplicationSourceHelm{
|
||||
ValuesObject: &runtime.RawExtension{
|
||||
Object: &unstructured.Unstructured{Object: map[string]interface{}{"key": []string{"value"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}},
|
||||
}
|
||||
|
||||
appModified := v1alpha1.Application{
|
||||
Status: v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{ComparedTo: v1alpha1.ComparedTo{
|
||||
Source: v1alpha1.ApplicationSource{
|
||||
Helm: &v1alpha1.ApplicationSourceHelm{
|
||||
ValuesObject: &runtime.RawExtension{
|
||||
Object: &unstructured.Unstructured{Object: map[string]interface{}{"key": []string{"value-modified1"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}},
|
||||
}
|
||||
|
||||
patch, _, err := createMergePatch(
|
||||
app,
|
||||
appModified)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, `{"status":{"sync":{"comparedTo":{"source":{"helm":{"valuesObject":{"key":["value-modified1"]}}}}}}}`, string(patch))
|
||||
}
|
||||
|
||||
func TestAppStatusIsReplaced(t *testing.T) {
|
||||
original := &v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{
|
||||
ComparedTo: v1alpha1.ComparedTo{
|
||||
Destination: v1alpha1.ApplicationDestination{
|
||||
Server: "https://mycluster",
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
updated := &v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{
|
||||
ComparedTo: v1alpha1.ComparedTo{
|
||||
Destination: v1alpha1.ApplicationDestination{
|
||||
Name: "mycluster",
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
patchData, ok, err := createMergePatch(original, updated)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
patchObj := map[string]interface{}{}
|
||||
require.NoError(t, json.Unmarshal(patchData, &patchObj))
|
||||
|
||||
val, has, err := unstructured.NestedFieldNoCopy(patchObj, "sync", "comparedTo", "destination", "server")
|
||||
require.NoError(t, err)
|
||||
require.True(t, has)
|
||||
require.Nil(t, val)
|
||||
}
|
||||
|
||||
@@ -98,6 +98,18 @@ func (ctrl *ApplicationController) executePostDeleteHooks(app *v1alpha1.Applicat
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if hookHealth == nil {
|
||||
logCtx.WithFields(log.Fields{
|
||||
"group": obj.GroupVersionKind().Group,
|
||||
"version": obj.GroupVersionKind().Version,
|
||||
"kind": obj.GetKind(),
|
||||
"name": obj.GetName(),
|
||||
"namespace": obj.GetNamespace(),
|
||||
}).Info("No health check defined for resource, considering it healthy")
|
||||
hookHealth = &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
}
|
||||
}
|
||||
if hookHealth.Status == health.HealthStatusProgressing {
|
||||
progressingHooksCnt++
|
||||
}
|
||||
@@ -128,6 +140,11 @@ func (ctrl *ApplicationController) cleanupPostDeleteHooks(liveObjs map[kube.Reso
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if hookHealth == nil {
|
||||
hookHealth = &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
}
|
||||
}
|
||||
if health.IsWorse(aggregatedHealth, hookHealth.Status) {
|
||||
aggregatedHealth = hookHealth.Status
|
||||
}
|
||||
|
||||
@@ -19,6 +19,14 @@ const observerCallback = function(mutationsList, observer) {
|
||||
const observer = new MutationObserver(observerCallback);
|
||||
observer.observe(targetNode, observerOptions);
|
||||
|
||||
function getCurrentVersion() {
|
||||
const currentVersion = window.location.href.match(/\/en\/(release-(?:v\d+|[\d\.]+|\w+)|latest|stable)\//);
|
||||
if (currentVersion && currentVersion.length > 1) {
|
||||
return currentVersion[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function initializeVersionDropdown() {
|
||||
const callbackName = 'callback_' + new Date().getTime();
|
||||
window[callbackName] = function(response) {
|
||||
@@ -42,18 +50,18 @@ function initializeVersionDropdown() {
|
||||
document.getElementsByTagName('head')[0].appendChild(CSSLink);
|
||||
|
||||
var script = document.createElement('script');
|
||||
const currentVersion = getCurrentVersion();
|
||||
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;
|
||||
'callback=' + callbackName + '&project=argo-cd&page=&theme=mkdocs&format=jsonp&docroot=docs&source_suffix=.md&version=' + (currentVersion || 'latest');
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
}
|
||||
|
||||
// VERSION WARNINGS
|
||||
window.addEventListener("DOMContentLoaded", function() {
|
||||
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 (currentVersion && currentVersion.length > 1) {
|
||||
currentVersion = currentVersion[1];
|
||||
const currentVersion = getCurrentVersion();
|
||||
if (currentVersion) {
|
||||
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;
|
||||
@@ -72,4 +80,4 @@ window.addEventListener("DOMContentLoaded", function() {
|
||||
"@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:" + (bannerHeight + headerHeight) + "px !important; }}";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,8 +42,11 @@ In order for an application to be managed and reconciled outside the Argo CD's c
|
||||
|
||||
In order to enable this feature, the Argo CD administrator must reconfigure the `argocd-server` and `argocd-application-controller` workloads to add the `--application-namespaces` parameter to the container's startup command.
|
||||
|
||||
The `--application-namespaces` parameter takes a comma-separated list of namespaces where `Applications` are to be allowed in. Each entry of the list supports shell-style wildcards such as `*`, so for example the entry `app-team-*` would match `app-team-one` and `app-team-two`. To enable all namespaces on the cluster where Argo CD is running on, you can just specify `*`, i.e. `--application-namespaces=*`.
|
||||
The `--application-namespaces` parameter takes a comma-separated list of namespaces where `Applications` are to be allowed in. Each entry of the list supports:
|
||||
|
||||
- shell-style wildcards such as `*`, so for example the entry `app-team-*` would match `app-team-one` and `app-team-two`. To enable all namespaces on the cluster where Argo CD is running on, you can just specify `*`, i.e. `--application-namespaces=*`.
|
||||
- regex, requires wrapping the string in ```/```, example to allow all namespaces except a particular one: ```/^((?!not-allowed).)*$/```.
|
||||
|
||||
The startup parameters for both, the `argocd-server` and the `argocd-application-controller` can also be conveniently set up and kept in sync by specifying the `application.namespaces` settings in the `argocd-cmd-params-cm` ConfigMap _instead_ of changing the manifests for the respective workloads. For example:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -7,6 +7,8 @@ The Git generator contains two subtypes: the Git directory generator, and Git fi
|
||||
If the `project` field in your ApplicationSet is templated, developers may be able to create Applications under Projects with excessive permissions.
|
||||
For ApplicationSets with a templated `project` field, [the source of truth _must_ be controlled by admins](./Security.md#templated-project-field)
|
||||
- in the case of git generators, PRs must require admin approval.
|
||||
- Git generator does not support Signature Verification For ApplicationSets with a templated `project` field.
|
||||
|
||||
|
||||
## Git Generator: Directories
|
||||
|
||||
@@ -326,7 +328,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 +362,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'
|
||||
|
||||
@@ -100,6 +100,17 @@ possible with Go text templates:
|
||||
- name: throw-away
|
||||
value: "{{end}}"
|
||||
|
||||
- Signature verification is not supported for the templated `project` field when using the Git generator.
|
||||
|
||||
::yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ApplicationSet
|
||||
spec:
|
||||
goTemplate: true
|
||||
template:
|
||||
spec:
|
||||
project: {{.project}}
|
||||
|
||||
|
||||
## Migration guide
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ Note:
|
||||
|
||||
- Referenced clusters must already be defined in Argo CD, for the ApplicationSet controller to use them
|
||||
- Only **one** of `name` or `server` may be specified: if both are specified, an error is returned.
|
||||
- Signature Verification does not work with the templated `project` field when using git generator.
|
||||
|
||||
The `metadata` field of template may also be used to set an Application `name`, or to add labels or annotations to the Application.
|
||||
|
||||
|
||||
@@ -420,3 +420,5 @@ 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
|
||||
@@ -1,6 +1,5 @@
|
||||
| Argo CD version | Kubernetes versions |
|
||||
|-----------------|---------------------|
|
||||
| 2.7 | v1.26, v1.25, v1.24, v1.23 |
|
||||
| 2.6 | v1.24, v1.23, v1.22 |
|
||||
| 2.5 | v1.24, v1.23, v1.22 |
|
||||
|
||||
| 2.12 | |
|
||||
| 2.11 | v1.29, v1.28, v1.27, v1.26, v1.25 |
|
||||
| 2.10 | v1.28, v1.27, v1.26, v1.25 |
|
||||
|
||||
@@ -19,6 +19,8 @@ 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
|
||||
|
||||

|
||||
|
||||
@@ -29,6 +29,11 @@ not possible using Helm repositories.
|
||||
trust models, and it is not necessary (nor possible) to sign the public keys
|
||||
you are going to import into ArgoCD.
|
||||
|
||||
|
||||
!!!note Limitations
|
||||
Signature verification is not supported for the templated `project` field when
|
||||
using the Git generator.
|
||||
|
||||
## Signature verification targets
|
||||
|
||||
If signature verification is enforced, ArgoCD will verify the signature using
|
||||
|
||||
7
go.mod
7
go.mod
@@ -11,7 +11,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.20240615185936-83ce6ca8cedc
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20240714153147-adb68bcaab73
|
||||
github.com/argoproj/notifications-engine v0.4.1-0.20240606074338-0802cd427621
|
||||
github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1
|
||||
github.com/aws/aws-sdk-go v1.50.8
|
||||
@@ -52,14 +52,14 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||
github.com/imdario/mergo v0.3.16
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
github.com/itchyny/gojq v0.12.13
|
||||
github.com/jeremywohl/flatten v1.0.1
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/ktrysmt/go-bitbucket v0.9.67
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-zglob v0.0.4
|
||||
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
|
||||
@@ -187,6 +187,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.2
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.8.0 // indirect
|
||||
|
||||
22
go.sum
22
go.sum
@@ -695,8 +695,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.20240615185936-83ce6ca8cedc h1:J7LJp2Gh9A9/eQN7Lg74JW+YOVO5NEjq5/cudGAiOwk=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20240615185936-83ce6ca8cedc/go.mod h1:ByLmH5B1Gs361tgI5x5f8oSFuBEXDYENYpG3zFDWtHU=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20240714153147-adb68bcaab73 h1:7kyTgFsPjvb6noafslp2pr7fBCS9s8OJ759LdLzrOro=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20240714153147-adb68bcaab73/go.mod h1:xMIbuLg9Qj2e0egTy+8NcukbhRaVmWwK9vm3aAQZoi4=
|
||||
github.com/argoproj/notifications-engine v0.4.1-0.20240606074338-0802cd427621 h1:Yg1nt+D2uDK1SL2jSlfukA4yc7db184TTN7iWy3voRE=
|
||||
github.com/argoproj/notifications-engine v0.4.1-0.20240606074338-0802cd427621/go.mod h1:N0A4sEws2soZjEpY4hgZpQS8mRIEw6otzwfkgc3g9uQ=
|
||||
github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 h1:qsHwwOJ21K2Ao0xPju1sNuqphyMnMYkyB3ZLoLtxWpo=
|
||||
@@ -843,6 +843,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.11.2 h1:/u628IuisSTwri5/UKloiIsH8+qF2Pu7xEQX+yIKg68=
|
||||
github.com/dlclark/regexp2 v1.11.2/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
|
||||
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
@@ -892,6 +894,8 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+ne
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
@@ -1242,14 +1246,14 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.1/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
@@ -1383,13 +1387,15 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsI
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
|
||||
@@ -27,6 +27,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
@@ -62,4 +64,4 @@ rules:
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- watch
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: latest
|
||||
newTag: v2.12.3
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -35,6 +35,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
|
||||
@@ -20822,6 +20822,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
@@ -21268,7 +21270,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -21386,7 +21388,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -21639,7 +21641,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -21691,7 +21693,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -21963,7 +21965,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: latest
|
||||
newTag: v2.12.3
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: latest
|
||||
newTag: v2.12.3
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -20860,6 +20860,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
@@ -21108,6 +21110,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
@@ -22609,7 +22613,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -22732,7 +22736,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -22814,7 +22818,7 @@ spec:
|
||||
key: notificationscontroller.selfservice.enabled
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -22933,7 +22937,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -23214,7 +23218,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -23266,7 +23270,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -23590,7 +23594,7 @@ spec:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -23889,7 +23893,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -149,6 +149,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
@@ -1686,7 +1688,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1809,7 +1811,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1891,7 +1893,7 @@ spec:
|
||||
key: notificationscontroller.selfservice.enabled
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2010,7 +2012,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -2291,7 +2293,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2343,7 +2345,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2667,7 +2669,7 @@ spec:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2966,7 +2968,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -20849,6 +20849,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
@@ -21075,6 +21077,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
@@ -21726,7 +21730,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -21849,7 +21853,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -21931,7 +21935,7 @@ spec:
|
||||
key: notificationscontroller.selfservice.enabled
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -22031,7 +22035,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -22284,7 +22288,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -22336,7 +22340,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -22658,7 +22662,7 @@ spec:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -22957,7 +22961,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -138,6 +138,8 @@ rules:
|
||||
- appprojects
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
@@ -803,7 +805,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -926,7 +928,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1008,7 +1010,7 @@ spec:
|
||||
key: notificationscontroller.selfservice.enabled
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1108,7 +1110,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -1361,7 +1363,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1413,7 +1415,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1735,7 +1737,7 @@ spec:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2034,7 +2036,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.12.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -122,7 +122,7 @@ func NewController(
|
||||
|
||||
// Check if app is not in the namespace where the controller is in, and also app is not in one of the applicationNamespaces
|
||||
func checkAppNotInAdditionalNamespaces(app *unstructured.Unstructured, namespace string, applicationNamespaces []string) bool {
|
||||
return namespace != app.GetNamespace() && !glob.MatchStringInList(applicationNamespaces, app.GetNamespace(), false)
|
||||
return namespace != app.GetNamespace() && !glob.MatchStringInList(applicationNamespaces, app.GetNamespace(), glob.REGEXP)
|
||||
}
|
||||
|
||||
func (c *notificationController) alterDestinations(obj v1.Object, destinations services.Destinations, cfg api.Config) services.Destinations {
|
||||
@@ -151,7 +151,7 @@ func newInformer(resClient dynamic.ResourceInterface, controllerNamespace string
|
||||
}
|
||||
newItems := []unstructured.Unstructured{}
|
||||
for _, res := range appList.Items {
|
||||
if controllerNamespace == res.GetNamespace() || glob.MatchStringInList(applicationNamespaces, res.GetNamespace(), false) {
|
||||
if controllerNamespace == res.GetNamespace() || glob.MatchStringInList(applicationNamespaces, res.GetNamespace(), glob.REGEXP) {
|
||||
newItems = append(newItems, res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,5 +562,5 @@ func (p AppProject) IsAppNamespacePermitted(app *Application, controllerNs strin
|
||||
return true
|
||||
}
|
||||
|
||||
return glob.MatchStringInList(p.Spec.SourceNamespaces, app.Namespace, false)
|
||||
return glob.MatchStringInList(p.Spec.SourceNamespaces, app.Namespace, glob.REGEXP)
|
||||
}
|
||||
|
||||
@@ -2241,7 +2241,6 @@ message SyncStatus {
|
||||
optional string status = 1;
|
||||
|
||||
// ComparedTo contains information about what has been compared
|
||||
// +patchStrategy=replace
|
||||
optional ComparedTo comparedTo = 2;
|
||||
|
||||
// Revision contains information about the revision the comparison has been performed to
|
||||
|
||||
@@ -7758,11 +7758,6 @@ func schema_pkg_apis_application_v1alpha1_SyncStatus(ref common.ReferenceCallbac
|
||||
},
|
||||
},
|
||||
"comparedTo": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-patch-strategy": "replace",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ComparedTo contains information about what has been compared",
|
||||
Default: map[string]interface{}{},
|
||||
|
||||
@@ -3,6 +3,7 @@ package v1alpha1
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/cert"
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
@@ -227,21 +228,22 @@ func getCAPath(repoURL string) string {
|
||||
}
|
||||
|
||||
hostname := ""
|
||||
// url.Parse() will happily parse most things thrown at it. When the URL
|
||||
// is either https or oci, we use the parsed hostname to retrieve the cert,
|
||||
// otherwise we'll use the parsed path (OCI repos are often specified as
|
||||
// hostname, without protocol).
|
||||
parsedURL, err := url.Parse(repoURL)
|
||||
var parsedURL *url.URL
|
||||
var err error
|
||||
// Without schema in url, url.Parse() treats the url as differently
|
||||
// and may incorrectly parses the hostname if url contains a path or port.
|
||||
// To ensure proper parsing, prepend a dummy schema.
|
||||
if !strings.Contains(repoURL, "://") {
|
||||
parsedURL, err = url.Parse("protocol://" + repoURL)
|
||||
} else {
|
||||
parsedURL, err = url.Parse(repoURL)
|
||||
}
|
||||
if err != nil {
|
||||
log.Warnf("Could not parse repo URL '%s': %v", repoURL, err)
|
||||
return ""
|
||||
}
|
||||
if parsedURL.Scheme == "https" || parsedURL.Scheme == "oci" {
|
||||
hostname = parsedURL.Host
|
||||
} else if parsedURL.Scheme == "" {
|
||||
hostname = parsedURL.Path
|
||||
}
|
||||
|
||||
hostname = parsedURL.Hostname()
|
||||
if hostname == "" {
|
||||
log.Warnf("Could not get hostname for repository '%s'", repoURL)
|
||||
return ""
|
||||
|
||||
@@ -1519,8 +1519,7 @@ type SyncStatus struct {
|
||||
// Status is the sync state of the comparison
|
||||
Status SyncStatusCode `json:"status" protobuf:"bytes,1,opt,name=status,casttype=SyncStatusCode"`
|
||||
// ComparedTo contains information about what has been compared
|
||||
// +patchStrategy=replace
|
||||
ComparedTo ComparedTo `json:"comparedTo,omitempty" protobuf:"bytes,2,opt,name=comparedTo" patchStrategy:"replace"`
|
||||
ComparedTo ComparedTo `json:"comparedTo,omitempty" protobuf:"bytes,2,opt,name=comparedTo"`
|
||||
// Revision contains information about the revision the comparison has been performed to
|
||||
Revision string `json:"revision,omitempty" protobuf:"bytes,3,opt,name=revision"`
|
||||
// Revisions contains information about the revisions of multiple sources the comparison has been performed to
|
||||
@@ -2073,6 +2072,8 @@ var validActions = map[string]bool{
|
||||
|
||||
var validActionPatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile("action/.*"),
|
||||
regexp.MustCompile("update/.*"),
|
||||
regexp.MustCompile("delete/.*"),
|
||||
}
|
||||
|
||||
func isValidAction(action string) bool {
|
||||
|
||||
@@ -11,10 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
argocdcommon "github.com/argoproj/argo-cd/v2/common"
|
||||
@@ -827,8 +824,12 @@ func TestAppProject_ValidPolicyRules(t *testing.T) {
|
||||
"p, proj:my-proj:my-role, applications, *, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, create, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, update, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, update/*, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, update/*/Pod/*, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, sync, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, delete, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, delete/*, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, delete/*/Pod/*, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, action/*, my-proj/foo, allow",
|
||||
"p, proj:my-proj:my-role, applications, action/apps/Deployment/restart, my-proj/foo, allow",
|
||||
}
|
||||
@@ -3089,7 +3090,7 @@ func TestOrphanedResourcesMonitorSettings_IsWarn(t *testing.T) {
|
||||
assert.True(t, settings.IsWarn())
|
||||
}
|
||||
|
||||
func Test_isValidPolicy(t *testing.T) {
|
||||
func Test_isValidPolicyObject(t *testing.T) {
|
||||
policyTests := []struct {
|
||||
name string
|
||||
policy string
|
||||
@@ -3239,18 +3240,25 @@ func TestGetCAPath(t *testing.T) {
|
||||
"https://foo.example.com",
|
||||
"oci://foo.example.com",
|
||||
"foo.example.com",
|
||||
"foo.example.com/charts",
|
||||
"https://foo.example.com:5000",
|
||||
"foo.example.com:5000",
|
||||
"foo.example.com:5000/charts",
|
||||
"ssh://foo.example.com",
|
||||
}
|
||||
invalidpath := []string{
|
||||
"https://bar.example.com",
|
||||
"oci://bar.example.com",
|
||||
"bar.example.com",
|
||||
"ssh://foo.example.com",
|
||||
"git@example.com:organization/reponame.git",
|
||||
"ssh://bar.example.com",
|
||||
"git@foo.example.com:organization/reponame.git",
|
||||
"ssh://git@foo.example.com:organization/reponame.git",
|
||||
"/some/invalid/thing",
|
||||
"../another/invalid/thing",
|
||||
"./also/invalid",
|
||||
"$invalid/as/well",
|
||||
"..",
|
||||
"://invalid",
|
||||
}
|
||||
|
||||
for _, str := range validcert {
|
||||
@@ -3734,35 +3742,3 @@ func TestApplicationSpec_GetSourcePtrByIndex(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelmValuesObjectHasReplaceStrategy(t *testing.T) {
|
||||
app := Application{
|
||||
Status: ApplicationStatus{Sync: SyncStatus{ComparedTo: ComparedTo{
|
||||
Source: ApplicationSource{
|
||||
Helm: &ApplicationSourceHelm{
|
||||
ValuesObject: &runtime.RawExtension{
|
||||
Object: &unstructured.Unstructured{Object: map[string]interface{}{"key": []string{"value"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}},
|
||||
}
|
||||
|
||||
appModified := Application{
|
||||
Status: ApplicationStatus{Sync: SyncStatus{ComparedTo: ComparedTo{
|
||||
Source: ApplicationSource{
|
||||
Helm: &ApplicationSourceHelm{
|
||||
ValuesObject: &runtime.RawExtension{
|
||||
Object: &unstructured.Unstructured{Object: map[string]interface{}{"key": []string{"value-modified1"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}},
|
||||
}
|
||||
|
||||
patch, _, err := diff.CreateTwoWayMergePatch(
|
||||
app,
|
||||
appModified, Application{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, `{"status":{"sync":{"comparedTo":{"destination":{},"source":{"helm":{"valuesObject":{"key":["value-modified1"]}},"repoURL":""}}}}}`, string(patch))
|
||||
}
|
||||
|
||||
@@ -2769,7 +2769,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)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
local health_status = {
|
||||
status = "Progressing",
|
||||
message = "Provisioning..."
|
||||
}
|
||||
|
||||
-- If .status is nil or doesn't have conditions, then the control plane is not ready
|
||||
if obj.status == nil or obj.status.conditions == nil then
|
||||
return health_status
|
||||
end
|
||||
|
||||
-- Accumulator for the error messages (could be multiple conditions in error state)
|
||||
err_msg = ""
|
||||
|
||||
-- Iterate over the conditions to determine the health status
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
-- Check if the Ready condition is True, then the control plane is ready
|
||||
if condition.type == "Ready" and condition.status == "True" then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Control plane is ready"
|
||||
return health_status
|
||||
end
|
||||
|
||||
-- If we have a condition that is False and has an Error severity, then the control plane is in a degraded state
|
||||
if condition.status == "False" and condition.severity == "Error" then
|
||||
health_status.status = "Degraded"
|
||||
err_msg = err_msg .. condition.message .. " "
|
||||
end
|
||||
end
|
||||
|
||||
-- If we have any error conditions, then the control plane is in a degraded state
|
||||
if health_status.status == "Degraded" then
|
||||
health_status.message = err_msg
|
||||
return health_status
|
||||
end
|
||||
|
||||
-- If .status.ready is False, then the control plane is not ready
|
||||
if obj.status.ready == false then
|
||||
health_status.status = "Progressing"
|
||||
health_status.message = "Control plane is not ready (.status.ready is false)"
|
||||
return health_status
|
||||
end
|
||||
|
||||
-- If we reach this point, then the control plane is not ready and we don't have any error conditions
|
||||
health_status.status = "Progressing"
|
||||
health_status.message = "Control plane is not ready"
|
||||
|
||||
return health_status
|
||||
@@ -0,0 +1,17 @@
|
||||
tests:
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: 'Control plane is ready'
|
||||
inputPath: testdata/healthy.yaml
|
||||
- healthStatus:
|
||||
status: Progressing
|
||||
message: 'Control plane is not ready (.status.ready is false)'
|
||||
inputPath: testdata/progressing_ready_false.yaml
|
||||
- healthStatus:
|
||||
status: Progressing
|
||||
message: 'Control plane is not ready'
|
||||
inputPath: testdata/progressing_ready_true.yaml
|
||||
- healthStatus:
|
||||
status: Degraded
|
||||
message: '7 of 10 completed failed reconciling OIDC provider for cluster: failed to create OIDC provider: error creating provider: LimitExceeded: Cannot exceed quota for OpenIdConnectProvidersPerAccount: 100 '
|
||||
inputPath: testdata/degraded.yaml
|
||||
@@ -0,0 +1,50 @@
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
|
||||
kind: AWSManagedControlPlane
|
||||
metadata:
|
||||
name: test
|
||||
namespace: ns-test
|
||||
ownerReferences:
|
||||
- apiVersion: cluster.x-k8s.io/v1beta1
|
||||
blockOwnerDeletion: true
|
||||
controller: true
|
||||
kind: Cluster
|
||||
name: test
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2024-07-30T11:10:03Z"
|
||||
status: "False"
|
||||
severity: Error
|
||||
message: "7 of 10 completed"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2024-07-26T14:35:48Z"
|
||||
status: "True"
|
||||
type: ClusterSecurityGroupsReady
|
||||
- lastTransitionTime: "2024-07-30T02:20:06Z"
|
||||
status: "True"
|
||||
type: EKSAddonsConfigured
|
||||
- lastTransitionTime: "2024-07-26T14:43:57Z"
|
||||
reason: created
|
||||
severity: Info
|
||||
status: "False"
|
||||
type: EKSControlPlaneCreating
|
||||
- lastTransitionTime: "2024-07-25T09:22:46Z"
|
||||
message: "failed reconciling OIDC provider for cluster: failed to create OIDC provider: error creating provider: LimitExceeded: Cannot exceed quota for OpenIdConnectProvidersPerAccount: 100"
|
||||
reason: EKSControlPlaneReconciliationFailed
|
||||
severity: Error
|
||||
status: "False"
|
||||
- lastTransitionTime: "2024-07-30T11:10:03Z"
|
||||
status: "True"
|
||||
type: EKSControlPlaneReady
|
||||
- lastTransitionTime: "2024-07-26T15:28:01Z"
|
||||
status: "True"
|
||||
type: IAMAuthenticatorConfigured
|
||||
- lastTransitionTime: "2024-07-26T15:27:58Z"
|
||||
status: "True"
|
||||
type: IAMControlPlaneRolesReady
|
||||
- lastTransitionTime: "2024-07-26T14:35:48Z"
|
||||
status: "True"
|
||||
type: SubnetsReady
|
||||
- lastTransitionTime: "2024-07-26T14:35:46Z"
|
||||
status: "True"
|
||||
type: VpcReady
|
||||
ready: true
|
||||
@@ -0,0 +1,46 @@
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
|
||||
kind: AWSManagedControlPlane
|
||||
metadata:
|
||||
name: test
|
||||
namespace: ns-test
|
||||
ownerReferences:
|
||||
- apiVersion: cluster.x-k8s.io/v1beta1
|
||||
blockOwnerDeletion: true
|
||||
controller: true
|
||||
kind: Cluster
|
||||
name: test
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2024-07-30T11:10:03Z"
|
||||
status: "True"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2024-07-26T14:35:48Z"
|
||||
status: "True"
|
||||
type: ClusterSecurityGroupsReady
|
||||
- lastTransitionTime: "2024-07-30T02:20:06Z"
|
||||
status: "True"
|
||||
type: EKSAddonsConfigured
|
||||
- lastTransitionTime: "2024-07-26T14:43:57Z"
|
||||
reason: created
|
||||
severity: Info
|
||||
status: "False"
|
||||
type: EKSControlPlaneCreating
|
||||
- lastTransitionTime: "2024-07-30T11:10:03Z"
|
||||
status: "True"
|
||||
type: EKSControlPlaneReady
|
||||
- lastTransitionTime: "2024-07-30T13:05:45Z"
|
||||
status: "True"
|
||||
type: EKSIdentityProviderConfigured
|
||||
- lastTransitionTime: "2024-07-26T15:28:01Z"
|
||||
status: "True"
|
||||
type: IAMAuthenticatorConfigured
|
||||
- lastTransitionTime: "2024-07-26T15:27:58Z"
|
||||
status: "True"
|
||||
type: IAMControlPlaneRolesReady
|
||||
- lastTransitionTime: "2024-07-26T14:35:48Z"
|
||||
status: "True"
|
||||
type: SubnetsReady
|
||||
- lastTransitionTime: "2024-07-26T14:35:46Z"
|
||||
status: "True"
|
||||
type: VpcReady
|
||||
ready: true
|
||||
@@ -0,0 +1,46 @@
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
|
||||
kind: AWSManagedControlPlane
|
||||
metadata:
|
||||
name: test
|
||||
namespace: ns-test
|
||||
ownerReferences:
|
||||
- apiVersion: cluster.x-k8s.io/v1beta1
|
||||
blockOwnerDeletion: true
|
||||
controller: true
|
||||
kind: Cluster
|
||||
name: test
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2024-07-30T11:10:03Z"
|
||||
status: "False"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2024-07-26T14:35:48Z"
|
||||
status: "True"
|
||||
type: ClusterSecurityGroupsReady
|
||||
- lastTransitionTime: "2024-07-30T02:20:06Z"
|
||||
status: "True"
|
||||
type: EKSAddonsConfigured
|
||||
- lastTransitionTime: "2024-07-26T14:43:57Z"
|
||||
reason: created
|
||||
severity: Info
|
||||
status: "False"
|
||||
type: EKSControlPlaneCreating
|
||||
- lastTransitionTime: "2024-07-30T11:10:03Z"
|
||||
status: "True"
|
||||
type: EKSControlPlaneReady
|
||||
- lastTransitionTime: "2024-07-30T13:05:45Z"
|
||||
status: "True"
|
||||
type: EKSIdentityProviderConfigured
|
||||
- lastTransitionTime: "2024-07-26T15:28:01Z"
|
||||
status: "True"
|
||||
type: IAMAuthenticatorConfigured
|
||||
- lastTransitionTime: "2024-07-26T15:27:58Z"
|
||||
status: "True"
|
||||
type: IAMControlPlaneRolesReady
|
||||
- lastTransitionTime: "2024-07-26T14:35:48Z"
|
||||
status: "True"
|
||||
type: SubnetsReady
|
||||
- lastTransitionTime: "2024-07-26T14:35:46Z"
|
||||
status: "True"
|
||||
type: VpcReady
|
||||
ready: false
|
||||
@@ -0,0 +1,46 @@
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
|
||||
kind: AWSManagedControlPlane
|
||||
metadata:
|
||||
name: test
|
||||
namespace: ns-test
|
||||
ownerReferences:
|
||||
- apiVersion: cluster.x-k8s.io/v1beta1
|
||||
blockOwnerDeletion: true
|
||||
controller: true
|
||||
kind: Cluster
|
||||
name: test
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2024-07-30T11:10:03Z"
|
||||
status: "False"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2024-07-26T14:35:48Z"
|
||||
status: "True"
|
||||
type: ClusterSecurityGroupsReady
|
||||
- lastTransitionTime: "2024-07-30T02:20:06Z"
|
||||
status: "True"
|
||||
type: EKSAddonsConfigured
|
||||
- lastTransitionTime: "2024-07-26T14:43:57Z"
|
||||
reason: created
|
||||
severity: Info
|
||||
status: "False"
|
||||
type: EKSControlPlaneCreating
|
||||
- lastTransitionTime: "2024-07-30T11:10:03Z"
|
||||
status: "True"
|
||||
type: EKSControlPlaneReady
|
||||
- lastTransitionTime: "2024-07-30T13:05:45Z"
|
||||
status: "True"
|
||||
type: EKSIdentityProviderConfigured
|
||||
- lastTransitionTime: "2024-07-26T15:28:01Z"
|
||||
status: "True"
|
||||
type: IAMAuthenticatorConfigured
|
||||
- lastTransitionTime: "2024-07-26T15:27:58Z"
|
||||
status: "True"
|
||||
type: IAMControlPlaneRolesReady
|
||||
- lastTransitionTime: "2024-07-26T14:35:48Z"
|
||||
status: "True"
|
||||
type: SubnetsReady
|
||||
- lastTransitionTime: "2024-07-26T14:35:46Z"
|
||||
status: "True"
|
||||
type: VpcReady
|
||||
ready: true
|
||||
@@ -1495,71 +1495,9 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var versionId int64 = 0
|
||||
if q.VersionId != nil {
|
||||
versionId = int64(*q.VersionId)
|
||||
}
|
||||
|
||||
var source *v1alpha1.ApplicationSource
|
||||
|
||||
// To support changes between single source and multi source revisions
|
||||
// we have to calculate if the operation has to be done as multisource or not.
|
||||
// There are 2 different scenarios, checking current revision and historic revision
|
||||
// - Current revision (VersionId is nil or 0):
|
||||
// - The application is multi source and required version too -> multi source
|
||||
// - The application is single source and the required version too -> single source
|
||||
// - The application is multi source and the required version is single source -> single source
|
||||
// - The application is single source and the required version is multi source -> multi source
|
||||
// - Historic revision:
|
||||
// - The application is multi source and the previous one too -> multi source
|
||||
// - The application is single source and the previous one too -> single source
|
||||
// - The application is multi source and the previous one is single source -> multi source
|
||||
// - The application is single source and the previous one is multi source -> single source
|
||||
isRevisionMultiSource := a.Spec.HasMultipleSources()
|
||||
emptyHistory := len(a.Status.History) == 0
|
||||
if !emptyHistory {
|
||||
for _, h := range a.Status.History {
|
||||
if h.ID == versionId {
|
||||
isRevisionMultiSource = len(h.Revisions) > 0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the historical data is empty (because the app hasn't been synced yet)
|
||||
// we can use the source, if not (the app has been synced at least once)
|
||||
// we have to use the history because sources can be added/removed
|
||||
if emptyHistory {
|
||||
if isRevisionMultiSource {
|
||||
source = &a.Spec.Sources[*q.SourceIndex]
|
||||
} else {
|
||||
s := a.Spec.GetSource()
|
||||
source = &s
|
||||
}
|
||||
} else {
|
||||
// the source count can change during the time, we cannot just trust in .status.sync
|
||||
// because if a source has been added/removed, the revisions there won't match
|
||||
// as this is only used for the UI and not internally, we can use the historical data
|
||||
// using the specific revisionId
|
||||
for _, h := range a.Status.History {
|
||||
if h.ID == versionId {
|
||||
// The iteration values are assigned to the respective iteration variables as in an assignment statement.
|
||||
// The iteration variables may be declared by the “range” clause using a form of short variable declaration (:=).
|
||||
// In this case their types are set to the types of the respective iteration values and their scope is the block of the "for" statement;
|
||||
// they are re-used in each iteration. If the iteration variables are declared outside the "for" statement,
|
||||
// after execution their values will be those of the last iteration.
|
||||
// https://golang.org/ref/spec#For_statements
|
||||
h := h
|
||||
if isRevisionMultiSource {
|
||||
source = &h.Sources[*q.SourceIndex]
|
||||
} else {
|
||||
source = &h.Source
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if source == nil {
|
||||
return nil, fmt.Errorf("revision not found: %w", err)
|
||||
source, err := getAppSourceBySourceIndexAndVersionId(a, q.SourceIndex, q.VersionId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app source by source index and version ID: %w", err)
|
||||
}
|
||||
|
||||
repo, err := s.db.GetRepository(ctx, source.RepoURL, proj.Name)
|
||||
@@ -1585,22 +1523,9 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var source *v1alpha1.ApplicationSource
|
||||
if a.Spec.HasMultipleSources() {
|
||||
// the source count can change during the time, we cannot just trust in .status.sync
|
||||
// because if a source has been added/removed, the revisions there won't match
|
||||
// as this is only used for the UI and not internally, we can use the historical data
|
||||
// using the specific revisionId
|
||||
for _, h := range a.Status.History {
|
||||
if h.ID == int64(*q.VersionId) {
|
||||
source = &h.Sources[*q.SourceIndex]
|
||||
}
|
||||
}
|
||||
if source == nil {
|
||||
return nil, fmt.Errorf("revision not found: %w", err)
|
||||
}
|
||||
} else {
|
||||
source = a.Spec.Source
|
||||
source, err := getAppSourceBySourceIndexAndVersionId(a, q.SourceIndex, q.VersionId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app source by source index and version ID: %w", err)
|
||||
}
|
||||
|
||||
if source.Chart == "" {
|
||||
@@ -1622,6 +1547,76 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi
|
||||
})
|
||||
}
|
||||
|
||||
// getAppSourceBySourceIndexAndVersionId returns the source for a specific source index and version ID. Source index and
|
||||
// version ID are optional. If the source index is not specified, it defaults to 0. If the version ID is not specified,
|
||||
// we use the source(s) currently configured for the app. If the version ID is specified, we find the source for that
|
||||
// version ID. If the version ID is not found, we return an error. If the source index is out of bounds for whichever
|
||||
// source we choose (configured sources or sources for a specific version), we return an error.
|
||||
func getAppSourceBySourceIndexAndVersionId(a *appv1.Application, sourceIndexMaybe *int32, versionIdMaybe *int32) (appv1.ApplicationSource, error) {
|
||||
// Start with all the app's configured sources.
|
||||
sources := a.Spec.GetSources()
|
||||
|
||||
// If the user specified a version, get the sources for that version. If the version is not found, return an error.
|
||||
if versionIdMaybe != nil {
|
||||
versionId := int64(*versionIdMaybe)
|
||||
var err error
|
||||
sources, err = getSourcesByVersionId(a, versionId)
|
||||
if err != nil {
|
||||
return appv1.ApplicationSource{}, fmt.Errorf("error getting source by version ID: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Start by assuming we want the first source.
|
||||
sourceIndex := 0
|
||||
|
||||
// If the user specified a source index, use that instead.
|
||||
if sourceIndexMaybe != nil {
|
||||
sourceIndex = int(*sourceIndexMaybe)
|
||||
if sourceIndex >= len(sources) {
|
||||
if len(sources) == 1 {
|
||||
return appv1.ApplicationSource{}, fmt.Errorf("source index %d not found because there is only 1 source", sourceIndex)
|
||||
}
|
||||
return appv1.ApplicationSource{}, fmt.Errorf("source index %d not found because there are only %d sources", sourceIndex, len(sources))
|
||||
}
|
||||
}
|
||||
|
||||
source := sources[sourceIndex]
|
||||
|
||||
return source, nil
|
||||
}
|
||||
|
||||
// getRevisionHistoryByVersionId returns the revision history for a specific version ID.
|
||||
// If the version ID is not found, it returns an empty revision history and false.
|
||||
func getRevisionHistoryByVersionId(histories v1alpha1.RevisionHistories, versionId int64) (appv1.RevisionHistory, bool) {
|
||||
for _, h := range histories {
|
||||
if h.ID == versionId {
|
||||
return h, true
|
||||
}
|
||||
}
|
||||
return appv1.RevisionHistory{}, false
|
||||
}
|
||||
|
||||
// getSourcesByVersionId returns the sources for a specific version ID. If there is no history, it returns an error.
|
||||
// If the version ID is not found, it returns an error. If the version ID is found, and there are multiple sources,
|
||||
// it returns the sources for that version ID. If the version ID is found, and there is only one source, it returns
|
||||
// a slice with just the single source.
|
||||
func getSourcesByVersionId(a *appv1.Application, versionId int64) ([]appv1.ApplicationSource, error) {
|
||||
if len(a.Status.History) == 0 {
|
||||
return nil, fmt.Errorf("version ID %d not found because the app has no history", versionId)
|
||||
}
|
||||
|
||||
h, ok := getRevisionHistoryByVersionId(a.Status.History, versionId)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("revision history not found for version ID %d", versionId)
|
||||
}
|
||||
|
||||
if len(h.Sources) > 0 {
|
||||
return h.Sources, nil
|
||||
}
|
||||
|
||||
return []v1alpha1.ApplicationSource{h.Source}, nil
|
||||
}
|
||||
|
||||
func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) bool {
|
||||
return (q.GetName() == "" || q.GetName() == key.Name) &&
|
||||
(q.GetNamespace() == "" || q.GetNamespace() == key.Namespace) &&
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
@@ -3025,3 +3027,265 @@ func TestServer_ResolveSourceRevisions_SingleSource(t *testing.T) {
|
||||
assert.Equal(t, ([]string)(nil), sourceRevisions)
|
||||
assert.Equal(t, ([]string)(nil), displayRevisions)
|
||||
}
|
||||
|
||||
func Test_RevisionMetadata(t *testing.T) {
|
||||
singleSourceApp := newTestApp()
|
||||
singleSourceApp.Name = "single-source-app"
|
||||
singleSourceApp.Spec = appv1.ApplicationSpec{
|
||||
Source: &appv1.ApplicationSource{
|
||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
||||
Path: "helm-guestbook",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
}
|
||||
|
||||
multiSourceApp := newTestApp()
|
||||
multiSourceApp.Name = "multi-source-app"
|
||||
multiSourceApp.Spec = appv1.ApplicationSpec{
|
||||
Sources: []appv1.ApplicationSource{
|
||||
{
|
||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
||||
Path: "helm-guestbook",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
{
|
||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
||||
Path: "kustomize-guestbook",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
singleSourceHistory := []appv1.RevisionHistory{
|
||||
{
|
||||
ID: 1,
|
||||
Source: singleSourceApp.Spec.GetSource(),
|
||||
Revision: "a",
|
||||
},
|
||||
}
|
||||
multiSourceHistory := []appv1.RevisionHistory{
|
||||
{
|
||||
ID: 1,
|
||||
Sources: multiSourceApp.Spec.GetSources(),
|
||||
Revisions: []string{"a", "b"},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
multiSource bool
|
||||
history *struct {
|
||||
matchesSourceType bool
|
||||
}
|
||||
sourceIndex *int32
|
||||
versionId *int32
|
||||
expectErrorContains *string
|
||||
}{
|
||||
{
|
||||
name: "single-source app without history, no source index, no version ID",
|
||||
multiSource: false,
|
||||
},
|
||||
{
|
||||
name: "single-source app without history, no source index, missing version ID",
|
||||
multiSource: false,
|
||||
versionId: pointer.Int32(999),
|
||||
expectErrorContains: pointer.String("the app has no history"),
|
||||
},
|
||||
{
|
||||
name: "single source app without history, present source index, no version ID",
|
||||
multiSource: false,
|
||||
sourceIndex: pointer.Int32(0),
|
||||
},
|
||||
{
|
||||
name: "single source app without history, invalid source index, no version ID",
|
||||
multiSource: false,
|
||||
sourceIndex: pointer.Int32(999),
|
||||
expectErrorContains: pointer.String("source index 999 not found"),
|
||||
},
|
||||
{
|
||||
name: "single source app with matching history, no source index, no version ID",
|
||||
multiSource: false,
|
||||
history: &struct{ matchesSourceType bool }{true},
|
||||
},
|
||||
{
|
||||
name: "single source app with matching history, no source index, missing version ID",
|
||||
multiSource: false,
|
||||
history: &struct{ matchesSourceType bool }{true},
|
||||
versionId: pointer.Int32(999),
|
||||
expectErrorContains: pointer.String("history not found for version ID 999"),
|
||||
},
|
||||
{
|
||||
name: "single source app with matching history, no source index, present version ID",
|
||||
multiSource: false,
|
||||
history: &struct{ matchesSourceType bool }{true},
|
||||
versionId: pointer.Int32(1),
|
||||
},
|
||||
{
|
||||
name: "single source app with multi-source history, no source index, no version ID",
|
||||
multiSource: false,
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
},
|
||||
{
|
||||
name: "single source app with multi-source history, no source index, missing version ID",
|
||||
multiSource: false,
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
versionId: pointer.Int32(999),
|
||||
expectErrorContains: pointer.String("history not found for version ID 999"),
|
||||
},
|
||||
{
|
||||
name: "single source app with multi-source history, no source index, present version ID",
|
||||
multiSource: false,
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
versionId: pointer.Int32(1),
|
||||
},
|
||||
{
|
||||
name: "single-source app with multi-source history, source index 1, no version ID",
|
||||
multiSource: false,
|
||||
sourceIndex: pointer.Int32(1),
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
// Since the user requested source index 1, but no version ID, we'll get an error when looking at the live
|
||||
// source, because the live source is single-source.
|
||||
expectErrorContains: pointer.String("there is only 1 source"),
|
||||
},
|
||||
{
|
||||
name: "single-source app with multi-source history, invalid source index, no version ID",
|
||||
multiSource: false,
|
||||
sourceIndex: pointer.Int32(999),
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
expectErrorContains: pointer.String("source index 999 not found"),
|
||||
},
|
||||
{
|
||||
name: "single-source app with multi-source history, valid source index, present version ID",
|
||||
multiSource: false,
|
||||
sourceIndex: pointer.Int32(1),
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
versionId: pointer.Int32(1),
|
||||
},
|
||||
{
|
||||
name: "multi-source app without history, no source index, no version ID",
|
||||
multiSource: true,
|
||||
},
|
||||
{
|
||||
name: "multi-source app without history, no source index, missing version ID",
|
||||
multiSource: true,
|
||||
versionId: pointer.Int32(999),
|
||||
expectErrorContains: pointer.String("the app has no history"),
|
||||
},
|
||||
{
|
||||
name: "multi-source app without history, present source index, no version ID",
|
||||
multiSource: true,
|
||||
sourceIndex: pointer.Int32(1),
|
||||
},
|
||||
{
|
||||
name: "multi-source app without history, invalid source index, no version ID",
|
||||
multiSource: true,
|
||||
sourceIndex: pointer.Int32(999),
|
||||
expectErrorContains: pointer.String("source index 999 not found"),
|
||||
},
|
||||
{
|
||||
name: "multi-source app with matching history, no source index, no version ID",
|
||||
multiSource: true,
|
||||
history: &struct{ matchesSourceType bool }{true},
|
||||
},
|
||||
{
|
||||
name: "multi-source app with matching history, no source index, missing version ID",
|
||||
multiSource: true,
|
||||
history: &struct{ matchesSourceType bool }{true},
|
||||
versionId: pointer.Int32(999),
|
||||
expectErrorContains: pointer.String("history not found for version ID 999"),
|
||||
},
|
||||
{
|
||||
name: "multi-source app with matching history, no source index, present version ID",
|
||||
multiSource: true,
|
||||
history: &struct{ matchesSourceType bool }{true},
|
||||
versionId: pointer.Int32(1),
|
||||
},
|
||||
{
|
||||
name: "multi-source app with single-source history, no source index, no version ID",
|
||||
multiSource: true,
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
},
|
||||
{
|
||||
name: "multi-source app with single-source history, no source index, missing version ID",
|
||||
multiSource: true,
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
versionId: pointer.Int32(999),
|
||||
expectErrorContains: pointer.String("history not found for version ID 999"),
|
||||
},
|
||||
{
|
||||
name: "multi-source app with single-source history, no source index, present version ID",
|
||||
multiSource: true,
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
versionId: pointer.Int32(1),
|
||||
},
|
||||
{
|
||||
name: "multi-source app with single-source history, source index 1, no version ID",
|
||||
multiSource: true,
|
||||
sourceIndex: pointer.Int32(1),
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
},
|
||||
{
|
||||
name: "multi-source app with single-source history, invalid source index, no version ID",
|
||||
multiSource: true,
|
||||
sourceIndex: pointer.Int32(999),
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
expectErrorContains: pointer.String("source index 999 not found"),
|
||||
},
|
||||
{
|
||||
name: "multi-source app with single-source history, valid source index, present version ID",
|
||||
multiSource: true,
|
||||
sourceIndex: pointer.Int32(0),
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
versionId: pointer.Int32(1),
|
||||
},
|
||||
{
|
||||
name: "multi-source app with single-source history, source index 1, present version ID",
|
||||
multiSource: true,
|
||||
sourceIndex: pointer.Int32(1),
|
||||
history: &struct{ matchesSourceType bool }{false},
|
||||
versionId: pointer.Int32(1),
|
||||
expectErrorContains: pointer.String("source index 1 not found"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tcc := tc
|
||||
t.Run(tcc.name, func(t *testing.T) {
|
||||
app := singleSourceApp
|
||||
if tcc.multiSource {
|
||||
app = multiSourceApp
|
||||
}
|
||||
if tcc.history != nil {
|
||||
if tcc.history.matchesSourceType {
|
||||
if tcc.multiSource {
|
||||
app.Status.History = multiSourceHistory
|
||||
} else {
|
||||
app.Status.History = singleSourceHistory
|
||||
}
|
||||
} else {
|
||||
if tcc.multiSource {
|
||||
app.Status.History = singleSourceHistory
|
||||
} else {
|
||||
app.Status.History = multiSourceHistory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s := newTestAppServer(t, app)
|
||||
|
||||
request := &application.RevisionMetadataQuery{
|
||||
Name: pointer.String(app.Name),
|
||||
Revision: pointer.String("HEAD"),
|
||||
SourceIndex: tcc.sourceIndex,
|
||||
VersionId: tcc.versionId,
|
||||
}
|
||||
|
||||
_, err := s.RevisionMetadata(context.Background(), request)
|
||||
if tcc.expectErrorContains != nil {
|
||||
require.ErrorContains(t, err, *tcc.expectErrorContains)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,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,12 +1,16 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/v2/util/rbac"
|
||||
|
||||
"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"
|
||||
@@ -32,6 +36,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{}
|
||||
@@ -40,6 +45,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
|
||||
@@ -49,7 +56,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
|
||||
@@ -60,12 +67,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
|
||||
}
|
||||
@@ -126,6 +136,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
|
||||
@@ -136,6 +169,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,25 +1,65 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"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 {
|
||||
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 +84,71 @@ func TestReconnect(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ReconnectMessage, message.Data)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1049,7 +1049,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)
|
||||
|
||||
|
||||
@@ -522,101 +522,6 @@ func TestSimpleListGeneratorGoTemplate(t *testing.T) {
|
||||
Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{*expectedAppNewMetadata}))
|
||||
}
|
||||
|
||||
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{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
|
||||
@@ -153,7 +153,9 @@ export const ApplicationDeploymentHistory = ({
|
||||
</React.Fragment>
|
||||
))
|
||||
)
|
||||
) : null}
|
||||
) : (
|
||||
<p>Click to see source details.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -192,7 +192,14 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
|
||||
)
|
||||
);
|
||||
|
||||
const getContentForChart = (aRevision: string, aSourceIndex: number, aVersionId: number, indx: number, aSource: models.ApplicationSource, sourceHeader?: JSX.Element) => {
|
||||
const getContentForChart = (
|
||||
aRevision: string,
|
||||
aSourceIndex: number | null,
|
||||
aVersionId: number | null,
|
||||
indx: number,
|
||||
aSource: models.ApplicationSource,
|
||||
sourceHeader?: JSX.Element
|
||||
) => {
|
||||
const showChartNonMetadataInfo = (aRevision: string, aRepoUrl: string) => {
|
||||
return (
|
||||
<>
|
||||
@@ -366,9 +373,9 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
|
||||
return <>{cont}</>;
|
||||
} else if (application.spec.source) {
|
||||
if (source.chart) {
|
||||
cont.push(getContentForChart(revision, 0, 0, 0, source));
|
||||
cont.push(getContentForChart(revision, null, null, 0, source));
|
||||
} else {
|
||||
cont.push(getContentForNonChart(revision, 0, getAppCurrentVersion(application), 0, source));
|
||||
cont.push(getContentForNonChart(revision, null, getAppCurrentVersion(application), 0, source));
|
||||
}
|
||||
return <>{cont}</>;
|
||||
} else {
|
||||
|
||||
@@ -247,8 +247,8 @@ export const ApplicationParameters = (props: {
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<DataLoader input={app} load={application => getSourceFromSources(application, index)}>
|
||||
{(details: models.RepoAppDetails) => getEditablePanelForOneSource(details, index, source)}
|
||||
<DataLoader input={app.spec.sources[index]} load={src => getSourceFromAppSources(src, app.metadata.name, app.spec.project, index, 0)}>
|
||||
{(details: models.RepoAppDetails) => getEditablePanelForOneSource(details, index, app.spec.sources[index])}
|
||||
</DataLoader>
|
||||
</div>
|
||||
</div>
|
||||
@@ -986,17 +986,12 @@ function gatherDetails(
|
||||
}
|
||||
|
||||
// For Sources field. Get one source with index i from the list
|
||||
async function getSourceFromSources(app: models.Application, i: number) {
|
||||
const sources: models.ApplicationSource[] = app.spec.sources;
|
||||
if (sources && i < sources.length) {
|
||||
const aSource = sources[i];
|
||||
const repoDetail = await services.repos.appDetails(aSource, app.metadata.name, app.spec.project, i, 0).catch(() => ({
|
||||
type: 'Directory' as models.AppSourceType,
|
||||
path: aSource.path
|
||||
}));
|
||||
return repoDetail;
|
||||
}
|
||||
return null;
|
||||
async function getSourceFromAppSources(aSource: models.ApplicationSource, name: string, project: string, index: number, version: number) {
|
||||
const repoDetail = await services.repos.appDetails(aSource, name, project, index, version).catch(() => ({
|
||||
type: 'Directory' as models.AppSourceType,
|
||||
path: aSource.path
|
||||
}));
|
||||
return repoDetail;
|
||||
}
|
||||
|
||||
// Delete when source field is removed
|
||||
|
||||
@@ -1131,9 +1131,9 @@ export function getAppDefaultOperationSyncRevision(app?: appModels.Application)
|
||||
|
||||
// getAppCurrentVersion gets the first app revisions from `status.sync.revisions` or, if that list is missing or empty, the `revision`
|
||||
// field.
|
||||
export function getAppCurrentVersion(app?: appModels.Application) {
|
||||
if (!app || !app.status || !app.status.history) {
|
||||
return 0;
|
||||
export function getAppCurrentVersion(app?: appModels.Application): number | null {
|
||||
if (!app || !app.status || !app.status.history || app.status.history.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return app.status.history[app.status.history.length - 1].id;
|
||||
}
|
||||
|
||||
@@ -857,7 +857,7 @@ export class ReposList extends React.Component<
|
||||
const confirmed = await this.appContext.apis.popup.confirm('Disconnect repository', `Are you sure you want to disconnect '${repo}'?`);
|
||||
if (confirmed) {
|
||||
try {
|
||||
await services.repos.delete(repo, project);
|
||||
await services.repos.delete(repo, project || '');
|
||||
this.repoLoader.reload();
|
||||
} catch (e) {
|
||||
this.appContext.apis.notifications.show({
|
||||
|
||||
@@ -53,22 +53,26 @@ export class ApplicationsService {
|
||||
.then(res => res.body as models.ApplicationSyncWindowState);
|
||||
}
|
||||
|
||||
public revisionMetadata(name: string, appNamespace: string, revision: string, sourceIndex: number, versionId: number): Promise<models.RevisionMetadata> {
|
||||
return requests
|
||||
.get(`/applications/${name}/revisions/${revision || 'HEAD'}/metadata`)
|
||||
.query({appNamespace})
|
||||
.query({sourceIndex})
|
||||
.query({versionId})
|
||||
.then(res => res.body as models.RevisionMetadata);
|
||||
public revisionMetadata(name: string, appNamespace: string, revision: string, sourceIndex: number | null, versionId: number | null): Promise<models.RevisionMetadata> {
|
||||
let r = requests.get(`/applications/${name}/revisions/${revision || 'HEAD'}/metadata`).query({appNamespace});
|
||||
if (sourceIndex !== null) {
|
||||
r = r.query({sourceIndex});
|
||||
}
|
||||
if (versionId !== null) {
|
||||
r = r.query({versionId});
|
||||
}
|
||||
return r.then(res => res.body as models.RevisionMetadata);
|
||||
}
|
||||
|
||||
public revisionChartDetails(name: string, appNamespace: string, revision: string, sourceIndex: number, versionId: number): Promise<models.ChartDetails> {
|
||||
return requests
|
||||
.get(`/applications/${name}/revisions/${revision || 'HEAD'}/chartdetails`)
|
||||
.query({appNamespace})
|
||||
.query({sourceIndex})
|
||||
.query({versionId})
|
||||
.then(res => res.body as models.ChartDetails);
|
||||
let r = requests.get(`/applications/${name}/revisions/${revision || 'HEAD'}/chartdetails`).query({appNamespace});
|
||||
if (sourceIndex !== null) {
|
||||
r = r.query({sourceIndex});
|
||||
}
|
||||
if (versionId !== null) {
|
||||
r = r.query({versionId});
|
||||
}
|
||||
return r.then(res => res.body as models.ChartDetails);
|
||||
}
|
||||
|
||||
public resourceTree(name: string, appNamespace: string): Promise<models.ApplicationTree> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -134,7 +134,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},
|
||||
|
||||
@@ -1132,7 +1132,7 @@ func GetAppEventLabels(app *argoappv1.Application, projLister applicationsv1.App
|
||||
// Filter out event labels to include
|
||||
inKeys := settingsManager.GetIncludeEventLabelKeys()
|
||||
for k, v := range labels {
|
||||
found := glob.MatchStringInList(inKeys, k, false)
|
||||
found := glob.MatchStringInList(inKeys, k, glob.GLOB)
|
||||
if found {
|
||||
eventLabels[k] = v
|
||||
}
|
||||
@@ -1141,7 +1141,7 @@ func GetAppEventLabels(app *argoappv1.Application, projLister applicationsv1.App
|
||||
// Remove excluded event labels
|
||||
exKeys := settingsManager.GetExcludeEventLabelKeys()
|
||||
for k := range eventLabels {
|
||||
found := glob.MatchStringInList(exKeys, k, false)
|
||||
found := glob.MatchStringInList(exKeys, k, glob.GLOB)
|
||||
if found {
|
||||
delete(eventLabels, k)
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ func (rt *resourceTracking) SetAppInstance(un *unstructured.Unstructured, key, v
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set app instance label: %w", err)
|
||||
}
|
||||
return nil
|
||||
case TrackingMethodAnnotation:
|
||||
return setAppInstanceAnnotation()
|
||||
case TrackingMethodAnnotationAndLabel:
|
||||
@@ -171,13 +172,14 @@ func (rt *resourceTracking) SetAppInstance(un *unstructured.Unstructured, key, v
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set app instance label: %w", err)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
err := argokube.SetAppInstanceLabel(un, key, val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set app instance label: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildAppInstanceValue build resource tracking id in format <application-name>;<group>/<kind>/<namespace>/<name>
|
||||
|
||||
@@ -31,26 +31,28 @@ func Test_Match(t *testing.T) {
|
||||
|
||||
func Test_MatchList(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
list []string
|
||||
exact bool
|
||||
result bool
|
||||
name string
|
||||
input string
|
||||
list []string
|
||||
patternMatch string
|
||||
result bool
|
||||
}{
|
||||
{"Exact name in list", "test", []string{"test"}, true, true},
|
||||
{"Exact name not in list", "test", []string{"other"}, true, false},
|
||||
{"Exact name not in list, multiple elements", "test", []string{"some", "other"}, true, false},
|
||||
{"Exact name not in list, list empty", "test", []string{}, true, false},
|
||||
{"Exact name not in list, empty element", "test", []string{""}, true, false},
|
||||
{"Glob name in list, but exact wanted", "test", []string{"*"}, true, false},
|
||||
{"Glob name in list with simple wildcard", "test", []string{"*"}, false, true},
|
||||
{"Glob name in list without wildcard", "test", []string{"test"}, false, true},
|
||||
{"Glob name in list, multiple elements", "test", []string{"other*", "te*"}, false, true},
|
||||
{"Exact name in list", "test", []string{"test"}, EXACT, true},
|
||||
{"Exact name not in list", "test", []string{"other"}, EXACT, false},
|
||||
{"Exact name not in list, multiple elements", "test", []string{"some", "other"}, EXACT, false},
|
||||
{"Exact name not in list, list empty", "test", []string{}, EXACT, false},
|
||||
{"Exact name not in list, empty element", "test", []string{""}, EXACT, false},
|
||||
{"Glob name in list, but exact wanted", "test", []string{"*"}, EXACT, false},
|
||||
{"Glob name in list with simple wildcard", "test", []string{"*"}, GLOB, true},
|
||||
{"Glob name in list without wildcard", "test", []string{"test"}, GLOB, true},
|
||||
{"Glob name in list, multiple elements", "test", []string{"other*", "te*"}, GLOB, true},
|
||||
{"match everything but specified word: fail", "disallowed", []string{"/^((?!disallowed).)*$/"}, REGEXP, false},
|
||||
{"match everything but specified word: pass", "allowed", []string{"/^((?!disallowed).)*$/"}, REGEXP, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
res := MatchStringInList(tt.list, tt.input, tt.exact)
|
||||
res := MatchStringInList(tt.list, tt.input, tt.patternMatch)
|
||||
assert.Equal(t, tt.result, res)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
package glob
|
||||
|
||||
// MatchStringInList will return true if item is contained in list. If
|
||||
// exactMatch is set to false, list may contain globs to be matched.
|
||||
func MatchStringInList(list []string, item string, exactMatch bool) bool {
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/regex"
|
||||
)
|
||||
|
||||
const (
|
||||
EXACT = "exact"
|
||||
GLOB = "glob"
|
||||
REGEXP = "regexp"
|
||||
)
|
||||
|
||||
// MatchStringInList will return true if item is contained in list.
|
||||
// patternMatch; can be set to exact, glob, regexp.
|
||||
// If patternMatch; is set to exact, the item must be an exact match.
|
||||
// If patternMatch; is set to glob, the item must match a glob pattern.
|
||||
// If patternMatch; is set to regexp, the item must match a regular expression or glob.
|
||||
func MatchStringInList(list []string, item string, patternMatch string) bool {
|
||||
for _, ll := range list {
|
||||
if item == ll || (!exactMatch && Match(ll, item)) {
|
||||
// If string is wrapped in "/", assume it is a regular expression.
|
||||
if patternMatch == REGEXP && strings.HasPrefix(ll, "/") && strings.HasSuffix(ll, "/") && regex.Match(ll[1:len(ll)-1], item) {
|
||||
return true
|
||||
} else if (patternMatch == REGEXP || patternMatch == GLOB) && Match(ll, item) {
|
||||
return true
|
||||
} else if patternMatch == EXACT && item == ll {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
)
|
||||
|
||||
var resourceNamePattern = regexp.MustCompile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
|
||||
@@ -15,6 +19,7 @@ func IsValidResourceName(name string) bool {
|
||||
}
|
||||
|
||||
// SetAppInstanceLabel the recommended app.kubernetes.io/instance label against an unstructured object
|
||||
// Uses the legacy labeling if environment variable is set
|
||||
func SetAppInstanceLabel(target *unstructured.Unstructured, key, val string) error {
|
||||
labels, _, err := nestedNullableStringMap(target.Object, "metadata", "labels")
|
||||
if err != nil {
|
||||
@@ -25,10 +30,75 @@ func SetAppInstanceLabel(target *unstructured.Unstructured, key, val string) err
|
||||
}
|
||||
labels[key] = val
|
||||
target.SetLabels(labels)
|
||||
if key != common.LabelKeyLegacyApplicationName {
|
||||
// we no longer label the pod template sub resources in v0.11
|
||||
return nil
|
||||
}
|
||||
|
||||
gvk := schema.FromAPIVersionAndKind(target.GetAPIVersion(), target.GetKind())
|
||||
// special case for deployment and job types: make sure that derived replicaset, and pod has
|
||||
// the application label
|
||||
switch gvk.Group {
|
||||
case "apps", "extensions":
|
||||
switch gvk.Kind {
|
||||
case kube.DeploymentKind, kube.ReplicaSetKind, kube.StatefulSetKind, kube.DaemonSetKind:
|
||||
templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok || templateLabels == nil {
|
||||
templateLabels = make(map[string]interface{})
|
||||
}
|
||||
templateLabels[key] = val
|
||||
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The following is a workaround for issue #335. In API version extensions/v1beta1 or
|
||||
// apps/v1beta1, if a spec omits spec.selector then k8s will default the
|
||||
// spec.selector.matchLabels to match spec.template.metadata.labels. This means Argo CD
|
||||
// labels can potentially make their way into spec.selector.matchLabels, which is a bad
|
||||
// thing. The following logic prevents this behavior.
|
||||
switch target.GetAPIVersion() {
|
||||
case "apps/v1beta1", "extensions/v1beta1":
|
||||
selector, _, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "selector")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(selector) == 0 {
|
||||
// If we get here, user did not set spec.selector in their manifest. We do not want
|
||||
// our Argo CD labels to get defaulted by kubernetes, so we explicitly set the labels
|
||||
// for them (minus the Argo CD labels).
|
||||
delete(templateLabels, key)
|
||||
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "selector", "matchLabels")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "batch":
|
||||
switch gvk.Kind {
|
||||
case kube.JobKind:
|
||||
templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok || templateLabels == nil {
|
||||
templateLabels = make(map[string]interface{})
|
||||
}
|
||||
templateLabels[key] = val
|
||||
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAppInstanceAnnotation the recommended app.kubernetes.io/instance annotation against an unstructured object
|
||||
// Uses the legacy labeling if environment variable is set
|
||||
func SetAppInstanceAnnotation(target *unstructured.Unstructured, key, val string) error {
|
||||
annotations, _, err := nestedNullableStringMap(target.Object, "metadata", "annotations")
|
||||
if err != nil {
|
||||
|
||||
@@ -84,6 +84,56 @@ func TestSetLabels(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetLegacyLabels(t *testing.T) {
|
||||
for _, yamlStr := range []string{depWithoutSelector, depWithSelector} {
|
||||
var obj unstructured.Unstructured
|
||||
err := yaml.Unmarshal([]byte(yamlStr), &obj)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = SetAppInstanceLabel(&obj, common.LabelKeyLegacyApplicationName, "my-app")
|
||||
require.NoError(t, err)
|
||||
|
||||
manifestBytes, err := json.MarshalIndent(obj.Object, "", " ")
|
||||
require.NoError(t, err)
|
||||
log.Println(string(manifestBytes))
|
||||
|
||||
var depV1Beta1 extv1beta1.Deployment
|
||||
err = json.Unmarshal(manifestBytes, &depV1Beta1)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, depV1Beta1.Spec.Selector.MatchLabels, 1)
|
||||
assert.Equal(t, "nginx", depV1Beta1.Spec.Selector.MatchLabels["app"])
|
||||
assert.Len(t, depV1Beta1.Spec.Template.Labels, 2)
|
||||
assert.Equal(t, "nginx", depV1Beta1.Spec.Template.Labels["app"])
|
||||
assert.Equal(t, "my-app", depV1Beta1.Spec.Template.Labels[common.LabelKeyLegacyApplicationName])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetLegacyJobLabel(t *testing.T) {
|
||||
yamlBytes, err := os.ReadFile("testdata/job.yaml")
|
||||
require.NoError(t, err)
|
||||
var obj unstructured.Unstructured
|
||||
err = yaml.Unmarshal(yamlBytes, &obj)
|
||||
require.NoError(t, err)
|
||||
err = SetAppInstanceLabel(&obj, common.LabelKeyLegacyApplicationName, "my-app")
|
||||
require.NoError(t, err)
|
||||
|
||||
manifestBytes, err := json.MarshalIndent(obj.Object, "", " ")
|
||||
require.NoError(t, err)
|
||||
log.Println(string(manifestBytes))
|
||||
|
||||
job := unstructured.Unstructured{}
|
||||
err = json.Unmarshal(manifestBytes, &job)
|
||||
require.NoError(t, err)
|
||||
|
||||
labels := job.GetLabels()
|
||||
assert.Equal(t, "my-app", labels[common.LabelKeyLegacyApplicationName])
|
||||
|
||||
templateLabels, ok, err := unstructured.NestedMap(job.UnstructuredContent(), "spec", "template", "metadata", "labels")
|
||||
assert.True(t, ok)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "my-app", templateLabels[common.LabelKeyLegacyApplicationName])
|
||||
}
|
||||
|
||||
func TestSetSvcLabel(t *testing.T) {
|
||||
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
20
util/regex/regex.go
Normal file
20
util/regex/regex.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package regex
|
||||
|
||||
import (
|
||||
"github.com/dlclark/regexp2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Match(pattern, text string) bool {
|
||||
compiledRegex, err := regexp2.Compile(pattern, 0)
|
||||
if err != nil {
|
||||
log.Warnf("failed to compile pattern %s due to error %v", pattern, err)
|
||||
return false
|
||||
}
|
||||
regexMatch, err := compiledRegex.MatchString(text)
|
||||
if err != nil {
|
||||
log.Warnf("failed to match pattern %s due to error %v", pattern, err)
|
||||
return false
|
||||
}
|
||||
return regexMatch
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func IsNamespaceEnabled(namespace string, serverNamespace string, enabledNamespaces []string) bool {
|
||||
return namespace == serverNamespace || glob.MatchStringInList(enabledNamespaces, namespace, false)
|
||||
return namespace == serverNamespace || glob.MatchStringInList(enabledNamespaces, namespace, glob.REGEXP)
|
||||
}
|
||||
|
||||
func NamespaceNotPermittedError(namespace string) error {
|
||||
|
||||
@@ -49,6 +49,20 @@ func Test_IsNamespaceEnabled(t *testing.T) {
|
||||
[]string{"allowed"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"match everything but specified word: fail",
|
||||
"disallowed",
|
||||
"argocd",
|
||||
[]string{"/^((?!disallowed).)*$/"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"match everything but specified word: pass",
|
||||
"allowed",
|
||||
"argocd",
|
||||
[]string{"/^((?!disallowed).)*$/"},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -434,6 +434,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
|
||||
@@ -517,6 +519,11 @@ const (
|
||||
RespectRBACValueNormal = "normal"
|
||||
)
|
||||
|
||||
const (
|
||||
// default max webhook payload size is 1GB
|
||||
defaultMaxWebhookPayloadSize = int64(1) * 1024 * 1024 * 1024
|
||||
)
|
||||
|
||||
var sourceTypeToEnableGenerationKey = map[v1alpha1.ApplicationSourceType]string{
|
||||
v1alpha1.ApplicationSourceTypeKustomize: "kustomize.enable",
|
||||
v1alpha1.ApplicationSourceTypeHelm: "helm.enable",
|
||||
@@ -761,11 +768,6 @@ func (mgr *SettingsManager) GetAppInstanceLabelKey() (string, error) {
|
||||
if label == "" {
|
||||
return common.LabelKeyAppInstance, nil
|
||||
}
|
||||
// return new label key if user is still using legacy key
|
||||
if label == common.LabelKeyLegacyApplicationName {
|
||||
log.Warnf("deprecated legacy application instance tracking key(%v) is present in configmap, new key(%v) will be used automatically", common.LabelKeyLegacyApplicationName, common.LabelKeyAppInstance)
|
||||
return common.LabelKeyAppInstance, nil
|
||||
}
|
||||
return label, nil
|
||||
}
|
||||
|
||||
@@ -2257,3 +2259,22 @@ func (mgr *SettingsManager) GetExcludeEventLabelKeys() []string {
|
||||
}
|
||||
return labelKeys
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -61,9 +61,10 @@ type ArgoCDWebhookHandler struct {
|
||||
azuredevopsAuthHandler func(r *http.Request) error
|
||||
gogs *gogs.Webhook
|
||||
settingsSrc settingsSource
|
||||
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 +114,7 @@ func NewHandler(namespace string, applicationNamespaces []string, appClientset a
|
||||
repoCache: repoCache,
|
||||
serverCache: serverCache,
|
||||
db: argoDB,
|
||||
maxWebhookPayloadSizeB: maxWebhookPayloadSizeB,
|
||||
}
|
||||
|
||||
return &acdWebhook
|
||||
@@ -276,7 +278,7 @@ func (a *ArgoCDWebhookHandler) HandleEvent(payload interface{}) {
|
||||
// nor in the list of enabled namespaces.
|
||||
var filteredApps []v1alpha1.Application
|
||||
for _, app := range apps.Items {
|
||||
if app.Namespace == a.ns || glob.MatchStringInList(a.appNs, app.Namespace, false) {
|
||||
if app.Namespace == a.ns || glob.MatchStringInList(a.appNs, app.Namespace, glob.REGEXP) {
|
||||
filteredApps = append(filteredApps, app)
|
||||
}
|
||||
}
|
||||
@@ -393,6 +395,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 {
|
||||
@@ -435,6 +439,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 {
|
||||
|
||||
@@ -56,6 +56,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 +77,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) {
|
||||
@@ -393,7 +398,7 @@ func TestInvalidEvent(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
h.Handler(w, req)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
expectedLogResult := "Webhook processing failed: error parsing payload"
|
||||
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 +609,20 @@ 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)
|
||||
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