diff --git a/README.md b/README.md index 3febbe3e..c957cfce 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,14 @@ We suggest running kube-applier as a Deployment (see [demo/](https://github.com/ set to `/git/repo`, and the file to be whitelisted is `/git/repo/apps/app1.json`, the line in the whiltelist file should be `apps/app1.json`). + +--- +**NOTE** +The blacklist and whiltelist files support line comments. +A single line gets ignored if the first non-blank character is # in that line. + +--- + * `POLL_INTERVAL_SECONDS` - (int) Number of seconds to wait between each check for new commits to the repo (default is 5). Set to 0 to disable the wait period. * `FULL_RUN_INTERVAL_SECONDS` - (int) Number of seconds between automatic full runs (default is 300, or 5 minutes). Set to 0 to disable the wait period. * `DIFF_URL_FORMAT` - (string) If specified, allows the status page to display a link to the source code referencing the diff for a specific commit. `DIFF_URL_FORMAT` should be a URL for a hosted remote repo that supports linking to a commit hash. Replace the commit hash portion with "%s" so it can be filled in by kube-applier (e.g. `https://github.com/kubernetes/kubernetes/commit/%s`). diff --git a/applylist/factory.go b/applylist/factory.go index 0c353538..756bb2a7 100644 --- a/applylist/factory.go +++ b/applylist/factory.go @@ -36,6 +36,23 @@ func (f *Factory) Create() ([]string, []string, []string, error) { return applyList, blacklist, whitelist, nil } +// purgeCommentsFromList iterates over the list contents and deletes comment +// lines. A comment is a line whose first non-space character is # +func (f *Factory) purgeCommentsFromList(rawList []string) []string { + + // http://stackoverflow.com/a/20551116/5771861 + i := 0 + for _, l := range rawList { + // # is the comment line + if len(l) > 0 && string(l[0]) != "#" { + rawList[i] = l + i++ + } + } + rv := rawList[:i] + return rv +} + // createFilelist reads lines from the given file, converts the relative // paths to full paths, and returns a sorted list of full paths. func (f *Factory) createFileList(listFilePath string) ([]string, error) { @@ -46,7 +63,10 @@ func (f *Factory) createFileList(listFilePath string) ([]string, error) { if err != nil { return nil, err } - list := prependToEachPath(f.RepoPath, rawList) + + filteredList := f.purgeCommentsFromList(rawList) + + list := prependToEachPath(f.RepoPath, filteredList) sort.Strings(list) return list, nil } diff --git a/applylist/factory_test.go b/applylist/factory_test.go index c36d039b..00ae41b8 100644 --- a/applylist/factory_test.go +++ b/applylist/factory_test.go @@ -18,6 +18,62 @@ type testCase struct { expectedErr error } +// TestPurgeComments verifies the comment processing and purging from the +// whitelist and blacklist specifications. +func TestPurgeComments(t *testing.T) { + + var testData = []struct { + rawList []string + expectedReturn []string + }{ + // No comment + { + []string{"/repo/a/b.json", "/repo/b/c", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, + []string{"/repo/a/b.json", "/repo/b/c", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, + }, + // First line is commented + { + []string{"#/repo/a/b.json", "/repo/b/c", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, + []string{"/repo/b/c", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, + }, + // Last line is commented + { + []string{"/repo/a/b.json", "/repo/b/c", "/repo/a/b/c.yaml", "/repo/a/b/c", "# /repo/c.json"}, + []string{"/repo/a/b.json", "/repo/b/c", "/repo/a/b/c.yaml", "/repo/a/b/c"}, + }, + // Empty line + { + []string{"/repo/a/b.json", "", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, + []string{"/repo/a/b.json", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, + }, + // Comment line only containing the comment character. + { + []string{"/repo/a/b.json", "#", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, + []string{"/repo/a/b.json", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, + }, + // Empty file + { + []string{}, + []string{}, + }, + // File with only comment lines. + { + []string{"# some comment "}, + []string{}, + }, + } + + assert := assert.New(t) + mockCtrl := gomock.NewController(t) + fs := sysutil.NewMockFileSystemInterface(mockCtrl) + f := &Factory{"", "", "", fs} + for _, td := range testData { + + rv := f.purgeCommentsFromList(td.rawList) + assert.Equal(rv, td.expectedReturn) + } +} + func TestFactoryCreate(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -128,6 +184,16 @@ func TestFactoryCreate(t *testing.T) { ) tc = testCase{"/repo", "/blacklist", "/whitelist", fs, []string{"/repo/c.json"}, []string{"/repo/a/b/c.yaml"}, nil} createAndAssert(t, tc) + + // Both whitelist and blacklist contain the same file and other comments. + gomock.InOrder( + fs.EXPECT().ReadLines("/blacklist").Times(1).Return([]string{"a/b/c.yaml", "# c.json"}, nil), + fs.EXPECT().ReadLines("/whitelist").Times(1).Return([]string{"a/b/c.yaml", "c.json", "# a/b/c.yaml"}, nil), + fs.EXPECT().ListAllFiles("/repo").Times(1).Return([]string{"/repo/a/b.json", "/repo/b/c", "/repo/a/b/c.yaml", "/repo/a/b/c", "/repo/c.json"}, nil), + ) + tc = testCase{"/repo", "/blacklist", "/whitelist", fs, []string{"/repo/c.json"}, []string{"/repo/a/b/c.yaml"}, nil} + createAndAssert(t, tc) + } func createAndAssert(t *testing.T, tc testCase) { diff --git a/sysutil/filesystem.go b/sysutil/filesystem.go index f07e92dc..ffe403c4 100644 --- a/sysutil/filesystem.go +++ b/sysutil/filesystem.go @@ -6,6 +6,7 @@ import ( "log" "os" "path/filepath" + "strings" "time" ) @@ -29,7 +30,7 @@ func (fs *FileSystem) ReadLines(filePath string) ([]string, error) { var result []string s := bufio.NewScanner(f) for s.Scan() { - result = append(result, s.Text()) + result = append(result, strings.TrimSpace(s.Text())) } if err := s.Err(); err != nil { return nil, fmt.Errorf("Error reading the file at %v: %v", filePath, err)