Skip to content

Commit 024a4d0

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 024a4d0

File tree

3 files changed

+358
-0
lines changed

3 files changed

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

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)