From d817a395a554c3bc36f61b86646da088ecf5aae5 Mon Sep 17 00:00:00 2001 From: Richard Gee Date: Sat, 9 Nov 2024 16:50:19 +0000 Subject: [PATCH] feat: add hold file for `chart upgrade` Signed-off-by: Richard Gee --- .gitignore | 2 +- cmd/chart/upgrade.go | 58 ++++++++++++++++++- cmd/chart/upgrade_test.go | 118 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 32f64cf69..ae8fc4ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ mc /arkade-* /faas-cli* test.out -docker-compose.yaml \ No newline at end of file +docker-compose.yaml* \ No newline at end of file diff --git a/cmd/chart/upgrade.go b/cmd/chart/upgrade.go index e9e1d7ece..b56e73529 100644 --- a/cmd/chart/upgrade.go +++ b/cmd/chart/upgrade.go @@ -1,6 +1,7 @@ package chart import ( + "bufio" "errors" "fmt" "log" @@ -96,9 +97,22 @@ Otherwise, it returns a non-zero exit code and the updated values.yaml file.`, } if verbose { - if len(filtered) > 0 { - log.Printf("Found %d images\n", len(filtered)) - } + log.Printf("Found %d images\n", len(filtered)) + } + + holdfile := fmt.Sprintf("%s.hold", file) + imagesToHold, err := readFileLines(holdfile) + if err != nil { + return err + } + + if verbose { + log.Printf("Found %d images to hold/ignore in %s", len(imagesToHold), holdfile) + } + + filtered = removeHoldImages(filtered, imagesToHold) + if verbose { + log.Printf("Processing %d images\n", len(filtered)) } wg := sync.WaitGroup{} @@ -252,3 +266,41 @@ func getTagAttributes(t string) tagAttributes { original: t, } } + +func readFileLines(filename string) ([]string, error) { + + if _, err := os.Stat(filename); os.IsNotExist(err) { + return nil, nil + } + + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading file: %w", err) + } + + return lines, nil +} + +func removeHoldImages(fullset map[string]string, held []string) map[string]string { + + for _, h := range held { + for k := range fullset { + if strings.EqualFold(h, k[len(k)-len(h):]) { + delete(fullset, k) + } + } + } + + return fullset +} diff --git a/cmd/chart/upgrade_test.go b/cmd/chart/upgrade_test.go index e22589b3a..4238cc361 100644 --- a/cmd/chart/upgrade_test.go +++ b/cmd/chart/upgrade_test.go @@ -1,6 +1,7 @@ package chart import ( + "reflect" "testing" ) @@ -372,3 +373,120 @@ func TestGetTagAttributes(t *testing.T) { }) } } + +func TestRemoveHoldImages(t *testing.T) { + tests := []struct { + name string + fullset map[string]string + held []string + expected map[string]string + }{ + { + name: "Basic exclusion", + fullset: map[string]string{ + "registry/img1:16": "going", + "registry/img1:17": "staying", + "registry/img1:18": "staying", + }, + held: []string{ + "img1:16", + }, + expected: map[string]string{ + "registry/img1:17": "staying", + "registry/img1:18": "staying", + }, + }, + { + name: "Basic exclusion / muli-match", + fullset: map[string]string{ + "registry/img1:16": "going", + "registry/img1:17": "going", + "registry/img1:18": "staying", + }, + held: []string{ + "img1:16", + "img1:17", + }, + expected: map[string]string{ + "registry/img1:18": "staying", + }, + }, + { + name: "No match", + fullset: map[string]string{ + "registry/img1:17": "staying", + "registry/img1:18": "staying", + "registry/img1:19": "staying", + }, + held: []string{ + "img1:16", + }, + expected: map[string]string{ + "registry/img1:17": "staying", + "registry/img1:18": "staying", + "registry/img1:19": "staying", + }, + }, + { + name: "Different Repos images match", + fullset: map[string]string{ + "registry/repo/img1:16": "going", + "registry/repo2/img1:16": "going", + "registry/repo3/img1:18": "staying", + }, + held: []string{ + "img1:16", + }, + expected: map[string]string{ + "registry/repo3/img1:18": "staying", + }, + }, + { + name: "Different Repos images match / full path exclude", + fullset: map[string]string{ + "registry/repo/img1:16": "going", + "registry/repo2/img1:16": "staying", + "registry/repo3/img1:18": "staying", + }, + held: []string{ + "registry/repo/img1:16", + }, + expected: map[string]string{ + "registry/repo2/img1:16": "staying", + "registry/repo3/img1:18": "staying", + }, + }, + { + name: "Different Repos images match / two exclude", + fullset: map[string]string{ + "registry/repo/img1:16": "going", + "registry/repo2/img1:16": "going", + "registry/repo3/img1:18": "staying", + }, + held: []string{ + "registry/repo/img1:16", + "img1:16", + }, + expected: map[string]string{ + "registry/repo3/img1:18": "staying", + }, + }, + { + name: "Empty fullset", + fullset: map[string]string{}, + held: []string{ + "hold", + }, + expected: map[string]string{}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := removeHoldImages(tc.fullset, tc.held) + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("\n%s \n got = %v\n want = %v", tc.name, result, tc.expected) + } + }) + } +}