From 8d75f434f0c34b5322a6cd19871fdc71d98f865a Mon Sep 17 00:00:00 2001 From: DTLP Date: Tue, 8 Apr 2025 12:03:53 +0100 Subject: [PATCH 1/9] Add yaml schema verification --- config.go | 105 ++++++++++++++++++++++++++++++++++++++ config_test.go | 133 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 236 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index b89c7ae..5a7b8da 100644 --- a/config.go +++ b/config.go @@ -3,8 +3,11 @@ package main import ( "context" "errors" + "fmt" "os" "path" + "reflect" + "slices" "time" "github.com/prometheus/client_golang/prometheus" @@ -174,14 +177,116 @@ func parseConfigFile(path string) (*mirror.RepoPoolConfig, error) { if err != nil { return nil, err } + + err = validateConfig([]byte(yamlFile)) + if err != nil { + return nil, err + } + conf := &mirror.RepoPoolConfig{} err = yaml.Unmarshal(yamlFile, conf) if err != nil { return nil, err } + return conf, nil } +func validateConfig(yamlData []byte) error { + var raw map[string]interface{} + if err := yaml.Unmarshal(yamlData, &raw); err != nil { + return err + } + + // defaults and repositories sections are mandatory + if _, ok := raw["defaults"]; !ok { + return fmt.Errorf("defaults config section is missing") + } + + if _, ok := raw["repositories"]; !ok { + return fmt.Errorf("repositories config section is missing") + } + + // check config sections for unexpected keys + allowedRepoPoolConfig := getAllowedKeys(mirror.RepoPoolConfig{}) + if key := findUnexpectedKey(raw, allowedRepoPoolConfig); key != "" { + return fmt.Errorf("unexpected key: .%v", key) + } + + // check "defaults" section + defaultsMap, ok := raw["defaults"].(map[string]interface{}) + if !ok { + return fmt.Errorf("defaults section is missing or not valid") + } + allowedDefaults := getAllowedKeys(mirror.DefaultConfig{}) + + if key := findUnexpectedKey(defaultsMap, allowedDefaults); key != "" { + return fmt.Errorf("unexpected key: .defaults.%v", key) + } + + // check "auth" section in "defaults" + if authMap, ok := defaultsMap["auth"].(map[string]interface{}); ok { + allowedAuthKeys := getAllowedKeys(mirror.Auth{}) + if key := findUnexpectedKey(authMap, allowedAuthKeys); key != "" { + return fmt.Errorf("unexpected key: .defaults.auth.%v", key) + } + } + + // check each repository in "repositories" section + allowedRepoKeys := getAllowedKeys(mirror.RepositoryConfig{}) + for _, repoInterface := range raw["repositories"].([]interface{}) { + repoMap, ok := repoInterface.(map[string]interface{}) + if !ok { + return fmt.Errorf("repositories config section is not valid") + } + + if key := findUnexpectedKey(repoMap, allowedRepoKeys); key != "" { + return fmt.Errorf("unexpected key: .repositories[%v].%v", repoMap["remote"], key) + } + + // check each "worktrees" section in each repository + for _, worktreeInterface := range repoMap["worktrees"].([]interface{}) { + worktreeMap, ok := worktreeInterface.(map[string]interface{}) + if !ok { + return fmt.Errorf("worktrees config section is not valid in .repositories[%v]", repoMap["remote"]) + } + + allowedWorktreeKeys := getAllowedKeys(mirror.WorktreeConfig{}) + if key := findUnexpectedKey(worktreeMap, allowedWorktreeKeys); key != "" { + return fmt.Errorf("unexpected key: .repositories[%v].worktrees[%v].%v", repoMap["remote"], worktreeMap["link"], key) + } + } + } + + return nil +} + +// getAllowedKeys retrieves a list of allowed keys from the specified struct +func getAllowedKeys(config interface{}) []string { + var allowedKeys []string + val := reflect.ValueOf(config) + typ := reflect.TypeOf(config) + + for i := 0; i < val.NumField(); i++ { + field := typ.Field(i) + yamlTag := field.Tag.Get("yaml") + if yamlTag != "" { + allowedKeys = append(allowedKeys, yamlTag) + } + } + return allowedKeys +} + +func findUnexpectedKey(raw interface{}, allowedKeys []string) string { + for key := range raw.(map[string]interface{}) { + if !slices.Contains(allowedKeys, key) { + return key + } + } + + return "" +} + // diffRepositories will do the diff between current state and new config and // return new repositories config and list of remote url which are not found in config func diffRepositories(repoPool *mirror.RepoPool, newConfig *mirror.RepoPoolConfig) ( diff --git a/config_test.go b/config_test.go index a8b77bf..15a233e 100644 --- a/config_test.go +++ b/config_test.go @@ -11,7 +11,6 @@ import ( ) func Test_diffRepositories(t *testing.T) { - tests := []struct { name string initialConfig *mirror.RepoPoolConfig @@ -199,7 +198,6 @@ func Test_diffWorktrees(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.initialRepoConf.PopulateEmptyLinkPaths(); err != nil { t.Fatalf("failed to create repo error = %v", err) } @@ -234,3 +232,134 @@ func Test_diffWorktrees(t *testing.T) { }) } } + +func Test_validateConfig(t *testing.T) { + tests := []struct { + name string + yamlData []byte + wantError bool + }{ + { + name: "valid", + yamlData: []byte(` +defaults: + root: /tmp/git-mirror + link_root: /tmp/links + interval: 30s + mirror_timeout: 2m + git_gc: always + auth: + ssh_key_path: /etc/git-secret/ssh + ssh_known_hosts_path: /etc/git-secret/known_hosts + +repositories: + - remote: https://github.com/utilitywarehouse/git-mirror + worktrees: + - link: aaa + ref: main + pathspecs: + - readme.md + - link: bbb + ref: main + - remote: https://github.com/utilitywarehouse/another-repo + worktrees: + - link: ccc + ref: main + pathspecs: + - readme.md +`), + wantError: false, + }, + { + name: "invalid - unexpected key", + yamlData: []byte(` +not-valid: + test: test + +defaults: + root: /tmp/git-mirror + +repositories: + - remote: https://github.com/utilitywarehouse/git-mirror +`), + wantError: true, + }, + { + name: "invalid - defaults missing", + yamlData: []byte(` +repositories: + - remote: https://github.com/utilitywarehouse/git-mirror +`), + wantError: true, + }, + { + name: "invalid - repositories missing", + yamlData: []byte(` +defaults: + root: /tmp/git-mirror +`), + wantError: true, + }, + { + name: "invalid - unexpected key in defaults", + yamlData: []byte(` +defaults: + root: /tmp/git-mirror + not_valid: test + +repositories: + - remote: https://github.com/utilitywarehouse/git-mirror +`), + wantError: true, + }, + { + name: "invalid - unexpected key in auth", + yamlData: []byte(` +defaults: + root: /tmp/git-mirror + not_valid: test + auth: + not_valid: test + +repositories: + - remote: https://github.com/utilitywarehouse/git-mirror +`), + wantError: true, + }, + { + name: "invalid - unexpected key in repositories", + yamlData: []byte(` +defaults: + root: /tmp/git-mirror + +repositories: + - remote: https://github.com/utilitywarehouse/git-mirror + not_valid: test +`), + wantError: true, + }, + { + name: "invalid - unexpected key in repository worktrees", + yamlData: []byte(` +defaults: + root: /tmp/git-mirror + +repositories: + - remote: https://github.com/utilitywarehouse/git-mirror + worktrees: + - link: aaa + not_valid: test +`), + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateConfig(tt.yamlData) + if (err != nil) != tt.wantError { + t.Errorf("validateConfig() error = %v, wantError %v", err, tt.wantError) + } + }) + } +} From b647db66d7d113b0a3cd765c1d120a67bb754309 Mon Sep 17 00:00:00 2001 From: DTLP Date: Wed, 9 Apr 2025 13:41:55 +0100 Subject: [PATCH 2/9] Improved validation and testing --- config.go | 83 +++++++++++++++++++++++++++++++------------------- config_test.go | 80 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 115 insertions(+), 48 deletions(-) diff --git a/config.go b/config.go index 5a7b8da..49273fb 100644 --- a/config.go +++ b/config.go @@ -198,62 +198,81 @@ func validateConfig(yamlData []byte) error { return err } - // defaults and repositories sections are mandatory - if _, ok := raw["defaults"]; !ok { - return fmt.Errorf("defaults config section is missing") + // skip checks if ".repositories" is empty + if raw["repositories"] == nil { + return nil } - if _, ok := raw["repositories"]; !ok { - return fmt.Errorf("repositories config section is missing") - } - - // check config sections for unexpected keys + // check all root config sections for unexpected keys allowedRepoPoolConfig := getAllowedKeys(mirror.RepoPoolConfig{}) if key := findUnexpectedKey(raw, allowedRepoPoolConfig); key != "" { return fmt.Errorf("unexpected key: .%v", key) } - // check "defaults" section - defaultsMap, ok := raw["defaults"].(map[string]interface{}) - if !ok { - return fmt.Errorf("defaults section is missing or not valid") - } - allowedDefaults := getAllowedKeys(mirror.DefaultConfig{}) + // check ".defaults" + if _, exists := raw["defaults"]; exists { + defaultsMap, ok := raw["defaults"].(map[string]interface{}) + if !ok { + return fmt.Errorf(".defaults config is not valid") + } + allowedDefaults := getAllowedKeys(mirror.DefaultConfig{}) - if key := findUnexpectedKey(defaultsMap, allowedDefaults); key != "" { - return fmt.Errorf("unexpected key: .defaults.%v", key) - } + if key := findUnexpectedKey(defaultsMap, allowedDefaults); key != "" { + return fmt.Errorf("unexpected key: .defaults.%v", key) + } - // check "auth" section in "defaults" - if authMap, ok := defaultsMap["auth"].(map[string]interface{}); ok { - allowedAuthKeys := getAllowedKeys(mirror.Auth{}) - if key := findUnexpectedKey(authMap, allowedAuthKeys); key != "" { - return fmt.Errorf("unexpected key: .defaults.auth.%v", key) + // check ".defaults.auth" + if authMap, ok := defaultsMap["auth"].(map[string]interface{}); ok { + allowedAuthKeys := getAllowedKeys(mirror.Auth{}) + if key := findUnexpectedKey(authMap, allowedAuthKeys); key != "" { + return fmt.Errorf("unexpected key: .defaults.auth.%v", key) + } } } - // check each repository in "repositories" section + // check ".repositories" + reposInterface, ok := raw["repositories"].([]interface{}) + if !ok { + return fmt.Errorf(".repositories config must be an array") + } + + // check each repository in ".repositories" allowedRepoKeys := getAllowedKeys(mirror.RepositoryConfig{}) - for _, repoInterface := range raw["repositories"].([]interface{}) { + + for _, repoInterface := range reposInterface { repoMap, ok := repoInterface.(map[string]interface{}) if !ok { - return fmt.Errorf("repositories config section is not valid") + return fmt.Errorf(".repositories config is not valid") } if key := findUnexpectedKey(repoMap, allowedRepoKeys); key != "" { return fmt.Errorf("unexpected key: .repositories[%v].%v", repoMap["remote"], key) } - // check each "worktrees" section in each repository - for _, worktreeInterface := range repoMap["worktrees"].([]interface{}) { - worktreeMap, ok := worktreeInterface.(map[string]interface{}) + // check "worktrees" in each repository + if worktreesInterface, exists := repoMap["worktrees"]; exists { + _, ok := repoMap["worktrees"].([]interface{}) if !ok { - return fmt.Errorf("worktrees config section is not valid in .repositories[%v]", repoMap["remote"]) + return fmt.Errorf("worktrees config must be an array in .repositories[%v]", repoMap["remote"]) } - allowedWorktreeKeys := getAllowedKeys(mirror.WorktreeConfig{}) - if key := findUnexpectedKey(worktreeMap, allowedWorktreeKeys); key != "" { - return fmt.Errorf("unexpected key: .repositories[%v].worktrees[%v].%v", repoMap["remote"], worktreeMap["link"], key) + for _, worktreeInterface := range worktreesInterface.([]interface{}) { + worktreeMap, ok := worktreeInterface.(map[string]interface{}) + if !ok { + return fmt.Errorf("worktrees config is not valid in .repositories[%v]", repoMap["remote"]) + } + + allowedWorktreeKeys := getAllowedKeys(mirror.WorktreeConfig{}) + if key := findUnexpectedKey(worktreeMap, allowedWorktreeKeys); key != "" { + return fmt.Errorf("unexpected key: .repositories[%v].worktrees[%v].%v", repoMap["remote"], worktreeMap["link"], key) + } + + // Check "pathspecs" in each worktree + if pathspecsInterface, exists := worktreeMap["pathspecs"]; exists { + if _, ok := pathspecsInterface.([]interface{}); !ok { + return fmt.Errorf("pathspecs config must be an array in .repositories[%v].worktrees[%v]", repoMap["remote"], worktreeMap["link"]) + } + } } } } diff --git a/config_test.go b/config_test.go index 15a233e..4850a1e 100644 --- a/config_test.go +++ b/config_test.go @@ -240,7 +240,7 @@ func Test_validateConfig(t *testing.T) { wantError bool }{ { - name: "valid", + name: "valid - full config", yamlData: []byte(` defaults: root: /tmp/git-mirror @@ -257,16 +257,43 @@ repositories: worktrees: - link: aaa ref: main - pathspecs: - - readme.md - link: bbb ref: main - remote: https://github.com/utilitywarehouse/another-repo + root: /some/other/location + link_root: /some/path + interval: 1m + mirror_timeout: 5m + git_gc: always + auth: + ssh_key_path: /some/other/location + ssh_known_hosts_path: /some/other/location worktrees: - - link: ccc + - link: alerts ref: main pathspecs: - - readme.md + - path + - path2/*.yaml +`), + wantError: false, + }, + { + name: "valid - empty config", + yamlData: []byte(` +`), + wantError: false, + }, + { + name: "valid - defaults config only", + yamlData: []byte(` +defaults: +`), + wantError: false, + }, + { + name: "valid - repositories config only", + yamlData: []byte(` +repositories: `), wantError: false, }, @@ -285,61 +312,81 @@ repositories: wantError: true, }, { - name: "invalid - defaults missing", + name: "invalid - unexpected key in defaults", yamlData: []byte(` +defaults: + root: /tmp/git-mirror + not_valid: test + repositories: - remote: https://github.com/utilitywarehouse/git-mirror `), wantError: true, }, { - name: "invalid - repositories missing", + name: "invalid - unexpected key in auth", yamlData: []byte(` defaults: root: /tmp/git-mirror + not_valid: test + auth: + not_valid: test + +repositories: + - remote: https://github.com/utilitywarehouse/git-mirror `), wantError: true, }, { - name: "invalid - unexpected key in defaults", + name: "invalid - unexpected key in repositories", yamlData: []byte(` defaults: root: /tmp/git-mirror - not_valid: test repositories: - remote: https://github.com/utilitywarehouse/git-mirror + not_valid: test `), wantError: true, }, { - name: "invalid - unexpected key in auth", + name: "invalid - unexpected key in repository worktrees", yamlData: []byte(` defaults: root: /tmp/git-mirror - not_valid: test - auth: - not_valid: test repositories: - remote: https://github.com/utilitywarehouse/git-mirror + worktrees: + - link: aaa + not_valid: test `), wantError: true, }, { - name: "invalid - unexpected key in repositories", + name: "invalid - repositories is not an array", + yamlData: []byte(` +defaults: + root: /tmp/git-mirror + +repositories: https://github.com/utilitywarehouse/git-mirror +`), + wantError: true, + }, + { + name: "invalid - worktrees is not an array", yamlData: []byte(` defaults: root: /tmp/git-mirror repositories: - remote: https://github.com/utilitywarehouse/git-mirror - not_valid: test + worktrees: test `), wantError: true, }, { - name: "invalid - unexpected key in repository worktrees", + name: "invalid - pathspecs is not an array", yamlData: []byte(` defaults: root: /tmp/git-mirror @@ -349,6 +396,7 @@ repositories: worktrees: - link: aaa not_valid: test + pathspecs: readme.md `), wantError: true, }, From 11ee84a5055774f5e11bd6aa9e1c0a28df5f006f Mon Sep 17 00:00:00 2001 From: DTLP Date: Wed, 9 Apr 2025 14:12:39 +0100 Subject: [PATCH 3/9] Fix yaml identation and update validation flow --- config.go | 20 +++++++++++--------- config_test.go | 14 +++++++------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/config.go b/config.go index 49273fb..9c51308 100644 --- a/config.go +++ b/config.go @@ -198,24 +198,21 @@ func validateConfig(yamlData []byte) error { return err } - // skip checks if ".repositories" is empty - if raw["repositories"] == nil { - return nil - } + allowedRepoPoolConfig := getAllowedKeys(mirror.RepoPoolConfig{}) // check all root config sections for unexpected keys - allowedRepoPoolConfig := getAllowedKeys(mirror.RepoPoolConfig{}) if key := findUnexpectedKey(raw, allowedRepoPoolConfig); key != "" { return fmt.Errorf("unexpected key: .%v", key) } - // check ".defaults" - if _, exists := raw["defaults"]; exists { + // check ".defaults" if it's not empty + if raw["defaults"] != nil { + allowedDefaults := getAllowedKeys(mirror.DefaultConfig{}) + defaultsMap, ok := raw["defaults"].(map[string]interface{}) if !ok { return fmt.Errorf(".defaults config is not valid") } - allowedDefaults := getAllowedKeys(mirror.DefaultConfig{}) if key := findUnexpectedKey(defaultsMap, allowedDefaults); key != "" { return fmt.Errorf("unexpected key: .defaults.%v", key) @@ -230,6 +227,11 @@ func validateConfig(yamlData []byte) error { } } + // skip further checks if ".repositories" is empty + if raw["repositories"] == nil { + return nil + } + // check ".repositories" reposInterface, ok := raw["repositories"].([]interface{}) if !ok { @@ -238,6 +240,7 @@ func validateConfig(yamlData []byte) error { // check each repository in ".repositories" allowedRepoKeys := getAllowedKeys(mirror.RepositoryConfig{}) + allowedWorktreeKeys := getAllowedKeys(mirror.WorktreeConfig{}) for _, repoInterface := range reposInterface { repoMap, ok := repoInterface.(map[string]interface{}) @@ -262,7 +265,6 @@ func validateConfig(yamlData []byte) error { return fmt.Errorf("worktrees config is not valid in .repositories[%v]", repoMap["remote"]) } - allowedWorktreeKeys := getAllowedKeys(mirror.WorktreeConfig{}) if key := findUnexpectedKey(worktreeMap, allowedWorktreeKeys); key != "" { return fmt.Errorf("unexpected key: .repositories[%v].worktrees[%v].%v", repoMap["remote"], worktreeMap["link"], key) } diff --git a/config_test.go b/config_test.go index 4850a1e..c15bfb2 100644 --- a/config_test.go +++ b/config_test.go @@ -301,7 +301,7 @@ repositories: name: "invalid - unexpected key", yamlData: []byte(` not-valid: - test: test + test: test defaults: root: /tmp/git-mirror @@ -316,7 +316,7 @@ repositories: yamlData: []byte(` defaults: root: /tmp/git-mirror - not_valid: test + not_valid: test repositories: - remote: https://github.com/utilitywarehouse/git-mirror @@ -328,7 +328,7 @@ repositories: yamlData: []byte(` defaults: root: /tmp/git-mirror - not_valid: test + not_valid: test auth: not_valid: test @@ -345,7 +345,7 @@ defaults: repositories: - remote: https://github.com/utilitywarehouse/git-mirror - not_valid: test + not_valid: test `), wantError: true, }, @@ -359,7 +359,7 @@ repositories: - remote: https://github.com/utilitywarehouse/git-mirror worktrees: - link: aaa - not_valid: test + not_valid: test `), wantError: true, }, @@ -395,8 +395,8 @@ repositories: - remote: https://github.com/utilitywarehouse/git-mirror worktrees: - link: aaa - not_valid: test - pathspecs: readme.md + not_valid: test + pathspecs: readme.md `), wantError: true, }, From f5517044cf0ac698f200d23eb0caad317d18859d Mon Sep 17 00:00:00 2001 From: DTLP Date: Wed, 9 Apr 2025 14:27:14 +0100 Subject: [PATCH 4/9] Move vars up, reduce nesting and other improvements --- config.go | 55 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/config.go b/config.go index 9c51308..ec5399e 100644 --- a/config.go +++ b/config.go @@ -35,6 +35,11 @@ var ( Name: "git_mirror_config_last_reload_success_timestamp_seconds", Help: "Timestamp of the last successful configuration reload.", }) + allowedRepoPoolConfig = getAllowedKeys(mirror.RepoPoolConfig{}) + allowedDefaults = getAllowedKeys(mirror.DefaultConfig{}) + allowedAuthKeys = getAllowedKeys(mirror.Auth{}) + allowedRepoKeys = getAllowedKeys(mirror.RepositoryConfig{}) + allowedWorktreeKeys = getAllowedKeys(mirror.WorktreeConfig{}) ) // WatchConfig polls the config file every interval and reloads if modified @@ -198,8 +203,6 @@ func validateConfig(yamlData []byte) error { return err } - allowedRepoPoolConfig := getAllowedKeys(mirror.RepoPoolConfig{}) - // check all root config sections for unexpected keys if key := findUnexpectedKey(raw, allowedRepoPoolConfig); key != "" { return fmt.Errorf("unexpected key: .%v", key) @@ -207,8 +210,6 @@ func validateConfig(yamlData []byte) error { // check ".defaults" if it's not empty if raw["defaults"] != nil { - allowedDefaults := getAllowedKeys(mirror.DefaultConfig{}) - defaultsMap, ok := raw["defaults"].(map[string]interface{}) if !ok { return fmt.Errorf(".defaults config is not valid") @@ -220,14 +221,13 @@ func validateConfig(yamlData []byte) error { // check ".defaults.auth" if authMap, ok := defaultsMap["auth"].(map[string]interface{}); ok { - allowedAuthKeys := getAllowedKeys(mirror.Auth{}) if key := findUnexpectedKey(authMap, allowedAuthKeys); key != "" { return fmt.Errorf("unexpected key: .defaults.auth.%v", key) } } } - // skip further checks if ".repositories" is empty + // skip further config checks if ".repositories" is empty if raw["repositories"] == nil { return nil } @@ -239,8 +239,6 @@ func validateConfig(yamlData []byte) error { } // check each repository in ".repositories" - allowedRepoKeys := getAllowedKeys(mirror.RepositoryConfig{}) - allowedWorktreeKeys := getAllowedKeys(mirror.WorktreeConfig{}) for _, repoInterface := range reposInterface { repoMap, ok := repoInterface.(map[string]interface{}) @@ -252,28 +250,31 @@ func validateConfig(yamlData []byte) error { return fmt.Errorf("unexpected key: .repositories[%v].%v", repoMap["remote"], key) } + // skip further repository checks if "worktrees" is empty + if repoMap["worktrees"] == nil { + continue + } + // check "worktrees" in each repository - if worktreesInterface, exists := repoMap["worktrees"]; exists { - _, ok := repoMap["worktrees"].([]interface{}) + worktreesInterface, ok := repoMap["worktrees"].([]interface{}) + if !ok { + return fmt.Errorf("worktrees config must be an array in .repositories[%v]", repoMap["remote"]) + } + + for _, worktreeInterface := range worktreesInterface { + worktreeMap, ok := worktreeInterface.(map[string]interface{}) if !ok { - return fmt.Errorf("worktrees config must be an array in .repositories[%v]", repoMap["remote"]) + return fmt.Errorf("worktrees config is not valid in .repositories[%v]", repoMap["remote"]) } - for _, worktreeInterface := range worktreesInterface.([]interface{}) { - worktreeMap, ok := worktreeInterface.(map[string]interface{}) - if !ok { - return fmt.Errorf("worktrees config is not valid in .repositories[%v]", repoMap["remote"]) - } - - if key := findUnexpectedKey(worktreeMap, allowedWorktreeKeys); key != "" { - return fmt.Errorf("unexpected key: .repositories[%v].worktrees[%v].%v", repoMap["remote"], worktreeMap["link"], key) - } + if key := findUnexpectedKey(worktreeMap, allowedWorktreeKeys); key != "" { + return fmt.Errorf("unexpected key: .repositories[%v].worktrees[%v].%v", repoMap["remote"], worktreeMap["link"], key) + } - // Check "pathspecs" in each worktree - if pathspecsInterface, exists := worktreeMap["pathspecs"]; exists { - if _, ok := pathspecsInterface.([]interface{}); !ok { - return fmt.Errorf("pathspecs config must be an array in .repositories[%v].worktrees[%v]", repoMap["remote"], worktreeMap["link"]) - } + // Check "pathspecs" in each worktree + if pathspecsInterface, exists := worktreeMap["pathspecs"]; exists { + if _, ok := pathspecsInterface.([]interface{}); !ok { + return fmt.Errorf("pathspecs config must be an array in .repositories[%v].worktrees[%v]", repoMap["remote"], worktreeMap["link"]) } } } @@ -298,8 +299,8 @@ func getAllowedKeys(config interface{}) []string { return allowedKeys } -func findUnexpectedKey(raw interface{}, allowedKeys []string) string { - for key := range raw.(map[string]interface{}) { +func findUnexpectedKey(raw map[string]interface{}, allowedKeys []string) string { + for key := range raw { if !slices.Contains(allowedKeys, key) { return key } From f2976118ec6fa8dc2523ac9f6cf9e57823fe054c Mon Sep 17 00:00:00 2001 From: DTLP Date: Wed, 9 Apr 2025 14:30:39 +0100 Subject: [PATCH 5/9] remove empty line --- config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/config.go b/config.go index ec5399e..1e97206 100644 --- a/config.go +++ b/config.go @@ -239,7 +239,6 @@ func validateConfig(yamlData []byte) error { } // check each repository in ".repositories" - for _, repoInterface := range reposInterface { repoMap, ok := repoInterface.(map[string]interface{}) if !ok { From 65c5ce020cb6d2399bc029700e279626eeaa76b5 Mon Sep 17 00:00:00 2001 From: DTLP Date: Wed, 9 Apr 2025 14:34:33 +0100 Subject: [PATCH 6/9] use index instead of link --- config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index 1e97206..920bba7 100644 --- a/config.go +++ b/config.go @@ -260,14 +260,14 @@ func validateConfig(yamlData []byte) error { return fmt.Errorf("worktrees config must be an array in .repositories[%v]", repoMap["remote"]) } - for _, worktreeInterface := range worktreesInterface { + for i, worktreeInterface := range worktreesInterface { worktreeMap, ok := worktreeInterface.(map[string]interface{}) if !ok { return fmt.Errorf("worktrees config is not valid in .repositories[%v]", repoMap["remote"]) } if key := findUnexpectedKey(worktreeMap, allowedWorktreeKeys); key != "" { - return fmt.Errorf("unexpected key: .repositories[%v].worktrees[%v].%v", repoMap["remote"], worktreeMap["link"], key) + return fmt.Errorf("unexpected key: .repositories[%v].worktrees[%v].%v", repoMap["remote"], i, key) } // Check "pathspecs" in each worktree From 557e55ccdb5f2e700921b158ae16fba27fcbfa8f Mon Sep 17 00:00:00 2001 From: DTLP Date: Wed, 9 Apr 2025 14:35:52 +0100 Subject: [PATCH 7/9] use index instead of link --- config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.go b/config.go index 920bba7..54ab772 100644 --- a/config.go +++ b/config.go @@ -273,7 +273,7 @@ func validateConfig(yamlData []byte) error { // Check "pathspecs" in each worktree if pathspecsInterface, exists := worktreeMap["pathspecs"]; exists { if _, ok := pathspecsInterface.([]interface{}); !ok { - return fmt.Errorf("pathspecs config must be an array in .repositories[%v].worktrees[%v]", repoMap["remote"], worktreeMap["link"]) + return fmt.Errorf("pathspecs config must be an array in .repositories[%v].worktrees[%v]", repoMap["remote"], i) } } } From b39c5b642ea2da333ab42a491b06b7861167ef3d Mon Sep 17 00:00:00 2001 From: DTLP Date: Wed, 9 Apr 2025 14:37:48 +0100 Subject: [PATCH 8/9] fix auth test --- config_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/config_test.go b/config_test.go index c15bfb2..f7239ae 100644 --- a/config_test.go +++ b/config_test.go @@ -328,7 +328,6 @@ repositories: yamlData: []byte(` defaults: root: /tmp/git-mirror - not_valid: test auth: not_valid: test From f75bb32b3233137920b2bdc59f846b7dcc8333e9 Mon Sep 17 00:00:00 2001 From: DTLP Date: Wed, 9 Apr 2025 14:40:45 +0100 Subject: [PATCH 9/9] Rename func to validateConfigYaml --- config.go | 4 ++-- config_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.go b/config.go index 54ab772..ea0fde1 100644 --- a/config.go +++ b/config.go @@ -183,7 +183,7 @@ func parseConfigFile(path string) (*mirror.RepoPoolConfig, error) { return nil, err } - err = validateConfig([]byte(yamlFile)) + err = validateConfigYaml([]byte(yamlFile)) if err != nil { return nil, err } @@ -197,7 +197,7 @@ func parseConfigFile(path string) (*mirror.RepoPoolConfig, error) { return conf, nil } -func validateConfig(yamlData []byte) error { +func validateConfigYaml(yamlData []byte) error { var raw map[string]interface{} if err := yaml.Unmarshal(yamlData, &raw); err != nil { return err diff --git a/config_test.go b/config_test.go index f7239ae..4f176e1 100644 --- a/config_test.go +++ b/config_test.go @@ -233,7 +233,7 @@ func Test_diffWorktrees(t *testing.T) { } } -func Test_validateConfig(t *testing.T) { +func Test_validateConfigYaml(t *testing.T) { tests := []struct { name string yamlData []byte @@ -403,9 +403,9 @@ repositories: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := validateConfig(tt.yamlData) + err := validateConfigYaml(tt.yamlData) if (err != nil) != tt.wantError { - t.Errorf("validateConfig() error = %v, wantError %v", err, tt.wantError) + t.Errorf("validateConfigYaml() error = %v, wantError %v", err, tt.wantError) } }) }