Skip to content

Commit 082be15

Browse files
committed
feat: use array with module field for allowed and blocked config
Replace map-based Allowed/Blocked config types with ordered slices of AllowedModule/BlockedModule structs, each with an explicit module field. Rename match_type to match-type to follow YAML kebab-case conventions.
1 parent de3b738 commit 082be15

22 files changed

Lines changed: 233 additions & 203 deletions

File tree

README.md

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,30 +39,30 @@ Results can be exported to different report formats. Which can be imported into
3939
# When this section is non-empty, any module not matched by an entry is blocked.
4040
# When omitted entirely, all modules are allowed except those in the blocked list.
4141
allowed:
42-
# Exact match (default when match_type is omitted).
43-
go.yaml.in/yaml/v4:
44-
github.com/go-xmlfmt/xmlfmt:
42+
# Exact match (default when match-type is omitted).
43+
- module: go.yaml.in/yaml/v4
44+
- module: github.com/go-xmlfmt/xmlfmt
4545

4646
# version constrains which versions of the module are allowed.
4747
# Uses semver constraint syntax (e.g. ">= 1.0.0", "~1.2", "== 2.5.0").
48-
github.com/confluentinc/confluent-kafka-go/v2:
48+
- module: github.com/confluentinc/confluent-kafka-go/v2
4949
version: "== 2.5.0"
5050

51-
# match_type controls how the key is matched against module paths.
51+
# match-type controls how the module is matched against module paths.
5252
# Options: exact (default), prefix, regex
53-
github.com/kubernetes:
54-
match_type: prefix
55-
github.com/apache/arrow-go:
56-
match_type: prefix
57-
"github.com/somecompany/.*":
58-
match_type: regex
53+
- module: github.com/kubernetes
54+
match-type: prefix
55+
- module: github.com/apache/arrow-go
56+
match-type: prefix
57+
- module: "github.com/somecompany/.*"
58+
match-type: regex
5959

6060
# blocked defines modules that are not permitted as direct dependencies.
6161
blocked:
62-
github.com/uudashr/go-module:
63-
# match_type controls how the key is matched against module paths.
62+
- module: github.com/uudashr/go-module
63+
# match-type controls how the module is matched against module paths.
6464
# Options: exact (default), prefix, regex
65-
match_type: exact
65+
match-type: exact
6666

6767
# recommendations lists alternative modules to suggest in the lint error.
6868
recommendations:
@@ -71,18 +71,18 @@ blocked:
7171
# reason is a human-readable explanation appended to the lint error.
7272
reason: "`mod` is the official go.mod parser library."
7373

74-
github.com/mitchellh/go-homedir:
74+
- module: github.com/mitchellh/go-homedir
7575
# version constrains which versions of the module are blocked.
7676
# Uses semver constraint syntax. When omitted, all versions are blocked.
7777
version: "<= 1.1.0"
7878
reason: "old versions have a known bug."
7979

80-
"github.com/badcompany/.*":
81-
match_type: regex
80+
- module: "github.com/badcompany/.*"
81+
match-type: regex
8282
reason: "No badcompany packages are permitted."
8383

84-
# Blocks 'replace' directives using local filesystem paths to prevent
85-
# accidental commits of dev overrides. Sibling modules in multi-module
84+
# Blocks 'replace' directives using local filesystem paths to prevent
85+
# accidental commits of dev overrides. Sibling modules in multi-module
8686
# repos are automatically detected and permitted.
8787
local_replace_directives: true
8888
```
@@ -93,15 +93,16 @@ local_replace_directives: true
9393
9494
| Field | Type | Default | Description |
9595
|---|---|---|---|
96-
| `allowed` | map | *(none)* | Modules that are permitted. When non-empty, anything not matched is blocked. |
97-
| `blocked` | map | *(none)* | Modules that are explicitly blocked. |
96+
| `allowed` | list | *(none)* | Modules that are permitted. When non-empty, anything not matched is blocked. |
97+
| `blocked` | list | *(none)* | Modules that are explicitly blocked. |
9898
| `local_replace_directives` | bool | `false` | Block any module whose `replace` directive points to a local filesystem path. Multi-module repo aware: sibling modules whose replacement path contains a matching `go.mod` are not blocked. |
9999

100-
#### `allowed` / `blocked` rule fields
100+
#### `allowed` / `blocked` entry fields
101101

102102
| Field | Type | Description |
103103
|---|---|---|
104-
| `match_type` | `exact` \| `prefix` \| `regex` | How the rule key is matched against a module path. Defaults to `exact`. |
104+
| `module` | string | The module path to match against. |
105+
| `match-type` | `exact` \| `prefix` \| `regex` | How `module` is matched against dependency paths. Defaults to `exact`. |
105106
| `version` | semver constraint string | Restricts the rule to specific versions (e.g. `<= 1.2.0`, `>= 2.0.0`). When omitted, all versions match. |
106107
| `recommendations` | list of module paths | *(blocked only)* Alternative modules to suggest in the lint error. If the module being linted is itself in this list, the block is skipped. |
107108
| `reason` | string | *(blocked only)* Human-readable explanation appended to the lint error. |

allowed.go

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ import (
66
"github.com/Masterminds/semver/v3"
77
)
88

9-
// Allowed is a map of modules or prefixes that are allowed to be used.
10-
type Allowed map[string]AllowedRule
9+
// Allowed is a list of modules that are allowed to be used.
10+
type Allowed []AllowedModule
1111

12-
// AllowedRule defines the options for an allowed module.
13-
type AllowedRule struct {
14-
MatchType MatchType `yaml:"match_type"`
12+
// AllowedModule is a single entry in the allowed list.
13+
type AllowedModule struct {
14+
Module string `yaml:"module"`
15+
MatchType MatchType `yaml:"match-type"`
1516
Version *semver.Constraints `yaml:"version"`
1617
Matcher Matcher `yaml:"-"`
1718
}
1819

1920
// CheckVersion returns true if the module version matches the allowed constraint,
2021
// or if no version constraint is specified.
21-
func (r *AllowedRule) CheckVersion(moduleVersion string) (bool, error) {
22+
func (r *AllowedModule) CheckVersion(moduleVersion string) (bool, error) {
2223
if r.Version == nil {
2324
return true, nil
2425
}
@@ -32,18 +33,10 @@ func (r *AllowedRule) CheckVersion(moduleVersion string) (bool, error) {
3233
}
3334

3435
// NotAllowedReason returns the reason why the module version is not allowed.
35-
func (r *AllowedRule) NotAllowedReason(moduleVersion string) string {
36+
func (r *AllowedModule) NotAllowedReason(moduleVersion string) string {
3637
if r == nil || r.Version == nil {
3738
return "the module is not in the allowed modules list."
3839
}
3940

4041
return fmt.Sprintf("version `%s` does not meet the allowed version constraint `%s`.", moduleVersion, r.Version)
4142
}
42-
43-
func (r *AllowedRule) ruleMatchType() MatchType {
44-
return r.MatchType
45-
}
46-
47-
func (r *AllowedRule) ruleMatcher() Matcher { //nolint:ireturn
48-
return r.Matcher
49-
}

blocked.go

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import (
77
"github.com/Masterminds/semver/v3"
88
)
99

10-
// Blocked is a map of modules that are blocked and not to be used.
11-
type Blocked map[string]BlockedRule
10+
// Blocked is a list of modules that are blocked and not to be used.
11+
type Blocked []BlockedModule
1212

13-
// BlockedRule represents a rule for a blocked module.
14-
type BlockedRule struct {
15-
MatchType MatchType `yaml:"match_type"`
13+
// BlockedModule is a single entry in the blocked list.
14+
type BlockedModule struct {
15+
Module string `yaml:"module"`
16+
MatchType MatchType `yaml:"match-type"`
1617
Recommendations []string `yaml:"recommendations"`
1718
Reason string `yaml:"reason"`
1819
Version *semver.Constraints `yaml:"version"`
@@ -21,7 +22,7 @@ type BlockedRule struct {
2122

2223
// CheckVersion returns true if the module version matches the blocked constraint.
2324
// If no version constraint is specified, all versions are considered blocked.
24-
func (r *BlockedRule) CheckVersion(moduleVersion string) (bool, error) {
25+
func (r *BlockedModule) CheckVersion(moduleVersion string) (bool, error) {
2526
if r.Version == nil {
2627
return true, nil
2728
}
@@ -35,7 +36,7 @@ func (r *BlockedRule) CheckVersion(moduleVersion string) (bool, error) {
3536
}
3637

3738
// BlockReason returns the reason why the module or version is blocked.
38-
func (r *BlockedRule) BlockReason(currentModuleVersion string) string {
39+
func (r *BlockedModule) BlockReason(currentModuleVersion string) string {
3940
var sb strings.Builder
4041

4142
if r.Version != nil {
@@ -74,7 +75,7 @@ func (r *BlockedRule) BlockReason(currentModuleVersion string) string {
7475
}
7576

7677
// IsCurrentModuleARecommendation returns true if the current module is in the Recommendations list.
77-
func (r *BlockedRule) IsCurrentModuleARecommendation(currentModuleName string) bool {
78+
func (r *BlockedModule) IsCurrentModuleARecommendation(currentModuleName string) bool {
7879
if r == nil {
7980
return false
8081
}
@@ -89,18 +90,10 @@ func (r *BlockedRule) IsCurrentModuleARecommendation(currentModuleName string) b
8990
}
9091

9192
// HasRecommendations returns true if the blocked package has recommended modules.
92-
func (r *BlockedRule) HasRecommendations() bool {
93+
func (r *BlockedModule) HasRecommendations() bool {
9394
if r == nil {
9495
return false
9596
}
9697

9798
return len(r.Recommendations) > 0
9899
}
99-
100-
func (r *BlockedRule) ruleMatchType() MatchType {
101-
return r.MatchType
102-
}
103-
104-
func (r *BlockedRule) ruleMatcher() Matcher { //nolint:ireturn
105-
return r.Matcher
106-
}

cmd/gomodguard/go.mod

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/Masterminds/semver/v3 v3.4.0
99
github.com/mitchellh/go-homedir v1.1.0
1010
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d
11-
github.com/ryancurrah/gomodguard/v2 v2.0.0
11+
github.com/ryancurrah/gomodguard/v2 v2.0.1
1212
github.com/stretchr/testify v1.11.1
1313
go.yaml.in/yaml/v4 v4.0.0-rc.4
1414
)
@@ -20,4 +20,7 @@ require (
2020
gopkg.in/yaml.v3 v3.0.1 // indirect
2121
)
2222

23-
retract v2.0.0
23+
retract (
24+
v2.0.1
25+
v2.0.0
26+
)

cmd/gomodguard/internal/cli/cli.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"flag"
77
"fmt"
88
"log"
9-
"maps"
109
"os"
1110
"path/filepath"
1211
"runtime/debug"
@@ -114,8 +113,21 @@ func Run() int {
114113
logger.Fatalf("error: %s", err)
115114
}
116115

117-
logger.Printf("info: allowed modules, %+v", slices.Sorted(maps.Keys(config.Allowed)))
118-
logger.Printf("info: blocked modules, %+v", slices.Sorted(maps.Keys(config.Blocked)))
116+
allowedModuleNames := make([]string, len(config.Allowed))
117+
for i, m := range config.Allowed {
118+
allowedModuleNames[i] = m.Module
119+
}
120+
121+
blockedModuleNames := make([]string, len(config.Blocked))
122+
for i, m := range config.Blocked {
123+
blockedModuleNames[i] = m.Module
124+
}
125+
126+
slices.Sort(allowedModuleNames)
127+
slices.Sort(blockedModuleNames)
128+
129+
logger.Printf("info: allowed modules, %+v", allowedModuleNames)
130+
logger.Printf("info: blocked modules, %+v", blockedModuleNames)
119131

120132
results := processor.ProcessFiles(filteredFiles)
121133

cmd/gomodguard/internal/cli/migrate.go

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,36 +52,48 @@ func MigrateConfig(filename string) int {
5252
}
5353

5454
v2 := gomodguard.Configuration{
55-
Allowed: make(gomodguard.Allowed),
56-
Blocked: make(gomodguard.Blocked),
5755
LocalReplaceDirectives: v1.Blocked.LocalReplaceDirectives,
5856
}
5957

6058
for _, mod := range v1.Allowed.Modules {
61-
v2.Allowed[mod] = gomodguard.AllowedRule{MatchType: gomodguard.ExactMatch}
59+
v2.Allowed = append(v2.Allowed, gomodguard.AllowedModule{
60+
Module: mod,
61+
MatchType: gomodguard.ExactMatch,
62+
})
6263
}
6364

6465
for _, pref := range v1.Allowed.Prefixes {
65-
v2.Allowed[pref] = gomodguard.AllowedRule{MatchType: gomodguard.PrefixMatch}
66+
v2.Allowed = append(v2.Allowed, gomodguard.AllowedModule{
67+
Module: pref,
68+
MatchType: gomodguard.PrefixMatch,
69+
})
6670
}
6771

6872
for _, dom := range v1.Allowed.Domains {
69-
v2.Allowed[dom] = gomodguard.AllowedRule{MatchType: gomodguard.PrefixMatch}
73+
v2.Allowed = append(v2.Allowed, gomodguard.AllowedModule{
74+
Module: dom,
75+
MatchType: gomodguard.PrefixMatch,
76+
})
7077
}
7178

79+
// Build a temporary map to merge modules and versions by module name.
80+
blockedIndex := make(map[string]int)
81+
7282
for _, modMap := range v1.Blocked.Modules {
7383
for modName, bm := range modMap {
74-
rule := v2.Blocked[modName]
75-
rule.MatchType = gomodguard.ExactMatch
76-
rule.Recommendations = bm.Recommendations
77-
rule.Reason = bm.Reason
78-
v2.Blocked[modName] = rule
84+
v2.Blocked = append(v2.Blocked, gomodguard.BlockedModule{
85+
Module: modName,
86+
MatchType: gomodguard.ExactMatch,
87+
Recommendations: bm.Recommendations,
88+
Reason: bm.Reason,
89+
})
90+
blockedIndex[modName] = len(v2.Blocked) - 1
7991
}
8092
}
8193

8294
for _, versMap := range v1.Blocked.Versions {
8395
for modName, bv := range versMap {
84-
rule := v2.Blocked[modName]
96+
var constraint *semver.Constraints
8597

8698
if bv.Version != "" {
8799
c, err := semver.NewConstraint(bv.Version)
@@ -90,24 +102,28 @@ func MigrateConfig(filename string) int {
90102
return 1
91103
}
92104

93-
rule.Version = c
94-
}
95-
96-
if bv.Reason != "" {
97-
rule.Reason = bv.Reason
105+
constraint = c
98106
}
99107

100-
rule.MatchType = gomodguard.ExactMatch
101-
v2.Blocked[modName] = rule
102-
}
103-
}
108+
if idx, ok := blockedIndex[modName]; ok {
109+
// Merge version info into existing entry.
110+
entry := v2.Blocked[idx]
111+
entry.Version = constraint
104112

105-
if len(v2.Allowed) == 0 {
106-
v2.Allowed = nil
107-
}
113+
if bv.Reason != "" {
114+
entry.Reason = bv.Reason
115+
}
108116

109-
if len(v2.Blocked) == 0 {
110-
v2.Blocked = nil
117+
v2.Blocked[idx] = entry
118+
} else {
119+
v2.Blocked = append(v2.Blocked, gomodguard.BlockedModule{
120+
Module: modName,
121+
MatchType: gomodguard.ExactMatch,
122+
Version: constraint,
123+
Reason: bv.Reason,
124+
})
125+
}
126+
}
111127
}
112128

113129
out, err := yaml.Marshal(v2)

cmd/gomodguard/internal/cli/processor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func TestProcessorYAMLConfig(t *testing.T) { //nolint:funlen
8080
"allowed version - invalid constraint is caught at parse time": {
8181
exampleDir: examplesDir + "allowedversion",
8282
wantParseErr: true,
83-
invalidYAML: "allowed:\n github.com/Masterminds/semver/v3:\n version: not-a-valid-constraint\n",
83+
invalidYAML: "allowed:\n - module: github.com/Masterminds/semver/v3\n version: not-a-valid-constraint\n",
8484
},
8585
"empty allow list - allows all modules": {
8686
exampleDir: examplesDir + "emptyallowlist",
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
allowed:
2-
gopkg.in/yaml.v3:
3-
github.com/go-xmlfmt/xmlfmt:
4-
github.com/Masterminds/semver/v3:
5-
golang.org:
6-
match_type: prefix
2+
- module: gopkg.in/yaml.v3
3+
- module: github.com/go-xmlfmt/xmlfmt
4+
- module: github.com/Masterminds/semver/v3
5+
- module: golang.org
6+
match-type: prefix
77

88
blocked:
9-
github.com/uudashr/go-module:
9+
- module: github.com/uudashr/go-module
1010
recommendations:
1111
- golang.org/x/mod
1212
reason: "`mod` is the official go.mod parser library."
13-
github.com/gofrs/uuid:
13+
- module: github.com/gofrs/uuid
1414
recommendations:
1515
- github.com/ryancurrah/gomodguard
1616
reason: "testing if module is not blocked when it is recommended."
17-
github.com/mitchellh/go-homedir:
17+
- module: github.com/mitchellh/go-homedir
1818
version: "<= 1.1.0"
1919
reason: "testing if blocked version constraint works."
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
allowed:
2-
github.com/Masterminds/semver/v3:
2+
- module: github.com/Masterminds/semver/v3
33
version: ">= 3.2.0"

0 commit comments

Comments
 (0)