Are you sure you want to delete {resource.kind} {resource.name}?
+
+ Deleting resources can be dangerous. Be sure you understand the effects of deleting this resource before continuing. Consider asking someone to
+ review the change first.
+
{isManaged ? (
diff --git a/util/app/path/path.go b/util/app/path/path.go
index 0ff0b80f0d29d..d2bb166fa1b26 100644
--- a/util/app/path/path.go
+++ b/util/app/path/path.go
@@ -6,7 +6,9 @@ import (
"path/filepath"
"strings"
+ "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/io/files"
+ "github.com/argoproj/argo-cd/v2/util/security"
)
func Path(root, path string) (string, error) {
@@ -88,3 +90,65 @@ func CheckOutOfBoundsSymlinks(basePath string) error {
return nil
})
}
+
+// GetAppRefreshPaths returns the list of paths that should trigger a refresh for an application
+func GetAppRefreshPaths(app *v1alpha1.Application) []string {
+ var paths []string
+ if val, ok := app.Annotations[v1alpha1.AnnotationKeyManifestGeneratePaths]; ok && val != "" {
+ for _, item := range strings.Split(val, ";") {
+ if item == "" {
+ continue
+ }
+ if filepath.IsAbs(item) {
+ paths = append(paths, item[1:])
+ } else {
+ for _, source := range app.Spec.GetSources() {
+ paths = append(paths, filepath.Clean(filepath.Join(source.Path, item)))
+ }
+ }
+ }
+ }
+ return paths
+}
+
+// AppFilesHaveChanged returns true if any of the changed files are under the given refresh paths
+// If refreshPaths is 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
+ if len(changedFiles) == 0 {
+ return false
+ }
+
+ if len(refreshPaths) == 0 {
+ // Apps without a given refreshed paths always be refreshed, regardless of changed files
+ // this is the "default" behavior
+ return true
+ }
+
+ // At last one changed file must be under refresh path
+ for _, f := range changedFiles {
+ f = ensureAbsPath(f)
+ for _, item := range refreshPaths {
+ item = ensureAbsPath(item)
+ changed := false
+ if f == item {
+ changed = true
+ } else if _, err := security.EnforceToCurrentRoot(item, f); err == nil {
+ changed = true
+ }
+ if changed {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func ensureAbsPath(input string) string {
+ if !filepath.IsAbs(input) {
+ return string(filepath.Separator) + input
+ }
+ return input
+}
diff --git a/util/app/path/path_test.go b/util/app/path/path_test.go
index cca37afc971ea..11c746a87f3b6 100644
--- a/util/app/path/path_test.go
+++ b/util/app/path/path_test.go
@@ -8,7 +8,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
fileutil "github.com/argoproj/argo-cd/v2/test/fixture/path"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestPathRoot(t *testing.T) {
@@ -90,3 +92,114 @@ func TestAbsSymlink(t *testing.T) {
assert.ErrorAs(t, err, &oobError)
assert.Equal(t, oobError.File, "abslink")
}
+
+func getApp(annotation string, sourcePath string) *v1alpha1.Application {
+ return &v1alpha1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Annotations: map[string]string{
+ v1alpha1.AnnotationKeyManifestGeneratePaths: annotation,
+ },
+ },
+ Spec: v1alpha1.ApplicationSpec{
+ Source: &v1alpha1.ApplicationSource{
+ Path: sourcePath,
+ },
+ },
+ }
+}
+
+func getMultiSourceApp(annotation string, paths ...string) *v1alpha1.Application {
+ var sources v1alpha1.ApplicationSources
+ for _, path := range paths {
+ sources = append(sources, v1alpha1.ApplicationSource{Path: path})
+ }
+ return &v1alpha1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Annotations: map[string]string{
+ v1alpha1.AnnotationKeyManifestGeneratePaths: annotation,
+ },
+ },
+ Spec: v1alpha1.ApplicationSpec{
+ Sources: sources,
+ },
+ }
+}
+
+func Test_AppFilesHaveChanged(t *testing.T) {
+ tests := []struct {
+ name string
+ app *v1alpha1.Application
+ files []string
+ changeExpected bool
+ }{
+ {"default no path", &v1alpha1.Application{}, []string{"README.md"}, true},
+ {"no files changed", getApp(".", "source/path"), []string{}, false},
+ {"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},
+ {"relative path - not matching", getApp(".", "source/path"), []string{"README.md"}, false},
+ {"relative path, multi source - not matching", getMultiSourceApp(".", "other/path", "unrelated/path"), []string{"README.md"}, false},
+ {"absolute path - matching", getApp("/source/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"absolute path, multi source - matching #1", getMultiSourceApp("/source/path", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"absolute path, multi source - matching #2", getMultiSourceApp("/source/path", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"absolute path - not matching", getApp("/source/path1", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
+ {"absolute path, multi source - not matching", getMultiSourceApp("/source/path1", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
+ {"two relative paths - matching", getApp(".;../shared", "my-app"), []string{"shared/my-deployment.yaml"}, true},
+ {"two relative paths, multi source - matching #1", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"shared/my-deployment.yaml"}, true},
+ {"two relative paths, multi source - matching #2", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"shared/my-deployment.yaml"}, true},
+ {"two relative paths - not matching", getApp(".;../shared", "my-app"), []string{"README.md"}, false},
+ {"two relative paths, multi source - not matching", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"README.md"}, false},
+ {"file relative path - matching", getApp("./my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"file relative path, multi source - matching #1", getMultiSourceApp("./my-deployment.yaml", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"file relative path, multi source - matching #2", getMultiSourceApp("./my-deployment.yaml", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"file relative path - not matching", getApp("./my-deployment.yaml", "source/path"), []string{"README.md"}, false},
+ {"file relative path, multi source - not matching", getMultiSourceApp("./my-deployment.yaml", "source/path", "other/path"), []string{"README.md"}, false},
+ {"file absolute path - matching", getApp("/source/path/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"file absolute path, multi source - matching #1", getMultiSourceApp("/source/path/my-deployment.yaml", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"file absolute path, multi source - matching #2", getMultiSourceApp("/source/path/my-deployment.yaml", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
+ {"file absolute path - not matching", getApp("/source/path1/README.md", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
+ {"file absolute path, multi source - not matching", getMultiSourceApp("/source/path1/README.md", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, false},
+ {"file two relative paths - matching", getApp("./README.md;../shared/my-deployment.yaml", "my-app"), []string{"shared/my-deployment.yaml"}, true},
+ {"file two relative paths, multi source - matching", getMultiSourceApp("./README.md;../shared/my-deployment.yaml", "my-app", "other-path"), []string{"shared/my-deployment.yaml"}, true},
+ {"file two relative paths - not matching", getApp(".README.md;../shared/my-deployment.yaml", "my-app"), []string{"kustomization.yaml"}, false},
+ {"file two relative paths, multi source - not matching", getMultiSourceApp(".README.md;../shared/my-deployment.yaml", "my-app", "other-path"), []string{"kustomization.yaml"}, false},
+ {"changed file absolute path - matching", getApp(".", "source/path"), []string{"/source/path/my-deployment.yaml"}, true},
+ }
+ for _, tt := range tests {
+ ttc := tt
+ t.Run(ttc.name, func(t *testing.T) {
+ t.Parallel()
+ refreshPaths := GetAppRefreshPaths(ttc.app)
+ if got := AppFilesHaveChanged(refreshPaths, ttc.files); got != ttc.changeExpected {
+ t.Errorf("AppFilesHaveChanged() = %v, want %v", got, ttc.changeExpected)
+ }
+ })
+ }
+}
+
+func Test_GetAppRefreshPaths(t *testing.T) {
+ tests := []struct {
+ name string
+ app *v1alpha1.Application
+ expectedPaths []string
+ }{
+ {"default no path", &v1alpha1.Application{}, []string{}},
+ {"relative path", getApp(".", "source/path"), []string{"source/path"}},
+ {"absolute path", getApp("/source/path", "source/path"), []string{"source/path"}},
+ {"absolute path - multi source", getMultiSourceApp("/source/path", "source/path", "other/path"), []string{"source/path"}},
+ {"two relative paths ", getApp(".;../shared", "my-app"), []string{"my-app", "shared"}},
+ {"file relative path", getApp("./my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}},
+ {"file absolute path", getApp("/source/path/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}},
+ {"file two relative paths", getApp("./README.md;../shared/my-deployment.yaml", "my-app"), []string{"my-app/README.md", "shared/my-deployment.yaml"}},
+ {"empty path", getApp(".;", "source/path"), []string{"source/path"}},
+ }
+ for _, tt := range tests {
+ ttc := tt
+ t.Run(ttc.name, func(t *testing.T) {
+ t.Parallel()
+ if got := GetAppRefreshPaths(ttc.app); !assert.ElementsMatch(t, ttc.expectedPaths, got) {
+ t.Errorf("GetAppRefreshPath() = %v, want %v", got, ttc.expectedPaths)
+ }
+ })
+ }
+}
diff --git a/util/cache/redis.go b/util/cache/redis.go
index a6f236093a451..61f1b643ec0bc 100644
--- a/util/cache/redis.go
+++ b/util/cache/redis.go
@@ -97,7 +97,12 @@ func (r *redisCache) unmarshal(data []byte, obj interface{}) error {
}
func (r *redisCache) Rename(oldKey string, newKey string, _ time.Duration) error {
- return r.client.Rename(context.TODO(), r.getKey(oldKey), r.getKey(newKey)).Err()
+ err := r.client.Rename(context.TODO(), r.getKey(oldKey), r.getKey(newKey)).Err()
+ if err != nil && err.Error() == "ERR no such key" {
+ err = ErrCacheMiss
+ }
+
+ return err
}
func (r *redisCache) Set(item *Item) error {
diff --git a/util/config/env.go b/util/config/env.go
index b6679bca7e460..d2007fba6af49 100644
--- a/util/config/env.go
+++ b/util/config/env.go
@@ -1,8 +1,10 @@
package config
import (
+ "encoding/csv"
"errors"
"os"
+ "strconv"
"strings"
"github.com/kballard/go-shellquote"
@@ -46,8 +48,8 @@ func loadFlags() error {
// pkg shellquota doesn't recognize `=` so that the opts in format `foo=bar` could not work.
// issue ref: https://github.com/argoproj/argo-cd/issues/6822
for k, v := range flags {
- if strings.Contains(k, "=") && strings.Count(k, "=") == 1 && v == "true" {
- kv := strings.Split(k, "=")
+ if strings.Contains(k, "=") && v == "true" {
+ kv := strings.SplitN(k, "=", 2)
actualKey, actualValue := kv[0], kv[1]
if _, ok := flags[actualKey]; !ok {
flags[actualKey] = actualValue
@@ -68,3 +70,34 @@ func GetFlag(key, fallback string) string {
func GetBoolFlag(key string) bool {
return GetFlag(key, "false") == "true"
}
+
+func GetIntFlag(key string, fallback int) int {
+ val, ok := flags[key]
+ if !ok {
+ return fallback
+ }
+
+ v, err := strconv.Atoi(val)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return v
+}
+
+func GetStringSliceFlag(key string, fallback []string) []string {
+ val, ok := flags[key]
+ if !ok {
+ return fallback
+ }
+
+ if val == "" {
+ return []string{}
+ }
+ stringReader := strings.NewReader(val)
+ csvReader := csv.NewReader(stringReader)
+ v, err := csvReader.Read()
+ if err != nil {
+ log.Fatal(err)
+ }
+ return v
+}
diff --git a/util/config/env_test.go b/util/config/env_test.go
index c19961813a457..da0ae71ba18da 100644
--- a/util/config/env_test.go
+++ b/util/config/env_test.go
@@ -54,6 +54,63 @@ func TestBooleanFlagAtEnd(t *testing.T) {
assert.True(t, GetBoolFlag("foo"))
}
+func TestIntFlag(t *testing.T) {
+ loadOpts(t, "--foo 2")
+
+ assert.Equal(t, 2, GetIntFlag("foo", 0))
+}
+
+func TestIntFlagAtStart(t *testing.T) {
+ loadOpts(t, "--foo 2 --bar baz")
+
+ assert.Equal(t, 2, GetIntFlag("foo", 0))
+}
+
+func TestIntFlagInMiddle(t *testing.T) {
+ loadOpts(t, "--bar baz --foo 2 --qux")
+
+ assert.Equal(t, 2, GetIntFlag("foo", 0))
+}
+
+func TestIntFlagAtEnd(t *testing.T) {
+ loadOpts(t, "--bar baz --foo 2")
+
+ assert.Equal(t, 2, GetIntFlag("foo", 0))
+}
+
+func TestStringSliceFlag(t *testing.T) {
+ loadOpts(t, "--header='Content-Type: application/json; charset=utf-8,Strict-Transport-Security: max-age=31536000'")
+ strings := GetStringSliceFlag("header", []string{})
+
+ assert.Equal(t, 2, len(strings))
+ assert.Equal(t, "Content-Type: application/json; charset=utf-8", strings[0])
+ assert.Equal(t, "Strict-Transport-Security: max-age=31536000", strings[1])
+}
+
+func TestStringSliceFlagAtStart(t *testing.T) {
+ loadOpts(t, "--header='Strict-Transport-Security: max-age=31536000' --bar baz")
+ strings := GetStringSliceFlag("header", []string{})
+
+ assert.Equal(t, 1, len(strings))
+ assert.Equal(t, "Strict-Transport-Security: max-age=31536000", strings[0])
+}
+
+func TestStringSliceFlagInMiddle(t *testing.T) {
+ loadOpts(t, "--bar baz --header='Strict-Transport-Security: max-age=31536000' --qux")
+ strings := GetStringSliceFlag("header", []string{})
+
+ assert.Equal(t, 1, len(strings))
+ assert.Equal(t, "Strict-Transport-Security: max-age=31536000", strings[0])
+}
+
+func TestStringSliceFlagAtEnd(t *testing.T) {
+ loadOpts(t, "--bar baz --header='Strict-Transport-Security: max-age=31536000'")
+ strings := GetStringSliceFlag("header", []string{})
+
+ assert.Equal(t, 1, len(strings))
+ assert.Equal(t, "Strict-Transport-Security: max-age=31536000", strings[0])
+}
+
func TestFlagAtStart(t *testing.T) {
loadOpts(t, "--foo bar")
diff --git a/util/git/client.go b/util/git/client.go
index 8fa8563498613..bbd510c5d106b 100644
--- a/util/git/client.go
+++ b/util/git/client.go
@@ -75,6 +75,7 @@ type Client interface {
RevisionMetadata(revision string) (*RevisionMetadata, error)
VerifyCommitSignature(string) (string, error)
IsAnnotatedTag(string) bool
+ ChangedFiles(revision string, targetRevision string) ([]string, error)
}
type EventHandlers struct {
@@ -704,6 +705,29 @@ func (m *nativeGitClient) IsAnnotatedTag(revision string) bool {
}
}
+// ChangedFiles returns a list of files changed between two revisions
+func (m *nativeGitClient) ChangedFiles(revision string, targetRevision string) ([]string, error) {
+ if revision == targetRevision {
+ return []string{}, nil
+ }
+
+ if !IsCommitSHA(revision) || !IsCommitSHA(targetRevision) {
+ return []string{}, fmt.Errorf("invalid revision provided, must be SHA")
+ }
+
+ out, err := m.runCmd("diff", "--name-only", fmt.Sprintf("%s..%s", revision, targetRevision))
+ if err != nil {
+ return nil, fmt.Errorf("failed to diff %s..%s: %w", revision, targetRevision, err)
+ }
+
+ if out == "" {
+ return []string{}, nil
+ }
+
+ files := strings.Split(out, "\n")
+ return files, nil
+}
+
// runWrapper runs a custom command with all the semantics of running the Git client
func (m *nativeGitClient) runGnuPGWrapper(wrapper string, args ...string) (string, error) {
cmd := exec.Command(wrapper, args...)
diff --git a/util/git/client_test.go b/util/git/client_test.go
index 6e91868549f3e..b9897de12f90f 100644
--- a/util/git/client_test.go
+++ b/util/git/client_test.go
@@ -118,6 +118,61 @@ func Test_IsAnnotatedTag(t *testing.T) {
assert.False(t, atag)
}
+func Test_ChangedFiles(t *testing.T) {
+ tempDir := t.TempDir()
+
+ client, err := NewClientExt(fmt.Sprintf("file://%s", tempDir), tempDir, NopCreds{}, true, false, "")
+ require.NoError(t, err)
+
+ err = client.Init()
+ require.NoError(t, err)
+
+ err = runCmd(client.Root(), "git", "commit", "-m", "Initial commit", "--allow-empty")
+ require.NoError(t, err)
+
+ // Create a tag to have a second ref
+ err = runCmd(client.Root(), "git", "tag", "some-tag")
+ require.NoError(t, err)
+
+ p := path.Join(client.Root(), "README")
+ f, err := os.Create(p)
+ require.NoError(t, err)
+ _, err = f.WriteString("Hello.")
+ require.NoError(t, err)
+ err = f.Close()
+ require.NoError(t, err)
+
+ err = runCmd(client.Root(), "git", "add", "README")
+ require.NoError(t, err)
+
+ err = runCmd(client.Root(), "git", "commit", "-m", "Changes", "-a")
+ require.NoError(t, err)
+
+ previousSHA, err := client.LsRemote("some-tag")
+ require.NoError(t, err)
+
+ commitSHA, err := client.LsRemote("HEAD")
+ require.NoError(t, err)
+
+ // Invalid commits, error
+ _, err = client.ChangedFiles("0000000000000000000000000000000000000000", "1111111111111111111111111111111111111111")
+ require.Error(t, err)
+
+ // Not SHAs, error
+ _, err = client.ChangedFiles(previousSHA, "HEAD")
+ require.Error(t, err)
+
+ // Same commit, no changes
+ changedFiles, err := client.ChangedFiles(commitSHA, commitSHA)
+ require.NoError(t, err)
+ assert.ElementsMatch(t, []string{}, changedFiles)
+
+ // Different ref, with changes
+ changedFiles, err = client.ChangedFiles(previousSHA, commitSHA)
+ require.NoError(t, err)
+ assert.ElementsMatch(t, []string{"README"}, changedFiles)
+}
+
func Test_nativeGitClient_Submodule(t *testing.T) {
tempDir, err := os.MkdirTemp("", "")
require.NoError(t, err)
diff --git a/util/git/mocks/Client.go b/util/git/mocks/Client.go
index 1d32c9bc9c5d2..16e13b2315173 100644
--- a/util/git/mocks/Client.go
+++ b/util/git/mocks/Client.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.30.1. DO NOT EDIT.
+// Code generated by mockery v2.32.4. DO NOT EDIT.
package mocks
@@ -12,6 +12,32 @@ type Client struct {
mock.Mock
}
+// ChangedFiles provides a mock function with given fields: revision, targetRevision
+func (_m *Client) ChangedFiles(revision string, targetRevision string) ([]string, error) {
+ ret := _m.Called(revision, targetRevision)
+
+ var r0 []string
+ var r1 error
+ if rf, ok := ret.Get(0).(func(string, string) ([]string, error)); ok {
+ return rf(revision, targetRevision)
+ }
+ if rf, ok := ret.Get(0).(func(string, string) []string); ok {
+ r0 = rf(revision, targetRevision)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]string)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(string, string) error); ok {
+ r1 = rf(revision, targetRevision)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
// Checkout provides a mock function with given fields: revision, submoduleEnabled
func (_m *Client) Checkout(revision string, submoduleEnabled bool) error {
ret := _m.Called(revision, submoduleEnabled)
diff --git a/util/settings/settings.go b/util/settings/settings.go
index 82b4d72dc23c8..45da68945a59f 100644
--- a/util/settings/settings.go
+++ b/util/settings/settings.go
@@ -103,6 +103,8 @@ type ArgoCDSettings struct {
InClusterEnabled bool `json:"inClusterEnabled"`
// ServerRBACLogEnforceEnable temporary var indicates whether rbac will be enforced on logs
ServerRBACLogEnforceEnable bool `json:"serverRBACLogEnforceEnable"`
+ // MaxPodLogsToRender the maximum number of pod logs to render
+ MaxPodLogsToRender int64 `json:"maxPodLogsToRender"`
// ExecEnabled indicates whether the UI exec feature is enabled
ExecEnabled bool `json:"execEnabled"`
// ExecShells restricts which shells are allowed for `exec` and in which order they are tried
@@ -485,6 +487,8 @@ const (
inClusterEnabledKey = "cluster.inClusterEnabled"
// settingsServerRBACLogEnforceEnable is the key to configure whether logs RBAC enforcement is enabled
settingsServerRBACLogEnforceEnableKey = "server.rbac.log.enforce.enable"
+ // MaxPodLogsToRender the maximum number of pod logs to render
+ settingsMaxPodLogsToRender = "server.maxPodLogsToRender"
// helmValuesFileSchemesKey is the key to configure the list of supported helm values file schemas
helmValuesFileSchemesKey = "helm.valuesFileSchemes"
// execEnabledKey is the key to configure whether the UI exec feature is enabled
@@ -788,6 +792,19 @@ func (mgr *SettingsManager) GetServerRBACLogEnforceEnable() (bool, error) {
return strconv.ParseBool(argoCDCM.Data[settingsServerRBACLogEnforceEnableKey])
}
+func (mgr *SettingsManager) GetMaxPodLogsToRender() (int64, error) {
+ argoCDCM, err := mgr.getConfigMap()
+ if err != nil {
+ return 10, err
+ }
+
+ if argoCDCM.Data[settingsMaxPodLogsToRender] == "" {
+ return 10, nil
+ }
+
+ return strconv.ParseInt(argoCDCM.Data[settingsMaxPodLogsToRender], 10, 64)
+}
+
func (mgr *SettingsManager) GetDeepLinks(deeplinkType string) ([]DeepLink, error) {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
@@ -1457,6 +1474,13 @@ func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.Confi
if settings.PasswordPattern == "" {
settings.PasswordPattern = common.PasswordPatten
}
+ if maxPodLogsToRenderStr, ok := argoCDCM.Data[settingsMaxPodLogsToRender]; ok {
+ if val, err := strconv.ParseInt(maxPodLogsToRenderStr, 10, 64); err != nil {
+ log.Warnf("Failed to parse '%s' key: %v", settingsMaxPodLogsToRender, err)
+ } else {
+ settings.MaxPodLogsToRender = val
+ }
+ }
settings.InClusterEnabled = argoCDCM.Data[inClusterEnabledKey] != "false"
settings.ExecEnabled = argoCDCM.Data[execEnabledKey] == "true"
execShells := argoCDCM.Data[execShellsKey]
diff --git a/util/webhook/webhook.go b/util/webhook/webhook.go
index 04746a1df0e37..dab69d7b131b7 100644
--- a/util/webhook/webhook.go
+++ b/util/webhook/webhook.go
@@ -7,7 +7,6 @@ import (
"html"
"net/http"
"net/url"
- "path/filepath"
"regexp"
"strings"
@@ -26,10 +25,10 @@ import (
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/v2/reposerver/cache"
servercache "github.com/argoproj/argo-cd/v2/server/cache"
+ "github.com/argoproj/argo-cd/v2/util/app/path"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/glob"
- "github.com/argoproj/argo-cd/v2/util/security"
"github.com/argoproj/argo-cd/v2/util/settings"
)
@@ -292,7 +291,8 @@ func (a *ArgoCDWebhookHandler) HandleEvent(payload interface{}) {
for _, source := range app.Spec.GetSources() {
if sourceRevisionHasChanged(source, revision, touchedHead) && sourceUsesURL(source, webURL, repoRegexp) {
- if appFilesHaveChanged(&app, changedFiles) {
+ refreshPaths := path.GetAppRefreshPaths(&app)
+ if path.AppFilesHaveChanged(refreshPaths, changedFiles) {
namespacedAppInterface := a.appClientset.ArgoprojV1alpha1().Applications(app.ObjectMeta.Namespace)
_, err = argo.RefreshApp(namespacedAppInterface, app.ObjectMeta.Name, v1alpha1.RefreshTypeNormal)
if err != nil {
@@ -358,70 +358,6 @@ func (a *ArgoCDWebhookHandler) storePreviouslyCachedManifests(app *v1alpha1.Appl
return nil
}
-func getAppRefreshPaths(app *v1alpha1.Application) []string {
- var paths []string
- if val, ok := app.Annotations[v1alpha1.AnnotationKeyManifestGeneratePaths]; ok && val != "" {
- for _, item := range strings.Split(val, ";") {
- if item == "" {
- continue
- }
- if filepath.IsAbs(item) {
- paths = append(paths, item[1:])
- } else {
- for _, source := range app.Spec.GetSources() {
- paths = append(paths, filepath.Clean(filepath.Join(source.Path, item)))
- }
- }
- }
- }
- return paths
-}
-
-func appFilesHaveChanged(app *v1alpha1.Application, changedFiles []string) bool {
- // an empty slice of changed files means that the payload didn't include a list
- // of changed files and w have to assume that a refresh is required
- if len(changedFiles) == 0 {
- return true
- }
-
- // Check to see if the app has requested refreshes only on a specific prefix
- refreshPaths := getAppRefreshPaths(app)
-
- if len(refreshPaths) == 0 {
- // Apps without a given refreshed paths always be refreshed, regardless of changed files
- // this is the "default" behavior
- return true
- }
-
- // At last one changed file must be under refresh path
- for _, f := range changedFiles {
- f = ensureAbsPath(f)
- for _, item := range refreshPaths {
- item = ensureAbsPath(item)
- changed := false
- if f == item {
- changed = true
- } else if _, err := security.EnforceToCurrentRoot(item, f); err == nil {
- changed = true
- }
- if changed {
- log.WithField("application", app.Name).Debugf("Application uses files that have changed")
- return true
- }
- }
- }
-
- log.WithField("application", app.Name).Debugf("Application does not use any of the files that have changed")
- return false
-}
-
-func ensureAbsPath(input string) string {
- if !filepath.IsAbs(input) {
- return string(filepath.Separator) + input
- }
- return input
-}
-
func sourceRevisionHasChanged(source v1alpha1.ApplicationSource, revision string, touchedHead bool) bool {
targetRev := parseRevision(source.TargetRevision)
if targetRev == "HEAD" || targetRev == "" { // revision is head
diff --git a/util/webhook/webhook_test.go b/util/webhook/webhook_test.go
index a1e1dd4ba6b05..b86df29f127af 100644
--- a/util/webhook/webhook_test.go
+++ b/util/webhook/webhook_test.go
@@ -411,87 +411,6 @@ func TestUnknownEvent(t *testing.T) {
hook.Reset()
}
-func getApp(annotation string, sourcePath string) *v1alpha1.Application {
- return &v1alpha1.Application{
- ObjectMeta: metav1.ObjectMeta{
- Annotations: map[string]string{
- v1alpha1.AnnotationKeyManifestGeneratePaths: annotation,
- },
- },
- Spec: v1alpha1.ApplicationSpec{
- Source: &v1alpha1.ApplicationSource{
- Path: sourcePath,
- },
- },
- }
-}
-
-func getMultiSourceApp(annotation string, paths ...string) *v1alpha1.Application {
- var sources v1alpha1.ApplicationSources
- for _, path := range paths {
- sources = append(sources, v1alpha1.ApplicationSource{Path: path})
- }
- return &v1alpha1.Application{
- ObjectMeta: metav1.ObjectMeta{
- Annotations: map[string]string{
- v1alpha1.AnnotationKeyManifestGeneratePaths: annotation,
- },
- },
- Spec: v1alpha1.ApplicationSpec{
- Sources: sources,
- },
- }
-}
-
-func Test_getAppRefreshPrefix(t *testing.T) {
- tests := []struct {
- name string
- app *v1alpha1.Application
- files []string
- changeExpected bool
- }{
- {"default no path", &v1alpha1.Application{}, []string{"README.md"}, 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},
- {"relative path - not matching", getApp(".", "source/path"), []string{"README.md"}, false},
- {"relative path, multi source - not matching", getMultiSourceApp(".", "other/path", "unrelated/path"), []string{"README.md"}, false},
- {"absolute path - matching", getApp("/source/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"absolute path, multi source - matching #1", getMultiSourceApp("/source/path", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"absolute path, multi source - matching #2", getMultiSourceApp("/source/path", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"absolute path - not matching", getApp("/source/path1", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
- {"absolute path, multi source - not matching", getMultiSourceApp("/source/path1", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
- {"two relative paths - matching", getApp(".;../shared", "my-app"), []string{"shared/my-deployment.yaml"}, true},
- {"two relative paths, multi source - matching #1", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"shared/my-deployment.yaml"}, true},
- {"two relative paths, multi source - matching #2", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"shared/my-deployment.yaml"}, true},
- {"two relative paths - not matching", getApp(".;../shared", "my-app"), []string{"README.md"}, false},
- {"two relative paths, multi source - not matching", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"README.md"}, false},
- {"file relative path - matching", getApp("./my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"file relative path, multi source - matching #1", getMultiSourceApp("./my-deployment.yaml", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"file relative path, multi source - matching #2", getMultiSourceApp("./my-deployment.yaml", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"file relative path - not matching", getApp("./my-deployment.yaml", "source/path"), []string{"README.md"}, false},
- {"file relative path, multi source - not matching", getMultiSourceApp("./my-deployment.yaml", "source/path", "other/path"), []string{"README.md"}, false},
- {"file absolute path - matching", getApp("/source/path/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"file absolute path, multi source - matching #1", getMultiSourceApp("/source/path/my-deployment.yaml", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"file absolute path, multi source - matching #2", getMultiSourceApp("/source/path/my-deployment.yaml", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
- {"file absolute path - not matching", getApp("/source/path1/README.md", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
- {"file absolute path, multi source - not matching", getMultiSourceApp("/source/path1/README.md", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, false},
- {"file two relative paths - matching", getApp("./README.md;../shared/my-deployment.yaml", "my-app"), []string{"shared/my-deployment.yaml"}, true},
- {"file two relative paths, multi source - matching", getMultiSourceApp("./README.md;../shared/my-deployment.yaml", "my-app", "other-path"), []string{"shared/my-deployment.yaml"}, true},
- {"file two relative paths - not matching", getApp(".README.md;../shared/my-deployment.yaml", "my-app"), []string{"kustomization.yaml"}, false},
- {"file two relative paths, multi source - not matching", getMultiSourceApp(".README.md;../shared/my-deployment.yaml", "my-app", "other-path"), []string{"kustomization.yaml"}, false},
- }
- for _, tt := range tests {
- ttc := tt
- t.Run(ttc.name, func(t *testing.T) {
- t.Parallel()
- if got := appFilesHaveChanged(ttc.app, ttc.files); got != ttc.changeExpected {
- t.Errorf("getAppRefreshPrefix() = %v, want %v", got, ttc.changeExpected)
- }
- })
- }
-}
-
func TestAppRevisionHasChanged(t *testing.T) {
getSource := func(targetRevision string) v1alpha1.ApplicationSource {
return v1alpha1.ApplicationSource{TargetRevision: targetRevision}