-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
137 lines (121 loc) · 3.53 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"os/exec"
"strings"
"github.com/samber/lo"
"gopkg.in/yaml.v2"
)
type Channel struct {
Releases []Release `json:"releases"`
}
type Release struct {
Version string `json:"version"`
}
const (
stdOutOutput = "stdout"
)
// This program is used to generate Google Kubernetes Engine JSON versions for Renovate custom datasource.
// Custom datasource docs: https://docs.renovatebot.com/modules/datasource/custom/
func main() {
ctx := context.Background()
requestedChannel := flag.String("channel", "stable", "Channel to scrape")
requestedLocation := flag.String("location", "us-central1-c", "GCP location to check versions for (they might differ per location)")
targetFile := flag.String("out", stdOutOutput, "Target file to write the output")
flag.Parse()
channel, err := scrapeChannel(ctx, *requestedChannel, *requestedLocation)
if err != nil {
fmt.Println("Error scraping channel:", err)
os.Exit(1)
}
if saveOutput(channel, *targetFile); err != nil {
fmt.Println("Error saving output:", err)
os.Exit(1)
}
}
func saveOutput(channel Channel, targetFile string) error {
var encoder *json.Encoder
if targetFile == stdOutOutput {
encoder = json.NewEncoder(os.Stdout)
} else {
file, err := os.Create(targetFile)
if err != nil {
return fmt.Errorf("error creating file: %w", err)
}
defer file.Close()
encoder = json.NewEncoder(file)
}
encoder.SetIndent("", " ")
if err := encoder.Encode(channel); err != nil {
return fmt.Errorf("error encoding JSON: %w", err)
}
return nil
}
func scrapeChannel(ctx context.Context, channel string, location string) (Channel, error) {
cmd := exec.CommandContext(ctx, "gcloud", "container", "get-server-config",
"--location", location,
"--flatten", "channels",
"--filter", "channels.channel="+channel,
"--format", "yaml(channels.channel,channels.validVersions)",
)
cmdOut, err := cmd.CombinedOutput()
if err != nil {
return Channel{}, fmt.Errorf("running gcloud command: %w, OUTPUT:\n %s", err, string(cmdOut))
}
releases, err := extractReleases(string(cmdOut))
if err != nil {
return Channel{}, fmt.Errorf("extracting releases: %w", err)
}
return Channel{Releases: releases}, nil
}
func extractReleases(cmdOut string) ([]Release, error) {
parts := strings.SplitN(cmdOut, "---", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("unexpected command output: %s", cmdOut)
}
channelsYAML := parts[1]
// Sample command output:
// Fetching server config for us-central1-c
// ---
// channels:
// channel: RAPID
// validVersions:
// - 1.28.2-gke.1157000
// - 1.27.6-gke.1506000
// - 1.27.6-gke.1445000
// - 1.27.6-gke.1248000
// - 1.27.5-gke.200
// - 1.27.4-gke.900
// - 1.26.9-gke.1507000
// - 1.26.9-gke.1437000
// - 1.26.8-gke.200
// - 1.26.7-gke.500
// - 1.25.14-gke.1474000
// - 1.25.14-gke.1421000
// - 1.25.13-gke.200
// - 1.25.12-gke.500
// - 1.24.17-gke.2155000
// - 1.24.17-gke.2113000
// - 1.24.17-gke.200`
var decoded struct {
Channels struct {
Channel string `yaml:"channel"`
ValidVersions []string `yaml:"validVersions"`
} `yaml:"channels"`
}
if err := yaml.Unmarshal([]byte(channelsYAML), &decoded); err != nil {
return nil, fmt.Errorf("unmarshaling YAML: %w", err)
}
var releases []Release
for _, v := range decoded.Channels.ValidVersions {
// We want to remove the "-gke.*" suffix from the version.
// Example: 1.24.17-gke.200 -> 1.24.17
v = strings.Split(v, "-")[0]
releases = append(releases, Release{Version: v})
}
return lo.Uniq(releases), nil
}