Skip to content

Commit 7e3058f

Browse files
committed
feat(registry): automatically generate version file from registry
1 parent dadb141 commit 7e3058f

File tree

2 files changed

+290
-0
lines changed

2 files changed

+290
-0
lines changed
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
//go:build ignore
2+
3+
package main
4+
5+
import (
6+
"bytes"
7+
"encoding/json"
8+
"fmt"
9+
"go/format"
10+
"io"
11+
"log"
12+
"net/http"
13+
"os"
14+
"path/filepath"
15+
"sort"
16+
"strings"
17+
"text/template"
18+
)
19+
20+
const registryURL = "https://raw.githubusercontent.com/platformsh/platformsh-docs/main/shared/data/registry.json"
21+
22+
type Registry map[string]Service
23+
24+
type Service struct {
25+
Description string `json:"description"`
26+
Disk bool `json:"disk"`
27+
Runtime bool `json:"runtime"`
28+
Type string `json:"type"`
29+
Versions map[string][]string `json:"versions"`
30+
}
31+
32+
// Mapping from registry service names to our ServiceName constants
33+
var serviceMapping = map[string]string{
34+
"chrome-headless": "ChromeHeadless",
35+
"influxdb": "InfluxDB",
36+
"kafka": "Kafka",
37+
"mariadb": "MariaDB",
38+
"memcached": "Memcached",
39+
"mysql": "MySQL",
40+
"network-storage": "NetworkStorage",
41+
"opensearch": "OpenSearch",
42+
"oracle-mysql": "OracleMySQL",
43+
"postgresql": "PostgreSQL",
44+
"rabbitmq": "RabbitMQ",
45+
"redis": "Redis",
46+
"solr": "Solr",
47+
"varnish": "Varnish",
48+
"vault-kms": "VaultKMS",
49+
}
50+
51+
// Mapping from registry runtime names to our Runtime constants
52+
var runtimeMapping = map[string]string{
53+
"dotnet": "DotNet",
54+
"elixir": "Elixir",
55+
"golang": "Golang",
56+
"java": "Java",
57+
"nodejs": "NodeJS",
58+
"php": "PHP",
59+
"python": "Python",
60+
"ruby": "Ruby",
61+
"rust": "Rust",
62+
}
63+
64+
const versionTemplate = `//go:generate go run generate_versions.go
65+
66+
package models
67+
68+
var (
69+
LanguageTypeVersions = map[Runtime][]string{
70+
{{- range .Languages }}
71+
{{ .Name }}: {{ .Versions }},
72+
{{- end }}
73+
}
74+
75+
ServiceTypeVersions = map[ServiceName][]string{
76+
{{- range .Services }}
77+
{{ .Name }}: {{ .Versions }},
78+
{{- end }}
79+
}
80+
)
81+
82+
func DefaultVersionForRuntime(r Runtime) string {
83+
versions := LanguageTypeVersions[r]
84+
if len(versions) == 0 {
85+
return ""
86+
}
87+
return versions[0]
88+
}
89+
`
90+
91+
type TemplateData struct {
92+
Languages []TypeVersion
93+
Services []TypeVersion
94+
}
95+
96+
type TypeVersion struct {
97+
Name string
98+
Versions string
99+
}
100+
101+
func main() {
102+
// Fetch the registry data
103+
resp, err := http.Get(registryURL)
104+
if err != nil {
105+
log.Fatalf("Failed to fetch registry: %v", err)
106+
}
107+
defer resp.Body.Close()
108+
109+
body, err := io.ReadAll(resp.Body)
110+
if err != nil {
111+
log.Fatalf("Failed to read response: %v", err)
112+
}
113+
114+
var registry Registry
115+
if err := json.Unmarshal(body, &registry); err != nil {
116+
log.Fatalf("Failed to parse JSON: %v", err)
117+
}
118+
119+
var languages []TypeVersion
120+
var services []TypeVersion
121+
122+
// Process runtimes (languages)
123+
for serviceName, service := range registry {
124+
if !service.Runtime {
125+
continue
126+
}
127+
128+
constantName, exists := runtimeMapping[serviceName]
129+
if !exists {
130+
continue // Skip runtimes we don't support
131+
}
132+
133+
supported := service.Versions["supported"]
134+
if len(supported) == 0 {
135+
continue
136+
}
137+
138+
versions := formatVersionSlice(supported)
139+
languages = append(languages, TypeVersion{
140+
Name: constantName,
141+
Versions: versions,
142+
})
143+
}
144+
145+
// Process services
146+
for serviceName, service := range registry {
147+
if service.Runtime {
148+
continue
149+
}
150+
151+
constantName, exists := serviceMapping[serviceName]
152+
if !exists {
153+
continue // Skip services we don't support
154+
}
155+
156+
supported := service.Versions["supported"]
157+
if len(supported) == 0 {
158+
continue
159+
}
160+
161+
versions := formatVersionSlice(supported)
162+
services = append(services, TypeVersion{
163+
Name: constantName,
164+
Versions: versions,
165+
})
166+
167+
// Add RedisPersistent right after Redis
168+
if serviceName == "redis" {
169+
services = append(services, TypeVersion{
170+
Name: "RedisPersistent",
171+
Versions: versions,
172+
})
173+
}
174+
}
175+
176+
// Sort for consistent output
177+
sort.Slice(languages, func(i, j int) bool {
178+
return languages[i].Name < languages[j].Name
179+
})
180+
sort.Slice(services, func(i, j int) bool {
181+
return services[i].Name < services[j].Name
182+
})
183+
184+
// Generate the Go code
185+
tmpl, err := template.New("version").Parse(versionTemplate)
186+
if err != nil {
187+
log.Fatalf("Failed to parse template: %v", err)
188+
}
189+
190+
var buf bytes.Buffer
191+
data := TemplateData{
192+
Languages: languages,
193+
Services: services,
194+
}
195+
196+
if err := tmpl.Execute(&buf, data); err != nil {
197+
log.Fatalf("Failed to execute template: %v", err)
198+
}
199+
200+
// Format the generated Go code
201+
formatted, err := format.Source(buf.Bytes())
202+
if err != nil {
203+
log.Fatalf("Failed to format Go code: %v", err)
204+
}
205+
206+
// Write to version.go file
207+
dir, err := os.Getwd()
208+
if err != nil {
209+
log.Fatalf("Failed to get working directory: %v", err)
210+
}
211+
212+
outputPath := filepath.Join(dir, "version.go")
213+
if err := os.WriteFile(outputPath, formatted, 0644); err != nil {
214+
log.Fatalf("Failed to write version.go: %v", err)
215+
}
216+
217+
fmt.Printf("Successfully generated %s\n", outputPath)
218+
}
219+
220+
func formatVersionSlice(versions []string) string {
221+
if len(versions) == 0 {
222+
return "{}"
223+
}
224+
225+
// Sort versions from latest to oldest
226+
sortedVersions := make([]string, len(versions))
227+
copy(sortedVersions, versions)
228+
229+
sort.Slice(sortedVersions, func(i, j int) bool {
230+
return compareVersions(sortedVersions[i], sortedVersions[j])
231+
})
232+
233+
quoted := make([]string, len(sortedVersions))
234+
for i, v := range sortedVersions {
235+
quoted[i] = fmt.Sprintf(`"%s"`, v)
236+
}
237+
238+
return fmt.Sprintf("{%s}", strings.Join(quoted, ", "))
239+
}
240+
241+
// compareVersions returns true if version a is greater than version b
242+
func compareVersions(a, b string) bool {
243+
// Parse version parts
244+
aParts := parseVersion(a)
245+
bParts := parseVersion(b)
246+
247+
// Compare each part
248+
maxLen := len(aParts)
249+
if len(bParts) > maxLen {
250+
maxLen = len(bParts)
251+
}
252+
253+
for i := 0; i < maxLen; i++ {
254+
aVal := 0
255+
bVal := 0
256+
257+
if i < len(aParts) {
258+
aVal = aParts[i]
259+
}
260+
if i < len(bParts) {
261+
bVal = bParts[i]
262+
}
263+
264+
if aVal > bVal {
265+
return true
266+
}
267+
if aVal < bVal {
268+
return false
269+
}
270+
}
271+
272+
return false // versions are equal
273+
}
274+
275+
// parseVersion parses a version string into numeric parts
276+
func parseVersion(version string) []int {
277+
parts := strings.Split(version, ".")
278+
nums := make([]int, 0, len(parts))
279+
280+
for _, part := range parts {
281+
// Handle cases like "25.05" or "1.0" or just "1"
282+
num := 0
283+
fmt.Sscanf(part, "%d", &num)
284+
nums = append(nums, num)
285+
}
286+
287+
return nums
288+
}

internal/question/models/version.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:generate go run generate_versions.go
2+
13
package models
24

35
var (

0 commit comments

Comments
 (0)