Skip to content

Commit ea02ddd

Browse files
"hydrating from base"Merge branch 'change_kotlin_ignores' into add-ktor-core
2 parents 81bdb68 + 2006469 commit ea02ddd

304 files changed

Lines changed: 15667 additions & 1309 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.fleetControl/configurationDefinitions.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ configurationDefinitions:
33
description: Java agent configuration
44
type: agent-config
55
version: 1.0.0
6-
# will add schema information here later
6+
schema: ./schemas/config.json
7+
format: yml
8+
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
#!/usr/bin/env groovy
2+
/*
3+
* Agent Config Schema Version Bump — release-time entry point.
4+
*
5+
* Compares the schema at a prior git ref (typically the last agent release
6+
* tag) to the current on-disk schema, classifies the diff, and bumps the
7+
* version in .fleetControl/configurationDefinitions.yml accordingly.
8+
*
9+
* Usage:
10+
* groovy BumpSchemaVersion.groovy --since=<ref> # dry-run
11+
* groovy BumpSchemaVersion.groovy --since=<ref> --ci # write
12+
*
13+
* Exit codes:
14+
* 0 — no bump needed (no schema diff, or bootstrap case where the ref
15+
* predates the .fleetControl/ system)
16+
* 1 — bump applied (with --ci) or recommended (without --ci)
17+
* 2 — generator failure (uncaught exception, missing args, etc.)
18+
*
19+
* The bump kind (major/minor/patch) is determined by the cumulative schema
20+
* diff between <ref> and HEAD. See SchemaDiff.groovy for classification rules.
21+
*
22+
* The schema file path is read from configurationDefinitions.yml's `schema`
23+
* field at both the historical ref and HEAD, so renames or relocations of
24+
* the schema file don't break the bump path.
25+
*/
26+
27+
@Grab('org.yaml:snakeyaml:2.2')
28+
import org.yaml.snakeyaml.Yaml
29+
import groovy.json.JsonSlurper
30+
31+
// ---------------------------------------------------------------------------
32+
// Paths
33+
// ---------------------------------------------------------------------------
34+
final File SCRIPT_DIR = new File(getClass().protectionDomain.codeSource.location.toURI()).parentFile ?: new File('.')
35+
final File FLEET_CONTROL_DIR = SCRIPT_DIR.parentFile
36+
final File REPO_ROOT = FLEET_CONTROL_DIR.parentFile
37+
final File CONFIG_DEF_PATH = new File(FLEET_CONTROL_DIR, 'configurationDefinitions.yml')
38+
39+
// Path to configurationDefinitions.yml relative to repo root — used for `git show`.
40+
final String CONFIG_DEF_REPO_PATH = '.fleetControl/configurationDefinitions.yml'
41+
42+
// ---------------------------------------------------------------------------
43+
// Helpers
44+
// ---------------------------------------------------------------------------
45+
46+
/**
47+
* Run `git show <ref>:<path>` and return the file contents, or null if the
48+
* file doesn't exist at that ref. Bootstrapping pre-system tags relies on
49+
* this returning null.
50+
*/
51+
static String gitShow(File repoRoot, String ref, String path) {
52+
Process p = ['git', '-C', repoRoot.absolutePath, 'show', "${ref}:${path}"].execute()
53+
StringBuilder out = new StringBuilder()
54+
StringBuilder err = new StringBuilder()
55+
p.waitForProcessOutput(out, err)
56+
if (p.exitValue() != 0) {
57+
return null // file doesn't exist at ref, or ref is invalid
58+
}
59+
return out.toString()
60+
}
61+
62+
/**
63+
* Parse configurationDefinitions.yml text → [version, schemaPath].
64+
*
65+
* If `strict` is true, missing fields throw. If false, missing fields return
66+
* null in the corresponding slot — used for the historical parse so a tag
67+
* with an incomplete configurationDefinitions.yml (e.g., a placeholder that
68+
* predates the schema work) maps to "bootstrap, no bump" rather than a
69+
* generator failure.
70+
*/
71+
static List<String> parseDefinitions(String yamlText, String contextLabel, boolean strict = true) {
72+
Map<String, Object> data = (Map<String, Object>) new Yaml().load(yamlText)
73+
List defs = (List) (data?.get('configurationDefinitions') ?: [])
74+
if (defs.isEmpty()) {
75+
if (strict) throw new IllegalStateException("${contextLabel}: configurationDefinitions list is empty")
76+
return [null, null]
77+
}
78+
Map<String, Object> def0 = (Map<String, Object>) defs[0]
79+
String version = def0.get('version')?.toString()
80+
String schemaPath = def0.get('schema')?.toString()
81+
if (strict) {
82+
if (!version) throw new IllegalStateException("${contextLabel}: version missing")
83+
if (!schemaPath) throw new IllegalStateException("${contextLabel}: schema missing")
84+
}
85+
return [version, schemaPath]
86+
}
87+
88+
/**
89+
* Resolve the schema path declared in configurationDefinitions.yml to a
90+
* repo-rooted path string suitable for `git show`. The yaml's `schema:`
91+
* field is relative to configurationDefinitions.yml's location.
92+
*/
93+
static String resolveSchemaRepoPath(String configDefRepoPath, String schemaRelative) {
94+
File configDir = new File(configDefRepoPath).parentFile
95+
File resolved = new File(configDir, schemaRelative).canonicalFile
96+
File cwd = new File('.').canonicalFile
97+
String rel = cwd.toPath().relativize(resolved.toPath()).toString()
98+
// git show expects forward slashes; normalize on Windows in case.
99+
return rel.replace('\\', '/')
100+
}
101+
102+
// ---------------------------------------------------------------------------
103+
// Main — wrapped in try/catch so uncaught exceptions exit 2
104+
// ---------------------------------------------------------------------------
105+
if (binding.variables.get('TEST_MODE') != true) {
106+
try {
107+
// Parse CLI flags
108+
String sinceRef = null
109+
boolean ciMode = false
110+
List<String> rawArgs = (binding.variables.get('args') ?: []) as List<String>
111+
rawArgs.each { String arg ->
112+
if (arg.startsWith('--since=')) {
113+
sinceRef = arg.substring('--since='.length())
114+
} else if (arg == '--ci') {
115+
ciMode = true
116+
} else {
117+
System.err.println "Unknown flag: ${arg}"
118+
System.err.println "Usage: groovy BumpSchemaVersion.groovy --since=<ref> [--ci]"
119+
System.exit(2)
120+
}
121+
}
122+
if (!sinceRef) {
123+
System.err.println "Missing required flag: --since=<ref>"
124+
System.err.println "Usage: groovy BumpSchemaVersion.groovy --since=<ref> [--ci]"
125+
System.exit(2)
126+
}
127+
128+
// Load the SchemaDiff library
129+
File schemaDiffFile = new File(SCRIPT_DIR, 'SchemaDiff.groovy')
130+
GroovyShell shell = new GroovyShell()
131+
Script lib = shell.parse(schemaDiffFile)
132+
lib.run()
133+
134+
println "Comparing schema at ${sinceRef} to current HEAD"
135+
136+
// Step 1: fetch historical configurationDefinitions.yml
137+
String historicalDefsText = gitShow(REPO_ROOT, sinceRef, CONFIG_DEF_REPO_PATH)
138+
if (historicalDefsText == null) {
139+
// Bootstrap case: the ref predates the .fleetControl/ system.
140+
// Treat as first release of the schema — no bump, no PR.
141+
println "configurationDefinitions.yml does not exist at ${sinceRef}."
142+
println "Treating this as the first release that includes the schema; no bump applied."
143+
System.exit(0)
144+
}
145+
146+
def (String starterVersion, String historicalSchemaRel) = parseDefinitions(
147+
historicalDefsText, "configurationDefinitions.yml@${sinceRef}", false)
148+
if (!starterVersion || !historicalSchemaRel) {
149+
// Bootstrap: file existed at the ref but didn't have the fields we need.
150+
// Common when a placeholder configurationDefinitions.yml was committed
151+
// before the schema work was complete.
152+
println "configurationDefinitions.yml at ${sinceRef} is incomplete (missing version or schema field)."
153+
println "Treating this as the first release that includes the schema; no bump applied."
154+
System.exit(0)
155+
}
156+
println "Starter version (from ${sinceRef}): ${starterVersion}"
157+
158+
// Step 2: fetch historical schema using its declared path
159+
String historicalSchemaPath = resolveSchemaRepoPath(CONFIG_DEF_REPO_PATH, historicalSchemaRel)
160+
String historicalSchemaText = gitShow(REPO_ROOT, sinceRef, historicalSchemaPath)
161+
if (historicalSchemaText == null) {
162+
// configurationDefinitions.yml existed but pointed at a schema file
163+
// that doesn't. Treat as bootstrap — same as missing definitions.
164+
println "Schema file at ${historicalSchemaPath} does not exist at ${sinceRef}."
165+
println "Treating this as the first release that includes the schema; no bump applied."
166+
System.exit(0)
167+
}
168+
Map<String, Object> historicalSchema = (Map<String, Object>) new JsonSlurper().parseText(historicalSchemaText)
169+
170+
// Step 3: read current schema using its declared path (handles renames)
171+
if (!CONFIG_DEF_PATH.exists()) {
172+
throw new FileNotFoundException("Current configurationDefinitions.yml not found at ${CONFIG_DEF_PATH}")
173+
}
174+
def (String currentVersion, String currentSchemaRel) = parseDefinitions(
175+
CONFIG_DEF_PATH.text, "configurationDefinitions.yml (current)")
176+
File currentSchemaFile = new File(CONFIG_DEF_PATH.parentFile, currentSchemaRel).canonicalFile
177+
if (!currentSchemaFile.exists()) {
178+
throw new FileNotFoundException("Current schema not found at ${currentSchemaFile}")
179+
}
180+
Map<String, Object> currentSchema = (Map<String, Object>) new JsonSlurper().parse(currentSchemaFile)
181+
182+
// Step 4: classify diff and recommend a bump
183+
List<Map<String, Object>> changes = lib.classifyChanges(historicalSchema, currentSchema)
184+
if (changes) {
185+
List<Map<String, Object>> breaking = changes.findAll { it.get('severity') == 'breaking' }
186+
List<Map<String, Object>> additive = changes.findAll { it.get('severity') == 'additive' }
187+
List<Map<String, Object>> cosmetic = changes.findAll { it.get('severity') == 'cosmetic' }
188+
println "\nSchema changes since ${sinceRef} (${changes.size()}):"
189+
if (breaking) {
190+
println " BREAKING (${breaking.size()}):"
191+
breaking.each { Map<String, Object> ch -> println " ${lib.renderChange(ch)}" }
192+
}
193+
if (additive) {
194+
println " ADDITIVE (${additive.size()}):"
195+
additive.each { Map<String, Object> ch -> println " ${lib.renderChange(ch)}" }
196+
}
197+
if (cosmetic) {
198+
println " COSMETIC (${cosmetic.size()}):"
199+
cosmetic.each { Map<String, Object> ch -> println " ${lib.renderChange(ch)}" }
200+
}
201+
} else {
202+
println "\nNo schema changes since ${sinceRef}."
203+
}
204+
205+
String bumpKind = lib.recommendBump(changes)
206+
if (bumpKind == 'none') {
207+
println "\nRecommended bump: none — no version change needed."
208+
System.exit(0)
209+
}
210+
211+
String newVersion = lib.applyBump(starterVersion, bumpKind)
212+
println "\nRecommended bump: ${bumpKind} (${starterVersion}${newVersion})"
213+
214+
if (!ciMode) {
215+
println "(dry-run; pass --ci to apply)"
216+
System.exit(1)
217+
}
218+
219+
// Apply the bump. Note: bumpVersion reads the version from the file,
220+
// applies the kind, and writes the result. Since the on-disk version
221+
// may have drifted from `starterVersion` (e.g., if a prior bump landed
222+
// but no release was cut), we explicitly write `newVersion` rather
223+
// than re-bumping the on-disk value.
224+
if (currentVersion != newVersion) {
225+
String text = CONFIG_DEF_PATH.text
226+
java.util.regex.Pattern versionLine = java.util.regex.Pattern.compile(/(?m)^(\s*version:\s*)(\S+)(\s*)$/)
227+
StringBuffer sb = new StringBuffer()
228+
def matcher = versionLine.matcher(text)
229+
int matches = 0
230+
while (matcher.find()) {
231+
matcher.appendReplacement(sb,
232+
java.util.regex.Matcher.quoteReplacement(
233+
"${matcher.group(1)}${newVersion}${matcher.group(3)}"
234+
)
235+
)
236+
matches++
237+
}
238+
matcher.appendTail(sb)
239+
if (matches != 1) {
240+
throw new IllegalStateException(
241+
"${CONFIG_DEF_PATH}: expected exactly 1 'version:' line, found ${matches}"
242+
)
243+
}
244+
CONFIG_DEF_PATH.text = sb.toString()
245+
println "Wrote: ${CONFIG_DEF_PATH} (version: ${currentVersion}${newVersion})"
246+
} else {
247+
println "On-disk version already matches the bumped value (${newVersion}); no write needed."
248+
}
249+
250+
System.exit(1)
251+
} catch (Throwable t) {
252+
System.err.println "Bump failed: ${t.class.name}: ${t.message}"
253+
t.printStackTrace(System.err)
254+
System.exit(2)
255+
}
256+
}

0 commit comments

Comments
 (0)