@intentius/chant 0.52.2 → 0.53.0
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/cdk/advise.d.ts +29 -0
- package/dist/cdk/advise.d.ts.map +1 -0
- package/dist/cdk/assembly.d.ts +38 -0
- package/dist/cdk/assembly.d.ts.map +1 -0
- package/dist/cdk/graph.d.ts +68 -0
- package/dist/cdk/graph.d.ts.map +1 -0
- package/dist/cdk/tier-map.d.ts +44 -0
- package/dist/cdk/tier-map.d.ts.map +1 -0
- package/dist/cdk/types.d.ts +114 -0
- package/dist/cdk/types.d.ts.map +1 -0
- package/dist/cli/commands/carve-apply.d.ts.map +1 -1
- package/dist/cli/commands/carve-bridge.d.ts.map +1 -1
- package/dist/cli/commands/carve-emit.d.ts.map +1 -1
- package/dist/cli/commands/carve.d.ts +48 -6
- package/dist/cli/commands/carve.d.ts.map +1 -1
- package/dist/cli/handlers/carve-emit.d.ts.map +1 -1
- package/dist/cli/handlers/carve.d.ts +5 -4
- package/dist/cli/handlers/carve.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +10 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/identity.d.ts +196 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +41 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/terraform/adopt-state.d.ts +17 -66
- package/dist/terraform/adopt-state.d.ts.map +1 -1
- package/dist/terraform/aws-resources.d.ts +1 -1
- package/dist/terraform/carve-provider.d.ts +142 -0
- package/dist/terraform/carve-provider.d.ts.map +1 -0
- package/dist/terraform/carve.d.ts +36 -3
- package/dist/terraform/carve.d.ts.map +1 -1
- package/dist/terraform/emit-source.d.ts +25 -0
- package/dist/terraform/emit-source.d.ts.map +1 -0
- package/dist/terraform/graduate.d.ts +11 -1
- package/dist/terraform/graduate.d.ts.map +1 -1
- package/dist/terraform/providers/aws.d.ts +19 -0
- package/dist/terraform/providers/aws.d.ts.map +1 -0
- package/dist/terraform/providers/gcp.d.ts +41 -0
- package/dist/terraform/providers/gcp.d.ts.map +1 -0
- package/dist/terraform/providers/index.d.ts +15 -0
- package/dist/terraform/providers/index.d.ts.map +1 -0
- package/dist/terraform/providers/kubernetes.d.ts +29 -0
- package/dist/terraform/providers/kubernetes.d.ts.map +1 -0
- package/dist/terraform/score.d.ts +70 -4
- package/dist/terraform/score.d.ts.map +1 -1
- package/dist/terraform/tier-map.d.ts +30 -35
- package/dist/terraform/tier-map.d.ts.map +1 -1
- package/dist/terraform/types.d.ts +6 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cdk/__fixtures__/cdk.out/AppStack.template.json +171 -0
- package/src/cdk/__fixtures__/cdk.out/DataStack.template.json +90 -0
- package/src/cdk/__fixtures__/cdk.out/cdk.out +1 -0
- package/src/cdk/__fixtures__/cdk.out/manifest.json +30 -0
- package/src/cdk/__fixtures__/cdk.out/tree.json +201 -0
- package/src/cdk/__fixtures__/cdk.out-dummy/LookupStack.template.json +29 -0
- package/src/cdk/__fixtures__/cdk.out-dummy/manifest.json +26 -0
- package/src/cdk/advise.test.ts +208 -0
- package/src/cdk/advise.ts +44 -0
- package/src/cdk/assembly.ts +133 -0
- package/src/cdk/graph.test.ts +206 -0
- package/src/cdk/graph.ts +525 -0
- package/src/cdk/tier-map.ts +71 -0
- package/src/cdk/types.ts +115 -0
- package/src/cli/commands/carve-apply.ts +20 -4
- package/src/cli/commands/carve-bridge.ts +9 -5
- package/src/cli/commands/carve-emit-k8s.test.ts +262 -0
- package/src/cli/commands/carve-emit-provider.test.ts +207 -0
- package/src/cli/commands/carve-emit.ts +28 -24
- package/src/cli/commands/carve.ts +139 -36
- package/src/cli/handlers/carve-emit.ts +8 -3
- package/src/cli/handlers/carve.ts +8 -7
- package/src/cli/handlers/lifecycle.test.ts +187 -1
- package/src/cli/handlers/lifecycle.ts +125 -1
- package/src/cli/main.ts +12 -5
- package/src/identity.test.ts +199 -0
- package/src/identity.ts +346 -0
- package/src/index.ts +1 -0
- package/src/lexicon.ts +53 -0
- package/src/terraform/__fixtures__/gcp-estate/main.tf +60 -0
- package/src/terraform/adopt-state.test.ts +131 -0
- package/src/terraform/adopt-state.ts +19 -167
- package/src/terraform/aws-resources.test.ts +38 -22
- package/src/terraform/aws-resources.ts +1 -1
- package/src/terraform/carve-provider.test.ts +155 -0
- package/src/terraform/carve-provider.ts +237 -0
- package/src/terraform/carve.test.ts +55 -1
- package/src/terraform/carve.ts +0 -0
- package/src/terraform/emit-source.ts +39 -0
- package/src/terraform/graduate.test.ts +37 -0
- package/src/terraform/graduate.ts +55 -7
- package/src/terraform/graph.ts +3 -3
- package/src/terraform/providers/aws.ts +169 -0
- package/src/terraform/providers/gcp.test.ts +228 -0
- package/src/terraform/providers/gcp.ts +329 -0
- package/src/terraform/providers/index.ts +21 -0
- package/src/terraform/providers/kubernetes.ts +224 -0
- package/src/terraform/score.ts +111 -25
- package/src/terraform/tier-map.ts +45 -107
- package/src/terraform/types.ts +6 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GCP carve provider (#2017) — advise-only.
|
|
3
|
+
*
|
|
4
|
+
* `mapsTo` names a Config Connector type as the gcp lexicon spells it
|
|
5
|
+
* (`GCP::Storage::Bucket`), not a Terraform type and not a raw CNRM kind. Every
|
|
6
|
+
* value here is checked against `lexicons/gcp/dist/meta.json` by
|
|
7
|
+
* `gcp.test.ts`, so an entry pointing at a type the lexicon does not define
|
|
8
|
+
* fails the suite rather than shipping a report that promises a resource chant
|
|
9
|
+
* cannot build.
|
|
10
|
+
*
|
|
11
|
+
* The google Terraform provider and Config Connector wrap the same GCP API, so
|
|
12
|
+
* resource granularity matches almost everywhere. That is why `foldsInto` is
|
|
13
|
+
* empty: unlike S3, where Terraform splits versioning, ACLs and encryption out
|
|
14
|
+
* of the bucket CloudFormation keeps together, GCP has no family where several
|
|
15
|
+
* `google_*` resources collapse into one CNRM resource.
|
|
16
|
+
*
|
|
17
|
+
* Tiers follow the reshaping the constructor demands, not the API's size:
|
|
18
|
+
*
|
|
19
|
+
* 1 — flat rename, `google_pubsub_topic` to `GCP::Pubsub::Topic`.
|
|
20
|
+
* 2 — a cross-resource reference becomes a `*Ref` object, or a Terraform
|
|
21
|
+
* block list becomes a CNRM object. Most of the table.
|
|
22
|
+
* 3 — the native shape is a re-modelling. `google_container_cluster` inlines
|
|
23
|
+
* node pools Terraform also declares separately, `google_sql_database_
|
|
24
|
+
* instance` carries everything under `settings`, and the IAM membership
|
|
25
|
+
* resources map onto one generic `GCP::Iam::PolicyMember` per binding.
|
|
26
|
+
*
|
|
27
|
+
* No `emitTypes` and no `adopt`, so `carve emit` refuses these types on both
|
|
28
|
+
* paths with the same message (#2015). Emit is a larger piece of work than the
|
|
29
|
+
* per-attribute rename the tier map implies: CNRM addresses a sibling through
|
|
30
|
+
* a `*Ref` object rather than the id or self-link Terraform state holds, and
|
|
31
|
+
* the resource's own name lives in `metadata.name` with `resourceID` as the
|
|
32
|
+
* override, so a faithful adopter has to resolve both per type. Declaring emit
|
|
33
|
+
* before that exists would put back the advise/emit cliff #2015 closed.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { CarveProvider, TierInfo } from "../carve-provider";
|
|
37
|
+
|
|
38
|
+
interface Row extends TierInfo {
|
|
39
|
+
/** The HCL attribute carrying the physical name. Null when the type has none. */
|
|
40
|
+
identityAttr: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A table row. Most google resources carry their physical name in `name`, so
|
|
45
|
+
* that is the default and only the exceptions are spelled out.
|
|
46
|
+
*/
|
|
47
|
+
function t(tier: 1 | 2 | 3, mapsTo: string, identityAttr: string | null = "name"): Row {
|
|
48
|
+
return { tier, mapsTo, identityAttr };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const TABLE: Record<string, Row> = {
|
|
52
|
+
// ── Storage ──
|
|
53
|
+
google_storage_bucket: t(1, "GCP::Storage::Bucket"),
|
|
54
|
+
google_storage_bucket_access_control: t(2, "GCP::Storage::BucketAccessControl", null),
|
|
55
|
+
google_storage_default_object_access_control: t(2, "GCP::Storage::DefaultObjectAccessControl", null),
|
|
56
|
+
google_storage_notification: t(2, "GCP::Storage::Notification", null),
|
|
57
|
+
google_storage_hmac_key: t(1, "GCP::Storage::HMACKey", null),
|
|
58
|
+
google_storage_managed_folder: t(1, "GCP::Storage::ManagedFolder"),
|
|
59
|
+
google_storage_transfer_job: t(2, "GCP::Storagetransfer::Job", null),
|
|
60
|
+
google_filestore_instance: t(2, "GCP::Filestore::Instance"),
|
|
61
|
+
|
|
62
|
+
// ── Compute: instances, disks, images ──
|
|
63
|
+
google_compute_instance: t(3, "GCP::Compute::Instance"),
|
|
64
|
+
google_compute_instance_template: t(3, "GCP::Compute::InstanceTemplate"),
|
|
65
|
+
google_compute_instance_group: t(2, "GCP::Compute::InstanceGroup"),
|
|
66
|
+
google_compute_instance_group_manager: t(2, "GCP::Compute::InstanceGroupManager"),
|
|
67
|
+
google_compute_region_instance_group_manager: t(2, "GCP::Compute::InstanceGroupManager"),
|
|
68
|
+
google_compute_autoscaler: t(2, "GCP::Compute::Autoscaler"),
|
|
69
|
+
google_compute_region_autoscaler: t(2, "GCP::Compute::RegionAutoscaler"),
|
|
70
|
+
google_compute_disk: t(1, "GCP::Compute::Disk"),
|
|
71
|
+
google_compute_snapshot: t(2, "GCP::Compute::Snapshot"),
|
|
72
|
+
google_compute_image: t(2, "GCP::Compute::Image"),
|
|
73
|
+
google_compute_resource_policy: t(2, "GCP::Compute::ResourcePolicy"),
|
|
74
|
+
google_compute_project_metadata: t(2, "GCP::Compute::ProjectMetadata", null),
|
|
75
|
+
|
|
76
|
+
// ── Compute: networking ──
|
|
77
|
+
google_compute_network: t(1, "GCP::Compute::Network"),
|
|
78
|
+
google_compute_subnetwork: t(2, "GCP::Compute::Subnetwork"),
|
|
79
|
+
google_compute_firewall: t(2, "GCP::Compute::Firewall"),
|
|
80
|
+
google_compute_route: t(2, "GCP::Compute::Route"),
|
|
81
|
+
google_compute_router: t(2, "GCP::Compute::Router"),
|
|
82
|
+
google_compute_router_nat: t(2, "GCP::Compute::RouterNAT"),
|
|
83
|
+
google_compute_router_interface: t(2, "GCP::Compute::RouterInterface"),
|
|
84
|
+
google_compute_router_peer: t(2, "GCP::Compute::RouterPeer"),
|
|
85
|
+
google_compute_address: t(1, "GCP::Compute::Address"),
|
|
86
|
+
// Global and regional addresses are one CNRM type, split by `location`.
|
|
87
|
+
google_compute_global_address: t(2, "GCP::Compute::Address"),
|
|
88
|
+
google_compute_network_peering: t(2, "GCP::Compute::NetworkPeering"),
|
|
89
|
+
google_compute_shared_vpc_host_project: t(1, "GCP::Compute::SharedVPCHostProject", null),
|
|
90
|
+
google_compute_shared_vpc_service_project: t(1, "GCP::Compute::SharedVPCServiceProject", null),
|
|
91
|
+
google_compute_network_endpoint_group: t(2, "GCP::Compute::NetworkEndpointGroup"),
|
|
92
|
+
google_compute_region_network_endpoint_group: t(2, "GCP::Compute::RegionNetworkEndpointGroup"),
|
|
93
|
+
google_compute_service_attachment: t(2, "GCP::Compute::ServiceAttachment"),
|
|
94
|
+
// The classic VPN gateway is CNRM's TargetVPNGateway; the HA one is VPNGateway.
|
|
95
|
+
google_compute_vpn_gateway: t(2, "GCP::Compute::TargetVPNGateway"),
|
|
96
|
+
google_compute_ha_vpn_gateway: t(2, "GCP::Compute::VPNGateway"),
|
|
97
|
+
google_compute_external_vpn_gateway: t(2, "GCP::Compute::ExternalVPNGateway"),
|
|
98
|
+
google_compute_vpn_tunnel: t(2, "GCP::Compute::VPNTunnel"),
|
|
99
|
+
google_compute_interconnect_attachment: t(2, "GCP::Compute::InterconnectAttachment"),
|
|
100
|
+
|
|
101
|
+
// ── Compute: load balancing ──
|
|
102
|
+
google_compute_backend_service: t(2, "GCP::Compute::BackendService"),
|
|
103
|
+
google_compute_region_backend_service: t(2, "GCP::Compute::BackendService"),
|
|
104
|
+
google_compute_backend_bucket: t(2, "GCP::Compute::BackendBucket"),
|
|
105
|
+
google_compute_health_check: t(2, "GCP::Compute::HealthCheck"),
|
|
106
|
+
google_compute_region_health_check: t(2, "GCP::Compute::HealthCheck"),
|
|
107
|
+
google_compute_http_health_check: t(2, "GCP::Compute::HTTPHealthCheck"),
|
|
108
|
+
google_compute_https_health_check: t(2, "GCP::Compute::HTTPSHealthCheck"),
|
|
109
|
+
google_compute_url_map: t(2, "GCP::Compute::URLMap"),
|
|
110
|
+
google_compute_region_url_map: t(2, "GCP::Compute::URLMap"),
|
|
111
|
+
google_compute_target_http_proxy: t(2, "GCP::Compute::TargetHTTPProxy"),
|
|
112
|
+
google_compute_target_https_proxy: t(2, "GCP::Compute::TargetHTTPSProxy"),
|
|
113
|
+
google_compute_target_tcp_proxy: t(2, "GCP::Compute::TargetTCPProxy"),
|
|
114
|
+
google_compute_target_ssl_proxy: t(2, "GCP::Compute::TargetSSLProxy"),
|
|
115
|
+
google_compute_target_grpc_proxy: t(2, "GCP::Compute::TargetGRPCProxy"),
|
|
116
|
+
google_compute_target_pool: t(2, "GCP::Compute::TargetPool"),
|
|
117
|
+
google_compute_target_instance: t(2, "GCP::Compute::TargetInstance"),
|
|
118
|
+
google_compute_forwarding_rule: t(2, "GCP::Compute::ForwardingRule"),
|
|
119
|
+
google_compute_global_forwarding_rule: t(2, "GCP::Compute::ForwardingRule"),
|
|
120
|
+
google_compute_ssl_certificate: t(2, "GCP::Compute::SSLCertificate"),
|
|
121
|
+
google_compute_managed_ssl_certificate: t(2, "GCP::Compute::ManagedSSLCertificate"),
|
|
122
|
+
google_compute_ssl_policy: t(2, "GCP::Compute::SSLPolicy"),
|
|
123
|
+
google_compute_region_ssl_policy: t(2, "GCP::Compute::RegionSSLPolicy"),
|
|
124
|
+
google_compute_security_policy: t(2, "GCP::Compute::SecurityPolicy"),
|
|
125
|
+
|
|
126
|
+
// ── GKE and fleet ──
|
|
127
|
+
google_container_cluster: t(3, "GCP::Container::Cluster"),
|
|
128
|
+
google_container_node_pool: t(2, "GCP::Container::NodePool"),
|
|
129
|
+
google_container_attached_cluster: t(3, "GCP::Containerattached::Cluster"),
|
|
130
|
+
google_gke_hub_membership: t(2, "GCP::Gkehub::Membership", "membership_id"),
|
|
131
|
+
google_gke_hub_feature: t(2, "GCP::Gkehub::Feature"),
|
|
132
|
+
google_gke_hub_feature_membership: t(3, "GCP::Gkehub::FeatureMembership", null),
|
|
133
|
+
google_gke_backup_backup_plan: t(2, "GCP::Gkebackup::BackupPlan"),
|
|
134
|
+
google_gke_backup_restore_plan: t(2, "GCP::Gkebackup::RestorePlan"),
|
|
135
|
+
|
|
136
|
+
// ── IAM ──
|
|
137
|
+
google_service_account: t(1, "GCP::Iam::ServiceAccount", "account_id"),
|
|
138
|
+
google_service_account_key: t(2, "GCP::Iam::ServiceAccountKey", null),
|
|
139
|
+
google_project_iam_custom_role: t(2, "GCP::Iam::CustomRole", "role_id"),
|
|
140
|
+
google_organization_iam_custom_role: t(2, "GCP::Iam::CustomRole", "role_id"),
|
|
141
|
+
// A per-resource Terraform binding becomes one generic policy object holding
|
|
142
|
+
// a reference to the resource it grants on. Tier 3 for all of them.
|
|
143
|
+
google_project_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
144
|
+
google_project_iam_binding: t(3, "GCP::Iam::PartialPolicy", null),
|
|
145
|
+
google_project_iam_policy: t(3, "GCP::Iam::Policy", null),
|
|
146
|
+
google_project_iam_audit_config: t(3, "GCP::Iam::AuditConfig", null),
|
|
147
|
+
google_folder_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
148
|
+
google_organization_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
149
|
+
google_storage_bucket_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
150
|
+
google_service_account_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
151
|
+
google_pubsub_topic_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
152
|
+
google_kms_crypto_key_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
153
|
+
google_secret_manager_secret_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
154
|
+
google_bigquery_dataset_iam_member: t(3, "GCP::Iam::PolicyMember", null),
|
|
155
|
+
google_iam_workload_identity_pool: t(1, "GCP::Iam::WorkloadIdentityPool", "workload_identity_pool_id"),
|
|
156
|
+
google_iam_workload_identity_pool_provider: t(
|
|
157
|
+
2,
|
|
158
|
+
"GCP::Iam::WorkloadIdentityPoolProvider",
|
|
159
|
+
"workload_identity_pool_provider_id",
|
|
160
|
+
),
|
|
161
|
+
google_iam_workforce_pool: t(2, "GCP::Iam::WorkforcePool", "workforce_pool_id"),
|
|
162
|
+
google_iam_workforce_pool_provider: t(2, "GCP::Iam::WorkforcePoolProvider", "provider_id"),
|
|
163
|
+
google_iam_deny_policy: t(2, "GCP::Iam::DenyPolicy"),
|
|
164
|
+
google_iam_access_boundary_policy: t(2, "GCP::Iam::AccessBoundaryPolicy"),
|
|
165
|
+
|
|
166
|
+
// ── Keys and secrets ──
|
|
167
|
+
google_kms_key_ring: t(1, "GCP::Kms::KeyRing"),
|
|
168
|
+
google_kms_crypto_key: t(2, "GCP::Kms::CryptoKey"),
|
|
169
|
+
google_kms_crypto_key_version: t(2, "GCP::Kms::CryptoKeyVersion", null),
|
|
170
|
+
google_kms_secret_ciphertext: t(2, "GCP::Kms::SecretCiphertext", null),
|
|
171
|
+
google_secret_manager_secret: t(2, "GCP::Secretmanager::Secret", "secret_id"),
|
|
172
|
+
google_secret_manager_secret_version: t(2, "GCP::Secretmanager::SecretVersion", null),
|
|
173
|
+
|
|
174
|
+
// ── Databases and analytics ──
|
|
175
|
+
google_sql_database_instance: t(3, "GCP::Sql::Instance"),
|
|
176
|
+
google_sql_database: t(2, "GCP::Sql::Database"),
|
|
177
|
+
google_sql_user: t(2, "GCP::Sql::User"),
|
|
178
|
+
google_sql_ssl_cert: t(2, "GCP::Sql::SSLCert", null),
|
|
179
|
+
google_bigquery_dataset: t(2, "GCP::Bigquery::Dataset", "dataset_id"),
|
|
180
|
+
google_bigquery_table: t(2, "GCP::Bigquery::Table", "table_id"),
|
|
181
|
+
google_bigquery_job: t(2, "GCP::Bigquery::Job", "job_id"),
|
|
182
|
+
google_bigquery_routine: t(2, "GCP::Bigquery::Routine", "routine_id"),
|
|
183
|
+
google_bigquery_connection: t(2, "GCP::Bigqueryconnection::Connection", "connection_id"),
|
|
184
|
+
google_bigtable_instance: t(2, "GCP::Bigtable::Instance"),
|
|
185
|
+
google_bigtable_table: t(2, "GCP::Bigtable::Table"),
|
|
186
|
+
google_bigtable_app_profile: t(2, "GCP::Bigtable::AppProfile", "app_profile_id"),
|
|
187
|
+
google_bigtable_gc_policy: t(2, "GCP::Bigtable::GCPolicy", null),
|
|
188
|
+
google_spanner_instance: t(2, "GCP::Spanner::Instance"),
|
|
189
|
+
google_spanner_database: t(2, "GCP::Spanner::Database"),
|
|
190
|
+
google_firestore_database: t(2, "GCP::Firestore::Database"),
|
|
191
|
+
google_firestore_index: t(2, "GCP::Firestore::Index", null),
|
|
192
|
+
google_datastore_index: t(2, "GCP::Datastore::Index", null),
|
|
193
|
+
google_redis_instance: t(2, "GCP::Redis::Instance"),
|
|
194
|
+
google_redis_cluster: t(2, "GCP::Redis::Cluster"),
|
|
195
|
+
google_memcache_instance: t(2, "GCP::Memcache::Instance"),
|
|
196
|
+
google_alloydb_cluster: t(3, "GCP::Alloydb::Cluster", "cluster_id"),
|
|
197
|
+
google_alloydb_instance: t(2, "GCP::Alloydb::Instance", "instance_id"),
|
|
198
|
+
google_dataproc_cluster: t(3, "GCP::Dataproc::Cluster"),
|
|
199
|
+
google_dataproc_job: t(2, "GCP::Dataproc::Job", null),
|
|
200
|
+
google_dataproc_autoscaling_policy: t(2, "GCP::Dataproc::AutoscalingPolicy", "policy_id"),
|
|
201
|
+
google_dataflow_job: t(2, "GCP::Dataflow::Job"),
|
|
202
|
+
google_dataflow_flex_template_job: t(2, "GCP::Dataflow::FlexTemplateJob"),
|
|
203
|
+
google_composer_environment: t(3, "GCP::Composer::Environment"),
|
|
204
|
+
google_data_fusion_instance: t(2, "GCP::Datafusion::Instance"),
|
|
205
|
+
|
|
206
|
+
// ── Messaging and serverless ──
|
|
207
|
+
google_pubsub_topic: t(1, "GCP::Pubsub::Topic"),
|
|
208
|
+
google_pubsub_subscription: t(2, "GCP::Pubsub::Subscription"),
|
|
209
|
+
google_pubsub_schema: t(1, "GCP::Pubsub::Schema"),
|
|
210
|
+
google_cloud_tasks_queue: t(2, "GCP::Cloudtasks::TasksQueue"),
|
|
211
|
+
google_cloud_scheduler_job: t(2, "GCP::Cloudscheduler::Job"),
|
|
212
|
+
google_eventarc_trigger: t(2, "GCP::Eventarc::Trigger"),
|
|
213
|
+
google_eventarc_channel: t(2, "GCP::Eventarc::Channel"),
|
|
214
|
+
google_cloud_run_service: t(3, "GCP::Run::Service"),
|
|
215
|
+
google_cloud_run_v2_service: t(3, "GCP::Run::Service"),
|
|
216
|
+
google_cloud_run_v2_job: t(3, "GCP::Run::Job"),
|
|
217
|
+
google_cloudfunctions_function: t(2, "GCP::Cloudfunctions::Function"),
|
|
218
|
+
google_cloudfunctions2_function: t(3, "GCP::Cloudfunctions2::Function"),
|
|
219
|
+
google_workflows_workflow: t(2, "GCP::Workflows::Workflow"),
|
|
220
|
+
google_vpc_access_connector: t(2, "GCP::Vpcaccess::Connector"),
|
|
221
|
+
|
|
222
|
+
// ── DNS ──
|
|
223
|
+
google_dns_managed_zone: t(2, "GCP::Dns::ManagedZone"),
|
|
224
|
+
google_dns_record_set: t(2, "GCP::Dns::RecordSet"),
|
|
225
|
+
google_dns_policy: t(2, "GCP::Dns::Policy"),
|
|
226
|
+
google_dns_response_policy: t(2, "GCP::Dns::ResponsePolicy", "response_policy_name"),
|
|
227
|
+
google_dns_response_policy_rule: t(2, "GCP::Dns::ResponsePolicyRule", "rule_name"),
|
|
228
|
+
|
|
229
|
+
// ── Logging and monitoring ──
|
|
230
|
+
google_logging_project_sink: t(2, "GCP::Logging::LogSink"),
|
|
231
|
+
google_logging_folder_sink: t(2, "GCP::Logging::LogSink"),
|
|
232
|
+
google_logging_organization_sink: t(2, "GCP::Logging::LogSink"),
|
|
233
|
+
google_logging_billing_account_sink: t(2, "GCP::Logging::LogSink"),
|
|
234
|
+
google_logging_metric: t(2, "GCP::Logging::LogMetric"),
|
|
235
|
+
google_logging_project_exclusion: t(2, "GCP::Logging::LogExclusion"),
|
|
236
|
+
google_logging_project_bucket_config: t(2, "GCP::Logging::LogBucket", "bucket_id"),
|
|
237
|
+
google_logging_log_view: t(2, "GCP::Logging::LogView"),
|
|
238
|
+
google_monitoring_alert_policy: t(2, "GCP::Monitoring::AlertPolicy", "display_name"),
|
|
239
|
+
google_monitoring_notification_channel: t(2, "GCP::Monitoring::NotificationChannel", "display_name"),
|
|
240
|
+
google_monitoring_dashboard: t(2, "GCP::Monitoring::Dashboard", null),
|
|
241
|
+
google_monitoring_uptime_check_config: t(2, "GCP::Monitoring::UptimeCheckConfig", "display_name"),
|
|
242
|
+
google_monitoring_group: t(2, "GCP::Monitoring::Group", "display_name"),
|
|
243
|
+
google_monitoring_custom_service: t(2, "GCP::Monitoring::Service", "display_name"),
|
|
244
|
+
google_monitoring_slo: t(2, "GCP::Monitoring::ServiceLevelObjective", null),
|
|
245
|
+
google_monitoring_monitored_project: t(1, "GCP::Monitoring::MonitoredProject", null),
|
|
246
|
+
|
|
247
|
+
// ── Build, registry, deploy ──
|
|
248
|
+
google_artifact_registry_repository: t(2, "GCP::Artifactregistry::Repository", "repository_id"),
|
|
249
|
+
google_cloudbuild_trigger: t(3, "GCP::Cloudbuild::Trigger"),
|
|
250
|
+
google_cloudbuild_worker_pool: t(2, "GCP::Cloudbuild::WorkerPool"),
|
|
251
|
+
google_sourcerepo_repository: t(1, "GCP::Sourcerepo::Repository"),
|
|
252
|
+
google_clouddeploy_target: t(2, "GCP::Clouddeploy::Target"),
|
|
253
|
+
google_clouddeploy_delivery_pipeline: t(2, "GCP::Clouddeploy::DeliveryPipeline"),
|
|
254
|
+
google_clouddeploy_automation: t(2, "GCP::Clouddeploy::Automation"),
|
|
255
|
+
|
|
256
|
+
// ── Project, folder, org ──
|
|
257
|
+
google_project: t(2, "GCP::Resourcemanager::Project", "project_id"),
|
|
258
|
+
google_folder: t(2, "GCP::Resourcemanager::Folder", "display_name"),
|
|
259
|
+
google_project_service: t(2, "GCP::Serviceusage::Service", "service"),
|
|
260
|
+
google_service_networking_connection: t(2, "GCP::Servicenetworking::Connection", null),
|
|
261
|
+
google_tags_tag_key: t(1, "GCP::Tags::TagKey", "short_name"),
|
|
262
|
+
google_tags_tag_value: t(1, "GCP::Tags::TagValue", "short_name"),
|
|
263
|
+
google_tags_tag_binding: t(2, "GCP::Tags::TagBinding", null),
|
|
264
|
+
google_tags_location_tag_binding: t(2, "GCP::Tags::LocationTagBinding", null),
|
|
265
|
+
google_essential_contacts_contact: t(1, "GCP::Essentialcontacts::Contact", "email"),
|
|
266
|
+
google_org_policy_policy: t(2, "GCP::Orgpolicy::Policy"),
|
|
267
|
+
google_org_policy_custom_constraint: t(2, "GCP::Orgpolicy::CustomConstraint"),
|
|
268
|
+
google_billing_budget: t(2, "GCP::Billingbudgets::Budget", "display_name"),
|
|
269
|
+
google_resource_manager_lien: t(2, "GCP::Resourcemanager::Lien", null),
|
|
270
|
+
google_apikeys_key: t(2, "GCP::Apikeys::Key"),
|
|
271
|
+
|
|
272
|
+
// ── Security ──
|
|
273
|
+
google_binary_authorization_policy: t(3, "GCP::Binaryauthorization::Policy", null),
|
|
274
|
+
google_binary_authorization_attestor: t(2, "GCP::Binaryauthorization::Attestor"),
|
|
275
|
+
google_privateca_ca_pool: t(2, "GCP::Privateca::CAPool"),
|
|
276
|
+
google_privateca_certificate_authority: t(3, "GCP::Privateca::CertificateAuthority", "certificate_authority_id"),
|
|
277
|
+
google_privateca_certificate: t(2, "GCP::Privateca::Certificate"),
|
|
278
|
+
google_privateca_certificate_template: t(2, "GCP::Privateca::CertificateTemplate"),
|
|
279
|
+
google_certificate_manager_certificate: t(2, "GCP::Certificatemanager::Certificate"),
|
|
280
|
+
google_certificate_manager_certificate_map: t(2, "GCP::Certificatemanager::CertificateMap"),
|
|
281
|
+
google_certificate_manager_certificate_map_entry: t(2, "GCP::Certificatemanager::CertificateMapEntry"),
|
|
282
|
+
google_certificate_manager_dns_authorization: t(2, "GCP::Certificatemanager::DNSAuthorization"),
|
|
283
|
+
google_network_security_authorization_policy: t(2, "GCP::Networksecurity::AuthorizationPolicy"),
|
|
284
|
+
google_network_security_client_tls_policy: t(2, "GCP::Networksecurity::ClientTLSPolicy"),
|
|
285
|
+
google_network_security_server_tls_policy: t(2, "GCP::Networksecurity::ServerTLSPolicy"),
|
|
286
|
+
|
|
287
|
+
// ── Network services and service directory ──
|
|
288
|
+
google_network_services_gateway: t(2, "GCP::Networkservices::Gateway"),
|
|
289
|
+
google_network_services_mesh: t(2, "GCP::Networkservices::Mesh"),
|
|
290
|
+
google_network_services_http_route: t(2, "GCP::Networkservices::HTTPRoute"),
|
|
291
|
+
google_network_services_grpc_route: t(2, "GCP::Networkservices::GRPCRoute"),
|
|
292
|
+
google_network_services_tcp_route: t(2, "GCP::Networkservices::TCPRoute"),
|
|
293
|
+
google_network_services_tls_route: t(2, "GCP::Networkservices::TLSRoute"),
|
|
294
|
+
google_network_services_endpoint_policy: t(2, "GCP::Networkservices::EndpointPolicy"),
|
|
295
|
+
google_network_connectivity_hub: t(2, "GCP::Networkconnectivity::Hub"),
|
|
296
|
+
google_network_connectivity_spoke: t(2, "GCP::Networkconnectivity::Spoke"),
|
|
297
|
+
google_network_connectivity_internal_range: t(2, "GCP::Networkconnectivity::InternalRange"),
|
|
298
|
+
google_network_management_connectivity_test: t(2, "GCP::Networkmanagement::ConnectivityTest"),
|
|
299
|
+
google_service_directory_namespace: t(1, "GCP::Servicedirectory::Namespace", "namespace_id"),
|
|
300
|
+
google_service_directory_service: t(2, "GCP::Servicedirectory::Service", "service_id"),
|
|
301
|
+
google_service_directory_endpoint: t(2, "GCP::Servicedirectory::Endpoint", "endpoint_id"),
|
|
302
|
+
|
|
303
|
+
// ── API Gateway ──
|
|
304
|
+
google_api_gateway_api: t(2, "GCP::Apigateway::API", "api_id"),
|
|
305
|
+
google_api_gateway_api_config: t(3, "GCP::Apigateway::APIConfig", "api_config_id"),
|
|
306
|
+
google_api_gateway_gateway: t(2, "GCP::Apigateway::Gateway", "gateway_id"),
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/** Terraform type → tier, the shape the registry merges into the tier map. */
|
|
310
|
+
export const GCP_TIERS: Readonly<Record<string, TierInfo>> = Object.fromEntries(
|
|
311
|
+
Object.entries(TABLE).map(([tfType, row]) => [tfType, { tier: row.tier, mapsTo: row.mapsTo }]),
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
/** Terraform type → the HCL attribute carrying its physical name. */
|
|
315
|
+
export const GCP_IDENTITY_ATTRS: Readonly<Record<string, string>> = Object.fromEntries(
|
|
316
|
+
Object.entries(TABLE)
|
|
317
|
+
.filter(([, row]) => row.identityAttr !== null)
|
|
318
|
+
.map(([tfType, row]) => [tfType, row.identityAttr!]),
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
export const gcpCarveProvider: CarveProvider = {
|
|
322
|
+
name: "gcp",
|
|
323
|
+
tfTypePrefixes: ["google_"],
|
|
324
|
+
lexicon: "gcp",
|
|
325
|
+
tiers: GCP_TIERS,
|
|
326
|
+
identityAttrs: GCP_IDENTITY_ATTRS,
|
|
327
|
+
// No family of google resources collapses into one CNRM resource, so nothing
|
|
328
|
+
// folds. See the module comment.
|
|
329
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The carve providers core ships with (#2016). Adding a provider is a file
|
|
3
|
+
* here plus a line in this list — no edit to `adopt-state.ts` or
|
|
4
|
+
* `carve-emit.ts`, which resolve everything through the registry.
|
|
5
|
+
*
|
|
6
|
+
* This module holds no runtime dependency on `../carve-provider`, so the
|
|
7
|
+
* registry can import the list without an import cycle.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { awsCarveProvider } from "./aws";
|
|
11
|
+
import { gcpCarveProvider } from "./gcp";
|
|
12
|
+
import { kubernetesCarveProvider } from "./kubernetes";
|
|
13
|
+
import type { CarveProvider } from "../carve-provider";
|
|
14
|
+
|
|
15
|
+
export const BUILTIN_CARVE_PROVIDERS: readonly CarveProvider[] = [
|
|
16
|
+
awsCarveProvider,
|
|
17
|
+
gcpCarveProvider,
|
|
18
|
+
kubernetesCarveProvider,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export { awsCarveProvider, gcpCarveProvider, kubernetesCarveProvider };
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Kubernetes carve provider (#2016 seam, #999 emit).
|
|
3
|
+
*
|
|
4
|
+
* `kubernetes_manifest` IS the manifest (tier 1) and is the type emit adopts.
|
|
5
|
+
* The typed provider resources (`kubernetes_deployment`, ...) reshape their HCL
|
|
6
|
+
* schema back into a manifest, so they rank tier 2; the common `_v1` aliases
|
|
7
|
+
* share their base type's entry. They are advise-only: recovering a manifest
|
|
8
|
+
* from the provider's own schema is a per-type reshaping (`container` blocks to
|
|
9
|
+
* `containers`, `metadata` blocks unwrapped from their single-element state
|
|
10
|
+
* arrays), not something the generic path below can do honestly, so emit
|
|
11
|
+
* refuses them on both paths with the same message (#2015).
|
|
12
|
+
*
|
|
13
|
+
* ── Two things `kubernetes_manifest` does not share with an AWS type ──
|
|
14
|
+
*
|
|
15
|
+
* It has no fixed entity type. The target kind lives INSIDE the resource: the
|
|
16
|
+
* `manifest` attribute is the body, and its `apiVersion`/`kind` are what say
|
|
17
|
+
* whether this block is a ConfigMap or a cert-manager Certificate. So `mapsTo`
|
|
18
|
+
* can only name the family, and `adopt` reads the body to learn the rest —
|
|
19
|
+
* which is also why one rule here covers every CRD.
|
|
20
|
+
*
|
|
21
|
+
* It has no live adoption path. `carve emit --env` filters a live export by a
|
|
22
|
+
* native type, and there is no type to filter by until the body is read; state
|
|
23
|
+
* is where the body is. `liveSelectorType` therefore returns undefined and
|
|
24
|
+
* `--env` refuses with "adopts it from state only", rather than importing
|
|
25
|
+
* something else.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { AdoptedSource, CarveProvider, DeferredParam, TierInfo } from "../carve-provider";
|
|
29
|
+
import type { StateResource } from "../state";
|
|
30
|
+
|
|
31
|
+
/** The chant lexicon the emitted source imports from. */
|
|
32
|
+
const K8S_LEXICON_IMPORT = "@intentius/chant-lexicon-k8s";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The chant entity types the ranked provider resources correspond to — real
|
|
36
|
+
* three-part `K8s::<Group>::<Kind>` names from the generated operation surface
|
|
37
|
+
* (`lexicons/k8s/src/generated/operations.json`). The earlier `k8s:Namespace`
|
|
38
|
+
* spelling was in no table: `operationFor` missed it and `deriveOperation`
|
|
39
|
+
* rejected it for not having three parts, so anything routed by these strings
|
|
40
|
+
* addressed nothing.
|
|
41
|
+
*/
|
|
42
|
+
const BASE_TIERS: Record<string, TierInfo> = {
|
|
43
|
+
// No fixed type: the kind comes from the manifest body, per resource.
|
|
44
|
+
kubernetes_manifest: { tier: 1, mapsTo: "K8s::*" },
|
|
45
|
+
kubernetes_namespace: { tier: 2, mapsTo: "K8s::Core::Namespace" },
|
|
46
|
+
kubernetes_config_map: { tier: 2, mapsTo: "K8s::Core::ConfigMap" },
|
|
47
|
+
kubernetes_secret: { tier: 2, mapsTo: "K8s::Core::Secret" },
|
|
48
|
+
kubernetes_service: { tier: 2, mapsTo: "K8s::Core::Service" },
|
|
49
|
+
kubernetes_service_account: { tier: 2, mapsTo: "K8s::Core::ServiceAccount" },
|
|
50
|
+
kubernetes_deployment: { tier: 2, mapsTo: "K8s::Apps::Deployment" },
|
|
51
|
+
kubernetes_stateful_set: { tier: 2, mapsTo: "K8s::Apps::StatefulSet" },
|
|
52
|
+
kubernetes_daemon_set: { tier: 2, mapsTo: "K8s::Apps::DaemonSet" },
|
|
53
|
+
kubernetes_job: { tier: 2, mapsTo: "K8s::Batch::Job" },
|
|
54
|
+
kubernetes_cron_job: { tier: 2, mapsTo: "K8s::Batch::CronJob" },
|
|
55
|
+
kubernetes_ingress: { tier: 2, mapsTo: "K8s::Networking::Ingress" },
|
|
56
|
+
kubernetes_network_policy: { tier: 2, mapsTo: "K8s::Networking::NetworkPolicy" },
|
|
57
|
+
kubernetes_persistent_volume_claim: { tier: 2, mapsTo: "K8s::Core::PersistentVolumeClaim" },
|
|
58
|
+
kubernetes_role: { tier: 2, mapsTo: "K8s::Rbac::Role" },
|
|
59
|
+
kubernetes_role_binding: { tier: 2, mapsTo: "K8s::Rbac::RoleBinding" },
|
|
60
|
+
kubernetes_cluster_role: { tier: 2, mapsTo: "K8s::Rbac::ClusterRole" },
|
|
61
|
+
kubernetes_cluster_role_binding: { tier: 2, mapsTo: "K8s::Rbac::ClusterRoleBinding" },
|
|
62
|
+
kubernetes_resource_quota: { tier: 2, mapsTo: "K8s::Core::ResourceQuota" },
|
|
63
|
+
kubernetes_limit_range: { tier: 2, mapsTo: "K8s::Core::LimitRange" },
|
|
64
|
+
kubernetes_priority_class: { tier: 2, mapsTo: "K8s::Scheduling::PriorityClass" },
|
|
65
|
+
kubernetes_pod_disruption_budget: { tier: 2, mapsTo: "K8s::Policy::PodDisruptionBudget" },
|
|
66
|
+
kubernetes_horizontal_pod_autoscaler: { tier: 2, mapsTo: "K8s::Autoscaling::HorizontalPodAutoscaler" },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const TIERS: Record<string, TierInfo> = Object.fromEntries(
|
|
70
|
+
Object.entries(BASE_TIERS).flatMap(([type, info]) =>
|
|
71
|
+
type === "kubernetes_manifest" ? [[type, info]] : [[type, info], [`${type}_v1`, info]],
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
// The one provider type whose current alias is _v2, not _v1.
|
|
75
|
+
TIERS.kubernetes_horizontal_pod_autoscaler_v2 = BASE_TIERS.kubernetes_horizontal_pod_autoscaler;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Metadata the API server owns. Present when the body was read back from the
|
|
79
|
+
* `object` attribute (the provider's computed view); carving them into source
|
|
80
|
+
* would declare a resourceVersion as if an author had chosen it.
|
|
81
|
+
*/
|
|
82
|
+
const SERVER_OWNED_METADATA = new Set([
|
|
83
|
+
"creationTimestamp",
|
|
84
|
+
"generation",
|
|
85
|
+
"managedFields",
|
|
86
|
+
"resourceVersion",
|
|
87
|
+
"selfLink",
|
|
88
|
+
"uid",
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
/** Attributes of the Terraform resource that configure the provider, not the object. */
|
|
92
|
+
const PROVIDER_BEHAVIOUR_ATTRS = ["wait", "wait_for", "field_manager", "computed_fields", "timeouts"];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The manifest body out of state. `manifest` is what the configuration
|
|
96
|
+
* declared; `object` is the provider's computed read-back of the live object,
|
|
97
|
+
* used only when `manifest` is absent, and cleaned of server-owned metadata.
|
|
98
|
+
*/
|
|
99
|
+
function manifestBody(attributes: Record<string, unknown>): Record<string, unknown> | null {
|
|
100
|
+
const raw = isObject(attributes.manifest) ? attributes.manifest : isObject(attributes.object) ? attributes.object : null;
|
|
101
|
+
if (!raw) return null;
|
|
102
|
+
if (typeof raw.apiVersion !== "string" || typeof raw.kind !== "string") return null;
|
|
103
|
+
|
|
104
|
+
const body: Record<string, unknown> = {};
|
|
105
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
106
|
+
if (key === "status") continue; // observed, never declared
|
|
107
|
+
if (key === "metadata" && isObject(value)) {
|
|
108
|
+
const metadata: Record<string, unknown> = {};
|
|
109
|
+
for (const [k, v] of Object.entries(value)) {
|
|
110
|
+
if (!SERVER_OWNED_METADATA.has(k)) metadata[k] = v;
|
|
111
|
+
}
|
|
112
|
+
body.metadata = metadata;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
body[key] = value;
|
|
116
|
+
}
|
|
117
|
+
// apiVersion and kind lead the emitted object, matching manifest convention.
|
|
118
|
+
const { apiVersion, kind, metadata, ...rest } = body;
|
|
119
|
+
return { apiVersion, kind, ...(metadata === undefined ? {} : { metadata }), ...rest };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
123
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** A Terraform logical name as a JS identifier — TF allows `-`, source does not. */
|
|
127
|
+
function identifier(name: string): string {
|
|
128
|
+
const cleaned = name.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
129
|
+
return /^[0-9]/.test(cleaned) ? `_${cleaned}` : cleaned;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** An object key: bare when it is an identifier, quoted otherwise (`app.kubernetes.io/name`). */
|
|
133
|
+
function renderKey(key: string): string {
|
|
134
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Render a manifest value as a TypeScript literal, nested objects and arrays included. */
|
|
138
|
+
function renderManifestValue(value: unknown, indent: number): string {
|
|
139
|
+
const pad = " ".repeat(indent);
|
|
140
|
+
const inner = " ".repeat(indent + 2);
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
if (value.length === 0) return "[]";
|
|
143
|
+
return `[\n${value.map((v) => `${inner}${renderManifestValue(v, indent + 2)},`).join("\n")}\n${pad}]`;
|
|
144
|
+
}
|
|
145
|
+
if (isObject(value)) {
|
|
146
|
+
const keys = Object.keys(value);
|
|
147
|
+
if (keys.length === 0) return "{}";
|
|
148
|
+
return `{\n${keys.map((k) => `${inner}${renderKey(k)}: ${renderManifestValue(value[k], indent + 2)},`).join("\n")}\n${pad}}`;
|
|
149
|
+
}
|
|
150
|
+
return JSON.stringify(value ?? null);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Adopt a `kubernetes_manifest` into chant source.
|
|
155
|
+
*
|
|
156
|
+
* The body is emitted verbatim through the lexicon's `k8sManifest` escape
|
|
157
|
+
* hatch rather than a typed constructor. A typed constructor cannot carry this:
|
|
158
|
+
* an arbitrary CRD has no generated class at all, and a generated class types
|
|
159
|
+
* its props as the kind's own schema, so it would reject the `apiVersion`/
|
|
160
|
+
* `kind` pair the state recorded. `k8sManifest` derives the entity type from
|
|
161
|
+
* that pair through the lexicon's one group→namespace rule, which is also why
|
|
162
|
+
* core does not resolve `K8s::<Group>::<Kind>` here — a second copy of that
|
|
163
|
+
* rule would drift from the overrides (`argoproj.io` to `Argo`) the lexicon
|
|
164
|
+
* keeps.
|
|
165
|
+
*
|
|
166
|
+
* Deferred outbound inputs (#998) are reported in a comment, not substituted.
|
|
167
|
+
* A `kubernetes_manifest` reads a survivor from inside the body, so the
|
|
168
|
+
* boundary report names the enclosing attribute (`manifest`) and the state
|
|
169
|
+
* resolves it to the whole object — there is no scalar to swap for a
|
|
170
|
+
* `params.<name>` reference. The emitted source keeps what the state resolved;
|
|
171
|
+
* the scaffold still declares the input as a build parameter.
|
|
172
|
+
*/
|
|
173
|
+
function adoptManifestFromState(resource: StateResource, params: DeferredParam[]): AdoptedSource | null {
|
|
174
|
+
const body = manifestBody(resource.attributes);
|
|
175
|
+
if (!body) return null;
|
|
176
|
+
const gvk = `${body.apiVersion} ${body.kind}`;
|
|
177
|
+
|
|
178
|
+
const L: string[] = [];
|
|
179
|
+
L.push(`// Adopted from Terraform state: ${resource.type}.${resource.name} -> ${gvk}`);
|
|
180
|
+
L.push(`// The Terraform type names no kind — apiVersion/kind come from the manifest`);
|
|
181
|
+
L.push(`// body in state, and the body is carried over verbatim.`);
|
|
182
|
+
const behaviour = PROVIDER_BEHAVIOUR_ATTRS.filter((a) => {
|
|
183
|
+
const v = resource.attributes[a];
|
|
184
|
+
return v !== undefined && v !== null && !(Array.isArray(v) && v.length === 0);
|
|
185
|
+
});
|
|
186
|
+
if (behaviour.length) {
|
|
187
|
+
L.push(`// Provider-behaviour attributes not part of the object: ${behaviour.join(", ")}.`);
|
|
188
|
+
}
|
|
189
|
+
if (params.length) {
|
|
190
|
+
for (const p of params) {
|
|
191
|
+
L.push(`// Deferred deploy-time input: ${p.tfAttr} read ${p.survivor}.${p.attrs.join("/")} — inlined as state resolved it,`);
|
|
192
|
+
L.push(`// and declared as build param "${p.name}" in chant.config.ts.`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
L.push(`import { k8sManifest } from "${K8S_LEXICON_IMPORT}";`);
|
|
196
|
+
L.push("");
|
|
197
|
+
L.push(`export const ${identifier(resource.name)} = k8sManifest(${renderManifestValue(body, 0)});`);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
fileName: `${identifier(resource.name)}.ts`,
|
|
201
|
+
content: L.join("\n") + "\n",
|
|
202
|
+
mapped: true,
|
|
203
|
+
nativeType: gvk,
|
|
204
|
+
// Nothing is parameterized: see the note above.
|
|
205
|
+
parameterized: [],
|
|
206
|
+
// The kubernetes provider splits no sub-resource out of a manifest.
|
|
207
|
+
folded: [],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const kubernetesCarveProvider: CarveProvider = {
|
|
212
|
+
name: "kubernetes",
|
|
213
|
+
tfTypePrefixes: ["kubernetes_"],
|
|
214
|
+
lexicon: "k8s",
|
|
215
|
+
tiers: TIERS,
|
|
216
|
+
// A dotted path into nested blocks: the graph walks it for identity, and
|
|
217
|
+
// `carve bridge` refuses the type because a data-source body cannot express it.
|
|
218
|
+
identityAttrs: { kubernetes_manifest: "manifest.metadata.name" },
|
|
219
|
+
emitTypes: ["kubernetes_manifest"],
|
|
220
|
+
adopt: (resource, params) => adoptManifestFromState(resource, params),
|
|
221
|
+
// State only — the kind is in the body, so a live export has nothing to
|
|
222
|
+
// filter on. Returning undefined is what makes `--env` say so.
|
|
223
|
+
liveSelectorType: () => undefined,
|
|
224
|
+
};
|