Skip to content

Commit 98b78b6

Browse files
feat(api): Add kubernetes version to coredns version mapping
Added script and makefile target to update coredns version mapping. `make coredns.sync` will fetch the list of releases from upstream release branches and create a mapping k8s releases to coredns versions.
1 parent 18b5adf commit 98b78b6

File tree

3 files changed

+377
-0
lines changed

3 files changed

+377
-0
lines changed

api/versions/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: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
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/versions/coredns.go", "Output file path")
23+
minKubernetesVersion = flag.String("min-kubernetes-version", "1.22", "Minimum Kubernetes version to include")
24+
)
25+
26+
const (
27+
constantsURLTemplate = "https://raw.githubusercontent.com/kubernetes/kubernetes/%s/cmd/kubeadm/app/constants/constants.go"
28+
branchesAPIURL = "https://api.github.com/repos/kubernetes/kubernetes/branches?per_page=100&page=%d"
29+
)
30+
31+
var goTemplate = `// Code generated by script; DO NOT EDIT. Run 'make coredns.sync' instead
32+
33+
package versions
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(*minKubernetesVersion)
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(minVersion string) ([]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, minVersion) >= 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+
resp, err := http.Get(url)
124+
if err != nil {
125+
return nil, fmt.Errorf("HTTP GET error: %w", err)
126+
}
127+
defer resp.Body.Close()
128+
129+
if resp.StatusCode != http.StatusOK {
130+
return nil, fmt.Errorf("non-200 HTTP status: %d", resp.StatusCode)
131+
}
132+
133+
var branches []struct {
134+
Name string `json:"name"`
135+
}
136+
if err := json.NewDecoder(resp.Body).Decode(&branches); err != nil {
137+
return nil, fmt.Errorf("decoding JSON error: %w", err)
138+
}
139+
140+
var branchNames []string
141+
for _, branch := range branches {
142+
branchNames = append(branchNames, branch.Name)
143+
}
144+
return branchNames, nil
145+
}
146+
147+
func fetchCoreDNSVersions(versions []string) (map[string]string, error) {
148+
versionMap := make(map[string]string)
149+
re := regexp.MustCompile(`CoreDNSVersion\s*=\s*"([^"]+)"`)
150+
151+
for _, k8sVersion := range versions {
152+
branch := "release-" + k8sVersion
153+
url := fmt.Sprintf(constantsURLTemplate, branch)
154+
coreDNSVersion, err := extractCoreDNSVersion(url, re)
155+
if err != nil {
156+
fmt.Fprintf(os.Stderr, "Warning: Failed for Kubernetes %s: %v\n", k8sVersion, err)
157+
continue
158+
}
159+
versionMap[k8sVersion] = coreDNSVersion
160+
}
161+
162+
if len(versionMap) == 0 {
163+
return nil, errors.New("no CoreDNS versions found")
164+
}
165+
166+
return versionMap, nil
167+
}
168+
169+
func extractCoreDNSVersion(url string, re *regexp.Regexp) (string, error) {
170+
resp, err := http.Get(url)
171+
if err != nil {
172+
return "", fmt.Errorf("HTTP GET error: %w", err)
173+
}
174+
defer resp.Body.Close()
175+
176+
if resp.StatusCode != http.StatusOK {
177+
return "", fmt.Errorf("non-200 HTTP status: %d", resp.StatusCode)
178+
}
179+
180+
bodyBytes, err := io.ReadAll(resp.Body)
181+
if err != nil {
182+
return "", fmt.Errorf("reading body error: %w", err)
183+
}
184+
185+
matches := re.FindStringSubmatch(string(bodyBytes))
186+
if len(matches) != 2 {
187+
return "", errors.New("CoreDNSVersion not found")
188+
}
189+
190+
return matches[1], nil
191+
}
192+
193+
func generateGoFile(versionMap map[string]string, outputPath string) error {
194+
data := prepareTemplateData(versionMap)
195+
196+
tmpl, err := template.New("versionMapping").Parse(goTemplate)
197+
if err != nil {
198+
return fmt.Errorf("parsing template error: %w", err)
199+
}
200+
201+
var buf bytes.Buffer
202+
if err := tmpl.Execute(&buf, data); err != nil {
203+
return fmt.Errorf("executing template error: %w", err)
204+
}
205+
206+
formattedSrc, err := format.Source(buf.Bytes())
207+
if err != nil {
208+
return fmt.Errorf("formatting source code error: %w", err)
209+
}
210+
211+
if err := os.MkdirAll(path.Dir(outputPath), os.ModePerm); err != nil {
212+
return fmt.Errorf("creating directories error: %w", err)
213+
}
214+
215+
if err := os.WriteFile(outputPath, formattedSrc, 0644); err != nil {
216+
return fmt.Errorf("writing file error: %w", err)
217+
}
218+
219+
return nil
220+
}
221+
222+
func prepareTemplateData(versionMap map[string]string) map[string]interface{} {
223+
type Const struct {
224+
Name string
225+
Version string
226+
}
227+
228+
var k8sConstants []Const
229+
var coreDNSConstants []Const
230+
type versionMapEntry struct {
231+
KubernetesVersion string
232+
KubernetesConst string
233+
CoreDNSConst string
234+
}
235+
var versionMapList []versionMapEntry
236+
237+
// Maps for deduplication
238+
k8sConstMap := make(map[string]string)
239+
coreDNSConstMap := make(map[string]string)
240+
241+
// Collect unique CoreDNS versions
242+
uniqueCoreDNSVersions := make(map[string]bool)
243+
for _, coreDNSVersion := range versionMap {
244+
uniqueCoreDNSVersions[coreDNSVersion] = true
245+
}
246+
247+
// Generate constants for Kubernetes versions
248+
for k8sVersion := range versionMap {
249+
constName := versionToConst("Kubernetes", k8sVersion)
250+
k8sConstMap[k8sVersion] = constName
251+
}
252+
253+
// Generate constants for CoreDNS versions
254+
for coreDNSVersion := range uniqueCoreDNSVersions {
255+
constName := versionToConst("CoreDNS", coreDNSVersion)
256+
coreDNSConstMap[coreDNSVersion] = constName
257+
}
258+
259+
// Prepare constants slices
260+
for k8sVersion, constName := range k8sConstMap {
261+
k8sConstants = append(k8sConstants, Const{Name: constName, Version: k8sVersion})
262+
}
263+
for coreDNSVersion, constName := range coreDNSConstMap {
264+
coreDNSConstants = append(coreDNSConstants, Const{Name: constName, Version: coreDNSVersion})
265+
}
266+
267+
// Sort constants
268+
sort.Slice(k8sConstants, func(i, j int) bool {
269+
return compareVersions(k8sConstants[i].Version, k8sConstants[j].Version) < 0
270+
})
271+
sort.Slice(coreDNSConstants, func(i, j int) bool {
272+
return compareVersions(coreDNSConstants[i].Version, coreDNSConstants[j].Version) < 0
273+
})
274+
275+
// Map Kubernetes constants to CoreDNS constants
276+
for k8sVersion, coreDNSVersion := range versionMap {
277+
versionMapList = append(versionMapList, versionMapEntry{
278+
KubernetesVersion: k8sVersion,
279+
KubernetesConst: k8sConstMap[k8sVersion],
280+
CoreDNSConst: coreDNSConstMap[coreDNSVersion],
281+
})
282+
}
283+
284+
// Sort version map
285+
sort.Slice(versionMapList, func(i, j int) bool {
286+
return compareVersions(versionMapList[i].KubernetesVersion, versionMapList[j].KubernetesVersion) < 0
287+
})
288+
289+
data := map[string]interface{}{
290+
"KubernetesConstants": k8sConstants,
291+
"CoreDNSConstants": coreDNSConstants,
292+
"VersionMap": versionMapList,
293+
}
294+
295+
return data
296+
}
297+
298+
func versionToConst(prefix, version string) string {
299+
// Remove 'v' prefix if present
300+
version = strings.TrimPrefix(version, "v")
301+
// Replace dots with underscores
302+
version = strings.ReplaceAll(version, ".", "_")
303+
// Prepend the prefix and 'V'
304+
return prefix + "_V" + version
305+
}
306+
307+
// compareVersions compares version strings numerically
308+
func compareVersions(a, b string) int {
309+
aParts := strings.Split(a, ".")
310+
bParts := strings.Split(b, ".")
311+
maxParts := len(aParts)
312+
if len(bParts) > maxParts {
313+
maxParts = len(bParts)
314+
}
315+
for i := 0; i < maxParts; i++ {
316+
var aPart, bPart int
317+
if i < len(aParts) {
318+
fmt.Sscanf(aParts[i], "%d", &aPart)
319+
}
320+
if i < len(bParts) {
321+
fmt.Sscanf(bParts[i], "%d", &bPart)
322+
}
323+
if aPart != bPart {
324+
if aPart < bPart {
325+
return -1
326+
}
327+
return 1
328+
}
329+
}
330+
return 0
331+
}

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)