Compare commits

...

11 Commits

Author SHA1 Message Date
github-actions[bot]
c5ea5c4df5 Bump version to 2.9.2 (#16405)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: gdsoumya <gdsoumya@users.noreply.github.com>
2023-11-20 22:45:24 +05:30
gdsoumya
c2c9746050 feat: add support for ALL_PROXY (#10451) (#16401)
Signed-off-by: xniu <cnxuniu@gmail.com>
Co-authored-by: yushiwho <cnxuniu@gmail.com>
Co-authored-by: xniu <xniu@nvidia.com>
2023-11-20 10:32:39 -05:00
gcp-cherry-pick-bot[bot]
78cd50b2c7 fix: set max for max cookie number to math.MaxInt (#16388) (#16397) 2023-11-20 10:04:04 -05:00
gcp-cherry-pick-bot[bot]
dd86b08369 docs: fix documentation of ignoreApplicationDifferences (#16365) (#16380)
Signed-off-by: Talia Stocks <928827+taliastocks@users.noreply.github.com>
Co-authored-by: Talia Stocks <928827+taliastocks@users.noreply.github.com>
2023-11-17 15:25:08 -05:00
Michael Over
0ca43663c9 bump helm 3.13.2 (#16344)
Signed-off-by: Michael Over <michael.over@mimacom.com>
Co-authored-by: Michael Over <michael.over@mimacom.com>
2023-11-15 11:36:13 -05:00
Blake Pettersson
d4c37e2521 fix: check for double definition (#16335)
A simpler fix - don't add a managed namespace to the targetObjs list if
a namespace already exists in the application source.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-11-14 15:17:20 -05:00
Michael Crenshaw
ba60fadd94 docs: fix upgrade instructions (#16327)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-11-14 13:38:50 -05:00
github-actions[bot]
58b04e5e11 Bump version to 2.9.1 (#16325)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-11-14 10:07:30 -05:00
Ishita Sequeira
3acd5ee30d fix(appset): ignoreApplicationDifferences not working (#15965) (#16299)
* fix(appset): ignoreApplicationDifferences not working



* tests, docs



* link to enhancement request



* handle error



* Update docs/operator-manual/applicationset/Controlling-Resource-Modification.md




* fix bug, fix docs



* fix docs



* normalize empty syncPolicy field



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Hugues Peccatte <hugues.peccatte@gmail.com>
2023-11-13 15:21:36 -05:00
gcp-cherry-pick-bot[bot]
45d5de702e fix: check for double definition (#15670) (#16271)
* fix: check for double definition

As found in #13965 (and as a follow-up to #13999), we also need to
define what happens if _both_ managedNamespaceMetadata _and_ an
Application manifest are both defined for the same namespace.

The idea here is that if that happens, we emit an
`ApplicationConditionRepeatedResourceWarning`, and set the sync status
to `Unknown`, since it's unclear what is supposed to happen.

The user will then have the option of removing one of the two
definitions.



* fix: check for double definition

A simpler fix - don't add a managed namespace to the targetObjs list if
a namespace already exists in the application source.



* test: add test cases

This adds a test case showing that an ns manifest will override
`managedNamespaceMetadata` without failing horribly (as it currently
does on `HEAD`), as well as a "standard" mNMd diff vs live.



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-11-09 17:30:21 -05:00
gcp-cherry-pick-bot[bot]
b8efc8b1ab feat(appset): fromYaml, fromYamlArray toYaml functions (#15063) (#16289)
* feat(appset): fromYaml, fromYamlArray toYaml functions



* fix(11993): Document and error messages



---------

Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-11-09 00:30:15 -05:00
35 changed files with 1091 additions and 324 deletions

View File

@@ -1 +1 @@
2.9.0
2.9.2

View File

@@ -16,7 +16,6 @@ package controllers
import (
"context"
"encoding/json"
"fmt"
"reflect"
"time"
@@ -25,7 +24,6 @@ import (
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
@@ -46,7 +44,6 @@ import (
"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common"
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/glob"
@@ -626,7 +623,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
},
}
action, err := utils.CreateOrUpdate(ctx, r.Client, found, func() error {
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, found, func() error {
// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
found.Spec = generatedApp.Spec
@@ -679,13 +676,6 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
found.ObjectMeta.Finalizers = generatedApp.Finalizers
found.ObjectMeta.Labels = generatedApp.Labels
if found != nil && len(found.Spec.IgnoreDifferences) > 0 {
err := applyIgnoreDifferences(applicationSet.Spec.IgnoreApplicationDifferences, found, generatedApp)
if err != nil {
return fmt.Errorf("failed to apply ignore differences: %w", err)
}
}
return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)
})
@@ -703,54 +693,6 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
return firstError
}
// applyIgnoreDifferences applies the ignore differences rules to the found application. It modifies the found application in place.
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp argov1alpha1.Application) error {
diffConfig, err := argodiff.NewDiffConfigBuilder().
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false).
WithNoCache().
Build()
if err != nil {
return fmt.Errorf("failed to build diff config: %w", err)
}
unstructuredFound, err := appToUnstructured(found)
if err != nil {
return fmt.Errorf("failed to convert found application to unstructured: %w", err)
}
unstructuredGenerated, err := appToUnstructured(&generatedApp)
if err != nil {
return fmt.Errorf("failed to convert found application to unstructured: %w", err)
}
result, err := argodiff.Normalize([]*unstructured.Unstructured{unstructuredFound}, []*unstructured.Unstructured{unstructuredGenerated}, diffConfig)
if err != nil {
return fmt.Errorf("failed to normalize application spec: %w", err)
}
if len(result.Targets) != 1 {
return fmt.Errorf("expected 1 normalized application, got %d", len(result.Targets))
}
jsonNormalized, err := json.Marshal(result.Targets[0].Object)
if err != nil {
return fmt.Errorf("failed to marshal normalized app to json: %w", err)
}
err = json.Unmarshal(jsonNormalized, &found)
if err != nil {
return fmt.Errorf("failed to unmarshal normalized app json to structured app: %w", err)
}
// Prohibit jq queries from mutating silly things.
found.TypeMeta = generatedApp.TypeMeta
found.Name = generatedApp.Name
found.Namespace = generatedApp.Namespace
found.Operation = generatedApp.Operation
return nil
}
func appToUnstructured(app *argov1alpha1.Application) (*unstructured.Unstructured, error) {
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(app)
if err != nil {
return nil, fmt.Errorf("failed to convert app object to unstructured: %w", err)
}
return &unstructured.Unstructured{Object: u}, nil
}
// createInCluster will filter from the desiredApplications only the application that needs to be created
// Then it will call createOrUpdateInCluster to do the actual create
func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
@@ -904,7 +846,11 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
if len(newFinalizers) != len(app.Finalizers) {
updated := app.DeepCopy()
updated.Finalizers = newFinalizers
if err := r.Client.Patch(ctx, updated, client.MergeFrom(app)); err != nil {
patch := client.MergeFrom(app)
if log.IsLevelEnabled(log.DebugLevel) {
utils.LogPatch(appLog, patch, updated)
}
if err := r.Client.Patch(ctx, updated, patch); err != nil {
return fmt.Errorf("error updating finalizers: %w", err)
}
r.updateCache(ctx, updated, appLog)

View File

@@ -12,8 +12,6 @@ import (
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -981,6 +979,296 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "Ensure that ignored targetRevision difference doesn't cause an update, even if another field changes",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.source.targetRevision"}},
},
Template: v1alpha1.ApplicationSetTemplate{
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://git.example.com/test-org/test-repo.git",
TargetRevision: "foo",
},
},
},
},
},
existingApps: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://git.example.com/test-org/test-repo.git",
TargetRevision: "bar",
},
},
},
},
desiredApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://git.example.com/test-org/test-repo.git",
// The targetRevision is ignored, so this should not be updated.
TargetRevision: "foo",
// This should be updated.
Helm: &v1alpha1.ApplicationSourceHelm{
Parameters: []v1alpha1.HelmParameter{
{Name: "hi", Value: "there"},
},
},
},
},
},
},
expected: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "3",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://git.example.com/test-org/test-repo.git",
// This is the existing value from the cluster, which should not be updated because the field is ignored.
TargetRevision: "bar",
// This was missing on the cluster, so it should be added.
Helm: &v1alpha1.ApplicationSourceHelm{
Parameters: []v1alpha1.HelmParameter{
{Name: "hi", Value: "there"},
},
},
},
},
},
},
}, {
// For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799
name: "ignore parameters added to a multi-source app in the cluster",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
},
Template: v1alpha1.ApplicationSetTemplate{
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
},
},
},
},
},
},
},
existingApps: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
Parameters: []v1alpha1.HelmParameter{
{Name: "hi", Value: "there"},
},
},
},
},
},
},
},
desiredApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
},
},
},
},
},
},
expected: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
// This should not be updated, because reconciliation shouldn't modify the App.
ResourceVersion: "2",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
Parameters: []v1alpha1.HelmParameter{
// This existed only in the cluster, but it shouldn't be removed, because the field is ignored.
{Name: "hi", Value: "there"},
},
},
},
},
},
},
},
}, {
name: "Demonstrate limitation of MergePatch", // Maybe we can fix this in Argo CD 3.0: https://github.com/argoproj/argo-cd/issues/15975
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
},
Template: v1alpha1.ApplicationSetTemplate{
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "new: values",
},
},
},
},
},
},
},
existingApps: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
Parameters: []v1alpha1.HelmParameter{
{Name: "hi", Value: "there"},
},
},
},
},
},
},
},
desiredApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "new: values",
},
},
},
},
},
},
expected: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "3",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "new: values",
// The Parameters field got blown away, because the values field changed. MergePatch
// doesn't merge list items, it replaces the whole list if an item changes.
// If we eventually add a `name` field to Sources, we can use StrategicMergePatch.
},
},
},
},
},
},
},
} {
@@ -1014,7 +1302,6 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
}, got)
err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
assert.Nil(t, err)
assert.Equal(t, obj, *got)
}
})
@@ -5719,173 +6006,3 @@ func TestOwnsHandler(t *testing.T) {
})
}
}
func Test_applyIgnoreDifferences(t *testing.T) {
appMeta := metav1.TypeMeta{
APIVersion: v1alpha1.ApplicationSchemaGroupVersionKind.GroupVersion().String(),
Kind: v1alpha1.ApplicationSchemaGroupVersionKind.Kind,
}
testCases := []struct {
name string
ignoreDifferences v1alpha1.ApplicationSetIgnoreDifferences
foundApp string
generatedApp string
expectedApp string
}{
{
name: "empty ignoreDifferences",
foundApp: `
spec: {}`,
generatedApp: `
spec: {}`,
expectedApp: `
spec: {}`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "ignore target revision with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.source.targetRevision"}},
},
foundApp: `
spec:
source:
targetRevision: foo`,
generatedApp: `
spec:
source:
targetRevision: bar`,
expectedApp: `
spec:
source:
targetRevision: foo`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1103593714
name: "ignore helm parameter with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.source.helm.parameters | select(.name == "image.tag")`}},
},
foundApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: test
- name: another
value: value`,
generatedApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: v1.0.0
- name: another
value: value`,
expectedApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: test
- name: another
value: value`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "ignore auto-sync with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.syncPolicy.automated"}},
},
foundApp: `
spec:
syncPolicy:
retry:
limit: 5`,
generatedApp: `
spec:
syncPolicy:
automated:
selfHeal: true
retry:
limit: 5`,
expectedApp: `
spec:
syncPolicy:
retry:
limit: 5`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1420656537
name: "ignore a one-off annotation with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.metadata.annotations | select(.["foo.bar"] == "baz")`}},
},
foundApp: `
metadata:
annotations:
foo.bar: baz
some.other: annotation`,
generatedApp: `
metadata:
annotations:
some.other: annotation`,
expectedApp: `
metadata:
annotations:
foo.bar: baz
some.other: annotation`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1515672638
name: "ignore the source.plugin field with a json pointer",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JSONPointers: []string{"/spec/source/plugin"}},
},
foundApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com`,
generatedApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com/wrong`,
expectedApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com`,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
foundApp := v1alpha1.Application{TypeMeta: appMeta}
err := yaml.Unmarshal([]byte(tc.foundApp), &foundApp)
require.NoError(t, err, tc.foundApp)
generatedApp := v1alpha1.Application{TypeMeta: appMeta}
err = yaml.Unmarshal([]byte(tc.generatedApp), &generatedApp)
require.NoError(t, err, tc.generatedApp)
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, generatedApp)
require.NoError(t, err)
jsonFound, err := json.Marshal(tc.foundApp)
require.NoError(t, err)
jsonExpected, err := json.Marshal(tc.expectedApp)
require.NoError(t, err)
assert.Equal(t, string(jsonExpected), string(jsonFound))
})
}
}

View File

@@ -2,19 +2,24 @@ package utils
import (
"context"
"encoding/json"
"fmt"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
)
// CreateOrUpdate overrides "sigs.k8s.io/controller-runtime" function
@@ -30,7 +35,7 @@ import (
// The MutateFn is called regardless of creating or updating an object.
//
// It returns the executed operation and an error.
func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ignoreAppDifferences argov1alpha1.ApplicationSetIgnoreDifferences, obj *argov1alpha1.Application, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
key := client.ObjectKeyFromObject(obj)
if err := c.Get(ctx, key, obj); err != nil {
@@ -46,15 +51,24 @@ func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f c
return controllerutil.OperationResultCreated, nil
}
existingObj := obj.DeepCopyObject()
existing, ok := existingObj.(client.Object)
if !ok {
panic(fmt.Errorf("existing object is not a client.Object"))
}
normalizedLive := obj.DeepCopy()
// Mutate the live object to match the desired state.
if err := mutate(f, key, obj); err != nil {
return controllerutil.OperationResultNone, err
}
// Apply ignoreApplicationDifferences rules to remove ignored fields from both the live and the desired state. This
// prevents those differences from appearing in the diff and therefore in the patch.
err := applyIgnoreDifferences(ignoreAppDifferences, normalizedLive, obj)
if err != nil {
return controllerutil.OperationResultNone, fmt.Errorf("failed to apply ignore differences: %w", err)
}
// Normalize to avoid diffing on unimportant differences.
normalizedLive.Spec = *argo.NormalizeApplicationSpec(&normalizedLive.Spec)
obj.Spec = *argo.NormalizeApplicationSpec(&obj.Spec)
equality := conversion.EqualitiesOrDie(
func(a, b resource.Quantity) bool {
// Ignore formatting, only care that numeric value stayed the same.
@@ -79,23 +93,35 @@ func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f c
return a.Namespace == b.Namespace && a.Name == b.Name && a.Server == b.Server
},
)
// make sure updated object has the same apiVersion & kind as original object
if objKind, ok := obj.(schema.ObjectKind); ok {
if existingKind, ok := existing.(schema.ObjectKind); ok {
existingKind.SetGroupVersionKind(objKind.GroupVersionKind())
}
}
if equality.DeepEqual(existing, obj) {
if equality.DeepEqual(normalizedLive, obj) {
return controllerutil.OperationResultNone, nil
}
if err := c.Patch(ctx, obj, client.MergeFrom(existing)); err != nil {
patch := client.MergeFrom(normalizedLive)
if log.IsLevelEnabled(log.DebugLevel) {
LogPatch(logCtx, patch, obj)
}
if err := c.Patch(ctx, obj, patch); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultUpdated, nil
}
func LogPatch(logCtx *log.Entry, patch client.Patch, obj *argov1alpha1.Application) {
patchBytes, err := patch.Data(obj)
if err != nil {
logCtx.Errorf("failed to generate patch: %v", err)
}
// Get the patch as a plain object so it is easier to work with in json logs.
var patchObj map[string]interface{}
err = json.Unmarshal(patchBytes, &patchObj)
if err != nil {
logCtx.Errorf("failed to unmarshal patch: %v", err)
}
logCtx.WithField("patch", patchObj).Debug("patching application")
}
// mutate wraps a MutateFn and applies validation to its result
func mutate(f controllerutil.MutateFn, key client.ObjectKey, obj client.Object) error {
if err := f(); err != nil {
@@ -106,3 +132,71 @@ func mutate(f controllerutil.MutateFn, key client.ObjectKey, obj client.Object)
}
return nil
}
// applyIgnoreDifferences applies the ignore differences rules to the found application. It modifies the applications in place.
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp *argov1alpha1.Application) error {
if len(applicationSetIgnoreDifferences) == 0 {
return nil
}
generatedAppCopy := generatedApp.DeepCopy()
diffConfig, err := argodiff.NewDiffConfigBuilder().
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false).
WithNoCache().
Build()
if err != nil {
return fmt.Errorf("failed to build diff config: %w", err)
}
unstructuredFound, err := appToUnstructured(found)
if err != nil {
return fmt.Errorf("failed to convert found application to unstructured: %w", err)
}
unstructuredGenerated, err := appToUnstructured(generatedApp)
if err != nil {
return fmt.Errorf("failed to convert found application to unstructured: %w", err)
}
result, err := argodiff.Normalize([]*unstructured.Unstructured{unstructuredFound}, []*unstructured.Unstructured{unstructuredGenerated}, diffConfig)
if err != nil {
return fmt.Errorf("failed to normalize application spec: %w", err)
}
if len(result.Lives) != 1 {
return fmt.Errorf("expected 1 normalized application, got %d", len(result.Lives))
}
foundJsonNormalized, err := json.Marshal(result.Lives[0].Object)
if err != nil {
return fmt.Errorf("failed to marshal normalized app to json: %w", err)
}
foundNormalized := &argov1alpha1.Application{}
err = json.Unmarshal(foundJsonNormalized, &foundNormalized)
if err != nil {
return fmt.Errorf("failed to unmarshal normalized app to json: %w", err)
}
if len(result.Targets) != 1 {
return fmt.Errorf("expected 1 normalized application, got %d", len(result.Targets))
}
foundNormalized.DeepCopyInto(found)
generatedJsonNormalized, err := json.Marshal(result.Targets[0].Object)
if err != nil {
return fmt.Errorf("failed to marshal normalized app to json: %w", err)
}
generatedAppNormalized := &argov1alpha1.Application{}
err = json.Unmarshal(generatedJsonNormalized, &generatedAppNormalized)
if err != nil {
return fmt.Errorf("failed to unmarshal normalized app json to structured app: %w", err)
}
generatedAppNormalized.DeepCopyInto(generatedApp)
// Prohibit jq queries from mutating silly things.
generatedApp.TypeMeta = generatedAppCopy.TypeMeta
generatedApp.Name = generatedAppCopy.Name
generatedApp.Namespace = generatedAppCopy.Namespace
generatedApp.Operation = generatedAppCopy.Operation
return nil
}
func appToUnstructured(app client.Object) (*unstructured.Unstructured, error) {
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(app)
if err != nil {
return nil, fmt.Errorf("failed to convert app object to unstructured: %w", err)
}
return &unstructured.Unstructured{Object: u}, nil
}

View File

@@ -0,0 +1,234 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func Test_applyIgnoreDifferences(t *testing.T) {
appMeta := metav1.TypeMeta{
APIVersion: v1alpha1.ApplicationSchemaGroupVersionKind.GroupVersion().String(),
Kind: v1alpha1.ApplicationSchemaGroupVersionKind.Kind,
}
testCases := []struct {
name string
ignoreDifferences v1alpha1.ApplicationSetIgnoreDifferences
foundApp string
generatedApp string
expectedApp string
}{
{
name: "empty ignoreDifferences",
foundApp: `
spec: {}`,
generatedApp: `
spec: {}`,
expectedApp: `
spec: {}`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "ignore target revision with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.source.targetRevision"}},
},
foundApp: `
spec:
source:
targetRevision: foo`,
generatedApp: `
spec:
source:
targetRevision: bar`,
expectedApp: `
spec:
source:
targetRevision: foo`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1103593714
name: "ignore helm parameter with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.source.helm.parameters | select(.name == "image.tag")`}},
},
foundApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: test
- name: another
value: value`,
generatedApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: v1.0.0
- name: another
value: value`,
expectedApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: test
- name: another
value: value`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "ignore auto-sync in appset when it's not in the cluster with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.syncPolicy.automated"}},
},
foundApp: `
spec:
syncPolicy:
retry:
limit: 5`,
generatedApp: `
spec:
syncPolicy:
automated:
selfHeal: true
retry:
limit: 5`,
expectedApp: `
spec:
syncPolicy:
retry:
limit: 5`,
},
{
name: "ignore auto-sync in the cluster when it's not in the appset with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.syncPolicy.automated"}},
},
foundApp: `
spec:
syncPolicy:
automated:
selfHeal: true
retry:
limit: 5`,
generatedApp: `
spec:
syncPolicy:
retry:
limit: 5`,
expectedApp: `
spec:
syncPolicy:
automated:
selfHeal: true
retry:
limit: 5`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1420656537
name: "ignore a one-off annotation with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.metadata.annotations | select(.["foo.bar"] == "baz")`}},
},
foundApp: `
metadata:
annotations:
foo.bar: baz
some.other: annotation`,
generatedApp: `
metadata:
annotations:
some.other: annotation`,
expectedApp: `
metadata:
annotations:
foo.bar: baz
some.other: annotation`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1515672638
name: "ignore the source.plugin field with a json pointer",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JSONPointers: []string{"/spec/source/plugin"}},
},
foundApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com`,
generatedApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com/wrong`,
expectedApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799
name: "ignore parameters added to a multi-source app in the cluster",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
},
foundApp: `
spec:
sources:
- repoURL: https://git.example.com/test-org/test-repo
helm:
parameters:
- name: test
value: hi`,
generatedApp: `
spec:
sources:
- repoURL: https://git.example.com/test-org/test-repo`,
expectedApp: `
spec:
sources:
- repoURL: https://git.example.com/test-org/test-repo
helm:
parameters:
- name: test
value: hi`,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
foundApp := v1alpha1.Application{TypeMeta: appMeta}
err := yaml.Unmarshal([]byte(tc.foundApp), &foundApp)
require.NoError(t, err, tc.foundApp)
generatedApp := v1alpha1.Application{TypeMeta: appMeta}
err = yaml.Unmarshal([]byte(tc.generatedApp), &generatedApp)
require.NoError(t, err, tc.generatedApp)
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, &generatedApp)
require.NoError(t, err)
yamlFound, err := yaml.Marshal(tc.foundApp)
require.NoError(t, err)
yamlExpected, err := yaml.Marshal(tc.expectedApp)
require.NoError(t, err)
assert.Equal(t, string(yamlExpected), string(yamlFound))
})
}
}

View File

@@ -0,0 +1,71 @@
package utils
import (
"regexp"
"strings"
"sigs.k8s.io/yaml"
)
// SanitizeName sanitizes the name in accordance with the below rules
// 1. contain no more than 253 characters
// 2. contain only lowercase alphanumeric characters, '-' or '.'
// 3. start and end with an alphanumeric character
func SanitizeName(name string) string {
invalidDNSNameChars := regexp.MustCompile("[^-a-z0-9.]")
maxDNSNameLength := 253
name = strings.ToLower(name)
name = invalidDNSNameChars.ReplaceAllString(name, "-")
if len(name) > maxDNSNameLength {
name = name[:maxDNSNameLength]
}
return strings.Trim(name, "-.")
}
// This has been copied from helm and may be removed as soon as it is retrofited in sprig
// toYAML takes an interface, marshals it to yaml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func toYAML(v interface{}) (string, error) {
data, err := yaml.Marshal(v)
if err != nil {
// Swallow errors inside of a template.
return "", err
}
return strings.TrimSuffix(string(data), "\n"), nil
}
// This has been copied from helm and may be removed as soon as it is retrofited in sprig
// fromYAML converts a YAML document into a map[string]interface{}.
//
// This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map.
func fromYAML(str string) (map[string]interface{}, error) {
m := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(str), &m); err != nil {
return nil, err
}
return m, nil
}
// This has been copied from helm and may be removed as soon as it is retrofited in sprig
// fromYAMLArray converts a YAML array into a []interface{}.
//
// This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string as
// the first and only item in the returned array.
func fromYAMLArray(str string) ([]interface{}, error) {
a := []interface{}{}
if err := yaml.Unmarshal([]byte(str), &a); err != nil {
return nil, err
}
return a, nil
}

View File

@@ -32,6 +32,9 @@ func init() {
delete(sprigFuncMap, "expandenv")
delete(sprigFuncMap, "getHostByName")
sprigFuncMap["normalize"] = SanitizeName
sprigFuncMap["toYaml"] = toYAML
sprigFuncMap["fromYaml"] = fromYAML
sprigFuncMap["fromYamlArray"] = fromYAMLArray
}
type Renderer interface {
@@ -431,23 +434,6 @@ func NormalizeBitbucketBasePath(basePath string) string {
return basePath
}
// SanitizeName sanitizes the name in accordance with the below rules
// 1. contain no more than 253 characters
// 2. contain only lowercase alphanumeric characters, '-' or '.'
// 3. start and end with an alphanumeric character
func SanitizeName(name string) string {
invalidDNSNameChars := regexp.MustCompile("[^-a-z0-9.]")
maxDNSNameLength := 253
name = strings.ToLower(name)
name = invalidDNSNameChars.ReplaceAllString(name, "-")
if len(name) > maxDNSNameLength {
name = name[:maxDNSNameLength]
}
return strings.Trim(name, "-.")
}
func getTlsConfigWithCACert(scmRootCAPath string) *tls.Config {
tlsConfig := &tls.Config{}

View File

@@ -555,6 +555,64 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
templateOptions: []string{"missingkey=error"},
errorMessage: `failed to execute go template --> {{.doesnotexist}} <--: template: :1:6: executing "" at <.doesnotexist>: map has no entry for key "doesnotexist"`,
},
{
name: "toYaml",
fieldVal: `{{ toYaml . | indent 2 }}`,
expectedVal: " foo:\n bar:\n bool: true\n number: 2\n str: Hello world",
params: map[string]interface{}{
"foo": map[string]interface{}{
"bar": map[string]interface{}{
"bool": true,
"number": 2,
"str": "Hello world",
},
},
},
},
{
name: "toYaml Error",
fieldVal: `{{ toYaml . | indent 2 }}`,
expectedVal: " foo:\n bar:\n bool: true\n number: 2\n str: Hello world",
errorMessage: "failed to execute go template {{ toYaml . | indent 2 }}: template: :1:3: executing \"\" at <toYaml .>: error calling toYaml: error marshaling into JSON: json: unsupported type: func(*string)",
params: map[string]interface{}{
"foo": func(test *string) {
},
},
},
{
name: "fromYaml",
fieldVal: `{{ get (fromYaml .value) "hello" }}`,
expectedVal: "world",
params: map[string]interface{}{
"value": "hello: world",
},
},
{
name: "fromYaml error",
fieldVal: `{{ get (fromYaml .value) "hello" }}`,
expectedVal: "world",
errorMessage: "failed to execute go template {{ get (fromYaml .value) \"hello\" }}: template: :1:8: executing \"\" at <fromYaml .value>: error calling fromYaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}",
params: map[string]interface{}{
"value": "non\n compliant\n yaml",
},
},
{
name: "fromYamlArray",
fieldVal: `{{ fromYamlArray .value | last }}`,
expectedVal: "bonjour tout le monde",
params: map[string]interface{}{
"value": "- hello world\n- bonjour tout le monde",
},
},
{
name: "fromYamlArray error",
fieldVal: `{{ fromYamlArray .value | last }}`,
expectedVal: "bonjour tout le monde",
errorMessage: "failed to execute go template {{ fromYamlArray .value | last }}: template: :1:3: executing \"\" at <fromYamlArray .value>: error calling fromYamlArray: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type []interface {}",
params: map[string]interface{}{
"value": "non\n compliant\n yaml",
},
},
}
for _, test := range tests {

View File

@@ -391,6 +391,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
now := metav1.Now()
var manifestInfos []*apiclient.ManifestResponse
targetNsExists := false
if len(localManifests) == 0 {
// If the length of revisions is not same as the length of sources,
@@ -453,6 +454,13 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
LastTransitionTime: &now,
})
}
// If we reach this path, this means that a namespace has been both defined in Git, as well in the
// application's managedNamespaceMetadata. We want to ensure that this manifest is the one being used instead
// of what is present in managedNamespaceMetadata.
if isManagedNamespace(targetObj, app) {
targetNsExists = true
}
}
ts.AddCheckpoint("dedup_ms")
@@ -511,7 +519,10 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
// entry in source control. In order for the namespace not to risk being pruned, we'll need to generate a
// namespace which we can compare the live namespace with. For that, we'll do the same as is done in
// gitops-engine, the difference here being that we create a managed namespace which is only used for comparison.
if isManagedNamespace(liveObj, app) {
//
// targetNsExists == true implies that it already exists as a target, so no need to add the namespace to the
// targetObjs array.
if isManagedNamespace(liveObj, app) && !targetNsExists {
nsSpec := &v1.Namespace{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: kubeutil.NamespaceKind}, ObjectMeta: metav1.ObjectMeta{Name: liveObj.GetName()}}
managedNs, err := kubeutil.ToUnstructured(nsSpec)

View File

@@ -89,6 +89,122 @@ func TestCompareAppStateNamespaceMetadataDiffers(t *testing.T) {
assert.Len(t, app.Status.Conditions, 0)
}
// TestCompareAppStateNamespaceMetadataDiffers tests comparison when managed namespace metadata differs to live and manifest ns
func TestCompareAppStateNamespaceMetadataDiffersToManifest(t *testing.T) {
ns := NewNamespace()
ns.SetName(test.FakeDestNamespace)
ns.SetNamespace(test.FakeDestNamespace)
ns.SetAnnotations(map[string]string{"bar": "bat"})
app := newFakeApp()
app.Spec.SyncPolicy.ManagedNamespaceMetadata = &argoappv1.ManagedNamespaceMetadata{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
},
}
app.Status.OperationState = &argoappv1.OperationState{
SyncResult: &argoappv1.SyncOperationResult{},
}
liveNs := ns.DeepCopy()
liveNs.SetAnnotations(nil)
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{toJSON(t, liveNs)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(ns): ns,
},
}
ctrl := newFakeController(&data)
sources := make([]argoappv1.ApplicationSource, 0)
sources = append(sources, app.Spec.GetSource())
revisions := make([]string, 0)
revisions = append(revisions, "")
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 1)
assert.Len(t, compRes.managedResources, 1)
assert.NotNil(t, compRes.diffResultList)
assert.Len(t, compRes.diffResultList.Diffs, 1)
result := NewNamespace()
assert.NoError(t, json.Unmarshal(compRes.diffResultList.Diffs[0].PredictedLive, result))
labels := result.GetLabels()
delete(labels, "kubernetes.io/metadata.name")
assert.Equal(t, map[string]string{}, labels)
// Manifests override definitions in managedNamespaceMetadata
assert.Equal(t, map[string]string{"bar": "bat"}, result.GetAnnotations())
assert.Len(t, app.Status.Conditions, 0)
}
// TestCompareAppStateNamespaceMetadata tests comparison when managed namespace metadata differs to live
func TestCompareAppStateNamespaceMetadata(t *testing.T) {
ns := NewNamespace()
ns.SetName(test.FakeDestNamespace)
ns.SetNamespace(test.FakeDestNamespace)
ns.SetAnnotations(map[string]string{"bar": "bat"})
app := newFakeApp()
app.Spec.SyncPolicy.ManagedNamespaceMetadata = &argoappv1.ManagedNamespaceMetadata{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
},
}
app.Status.OperationState = &argoappv1.OperationState{
SyncResult: &argoappv1.SyncOperationResult{},
}
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(ns): ns,
},
}
ctrl := newFakeController(&data)
sources := make([]argoappv1.ApplicationSource, 0)
sources = append(sources, app.Spec.GetSource())
revisions := make([]string, 0)
revisions = append(revisions, "")
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 1)
assert.Len(t, compRes.managedResources, 1)
assert.NotNil(t, compRes.diffResultList)
assert.Len(t, compRes.diffResultList.Diffs, 1)
result := NewNamespace()
assert.NoError(t, json.Unmarshal(compRes.diffResultList.Diffs[0].PredictedLive, result))
labels := result.GetLabels()
delete(labels, "kubernetes.io/metadata.name")
assert.Equal(t, map[string]string{"foo": "bar"}, labels)
assert.Equal(t, map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true", "bar": "bat", "foo": "bar"}, result.GetAnnotations())
assert.Len(t, app.Status.Conditions, 0)
}
// TestCompareAppStateNamespaceMetadataIsTheSame tests comparison when managed namespace metadata is the same
func TestCompareAppStateNamespaceMetadataIsTheSame(t *testing.T) {
app := newFakeApp()

View File

@@ -33,6 +33,6 @@ spec:
- jsonPointers:
- /spec/source/targetRevision
- name: some-app
jqExpressions:
jqPathExpressions:
- .spec.source.helm.values

View File

@@ -6,7 +6,7 @@ These settings allow you to exert control over when, and how, changes are made t
Here are some of the controller settings that may be modified to alter the ApplicationSet controller's resource-handling behaviour.
### Dry run: prevent ApplicationSet from creating, modifying, or deleting all Applications
## Dry run: prevent ApplicationSet from creating, modifying, or deleting all Applications
To prevent the ApplicationSet controller from creating, modifying, or deleting any `Application` resources, you may enable `dry-run` mode. This essentially switches the controller into a "read only" mode, where the controller Reconcile loop will run, but no resources will be modified.
@@ -14,7 +14,7 @@ To enable dry-run, add `--dryrun true` to the ApplicationSet Deployment's contai
See 'How to modify ApplicationSet container parameters' below for detailed steps on how to add this parameter to the controller.
### Managed Applications modification Policies
## Managed Applications modification Policies
The ApplicationSet controller supports a parameter `--policy`, which is specified on launch (within the controller Deployment container), and which restricts what types of modifications will be made to managed Argo CD `Application` resources.
@@ -41,7 +41,7 @@ If the controller parameter `--policy` is set, it takes precedence on the field
This does not prevent deletion of Applications if the ApplicationSet is deleted
#### Controller parameter
### Controller parameter
To allow the ApplicationSet controller to *create* `Application` resources, but prevent any further modification, such as deletion, or modification of Application fields, add this parameter in the ApplicationSet controller:
```
@@ -59,7 +59,7 @@ spec:
applicationsSync: create-only
```
### Policy - `create-update`: Prevent ApplicationSet controller from deleting Applications
## Policy - `create-update`: Prevent ApplicationSet controller from deleting Applications
To allow the ApplicationSet controller to create or modify `Application` resources, but prevent Applications from being deleted, add the following parameter to the ApplicationSet controller `Deployment`:
```
@@ -79,7 +79,7 @@ spec:
applicationsSync: create-update
```
### Ignore certain changes to Applications
## Ignore certain changes to Applications
The ApplicationSet spec includes an `ignoreApplicationDifferences` field, which allows you to specify which fields of
the ApplicationSet should be ignored when comparing Applications.
@@ -98,11 +98,94 @@ spec:
- jsonPointers:
- /spec/source/targetRevision
- name: some-app
jqExpressions:
jqPathExpressions:
- .spec.source.helm.values
```
### Prevent an `Application`'s child resources from being deleted, when the parent Application is deleted
### Allow temporarily toggling auto-sync
One of the most common use cases for ignoring differences is to allow temporarily toggling auto-sync for an Application.
For example, if you have an ApplicationSet that is configured to automatically sync Applications, you may want to temporarily
disable auto-sync for a specific Application. You can do this by adding an ignore rule for the `spec.syncPolicy.automated` field.
```yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
spec:
ignoreApplicationDifferences:
- jsonPointers:
- /spec/syncPolicy
```
### Limitations of `ignoreApplicationDifferences`
When an ApplicationSet is reconciled, the controller will compare the ApplicationSet spec with the spec of each Application
that it manages. If there are any differences, the controller will generate a patch to update the Application to match the
ApplicationSet spec.
The generated patch is a MergePatch. According to the MergePatch documentation, "existing lists will be completely
replaced by new lists" when there is a change to the list.
This limits the effectiveness of `ignoreApplicationDifferences` when the ignored field is in a list. For example, if you
have an application with multiple sources, and you want to ignore changes to the `targetRevision` of one of the sources,
changes in other fields or in other sources will cause the entire `sources` list to be replaced, and the `targetRevision`
field will be reset to the value defined in the ApplicationSet.
For example, consider this ApplicationSet:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
spec:
ignoreApplicationDifferences:
- jqPathExpressions:
- .spec.sources[] | select(.repoURL == "https://git.example.com/org/repo1").targetRevision
template:
spec:
sources:
- repoURL: https://git.example.com/org/repo1
targetRevision: main
- repoURL: https://git.example.com/org/repo2
targetRevision: main
```
You can freely change the `targetRevision` of the `repo1` source, and the ApplicationSet controller will not overwrite
your change.
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
sources:
- repoURL: https://git.example.com/org/repo1
targetRevision: fix/bug-123
- repoURL: https://git.example.com/org/repo2
targetRevision: main
```
However, if you change the `targetRevision` of the `repo2` source, the ApplicationSet controller will overwrite the entire
`sources` field.
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
sources:
- repoURL: https://git.example.com/org/repo1
targetRevision: main
- repoURL: https://git.example.com/org/repo2
targetRevision: main
```
!!! note
[Future improvements](https://github.com/argoproj/argo-cd/issues/15975) to the ApplicationSet controller may
eliminate this problem. For example, the `ref` field might be made a merge key, allowing the ApplicationSet
controller to generate and use a StrategicMergePatch instead of a MergePatch. You could then target a specific
source by `ref`, ignore changes to a field in that source, and changes to other sources would not cause the ignored
field to be overwritten.
## Prevent an `Application`'s child resources from being deleted, when the parent Application is deleted
By default, when an `Application` resource is deleted by the ApplicationSet controller, all of the child resources of the Application will be deleted as well (such as, all of the Application's `Deployments`, `Services`, etc).
@@ -119,7 +202,7 @@ spec:
More information on the specific behaviour of `preserveResourcesOnDeletion`, and deletion in ApplicationSet controller and Argo CD in general, can be found on the [Application Deletion](Application-Deletion.md) page.
### Prevent an Application's child resources from being modified
## Prevent an Application's child resources from being modified
Changes made to the ApplicationSet will propagate to the Applications managed by the ApplicationSet, and then Argo CD will propagate the Application changes to the underlying cluster resources (as per [Argo CD Integration](Argo-CD-Integration.md)).
@@ -185,6 +268,11 @@ kubectl apply -n argocd -f install.yaml
## Preserving changes made to an Applications annotations and labels
!!! note
The same behavior can be achieved on a per-app basis using the [`ignoreApplicationDifferences`](#ignore-certain-changes-to-applications)
feature described above. However, preserved fields may be configured globally, a feature that is not yet available
for `ignoreApplicationDifferences`.
It is common practice in Kubernetes to store state in annotations, operators will often make use of this. To allow for this, it is possible to configure a list of annotations that the ApplicationSet should preserve when reconciling.
For example, imagine that we have an Application created from an ApplicationSet, but a custom annotation and label has since been added (to the Application) that does not exist in the `ApplicationSet` resource:
@@ -220,3 +308,18 @@ By default, the Argo CD notifications and the Argo CD refresh type annotations a
!!!note
One can also set global preserved fields for the controller by passing a comma separated list of annotations and labels to
`ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_ANNOTATIONS` and `ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_LABELS` respectively.
## Debugging unexpected changes to Applications
When the ApplicationSet controller makes a change to an application, it logs the patch at the debug level. To see these
logs, set the log level to debug in the `argocd-cmd-params-cm` ConfigMap in the `argocd` namespace:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
namespace: argocd
data:
applicationsetcontroller.log.level: debug
```

View File

@@ -174,6 +174,18 @@ It is also possible to use Sprig functions to construct the path variables manua
| `{{path.filenameNormalized}}` | `{{.path.filenameNormalized}}` | `{{normalize .path.filename}}` |
| `{{path[N]}}` | `-` | `{{index .path.segments N}}` |
## Available template functions
ApplicationSet controller provides:
- all [sprig](http://masterminds.github.io/sprig/) Go templates function except `env`, `expandenv` and `getHostByName`
- `normalize`: sanitizes the input so that it complies with the following rules:
1. contains no more than 253 characters
2. contains only lowercase alphanumeric characters, '-' or '.'
3. starts and ends with an alphanumeric character
- `toYaml` / `fromYaml` / `fromYamlArray` helm like functions
## Examples
### Basic Go template usage

View File

@@ -0,0 +1,5 @@
# 2.8 to 2.9
## Upgraded Kustomize Version
Note that bundled Kustomize version has been upgraded from 5.1.0 to 5.2.1.

View File

@@ -37,6 +37,7 @@ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/<v
<hr/>
* [v2.8 to v2.9](./2.8-2.9.md)
* [v2.7 to v2.8](./2.7-2.8.md)
* [v2.6 to v2.7](./2.6-2.7.md)
* [v2.5 to v2.6](./2.5-2.6.md)

8
go.mod
View File

@@ -77,11 +77,11 @@ require (
go.opentelemetry.io/otel v1.16.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0
go.opentelemetry.io/otel/sdk v1.16.0
golang.org/x/crypto v0.13.0
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/oauth2 v0.11.0
golang.org/x/sync v0.3.0
golang.org/x/term v0.12.0
golang.org/x/term v0.13.0
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc
google.golang.org/grpc v1.56.2
google.golang.org/protobuf v1.31.0
@@ -261,8 +261,8 @@ require (
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect

12
go.sum
View File

@@ -2005,8 +2005,9 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -2158,8 +2159,9 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -2351,8 +2353,9 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -2365,8 +2368,9 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -0,0 +1 @@
55a8e6dce87a1e52c61e0ce7a89bf85b38725ba3e8deb51d4a08ade8a2c70b2d helm-v3.13.2-linux-amd64.tar.gz

View File

@@ -0,0 +1 @@
f5654aaed63a0da72852776e1d3f851b2ea9529cb5696337202703c2e1ed2321 helm-v3.13.2-linux-arm64.tar.gz

View File

@@ -0,0 +1 @@
11d96134cc4ec106c23cd8c163072e9aed6cd73e36a3da120e5876d426203f37 helm-v3.13.2-linux-ppc64le.tar.gz

View File

@@ -0,0 +1 @@
3ffc5b4a041e5306dc00905ebe5dfea449e34ada268a713d34c69709afd6a9a2 helm-v3.13.2-linux-s390x.tar.gz

View File

@@ -11,7 +11,7 @@
# Use ./hack/installers/checksums/add-helm-checksums.sh and
# add-kustomize-checksums.sh to help download checksums.
###############################################################################
helm3_version=3.12.1
helm3_version=3.13.2
kubectl_version=1.17.8
kubectx_version=0.6.3
kustomize5_version=5.2.1

View File

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

View File

@@ -20742,7 +20742,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21042,7 +21042,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -21094,7 +21094,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -21313,7 +21313,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

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

View File

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

View File

@@ -21999,7 +21999,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -22122,7 +22122,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -22198,7 +22198,7 @@ spec:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -22529,7 +22529,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -22581,7 +22581,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -22870,7 +22870,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -23116,7 +23116,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -1654,7 +1654,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1777,7 +1777,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1853,7 +1853,7 @@ spec:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2184,7 +2184,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2236,7 +2236,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2525,7 +2525,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2771,7 +2771,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -21094,7 +21094,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21217,7 +21217,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -21293,7 +21293,7 @@ spec:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -21575,7 +21575,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -21627,7 +21627,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -21914,7 +21914,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -22160,7 +22160,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -749,7 +749,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -872,7 +872,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -948,7 +948,7 @@ spec:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1230,7 +1230,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1282,7 +1282,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1569,7 +1569,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1815,7 +1815,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.0
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -123,6 +123,7 @@ nav:
- operator-manual/server-commands/additional-configuration-method.md
- Upgrading:
- operator-manual/upgrading/overview.md
- operator-manual/upgrading/2.8-2.9.md
- operator-manual/upgrading/2.7-2.8.md
- operator-manual/upgrading/2.6-2.7.md
- operator-manual/upgrading/2.5-2.6.md

View File

@@ -1135,11 +1135,12 @@ type SyncPolicy struct {
Retry *RetryStrategy `json:"retry,omitempty" protobuf:"bytes,3,opt,name=retry"`
// ManagedNamespaceMetadata controls metadata in the given namespace (if CreateNamespace=true)
ManagedNamespaceMetadata *ManagedNamespaceMetadata `json:"managedNamespaceMetadata,omitempty" protobuf:"bytes,4,opt,name=managedNamespaceMetadata"`
// If you add a field here, be sure to update IsZero.
}
// IsZero returns true if the sync policy is empty
func (p *SyncPolicy) IsZero() bool {
return p == nil || (p.Automated == nil && len(p.SyncOptions) == 0 && p.Retry == nil)
return p == nil || (p.Automated == nil && len(p.SyncOptions) == 0 && p.Retry == nil && p.ManagedNamespaceMetadata == nil)
}
// RetryStrategy contains information about the strategy to apply when a sync failed

View File

@@ -855,7 +855,9 @@ func NormalizeApplicationSpec(spec *argoappv1.ApplicationSpec) *argoappv1.Applic
if spec.Project == "" {
spec.Project = argoappv1.DefaultAppProjectName
}
if spec.SyncPolicy.IsZero() {
spec.SyncPolicy = nil
}
if spec.Sources != nil && len(spec.Sources) > 0 {
for _, source := range spec.Sources {
NormalizeSource(&source)

View File

@@ -10,6 +10,7 @@ import (
"github.com/argoproj/argo-cd/v2/common"
"github.com/sirupsen/logrus"
"golang.org/x/net/proxy"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
@@ -63,7 +64,7 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
dialer := func(ctx context.Context, address string) (net.Conn, error) {
conn, err := (&net.Dialer{Cancel: ctx.Done()}).Dial(network, address)
conn, err := proxy.Dial(ctx, network, address)
if err != nil {
writeResult(err)
return nil, err

View File

@@ -19,7 +19,7 @@ const maxCookieLength = 4093
// max number of chunks a cookie can be broken into. To be compatible with
// widest range of browsers, you shouldn't create more than 30 cookies per domain
var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 20, 0, math.MaxInt64)
var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 20, 0, math.MaxInt)
// MakeCookieMetadata generates a string representing a Web cookie. Yum!
func MakeCookieMetadata(key, value string, flags ...string) ([]string, error) {