@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,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudRunService composite — RunService + optional IAMPolicyMember for public access.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface CloudRunServiceProps {
|
|
6
|
+
/** Service name. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Container image. */
|
|
9
|
+
image: string;
|
|
10
|
+
/** GCP region. */
|
|
11
|
+
location?: string;
|
|
12
|
+
/** Port the container listens on (default: 8080). */
|
|
13
|
+
port?: number;
|
|
14
|
+
/** CPU limit (default: "1000m"). */
|
|
15
|
+
cpuLimit?: string;
|
|
16
|
+
/** Memory limit (default: "512Mi"). */
|
|
17
|
+
memoryLimit?: string;
|
|
18
|
+
/** Maximum concurrent requests per instance (default: 80). */
|
|
19
|
+
maxInstanceRequestConcurrency?: number;
|
|
20
|
+
/** Minimum instances (default: 0). */
|
|
21
|
+
minInstanceCount?: number;
|
|
22
|
+
/** Maximum instances (default: 100). */
|
|
23
|
+
maxInstanceCount?: number;
|
|
24
|
+
/** Enable public (unauthenticated) access (default: false). */
|
|
25
|
+
publicAccess?: boolean;
|
|
26
|
+
/** Environment variables. */
|
|
27
|
+
env?: Array<{ name: string; value: string }>;
|
|
28
|
+
/** Additional labels. */
|
|
29
|
+
labels?: Record<string, string>;
|
|
30
|
+
/** Namespace for all resources. */
|
|
31
|
+
namespace?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CloudRunServiceResult {
|
|
35
|
+
service: Record<string, unknown>;
|
|
36
|
+
publicIam?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a CloudRunService composite.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { CloudRunService } from "@intentius/chant-lexicon-gcp";
|
|
45
|
+
*
|
|
46
|
+
* const { service, publicIam } = CloudRunService({
|
|
47
|
+
* name: "my-api",
|
|
48
|
+
* image: "gcr.io/my-project/my-api:latest",
|
|
49
|
+
* publicAccess: true,
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function CloudRunService(props: CloudRunServiceProps): CloudRunServiceResult {
|
|
54
|
+
const {
|
|
55
|
+
name,
|
|
56
|
+
image,
|
|
57
|
+
location,
|
|
58
|
+
port = 8080,
|
|
59
|
+
cpuLimit = "1000m",
|
|
60
|
+
memoryLimit = "512Mi",
|
|
61
|
+
maxInstanceRequestConcurrency = 80,
|
|
62
|
+
minInstanceCount = 0,
|
|
63
|
+
maxInstanceCount = 100,
|
|
64
|
+
publicAccess = false,
|
|
65
|
+
env,
|
|
66
|
+
labels: extraLabels = {},
|
|
67
|
+
namespace,
|
|
68
|
+
} = props;
|
|
69
|
+
|
|
70
|
+
const commonLabels: Record<string, string> = {
|
|
71
|
+
"app.kubernetes.io/name": name,
|
|
72
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
73
|
+
...extraLabels,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const containers: Record<string, unknown>[] = [
|
|
77
|
+
{
|
|
78
|
+
image,
|
|
79
|
+
ports: [{ containerPort: port, name: "http1" }],
|
|
80
|
+
resources: {
|
|
81
|
+
limits: { cpu: cpuLimit, memory: memoryLimit },
|
|
82
|
+
},
|
|
83
|
+
...(env && env.length > 0 && { env }),
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const service: Record<string, unknown> = {
|
|
88
|
+
metadata: {
|
|
89
|
+
name,
|
|
90
|
+
...(namespace && { namespace }),
|
|
91
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "service" },
|
|
92
|
+
},
|
|
93
|
+
...(location && { location }),
|
|
94
|
+
template: {
|
|
95
|
+
maxInstanceRequestConcurrency,
|
|
96
|
+
scaling: {
|
|
97
|
+
minInstanceCount,
|
|
98
|
+
maxInstanceCount,
|
|
99
|
+
},
|
|
100
|
+
containers,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const result: CloudRunServiceResult = { service };
|
|
105
|
+
|
|
106
|
+
if (publicAccess) {
|
|
107
|
+
result.publicIam = {
|
|
108
|
+
metadata: {
|
|
109
|
+
name: `${name}-public`,
|
|
110
|
+
...(namespace && { namespace }),
|
|
111
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "iam" },
|
|
112
|
+
},
|
|
113
|
+
member: "allUsers",
|
|
114
|
+
role: "roles/run.invoker",
|
|
115
|
+
resourceRef: {
|
|
116
|
+
apiVersion: "run.cnrm.cloud.google.com/v1beta1",
|
|
117
|
+
kind: "RunService",
|
|
118
|
+
name,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudSqlInstance composite — SQLInstance + SQLDatabase + SQLUser.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface CloudSqlInstanceProps {
|
|
6
|
+
/** Instance name. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Database version (default: "POSTGRES_15"). */
|
|
9
|
+
databaseVersion?: string;
|
|
10
|
+
/** Machine tier (default: "db-f1-micro"). */
|
|
11
|
+
tier?: string;
|
|
12
|
+
/** GCP region. */
|
|
13
|
+
region?: string;
|
|
14
|
+
/** Database name to create (default: same as instance name). */
|
|
15
|
+
databaseName?: string;
|
|
16
|
+
/** User name (default: "admin"). */
|
|
17
|
+
userName?: string;
|
|
18
|
+
/** Disk size in GB (default: 10). */
|
|
19
|
+
diskSize?: number;
|
|
20
|
+
/** Disk autoresize (default: true). */
|
|
21
|
+
diskAutoresize?: boolean;
|
|
22
|
+
/** Enable backups (default: true). */
|
|
23
|
+
backupEnabled?: boolean;
|
|
24
|
+
/** Enable high availability (default: false). */
|
|
25
|
+
highAvailability?: boolean;
|
|
26
|
+
/** Additional labels. */
|
|
27
|
+
labels?: Record<string, string>;
|
|
28
|
+
/** Namespace for all resources. */
|
|
29
|
+
namespace?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CloudSqlInstanceResult {
|
|
33
|
+
instance: Record<string, unknown>;
|
|
34
|
+
database: Record<string, unknown>;
|
|
35
|
+
user: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a CloudSqlInstance composite.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { CloudSqlInstance } from "@intentius/chant-lexicon-gcp";
|
|
44
|
+
*
|
|
45
|
+
* const { instance, database, user } = CloudSqlInstance({
|
|
46
|
+
* name: "my-db",
|
|
47
|
+
* databaseVersion: "POSTGRES_15",
|
|
48
|
+
* tier: "db-custom-2-7680",
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function CloudSqlInstance(props: CloudSqlInstanceProps): CloudSqlInstanceResult {
|
|
53
|
+
const {
|
|
54
|
+
name,
|
|
55
|
+
databaseVersion = "POSTGRES_15",
|
|
56
|
+
tier = "db-f1-micro",
|
|
57
|
+
region,
|
|
58
|
+
databaseName = name,
|
|
59
|
+
userName = "admin",
|
|
60
|
+
diskSize = 10,
|
|
61
|
+
diskAutoresize = true,
|
|
62
|
+
backupEnabled = true,
|
|
63
|
+
highAvailability = false,
|
|
64
|
+
labels: extraLabels = {},
|
|
65
|
+
namespace,
|
|
66
|
+
} = props;
|
|
67
|
+
|
|
68
|
+
const commonLabels: Record<string, string> = {
|
|
69
|
+
"app.kubernetes.io/name": name,
|
|
70
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
71
|
+
...extraLabels,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const settings: Record<string, unknown> = {
|
|
75
|
+
tier,
|
|
76
|
+
diskSize,
|
|
77
|
+
diskAutoresize,
|
|
78
|
+
...(highAvailability && { availabilityType: "REGIONAL" }),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (backupEnabled) {
|
|
82
|
+
settings.backupConfiguration = {
|
|
83
|
+
enabled: true,
|
|
84
|
+
startTime: "03:00",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const instance: Record<string, unknown> = {
|
|
89
|
+
metadata: {
|
|
90
|
+
name,
|
|
91
|
+
...(namespace && { namespace }),
|
|
92
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "database" },
|
|
93
|
+
},
|
|
94
|
+
databaseVersion,
|
|
95
|
+
...(region && { region }),
|
|
96
|
+
settings,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const database: Record<string, unknown> = {
|
|
100
|
+
metadata: {
|
|
101
|
+
name: databaseName,
|
|
102
|
+
...(namespace && { namespace }),
|
|
103
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "database" },
|
|
104
|
+
},
|
|
105
|
+
instanceRef: { name },
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const user: Record<string, unknown> = {
|
|
109
|
+
metadata: {
|
|
110
|
+
name: `${name}-${userName}`,
|
|
111
|
+
...(namespace && { namespace }),
|
|
112
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "database" },
|
|
113
|
+
},
|
|
114
|
+
instanceRef: { name },
|
|
115
|
+
password: {
|
|
116
|
+
valueFrom: {
|
|
117
|
+
secretKeyRef: {
|
|
118
|
+
name: `${name}-db-password`,
|
|
119
|
+
key: "password",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return { instance, database, user };
|
|
126
|
+
}
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { GkeCluster } from "./gke-cluster";
|
|
3
|
+
import { CloudRunService } from "./cloud-run-service";
|
|
4
|
+
import { CloudSqlInstance } from "./cloud-sql-instance";
|
|
5
|
+
import { GcsBucket } from "./gcs-bucket";
|
|
6
|
+
import { VpcNetwork } from "./vpc-network";
|
|
7
|
+
import { PubSubPipeline } from "./pubsub-pipeline";
|
|
8
|
+
import { CloudFunctionWithTrigger } from "./cloud-function";
|
|
9
|
+
import { PrivateService } from "./private-service";
|
|
10
|
+
import { ManagedCertificate } from "./managed-certificate";
|
|
11
|
+
import { SecureProject } from "./secure-project";
|
|
12
|
+
|
|
13
|
+
// ── GkeCluster ──────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
describe("GkeCluster", () => {
|
|
16
|
+
test("returns cluster and node pool", () => {
|
|
17
|
+
const result = GkeCluster({ name: "my-cluster" });
|
|
18
|
+
expect(result.cluster).toBeDefined();
|
|
19
|
+
expect(result.nodePool).toBeDefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("includes common labels", () => {
|
|
23
|
+
const result = GkeCluster({ name: "my-cluster" });
|
|
24
|
+
const labels = result.cluster.metadata as any;
|
|
25
|
+
expect(labels.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("node pool references cluster", () => {
|
|
29
|
+
const result = GkeCluster({ name: "my-cluster" });
|
|
30
|
+
expect((result.nodePool as any).clusterRef.name).toBe("my-cluster");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("respects maxNodeCount", () => {
|
|
34
|
+
const result = GkeCluster({ name: "c", maxNodeCount: 20 });
|
|
35
|
+
expect((result.nodePool as any).autoscaling.maxNodeCount).toBe(20);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("sets namespace when provided", () => {
|
|
39
|
+
const result = GkeCluster({ name: "c", namespace: "infra" });
|
|
40
|
+
expect((result.cluster.metadata as any).namespace).toBe("infra");
|
|
41
|
+
expect((result.nodePool.metadata as any).namespace).toBe("infra");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("enables workload identity by default", () => {
|
|
45
|
+
const result = GkeCluster({ name: "c" });
|
|
46
|
+
expect((result.cluster as any).workloadIdentityConfig).toBeDefined();
|
|
47
|
+
expect((result.nodePool as any).nodeConfig.workloadMetadataConfig).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// ── CloudRunService ─────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
describe("CloudRunService", () => {
|
|
54
|
+
test("returns service", () => {
|
|
55
|
+
const result = CloudRunService({ name: "api", image: "gcr.io/p/api:1" });
|
|
56
|
+
expect(result.service).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("no public IAM by default", () => {
|
|
60
|
+
const result = CloudRunService({ name: "api", image: "gcr.io/p/api:1" });
|
|
61
|
+
expect(result.publicIam).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("creates public IAM when requested", () => {
|
|
65
|
+
const result = CloudRunService({
|
|
66
|
+
name: "api",
|
|
67
|
+
image: "gcr.io/p/api:1",
|
|
68
|
+
publicAccess: true,
|
|
69
|
+
});
|
|
70
|
+
expect(result.publicIam).toBeDefined();
|
|
71
|
+
expect((result.publicIam as any).member).toBe("allUsers");
|
|
72
|
+
expect((result.publicIam as any).role).toBe("roles/run.invoker");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("sets custom port", () => {
|
|
76
|
+
const result = CloudRunService({ name: "api", image: "img", port: 3000 });
|
|
77
|
+
const containers = (result.service as any).template.containers;
|
|
78
|
+
expect(containers[0].ports[0].containerPort).toBe(3000);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ── CloudSqlInstance ────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
describe("CloudSqlInstance", () => {
|
|
85
|
+
test("returns instance, database, and user", () => {
|
|
86
|
+
const result = CloudSqlInstance({ name: "db" });
|
|
87
|
+
expect(result.instance).toBeDefined();
|
|
88
|
+
expect(result.database).toBeDefined();
|
|
89
|
+
expect(result.user).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("database references instance", () => {
|
|
93
|
+
const result = CloudSqlInstance({ name: "db" });
|
|
94
|
+
expect((result.database as any).instanceRef.name).toBe("db");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("default database version is POSTGRES_15", () => {
|
|
98
|
+
const result = CloudSqlInstance({ name: "db" });
|
|
99
|
+
expect((result.instance as any).databaseVersion).toBe("POSTGRES_15");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("enables backups by default", () => {
|
|
103
|
+
const result = CloudSqlInstance({ name: "db" });
|
|
104
|
+
expect((result.instance as any).settings.backupConfiguration.enabled).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("high availability when requested", () => {
|
|
108
|
+
const result = CloudSqlInstance({ name: "db", highAvailability: true });
|
|
109
|
+
expect((result.instance as any).settings.availabilityType).toBe("REGIONAL");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── GcsBucket ───────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
describe("GcsBucket", () => {
|
|
116
|
+
test("returns bucket", () => {
|
|
117
|
+
const result = GcsBucket({ name: "my-bucket" });
|
|
118
|
+
expect(result.bucket).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("uniform access enabled by default", () => {
|
|
122
|
+
const result = GcsBucket({ name: "my-bucket" });
|
|
123
|
+
expect((result.bucket as any).uniformBucketLevelAccess).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("adds lifecycle rules", () => {
|
|
127
|
+
const result = GcsBucket({
|
|
128
|
+
name: "my-bucket",
|
|
129
|
+
lifecycleDeleteAfterDays: 30,
|
|
130
|
+
});
|
|
131
|
+
expect((result.bucket as any).lifecycleRule).toHaveLength(1);
|
|
132
|
+
expect((result.bucket as any).lifecycleRule[0].condition.age).toBe(30);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("adds encryption when kmsKeyName provided", () => {
|
|
136
|
+
const result = GcsBucket({
|
|
137
|
+
name: "my-bucket",
|
|
138
|
+
kmsKeyName: "projects/p/locations/l/keyRings/kr/cryptoKeys/k",
|
|
139
|
+
});
|
|
140
|
+
expect((result.bucket as any).encryption).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("versioning disabled by default", () => {
|
|
144
|
+
const result = GcsBucket({ name: "my-bucket" });
|
|
145
|
+
expect((result.bucket as any).versioning).toBeUndefined();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("versioning enabled when requested", () => {
|
|
149
|
+
const result = GcsBucket({ name: "my-bucket", versioning: true });
|
|
150
|
+
expect((result.bucket as any).versioning.enabled).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ── VpcNetwork ──────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
describe("VpcNetwork", () => {
|
|
157
|
+
test("returns network", () => {
|
|
158
|
+
const result = VpcNetwork({ name: "my-vpc" });
|
|
159
|
+
expect(result.network).toBeDefined();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("creates subnets", () => {
|
|
163
|
+
const result = VpcNetwork({
|
|
164
|
+
name: "my-vpc",
|
|
165
|
+
subnets: [
|
|
166
|
+
{ name: "app", ipCidrRange: "10.0.0.0/24", region: "us-central1" },
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
expect(result.subnets).toHaveLength(1);
|
|
170
|
+
expect((result.subnets[0] as any).ipCidrRange).toBe("10.0.0.0/24");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("subnet references network", () => {
|
|
174
|
+
const result = VpcNetwork({
|
|
175
|
+
name: "my-vpc",
|
|
176
|
+
subnets: [{ name: "app", ipCidrRange: "10.0.0.0/24", region: "us-central1" }],
|
|
177
|
+
});
|
|
178
|
+
expect((result.subnets[0] as any).networkRef.name).toBe("my-vpc");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("creates internal firewall by default", () => {
|
|
182
|
+
const result = VpcNetwork({
|
|
183
|
+
name: "my-vpc",
|
|
184
|
+
subnets: [{ name: "app", ipCidrRange: "10.0.0.0/24", region: "us-central1" }],
|
|
185
|
+
});
|
|
186
|
+
expect(result.firewalls.length).toBeGreaterThanOrEqual(1);
|
|
187
|
+
expect((result.firewalls[0] as any).metadata.name).toBe("my-vpc-allow-internal");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("creates NAT when enabled", () => {
|
|
191
|
+
const result = VpcNetwork({
|
|
192
|
+
name: "my-vpc",
|
|
193
|
+
enableNat: true,
|
|
194
|
+
natRegion: "us-central1",
|
|
195
|
+
});
|
|
196
|
+
expect(result.router).toBeDefined();
|
|
197
|
+
expect(result.routerNat).toBeDefined();
|
|
198
|
+
expect((result.routerNat as any).routerRef.name).toBe("my-vpc-router");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("no NAT by default", () => {
|
|
202
|
+
const result = VpcNetwork({ name: "my-vpc" });
|
|
203
|
+
expect(result.router).toBeUndefined();
|
|
204
|
+
expect(result.routerNat).toBeUndefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("IAP SSH firewall when requested", () => {
|
|
208
|
+
const result = VpcNetwork({ name: "my-vpc", allowIapSsh: true });
|
|
209
|
+
const iapFw = result.firewalls.find((f: any) => (f.metadata as any).name.includes("iap-ssh"));
|
|
210
|
+
expect(iapFw).toBeDefined();
|
|
211
|
+
expect((iapFw as any).sourceRanges).toContain("35.235.240.0/20");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ── PubSubPipeline ─────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
describe("PubSubPipeline", () => {
|
|
218
|
+
test("returns topic and subscription", () => {
|
|
219
|
+
const result = PubSubPipeline({ name: "events" });
|
|
220
|
+
expect(result.topic).toBeDefined();
|
|
221
|
+
expect(result.subscription).toBeDefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("subscription references topic", () => {
|
|
225
|
+
const result = PubSubPipeline({ name: "events" });
|
|
226
|
+
expect((result.subscription as any).topicRef.name).toBe("events-topic");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("no DLQ by default", () => {
|
|
230
|
+
const result = PubSubPipeline({ name: "events" });
|
|
231
|
+
expect(result.deadLetterTopic).toBeUndefined();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("creates DLQ when enabled", () => {
|
|
235
|
+
const result = PubSubPipeline({ name: "events", enableDeadLetterQueue: true });
|
|
236
|
+
expect(result.deadLetterTopic).toBeDefined();
|
|
237
|
+
expect((result.deadLetterTopic as any).metadata.name).toBe("events-dlq");
|
|
238
|
+
expect((result.subscription as any).deadLetterPolicy.deadLetterTopicRef.name).toBe("events-dlq");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("creates subscriber IAM when service account provided", () => {
|
|
242
|
+
const result = PubSubPipeline({
|
|
243
|
+
name: "events",
|
|
244
|
+
subscriberServiceAccount: "worker@project.iam.gserviceaccount.com",
|
|
245
|
+
});
|
|
246
|
+
expect(result.subscriberIam).toBeDefined();
|
|
247
|
+
expect((result.subscriberIam as any).role).toBe("roles/pubsub.subscriber");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("sets namespace when provided", () => {
|
|
251
|
+
const result = PubSubPipeline({ name: "events", namespace: "infra" });
|
|
252
|
+
expect((result.topic as any).metadata.namespace).toBe("infra");
|
|
253
|
+
expect((result.subscription as any).metadata.namespace).toBe("infra");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("includes managed-by label", () => {
|
|
257
|
+
const result = PubSubPipeline({ name: "events" });
|
|
258
|
+
expect((result.topic as any).metadata.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// ── CloudFunctionWithTrigger ───────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
describe("CloudFunctionWithTrigger", () => {
|
|
265
|
+
test("returns function and source bucket", () => {
|
|
266
|
+
const result = CloudFunctionWithTrigger({
|
|
267
|
+
name: "my-fn",
|
|
268
|
+
runtime: "nodejs20",
|
|
269
|
+
entryPoint: "handler",
|
|
270
|
+
});
|
|
271
|
+
expect(result.function).toBeDefined();
|
|
272
|
+
expect(result.sourceBucket).toBeDefined();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("no invoker IAM by default", () => {
|
|
276
|
+
const result = CloudFunctionWithTrigger({
|
|
277
|
+
name: "my-fn",
|
|
278
|
+
runtime: "nodejs20",
|
|
279
|
+
entryPoint: "handler",
|
|
280
|
+
});
|
|
281
|
+
expect(result.invokerIam).toBeUndefined();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("creates invoker IAM when public access enabled", () => {
|
|
285
|
+
const result = CloudFunctionWithTrigger({
|
|
286
|
+
name: "my-fn",
|
|
287
|
+
runtime: "nodejs20",
|
|
288
|
+
entryPoint: "handler",
|
|
289
|
+
publicAccess: true,
|
|
290
|
+
});
|
|
291
|
+
expect(result.invokerIam).toBeDefined();
|
|
292
|
+
expect((result.invokerIam as any).member).toBe("allUsers");
|
|
293
|
+
expect((result.invokerIam as any).role).toBe("roles/cloudfunctions.invoker");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("sets runtime and entry point", () => {
|
|
297
|
+
const result = CloudFunctionWithTrigger({
|
|
298
|
+
name: "my-fn",
|
|
299
|
+
runtime: "python312",
|
|
300
|
+
entryPoint: "main",
|
|
301
|
+
});
|
|
302
|
+
expect((result.function as any).runtime).toBe("python312");
|
|
303
|
+
expect((result.function as any).entryPoint).toBe("main");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("configures pubsub trigger", () => {
|
|
307
|
+
const result = CloudFunctionWithTrigger({
|
|
308
|
+
name: "my-fn",
|
|
309
|
+
runtime: "nodejs20",
|
|
310
|
+
entryPoint: "handler",
|
|
311
|
+
triggerType: "pubsub",
|
|
312
|
+
triggerTopic: "my-topic",
|
|
313
|
+
});
|
|
314
|
+
expect((result.function as any).eventTrigger).toBeDefined();
|
|
315
|
+
expect((result.function as any).eventTrigger.pubsubTopic).toBe("my-topic");
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ── PrivateService ─────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
describe("PrivateService", () => {
|
|
322
|
+
test("returns global address and service connection", () => {
|
|
323
|
+
const result = PrivateService({ name: "db", networkName: "my-vpc" });
|
|
324
|
+
expect(result.globalAddress).toBeDefined();
|
|
325
|
+
expect(result.serviceConnection).toBeDefined();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("address references network", () => {
|
|
329
|
+
const result = PrivateService({ name: "db", networkName: "my-vpc" });
|
|
330
|
+
expect((result.globalAddress as any).networkRef.name).toBe("my-vpc");
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("connection references network", () => {
|
|
334
|
+
const result = PrivateService({ name: "db", networkName: "my-vpc" });
|
|
335
|
+
expect((result.serviceConnection as any).networkRef.name).toBe("my-vpc");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("no DNS by default", () => {
|
|
339
|
+
const result = PrivateService({ name: "db", networkName: "my-vpc" });
|
|
340
|
+
expect(result.dnsZone).toBeUndefined();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("creates DNS zone when enabled", () => {
|
|
344
|
+
const result = PrivateService({ name: "db", networkName: "my-vpc", enableDns: true });
|
|
345
|
+
expect(result.dnsZone).toBeDefined();
|
|
346
|
+
expect((result.dnsZone as any).visibility).toBe("private");
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ── ManagedCertificate ─────────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
describe("ManagedCertificate", () => {
|
|
353
|
+
test("returns certificate", () => {
|
|
354
|
+
const result = ManagedCertificate({ name: "my-cert", domains: ["example.com"] });
|
|
355
|
+
expect(result.certificate).toBeDefined();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("certificate includes domains", () => {
|
|
359
|
+
const result = ManagedCertificate({ name: "my-cert", domains: ["example.com", "www.example.com"] });
|
|
360
|
+
expect((result.certificate as any).managed.domains).toEqual(["example.com", "www.example.com"]);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("no proxy by default", () => {
|
|
364
|
+
const result = ManagedCertificate({ name: "my-cert", domains: ["example.com"] });
|
|
365
|
+
expect(result.targetHttpsProxy).toBeUndefined();
|
|
366
|
+
expect(result.urlMap).toBeUndefined();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("creates proxy and url map when requested", () => {
|
|
370
|
+
const result = ManagedCertificate({
|
|
371
|
+
name: "my-cert",
|
|
372
|
+
domains: ["example.com"],
|
|
373
|
+
createProxy: true,
|
|
374
|
+
backendServiceName: "my-backend",
|
|
375
|
+
});
|
|
376
|
+
expect(result.targetHttpsProxy).toBeDefined();
|
|
377
|
+
expect(result.urlMap).toBeDefined();
|
|
378
|
+
expect((result.urlMap as any).defaultService.backendServiceRef.name).toBe("my-backend");
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// ── SecureProject ──────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
describe("SecureProject", () => {
|
|
385
|
+
test("returns project, audit config, and services", () => {
|
|
386
|
+
const result = SecureProject({ name: "my-project" });
|
|
387
|
+
expect(result.project).toBeDefined();
|
|
388
|
+
expect(result.auditConfig).toBeDefined();
|
|
389
|
+
expect(result.services.length).toBeGreaterThan(0);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("audit config covers all services", () => {
|
|
393
|
+
const result = SecureProject({ name: "my-project" });
|
|
394
|
+
expect((result.auditConfig as any).service).toBe("allServices");
|
|
395
|
+
expect((result.auditConfig as any).auditLogConfigs.length).toBe(3);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("enables default APIs", () => {
|
|
399
|
+
const result = SecureProject({ name: "my-project" });
|
|
400
|
+
expect(result.services.length).toBe(5);
|
|
401
|
+
expect(result.services.some((s: any) => s.resourceID === "compute.googleapis.com")).toBe(true);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("no owner IAM by default", () => {
|
|
405
|
+
const result = SecureProject({ name: "my-project" });
|
|
406
|
+
expect(result.ownerIam).toBeUndefined();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("creates owner IAM when provided", () => {
|
|
410
|
+
const result = SecureProject({
|
|
411
|
+
name: "my-project",
|
|
412
|
+
owner: "user:admin@example.com",
|
|
413
|
+
});
|
|
414
|
+
expect(result.ownerIam).toBeDefined();
|
|
415
|
+
expect((result.ownerIam as any).member).toBe("user:admin@example.com");
|
|
416
|
+
expect((result.ownerIam as any).role).toBe("roles/owner");
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("creates logging sink when destination provided", () => {
|
|
420
|
+
const result = SecureProject({
|
|
421
|
+
name: "my-project",
|
|
422
|
+
loggingSinkDestination: "bigquery.googleapis.com/projects/my-project/datasets/audit_logs",
|
|
423
|
+
});
|
|
424
|
+
expect(result.loggingSink).toBeDefined();
|
|
425
|
+
expect((result.loggingSink as any).destination).toContain("bigquery");
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("no logging sink by default", () => {
|
|
429
|
+
const result = SecureProject({ name: "my-project" });
|
|
430
|
+
expect(result.loggingSink).toBeUndefined();
|
|
431
|
+
});
|
|
432
|
+
});
|