@@ -5,25 +5,25 @@ package main
5
5
6
6
import (
7
7
"bytes"
8
- "encoding/json "
8
+ "context "
9
9
"errors"
10
10
"flag"
11
11
"fmt"
12
12
"go/format"
13
- "io"
14
- "net/http"
15
13
"os"
14
+ "os/signal"
16
15
"path"
17
16
"regexp"
18
17
"sort"
19
18
"strings"
20
19
"text/template"
21
20
22
21
"github.com/blang/semver/v4"
22
+ "github.com/google/go-github/v72/github"
23
23
)
24
24
25
25
var (
26
- // Command-line flags
26
+ // Command-line flags.
27
27
outputFile = flag .String ("output" , "api/versions/coredns.go" , "Output file path" )
28
28
minKubernetesVersion = flag .String (
29
29
"min-kubernetes-version" ,
33
33
)
34
34
35
35
const (
36
- constantsURLTemplate = "https://raw.githubusercontent.com/kubernetes/kubernetes/v%s/cmd/kubeadm/app/constants/constants.go"
37
- tagsAPIURL = "https://api.github.com/repos/kubernetes/kubernetes/tags?per_page=100&page=%d"
36
+ kubeadmConstantsFilePathInRepo = "cmd/kubeadm/app/constants/constants.go"
38
37
)
39
38
40
39
var goTemplate = `// Copyright 2024 Nutanix. All rights reserved.
@@ -51,14 +50,14 @@ import (
51
50
"github.com/blang/semver/v4"
52
51
)
53
52
54
- // Kubernetes versions
53
+ // Kubernetes versions.
55
54
const (
56
55
{{- range .KubernetesConstants }}
57
56
{{ .Name }} = "{{ .Version }}"
58
57
{{- end }}
59
58
)
60
59
61
- // CoreDNS versions
60
+ // CoreDNS versions.
62
61
const (
63
62
{{- range .CoreDNSConstants }}
64
63
{{ .Name }} = "{{ .Version }}"
@@ -114,13 +113,21 @@ func main() {
114
113
os .Exit (1 )
115
114
}
116
115
117
- versions , err := fetchKubernetesVersions (minSemverVersion )
116
+ ctx , cancelFunc := signal .NotifyContext (context .Background (), os .Interrupt )
117
+ defer cancelFunc ()
118
+
119
+ ghClient := github .NewClient (nil )
120
+ if ghToken := os .Getenv ("GH_TOKEN" ); ghToken != "" {
121
+ ghClient = ghClient .WithAuthToken (ghToken )
122
+ }
123
+
124
+ versions , err := fetchKubernetesVersions (ctx , minSemverVersion , ghClient )
118
125
if err != nil {
119
126
fmt .Fprintf (os .Stderr , "Error fetching Kubernetes versions: %v\n " , err )
120
- os .Exit (1 )
127
+ os .Exit (1 ) //nolint:gocritic // This will still be a clean exit.
121
128
}
122
129
123
- versionMap , err := fetchCoreDNSVersions (versions )
130
+ versionMap , err := fetchCoreDNSVersions (ctx , versions , ghClient )
124
131
if err != nil {
125
132
fmt .Fprintf (os .Stderr , "Error fetching CoreDNS versions: %v\n " , err )
126
133
os .Exit (1 )
@@ -134,47 +141,51 @@ func main() {
134
141
fmt .Printf ("Successfully generated %s\n " , * outputFile )
135
142
}
136
143
137
- // Fetch Kubernetes versions from GitHub branches
138
- func fetchKubernetesVersions (minVersion semver.Version ) ([]semver.Version , error ) {
139
- minorVersionToPatchVersion := make (map [string ]semver.Version )
140
- page := 1
144
+ // Fetch Kubernetes versions from GitHub branches.
145
+ func fetchKubernetesVersions (
146
+ ctx context.Context ,
147
+ minVersion semver.Version ,
148
+ ghClient * github.Client ,
149
+ ) ([]semver.Version , error ) {
150
+ listOptions := & github.ListOptions {
151
+ PerPage : 100 ,
152
+ }
141
153
154
+ minorVersionToPatchVersion := make (map [string ]semver.Version )
142
155
for {
143
- url := fmt .Sprintf (tagsAPIURL , page )
144
- tagNames , err := fetchTagNames (url )
156
+ tags , resp , err := ghClient .Repositories .ListTags (ctx , "kubernetes" , "kubernetes" , listOptions )
145
157
if err != nil {
146
- return nil , err
158
+ return nil , fmt . Errorf ( "failed to list Kubernetes versions: %w" , err )
147
159
}
148
160
149
- if len (tagNames ) == 0 {
150
- break
151
- }
161
+ for _ , tag := range tags {
162
+ if ! strings .HasPrefix (tag .GetName (), "v" ) {
163
+ continue // Skip tags without 'v' prefix
164
+ }
152
165
153
- for _ , tag := range tagNames {
154
- if strings .HasPrefix (tag , "v" ) {
155
- v , err := semver .ParseTolerant (tag )
156
- if err != nil {
157
- continue // Skip invalid version
158
- }
159
-
160
- if v .Pre != nil {
161
- continue // Skip pre-release versions
162
- }
163
-
164
- if v .LT (minVersion ) {
165
- continue // Skip versions below the minimum
166
- }
167
-
168
- // Store the highest patch version for each minor version
169
- minorVersion := fmt .Sprintf ("v%d.%d" , v .Major , v .Minor )
170
- if existingPatchVersionForMinor , exists := minorVersionToPatchVersion [minorVersion ]; ! exists ||
171
- v .GT (existingPatchVersionForMinor ) {
172
- minorVersionToPatchVersion [minorVersion ] = v
173
- }
166
+ v , err := semver .ParseTolerant (tag .GetName ())
167
+ if err != nil {
168
+ continue // Skip invalid versions
169
+ }
170
+ if v .Pre != nil {
171
+ continue // Skip pre-release versions
172
+ }
173
+ if v .LT (minVersion ) {
174
+ continue // Skip versions below the minimum
175
+ }
176
+ // Store the highest patch version for each minor version
177
+ minorVersion := fmt .Sprintf ("v%d.%d" , v .Major , v .Minor )
178
+ if existingPatchVersionForMinor , exists := minorVersionToPatchVersion [minorVersion ]; ! exists ||
179
+ v .GT (existingPatchVersionForMinor ) {
180
+ minorVersionToPatchVersion [minorVersion ] = v
174
181
}
175
182
}
176
183
177
- page ++
184
+ if resp .NextPage == 0 {
185
+ break
186
+ }
187
+
188
+ listOptions .Page = resp .NextPage
178
189
}
179
190
180
191
if len (minorVersionToPatchVersion ) == 0 {
@@ -191,56 +202,58 @@ func fetchKubernetesVersions(minVersion semver.Version) ([]semver.Version, error
191
202
return versions , nil
192
203
}
193
204
194
- // Fetch branch names from GitHub API
195
- func fetchTagNames (url string ) ([]string , error ) {
196
- resp , err := http .Get (url )
197
- if err != nil {
198
- return nil , fmt .Errorf ("HTTP GET error: %w" , err )
199
- }
200
- defer resp .Body .Close ()
201
-
202
- if resp .StatusCode != http .StatusOK {
203
- return nil , fmt .Errorf ("non-200 HTTP status: %d" , resp .StatusCode )
204
- }
205
-
206
- var tags []struct {
207
- Name string `json:"name"`
208
- }
209
-
210
- if err := json .NewDecoder (resp .Body ).Decode (& tags ); err != nil {
211
- return nil , fmt .Errorf ("decoding JSON error: %w" , err )
212
- }
213
-
214
- tagNames := make ([]string , 0 , len (tags ))
215
- for _ , tag := range tags {
216
- tagNames = append (tagNames , tag .Name )
217
- }
205
+ func fetchCoreDNSVersions (
206
+ ctx context.Context , versions []semver.Version , ghClient * github.Client ,
207
+ ) (map [string ]string , error ) {
208
+ versionMap := make (map [string ]string , len (versions ))
218
209
219
- return tagNames , nil
220
- }
221
-
222
- func fetchCoreDNSVersions (versions []semver.Version ) (map [string ]string , error ) {
223
- versionMap := make (map [string ]string )
224
210
re := regexp .MustCompile (`CoreDNSVersion\s*=\s*"([^"]+)"` )
225
211
226
212
for _ , k8sVersion := range versions {
227
- url := fmt .Sprintf (constantsURLTemplate , k8sVersion )
228
- coreDNSVersionStr , err := extractCoreDNSVersion (url , re )
213
+ fileContent , _ , _ , err := ghClient .Repositories .GetContents (
214
+ ctx ,
215
+ "kubernetes" ,
216
+ "kubernetes" ,
217
+ kubeadmConstantsFilePathInRepo ,
218
+ & github.RepositoryContentGetOptions {
219
+ Ref : "v" + k8sVersion .String (),
220
+ },
221
+ )
229
222
if err != nil {
230
- fmt .Fprintf (os .Stderr , "Warning: Failed for Kubernetes %s: %v\n " , k8sVersion , err )
231
- continue
223
+ return nil , fmt .Errorf (
224
+ "failed to get Kubeadm constants file contents for Kubernetes version v%s: %w" ,
225
+ k8sVersion ,
226
+ err ,
227
+ )
232
228
}
233
229
230
+ decodedContent , err := fileContent .GetContent ()
231
+ if err != nil {
232
+ return nil , fmt .Errorf (
233
+ "failed to decode Kubeadm constants file contents for Kubernetes version v%s: %w" ,
234
+ k8sVersion ,
235
+ err ,
236
+ )
237
+ }
238
+
239
+ matches := re .FindStringSubmatch (decodedContent )
240
+ if len (matches ) != 2 {
241
+ return nil , errors .New (
242
+ "CoreDNS version not found in Kubeadm constants file for Kubernetes version " + k8sVersion .String (),
243
+ )
244
+ }
245
+
246
+ coreDNSVersionStr := matches [1 ]
247
+
234
248
// Parse and normalize CoreDNS version
235
249
v , err := semver .ParseTolerant (coreDNSVersionStr )
236
250
if err != nil {
237
- fmt .Fprintf (
238
- os .Stderr ,
239
- "Warning: Invalid CoreDNS version '%s' for Kubernetes %s\n " ,
251
+ return nil , fmt .Errorf (
252
+ "invalid CoreDNS version '%s' for Kubernetes %s: %w" ,
240
253
coreDNSVersionStr ,
241
254
k8sVersion ,
255
+ err ,
242
256
)
243
- continue
244
257
}
245
258
246
259
coreDNSVersion := "v" + v .String ()
@@ -257,31 +270,6 @@ func fetchCoreDNSVersions(versions []semver.Version) (map[string]string, error)
257
270
return versionMap , nil
258
271
}
259
272
260
- func extractCoreDNSVersion (url string , re * regexp.Regexp ) (string , error ) {
261
- resp , err := http .Get (url )
262
- if err != nil {
263
- return "" , fmt .Errorf ("HTTP GET error: %w" , err )
264
- }
265
- defer resp .Body .Close ()
266
-
267
- if resp .StatusCode != http .StatusOK {
268
- return "" , fmt .Errorf ("non-200 HTTP status: %d" , resp .StatusCode )
269
- }
270
-
271
- bodyBytes , err := io .ReadAll (resp .Body )
272
- if err != nil {
273
- return "" , fmt .Errorf ("reading body error: %w" , err )
274
- }
275
-
276
- matches := re .FindStringSubmatch (string (bodyBytes ))
277
- if len (matches ) != 2 {
278
- return "" , errors .New ("CoreDNSVersion not found" )
279
- }
280
-
281
- coreDNSVersion := matches [1 ]
282
- return coreDNSVersion , nil
283
- }
284
-
285
273
func generateGoFile (versionMap map [string ]string , outputPath string ) error {
286
274
data := prepareTemplateData (versionMap )
287
275
@@ -304,7 +292,7 @@ func generateGoFile(versionMap map[string]string, outputPath string) error {
304
292
return fmt .Errorf ("creating directories error: %w" , err )
305
293
}
306
294
307
- if err := os .WriteFile (outputPath , formattedSrc , 0o644 ); err != nil {
295
+ if err := os .WriteFile (outputPath , formattedSrc , 0o644 ); err != nil { //nolint:gosec // The output file should be world readable.
308
296
return fmt .Errorf ("writing file error: %w" , err )
309
297
}
310
298
@@ -316,15 +304,12 @@ func prepareTemplateData(versionMap map[string]string) map[string]interface{} {
316
304
Name string
317
305
Version string
318
306
}
319
- var k8sConstants []Const
320
- var coreDNSConstants []Const
321
307
322
308
type versionMapEntry struct {
323
309
KubernetesVersion string
324
310
KubernetesConst string
325
311
CoreDNSConst string
326
312
}
327
- var versionMapList []versionMapEntry
328
313
329
314
// Maps for deduplication
330
315
k8sConstMap := make (map [string ]string )
@@ -337,13 +322,15 @@ func prepareTemplateData(versionMap map[string]string) map[string]interface{} {
337
322
}
338
323
339
324
// Generate constants for CoreDNS versions
325
+ coreDNSConstants := make ([]Const , 0 , len (uniqueCoreDNSVersions ))
340
326
for coreDNSVersion := range uniqueCoreDNSVersions {
341
327
constName := versionToConst ("CoreDNS" , coreDNSVersion )
342
328
coreDNSConstMap [coreDNSVersion ] = constName
343
329
coreDNSConstants = append (coreDNSConstants , Const {Name : constName , Version : coreDNSVersion })
344
330
}
345
331
346
332
// Generate constants and mapping for Kubernetes versions
333
+ k8sConstants := make ([]Const , 0 , len (versionMap ))
347
334
for k8sVersion := range versionMap {
348
335
if _ , exists := k8sConstMap [k8sVersion ]; ! exists {
349
336
constName := versionToConst ("Kubernetes" , k8sVersion )
@@ -353,6 +340,7 @@ func prepareTemplateData(versionMap map[string]string) map[string]interface{} {
353
340
}
354
341
355
342
// Map Kubernetes constants to CoreDNS constants
343
+ versionMapList := make ([]versionMapEntry , 0 , len (versionMap ))
356
344
for k8sVersion , coreDNSVersion := range versionMap {
357
345
versionMapList = append (versionMapList , versionMapEntry {
358
346
KubernetesVersion : k8sVersion ,
0 commit comments