github.com/argoproj/argo-cd/v3@v3.2.1/util/app/path/path_test.go (about) 1 package path 2 3 import ( 4 "os" 5 "path" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 13 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 14 fileutil "github.com/argoproj/argo-cd/v3/test/fixture/path" 15 ) 16 17 func TestPathRoot(t *testing.T) { 18 _, err := Path("./testdata", "/") 19 require.EqualError(t, err, "/: app path is absolute") 20 } 21 22 func TestPathAbsolute(t *testing.T) { 23 _, err := Path("./testdata", "/etc/passwd") 24 require.EqualError(t, err, "/etc/passwd: app path is absolute") 25 } 26 27 func TestPathDotDot(t *testing.T) { 28 _, err := Path("./testdata", "..") 29 require.EqualError(t, err, "..: app path outside root") 30 } 31 32 func TestPathDotDotSlash(t *testing.T) { 33 _, err := Path("./testdata", "../") 34 require.EqualError(t, err, "../: app path outside root") 35 } 36 37 func TestPathDot(t *testing.T) { 38 _, err := Path("./testdata", ".") 39 require.NoError(t, err) 40 } 41 42 func TestPathDotSlash(t *testing.T) { 43 _, err := Path("./testdata", "./") 44 require.NoError(t, err) 45 } 46 47 func TestNonExistentPath(t *testing.T) { 48 _, err := Path("./testdata", "does-not-exist") 49 require.EqualError(t, err, "does-not-exist: app path does not exist") 50 } 51 52 func TestPathNotDir(t *testing.T) { 53 _, err := Path("./testdata", "file.txt") 54 require.EqualError(t, err, "file.txt: app path is not a directory") 55 } 56 57 func TestGoodSymlinks(t *testing.T) { 58 err := CheckOutOfBoundsSymlinks("./testdata/goodlink") 59 require.NoError(t, err) 60 } 61 62 // Simple check of leaving the repo 63 func TestBadSymlinks(t *testing.T) { 64 err := CheckOutOfBoundsSymlinks("./testdata/badlink") 65 var oobError *OutOfBoundsSymlinkError 66 require.ErrorAs(t, err, &oobError) 67 assert.Equal(t, "badlink", oobError.File) 68 } 69 70 // Crazy formatting check 71 func TestBadSymlinks2(t *testing.T) { 72 err := CheckOutOfBoundsSymlinks("./testdata/badlink2") 73 var oobError *OutOfBoundsSymlinkError 74 require.ErrorAs(t, err, &oobError) 75 assert.Equal(t, "badlink", oobError.File) 76 } 77 78 // Make sure no part of the symlink can leave the repo, even if it ultimately targets inside the repo 79 func TestBadSymlinks3(t *testing.T) { 80 err := CheckOutOfBoundsSymlinks("./testdata/badlink3") 81 var oobError *OutOfBoundsSymlinkError 82 require.ErrorAs(t, err, &oobError) 83 assert.Equal(t, "badlink", oobError.File) 84 } 85 86 // No absolute symlinks allowed 87 func TestAbsSymlink(t *testing.T) { 88 const testDir = "./testdata/abslink" 89 wd, err := os.Getwd() 90 require.NoError(t, err) 91 t.Cleanup(func() { 92 os.Remove(path.Join(testDir, "abslink")) 93 }) 94 t.Chdir(testDir) 95 require.NoError(t, fileutil.CreateSymlink(t, "/somethingbad", "abslink")) 96 t.Chdir(wd) 97 err = CheckOutOfBoundsSymlinks(testDir) 98 var oobError *OutOfBoundsSymlinkError 99 require.ErrorAs(t, err, &oobError) 100 assert.Equal(t, "abslink", oobError.File) 101 } 102 103 func getApp(annotation string, sourcePath string) *v1alpha1.Application { 104 return &v1alpha1.Application{ 105 ObjectMeta: metav1.ObjectMeta{ 106 Annotations: map[string]string{ 107 v1alpha1.AnnotationKeyManifestGeneratePaths: annotation, 108 }, 109 }, 110 Spec: v1alpha1.ApplicationSpec{ 111 Source: &v1alpha1.ApplicationSource{ 112 Path: sourcePath, 113 }, 114 }, 115 } 116 } 117 118 func getMultiSourceApp(annotation string, paths ...string) *v1alpha1.Application { 119 var sources v1alpha1.ApplicationSources 120 for _, path := range paths { 121 sources = append(sources, v1alpha1.ApplicationSource{Path: path}) 122 } 123 return &v1alpha1.Application{ 124 ObjectMeta: metav1.ObjectMeta{ 125 Annotations: map[string]string{ 126 v1alpha1.AnnotationKeyManifestGeneratePaths: annotation, 127 }, 128 }, 129 Spec: v1alpha1.ApplicationSpec{ 130 Sources: sources, 131 }, 132 } 133 } 134 135 func Test_AppFilesHaveChanged(t *testing.T) { 136 t.Parallel() 137 138 tests := []struct { 139 name string 140 app *v1alpha1.Application 141 files []string 142 changeExpected bool 143 }{ 144 {"default no path", &v1alpha1.Application{}, []string{"README.md"}, true}, 145 {"no files changed", getApp(".", "source/path"), []string{}, true}, 146 {"relative path - matching", getApp(".", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 147 {"relative path, multi source - matching #1", getMultiSourceApp(".", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true}, 148 {"relative path, multi source - matching #2", getMultiSourceApp(".", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 149 {"relative path - not matching", getApp(".", "source/path"), []string{"README.md"}, false}, 150 {"relative path, multi source - not matching", getMultiSourceApp(".", "other/path", "unrelated/path"), []string{"README.md"}, false}, 151 {"absolute path - matching", getApp("/source/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 152 {"absolute path, multi source - matching #1", getMultiSourceApp("/source/path", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true}, 153 {"absolute path, multi source - matching #2", getMultiSourceApp("/source/path", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 154 {"absolute path - not matching", getApp("/source/path1", "source/path"), []string{"source/path/my-deployment.yaml"}, false}, 155 {"absolute path, multi source - not matching", getMultiSourceApp("/source/path1", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, false}, 156 {"glob path * - matching", getApp("/source/**/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 157 {"glob path * - not matching", getApp("/source/**/my-service.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, false}, 158 {"glob path ? - matching", getApp("/source/path/my-deployment-?.yaml", "source/path"), []string{"source/path/my-deployment-0.yaml"}, true}, 159 {"glob path ? - not matching", getApp("/source/path/my-deployment-?.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, false}, 160 {"glob path char range - matching", getApp("/source/path[0-9]/my-deployment.yaml", "source/path"), []string{"source/path1/my-deployment.yaml"}, true}, 161 {"glob path char range - not matching", getApp("/source/path[0-9]/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, false}, 162 {"mixed glob path - matching", getApp("/source/path[0-9]/my-*.yaml", "source/path"), []string{"source/path1/my-deployment.yaml"}, true}, 163 {"mixed glob path - not matching", getApp("/source/path[0-9]/my-*.yaml", "source/path"), []string{"README.md"}, false}, 164 {"two relative paths - matching", getApp(".;../shared", "my-app"), []string{"shared/my-deployment.yaml"}, true}, 165 {"two relative paths, multi source - matching #1", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"shared/my-deployment.yaml"}, true}, 166 {"two relative paths, multi source - matching #2", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"shared/my-deployment.yaml"}, true}, 167 {"two relative paths - not matching", getApp(".;../shared", "my-app"), []string{"README.md"}, false}, 168 {"two relative paths, multi source - not matching", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"README.md"}, false}, 169 {"file relative path - matching", getApp("./my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 170 {"file relative path, multi source - matching #1", getMultiSourceApp("./my-deployment.yaml", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true}, 171 {"file relative path, multi source - matching #2", getMultiSourceApp("./my-deployment.yaml", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 172 {"file relative path - not matching", getApp("./my-deployment.yaml", "source/path"), []string{"README.md"}, false}, 173 {"file relative path, multi source - not matching", getMultiSourceApp("./my-deployment.yaml", "source/path", "other/path"), []string{"README.md"}, false}, 174 {"file absolute path - matching", getApp("/source/path/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 175 {"file absolute path, multi source - matching #1", getMultiSourceApp("/source/path/my-deployment.yaml", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true}, 176 {"file absolute path, multi source - matching #2", getMultiSourceApp("/source/path/my-deployment.yaml", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true}, 177 {"file absolute path - not matching", getApp("/source/path1/README.md", "source/path"), []string{"source/path/my-deployment.yaml"}, false}, 178 {"file absolute path, multi source - not matching", getMultiSourceApp("/source/path1/README.md", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, false}, 179 {"file two relative paths - matching", getApp("./README.md;../shared/my-deployment.yaml", "my-app"), []string{"shared/my-deployment.yaml"}, true}, 180 {"file two relative paths, multi source - matching", getMultiSourceApp("./README.md;../shared/my-deployment.yaml", "my-app", "other-path"), []string{"shared/my-deployment.yaml"}, true}, 181 {"file two relative paths - not matching", getApp(".README.md;../shared/my-deployment.yaml", "my-app"), []string{"kustomization.yaml"}, false}, 182 {"file two relative paths, multi source - not matching", getMultiSourceApp(".README.md;../shared/my-deployment.yaml", "my-app", "other-path"), []string{"kustomization.yaml"}, false}, 183 {"changed file absolute path - matching", getApp(".", "source/path"), []string{"/source/path/my-deployment.yaml"}, true}, 184 } 185 for _, tt := range tests { 186 ttc := tt 187 t.Run(ttc.name, func(t *testing.T) { 188 t.Parallel() 189 refreshPaths := GetAppRefreshPaths(ttc.app) 190 assert.Equal(t, ttc.changeExpected, AppFilesHaveChanged(refreshPaths, ttc.files), "AppFilesHaveChanged()") 191 }) 192 } 193 } 194 195 func Test_GetAppRefreshPaths(t *testing.T) { 196 t.Parallel() 197 198 tests := []struct { 199 name string 200 app *v1alpha1.Application 201 expectedPaths []string 202 }{ 203 {"default no path", &v1alpha1.Application{}, []string{}}, 204 {"relative path", getApp(".", "source/path"), []string{"source/path"}}, 205 {"absolute path - multi source", getMultiSourceApp("/source/path", "source/path", "other/path"), []string{"source/path"}}, 206 {"two relative paths ", getApp(".;../shared", "my-app"), []string{"my-app", "shared"}}, 207 {"file relative path", getApp("./my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}}, 208 {"file absolute path", getApp("/source/path/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}}, 209 {"file two relative paths", getApp("./README.md;../shared/my-deployment.yaml", "my-app"), []string{"my-app/README.md", "shared/my-deployment.yaml"}}, 210 {"glob path", getApp("/source/*/my-deployment.yaml", "source/path"), []string{"source/*/my-deployment.yaml"}}, 211 {"empty path", getApp(".;", "source/path"), []string{"source/path"}}, 212 } 213 for _, tt := range tests { 214 ttc := tt 215 t.Run(ttc.name, func(t *testing.T) { 216 t.Parallel() 217 assert.ElementsMatch(t, ttc.expectedPaths, GetAppRefreshPaths(ttc.app), "GetAppRefreshPath()") 218 }) 219 } 220 }