@intentius/chant-lexicon-gcp 0.0.15
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 +36 -0
- package/dist/manifest.json +12 -0
- package/dist/meta.json +10919 -0
- package/dist/rules/gcp-helpers.ts +117 -0
- package/dist/rules/hardcoded-project.ts +58 -0
- package/dist/rules/hardcoded-region.ts +56 -0
- package/dist/rules/public-iam.ts +43 -0
- package/dist/rules/wgc101.ts +56 -0
- package/dist/rules/wgc102.ts +35 -0
- package/dist/rules/wgc103.ts +45 -0
- package/dist/rules/wgc104.ts +42 -0
- package/dist/rules/wgc105.ts +46 -0
- package/dist/rules/wgc106.ts +36 -0
- package/dist/rules/wgc107.ts +39 -0
- package/dist/rules/wgc108.ts +41 -0
- package/dist/rules/wgc109.ts +39 -0
- package/dist/rules/wgc110.ts +38 -0
- package/dist/rules/wgc111.ts +54 -0
- package/dist/rules/wgc112.ts +56 -0
- package/dist/rules/wgc113.ts +42 -0
- package/dist/rules/wgc201.ts +36 -0
- package/dist/rules/wgc202.ts +39 -0
- package/dist/rules/wgc203.ts +44 -0
- package/dist/rules/wgc204.ts +39 -0
- package/dist/rules/wgc301.ts +34 -0
- package/dist/rules/wgc302.ts +34 -0
- package/dist/rules/wgc303.ts +37 -0
- package/dist/skills/chant-gcp-patterns.md +367 -0
- package/dist/skills/chant-gcp-security.md +276 -0
- package/dist/skills/chant-gcp.md +108 -0
- package/dist/types/index.d.ts +26529 -0
- package/package.json +35 -0
- package/src/actions/index.ts +52 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +820 -0
- package/src/codegen/generate-cli.ts +24 -0
- package/src/codegen/generate.ts +252 -0
- package/src/codegen/naming.test.ts +49 -0
- package/src/codegen/naming.ts +132 -0
- package/src/codegen/package.ts +66 -0
- package/src/composites/cloud-function.ts +117 -0
- package/src/composites/cloud-run-service.ts +124 -0
- package/src/composites/cloud-sql-instance.ts +126 -0
- package/src/composites/composites.test.ts +432 -0
- package/src/composites/gcs-bucket.ts +111 -0
- package/src/composites/gke-cluster.ts +125 -0
- package/src/composites/index.ts +20 -0
- package/src/composites/managed-certificate.ts +79 -0
- package/src/composites/private-service.ts +95 -0
- package/src/composites/pubsub-pipeline.ts +102 -0
- package/src/composites/secure-project.ts +128 -0
- package/src/composites/vpc-network.ts +165 -0
- package/src/coverage.test.ts +27 -0
- package/src/coverage.ts +51 -0
- package/src/default-labels.test.ts +111 -0
- package/src/default-labels.ts +93 -0
- package/src/generated/index.d.ts +26529 -0
- package/src/generated/index.ts +1723 -0
- package/src/generated/lexicon-gcp.json +10919 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +125 -0
- package/src/import/generator.ts +82 -0
- package/src/import/parser.test.ts +167 -0
- package/src/import/parser.ts +80 -0
- package/src/import/roundtrip.test.ts +66 -0
- package/src/index.ts +54 -0
- package/src/lint/post-synth/gcp-helpers.ts +117 -0
- package/src/lint/post-synth/index.ts +20 -0
- package/src/lint/post-synth/post-synth.test.ts +693 -0
- package/src/lint/post-synth/wgc101.ts +56 -0
- package/src/lint/post-synth/wgc102.ts +35 -0
- package/src/lint/post-synth/wgc103.ts +45 -0
- package/src/lint/post-synth/wgc104.ts +42 -0
- package/src/lint/post-synth/wgc105.ts +46 -0
- package/src/lint/post-synth/wgc106.ts +36 -0
- package/src/lint/post-synth/wgc107.ts +39 -0
- package/src/lint/post-synth/wgc108.ts +41 -0
- package/src/lint/post-synth/wgc109.ts +39 -0
- package/src/lint/post-synth/wgc110.ts +38 -0
- package/src/lint/post-synth/wgc111.ts +54 -0
- package/src/lint/post-synth/wgc112.ts +56 -0
- package/src/lint/post-synth/wgc113.ts +42 -0
- package/src/lint/post-synth/wgc201.ts +36 -0
- package/src/lint/post-synth/wgc202.ts +39 -0
- package/src/lint/post-synth/wgc203.ts +44 -0
- package/src/lint/post-synth/wgc204.ts +39 -0
- package/src/lint/post-synth/wgc301.ts +34 -0
- package/src/lint/post-synth/wgc302.ts +34 -0
- package/src/lint/post-synth/wgc303.ts +37 -0
- package/src/lint/rules/hardcoded-project.ts +58 -0
- package/src/lint/rules/hardcoded-region.ts +56 -0
- package/src/lint/rules/index.ts +3 -0
- package/src/lint/rules/public-iam.ts +43 -0
- package/src/lint/rules/rules.test.ts +63 -0
- package/src/lsp/completions.test.ts +67 -0
- package/src/lsp/completions.ts +17 -0
- package/src/lsp/hover.test.ts +66 -0
- package/src/lsp/hover.ts +54 -0
- package/src/package-cli.ts +24 -0
- package/src/plugin.test.ts +250 -0
- package/src/plugin.ts +405 -0
- package/src/pseudo.test.ts +40 -0
- package/src/pseudo.ts +19 -0
- package/src/serializer.test.ts +250 -0
- package/src/serializer.ts +232 -0
- package/src/skills/chant-gcp-patterns.md +367 -0
- package/src/skills/chant-gcp-security.md +276 -0
- package/src/skills/chant-gcp.md +108 -0
- package/src/spec/fetch.test.ts +16 -0
- package/src/spec/fetch.ts +121 -0
- package/src/spec/parse.test.ts +163 -0
- package/src/spec/parse.ts +432 -0
- package/src/testdata/compute-instance.yaml +93 -0
- package/src/testdata/iam-policy-member.yaml +66 -0
- package/src/testdata/manifests/compute-instance.yaml +18 -0
- package/src/testdata/manifests/full-app.yaml +34 -0
- package/src/testdata/manifests/storage-bucket.yaml +12 -0
- package/src/testdata/storage-bucket.yaml +100 -0
- package/src/validate-cli.ts +13 -0
- package/src/validate.test.ts +38 -0
- package/src/validate.ts +30 -0
- package/src/variables.ts +15 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-gcp
|
|
3
|
+
description: Build, validate, and deploy GCP Config Connector manifests from a chant project
|
|
4
|
+
source: chant-lexicon
|
|
5
|
+
user-invocable: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# GCP Config Connector Operational Playbook
|
|
9
|
+
|
|
10
|
+
## How chant and Config Connector relate
|
|
11
|
+
|
|
12
|
+
chant is a **synthesis-only** tool — it compiles TypeScript source files into Config Connector YAML manifests. chant does NOT call GCP APIs. Your job as an agent is to bridge the two:
|
|
13
|
+
|
|
14
|
+
- Use **chant** for: build, lint, diff (local YAML comparison)
|
|
15
|
+
- Use **kubectl** for: apply, rollback, monitoring, troubleshooting
|
|
16
|
+
|
|
17
|
+
The source of truth for infrastructure is the TypeScript in `src/`. The generated YAML manifests are intermediate artifacts.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
1. A GKE cluster with Config Connector installed
|
|
22
|
+
2. A ConfigConnectorContext resource per namespace
|
|
23
|
+
3. A GCP Service Account with appropriate IAM roles
|
|
24
|
+
|
|
25
|
+
## Build and validate
|
|
26
|
+
|
|
27
|
+
### Build manifests
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
chant build src/ --output manifests.yaml
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Lint the source
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
chant lint src/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### What each step catches
|
|
40
|
+
|
|
41
|
+
| Step | Catches | When to run |
|
|
42
|
+
|------|---------|-------------|
|
|
43
|
+
| `chant lint` | Hardcoded project IDs (WGC001), regions (WGC002), public IAM (WGC003) | Every edit |
|
|
44
|
+
| `chant build` | Post-synth: missing encryption (WGC101), public IAM in output (WGC102), missing project annotation (WGC103) | Before apply |
|
|
45
|
+
|
|
46
|
+
## Applying to Kubernetes
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Build
|
|
50
|
+
chant build src/ --output manifests.yaml
|
|
51
|
+
|
|
52
|
+
# Dry run
|
|
53
|
+
kubectl apply -f manifests.yaml --dry-run=server
|
|
54
|
+
|
|
55
|
+
# Apply
|
|
56
|
+
kubectl apply -f manifests.yaml
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Resource reference patterns
|
|
60
|
+
|
|
61
|
+
Config Connector resources reference each other using `resourceRef`:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
# By name (same namespace)
|
|
65
|
+
resourceRef:
|
|
66
|
+
name: my-network
|
|
67
|
+
|
|
68
|
+
# By external reference (cross-project)
|
|
69
|
+
resourceRef:
|
|
70
|
+
external: projects/my-project/global/networks/my-network
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Project binding
|
|
74
|
+
|
|
75
|
+
Bind resources to a GCP project via annotations:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
metadata:
|
|
79
|
+
annotations:
|
|
80
|
+
cnrm.cloud.google.com/project-id: my-project
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Or use defaultAnnotations in chant:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
export const annotations = defaultAnnotations({
|
|
87
|
+
"cnrm.cloud.google.com/project-id": GCP.ProjectId,
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Troubleshooting
|
|
92
|
+
|
|
93
|
+
| Status | Meaning | Fix |
|
|
94
|
+
|--------|---------|-----|
|
|
95
|
+
| UpToDate | Resource is in sync | None needed |
|
|
96
|
+
| UpdateFailed | GCP API error | Check `kubectl describe` events |
|
|
97
|
+
| DependencyNotReady | Waiting for referenced resource | Ensure dependency exists |
|
|
98
|
+
| DeletionFailed | Cannot delete GCP resource | Check IAM permissions |
|
|
99
|
+
|
|
100
|
+
## Quick reference
|
|
101
|
+
|
|
102
|
+
| Command | Description |
|
|
103
|
+
|---------|-------------|
|
|
104
|
+
| `chant build src/` | Synthesize manifests |
|
|
105
|
+
| `chant lint src/` | Check for anti-patterns |
|
|
106
|
+
| `kubectl apply -f manifests.yaml` | Apply to cluster |
|
|
107
|
+
| `kubectl get gcp` | List all Config Connector resources |
|
|
108
|
+
| `kubectl describe <resource>` | Check reconciliation status |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { getCachePath, KCC_VERSION } from "./fetch";
|
|
3
|
+
|
|
4
|
+
describe("fetch", () => {
|
|
5
|
+
test("cache path includes version", () => {
|
|
6
|
+
const path = getCachePath();
|
|
7
|
+
expect(path).toContain(KCC_VERSION);
|
|
8
|
+
expect(path).toContain(".chant");
|
|
9
|
+
expect(path).toEndWith(".tar.gz");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("custom version in cache path", () => {
|
|
13
|
+
const path = getCachePath("v1.120.0");
|
|
14
|
+
expect(path).toContain("v1.120.0");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema fetching — downloads Config Connector CRD bundle from GitHub releases.
|
|
3
|
+
*
|
|
4
|
+
* Uses the release bundle tar.gz, filtering for CRD YAML files.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { fetchWithCache, clearCacheFile } from "@intentius/chant/codegen/fetch";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Pinned Config Connector version for reproducible codegen.
|
|
13
|
+
*/
|
|
14
|
+
export const KCC_VERSION = "v1.145.0";
|
|
15
|
+
|
|
16
|
+
const CACHE_DIR = join(homedir(), ".chant");
|
|
17
|
+
|
|
18
|
+
function bundleUrl(version: string): string {
|
|
19
|
+
// The release-bundle.tar.gz asset is no longer published.
|
|
20
|
+
// Use GitHub's auto-generated source tarball instead — CRDs are at config/crds/resources/.
|
|
21
|
+
return `https://github.com/GoogleCloudPlatform/k8s-config-connector/archive/refs/tags/${version}.tar.gz`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function cacheFile(version: string): string {
|
|
25
|
+
return join(CACHE_DIR, `kcc-crds-${version}.tar.gz`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fetch the Config Connector CRD bundle and extract CRD YAML files.
|
|
30
|
+
*
|
|
31
|
+
* Returns a Map keyed by CRD filename (e.g., "computeinstance.yaml") with
|
|
32
|
+
* the raw YAML content as a Buffer.
|
|
33
|
+
*/
|
|
34
|
+
export async function fetchCRDBundle(
|
|
35
|
+
force = false,
|
|
36
|
+
version: string = KCC_VERSION,
|
|
37
|
+
): Promise<Map<string, Buffer>> {
|
|
38
|
+
const url = bundleUrl(version);
|
|
39
|
+
const cache = cacheFile(version);
|
|
40
|
+
|
|
41
|
+
const tarData = await fetchWithCache({ url, cacheFile: cache }, force);
|
|
42
|
+
return extractCRDs(tarData);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extract CRD YAML files from a tar.gz bundle.
|
|
47
|
+
*
|
|
48
|
+
* Filters for entries under a crds/ directory or standalone CRD YAML files.
|
|
49
|
+
*/
|
|
50
|
+
async function extractCRDs(tarData: Buffer): Promise<Map<string, Buffer>> {
|
|
51
|
+
const { gunzipSync } = await import("fflate");
|
|
52
|
+
const crds = new Map<string, Buffer>();
|
|
53
|
+
|
|
54
|
+
// Decompress gzip synchronously (avoids lingering event loop references)
|
|
55
|
+
const gunzipped = gunzipSync(new Uint8Array(tarData));
|
|
56
|
+
|
|
57
|
+
// Parse tar (512-byte header blocks) with PAX extended header support.
|
|
58
|
+
// GitHub source tarballs use PAX headers (type 'x') for long filenames.
|
|
59
|
+
let offset = 0;
|
|
60
|
+
const data = gunzipped;
|
|
61
|
+
let paxPath: string | null = null;
|
|
62
|
+
|
|
63
|
+
while (offset < data.length - 512) {
|
|
64
|
+
const header = data.slice(offset, offset + 512);
|
|
65
|
+
offset += 512;
|
|
66
|
+
|
|
67
|
+
if (header.every((b) => b === 0)) break;
|
|
68
|
+
|
|
69
|
+
// Extract filename: ustar prefix (bytes 345-499) + name (bytes 0-99)
|
|
70
|
+
const nameBytes = header.slice(0, 100);
|
|
71
|
+
const nameEnd = nameBytes.indexOf(0);
|
|
72
|
+
let name = new TextDecoder().decode(nameBytes.slice(0, nameEnd > 0 ? nameEnd : 100)).trim();
|
|
73
|
+
const prefixBytes = header.slice(345, 500);
|
|
74
|
+
const prefixEnd = prefixBytes.indexOf(0);
|
|
75
|
+
const ustarPrefix = new TextDecoder().decode(prefixBytes.slice(0, prefixEnd > 0 ? prefixEnd : 155)).trim();
|
|
76
|
+
if (ustarPrefix) name = `${ustarPrefix}/${name}`;
|
|
77
|
+
|
|
78
|
+
const sizeStr = new TextDecoder().decode(header.slice(124, 136)).trim().replace(/\0/g, "");
|
|
79
|
+
const size = parseInt(sizeStr, 8) || 0;
|
|
80
|
+
const typeFlag = header[156];
|
|
81
|
+
const typeFlagChar = String.fromCharCode(typeFlag);
|
|
82
|
+
|
|
83
|
+
const blocks = Math.ceil(size / 512);
|
|
84
|
+
const fileData = data.slice(offset, offset + size);
|
|
85
|
+
offset += blocks * 512;
|
|
86
|
+
|
|
87
|
+
// PAX extended header — extract path= for the next entry
|
|
88
|
+
if (typeFlagChar === "x") {
|
|
89
|
+
const paxStr = new TextDecoder().decode(fileData);
|
|
90
|
+
const match = paxStr.match(/\bpath=([^\n]+)/);
|
|
91
|
+
if (match) paxPath = match[1];
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Use PAX path if available, then reset
|
|
96
|
+
if (paxPath) {
|
|
97
|
+
name = paxPath;
|
|
98
|
+
paxPath = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Only process regular files
|
|
102
|
+
if (typeFlag !== 48 && typeFlag !== 0) continue;
|
|
103
|
+
if (size === 0) continue;
|
|
104
|
+
|
|
105
|
+
// Match CRD YAML files — source tarball has them at config/crds/resources/
|
|
106
|
+
if (name.includes("config/crds/resources/") && name.endsWith(".yaml")) {
|
|
107
|
+
const basename = name.split("/").pop() || name;
|
|
108
|
+
crds.set(basename, Buffer.from(fileData));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return crds;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getCachePath(version: string = KCC_VERSION): string {
|
|
116
|
+
return cacheFile(version);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function clearCache(version: string = KCC_VERSION): void {
|
|
120
|
+
clearCacheFile(cacheFile(version));
|
|
121
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import {
|
|
6
|
+
parseGcpCRD,
|
|
7
|
+
gcpServiceName,
|
|
8
|
+
stripServicePrefix,
|
|
9
|
+
gcpTypeName,
|
|
10
|
+
gcpShortName,
|
|
11
|
+
} from "./parse";
|
|
12
|
+
|
|
13
|
+
const __dirname_ = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const testdata = join(__dirname_, "..", "testdata");
|
|
15
|
+
|
|
16
|
+
describe("gcpServiceName", () => {
|
|
17
|
+
test("extracts service from CNRM group", () => {
|
|
18
|
+
expect(gcpServiceName("compute.cnrm.cloud.google.com")).toBe("Compute");
|
|
19
|
+
expect(gcpServiceName("storage.cnrm.cloud.google.com")).toBe("Storage");
|
|
20
|
+
expect(gcpServiceName("iam.cnrm.cloud.google.com")).toBe("Iam");
|
|
21
|
+
expect(gcpServiceName("container.cnrm.cloud.google.com")).toBe("Container");
|
|
22
|
+
expect(gcpServiceName("sql.cnrm.cloud.google.com")).toBe("Sql");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("stripServicePrefix", () => {
|
|
27
|
+
test("strips known prefix", () => {
|
|
28
|
+
expect(stripServicePrefix("ComputeInstance", "Compute")).toBe("Instance");
|
|
29
|
+
expect(stripServicePrefix("StorageBucket", "Storage")).toBe("Bucket");
|
|
30
|
+
expect(stripServicePrefix("IAMPolicyMember", "IAM")).toBe("PolicyMember");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("returns kind if no prefix match", () => {
|
|
34
|
+
expect(stripServicePrefix("VPCNetwork", "Compute")).toBe("VPCNetwork");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("gcpTypeName", () => {
|
|
39
|
+
test("builds full type name", () => {
|
|
40
|
+
expect(gcpTypeName("compute.cnrm.cloud.google.com", "ComputeInstance")).toBe("GCP::Compute::Instance");
|
|
41
|
+
expect(gcpTypeName("storage.cnrm.cloud.google.com", "StorageBucket")).toBe("GCP::Storage::Bucket");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("gcpShortName", () => {
|
|
46
|
+
test("extracts short name", () => {
|
|
47
|
+
expect(gcpShortName("GCP::Compute::Instance")).toBe("Instance");
|
|
48
|
+
expect(gcpShortName("GCP::Storage::Bucket")).toBe("Bucket");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("parseGcpCRD", () => {
|
|
53
|
+
test("parses ComputeInstance CRD", () => {
|
|
54
|
+
const content = readFileSync(join(testdata, "compute-instance.yaml"), "utf-8");
|
|
55
|
+
const results = parseGcpCRD(content);
|
|
56
|
+
|
|
57
|
+
expect(results).toHaveLength(1);
|
|
58
|
+
const result = results[0];
|
|
59
|
+
|
|
60
|
+
expect(result.resource.typeName).toBe("GCP::Compute::Instance");
|
|
61
|
+
expect(result.gvk.group).toBe("compute.cnrm.cloud.google.com");
|
|
62
|
+
expect(result.gvk.version).toBe("v1beta1");
|
|
63
|
+
expect(result.gvk.kind).toBe("ComputeInstance");
|
|
64
|
+
|
|
65
|
+
// Check properties
|
|
66
|
+
const propNames = result.resource.properties.map((p) => p.name);
|
|
67
|
+
expect(propNames).toContain("machineType");
|
|
68
|
+
expect(propNames).toContain("zone");
|
|
69
|
+
expect(propNames).toContain("canIpForward");
|
|
70
|
+
expect(propNames).toContain("tags");
|
|
71
|
+
|
|
72
|
+
// Check required
|
|
73
|
+
const machineType = result.resource.properties.find((p) => p.name === "machineType")!;
|
|
74
|
+
expect(machineType.required).toBe(true);
|
|
75
|
+
expect(machineType.tsType).toBe("string");
|
|
76
|
+
|
|
77
|
+
// Check resourceRef detection
|
|
78
|
+
const netRef = result.resource.properties.find((p) => p.name === "networkInterfaceRef");
|
|
79
|
+
expect(netRef).toBeDefined();
|
|
80
|
+
expect(netRef!.isResourceRef).toBe(true);
|
|
81
|
+
|
|
82
|
+
// Check attributes
|
|
83
|
+
expect(result.resource.attributes).toHaveLength(3);
|
|
84
|
+
expect(result.resource.attributes[0].name).toBe("name");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("parses StorageBucket CRD", () => {
|
|
88
|
+
const content = readFileSync(join(testdata, "storage-bucket.yaml"), "utf-8");
|
|
89
|
+
const results = parseGcpCRD(content);
|
|
90
|
+
|
|
91
|
+
expect(results).toHaveLength(1);
|
|
92
|
+
const result = results[0];
|
|
93
|
+
|
|
94
|
+
expect(result.resource.typeName).toBe("GCP::Storage::Bucket");
|
|
95
|
+
|
|
96
|
+
// Check enum
|
|
97
|
+
const storageClass = result.resource.properties.find((p) => p.name === "storageClass");
|
|
98
|
+
expect(storageClass).toBeDefined();
|
|
99
|
+
expect(storageClass!.enum).toContain("STANDARD");
|
|
100
|
+
expect(storageClass!.enum).toContain("NEARLINE");
|
|
101
|
+
|
|
102
|
+
// Check property types (nested objects)
|
|
103
|
+
expect(result.propertyTypes.length).toBeGreaterThan(0);
|
|
104
|
+
const versioning = result.propertyTypes.find((pt) => pt.specType === "versioning");
|
|
105
|
+
expect(versioning).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("parses IAMPolicyMember CRD", () => {
|
|
109
|
+
const content = readFileSync(join(testdata, "iam-policy-member.yaml"), "utf-8");
|
|
110
|
+
const results = parseGcpCRD(content);
|
|
111
|
+
|
|
112
|
+
expect(results).toHaveLength(1);
|
|
113
|
+
const result = results[0];
|
|
114
|
+
|
|
115
|
+
expect(result.resource.typeName).toBe("GCP::Iam::PolicyMember");
|
|
116
|
+
|
|
117
|
+
// Check required properties
|
|
118
|
+
const member = result.resource.properties.find((p) => p.name === "member")!;
|
|
119
|
+
expect(member.required).toBe(true);
|
|
120
|
+
|
|
121
|
+
const role = result.resource.properties.find((p) => p.name === "role")!;
|
|
122
|
+
expect(role.required).toBe(true);
|
|
123
|
+
|
|
124
|
+
// resourceRef should be detected
|
|
125
|
+
const resRef = result.resource.properties.find((p) => p.name === "resourceRef");
|
|
126
|
+
expect(resRef).toBeDefined();
|
|
127
|
+
expect(resRef!.isResourceRef).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("handles multi-document CRD bundle", () => {
|
|
131
|
+
const content = [
|
|
132
|
+
readFileSync(join(testdata, "compute-instance.yaml"), "utf-8"),
|
|
133
|
+
readFileSync(join(testdata, "storage-bucket.yaml"), "utf-8"),
|
|
134
|
+
].join("\n---\n");
|
|
135
|
+
|
|
136
|
+
const results = parseGcpCRD(content);
|
|
137
|
+
expect(results).toHaveLength(2);
|
|
138
|
+
expect(results.map((r) => r.resource.typeName)).toContain("GCP::Compute::Instance");
|
|
139
|
+
expect(results.map((r) => r.resource.typeName)).toContain("GCP::Storage::Bucket");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("skips non-CNRM CRDs", () => {
|
|
143
|
+
const content = `
|
|
144
|
+
apiVersion: apiextensions.k8s.io/v1
|
|
145
|
+
kind: CustomResourceDefinition
|
|
146
|
+
metadata:
|
|
147
|
+
name: foo.example.com
|
|
148
|
+
spec:
|
|
149
|
+
group: example.com
|
|
150
|
+
names:
|
|
151
|
+
kind: Foo
|
|
152
|
+
versions:
|
|
153
|
+
- name: v1
|
|
154
|
+
served: true
|
|
155
|
+
storage: true
|
|
156
|
+
schema:
|
|
157
|
+
openAPIV3Schema:
|
|
158
|
+
type: object
|
|
159
|
+
`;
|
|
160
|
+
const results = parseGcpCRD(content);
|
|
161
|
+
expect(results).toHaveLength(0);
|
|
162
|
+
});
|
|
163
|
+
});
|