@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
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* CloudRunServiceComposite composite — RunService + optional IAMPolicyMember for public access.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
6
|
+
import {
|
|
7
|
+
CloudRunService as CloudRunServiceResource,
|
|
8
|
+
IAMPolicyMember,
|
|
9
|
+
} from "../generated";
|
|
10
|
+
|
|
5
11
|
export interface CloudRunServiceProps {
|
|
6
12
|
/** Service name. */
|
|
7
13
|
name: string;
|
|
@@ -29,11 +35,11 @@ export interface CloudRunServiceProps {
|
|
|
29
35
|
labels?: Record<string, string>;
|
|
30
36
|
/** Namespace for all resources. */
|
|
31
37
|
namespace?: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
/** Per-member defaults for customizing individual resources. */
|
|
39
|
+
defaults?: {
|
|
40
|
+
service?: Partial<ConstructorParameters<typeof CloudRunServiceResource>[0]>;
|
|
41
|
+
publicIam?: Partial<ConstructorParameters<typeof IAMPolicyMember>[0]>;
|
|
42
|
+
};
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|
|
@@ -50,7 +56,7 @@ export interface CloudRunServiceResult {
|
|
|
50
56
|
* });
|
|
51
57
|
* ```
|
|
52
58
|
*/
|
|
53
|
-
export
|
|
59
|
+
export const CloudRunServiceComposite = Composite<CloudRunServiceProps>((props) => {
|
|
54
60
|
const {
|
|
55
61
|
name,
|
|
56
62
|
image,
|
|
@@ -65,6 +71,7 @@ export function CloudRunService(props: CloudRunServiceProps): CloudRunServiceRes
|
|
|
65
71
|
env,
|
|
66
72
|
labels: extraLabels = {},
|
|
67
73
|
namespace,
|
|
74
|
+
defaults: defs,
|
|
68
75
|
} = props;
|
|
69
76
|
|
|
70
77
|
const commonLabels: Record<string, string> = {
|
|
@@ -84,7 +91,7 @@ export function CloudRunService(props: CloudRunServiceProps): CloudRunServiceRes
|
|
|
84
91
|
},
|
|
85
92
|
];
|
|
86
93
|
|
|
87
|
-
const service
|
|
94
|
+
const service = new CloudRunServiceResource(mergeDefaults({
|
|
88
95
|
metadata: {
|
|
89
96
|
name,
|
|
90
97
|
...(namespace && { namespace }),
|
|
@@ -99,12 +106,12 @@ export function CloudRunService(props: CloudRunServiceProps): CloudRunServiceRes
|
|
|
99
106
|
},
|
|
100
107
|
containers,
|
|
101
108
|
},
|
|
102
|
-
};
|
|
109
|
+
} as Record<string, unknown>, defs?.service));
|
|
103
110
|
|
|
104
|
-
const result:
|
|
111
|
+
const result: Record<string, any> = { service };
|
|
105
112
|
|
|
106
113
|
if (publicAccess) {
|
|
107
|
-
result.publicIam = {
|
|
114
|
+
result.publicIam = new IAMPolicyMember(mergeDefaults({
|
|
108
115
|
metadata: {
|
|
109
116
|
name: `${name}-public`,
|
|
110
117
|
...(namespace && { namespace }),
|
|
@@ -117,8 +124,8 @@ export function CloudRunService(props: CloudRunServiceProps): CloudRunServiceRes
|
|
|
117
124
|
kind: "RunService",
|
|
118
125
|
name,
|
|
119
126
|
},
|
|
120
|
-
};
|
|
127
|
+
} as Record<string, unknown>, defs?.publicIam));
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
return result;
|
|
124
|
-
}
|
|
131
|
+
}, "CloudRunServiceComposite");
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* CloudSqlInstance composite — SQLInstance + SQLDatabase + SQLUser.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
6
|
+
import { SQLInstance, SQLDatabase, SQLUser } from "../generated";
|
|
7
|
+
|
|
5
8
|
export interface CloudSqlInstanceProps {
|
|
6
9
|
/** Instance name. */
|
|
7
10
|
name: string;
|
|
@@ -27,12 +30,12 @@ export interface CloudSqlInstanceProps {
|
|
|
27
30
|
labels?: Record<string, string>;
|
|
28
31
|
/** Namespace for all resources. */
|
|
29
32
|
namespace?: string;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
/** Per-member defaults for customizing individual resources. */
|
|
34
|
+
defaults?: {
|
|
35
|
+
instance?: Partial<ConstructorParameters<typeof SQLInstance>[0]>;
|
|
36
|
+
database?: Partial<ConstructorParameters<typeof SQLDatabase>[0]>;
|
|
37
|
+
user?: Partial<ConstructorParameters<typeof SQLUser>[0]>;
|
|
38
|
+
};
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
@@ -49,7 +52,7 @@ export interface CloudSqlInstanceResult {
|
|
|
49
52
|
* });
|
|
50
53
|
* ```
|
|
51
54
|
*/
|
|
52
|
-
export
|
|
55
|
+
export const CloudSqlInstance = Composite<CloudSqlInstanceProps>((props) => {
|
|
53
56
|
const {
|
|
54
57
|
name,
|
|
55
58
|
databaseVersion = "POSTGRES_15",
|
|
@@ -63,6 +66,7 @@ export function CloudSqlInstance(props: CloudSqlInstanceProps): CloudSqlInstance
|
|
|
63
66
|
highAvailability = false,
|
|
64
67
|
labels: extraLabels = {},
|
|
65
68
|
namespace,
|
|
69
|
+
defaults: defs,
|
|
66
70
|
} = props;
|
|
67
71
|
|
|
68
72
|
const commonLabels: Record<string, string> = {
|
|
@@ -85,7 +89,7 @@ export function CloudSqlInstance(props: CloudSqlInstanceProps): CloudSqlInstance
|
|
|
85
89
|
};
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
const instance
|
|
92
|
+
const instance = new SQLInstance(mergeDefaults({
|
|
89
93
|
metadata: {
|
|
90
94
|
name,
|
|
91
95
|
...(namespace && { namespace }),
|
|
@@ -94,18 +98,18 @@ export function CloudSqlInstance(props: CloudSqlInstanceProps): CloudSqlInstance
|
|
|
94
98
|
databaseVersion,
|
|
95
99
|
...(region && { region }),
|
|
96
100
|
settings,
|
|
97
|
-
};
|
|
101
|
+
} as Record<string, unknown>, defs?.instance));
|
|
98
102
|
|
|
99
|
-
const database
|
|
103
|
+
const database = new SQLDatabase(mergeDefaults({
|
|
100
104
|
metadata: {
|
|
101
105
|
name: databaseName,
|
|
102
106
|
...(namespace && { namespace }),
|
|
103
107
|
labels: { ...commonLabels, "app.kubernetes.io/component": "database" },
|
|
104
108
|
},
|
|
105
109
|
instanceRef: { name },
|
|
106
|
-
};
|
|
110
|
+
} as Record<string, unknown>, defs?.database));
|
|
107
111
|
|
|
108
|
-
const user
|
|
112
|
+
const user = new SQLUser(mergeDefaults({
|
|
109
113
|
metadata: {
|
|
110
114
|
name: `${name}-${userName}`,
|
|
111
115
|
...(namespace && { namespace }),
|
|
@@ -120,7 +124,7 @@ export function CloudSqlInstance(props: CloudSqlInstanceProps): CloudSqlInstance
|
|
|
120
124
|
},
|
|
121
125
|
},
|
|
122
126
|
},
|
|
123
|
-
};
|
|
127
|
+
} as Record<string, unknown>, defs?.user));
|
|
124
128
|
|
|
125
129
|
return { instance, database, user };
|
|
126
|
-
}
|
|
130
|
+
}, "CloudSqlInstance");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, test, expect } from "bun:test";
|
|
2
2
|
import { GkeCluster } from "./gke-cluster";
|
|
3
|
-
import {
|
|
3
|
+
import { CloudRunServiceComposite } from "./cloud-run-service";
|
|
4
4
|
import { CloudSqlInstance } from "./cloud-sql-instance";
|
|
5
5
|
import { GcsBucket } from "./gcs-bucket";
|
|
6
6
|
import { VpcNetwork } from "./vpc-network";
|
|
@@ -10,6 +10,11 @@ import { PrivateService } from "./private-service";
|
|
|
10
10
|
import { ManagedCertificate } from "./managed-certificate";
|
|
11
11
|
import { SecureProject } from "./secure-project";
|
|
12
12
|
|
|
13
|
+
/** Helper to extract props from a Declarable member. */
|
|
14
|
+
function p(member: unknown): Record<string, any> {
|
|
15
|
+
return (member as any).props;
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
// ── GkeCluster ──────────────────────────────────────────────────────
|
|
14
19
|
|
|
15
20
|
describe("GkeCluster", () => {
|
|
@@ -21,60 +26,81 @@ describe("GkeCluster", () => {
|
|
|
21
26
|
|
|
22
27
|
test("includes common labels", () => {
|
|
23
28
|
const result = GkeCluster({ name: "my-cluster" });
|
|
24
|
-
const
|
|
25
|
-
expect(
|
|
29
|
+
const meta = p(result.cluster).metadata;
|
|
30
|
+
expect(meta.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
26
31
|
});
|
|
27
32
|
|
|
28
33
|
test("node pool references cluster", () => {
|
|
29
34
|
const result = GkeCluster({ name: "my-cluster" });
|
|
30
|
-
expect((result.nodePool
|
|
35
|
+
expect(p(result.nodePool).clusterRef.name).toBe("my-cluster");
|
|
31
36
|
});
|
|
32
37
|
|
|
33
38
|
test("respects maxNodeCount", () => {
|
|
34
39
|
const result = GkeCluster({ name: "c", maxNodeCount: 20 });
|
|
35
|
-
expect((result.nodePool
|
|
40
|
+
expect(p(result.nodePool).autoscaling.maxNodeCount).toBe(20);
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
test("sets namespace when provided", () => {
|
|
39
44
|
const result = GkeCluster({ name: "c", namespace: "infra" });
|
|
40
|
-
expect((result.cluster.metadata
|
|
41
|
-
expect((result.nodePool.metadata
|
|
45
|
+
expect(p(result.cluster).metadata.namespace).toBe("infra");
|
|
46
|
+
expect(p(result.nodePool).metadata.namespace).toBe("infra");
|
|
42
47
|
});
|
|
43
48
|
|
|
44
49
|
test("enables workload identity by default", () => {
|
|
45
50
|
const result = GkeCluster({ name: "c" });
|
|
46
|
-
expect((result.cluster
|
|
47
|
-
expect((result.nodePool
|
|
51
|
+
expect(p(result.cluster).workloadIdentityConfig).toBeDefined();
|
|
52
|
+
expect(p(result.nodePool).nodeConfig.workloadMetadataConfig).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("workloadPool uses explicit projectId", () => {
|
|
56
|
+
const result = GkeCluster({ name: "c", projectId: "my-project-123" });
|
|
57
|
+
expect(p(result.cluster).workloadIdentityConfig.workloadPool).toBe(
|
|
58
|
+
"my-project-123.svc.id.goog",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("workloadPool falls back to GCP_PROJECT_ID env var", () => {
|
|
63
|
+
const prev = process.env.GCP_PROJECT_ID;
|
|
64
|
+
process.env.GCP_PROJECT_ID = "env-project-456";
|
|
65
|
+
try {
|
|
66
|
+
const result = GkeCluster({ name: "c" });
|
|
67
|
+
expect(p(result.cluster).workloadIdentityConfig.workloadPool).toBe(
|
|
68
|
+
"env-project-456.svc.id.goog",
|
|
69
|
+
);
|
|
70
|
+
} finally {
|
|
71
|
+
if (prev === undefined) delete process.env.GCP_PROJECT_ID;
|
|
72
|
+
else process.env.GCP_PROJECT_ID = prev;
|
|
73
|
+
}
|
|
48
74
|
});
|
|
49
75
|
});
|
|
50
76
|
|
|
51
77
|
// ── CloudRunService ─────────────────────────────────────────────────
|
|
52
78
|
|
|
53
|
-
describe("
|
|
79
|
+
describe("CloudRunServiceComposite", () => {
|
|
54
80
|
test("returns service", () => {
|
|
55
|
-
const result =
|
|
81
|
+
const result = CloudRunServiceComposite({ name: "api", image: "gcr.io/p/api:1" });
|
|
56
82
|
expect(result.service).toBeDefined();
|
|
57
83
|
});
|
|
58
84
|
|
|
59
85
|
test("no public IAM by default", () => {
|
|
60
|
-
const result =
|
|
86
|
+
const result = CloudRunServiceComposite({ name: "api", image: "gcr.io/p/api:1" });
|
|
61
87
|
expect(result.publicIam).toBeUndefined();
|
|
62
88
|
});
|
|
63
89
|
|
|
64
90
|
test("creates public IAM when requested", () => {
|
|
65
|
-
const result =
|
|
91
|
+
const result = CloudRunServiceComposite({
|
|
66
92
|
name: "api",
|
|
67
93
|
image: "gcr.io/p/api:1",
|
|
68
94
|
publicAccess: true,
|
|
69
95
|
});
|
|
70
96
|
expect(result.publicIam).toBeDefined();
|
|
71
|
-
expect((result.publicIam
|
|
72
|
-
expect((result.publicIam
|
|
97
|
+
expect(p(result.publicIam).member).toBe("allUsers");
|
|
98
|
+
expect(p(result.publicIam).role).toBe("roles/run.invoker");
|
|
73
99
|
});
|
|
74
100
|
|
|
75
101
|
test("sets custom port", () => {
|
|
76
|
-
const result =
|
|
77
|
-
const containers = (result.service
|
|
102
|
+
const result = CloudRunServiceComposite({ name: "api", image: "img", port: 3000 });
|
|
103
|
+
const containers = p(result.service).template.containers;
|
|
78
104
|
expect(containers[0].ports[0].containerPort).toBe(3000);
|
|
79
105
|
});
|
|
80
106
|
});
|
|
@@ -91,22 +117,22 @@ describe("CloudSqlInstance", () => {
|
|
|
91
117
|
|
|
92
118
|
test("database references instance", () => {
|
|
93
119
|
const result = CloudSqlInstance({ name: "db" });
|
|
94
|
-
expect((result.database
|
|
120
|
+
expect(p(result.database).instanceRef.name).toBe("db");
|
|
95
121
|
});
|
|
96
122
|
|
|
97
123
|
test("default database version is POSTGRES_15", () => {
|
|
98
124
|
const result = CloudSqlInstance({ name: "db" });
|
|
99
|
-
expect((result.instance
|
|
125
|
+
expect(p(result.instance).databaseVersion).toBe("POSTGRES_15");
|
|
100
126
|
});
|
|
101
127
|
|
|
102
128
|
test("enables backups by default", () => {
|
|
103
129
|
const result = CloudSqlInstance({ name: "db" });
|
|
104
|
-
expect((result.instance
|
|
130
|
+
expect(p(result.instance).settings.backupConfiguration.enabled).toBe(true);
|
|
105
131
|
});
|
|
106
132
|
|
|
107
133
|
test("high availability when requested", () => {
|
|
108
134
|
const result = CloudSqlInstance({ name: "db", highAvailability: true });
|
|
109
|
-
expect((result.instance
|
|
135
|
+
expect(p(result.instance).settings.availabilityType).toBe("REGIONAL");
|
|
110
136
|
});
|
|
111
137
|
});
|
|
112
138
|
|
|
@@ -120,7 +146,7 @@ describe("GcsBucket", () => {
|
|
|
120
146
|
|
|
121
147
|
test("uniform access enabled by default", () => {
|
|
122
148
|
const result = GcsBucket({ name: "my-bucket" });
|
|
123
|
-
expect((result.bucket
|
|
149
|
+
expect(p(result.bucket).uniformBucketLevelAccess).toBe(true);
|
|
124
150
|
});
|
|
125
151
|
|
|
126
152
|
test("adds lifecycle rules", () => {
|
|
@@ -128,8 +154,8 @@ describe("GcsBucket", () => {
|
|
|
128
154
|
name: "my-bucket",
|
|
129
155
|
lifecycleDeleteAfterDays: 30,
|
|
130
156
|
});
|
|
131
|
-
expect((result.bucket
|
|
132
|
-
expect((result.bucket
|
|
157
|
+
expect(p(result.bucket).lifecycleRule).toHaveLength(1);
|
|
158
|
+
expect(p(result.bucket).lifecycleRule[0].condition.age).toBe(30);
|
|
133
159
|
});
|
|
134
160
|
|
|
135
161
|
test("adds encryption when kmsKeyName provided", () => {
|
|
@@ -137,17 +163,17 @@ describe("GcsBucket", () => {
|
|
|
137
163
|
name: "my-bucket",
|
|
138
164
|
kmsKeyName: "projects/p/locations/l/keyRings/kr/cryptoKeys/k",
|
|
139
165
|
});
|
|
140
|
-
expect((result.bucket
|
|
166
|
+
expect(p(result.bucket).encryption).toBeDefined();
|
|
141
167
|
});
|
|
142
168
|
|
|
143
169
|
test("versioning disabled by default", () => {
|
|
144
170
|
const result = GcsBucket({ name: "my-bucket" });
|
|
145
|
-
expect((result.bucket
|
|
171
|
+
expect(p(result.bucket).versioning).toBeUndefined();
|
|
146
172
|
});
|
|
147
173
|
|
|
148
174
|
test("versioning enabled when requested", () => {
|
|
149
175
|
const result = GcsBucket({ name: "my-bucket", versioning: true });
|
|
150
|
-
expect((result.bucket
|
|
176
|
+
expect(p(result.bucket).versioning.enabled).toBe(true);
|
|
151
177
|
});
|
|
152
178
|
});
|
|
153
179
|
|
|
@@ -166,8 +192,8 @@ describe("VpcNetwork", () => {
|
|
|
166
192
|
{ name: "app", ipCidrRange: "10.0.0.0/24", region: "us-central1" },
|
|
167
193
|
],
|
|
168
194
|
});
|
|
169
|
-
expect(result.
|
|
170
|
-
expect((result.
|
|
195
|
+
expect(result.subnet_app).toBeDefined();
|
|
196
|
+
expect(p(result.subnet_app).ipCidrRange).toBe("10.0.0.0/24");
|
|
171
197
|
});
|
|
172
198
|
|
|
173
199
|
test("subnet references network", () => {
|
|
@@ -175,7 +201,7 @@ describe("VpcNetwork", () => {
|
|
|
175
201
|
name: "my-vpc",
|
|
176
202
|
subnets: [{ name: "app", ipCidrRange: "10.0.0.0/24", region: "us-central1" }],
|
|
177
203
|
});
|
|
178
|
-
expect((result.
|
|
204
|
+
expect(p(result.subnet_app).networkRef.name).toBe("my-vpc");
|
|
179
205
|
});
|
|
180
206
|
|
|
181
207
|
test("creates internal firewall by default", () => {
|
|
@@ -183,8 +209,8 @@ describe("VpcNetwork", () => {
|
|
|
183
209
|
name: "my-vpc",
|
|
184
210
|
subnets: [{ name: "app", ipCidrRange: "10.0.0.0/24", region: "us-central1" }],
|
|
185
211
|
});
|
|
186
|
-
expect(result.
|
|
187
|
-
expect((result.
|
|
212
|
+
expect(result.firewallAllowInternal).toBeDefined();
|
|
213
|
+
expect(p(result.firewallAllowInternal).metadata.name).toBe("my-vpc-allow-internal");
|
|
188
214
|
});
|
|
189
215
|
|
|
190
216
|
test("creates NAT when enabled", () => {
|
|
@@ -195,7 +221,7 @@ describe("VpcNetwork", () => {
|
|
|
195
221
|
});
|
|
196
222
|
expect(result.router).toBeDefined();
|
|
197
223
|
expect(result.routerNat).toBeDefined();
|
|
198
|
-
expect((result.routerNat
|
|
224
|
+
expect(p(result.routerNat).routerRef.name).toBe("my-vpc-router");
|
|
199
225
|
});
|
|
200
226
|
|
|
201
227
|
test("no NAT by default", () => {
|
|
@@ -206,9 +232,8 @@ describe("VpcNetwork", () => {
|
|
|
206
232
|
|
|
207
233
|
test("IAP SSH firewall when requested", () => {
|
|
208
234
|
const result = VpcNetwork({ name: "my-vpc", allowIapSsh: true });
|
|
209
|
-
|
|
210
|
-
expect(
|
|
211
|
-
expect((iapFw as any).sourceRanges).toContain("35.235.240.0/20");
|
|
235
|
+
expect(result.firewallAllowIapSsh).toBeDefined();
|
|
236
|
+
expect(p(result.firewallAllowIapSsh).sourceRanges).toContain("35.235.240.0/20");
|
|
212
237
|
});
|
|
213
238
|
});
|
|
214
239
|
|
|
@@ -223,7 +248,7 @@ describe("PubSubPipeline", () => {
|
|
|
223
248
|
|
|
224
249
|
test("subscription references topic", () => {
|
|
225
250
|
const result = PubSubPipeline({ name: "events" });
|
|
226
|
-
expect((result.subscription
|
|
251
|
+
expect(p(result.subscription).topicRef.name).toBe("events-topic");
|
|
227
252
|
});
|
|
228
253
|
|
|
229
254
|
test("no DLQ by default", () => {
|
|
@@ -234,8 +259,8 @@ describe("PubSubPipeline", () => {
|
|
|
234
259
|
test("creates DLQ when enabled", () => {
|
|
235
260
|
const result = PubSubPipeline({ name: "events", enableDeadLetterQueue: true });
|
|
236
261
|
expect(result.deadLetterTopic).toBeDefined();
|
|
237
|
-
expect((result.deadLetterTopic
|
|
238
|
-
expect((result.subscription
|
|
262
|
+
expect(p(result.deadLetterTopic).metadata.name).toBe("events-dlq");
|
|
263
|
+
expect(p(result.subscription).deadLetterPolicy.deadLetterTopicRef.name).toBe("events-dlq");
|
|
239
264
|
});
|
|
240
265
|
|
|
241
266
|
test("creates subscriber IAM when service account provided", () => {
|
|
@@ -244,18 +269,18 @@ describe("PubSubPipeline", () => {
|
|
|
244
269
|
subscriberServiceAccount: "worker@project.iam.gserviceaccount.com",
|
|
245
270
|
});
|
|
246
271
|
expect(result.subscriberIam).toBeDefined();
|
|
247
|
-
expect((result.subscriberIam
|
|
272
|
+
expect(p(result.subscriberIam).role).toBe("roles/pubsub.subscriber");
|
|
248
273
|
});
|
|
249
274
|
|
|
250
275
|
test("sets namespace when provided", () => {
|
|
251
276
|
const result = PubSubPipeline({ name: "events", namespace: "infra" });
|
|
252
|
-
expect((result.topic
|
|
253
|
-
expect((result.subscription
|
|
277
|
+
expect(p(result.topic).metadata.namespace).toBe("infra");
|
|
278
|
+
expect(p(result.subscription).metadata.namespace).toBe("infra");
|
|
254
279
|
});
|
|
255
280
|
|
|
256
281
|
test("includes managed-by label", () => {
|
|
257
282
|
const result = PubSubPipeline({ name: "events" });
|
|
258
|
-
expect((result.topic
|
|
283
|
+
expect(p(result.topic).metadata.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
259
284
|
});
|
|
260
285
|
});
|
|
261
286
|
|
|
@@ -289,8 +314,8 @@ describe("CloudFunctionWithTrigger", () => {
|
|
|
289
314
|
publicAccess: true,
|
|
290
315
|
});
|
|
291
316
|
expect(result.invokerIam).toBeDefined();
|
|
292
|
-
expect((result.invokerIam
|
|
293
|
-
expect((result.invokerIam
|
|
317
|
+
expect(p(result.invokerIam).member).toBe("allUsers");
|
|
318
|
+
expect(p(result.invokerIam).role).toBe("roles/cloudfunctions.invoker");
|
|
294
319
|
});
|
|
295
320
|
|
|
296
321
|
test("sets runtime and entry point", () => {
|
|
@@ -299,8 +324,8 @@ describe("CloudFunctionWithTrigger", () => {
|
|
|
299
324
|
runtime: "python312",
|
|
300
325
|
entryPoint: "main",
|
|
301
326
|
});
|
|
302
|
-
expect((result.function
|
|
303
|
-
expect((result.function
|
|
327
|
+
expect(p(result.function).runtime).toBe("python312");
|
|
328
|
+
expect(p(result.function).entryPoint).toBe("main");
|
|
304
329
|
});
|
|
305
330
|
|
|
306
331
|
test("configures pubsub trigger", () => {
|
|
@@ -311,8 +336,8 @@ describe("CloudFunctionWithTrigger", () => {
|
|
|
311
336
|
triggerType: "pubsub",
|
|
312
337
|
triggerTopic: "my-topic",
|
|
313
338
|
});
|
|
314
|
-
expect((result.function
|
|
315
|
-
expect((result.function
|
|
339
|
+
expect(p(result.function).eventTrigger).toBeDefined();
|
|
340
|
+
expect(p(result.function).eventTrigger.pubsubTopic).toBe("my-topic");
|
|
316
341
|
});
|
|
317
342
|
});
|
|
318
343
|
|
|
@@ -327,12 +352,12 @@ describe("PrivateService", () => {
|
|
|
327
352
|
|
|
328
353
|
test("address references network", () => {
|
|
329
354
|
const result = PrivateService({ name: "db", networkName: "my-vpc" });
|
|
330
|
-
expect((result.globalAddress
|
|
355
|
+
expect(p(result.globalAddress).networkRef.name).toBe("my-vpc");
|
|
331
356
|
});
|
|
332
357
|
|
|
333
358
|
test("connection references network", () => {
|
|
334
359
|
const result = PrivateService({ name: "db", networkName: "my-vpc" });
|
|
335
|
-
expect((result.serviceConnection
|
|
360
|
+
expect(p(result.serviceConnection).networkRef.name).toBe("my-vpc");
|
|
336
361
|
});
|
|
337
362
|
|
|
338
363
|
test("no DNS by default", () => {
|
|
@@ -343,7 +368,7 @@ describe("PrivateService", () => {
|
|
|
343
368
|
test("creates DNS zone when enabled", () => {
|
|
344
369
|
const result = PrivateService({ name: "db", networkName: "my-vpc", enableDns: true });
|
|
345
370
|
expect(result.dnsZone).toBeDefined();
|
|
346
|
-
expect((result.dnsZone
|
|
371
|
+
expect(p(result.dnsZone).visibility).toBe("private");
|
|
347
372
|
});
|
|
348
373
|
});
|
|
349
374
|
|
|
@@ -357,7 +382,7 @@ describe("ManagedCertificate", () => {
|
|
|
357
382
|
|
|
358
383
|
test("certificate includes domains", () => {
|
|
359
384
|
const result = ManagedCertificate({ name: "my-cert", domains: ["example.com", "www.example.com"] });
|
|
360
|
-
expect((result.certificate
|
|
385
|
+
expect(p(result.certificate).managed.domains).toEqual(["example.com", "www.example.com"]);
|
|
361
386
|
});
|
|
362
387
|
|
|
363
388
|
test("no proxy by default", () => {
|
|
@@ -375,7 +400,7 @@ describe("ManagedCertificate", () => {
|
|
|
375
400
|
});
|
|
376
401
|
expect(result.targetHttpsProxy).toBeDefined();
|
|
377
402
|
expect(result.urlMap).toBeDefined();
|
|
378
|
-
expect((result.urlMap
|
|
403
|
+
expect(p(result.urlMap).defaultService.backendServiceRef.name).toBe("my-backend");
|
|
379
404
|
});
|
|
380
405
|
});
|
|
381
406
|
|
|
@@ -386,19 +411,26 @@ describe("SecureProject", () => {
|
|
|
386
411
|
const result = SecureProject({ name: "my-project" });
|
|
387
412
|
expect(result.project).toBeDefined();
|
|
388
413
|
expect(result.auditConfig).toBeDefined();
|
|
389
|
-
|
|
414
|
+
// Default 5 APIs become service_compute, service_container, etc.
|
|
415
|
+
expect(result.service_compute).toBeDefined();
|
|
416
|
+
expect(result.service_container).toBeDefined();
|
|
417
|
+
expect(result.service_iam).toBeDefined();
|
|
418
|
+
expect(result.service_logging).toBeDefined();
|
|
419
|
+
expect(result.service_monitoring).toBeDefined();
|
|
390
420
|
});
|
|
391
421
|
|
|
392
422
|
test("audit config covers all services", () => {
|
|
393
423
|
const result = SecureProject({ name: "my-project" });
|
|
394
|
-
expect((result.auditConfig
|
|
395
|
-
expect((result.auditConfig
|
|
424
|
+
expect(p(result.auditConfig).service).toBe("allServices");
|
|
425
|
+
expect(p(result.auditConfig).auditLogConfigs.length).toBe(3);
|
|
396
426
|
});
|
|
397
427
|
|
|
398
428
|
test("enables default APIs", () => {
|
|
399
429
|
const result = SecureProject({ name: "my-project" });
|
|
400
|
-
|
|
401
|
-
|
|
430
|
+
// 5 services as individual members
|
|
431
|
+
const serviceKeys = Object.keys(result.members).filter((k) => k.startsWith("service_"));
|
|
432
|
+
expect(serviceKeys.length).toBe(5);
|
|
433
|
+
expect(p(result.service_compute).resourceID).toBe("compute.googleapis.com");
|
|
402
434
|
});
|
|
403
435
|
|
|
404
436
|
test("no owner IAM by default", () => {
|
|
@@ -412,8 +444,8 @@ describe("SecureProject", () => {
|
|
|
412
444
|
owner: "user:admin@example.com",
|
|
413
445
|
});
|
|
414
446
|
expect(result.ownerIam).toBeDefined();
|
|
415
|
-
expect((result.ownerIam
|
|
416
|
-
expect((result.ownerIam
|
|
447
|
+
expect(p(result.ownerIam).member).toBe("user:admin@example.com");
|
|
448
|
+
expect(p(result.ownerIam).role).toBe("roles/owner");
|
|
417
449
|
});
|
|
418
450
|
|
|
419
451
|
test("creates logging sink when destination provided", () => {
|
|
@@ -422,7 +454,7 @@ describe("SecureProject", () => {
|
|
|
422
454
|
loggingSinkDestination: "bigquery.googleapis.com/projects/my-project/datasets/audit_logs",
|
|
423
455
|
});
|
|
424
456
|
expect(result.loggingSink).toBeDefined();
|
|
425
|
-
expect((result.loggingSink
|
|
457
|
+
expect(p(result.loggingSink).destination).toContain("bigquery");
|
|
426
458
|
});
|
|
427
459
|
|
|
428
460
|
test("no logging sink by default", () => {
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* GcsBucket composite — StorageBucket with encryption, uniform access, and lifecycle.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
6
|
+
import { StorageBucket } from "../generated";
|
|
7
|
+
|
|
5
8
|
export interface GcsBucketProps {
|
|
6
9
|
/** Bucket name. */
|
|
7
10
|
name: string;
|
|
@@ -23,10 +26,10 @@ export interface GcsBucketProps {
|
|
|
23
26
|
labels?: Record<string, string>;
|
|
24
27
|
/** Namespace for all resources. */
|
|
25
28
|
namespace?: string;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
/** Per-member defaults for customizing individual resources. */
|
|
30
|
+
defaults?: {
|
|
31
|
+
bucket?: Partial<ConstructorParameters<typeof StorageBucket>[0]>;
|
|
32
|
+
};
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/**
|
|
@@ -44,7 +47,7 @@ export interface GcsBucketResult {
|
|
|
44
47
|
* });
|
|
45
48
|
* ```
|
|
46
49
|
*/
|
|
47
|
-
export
|
|
50
|
+
export const GcsBucket = Composite<GcsBucketProps>((props) => {
|
|
48
51
|
const {
|
|
49
52
|
name,
|
|
50
53
|
location = "US",
|
|
@@ -56,6 +59,7 @@ export function GcsBucket(props: GcsBucketProps): GcsBucketResult {
|
|
|
56
59
|
lifecycleNearlineAfterDays,
|
|
57
60
|
labels: extraLabels = {},
|
|
58
61
|
namespace,
|
|
62
|
+
defaults: defs,
|
|
59
63
|
} = props;
|
|
60
64
|
|
|
61
65
|
const commonLabels: Record<string, string> = {
|
|
@@ -75,7 +79,7 @@ export function GcsBucket(props: GcsBucketProps): GcsBucketResult {
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
if (kmsKeyName) {
|
|
78
|
-
spec.encryption = {
|
|
82
|
+
spec.encryption = { kmsKeyRef: { external: kmsKeyName } };
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const lifecycleRules: Array<Record<string, unknown>> = [];
|
|
@@ -98,14 +102,14 @@ export function GcsBucket(props: GcsBucketProps): GcsBucketResult {
|
|
|
98
102
|
spec.lifecycleRule = lifecycleRules;
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
const bucket
|
|
105
|
+
const bucket = new StorageBucket(mergeDefaults({
|
|
102
106
|
metadata: {
|
|
103
107
|
name,
|
|
104
108
|
...(namespace && { namespace }),
|
|
105
109
|
labels: { ...commonLabels, "app.kubernetes.io/component": "storage" },
|
|
106
110
|
},
|
|
107
111
|
...spec,
|
|
108
|
-
};
|
|
112
|
+
} as Record<string, unknown>, defs?.bucket));
|
|
109
113
|
|
|
110
114
|
return { bucket };
|
|
111
|
-
}
|
|
115
|
+
}, "GcsBucket");
|