Skip to content

Commit 1728d46

Browse files
api(coredns): Add kubernetes version to coredns version mapping
Added script and makefile target to update coredns version mapping.
1 parent 18b5adf commit 1728d46

File tree

3 files changed

+383
-0
lines changed

3 files changed

+383
-0
lines changed

api/constants/coredns.go

Lines changed: 41 additions & 0 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: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"flag"
8+
"fmt"
9+
"go/format"
10+
"io"
11+
"net/http"
12+
"os"
13+
"path"
14+
"regexp"
15+
"sort"
16+
"strings"
17+
"text/template"
18+
)
19+
20+
var (
21+
// Command-line flags
22+
outputFile = flag.String("output", "api/constants/coredns.go", "Output file path")
23+
)
24+
25+
const (
26+
constantsURLTemplate = "https://raw.githubusercontent.com/kubernetes/kubernetes/%s/cmd/kubeadm/app/constants/constants.go"
27+
branchesAPIURL = "https://api.github.com/repos/kubernetes/kubernetes/branches?per_page=100&page=%d"
28+
minKubernetesVersion = "1.22"
29+
)
30+
31+
var goTemplate = `// Code generated by script; DO NOT EDIT. Run 'make coredns.sync' instead
32+
33+
package constants
34+
35+
// Kubernetes versions
36+
const (
37+
{{- range .KubernetesConstants }}
38+
{{ .Name }} = "{{ .Version }}"
39+
{{- end }}
40+
)
41+
42+
// CoreDNS versions
43+
const (
44+
{{- range .CoreDNSConstants }}
45+
{{ .Name }} = "{{ .Version }}"
46+
{{- end }}
47+
)
48+
49+
// KubernetesToCoreDNSVersion maps Kubernetes versions to CoreDNS versions.
50+
var KubernetesToCoreDNSVersion = map[string]string{
51+
{{- range .VersionMap }}
52+
{{ .KubernetesConst }}: {{ .CoreDNSConst }},
53+
{{- end }}
54+
}
55+
`
56+
57+
func main() {
58+
flag.Parse()
59+
60+
versions, err := fetchKubernetesVersions()
61+
if err != nil {
62+
fmt.Fprintf(os.Stderr, "Error fetching Kubernetes versions: %v\n", err)
63+
os.Exit(1)
64+
}
65+
66+
versionMap, err := fetchCoreDNSVersions(versions)
67+
if err != nil {
68+
fmt.Fprintf(os.Stderr, "Error fetching CoreDNS versions: %v\n", err)
69+
os.Exit(1)
70+
}
71+
72+
if err := generateGoFile(versionMap, *outputFile); err != nil {
73+
fmt.Fprintf(os.Stderr, "Error generating Go file: %v\n", err)
74+
os.Exit(1)
75+
}
76+
77+
fmt.Printf("Successfully generated %s\n", *outputFile)
78+
}
79+
80+
// Fetch Kubernetes versions from GitHub branches
81+
func fetchKubernetesVersions() ([]string, error) {
82+
var versions []string
83+
page := 1
84+
for {
85+
url := fmt.Sprintf(branchesAPIURL, page)
86+
branchNames, err := fetchBranchNames(url)
87+
if err != nil {
88+
return nil, err
89+
}
90+
if len(branchNames) == 0 {
91+
break
92+
}
93+
for _, branch := range branchNames {
94+
if strings.HasPrefix(branch, "release-1.") {
95+
version := strings.TrimPrefix(branch, "release-")
96+
if compareVersions(version, minKubernetesVersion) >= 0 {
97+
versions = append(versions, version)
98+
}
99+
}
100+
}
101+
page++
102+
}
103+
if len(versions) == 0 {
104+
return nil, errors.New("no Kubernetes versions found")
105+
}
106+
// Remove duplicates and sort
107+
versionSet := make(map[string]struct{})
108+
for _, v := range versions {
109+
versionSet[v] = struct{}{}
110+
}
111+
versions = nil
112+
for v := range versionSet {
113+
versions = append(versions, v)
114+
}
115+
sort.Slice(versions, func(i, j int) bool {
116+
return compareVersions(versions[i], versions[j]) < 0
117+
})
118+
return versions, nil
119+
}
120+
121+
// Fetch branch names from GitHub API
122+
func fetchBranchNames(url string) ([]string, error) {
123+
// Optional: Add your GitHub token for authenticated requests
124+
// token := "your_github_token"
125+
// req.Header.Set("Authorization", "token "+token)
126+
127+
resp, err := http.Get(url)
128+
if err != nil {
129+
return nil, fmt.Errorf("HTTP GET error: %w", err)
130+
}
131+
defer resp.Body.Close()
132+
133+
if resp.StatusCode != http.StatusOK {
134+
return nil, fmt.Errorf("non-200 HTTP status: %d", resp.StatusCode)
135+
}
136+
137+
var branches []struct {
138+
Name string `json:"name"`
139+
}
140+
if err := json.NewDecoder(resp.Body).Decode(&branches); err != nil {
141+
return nil, fmt.Errorf("decoding JSON error: %w", err)
142+
}
143+
144+
var branchNames []string
145+
for _, branch := range branches {
146+
branchNames = append(branchNames, branch.Name)
147+
}
148+
return branchNames, nil
149+
}
150+
151+
func fetchCoreDNSVersions(versions []string) (map[string]string, error) {
152+
versionMap := make(map[string]string)
153+
re := regexp.MustCompile(`CoreDNSVersion\s*=\s*"([^"]+)"`)
154+
155+
for _, k8sVersion := range versions {
156+
branch := "release-" + k8sVersion
157+
url := fmt.Sprintf(constantsURLTemplate, branch)
158+
coreDNSVersion, err := extractCoreDNSVersion(url, re)
159+
if err != nil {
160+
fmt.Fprintf(os.Stderr, "Warning: Failed for Kubernetes %s: %v\n", k8sVersion, err)
161+
continue
162+
}
163+
versionMap[k8sVersion] = coreDNSVersion
164+
}
165+
166+
if len(versionMap) == 0 {
167+
return nil, errors.New("no CoreDNS versions found")
168+
}
169+
170+
return versionMap, nil
171+
}
172+
173+
func extractCoreDNSVersion(url string, re *regexp.Regexp) (string, error) {
174+
// Optional: Add caching to reduce network calls if needed
175+
resp, err := http.Get(url)
176+
if err != nil {
177+
return "", fmt.Errorf("HTTP GET error: %w", err)
178+
}
179+
defer resp.Body.Close()
180+
181+
if resp.StatusCode != http.StatusOK {
182+
return "", fmt.Errorf("non-200 HTTP status: %d", resp.StatusCode)
183+
}
184+
185+
bodyBytes, err := io.ReadAll(resp.Body)
186+
if err != nil {
187+
return "", fmt.Errorf("reading body error: %w", err)
188+
}
189+
190+
matches := re.FindStringSubmatch(string(bodyBytes))
191+
if len(matches) != 2 {
192+
return "", errors.New("CoreDNSVersion not found")
193+
}
194+
195+
return matches[1], nil
196+
}
197+
198+
func generateGoFile(versionMap map[string]string, outputPath string) error {
199+
data := prepareTemplateData(versionMap)
200+
201+
tmpl, err := template.New("versionMapping").Parse(goTemplate)
202+
if err != nil {
203+
return fmt.Errorf("parsing template error: %w", err)
204+
}
205+
206+
var buf bytes.Buffer
207+
if err := tmpl.Execute(&buf, data); err != nil {
208+
return fmt.Errorf("executing template error: %w", err)
209+
}
210+
211+
formattedSrc, err := format.Source(buf.Bytes())
212+
if err != nil {
213+
return fmt.Errorf("formatting source code error: %w", err)
214+
}
215+
216+
if err := os.MkdirAll(path.Dir(outputPath), os.ModePerm); err != nil {
217+
return fmt.Errorf("creating directories error: %w", err)
218+
}
219+
220+
if err := os.WriteFile(outputPath, formattedSrc, 0644); err != nil {
221+
return fmt.Errorf("writing file error: %w", err)
222+
}
223+
224+
return nil
225+
}
226+
227+
func prepareTemplateData(versionMap map[string]string) map[string]interface{} {
228+
// Generate constants for Kubernetes and CoreDNS versions
229+
type Const struct {
230+
Name string
231+
Version string
232+
}
233+
234+
var k8sConstants []Const
235+
var coreDNSConstants []Const
236+
var versionMapList []struct {
237+
KubernetesConst string
238+
CoreDNSConst string
239+
}
240+
241+
// Maps for deduplication
242+
k8sConstMap := make(map[string]string)
243+
coreDNSConstMap := make(map[string]string)
244+
245+
// Collect unique CoreDNS versions
246+
uniqueCoreDNSVersions := make(map[string]bool)
247+
for _, coreDNSVersion := range versionMap {
248+
uniqueCoreDNSVersions[coreDNSVersion] = true
249+
}
250+
251+
// Generate constants for Kubernetes versions
252+
for k8sVersion := range versionMap {
253+
constName := versionToConst(k8sVersion)
254+
k8sConstMap[k8sVersion] = constName
255+
}
256+
257+
// Generate constants for CoreDNS versions
258+
for coreDNSVersion := range uniqueCoreDNSVersions {
259+
constName := versionToConst(coreDNSVersion)
260+
coreDNSConstMap[coreDNSVersion] = constName
261+
}
262+
263+
// Prepare constants slices
264+
for k8sVersion, constName := range k8sConstMap {
265+
k8sConstants = append(k8sConstants, Const{Name: constName, Version: k8sVersion})
266+
}
267+
for coreDNSVersion, constName := range coreDNSConstMap {
268+
coreDNSConstants = append(coreDNSConstants, Const{Name: constName, Version: coreDNSVersion})
269+
}
270+
271+
// Sort constants
272+
sort.Slice(k8sConstants, func(i, j int) bool {
273+
return compareVersions(k8sConstants[i].Version, k8sConstants[j].Version) < 0
274+
})
275+
sort.Slice(coreDNSConstants, func(i, j int) bool {
276+
return compareVersions(coreDNSConstants[i].Version, coreDNSConstants[j].Version) < 0
277+
})
278+
279+
// Map Kubernetes constants to CoreDNS constants
280+
for k8sVersion, coreDNSVersion := range versionMap {
281+
versionMapList = append(versionMapList, struct {
282+
KubernetesConst string
283+
CoreDNSConst string
284+
}{
285+
KubernetesConst: k8sConstMap[k8sVersion],
286+
CoreDNSConst: coreDNSConstMap[coreDNSVersion],
287+
})
288+
}
289+
290+
// Sort version map
291+
sort.Slice(versionMapList, func(i, j int) bool {
292+
return compareVersions(versionMapList[i].KubernetesConst, versionMapList[j].KubernetesConst) < 0
293+
})
294+
295+
data := map[string]interface{}{
296+
"KubernetesConstants": k8sConstants,
297+
"CoreDNSConstants": coreDNSConstants,
298+
"VersionMap": versionMapList,
299+
}
300+
301+
return data
302+
}
303+
304+
func versionToConst(version string) string {
305+
// Remove 'v' prefix if present
306+
version = strings.TrimPrefix(version, "v")
307+
// Replace dots with underscores
308+
version = strings.ReplaceAll(version, ".", "_")
309+
// Prepend 'V'
310+
return "V" + version
311+
}
312+
313+
// compareVersions compares version strings numerically
314+
func compareVersions(a, b string) int {
315+
aParts := strings.Split(a, ".")
316+
bParts := strings.Split(b, ".")
317+
maxParts := len(aParts)
318+
if len(bParts) > maxParts {
319+
maxParts = len(bParts)
320+
}
321+
for i := 0; i < maxParts; i++ {
322+
var aPart, bPart int
323+
if i < len(aParts) {
324+
fmt.Sscanf(aParts[i], "%d", &aPart)
325+
}
326+
if i < len(bParts) {
327+
fmt.Sscanf(bParts[i], "%d", &bPart)
328+
}
329+
if aPart != bPart {
330+
if aPart < bPart {
331+
return -1
332+
}
333+
return 1
334+
}
335+
}
336+
return 0
337+
}

make/apis.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,8 @@ api.sync.%: ; $(info $(M) syncing external API: $(PROVIDER_MODULE_$*)/$(PROVIDER
5555
$(PROVIDER_API_DIR)
5656
find $(PROVIDER_API_DIR) -type d -exec chmod 0755 {} \;
5757
find $(PROVIDER_API_DIR) -type f -exec chmod 0644 {} \;
58+
59+
.PHONY: coredns.sync
60+
coredns.sync: ## Syncs the Kubernetes version to CoreDNS version mapping used in the cluster upgrade
61+
coredns.sync: ; $(info $(M) syncing CoreDNS version mapping)
62+
go run hack/tools/coredns-versions/main.go

0 commit comments

Comments
 (0)