Skip to content

Commit f02f247

Browse files
jimmidysonCopilot
andauthored
build: Use GH client and token for coredns version mapping (#1238)
Fixes 403 errors in coredns version mapping checks due to rate limiting. --------- Co-authored-by: Copilot <[email protected]>
1 parent 96e26c5 commit f02f247

File tree

7 files changed

+112
-117
lines changed

7 files changed

+112
-117
lines changed

.github/workflows/checks.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ jobs:
195195
run: devbox run -- make pre-commit
196196
env:
197197
SKIP: no-commit-to-branch,golangci-lint,actionlint-system
198+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
198199

199200
lint-test-helm:
200201
runs-on: ubuntu-24.04

.pre-commit-config.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ repos:
1515
language: system
1616
files: "(.*\\.go|go.mod|go.sum|go.mk)$"
1717
pass_filenames: false
18+
- id: check-coredns-versions
19+
name: check-coredns-versions
20+
entry: make coredns.sync
21+
language: system
22+
files: "^api/versions/coredns.go$"
1823
- id: golangci-lint
1924
name: golangci-lint
2025
entry: make lint
@@ -63,11 +68,6 @@ repos:
6368
language: system
6469
files: "^devbox.(yaml|lock)$"
6570
pass_filenames: false
66-
- id: check-coredns-versions
67-
name: check-coredns-versions
68-
entry: make coredns.sync
69-
language: system
70-
files: "^api/versions/coredns.go$"
7171
- repo: https://github.com/tekwizely/pre-commit-golang
7272
rev: v1.0.0-rc.1
7373
hooks:

api/versions/coredns.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hack/tools/coredns-versions/main.go

Lines changed: 97 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,25 @@ package main
55

66
import (
77
"bytes"
8-
"encoding/json"
8+
"context"
99
"errors"
1010
"flag"
1111
"fmt"
1212
"go/format"
13-
"io"
14-
"net/http"
1513
"os"
14+
"os/signal"
1615
"path"
1716
"regexp"
1817
"sort"
1918
"strings"
2019
"text/template"
2120

2221
"github.com/blang/semver/v4"
22+
"github.com/google/go-github/v72/github"
2323
)
2424

2525
var (
26-
// Command-line flags
26+
// Command-line flags.
2727
outputFile = flag.String("output", "api/versions/coredns.go", "Output file path")
2828
minKubernetesVersion = flag.String(
2929
"min-kubernetes-version",
@@ -33,8 +33,7 @@ var (
3333
)
3434

3535
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"
3837
)
3938

4039
var goTemplate = `// Copyright 2024 Nutanix. All rights reserved.
@@ -51,14 +50,14 @@ import (
5150
"github.com/blang/semver/v4"
5251
)
5352
54-
// Kubernetes versions
53+
// Kubernetes versions.
5554
const (
5655
{{- range .KubernetesConstants }}
5756
{{ .Name }} = "{{ .Version }}"
5857
{{- end }}
5958
)
6059
61-
// CoreDNS versions
60+
// CoreDNS versions.
6261
const (
6362
{{- range .CoreDNSConstants }}
6463
{{ .Name }} = "{{ .Version }}"
@@ -114,13 +113,21 @@ func main() {
114113
os.Exit(1)
115114
}
116115

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)
118125
if err != nil {
119126
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.
121128
}
122129

123-
versionMap, err := fetchCoreDNSVersions(versions)
130+
versionMap, err := fetchCoreDNSVersions(ctx, versions, ghClient)
124131
if err != nil {
125132
fmt.Fprintf(os.Stderr, "Error fetching CoreDNS versions: %v\n", err)
126133
os.Exit(1)
@@ -134,47 +141,51 @@ func main() {
134141
fmt.Printf("Successfully generated %s\n", *outputFile)
135142
}
136143

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+
}
141153

154+
minorVersionToPatchVersion := make(map[string]semver.Version)
142155
for {
143-
url := fmt.Sprintf(tagsAPIURL, page)
144-
tagNames, err := fetchTagNames(url)
156+
tags, resp, err := ghClient.Repositories.ListTags(ctx, "kubernetes", "kubernetes", listOptions)
145157
if err != nil {
146-
return nil, err
158+
return nil, fmt.Errorf("failed to list Kubernetes versions: %w", err)
147159
}
148160

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+
}
152165

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
174181
}
175182
}
176183

177-
page++
184+
if resp.NextPage == 0 {
185+
break
186+
}
187+
188+
listOptions.Page = resp.NextPage
178189
}
179190

180191
if len(minorVersionToPatchVersion) == 0 {
@@ -191,56 +202,58 @@ func fetchKubernetesVersions(minVersion semver.Version) ([]semver.Version, error
191202
return versions, nil
192203
}
193204

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))
218209

219-
return tagNames, nil
220-
}
221-
222-
func fetchCoreDNSVersions(versions []semver.Version) (map[string]string, error) {
223-
versionMap := make(map[string]string)
224210
re := regexp.MustCompile(`CoreDNSVersion\s*=\s*"([^"]+)"`)
225211

226212
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+
)
229222
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+
)
232228
}
233229

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+
234248
// Parse and normalize CoreDNS version
235249
v, err := semver.ParseTolerant(coreDNSVersionStr)
236250
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",
240253
coreDNSVersionStr,
241254
k8sVersion,
255+
err,
242256
)
243-
continue
244257
}
245258

246259
coreDNSVersion := "v" + v.String()
@@ -257,31 +270,6 @@ func fetchCoreDNSVersions(versions []semver.Version) (map[string]string, error)
257270
return versionMap, nil
258271
}
259272

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-
285273
func generateGoFile(versionMap map[string]string, outputPath string) error {
286274
data := prepareTemplateData(versionMap)
287275

@@ -304,7 +292,7 @@ func generateGoFile(versionMap map[string]string, outputPath string) error {
304292
return fmt.Errorf("creating directories error: %w", err)
305293
}
306294

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.
308296
return fmt.Errorf("writing file error: %w", err)
309297
}
310298

@@ -316,15 +304,12 @@ func prepareTemplateData(versionMap map[string]string) map[string]interface{} {
316304
Name string
317305
Version string
318306
}
319-
var k8sConstants []Const
320-
var coreDNSConstants []Const
321307

322308
type versionMapEntry struct {
323309
KubernetesVersion string
324310
KubernetesConst string
325311
CoreDNSConst string
326312
}
327-
var versionMapList []versionMapEntry
328313

329314
// Maps for deduplication
330315
k8sConstMap := make(map[string]string)
@@ -337,13 +322,15 @@ func prepareTemplateData(versionMap map[string]string) map[string]interface{} {
337322
}
338323

339324
// Generate constants for CoreDNS versions
325+
coreDNSConstants := make([]Const, 0, len(uniqueCoreDNSVersions))
340326
for coreDNSVersion := range uniqueCoreDNSVersions {
341327
constName := versionToConst("CoreDNS", coreDNSVersion)
342328
coreDNSConstMap[coreDNSVersion] = constName
343329
coreDNSConstants = append(coreDNSConstants, Const{Name: constName, Version: coreDNSVersion})
344330
}
345331

346332
// Generate constants and mapping for Kubernetes versions
333+
k8sConstants := make([]Const, 0, len(versionMap))
347334
for k8sVersion := range versionMap {
348335
if _, exists := k8sConstMap[k8sVersion]; !exists {
349336
constName := versionToConst("Kubernetes", k8sVersion)
@@ -353,6 +340,7 @@ func prepareTemplateData(versionMap map[string]string) map[string]interface{} {
353340
}
354341

355342
// Map Kubernetes constants to CoreDNS constants
343+
versionMapList := make([]versionMapEntry, 0, len(versionMap))
356344
for k8sVersion, coreDNSVersion := range versionMap {
357345
versionMapList = append(versionMapList, versionMapEntry{
358346
KubernetesVersion: k8sVersion,

0 commit comments

Comments
 (0)