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  }