@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.
Files changed (103) hide show
  1. package/dist/cdk/advise.d.ts +29 -0
  2. package/dist/cdk/advise.d.ts.map +1 -0
  3. package/dist/cdk/assembly.d.ts +38 -0
  4. package/dist/cdk/assembly.d.ts.map +1 -0
  5. package/dist/cdk/graph.d.ts +68 -0
  6. package/dist/cdk/graph.d.ts.map +1 -0
  7. package/dist/cdk/tier-map.d.ts +44 -0
  8. package/dist/cdk/tier-map.d.ts.map +1 -0
  9. package/dist/cdk/types.d.ts +114 -0
  10. package/dist/cdk/types.d.ts.map +1 -0
  11. package/dist/cli/commands/carve-apply.d.ts.map +1 -1
  12. package/dist/cli/commands/carve-bridge.d.ts.map +1 -1
  13. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  14. package/dist/cli/commands/carve.d.ts +48 -6
  15. package/dist/cli/commands/carve.d.ts.map +1 -1
  16. package/dist/cli/handlers/carve-emit.d.ts.map +1 -1
  17. package/dist/cli/handlers/carve.d.ts +5 -4
  18. package/dist/cli/handlers/carve.d.ts.map +1 -1
  19. package/dist/cli/handlers/lifecycle.d.ts +10 -0
  20. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  21. package/dist/cli/main.d.ts.map +1 -1
  22. package/dist/identity.d.ts +196 -0
  23. package/dist/identity.d.ts.map +1 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/lexicon.d.ts +41 -0
  27. package/dist/lexicon.d.ts.map +1 -1
  28. package/dist/terraform/adopt-state.d.ts +17 -66
  29. package/dist/terraform/adopt-state.d.ts.map +1 -1
  30. package/dist/terraform/aws-resources.d.ts +1 -1
  31. package/dist/terraform/carve-provider.d.ts +142 -0
  32. package/dist/terraform/carve-provider.d.ts.map +1 -0
  33. package/dist/terraform/carve.d.ts +36 -3
  34. package/dist/terraform/carve.d.ts.map +1 -1
  35. package/dist/terraform/emit-source.d.ts +25 -0
  36. package/dist/terraform/emit-source.d.ts.map +1 -0
  37. package/dist/terraform/graduate.d.ts +11 -1
  38. package/dist/terraform/graduate.d.ts.map +1 -1
  39. package/dist/terraform/providers/aws.d.ts +19 -0
  40. package/dist/terraform/providers/aws.d.ts.map +1 -0
  41. package/dist/terraform/providers/gcp.d.ts +41 -0
  42. package/dist/terraform/providers/gcp.d.ts.map +1 -0
  43. package/dist/terraform/providers/index.d.ts +15 -0
  44. package/dist/terraform/providers/index.d.ts.map +1 -0
  45. package/dist/terraform/providers/kubernetes.d.ts +29 -0
  46. package/dist/terraform/providers/kubernetes.d.ts.map +1 -0
  47. package/dist/terraform/score.d.ts +70 -4
  48. package/dist/terraform/score.d.ts.map +1 -1
  49. package/dist/terraform/tier-map.d.ts +30 -35
  50. package/dist/terraform/tier-map.d.ts.map +1 -1
  51. package/dist/terraform/types.d.ts +6 -0
  52. package/dist/terraform/types.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/cdk/__fixtures__/cdk.out/AppStack.template.json +171 -0
  55. package/src/cdk/__fixtures__/cdk.out/DataStack.template.json +90 -0
  56. package/src/cdk/__fixtures__/cdk.out/cdk.out +1 -0
  57. package/src/cdk/__fixtures__/cdk.out/manifest.json +30 -0
  58. package/src/cdk/__fixtures__/cdk.out/tree.json +201 -0
  59. package/src/cdk/__fixtures__/cdk.out-dummy/LookupStack.template.json +29 -0
  60. package/src/cdk/__fixtures__/cdk.out-dummy/manifest.json +26 -0
  61. package/src/cdk/advise.test.ts +208 -0
  62. package/src/cdk/advise.ts +44 -0
  63. package/src/cdk/assembly.ts +133 -0
  64. package/src/cdk/graph.test.ts +206 -0
  65. package/src/cdk/graph.ts +525 -0
  66. package/src/cdk/tier-map.ts +71 -0
  67. package/src/cdk/types.ts +115 -0
  68. package/src/cli/commands/carve-apply.ts +20 -4
  69. package/src/cli/commands/carve-bridge.ts +9 -5
  70. package/src/cli/commands/carve-emit-k8s.test.ts +262 -0
  71. package/src/cli/commands/carve-emit-provider.test.ts +207 -0
  72. package/src/cli/commands/carve-emit.ts +28 -24
  73. package/src/cli/commands/carve.ts +139 -36
  74. package/src/cli/handlers/carve-emit.ts +8 -3
  75. package/src/cli/handlers/carve.ts +8 -7
  76. package/src/cli/handlers/lifecycle.test.ts +187 -1
  77. package/src/cli/handlers/lifecycle.ts +125 -1
  78. package/src/cli/main.ts +12 -5
  79. package/src/identity.test.ts +199 -0
  80. package/src/identity.ts +346 -0
  81. package/src/index.ts +1 -0
  82. package/src/lexicon.ts +53 -0
  83. package/src/terraform/__fixtures__/gcp-estate/main.tf +60 -0
  84. package/src/terraform/adopt-state.test.ts +131 -0
  85. package/src/terraform/adopt-state.ts +19 -167
  86. package/src/terraform/aws-resources.test.ts +38 -22
  87. package/src/terraform/aws-resources.ts +1 -1
  88. package/src/terraform/carve-provider.test.ts +155 -0
  89. package/src/terraform/carve-provider.ts +237 -0
  90. package/src/terraform/carve.test.ts +55 -1
  91. package/src/terraform/carve.ts +0 -0
  92. package/src/terraform/emit-source.ts +39 -0
  93. package/src/terraform/graduate.test.ts +37 -0
  94. package/src/terraform/graduate.ts +55 -7
  95. package/src/terraform/graph.ts +3 -3
  96. package/src/terraform/providers/aws.ts +169 -0
  97. package/src/terraform/providers/gcp.test.ts +228 -0
  98. package/src/terraform/providers/gcp.ts +329 -0
  99. package/src/terraform/providers/index.ts +21 -0
  100. package/src/terraform/providers/kubernetes.ts +224 -0
  101. package/src/terraform/score.ts +111 -25
  102. package/src/terraform/tier-map.ts +45 -107
  103. 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
+ };