@@ -3,70 +3,106 @@ package main
3
3
import (
4
4
"encoding/json"
5
5
"fmt"
6
- "io"
7
- "net/http"
8
6
"os"
7
+ "os/exec"
9
8
"regexp"
10
9
"strings"
11
10
12
- "sigs.k8s.io/controller-runtime/pkg/log/zap"
11
+ "go.uber.org/zap"
12
+ "sigs.k8s.io/yaml"
13
13
)
14
14
15
15
const (
16
- kkModUrl = "https://raw.githubusercontent.com/kubernetes/kubernetes/master/go.mod"
17
- modFile = "./go.mod"
16
+ modFile = "./go.mod"
18
17
)
19
18
20
- var (
21
- cleanMods = regexp .MustCompile (`\t| *//.*` )
22
- modDelimStart = regexp .MustCompile (`^require.*` )
23
- modDelimEnd = ")"
24
- )
19
+ type config struct {
20
+ UpstreamRefs []string `yaml:"upstreamRefs"`
21
+ ExcludedModules []string `yaml:"excludedModules"`
22
+ }
23
+
24
+ type upstream struct {
25
+ Ref string `json:"ref"`
26
+ Version string `json:"version"`
27
+ }
25
28
29
+ // representation of an out of sync module
26
30
type oosMod struct {
27
- Name string `json:"name"`
28
- Version string `json:"version"`
29
- UpstreamVersion string `json:"upstreamVersion "`
31
+ Name string `json:"name"`
32
+ Version string `json:"version"`
33
+ Upstreams [] upstream `json:"upstreams "`
30
34
}
31
35
32
36
func main () {
33
- logger := zap .New ()
34
- excludedMods := getExcludedMods ()
37
+ l , _ := zap .NewProduction ()
38
+ logger := l . Sugar ()
35
39
36
- // 1. kk
37
- resp , err := http .Get (kkModUrl )
38
- if err != nil {
39
- panic (err .Error ())
40
+ if len (os .Args ) < 2 {
41
+ fmt .Printf ("USAGE: %s [PATH_TO_CONFIG_FILE]\n " , os .Args [0 ])
42
+ os .Exit (1 )
40
43
}
41
44
42
- b , err := io .ReadAll (resp .Body )
45
+ // --- 1. parse config
46
+ b , err := os .ReadFile (os .Args [1 ])
43
47
if err != nil {
44
- panic (err .Error ())
48
+ logger .Fatal (err .Error ())
49
+ }
50
+
51
+ cfg := new (config )
52
+ if err := yaml .Unmarshal (b , cfg ); err != nil {
53
+ logger .Fatal (err .Error ())
54
+ }
55
+
56
+ excludedMods := make (map [string ]any )
57
+ for _ , mod := range cfg .ExcludedModules {
58
+ excludedMods [mod ] = nil
45
59
}
46
60
47
- kkMods := parseMod (b )
61
+ // --- 2. project mods
62
+ deps , err := parseModFile ()
63
+ if err != nil {
64
+ logger .Fatal (err .Error ())
65
+ }
48
66
49
- // 2. go.mod
50
- b , err = os . ReadFile ( modFile )
67
+ // --- 3. upstream mods (holding upstream refs)
68
+ upstreamModGraph , err := getUpstreamModGraph ( cfg . UpstreamRefs )
51
69
if err != nil {
52
- return
70
+ logger . Fatal ( err . Error ())
53
71
}
54
72
55
73
oosMods := make ([]oosMod , 0 )
56
74
57
- mods := parseMod (b )
58
- for k , v := range mods {
59
- if _ , ok := excludedMods [k ]; ok {
60
- logger .Info (fmt .Sprintf ("skipped module: %s" , k ))
75
+ // --- 4. validate
76
+ // for each module in our project,
77
+ // if it matches an upstream module,
78
+ // then for each upstream module,
79
+ // if project module version doesn't match upstream version,
80
+ // then we add the version and the ref to the list of out of sync modules.
81
+ for mod , version := range deps {
82
+ if _ , ok := excludedMods [mod ]; ok {
83
+ logger .Infof ("skipped excluded module: %s" , mod )
61
84
continue
62
85
}
63
86
64
- if upstreamVersion , ok := kkMods [k ]; ok && v != upstreamVersion {
65
- oosMods = append (oosMods , oosMod {
66
- Name : k ,
67
- Version : v ,
68
- UpstreamVersion : upstreamVersion ,
69
- })
87
+ if versionToRef , ok := upstreamModGraph [mod ]; ok {
88
+ upstreams := make ([]upstream , 0 )
89
+
90
+ for upstreamVersion , upstreamRef := range versionToRef {
91
+ if version != upstreamVersion {
92
+ upstreams = append (upstreams , upstream {
93
+ Ref : upstreamRef ,
94
+ Version : upstreamVersion ,
95
+ })
96
+ }
97
+ }
98
+
99
+ if len (upstreams ) > 0 {
100
+ oosMods = append (oosMods , oosMod {
101
+ Name : mod ,
102
+ Version : version ,
103
+ Upstreams : upstreams ,
104
+ })
105
+ }
70
106
}
71
107
}
72
108
@@ -84,7 +120,18 @@ func main() {
84
120
os .Exit (1 )
85
121
}
86
122
87
- func parseMod (b []byte ) map [string ]string {
123
+ var (
124
+ cleanMods = regexp .MustCompile (`\t| *//.*` )
125
+ modDelimStart = regexp .MustCompile (`^require.*` )
126
+ modDelimEnd = ")"
127
+ )
128
+
129
+ func parseModFile () (map [string ]string , error ) {
130
+ b , err := os .ReadFile (modFile )
131
+ if err != nil {
132
+ return nil , err
133
+ }
134
+
88
135
in := string (cleanMods .ReplaceAll (b , []byte ("" )))
89
136
out := make (map [string ]string )
90
137
@@ -94,26 +141,63 @@ func parseMod(b []byte) map[string]string {
94
141
case modDelimStart .MatchString (s ) && ! start :
95
142
start = true
96
143
case s == modDelimEnd :
97
- return out
144
+ return out , nil
98
145
case start :
99
146
kv := strings .SplitN (s , " " , 2 )
100
147
if len (kv ) < 2 {
101
- panic ( fmt .Sprintf ("unexpected format for module: %q" , s ) )
148
+ return nil , fmt .Errorf ("unexpected format for module: %q" , s )
102
149
}
103
150
104
151
out [kv [0 ]] = kv [1 ]
105
152
}
106
153
}
107
154
108
- return out
155
+ return out , nil
109
156
}
110
157
111
- func getExcludedMods () map [string ]any {
112
- out := make (map [string ]any )
158
+ func getUpstreamModGraph (upstreamRefs []string ) (map [string ]map [string ]string , error ) {
159
+ b , err := exec .Command ("go" , "mod" , "graph" ).Output ()
160
+ if err != nil {
161
+ return nil , err
162
+ }
163
+
164
+ graph := string (b )
165
+ o1Refs := make (map [string ]bool )
166
+ for _ , upstreamRef := range upstreamRefs {
167
+ o1Refs [upstreamRef ] = false
168
+ }
169
+
170
+ modToVersionToUpstreamRef := make (map [string ]map [string ]string )
171
+
172
+ for _ , line := range strings .Split (graph , "\n " ) {
173
+ upstreamRef := strings .SplitN (line , "@" , 2 )[0 ]
174
+ if _ , ok := o1Refs [upstreamRef ]; ok {
175
+ o1Refs [upstreamRef ] = true
176
+ kv := strings .SplitN (strings .SplitN (line , " " , 2 )[1 ], "@" , 2 )
177
+ name := kv [0 ]
178
+ version := kv [1 ]
179
+
180
+ if m , ok := modToVersionToUpstreamRef [kv [0 ]]; ok {
181
+ m [version ] = upstreamRef
182
+ } else {
183
+ versionToRef := map [string ]string {version : upstreamRef }
184
+ modToVersionToUpstreamRef [name ] = versionToRef
185
+ }
186
+ }
187
+ }
188
+
189
+ notFound := ""
190
+ for ref , found := range o1Refs {
191
+ if ! found {
192
+ notFound = fmt .Sprintf ("%s%s, " , notFound , ref )
193
+ }
194
+ }
113
195
114
- for _ , mod := range os .Args [1 :] {
115
- out [mod ] = nil
196
+ if notFound != "" {
197
+ return nil , fmt .Errorf ("cannot verify modules;" +
198
+ "the following specified upstream module cannot be found in go.mod: [ %s ]" ,
199
+ strings .TrimSuffix (notFound , ", " ))
116
200
}
117
201
118
- return out
202
+ return modToVersionToUpstreamRef , nil
119
203
}
0 commit comments