@intentius/chant-lexicon-gcp 0.0.18 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/integrity.json +12 -8
- package/dist/manifest.json +1 -1
- package/dist/meta.json +18141 -0
- package/dist/rules/schema-registry.ts +91 -0
- package/dist/rules/wgc101.ts +1 -1
- package/dist/rules/wgc401.ts +59 -0
- package/dist/rules/wgc402.ts +54 -0
- package/dist/rules/wgc403.ts +84 -0
- package/dist/skills/{chant-gke.md → chant-gcp-gke.md} +1 -1
- package/dist/skills/chant-gcp-patterns.md +3 -2
- package/dist/skills/chant-gcp-security.md +3 -2
- package/dist/skills/chant-gcp.md +363 -28
- package/package.json +20 -2
- package/src/codegen/docs.test.ts +16 -0
- package/src/codegen/generate.test.ts +18 -0
- package/src/codegen/generate.ts +11 -0
- package/src/codegen/package.test.ts +16 -0
- package/src/composites/cloud-function.ts +23 -15
- package/src/composites/cloud-run-service.ts +20 -13
- package/src/composites/cloud-sql-instance.ts +18 -14
- package/src/composites/composites.test.ts +94 -62
- package/src/composites/gcs-bucket.ts +13 -9
- package/src/composites/gke-cluster.ts +91 -16
- package/src/composites/index.ts +11 -11
- package/src/composites/managed-certificate.ts +19 -15
- package/src/composites/private-service.ts +23 -15
- package/src/composites/pubsub-pipeline.ts +30 -18
- package/src/composites/secure-project.ts +42 -27
- package/src/composites/vpc-network.ts +42 -35
- package/src/generated/lexicon-gcp.json +18141 -0
- package/src/import/import-fixtures.test.ts +98 -0
- package/src/index.ts +11 -11
- package/src/lint/post-synth/gcp-helpers.test.ts +166 -0
- package/src/lint/post-synth/post-synth.test.ts +132 -1
- package/src/lint/post-synth/schema-registry.ts +91 -0
- package/src/lint/post-synth/wgc101.test.ts +40 -0
- package/src/lint/post-synth/wgc101.ts +1 -1
- package/src/lint/post-synth/wgc102.test.ts +38 -0
- package/src/lint/post-synth/wgc103.test.ts +38 -0
- package/src/lint/post-synth/wgc104.test.ts +37 -0
- package/src/lint/post-synth/wgc105.test.ts +46 -0
- package/src/lint/post-synth/wgc106.test.ts +38 -0
- package/src/lint/post-synth/wgc107.test.ts +38 -0
- package/src/lint/post-synth/wgc108.test.ts +42 -0
- package/src/lint/post-synth/wgc109.test.ts +46 -0
- package/src/lint/post-synth/wgc110.test.ts +37 -0
- package/src/lint/post-synth/wgc111.test.ts +46 -0
- package/src/lint/post-synth/wgc112.test.ts +48 -0
- package/src/lint/post-synth/wgc113.test.ts +36 -0
- package/src/lint/post-synth/wgc201.test.ts +38 -0
- package/src/lint/post-synth/wgc202.test.ts +38 -0
- package/src/lint/post-synth/wgc203.test.ts +45 -0
- package/src/lint/post-synth/wgc204.test.ts +42 -0
- package/src/lint/post-synth/wgc301.test.ts +39 -0
- package/src/lint/post-synth/wgc302.test.ts +36 -0
- package/src/lint/post-synth/wgc303.test.ts +37 -0
- package/src/lint/post-synth/wgc401.test.ts +46 -0
- package/src/lint/post-synth/wgc401.ts +59 -0
- package/src/lint/post-synth/wgc402.test.ts +40 -0
- package/src/lint/post-synth/wgc402.ts +54 -0
- package/src/lint/post-synth/wgc403.test.ts +59 -0
- package/src/lint/post-synth/wgc403.ts +84 -0
- package/src/plugin.test.ts +4 -1
- package/src/plugin.ts +258 -177
- package/src/skills/{chant-gke.md → chant-gcp-gke.md} +1 -1
- package/src/skills/chant-gcp-patterns.md +3 -2
- package/src/skills/chant-gcp-security.md +3 -2
- package/src/skills/chant-gcp.md +363 -28
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema registry for GCP Config Connector resources.
|
|
3
|
+
*
|
|
4
|
+
* Loads lexicon-gcp.json and builds a lookup from CRD kind to field schema,
|
|
5
|
+
* used by post-synth rules to validate YAML against known CRD schemas.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
export interface FieldSchema {
|
|
12
|
+
type: string;
|
|
13
|
+
required: boolean;
|
|
14
|
+
enum?: string[];
|
|
15
|
+
ref?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResourceSchema {
|
|
19
|
+
fields: Record<string, FieldSchema>;
|
|
20
|
+
required: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface LexiconEntry {
|
|
24
|
+
kind: "resource" | "property";
|
|
25
|
+
gvkKind?: string;
|
|
26
|
+
schema?: ResourceSchema;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let cachedRegistry: Map<string, ResourceSchema> | null = null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the schema registry: Map<gvkKind, ResourceSchema>.
|
|
33
|
+
*/
|
|
34
|
+
export function getSchemaRegistry(): Map<string, ResourceSchema> {
|
|
35
|
+
if (cachedRegistry) return cachedRegistry;
|
|
36
|
+
|
|
37
|
+
cachedRegistry = new Map();
|
|
38
|
+
try {
|
|
39
|
+
const lexicon = require("../../generated/lexicon-gcp.json") as Record<string, LexiconEntry>;
|
|
40
|
+
for (const entry of Object.values(lexicon)) {
|
|
41
|
+
if (entry.kind === "resource" && entry.gvkKind && entry.schema) {
|
|
42
|
+
cachedRegistry.set(entry.gvkKind, entry.schema);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// Lexicon JSON not yet generated — return empty registry
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return cachedRegistry;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Levenshtein distance between two strings.
|
|
54
|
+
*/
|
|
55
|
+
export function levenshtein(a: string, b: string): number {
|
|
56
|
+
const m = a.length;
|
|
57
|
+
const n = b.length;
|
|
58
|
+
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
61
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
62
|
+
|
|
63
|
+
for (let i = 1; i <= m; i++) {
|
|
64
|
+
for (let j = 1; j <= n; j++) {
|
|
65
|
+
dp[i][j] = a[i - 1] === b[j - 1]
|
|
66
|
+
? dp[i - 1][j - 1]
|
|
67
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return dp[m][n];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Find the closest field name by Levenshtein distance.
|
|
76
|
+
* Returns the suggestion if distance ≤ 3, otherwise undefined.
|
|
77
|
+
*/
|
|
78
|
+
export function suggestField(unknown: string, knownFields: string[]): string | undefined {
|
|
79
|
+
let best: string | undefined;
|
|
80
|
+
let bestDist = 4; // threshold
|
|
81
|
+
|
|
82
|
+
for (const field of knownFields) {
|
|
83
|
+
const dist = levenshtein(unknown.toLowerCase(), field.toLowerCase());
|
|
84
|
+
if (dist < bestDist) {
|
|
85
|
+
bestDist = dist;
|
|
86
|
+
best = field;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return best;
|
|
91
|
+
}
|
package/dist/rules/wgc101.ts
CHANGED
|
@@ -30,7 +30,7 @@ export const wgc101: PostSynthCheck = {
|
|
|
30
30
|
diagnostics.push({
|
|
31
31
|
checkId: "WGC101",
|
|
32
32
|
severity: "warning",
|
|
33
|
-
message: `StorageBucket "${name}" has no encryption configuration — consider adding spec.encryption.
|
|
33
|
+
message: `StorageBucket "${name}" has no encryption configuration — consider adding spec.encryption.kmsKeyRef`,
|
|
34
34
|
entity: name,
|
|
35
35
|
lexicon: "gcp",
|
|
36
36
|
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC401: Unknown spec field
|
|
3
|
+
*
|
|
4
|
+
* Flags fields in a Config Connector resource spec that are not defined
|
|
5
|
+
* in the CRD schema. Includes "did you mean?" suggestion via Levenshtein.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
|
|
10
|
+
import { getSchemaRegistry, suggestField } from "./schema-registry";
|
|
11
|
+
|
|
12
|
+
export const wgc401: PostSynthCheck = {
|
|
13
|
+
id: "WGC401",
|
|
14
|
+
description: "Config Connector resource spec contains unknown field",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
const registry = getSchemaRegistry();
|
|
19
|
+
|
|
20
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
21
|
+
if (typeof output !== "string") continue;
|
|
22
|
+
|
|
23
|
+
const manifests = parseGcpManifests(output);
|
|
24
|
+
|
|
25
|
+
for (const manifest of manifests) {
|
|
26
|
+
if (!isConfigConnectorResource(manifest)) continue;
|
|
27
|
+
|
|
28
|
+
const kind = manifest.kind;
|
|
29
|
+
if (!kind) continue;
|
|
30
|
+
|
|
31
|
+
const schema = registry.get(kind);
|
|
32
|
+
if (!schema) continue; // No schema available for this kind
|
|
33
|
+
|
|
34
|
+
const spec = getSpec(manifest);
|
|
35
|
+
if (!spec) continue;
|
|
36
|
+
|
|
37
|
+
const knownFields = Object.keys(schema.fields);
|
|
38
|
+
const resourceName = getResourceName(manifest);
|
|
39
|
+
|
|
40
|
+
for (const field of Object.keys(spec)) {
|
|
41
|
+
if (field in schema.fields) continue;
|
|
42
|
+
|
|
43
|
+
const suggestion = suggestField(field, knownFields);
|
|
44
|
+
const hint = suggestion ? ` — did you mean "${suggestion}"?` : "";
|
|
45
|
+
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
checkId: "WGC401",
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: `Resource "${resourceName}" (${kind}): unknown spec field "${field}"${hint}`,
|
|
50
|
+
entity: resourceName,
|
|
51
|
+
lexicon: "gcp",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return diagnostics;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC402: Missing required spec field
|
|
3
|
+
*
|
|
4
|
+
* Flags Config Connector resources that are missing required fields
|
|
5
|
+
* according to their CRD schema.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
|
|
10
|
+
import { getSchemaRegistry } from "./schema-registry";
|
|
11
|
+
|
|
12
|
+
export const wgc402: PostSynthCheck = {
|
|
13
|
+
id: "WGC402",
|
|
14
|
+
description: "Config Connector resource is missing a required spec field",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
const registry = getSchemaRegistry();
|
|
19
|
+
|
|
20
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
21
|
+
if (typeof output !== "string") continue;
|
|
22
|
+
|
|
23
|
+
const manifests = parseGcpManifests(output);
|
|
24
|
+
|
|
25
|
+
for (const manifest of manifests) {
|
|
26
|
+
if (!isConfigConnectorResource(manifest)) continue;
|
|
27
|
+
|
|
28
|
+
const kind = manifest.kind;
|
|
29
|
+
if (!kind) continue;
|
|
30
|
+
|
|
31
|
+
const schema = registry.get(kind);
|
|
32
|
+
if (!schema) continue;
|
|
33
|
+
|
|
34
|
+
const spec = getSpec(manifest);
|
|
35
|
+
const specKeys = new Set(spec ? Object.keys(spec) : []);
|
|
36
|
+
const resourceName = getResourceName(manifest);
|
|
37
|
+
|
|
38
|
+
for (const requiredField of schema.required) {
|
|
39
|
+
if (!specKeys.has(requiredField)) {
|
|
40
|
+
diagnostics.push({
|
|
41
|
+
checkId: "WGC402",
|
|
42
|
+
severity: "error",
|
|
43
|
+
message: `Resource "${resourceName}" (${kind}): missing required field "${requiredField}"`,
|
|
44
|
+
entity: resourceName,
|
|
45
|
+
lexicon: "gcp",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return diagnostics;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC403: Type/structure mismatch in spec field
|
|
3
|
+
*
|
|
4
|
+
* Flags spec fields where the value type doesn't match the CRD schema:
|
|
5
|
+
* - String where number expected (e.g. availableMemoryMb: "512")
|
|
6
|
+
* - Scalar string where resourceRef object expected (e.g. topicRef: "name")
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
10
|
+
import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
|
|
11
|
+
import { getSchemaRegistry } from "./schema-registry";
|
|
12
|
+
|
|
13
|
+
export const wgc403: PostSynthCheck = {
|
|
14
|
+
id: "WGC403",
|
|
15
|
+
description: "Config Connector resource spec field has wrong type or structure",
|
|
16
|
+
|
|
17
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
18
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
19
|
+
const registry = getSchemaRegistry();
|
|
20
|
+
|
|
21
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
22
|
+
if (typeof output !== "string") continue;
|
|
23
|
+
|
|
24
|
+
const manifests = parseGcpManifests(output);
|
|
25
|
+
|
|
26
|
+
for (const manifest of manifests) {
|
|
27
|
+
if (!isConfigConnectorResource(manifest)) continue;
|
|
28
|
+
|
|
29
|
+
const kind = manifest.kind;
|
|
30
|
+
if (!kind) continue;
|
|
31
|
+
|
|
32
|
+
const schema = registry.get(kind);
|
|
33
|
+
if (!schema) continue;
|
|
34
|
+
|
|
35
|
+
const spec = getSpec(manifest);
|
|
36
|
+
if (!spec) continue;
|
|
37
|
+
|
|
38
|
+
const resourceName = getResourceName(manifest);
|
|
39
|
+
|
|
40
|
+
for (const [field, value] of Object.entries(spec)) {
|
|
41
|
+
const fieldSchema = schema.fields[field];
|
|
42
|
+
if (!fieldSchema) continue; // Unknown fields handled by WGC401
|
|
43
|
+
|
|
44
|
+
// Check: resourceRef field expects an object, got a scalar
|
|
45
|
+
if (fieldSchema.ref && typeof value === "string") {
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
checkId: "WGC403",
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: `Resource "${resourceName}" (${kind}): field "${field}" expects a resourceRef object (e.g. { name, kind }), got string "${value}"`,
|
|
50
|
+
entity: resourceName,
|
|
51
|
+
lexicon: "gcp",
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check: number field got a string that looks numeric
|
|
57
|
+
if (fieldSchema.type === "number" && typeof value === "string") {
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
checkId: "WGC403",
|
|
60
|
+
severity: "error",
|
|
61
|
+
message: `Resource "${resourceName}" (${kind}): field "${field}" expects a number, got string "${value}"`,
|
|
62
|
+
entity: resourceName,
|
|
63
|
+
lexicon: "gcp",
|
|
64
|
+
});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check: boolean field got a string
|
|
69
|
+
if (fieldSchema.type === "boolean" && typeof value === "string") {
|
|
70
|
+
diagnostics.push({
|
|
71
|
+
checkId: "WGC403",
|
|
72
|
+
severity: "error",
|
|
73
|
+
message: `Resource "${resourceName}" (${kind}): field "${field}" expects a boolean, got string "${value}"`,
|
|
74
|
+
entity: resourceName,
|
|
75
|
+
lexicon: "gcp",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return diagnostics;
|
|
83
|
+
},
|
|
84
|
+
};
|