@intentius/chant-lexicon-gcp 0.124.31 → 0.124.32
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/actions/index.js +45 -0
- package/dist/api/read-client.js +105 -0
- package/dist/codegen/docs-cli.js +6 -0
- package/dist/codegen/docs.js +115 -0
- package/dist/codegen/generate-cli.js +17 -0
- package/dist/codegen/generate.js +214 -0
- package/dist/codegen/naming.js +164 -0
- package/dist/codegen/package.js +43 -0
- package/dist/composites/catalog.js +1245 -0
- package/dist/composites/cloud-function.js +65 -0
- package/dist/composites/cloud-run-service.js +71 -0
- package/dist/composites/cloud-sql-instance.js +85 -0
- package/dist/composites/gcs-bucket.js +64 -0
- package/dist/composites/gke-cluster.js +113 -0
- package/dist/composites/gke-crdb-region.js +137 -0
- package/dist/composites/gpu-node-pool.js +74 -0
- package/dist/composites/index.js +15 -0
- package/dist/composites/landing-zone.js +89 -0
- package/dist/composites/managed-certificate.js +46 -0
- package/dist/composites/memorystore-redis.js +52 -0
- package/dist/composites/multi-region-vpc.js +108 -0
- package/dist/composites/private-service.js +50 -0
- package/dist/composites/pubsub-pipeline.js +63 -0
- package/dist/composites/secure-project.js +85 -0
- package/dist/composites/vpc-network.js +113 -0
- package/dist/coverage.js +28 -0
- package/dist/deep-observe-hooks.js +152 -0
- package/dist/deep-observe.js +273 -0
- package/dist/default-labels.js +67 -0
- package/dist/describe-resources.js +264 -0
- package/dist/detect.js +16 -0
- package/dist/export-resources.js +59 -0
- package/dist/generated/index.js +1804 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/governance.js +98 -0
- package/dist/import/generator.js +68 -0
- package/dist/import/live-export.js +73 -0
- package/dist/import/parser.js +61 -0
- package/dist/index.js +36 -0
- package/dist/integrity.json +3 -3
- package/dist/lint/audit-catalog.js +38 -0
- package/dist/lint/audit-lineage.js +63 -0
- package/dist/lint/post-synth/gcp-helpers.js +91 -0
- package/dist/lint/post-synth/index.js +54 -0
- package/dist/lint/post-synth/schema-registry.d.ts.map +1 -1
- package/dist/lint/post-synth/schema-registry.js +69 -0
- package/dist/lint/post-synth/wgc101.js +47 -0
- package/dist/lint/post-synth/wgc102.js +29 -0
- package/dist/lint/post-synth/wgc103.js +37 -0
- package/dist/lint/post-synth/wgc104.js +34 -0
- package/dist/lint/post-synth/wgc105.js +39 -0
- package/dist/lint/post-synth/wgc106.js +30 -0
- package/dist/lint/post-synth/wgc107.js +33 -0
- package/dist/lint/post-synth/wgc108.js +34 -0
- package/dist/lint/post-synth/wgc109.js +33 -0
- package/dist/lint/post-synth/wgc110.js +32 -0
- package/dist/lint/post-synth/wgc111.js +47 -0
- package/dist/lint/post-synth/wgc112.js +46 -0
- package/dist/lint/post-synth/wgc113.js +35 -0
- package/dist/lint/post-synth/wgc201.js +30 -0
- package/dist/lint/post-synth/wgc202.js +33 -0
- package/dist/lint/post-synth/wgc203.js +35 -0
- package/dist/lint/post-synth/wgc204.js +33 -0
- package/dist/lint/post-synth/wgc301.js +28 -0
- package/dist/lint/post-synth/wgc302.js +28 -0
- package/dist/lint/post-synth/wgc303.js +29 -0
- package/dist/lint/post-synth/wgc401.js +50 -0
- package/dist/lint/post-synth/wgc402.js +46 -0
- package/dist/lint/post-synth/wgc403.js +74 -0
- package/dist/lint/post-synth/wgc501.js +63 -0
- package/dist/lint/post-synth/wgc502.js +37 -0
- package/dist/lint/post-synth/wgc503.js +49 -0
- package/dist/lint/rules/hardcoded-project.js +51 -0
- package/dist/lint/rules/hardcoded-region.js +51 -0
- package/dist/lint/rules/index.js +3 -0
- package/dist/lint/rules/public-iam.js +37 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +15 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +46 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/floci-gcp.js +49 -0
- package/dist/op/activities/gcp-apply.js +618 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +50 -0
- package/dist/ownership.js +38 -0
- package/dist/package-cli.js +20 -0
- package/dist/plugin.js +371 -0
- package/dist/pseudo.js +15 -0
- package/dist/rules/schema-registry.ts +2 -1
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +233 -0
- package/dist/spec/fetch.js +107 -0
- package/dist/spec/parse.js +323 -0
- package/dist/validate-cli.js +11 -0
- package/dist/validate.js +24 -0
- package/dist/variables.js +15 -0
- package/package.json +8 -8
- package/src/codegen/package.ts +2 -2
- package/src/lint/post-synth/schema-registry.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/serializer.ts +2 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Landing-zone bootstrap composites — the GCP slice.
|
|
3
|
+
*
|
|
4
|
+
* Fixed-shape resources for greenfield creation of the recommended
|
|
5
|
+
* governance foundation through a Config Connector management stack. The
|
|
6
|
+
* evaluability rules (EVL002/004) require composites to declare a fixed set
|
|
7
|
+
* of resources, so these carry the foundation only; arbitrary folder/project
|
|
8
|
+
* trees are authored as config with `landingZoneConfig()` (../governance.ts)
|
|
9
|
+
* and reconciled by an external governance reconciler, and further folders/projects are
|
|
10
|
+
* declared as ordinary resources.
|
|
11
|
+
*
|
|
12
|
+
* The pieces compose:
|
|
13
|
+
* const lz = GovernanceFoundation({ orgId: "123456789012" });
|
|
14
|
+
* const lr = LocationRestriction({ orgId: "123456789012", locations: ["in:eu-locations"] });
|
|
15
|
+
* const ac = OrganizationAuditConfig({ orgId: "123456789012" });
|
|
16
|
+
* There is no OrganizationRoot analog — GCP organizations come from Cloud
|
|
17
|
+
* Identity / Workspace, not the resource API, so greenfield and brownfield
|
|
18
|
+
* both start from an existing org id.
|
|
19
|
+
*/
|
|
20
|
+
import { Composite } from "@intentius/chant";
|
|
21
|
+
import { IAMAuditConfig, OrgpolicyPolicy, ResourcemanagerFolder } from "../generated/index.js";
|
|
22
|
+
import { AUDIT_ALL_SERVICES, DISABLE_SA_KEY_CREATION, SKIP_DEFAULT_NETWORK, resourceLocationRestriction, } from "../governance.js";
|
|
23
|
+
/** "organizations/123" and "123" are both accepted everywhere an org id appears. */
|
|
24
|
+
function orgNumber(orgId) {
|
|
25
|
+
return orgId.replace(/^organizations\//, "");
|
|
26
|
+
}
|
|
27
|
+
function labels(component) {
|
|
28
|
+
return {
|
|
29
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
30
|
+
"app.kubernetes.io/component": component,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function orgPolicy(metaName, policy, orgId, namespace) {
|
|
34
|
+
return new OrgpolicyPolicy({
|
|
35
|
+
metadata: {
|
|
36
|
+
name: metaName,
|
|
37
|
+
...(namespace && { namespace }),
|
|
38
|
+
labels: labels("org-policy"),
|
|
39
|
+
},
|
|
40
|
+
resourceID: policy.constraint,
|
|
41
|
+
organizationRef: { external: orgNumber(orgId) },
|
|
42
|
+
spec: { rules: policy.rules },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Security / Infrastructure / Sandbox / Workloads folders with
|
|
47
|
+
* `disable-sa-key-creation` and `skip-default-network` enforced at the
|
|
48
|
+
* organization root — the same baseline `landingZoneConfig()` declares.
|
|
49
|
+
*/
|
|
50
|
+
export const GovernanceFoundation = Composite((props) => {
|
|
51
|
+
const { orgId, namespace } = props;
|
|
52
|
+
const folder = (metaName, displayName) => new ResourcemanagerFolder({
|
|
53
|
+
metadata: {
|
|
54
|
+
name: metaName,
|
|
55
|
+
...(namespace && { namespace }),
|
|
56
|
+
labels: labels("folder"),
|
|
57
|
+
},
|
|
58
|
+
displayName,
|
|
59
|
+
organizationRef: { external: orgNumber(orgId) },
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
folderSecurity: folder("security", "Security"),
|
|
63
|
+
folderInfrastructure: folder("infrastructure", "Infrastructure"),
|
|
64
|
+
folderSandbox: folder("sandbox", "Sandbox"),
|
|
65
|
+
folderWorkloads: folder("workloads", "Workloads"),
|
|
66
|
+
policyDisableSaKeyCreation: orgPolicy("disable-sa-key-creation", DISABLE_SA_KEY_CREATION, orgId, namespace),
|
|
67
|
+
policySkipDefaultNetwork: orgPolicy("skip-default-network", SKIP_DEFAULT_NETWORK, orgId, namespace),
|
|
68
|
+
};
|
|
69
|
+
}, "GovernanceFoundation");
|
|
70
|
+
export const LocationRestriction = Composite((props) => {
|
|
71
|
+
const policyResourceLocations = orgPolicy("resource-location-restriction", resourceLocationRestriction(props.locations), props.orgId, props.namespace);
|
|
72
|
+
return { policyResourceLocations };
|
|
73
|
+
}, "LocationRestriction");
|
|
74
|
+
export const OrganizationAuditConfig = Composite((props) => {
|
|
75
|
+
const auditConfig = new IAMAuditConfig({
|
|
76
|
+
metadata: {
|
|
77
|
+
name: "organization-audit",
|
|
78
|
+
...(props.namespace && { namespace: props.namespace }),
|
|
79
|
+
labels: labels("audit"),
|
|
80
|
+
},
|
|
81
|
+
resourceRef: {
|
|
82
|
+
kind: "Organization",
|
|
83
|
+
external: `organizations/${orgNumber(props.orgId)}`,
|
|
84
|
+
},
|
|
85
|
+
service: props.service ?? AUDIT_ALL_SERVICES.service,
|
|
86
|
+
auditLogConfigs: (props.logTypes ?? [...AUDIT_ALL_SERVICES.logTypes]).map((logType) => ({ logType })),
|
|
87
|
+
});
|
|
88
|
+
return { auditConfig };
|
|
89
|
+
}, "OrganizationAuditConfig");
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ManagedCertificate composite — ManagedSslCertificate + optional TargetHttpsProxy + UrlMap.
|
|
3
|
+
*/
|
|
4
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
5
|
+
import { ManagedSSLCertificate, TargetHTTPSProxy, URLMap } from "../generated/index.js";
|
|
6
|
+
export const ManagedCertificate = Composite((props) => {
|
|
7
|
+
const { name, domains, createProxy = false, backendServiceName, labels: extraLabels = {}, namespace, defaults: defs, } = props;
|
|
8
|
+
const commonLabels = {
|
|
9
|
+
"app.kubernetes.io/name": name,
|
|
10
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
11
|
+
...extraLabels,
|
|
12
|
+
};
|
|
13
|
+
const certificate = new ManagedSSLCertificate(mergeDefaults({
|
|
14
|
+
metadata: {
|
|
15
|
+
name,
|
|
16
|
+
...(namespace && { namespace }),
|
|
17
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "certificate" },
|
|
18
|
+
},
|
|
19
|
+
managed: {
|
|
20
|
+
domains,
|
|
21
|
+
},
|
|
22
|
+
}, defs?.certificate));
|
|
23
|
+
const result = { certificate };
|
|
24
|
+
if (createProxy && backendServiceName) {
|
|
25
|
+
result.urlMap = new URLMap(mergeDefaults({
|
|
26
|
+
metadata: {
|
|
27
|
+
name: `${name}-url-map`,
|
|
28
|
+
...(namespace && { namespace }),
|
|
29
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "url-map" },
|
|
30
|
+
},
|
|
31
|
+
defaultService: {
|
|
32
|
+
backendServiceRef: { name: backendServiceName },
|
|
33
|
+
},
|
|
34
|
+
}, defs?.urlMap));
|
|
35
|
+
result.targetHttpsProxy = new TargetHTTPSProxy(mergeDefaults({
|
|
36
|
+
metadata: {
|
|
37
|
+
name: `${name}-proxy`,
|
|
38
|
+
...(namespace && { namespace }),
|
|
39
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "proxy" },
|
|
40
|
+
},
|
|
41
|
+
urlMapRef: { name: `${name}-url-map` },
|
|
42
|
+
sslCertificates: [{ name }],
|
|
43
|
+
}, defs?.targetHttpsProxy));
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}, "ManagedCertificate");
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemorystoreRedis composite — RedisInstance with purpose-driven defaults.
|
|
3
|
+
*/
|
|
4
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
5
|
+
import { RedisInstance } from "../generated/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create a MemorystoreRedis composite.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { MemorystoreRedis } from "@intentius/chant-lexicon-gcp";
|
|
12
|
+
*
|
|
13
|
+
* const { instance } = MemorystoreRedis({
|
|
14
|
+
* name: "my-app-persistent",
|
|
15
|
+
* purpose: "persistent",
|
|
16
|
+
* tier: "STANDARD_HA",
|
|
17
|
+
* memorySizeGb: 5,
|
|
18
|
+
* region: "us-central1",
|
|
19
|
+
* networkRef: "my-vpc",
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const MemorystoreRedis = Composite((props) => {
|
|
24
|
+
const { name, purpose, tier, memorySizeGb, region, networkRef, redisVersion = "REDIS_7_0", authEnabled = true, namespace, labels: extraLabels = {}, defaults: defs, } = props;
|
|
25
|
+
const maxmemoryPolicy = purpose === "persistent" ? "noeviction" : "allkeys-lru";
|
|
26
|
+
const commonLabels = {
|
|
27
|
+
"app.kubernetes.io/name": name,
|
|
28
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
29
|
+
...extraLabels,
|
|
30
|
+
};
|
|
31
|
+
const instance = new RedisInstance(mergeDefaults({
|
|
32
|
+
metadata: {
|
|
33
|
+
name,
|
|
34
|
+
...(namespace && { namespace }),
|
|
35
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "cache" },
|
|
36
|
+
},
|
|
37
|
+
region,
|
|
38
|
+
tier,
|
|
39
|
+
memorySizeGb,
|
|
40
|
+
authEnabled,
|
|
41
|
+
redisVersion,
|
|
42
|
+
connectMode: "PRIVATE_SERVICE_ACCESS",
|
|
43
|
+
authorizedNetworkRef: { name: networkRef },
|
|
44
|
+
// TLS disabled: GitLab's redis-rb client requires either no TLS or a custom CA cert
|
|
45
|
+
// injected via REDIS_SSL_CA_FILE. AUTH (authEnabled: true) provides access control.
|
|
46
|
+
transitEncryptionMode: "DISABLED",
|
|
47
|
+
redisConfigs: {
|
|
48
|
+
"maxmemory-policy": maxmemoryPolicy,
|
|
49
|
+
},
|
|
50
|
+
}, defs?.instance));
|
|
51
|
+
return { instance };
|
|
52
|
+
}, "MemorystoreRedis");
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MultiRegionVpc composite — VPC + 2 subnets per region + Router + RouterNAT per region + Firewall.
|
|
3
|
+
*
|
|
4
|
+
* Collapses the boilerplate of a multi-region GKE networking stack into a single call.
|
|
5
|
+
* Each region gets node and pod subnets, a Cloud Router, and a Cloud NAT gateway.
|
|
6
|
+
* A single allow-internal firewall rule covers all region CIDRs.
|
|
7
|
+
*/
|
|
8
|
+
import { Composite } from "@intentius/chant";
|
|
9
|
+
import { VPCNetwork, Subnetwork, Router, RouterNAT, Firewall } from "../generated/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Create a MultiRegionVpc composite — one VPC with subnets, Cloud NAT, and an
|
|
12
|
+
* allow-internal firewall for every region in the array.
|
|
13
|
+
*
|
|
14
|
+
* The composite eliminates the per-region Router/NAT boilerplate that arises when
|
|
15
|
+
* `VpcNetwork` only handles a single NAT region and the rest must be wired manually.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { MultiRegionVpc } from "@intentius/chant-lexicon-gcp";
|
|
20
|
+
*
|
|
21
|
+
* export const network = MultiRegionVpc({
|
|
22
|
+
* name: "crdb-multi-region",
|
|
23
|
+
* regions: [
|
|
24
|
+
* { region: "us-east4", regionAlias: "east", nodeSubnetCidr: "10.1.0.0/20", podSubnetCidr: "10.1.16.0/20" },
|
|
25
|
+
* { region: "us-central1", regionAlias: "central", nodeSubnetCidr: "10.2.0.0/20", podSubnetCidr: "10.2.16.0/20" },
|
|
26
|
+
* { region: "us-west1", regionAlias: "west", nodeSubnetCidr: "10.3.0.0/20", podSubnetCidr: "10.3.16.0/20" },
|
|
27
|
+
* ],
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const MultiRegionVpc = Composite((props) => {
|
|
32
|
+
const { name, regions, enableFlowLogs = false, labels: extraLabels = {}, namespace, } = props;
|
|
33
|
+
const commonLabels = {
|
|
34
|
+
"app.kubernetes.io/name": name,
|
|
35
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
36
|
+
...extraLabels,
|
|
37
|
+
};
|
|
38
|
+
const meta = (component, resourceName) => ({
|
|
39
|
+
metadata: {
|
|
40
|
+
name: resourceName ?? name,
|
|
41
|
+
...(namespace && { namespace }),
|
|
42
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": component },
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
const network = new VPCNetwork({
|
|
46
|
+
...meta("network"),
|
|
47
|
+
autoCreateSubnetworks: false,
|
|
48
|
+
routingMode: "REGIONAL",
|
|
49
|
+
});
|
|
50
|
+
const subnetEntries = {};
|
|
51
|
+
const routerEntries = {};
|
|
52
|
+
const natEntries = {};
|
|
53
|
+
const allCidrs = [];
|
|
54
|
+
for (const r of regions) {
|
|
55
|
+
const alias = r.regionAlias ?? r.region;
|
|
56
|
+
allCidrs.push(r.nodeSubnetCidr, r.podSubnetCidr);
|
|
57
|
+
const flowConfig = enableFlowLogs
|
|
58
|
+
? { logConfig: { enable: true, aggregationInterval: "INTERVAL_5_SEC", flowSampling: 0.5 } }
|
|
59
|
+
: {};
|
|
60
|
+
subnetEntries[`subnet_${alias}_nodes`] = new Subnetwork({
|
|
61
|
+
...meta("subnet", `${name}-${alias}-nodes`),
|
|
62
|
+
networkRef: { name },
|
|
63
|
+
ipCidrRange: r.nodeSubnetCidr,
|
|
64
|
+
region: r.region,
|
|
65
|
+
privateIpGoogleAccess: true,
|
|
66
|
+
...flowConfig,
|
|
67
|
+
});
|
|
68
|
+
subnetEntries[`subnet_${alias}_pods`] = new Subnetwork({
|
|
69
|
+
...meta("subnet", `${name}-${alias}-pods`),
|
|
70
|
+
networkRef: { name },
|
|
71
|
+
ipCidrRange: r.podSubnetCidr,
|
|
72
|
+
region: r.region,
|
|
73
|
+
privateIpGoogleAccess: true,
|
|
74
|
+
...flowConfig,
|
|
75
|
+
});
|
|
76
|
+
const routerName = `${name}-${alias}`;
|
|
77
|
+
routerEntries[`router_${alias}`] = new Router({
|
|
78
|
+
...meta("router", routerName),
|
|
79
|
+
networkRef: { name },
|
|
80
|
+
region: r.region,
|
|
81
|
+
});
|
|
82
|
+
natEntries[`nat_${alias}`] = new RouterNAT({
|
|
83
|
+
...meta("nat", routerName),
|
|
84
|
+
routerRef: { name: routerName },
|
|
85
|
+
region: r.region,
|
|
86
|
+
natIpAllocateOption: "AUTO_ONLY",
|
|
87
|
+
sourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Allow all TCP/UDP/ICMP between every subnet CIDR in the VPC.
|
|
91
|
+
const firewallInternal = new Firewall({
|
|
92
|
+
...meta("firewall", `${name}-allow-internal`),
|
|
93
|
+
networkRef: { name },
|
|
94
|
+
allow: [
|
|
95
|
+
{ protocol: "tcp", ports: ["0-65535"] },
|
|
96
|
+
{ protocol: "udp", ports: ["0-65535"] },
|
|
97
|
+
{ protocol: "icmp" },
|
|
98
|
+
],
|
|
99
|
+
sourceRanges: allCidrs,
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
network,
|
|
103
|
+
...subnetEntries,
|
|
104
|
+
...routerEntries,
|
|
105
|
+
...natEntries,
|
|
106
|
+
firewallInternal,
|
|
107
|
+
};
|
|
108
|
+
}, "MultiRegionVpc");
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PrivateService composite — GlobalAddress + ServiceNetworkingConnection + optional DNS.
|
|
3
|
+
*/
|
|
4
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
5
|
+
import { ComputeAddress, ServicenetworkingConnection, DNSManagedZone, } from "../generated/index.js";
|
|
6
|
+
export const PrivateService = Composite((props) => {
|
|
7
|
+
const { name, networkName, prefixLength = 16, addressType = "INTERNAL", purpose = "VPC_PEERING", enableDns = false, dnsZoneName, dnsSuffix = "internal.", labels: extraLabels = {}, namespace, defaults: defs, } = props;
|
|
8
|
+
const commonLabels = {
|
|
9
|
+
"app.kubernetes.io/name": name,
|
|
10
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
11
|
+
...extraLabels,
|
|
12
|
+
};
|
|
13
|
+
const globalAddress = new ComputeAddress(mergeDefaults({
|
|
14
|
+
metadata: {
|
|
15
|
+
name: `${name}-address`,
|
|
16
|
+
...(namespace && { namespace }),
|
|
17
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "address" },
|
|
18
|
+
},
|
|
19
|
+
addressType,
|
|
20
|
+
purpose,
|
|
21
|
+
prefixLength,
|
|
22
|
+
networkRef: { name: networkName },
|
|
23
|
+
}, defs?.globalAddress));
|
|
24
|
+
const serviceConnection = new ServicenetworkingConnection(mergeDefaults({
|
|
25
|
+
metadata: {
|
|
26
|
+
name: `${name}-connection`,
|
|
27
|
+
...(namespace && { namespace }),
|
|
28
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "connection" },
|
|
29
|
+
},
|
|
30
|
+
networkRef: { name: networkName },
|
|
31
|
+
service: "servicenetworking.googleapis.com",
|
|
32
|
+
reservedPeeringRanges: [{ name: `${name}-address` }],
|
|
33
|
+
}, defs?.serviceConnection));
|
|
34
|
+
const result = { globalAddress, serviceConnection };
|
|
35
|
+
if (enableDns) {
|
|
36
|
+
result.dnsZone = new DNSManagedZone(mergeDefaults({
|
|
37
|
+
metadata: {
|
|
38
|
+
name: dnsZoneName ?? `${name}-dns`,
|
|
39
|
+
...(namespace && { namespace }),
|
|
40
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "dns" },
|
|
41
|
+
},
|
|
42
|
+
dnsName: dnsSuffix,
|
|
43
|
+
visibility: "private",
|
|
44
|
+
privateVisibilityConfig: {
|
|
45
|
+
networks: [{ networkRef: { name: networkName } }],
|
|
46
|
+
},
|
|
47
|
+
}, defs?.dnsZone));
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}, "PrivateService");
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PubSubPipeline composite — Topic + Subscription + optional DLQ topic + subscriber IAM binding.
|
|
3
|
+
*/
|
|
4
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
5
|
+
import { PubSubTopic, PubSubSubscription, IAMPolicyMember } from "../generated/index.js";
|
|
6
|
+
export const PubSubPipeline = Composite((props) => {
|
|
7
|
+
const { name, ackDeadlineSeconds = 10, messageRetentionDuration = "604800s", enableDeadLetterQueue = false, maxDeliveryAttempts = 5, subscriberServiceAccount, labels: extraLabels = {}, namespace, defaults: defs, } = props;
|
|
8
|
+
const commonLabels = {
|
|
9
|
+
"app.kubernetes.io/name": name,
|
|
10
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
11
|
+
...extraLabels,
|
|
12
|
+
};
|
|
13
|
+
const topic = new PubSubTopic(mergeDefaults({
|
|
14
|
+
metadata: {
|
|
15
|
+
name: `${name}-topic`,
|
|
16
|
+
...(namespace && { namespace }),
|
|
17
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "topic" },
|
|
18
|
+
},
|
|
19
|
+
}, defs?.topic));
|
|
20
|
+
const subscriptionProps = {
|
|
21
|
+
metadata: {
|
|
22
|
+
name: `${name}-subscription`,
|
|
23
|
+
...(namespace && { namespace }),
|
|
24
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "subscription" },
|
|
25
|
+
},
|
|
26
|
+
topicRef: { name: `${name}-topic` },
|
|
27
|
+
ackDeadlineSeconds,
|
|
28
|
+
messageRetentionDuration,
|
|
29
|
+
};
|
|
30
|
+
const result = { topic };
|
|
31
|
+
if (enableDeadLetterQueue) {
|
|
32
|
+
result.deadLetterTopic = new PubSubTopic(mergeDefaults({
|
|
33
|
+
metadata: {
|
|
34
|
+
name: `${name}-dlq`,
|
|
35
|
+
...(namespace && { namespace }),
|
|
36
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "dead-letter" },
|
|
37
|
+
},
|
|
38
|
+
}, defs?.deadLetterTopic));
|
|
39
|
+
subscriptionProps.deadLetterPolicy = {
|
|
40
|
+
deadLetterTopicRef: { name: `${name}-dlq` },
|
|
41
|
+
maxDeliveryAttempts,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const subscription = new PubSubSubscription(mergeDefaults(subscriptionProps, defs?.subscription));
|
|
45
|
+
result.subscription = subscription;
|
|
46
|
+
if (subscriberServiceAccount) {
|
|
47
|
+
result.subscriberIam = new IAMPolicyMember(mergeDefaults({
|
|
48
|
+
metadata: {
|
|
49
|
+
name: `${name}-subscriber`,
|
|
50
|
+
...(namespace && { namespace }),
|
|
51
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "iam" },
|
|
52
|
+
},
|
|
53
|
+
member: `serviceAccount:${subscriberServiceAccount}`,
|
|
54
|
+
role: "roles/pubsub.subscriber",
|
|
55
|
+
resourceRef: {
|
|
56
|
+
apiVersion: "pubsub.cnrm.cloud.google.com/v1beta1",
|
|
57
|
+
kind: "PubSubSubscription",
|
|
58
|
+
name: `${name}-subscription`,
|
|
59
|
+
},
|
|
60
|
+
}, defs?.subscriberIam));
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}, "PubSubPipeline");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecureProject composite — Project + IAMAuditConfig + Service enablement + owner IAM + LoggingSink.
|
|
3
|
+
*/
|
|
4
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
5
|
+
import { ResourcemanagerProject, IAMAuditConfig, ServiceusageService, IAMPolicyMember, LogSink, } from "../generated/index.js";
|
|
6
|
+
export const SecureProject = Composite((props) => {
|
|
7
|
+
const { name, orgId, folderId, billingAccountRef, owner, enabledApis = [
|
|
8
|
+
"compute.googleapis.com",
|
|
9
|
+
"container.googleapis.com",
|
|
10
|
+
"iam.googleapis.com",
|
|
11
|
+
"logging.googleapis.com",
|
|
12
|
+
"monitoring.googleapis.com",
|
|
13
|
+
], loggingSinkDestination, loggingSinkFilter = 'logName:"cloudaudit.googleapis.com"', labels: extraLabels = {}, namespace, defaults: defs, } = props;
|
|
14
|
+
const commonLabels = {
|
|
15
|
+
"app.kubernetes.io/name": name,
|
|
16
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
17
|
+
...extraLabels,
|
|
18
|
+
};
|
|
19
|
+
const project = new ResourcemanagerProject(mergeDefaults({
|
|
20
|
+
metadata: {
|
|
21
|
+
name,
|
|
22
|
+
...(namespace && { namespace }),
|
|
23
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "project" },
|
|
24
|
+
},
|
|
25
|
+
...(orgId && { organizationRef: { external: orgId } }),
|
|
26
|
+
...(folderId && { folderRef: { external: folderId } }),
|
|
27
|
+
...(billingAccountRef && { billingAccountRef: { external: billingAccountRef } }),
|
|
28
|
+
}, defs?.project));
|
|
29
|
+
const auditConfig = new IAMAuditConfig(mergeDefaults({
|
|
30
|
+
metadata: {
|
|
31
|
+
name: `${name}-audit`,
|
|
32
|
+
...(namespace && { namespace }),
|
|
33
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "audit" },
|
|
34
|
+
},
|
|
35
|
+
service: "allServices",
|
|
36
|
+
auditLogConfigs: [
|
|
37
|
+
{ logType: "ADMIN_READ" },
|
|
38
|
+
{ logType: "DATA_READ" },
|
|
39
|
+
{ logType: "DATA_WRITE" },
|
|
40
|
+
],
|
|
41
|
+
}, defs?.auditConfig));
|
|
42
|
+
// Spread services into named members (service_compute, service_container, etc.)
|
|
43
|
+
// so the Composite validator accepts them as individual Declarables.
|
|
44
|
+
const serviceEntries = {};
|
|
45
|
+
for (const api of enabledApis) {
|
|
46
|
+
const key = `service_${api.split(".")[0]}`;
|
|
47
|
+
serviceEntries[key] = new ServiceusageService({
|
|
48
|
+
metadata: {
|
|
49
|
+
name: `${name}-${api.split(".")[0]}`,
|
|
50
|
+
...(namespace && { namespace }),
|
|
51
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "service" },
|
|
52
|
+
},
|
|
53
|
+
resourceID: api,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const result = { project, auditConfig, ...serviceEntries };
|
|
57
|
+
if (owner) {
|
|
58
|
+
result.ownerIam = new IAMPolicyMember(mergeDefaults({
|
|
59
|
+
metadata: {
|
|
60
|
+
name: `${name}-owner`,
|
|
61
|
+
...(namespace && { namespace }),
|
|
62
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "iam" },
|
|
63
|
+
},
|
|
64
|
+
member: owner,
|
|
65
|
+
role: "roles/owner",
|
|
66
|
+
resourceRef: {
|
|
67
|
+
apiVersion: "resourcemanager.cnrm.cloud.google.com/v1beta1",
|
|
68
|
+
kind: "Project",
|
|
69
|
+
name,
|
|
70
|
+
},
|
|
71
|
+
}, defs?.ownerIam));
|
|
72
|
+
}
|
|
73
|
+
if (loggingSinkDestination) {
|
|
74
|
+
result.loggingSink = new LogSink(mergeDefaults({
|
|
75
|
+
metadata: {
|
|
76
|
+
name: `${name}-audit-sink`,
|
|
77
|
+
...(namespace && { namespace }),
|
|
78
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "logging" },
|
|
79
|
+
},
|
|
80
|
+
destination: loggingSinkDestination,
|
|
81
|
+
filter: loggingSinkFilter,
|
|
82
|
+
}, defs?.loggingSink));
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}, "SecureProject");
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VpcNetwork composite — ComputeNetwork + ComputeSubnetwork + ComputeFirewall + ComputeRouterNAT.
|
|
3
|
+
*/
|
|
4
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
5
|
+
import { VPCNetwork, Subnetwork, Firewall, Router, RouterNAT } from "../generated/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create a VpcNetwork composite.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { VpcNetwork } from "@intentius/chant-lexicon-gcp";
|
|
12
|
+
*
|
|
13
|
+
* const { network, subnets, firewalls } = VpcNetwork({
|
|
14
|
+
* name: "my-vpc",
|
|
15
|
+
* subnets: [
|
|
16
|
+
* { name: "app", ipCidrRange: "10.0.0.0/24", region: "us-central1" },
|
|
17
|
+
* { name: "data", ipCidrRange: "10.0.1.0/24", region: "us-central1" },
|
|
18
|
+
* ],
|
|
19
|
+
* enableNat: true,
|
|
20
|
+
* natRegion: "us-central1",
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export const VpcNetwork = Composite((props) => {
|
|
25
|
+
const { name, autoCreateSubnetworks = false, subnets: subnetDefs = [], enableNat = false, natRegion, allowInternalTraffic = true, allowIapSsh = false, labels: extraLabels = {}, namespace, defaults: defs, } = props;
|
|
26
|
+
const commonLabels = {
|
|
27
|
+
"app.kubernetes.io/name": name,
|
|
28
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
29
|
+
...extraLabels,
|
|
30
|
+
};
|
|
31
|
+
const network = new VPCNetwork(mergeDefaults({
|
|
32
|
+
metadata: {
|
|
33
|
+
name,
|
|
34
|
+
...(namespace && { namespace }),
|
|
35
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "network" },
|
|
36
|
+
},
|
|
37
|
+
autoCreateSubnetworks,
|
|
38
|
+
routingMode: "REGIONAL",
|
|
39
|
+
}, defs?.network));
|
|
40
|
+
// Spread subnets into named members (subnet_<name>) for Composite validation.
|
|
41
|
+
const subnetEntries = {};
|
|
42
|
+
for (const sub of subnetDefs) {
|
|
43
|
+
subnetEntries[`subnet_${sub.name}`] = new Subnetwork({
|
|
44
|
+
metadata: {
|
|
45
|
+
name: `${name}-${sub.name}`,
|
|
46
|
+
...(namespace && { namespace }),
|
|
47
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "subnet" },
|
|
48
|
+
},
|
|
49
|
+
networkRef: { name },
|
|
50
|
+
ipCidrRange: sub.ipCidrRange,
|
|
51
|
+
region: sub.region,
|
|
52
|
+
privateIpGoogleAccess: sub.privateIpGoogleAccess ?? true,
|
|
53
|
+
...(sub.secondaryIpRanges && sub.secondaryIpRanges.length > 0 && {
|
|
54
|
+
secondaryIpRange: sub.secondaryIpRanges,
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Spread firewalls into named members for Composite validation.
|
|
59
|
+
const firewallEntries = {};
|
|
60
|
+
if (allowInternalTraffic) {
|
|
61
|
+
firewallEntries.firewallAllowInternal = new Firewall({
|
|
62
|
+
metadata: {
|
|
63
|
+
name: `${name}-allow-internal`,
|
|
64
|
+
...(namespace && { namespace }),
|
|
65
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "firewall" },
|
|
66
|
+
},
|
|
67
|
+
networkRef: { name },
|
|
68
|
+
allow: [
|
|
69
|
+
{ protocol: "tcp", ports: ["0-65535"] },
|
|
70
|
+
{ protocol: "udp", ports: ["0-65535"] },
|
|
71
|
+
{ protocol: "icmp" },
|
|
72
|
+
],
|
|
73
|
+
sourceRanges: subnetDefs.map((s) => s.ipCidrRange),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (allowIapSsh) {
|
|
77
|
+
firewallEntries.firewallAllowIapSsh = new Firewall({
|
|
78
|
+
metadata: {
|
|
79
|
+
name: `${name}-allow-iap-ssh`,
|
|
80
|
+
...(namespace && { namespace }),
|
|
81
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "firewall" },
|
|
82
|
+
},
|
|
83
|
+
networkRef: { name },
|
|
84
|
+
allow: [{ protocol: "tcp", ports: ["22"] }],
|
|
85
|
+
sourceRanges: ["35.235.240.0/20"], // IAP IP range
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const result = { network, ...subnetEntries, ...firewallEntries };
|
|
89
|
+
if (enableNat && natRegion) {
|
|
90
|
+
const routerName = `${name}-router`;
|
|
91
|
+
result.router = new Router(mergeDefaults({
|
|
92
|
+
metadata: {
|
|
93
|
+
name: routerName,
|
|
94
|
+
...(namespace && { namespace }),
|
|
95
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "router" },
|
|
96
|
+
},
|
|
97
|
+
networkRef: { name },
|
|
98
|
+
region: natRegion,
|
|
99
|
+
}, defs?.router));
|
|
100
|
+
result.routerNat = new RouterNAT(mergeDefaults({
|
|
101
|
+
metadata: {
|
|
102
|
+
name: `${name}-nat`,
|
|
103
|
+
...(namespace && { namespace }),
|
|
104
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "nat" },
|
|
105
|
+
},
|
|
106
|
+
routerRef: { name: routerName },
|
|
107
|
+
region: natRegion,
|
|
108
|
+
natIpAllocateOption: "AUTO_ONLY",
|
|
109
|
+
sourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_IP_RANGES",
|
|
110
|
+
}, defs?.routerNat));
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}, "VpcNetwork");
|
package/dist/coverage.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCP lexicon coverage analysis.
|
|
3
|
+
*/
|
|
4
|
+
import { computeCoverage, formatSummary, formatVerbose, checkThresholds, } from "@intentius/chant/codegen/coverage";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
export { computeCoverage, overallPct, formatSummary, formatVerbose, checkThresholds, } from "@intentius/chant/codegen/coverage";
|
|
9
|
+
export async function analyzeGcpCoverage(opts) {
|
|
10
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
11
|
+
const lexiconPath = join(pkgDir, "src", "generated", "lexicon-gcp.json");
|
|
12
|
+
const content = readFileSync(lexiconPath, "utf-8");
|
|
13
|
+
const report = computeCoverage(content);
|
|
14
|
+
if (opts?.verbose) {
|
|
15
|
+
console.log(formatVerbose(report));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.log(formatSummary(report));
|
|
19
|
+
}
|
|
20
|
+
if (typeof opts?.minOverall === "number") {
|
|
21
|
+
const result = checkThresholds(report, { minOverallPct: opts.minOverall });
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
for (const v of result.violations)
|
|
24
|
+
console.error(` FAIL: ${v}`);
|
|
25
|
+
throw new Error("Coverage below threshold");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|