Help us shape the future of Go! Take the 2026 Go Developer Survey

identity

package
v0.32.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: MPL-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package identity is choudoufu's per-type identity knowledge: for each Terraform resource type it covers, what names one instance of it - the configuration arguments an import ID is built from, the character that joins them, which parts the cloud assigns, and which parts are the account, region or subscription the run is against.

It reads no configuration, starts no provider and calls no cloud, and it imports nothing outside the standard library. That is what lets a module other than choudoufu import it: plunge reads the same rows to adopt a Pulumi resource whose Terraform type a row covers (GitHub issue #2244, INTENTIUS/plunge#7). choudoufu's own resolver, which evaluates HCL to decide what each row's arguments hold for one instance, stays in internal/live/identity and reads every table here through aliases.

The tables

Most of these are generated: tools/row-gen -emit writes the AWS tables and tools/gcp-row-gen the Google ones, into this directory. A generated file says so in its header and is never edited by hand.

Reading a provider's identity schema

A provider that serves resource identity schemas says, per type, which identity attributes it requires for import. IdentityAttrs, IdentityCandidates, IsContextAttr and NotContext are the rules that read those schemas: which attributes name one instance and which are context the provider fills in itself. They take the schemas through the TypeSchema and SchemaSet interfaces, so a caller can adapt whatever schema representation it holds. Schema and Schemas are plain implementations for a caller that builds them by hand.

Cloud Control identifiers

CloudControlTranslation turns a Cloud Control identifier (and, for some types, its resource model) into the Terraform id the type is imported by, from the type's row or a written-out rule (GitHub issue #2286, moved from plunge). The one value it cannot read from the listing, the AWS account ID aws_budgets_budget_action's id starts with, comes in from the engine through an AccountSource; this package still calls no cloud.

What stays in internal/live/identity, because it reads configuration or OpenTofu's provider types: resolution of instances, the config-side naming signal, schema-based row synthesis, and the table verification that reports against tfdiags.

Stability

The exported names here are what plunge builds against. A change to a field's meaning, or a removal, is a breaking change for that module and is made in both repositories together. Adding a row, a table entry or a new field with a zero value that keeps today's behaviour is not.

The parent-read sweep leg (issue #60): an untaggable admitted type cannot carry an ownership marker of its own, but several of them do not need one, because their whole identity is already built out of an admitted parent's own identity - a bucket policy's "bucket" argument is the same string as the bucket's own identity, a KMS-style child that reads a role or a target group is the same shape. Reading the parent - which is admitted and, being taggable, discoverable by marker on its own - answers "does this child exist" with no memory of the child required.

This file is the mechanical half: which untaggable admitted types have that shape, and through which of their own arguments. It answers the question from data this package already carries (DefaultTable's Components), not from a second, hand-maintained list - the whole point raised by live/LIMITATIONS.md's "Untaggable types cannot be removed by the sweep" entry growing by hand at every admission batch. See tools/survey-gen/parent_render.go for where this feeds the doc, and internal/live/discovery's parent-read sweep leg for where it feeds a run.

Index

Constants

View Source
const AzureMarkerlessReason = "the Azure service mints part of this type's id (a create-response value, a UUID or a lookup at apply) " +
	"and the type has no ARM tags argument, so every instance would need marker discovery to be found again and there is nowhere to write the marker"

AzureMarkerlessReason is why an untaggable roster member is vetoed without a record_store, the Azure wording of MarkerlessReason.

View Source
const AzureRecordCarriedReason = "the type has no tags argument and no id of its own to tell it from its parent: " +
	"an azurerm settings object takes its parent's ARM id as its own, and azapi_data_plane_resource is a " +
	"data-plane object with no tags and no ARM id, so no marker can name it and the estate's record is what finds it again"

AzureRecordCarriedReason is why every type in AzureRecordCarriedTypes reaches the record rung, worded as MarkerlessReason is for AWS.

View Source
const DiscoverableFallbackReason = "" /* 289-byte string literal not displayed */

DiscoverableFallbackReason is why every type in DiscoverableFallbackTypes may answer a failed configuration-derived identity with the tofu-address marker instead of refusing. It is one ruling covering the whole set, not a summary of many.

View Source
const GoogleMarkerlessReason = "" /* 181-byte string literal not displayed */

GoogleMarkerlessReason is why every type in GoogleMarkerlessTypes is outside GoogleTable, the hashicorp/google wording of MarkerlessReason.

View Source
const MarkerlessReason = "" /* 179-byte string literal not displayed */

MarkerlessReason is why every type in MarkerlessTypes is outside DefaultTable. It is one ruling covering the whole set, not a summary of many: the two facts it names are the only two the rule reads.

View Source
const NotImportableReason = "" /* 345-byte string literal not displayed */

NotImportableReason is why every type in NotImportableTypes is refused admission regardless of what its identity schema or taggability otherwise promise. It is one ruling covering the whole set, not a summary of many.

View Source
const PerElementShrinkCaveat = "removing an element changes the identity, so a shrinking " +
	"per-element set resolves to a different object and the removal is never applied"

PerElementShrinkCaveat records what a per-element identity does NOT promise, because the gap is invisible in a green run and belongs beside the field rather than in a commit message.

The identity IS the set. So removing an element changes the identity, and a resource whose set shrinks resolves to a DIFFERENT object than the one it owns. For aws_iam_user_group_membership, whose docs say it "can be used multiple times with the same user for non-overlapping groups", the concrete shape is:

groups = ["a", "b"]  ->  groups = ["a"]

The resolved identity becomes user/a, which imports cleanly because the user really is in group a, and reads back groups = ["a"] - so the plan is empty and membership in b is never revoked. Stock computes old-minus-new against its state and revokes it. The set argument is not ForceNew; updating it in place is the resource's whole purpose.

This is an inference from the provider's own documentation - that a non-overlapping second resource is supported at all requires the read to filter to the set the ID names - and it was not confirmed against the provider's source, which is not available offline here. It is written down rather than acted on for that reason. Confirming it makes this either a refusal when a per-element identity would shrink, or a documented limitation; leaving it unwritten makes it neither.

View Source
const SameNameIdentity = "*"

SameNameIdentity is Component.IdentityAttr for the ordinary case: the identity attribute is the argument that supplied the value, under its own name. It is not an attribute name and no schema has one - the character is not legal in an identifier - because it stands for a rule that is applied per instance, once the argument is chosen.

Variables

View Source
var AzureRecordCarriedTypes = map[string]struct{}{
	"azapi_data_plane_resource":                                   {},
	"azurerm_api_management_api_operation_policy":                 {},
	"azurerm_api_management_api_policy":                           {},
	"azurerm_api_management_policy":                               {},
	"azurerm_api_management_product_policy":                       {},
	"azurerm_api_management_workspace_policy":                     {},
	"azurerm_app_service_source_control":                          {},
	"azurerm_app_service_virtual_network_swift_connection":        {},
	"azurerm_cognitive_account_customer_managed_key":              {},
	"azurerm_container_app_environment_custom_domain":             {},
	"azurerm_data_factory_customer_managed_key":                   {},
	"azurerm_data_protection_backup_vault_customer_managed_key":   {},
	"azurerm_databricks_workspace_root_dbfs_customer_managed_key": {},
	"azurerm_eventgrid_partner_configuration":                     {},
	"azurerm_eventhub_namespace_customer_managed_key":             {},
	"azurerm_iotcentral_application_network_rule_set":             {},
	"azurerm_iothub_file_upload":                                  {},
	"azurerm_kubernetes_cluster_deployment_safeguard":             {},
	"azurerm_kusto_cluster_customer_managed_key":                  {},
	"azurerm_log_analytics_cluster_customer_managed_key":          {},
	"azurerm_managed_disk_sas_token":                              {},
	"azurerm_managed_redis_geo_replication":                       {},
	"azurerm_mssql_job_schedule":                                  {},
	"azurerm_netapp_account_encryption":                           {},
	"azurerm_servicebus_namespace_customer_managed_key":           {},
	"azurerm_signalr_service_network_acl":                         {},
	"azurerm_spring_cloud_active_deployment":                      {},
	"azurerm_storage_account_customer_managed_key":                {},
	"azurerm_storage_account_network_rules":                       {},
	"azurerm_storage_account_queue_properties":                    {},
	"azurerm_storage_account_static_website":                      {},
	"azurerm_storage_account_table_properties":                    {},
	"azurerm_storage_blob_inventory_policy":                       {},
	"azurerm_stream_analytics_job_storage_account":                {},
	"azurerm_subnet_nat_gateway_association":                      {},
	"azurerm_subnet_network_security_group_association":           {},
	"azurerm_subnet_route_table_association":                      {},
	"azurerm_web_pubsub_network_acl":                              {},
}

AzureRecordCarriedTypes is the maintainer's 2026-10-23 ruling on INTENTIUS/choudoufu#2201 (items 2 and 8; built by #2203): the 37 azurerm parent-id settings objects and azapi_data_plane_resource are admitted as record-carried BY DECLARATION, located by the record rung keyed by type and parent id, as AWS's MarkerlessTypes rule does for its untaggable types.

The list is a declaration, not a derivation. The 37 are the "parent-id" population of the #2147 audit (azurerm 5.10.0): the id azurerm stores is the parent's own ARM id, so the object cannot be told apart from the parent by id, and it has no tags argument to carry a marker. The object is found again by the record, which holds the applied object's id (the parent's id) under the block's address; the pair (type, parent id) is the key in the sense that two blocks of one type over one parent name the same object and the plan refuses the collision. Moving a type in or out of this list is a ruling.

IsMarkerless answers true for a member, so the lint veto, the check roster and [LocatedType] treat it as they treat an AWS member: with a record_store declared the type is admitted ([ClassRecordLocated]); without one it is refused as markerless, with AzureRecordCarriedReason. [LocatedType] additionally requires, for a member, that the schema carries no marker surface, so a type that gains a tags argument at a provider bump leaves this route for the ordinary one rather than being recorded.

View Source
var AzureServerMintedTypes = map[string]AzureServerMinted{
	"azurerm_active_directory_domain_service":                     {Mints: "the id ends in /initialReplicaSetId/<id>, which the service mints", Taggable: true},
	"azurerm_active_directory_domain_service_replica_set":         {Mints: "the replica set id is read back from the domain service"},
	"azurerm_application_insights_analytics_item":                 {Mints: "the id is read from the create response"},
	"azurerm_key_vault_key":                                       {Mints: "the versioned id comes from the create response", Taggable: true, DataPlane: true},
	"azurerm_key_vault_secret":                                    {Mints: "the versioned id comes from the create response", Taggable: true, DataPlane: true},
	"azurerm_key_vault_certificate":                               {Mints: "the versioned id comes from the create response", Taggable: true, DataPlane: true},
	"azurerm_sentinel_threat_intelligence_indicator":              {Mints: "the id comes from the create response", DataPlane: true},
	"azurerm_sentinel_alert_rule_anomaly_duplicate":               {Mints: "uuid.New() on every create"},
	"azurerm_security_center_assessment_policy":                   {Mints: "uuid.New() on every create"},
	"azurerm_synapse_role_assignment":                             {Mints: "a UUID is generated inside create"},
	"azurerm_storage_object_replication":                          {Mints: "the policy id is assigned by the destination account"},
	"azurerm_app_service_managed_certificate":                     {Mints: "the resource group is read from the web app's plan at apply", Taggable: true},
	"azurerm_backup_protected_file_share":                         {Mints: "the share's system name is known only to Azure Backup"},
	"azurerm_site_recovery_hyperv_network_mapping":                {Mints: "fabric, network or container ids are looked up by friendly name at apply"},
	"azurerm_site_recovery_hyperv_replication_policy_association": {Mints: "fabric, network or container ids are looked up by friendly name at apply"},
	"azurerm_site_recovery_vmware_replicated_vm":                  {Mints: "fabric, network or container ids are looked up by friendly name at apply"},
	"azurerm_site_recovery_vmware_replication_policy_association": {Mints: "fabric, network or container ids are looked up by friendly name at apply"},
}

AzureServerMintedTypes is every azurerm type whose id the server mints (17 at azurerm 5.10.0, #2147).

View Source
var ContentMatchTypes = map[string]ContentMatchBinding{
	"aws_alb": {
		Argument:     "name",
		CFNType:      "AWS::ElasticLoadBalancingV2::LoadBalancer",
		PropertyPath: []string{"Name"},
	},
	"aws_athena_data_catalog": {
		Argument:     "name",
		CFNType:      "AWS::Athena::DataCatalog",
		PropertyPath: []string{"Name"},
	},
	"aws_cloudfront_cache_policy": {
		Argument:     "name",
		CFNType:      "AWS::CloudFront::CachePolicy",
		PropertyPath: []string{"CachePolicyConfig", "Name"},
	},
	"aws_cloudfront_origin_request_policy": {
		Argument:     "name",
		CFNType:      "AWS::CloudFront::OriginRequestPolicy",
		PropertyPath: []string{"OriginRequestPolicyConfig", "Name"},
	},
	"aws_cloudfront_realtime_log_config": {
		Argument:     "name",
		CFNType:      "AWS::CloudFront::RealtimeLogConfig",
		PropertyPath: []string{"Name"},
	},
	"aws_cloudfront_response_headers_policy": {
		Argument:     "name",
		CFNType:      "AWS::CloudFront::ResponseHeadersPolicy",
		PropertyPath: []string{"ResponseHeadersPolicyConfig", "Name"},
	},
	"aws_lb": {
		Argument:     "name",
		CFNType:      "AWS::ElasticLoadBalancingV2::LoadBalancer",
		PropertyPath: []string{"Name"},
	},
	"aws_route53_cidr_collection": {
		Argument:     "name",
		CFNType:      "AWS::Route53::CidrCollection",
		PropertyPath: []string{"Name"},
	},
	"aws_timestreaminfluxdb_db_cluster": {
		Argument:     "name",
		CFNType:      "AWS::Timestream::InfluxDBCluster",
		PropertyPath: []string{"Name"},
	},
	"aws_timestreaminfluxdb_db_instance": {
		Argument:     "name",
		CFNType:      "AWS::Timestream::InfluxDBInstance",
		PropertyPath: []string{"Name"},
	},
}

ContentMatchTypes is every provider resource type issue #272's two-source rule qualifies for content-match discovery - see ContentMatchBinding.

The set is derived on every generator run from live/import-grammar.json's and live/registry-schema-facts.json's own DeclaredUnique signals, joined by the property name both sources describe. Nothing here is maintained by hand.

View Source
var DefaultTable = map[string]TypeIdentity{}/* 1049 elements not displayed */

DefaultTable is the v0 identity table: every AWS resource type the live-mode subset admits, keyed by provider-local type name. A type absent from this table is outside the subset and resolving it is an error.

Every row is derived by tools/row-gen from the provider's own schema, the CloudFormation Registry's primaryIdentifier, and the provider documentation's import grammar. Corrections belong in the generator's rules or in its annotation ledger (tools/row-gen/annotations.json), never here: this file is overwritten in full on every run.

View Source
var DiscoverableFallbackTypes = map[string]struct{}{}/* 212 elements not displayed */

DiscoverableFallbackTypes is every admitted provider resource type whose identity is built from configuration (not TypeIdentity.ServerAssigned, not TypeIdentity.RecordBacked) but which may still fall back to the tofu-address marker when configuration does not settle it: the type is taggable, per live/survey-full.json's own signal, and enumerable through either a native list resource or a CloudFormation list handler - see DiscoverableFallbackReason and GitHub issue #289.

[resolver.markerFallback] is the sole reader: it converts one of a fixed set of identity-VALUE refusals (see markerfallback.go's own refusal table) into [ClassNeedsDiscovery] with [DiscoveryMarkerFallback] for a type in this set, and leaves every other type refusing exactly as before. A type absent from this set - untaggable, or taggable but unlistable by either route - never reaches that fallback; it keeps refusing in configuration language, the same as it always has.

The set is derived on every generator run from live/survey-full.json's taggability and native-list signals and live/registry.json's CloudFormation list handler, joined through live/mapping.json. Nothing here is maintained by hand.

View Source
var DocumentedImportIDs = map[string]DocumentedImportID{}/* 157 elements not displayed */

DocumentedImportIDs is every provider resource type whose Import section documents a COMPOSITE import string, whose exported `id` no source proves to be that whole string, and whose segments the page names one token at a time - the grammar, in the documented order, with the documented separator.

It is issue #337's second half, and it is the second way a type qualifies for a composite located identity. IDNotProvenWholeTypes is the first half: it refuses a type whose `id` might be a fragment. A type here is one the same page goes on to describe well enough to COMPOSE the whole string from the applied object, so [LocatedIdentityPlanFor] admits it instead of refusing it.

What Parts holds, and what it does not

Parts are segment names in the documented segment order, reduced to the comparison form a name match uses (lower case, punctuation removed), so "REST-API-ID" and "rest_api_id" are one name. They are names read off a documentation page and nothing has checked them against a provider: a name here may correspond to no attribute at all. [LocatedIdentityPlanFor] is where every name is resolved against the real schema, and a name that resolves to nothing is what makes the type refused rather than admitted.

Why an ORDER is safe to carry here

Issue #105 forbids flattening a composite IDENTITY into a plausible string, and #329 kept that rule by having nothing to flatten. This is a different fact: a documented import ID is a string, and the page states both its separator and its segment order. tools/importdocs-gen returns segments only when the names it read account for every segment of the documented example under that separator, so a partial reading never becomes an order.

The set is derived on every generator run from live/import-grammar.json. Nothing here is maintained by hand.

View Source
var GoogleAssetImports = map[string]GoogleAssetImport{}/* 303 elements not displayed */

GoogleAssetImports says, for each labelled hashicorp/google and google-beta 8.6.0 type whose import form the provider source at the pin states, how a Cloud Asset Inventory hit's name becomes an import ID (INTENTIUS/choudoufu#2139 item 7). Of 318 labelled types (297 served by hashicorp/google), 300 take the Asset path as it is (282), 3 take it rewritten into another accepted form (3), and 15 are unknown (12) and have no row: a hit of one is refused by name, never imported with a guessed ID.

The provider source states no Asset name for a handwritten type, and 11 rows (11) take the name Google documents for the type's asset type instead, marked Documented. The page is https://docs.cloud.google.com/asset-inventory/docs/asset-names (last updated 2026-10-19), read into live/gcp-asset-names.json.

View Source
var GoogleAssetUnknown = map[string]string{
	"google_active_directory_peering":            "the provider source states no import format for this type",
	"google_assured_workloads_workload":          "no import format carries exactly the parts of the documented Asset name //assuredworkloads.googleapis.com/{WORKLOAD}",
	"google_compute_instance_from_machine_image": "the provider source states no import format for this type (its documented asset type is compute.googleapis.com/Instance)",
	"google_compute_instance_from_template":      "the provider source states no import format for this type (its documented asset type is compute.googleapis.com/Instance)",
	"google_dataflow_flex_template_job":          "the provider source states no import format for this type (its documented asset type is dataflow.googleapis.com/Job)",
	"google_dataflow_job":                        "the importer is schema.ImportStatePassthrough and Create sets the id from no ReplaceVars template, so the import ID is the id alone. Google documents the Asset name as //dataflow.googleapis.com/projects/{PROJECT_ID}/locations/{LOCATION}/jobs/{JOB}, and only a real Asset response shows whether {JOB} is the value Create sets. The importer would also drop the name's other parts for the provider's defaults",
	"google_dataproc_cluster":                    "the provider source states no import format for this type (its documented asset type is dataproc.googleapis.com/Cluster)",
	"google_dataproc_job":                        "the provider source states no import format for this type (its documented asset type is dataproc.googleapis.com/Job)",
	"google_dataproc_session_template":           "the import form \"{{name}}\" is the whole name the API gives it under projects/{{project}}/locations/{{location}}/sessionTemplates, and no Asset name Google documents under dataproc.googleapis.com has that shape",
	"google_project":                             "the provider source states no import format for this type (its documented asset type is cloudresourcemanager.googleapis.com/Project)",
	"google_recaptcha_enterprise_key":            "no import format carries exactly the parts of the documented Asset name //recaptchaenterprise.googleapis.com/projects/{PROJECT_NUMBER}/keys/{KEY}; a project number is not the project ID the provider's project argument holds",
	"google_service_directory_namespace":         "the importer takes the whole import ID as name, and Google documents its Asset name as //servicedirectory.googleapis.com/projects/{PROJECT_NUMBER}/locations/{LOCATION}/namespaces/{NAMESPACE}; a project number is not the project ID the provider's project argument holds",
	"google_vertex_ai_dataset":                   "the provider source states no import format for this type",
	"google_vertex_ai_tensorboard":               "the importer sets name to \"projects/{{project}}/locations/{{region}}/tensorboards/{{name}}\", and Google documents its Asset name as //aiplatform.googleapis.com/projects/{PROJECT_NUMBER}/locations/{LOCATION}/tensorboards/{TENSORBOARD}; a project number is not the project ID the provider's project argument holds",
	"google_workflows_workflow":                  "the provider source states no import format for this type",
}

GoogleAssetUnknown is every labelled type with no GoogleAssetImports row, and why neither the source at the pin nor Google's asset names page gives one.

View Source
var GoogleCompositeImports = map[string][]GoogleImportFormat{
	"google_access_context_manager_egress_policy":                      {{Template: "{{egress_policy_name}}/{{resource}}"}},
	"google_access_context_manager_ingress_policy":                     {{Template: "{{ingress_policy_name}}/{{resource}}"}},
	"google_access_context_manager_service_perimeter_dry_run_resource": {{Template: "{{perimeter_name}}/{{resource}}"}},
	"google_access_context_manager_service_perimeter_resource":         {{Template: "{{perimeter_name}}/{{resource}}"}},
	"google_bigquery_reservation_assignment":                           {{Template: "projects/{{project}}/locations/{{location}}/reservations/{{reservation}}/assignments/{{name}}", Regex: "^projects/(?P<project>[^/]+)/locations/(?P<location>[^/]+)/reservations/(?P<reservation>[^/]+)/assignments/(?P<name>[^/]+)$"}, {Template: "{{project}}/{{location}}/{{reservation}}/{{name}}", Regex: "^(?P<project>[^/]+)/(?P<location>[^/]+)/(?P<reservation>[^/]+)/(?P<name>[^/]+)$"}, {Template: "{{location}}/{{reservation}}/{{name}}", Regex: "^(?P<location>[^/]+)/(?P<reservation>[^/]+)/(?P<name>[^/]+)$"}},
	"google_ces_app_root_agent_association":                            {{Template: "projects/{{project}}/locations/{{location}}/apps/{{app_id}}/agents/{{agent_id}}"}, {Template: "{{project}}/{{location}}/{{app_id}}/{{agent_id}}"}, {Template: "{{location}}/{{app_id}}/{{agent_id}}"}},
	"google_cloud_security_compliance_cloud_control":                   {{Template: "{{parent}}/locations/{{location}}/cloudControls/{{cloud_control_id}}"}},
	"google_cloud_security_compliance_framework":                       {{Template: "{{parent}}/locations/{{location}}/frameworks/{{framework_id}}"}},
	"google_cloud_security_compliance_framework_deployment":            {{Template: "{{parent}}/locations/{{location}}/frameworkDeployments/{{framework_deployment_id}}"}},
	"google_cloudbuildv2_repository":                                   {{Template: "projects/{{project}}/locations/{{location}}/connections/{{parent_connection}}/repositories/{{name}}", Regex: "^projects/(?P<project>[^/]+)/locations/(?P<location>[^/]+)/connections/(?P<parent_connection>[^/]+)/repositories/(?P<name>[^/]+)$"}, {Template: "{{project}}/{{location}}/{{parent_connection}}/{{name}}", Regex: "^(?P<project>[^/]+)/(?P<location>[^/]+)/(?P<parent_connection>[^/]+)/(?P<name>[^/]+)$"}, {Template: "{{location}}/{{parent_connection}}/{{name}}", Regex: "^(?P<location>[^/]+)/(?P<parent_connection>[^/]+)/(?P<name>[^/]+)$"}},
	"google_healthcare_dicom_store":                                    {{Template: "{{dataset}}/dicomStores/{{name}}"}, {Template: "{{dataset}}/{{name}}"}},
	"google_healthcare_fhir_store":                                     {{Template: "{{dataset}}/fhirStores/{{name}}"}, {Template: "{{dataset}}/{{name}}"}},
	"google_healthcare_hl7_v2_store":                                   {{Template: "{{dataset}}/hl7V2Stores/{{name}}"}, {Template: "{{dataset}}/{{name}}"}},
	"google_kms_crypto_key":                                            {{Template: "{{key_ring}}/cryptoKeys/{{name}}"}, {Template: "{{key_ring}}/{{name}}"}},
	"google_logging_linked_dataset":                                    {{Template: "{{parent}}/locations/{{location}}/buckets/{{bucket}}/links/{{link_id}}", Regex: "^(?P<parent>.+)/locations/(?P<location>[^/]+)/buckets/(?P<bucket>[^/]+)/links/(?P<link_id>[^/]+)$"}},
	"google_logging_log_view":                                          {{Template: "{{parent}}/locations/{{location}}/buckets/{{bucket}}/views/{{name}}", Regex: "^(?P<parent>.+)/locations/(?P<location>[^/]+)/buckets/(?P<bucket>[^/]+)/views/(?P<name>[^/]+)$"}},
}

GoogleCompositeImports are the import forms of each google type with no GoogleTable row whose identity schema requires two or more attributes (INTENTIUS/choudoufu#2139 item 3). Its record holds those attributes as Components and no import ID, and the record leg composes one from the first form whose every part the record carries. Of 23 such types (4 labelled), 16 compose (4 labelled) and 7 are refused by name (GoogleCompositeRefused).

View Source
var GoogleCompositeRefused = map[string]string{
	"google_apigee_endpoint_attachment":             "no import format is made of the identity schema's attributes",
	"google_compute_backend_bucket_signed_url_key":  "the provider source states no import format for this type",
	"google_compute_backend_service_signed_url_key": "the provider source states no import format for this type",
	"google_service_directory_namespace_iam_member": "an IAM type's import ID is space-separated words, not a form its identity parts fill",
	"google_service_directory_service_iam_member":   "an IAM type's import ID is space-separated words, not a form its identity parts fill",
	"google_sql_user": "no import format is made of the identity schema's attributes",
	"google_vertex_ai_feature_online_store_featureview_iam_member": "an IAM type's import ID is space-separated words, not a form its identity parts fill",
}

GoogleCompositeRefused is every such type with no form, and why.

View Source
var GoogleIdentityImportRefused = map[string]string{
	"google_access_context_manager_egress_policy":                      "the importer resourceAccessContextManagerEgressPolicyImport reads no identity and calls no tpgresource.ParseImportId",
	"google_access_context_manager_ingress_policy":                     "the importer resourceAccessContextManagerIngressPolicyImport reads no identity and calls no tpgresource.ParseImportId",
	"google_access_context_manager_service_perimeter_dry_run_resource": "the importer resourceAccessContextManagerServicePerimeterDryRunResourceImport reads no identity and calls no tpgresource.ParseImportId",
	"google_access_context_manager_service_perimeter_resource":         "the importer resourceAccessContextManagerServicePerimeterResourceImport reads no identity and calls no tpgresource.ParseImportId",
	"google_apigee_api_product":                                        "the importer's id format {{org_id}}/apiproducts/{{name}} names {{org_id}}, which its first regex does not set",
	"google_apigee_app_group":                                          "the importer's id format {{org_id}}/appgroups/{{name}} names {{org_id}}, which its first regex does not set",
	"google_apigee_data_collector":                                     "the importer's id format {{org_id}}/datacollectors/{{data_collector_id}} names {{org_id}}, which its first regex does not set",
	"google_apigee_datastore":                                          "the importer's id format {{org_id}}/analytics/datastores/{{name}} names {{org_id}}, which its first regex does not set",
	"google_apigee_developer":                                          "after tpgresource.ParseImportId the importer reads d.Get(\"name\"), which its first regex does not set",
	"google_apigee_developer_app":                                      "the importer's tpgresource.ParseImportId names no regex",
	"google_apigee_endpoint_attachment":                                "the importer needs name, which the identity schema does not carry",
	"google_apigee_envgroup":                                           "the importer's id format {{org_id}}/envgroups/{{name}} names {{org_id}}, which its first regex does not set",
	"google_apigee_environment":                                        "the importer's id format {{org_id}}/environments/{{name}} names {{org_id}}, which its first regex does not set",
	"google_apigee_instance":                                           "the importer's id format {{org_id}}/instances/{{name}} names {{org_id}}, which its first regex does not set",
	"google_ces_app_root_agent_association":                            "the importer resourceCESAppRootAgentAssociationImport reads no identity and calls no tpgresource.ParseImportId",
	"google_cloud_asset_folder_feed":                                   "the importer resourceCloudAssetFolderFeedImport reads no identity and calls no tpgresource.ParseImportId",
	"google_cloud_asset_organization_feed":                             "the importer resourceCloudAssetOrganizationFeedImport reads no identity and calls no tpgresource.ParseImportId",
	"google_cloud_security_compliance_cloud_control":                   "after tpgresource.ParseImportId the importer reads d.Get(\"organization\"), which its first regex does not set",
	"google_cloud_security_compliance_framework":                       "after tpgresource.ParseImportId the importer reads d.Get(\"organization\"), which its first regex does not set",
	"google_cloud_security_compliance_framework_deployment":            "after tpgresource.ParseImportId the importer reads d.Get(\"organization\"), which its first regex does not set",
	"google_compute_backend_bucket_signed_url_key":                     "the resource registers no importer",
	"google_compute_backend_service_signed_url_key":                    "the resource registers no importer",
	"google_data_loss_prevention_content_policy":                       "the importer's id format {{parent}}/contentPolicies/{{name}} names {{parent}}, which its first regex does not set",
	"google_data_loss_prevention_deidentify_template":                  "the importer's id format {{parent}}/deidentifyTemplates/{{name}} names {{parent}}, which its first regex does not set",
	"google_data_loss_prevention_discovery_config":                     "the importer's id format {{parent}}/discoveryConfigs/{{name}} names {{parent}}, which its first regex does not set",
	"google_data_loss_prevention_inspect_template":                     "the importer's id format {{parent}}/inspectTemplates/{{name}} names {{parent}}, which its first regex does not set",
	"google_data_loss_prevention_job_trigger":                          "the importer's id format {{parent}}/jobTriggers/{{name}} names {{parent}}, which its first regex does not set",
	"google_data_loss_prevention_stored_info_type":                     "the importer's id format {{parent}}/storedInfoTypes/{{name}} names {{parent}}, which its first regex does not set",
	"google_discovery_engine_target_site":                              "after tpgresource.ParseImportId the importer reads d.Get(\"name\"), which its first regex does not set",
	"google_healthcare_dicom_store":                                    "the importer resourceHealthcareDicomStoreImport reads no identity and calls no tpgresource.ParseImportId",
	"google_healthcare_fhir_store":                                     "the importer resourceHealthcareFhirStoreImport reads no identity and calls no tpgresource.ParseImportId",
	"google_healthcare_hl7_v2_store":                                   "the importer resourceHealthcareHl7V2StoreImport reads no identity and calls no tpgresource.ParseImportId",
	"google_kms_crypto_key":                                            "the importer resourceKMSCryptoKeyImport reads no identity and calls no tpgresource.ParseImportId",
	"google_secret_manager_secret_version":                             "the importer resourceSecretManagerSecretVersionImport reads no identity and calls no tpgresource.ParseImportId",
}

GoogleIdentityImportRefused is every such type whose importer takes no identity, and why, quoted from the source at the pin.

View Source
var GoogleIdentityImports = map[string]GoogleIdentityImport{}/* 609 elements not displayed */

GoogleIdentityImports says, for each hashicorp/google and google-beta 8.6.0 type whose identity schema requires two or more attributes and whose importer takes an identity object, which attributes a record must carry for the importer to find the object by them (INTENTIUS/choudoufu#2145 item 4). The record of such a type holds its identity as Components and no import ID. When no import ID composes from them, the record leg imports by the identity instead, exactly where the importer at the pin reads one: tpgresource.ParseImportId's identity branch (465), the importer's own identity branch (1), or the IAM framework's parent identity parser (143). Of 643 such types, 609 take an identity and 34 do not (GoogleIdentityImportRefused).

View Source
var GoogleMarkerlessTypes = map[string]struct{}{}/* 135 elements not displayed */

GoogleMarkerlessTypes is every hashicorp/google and google-beta resource type tools/gcp-row-gen refuses to admit because the provider mints its identity and it carries no labels argument - see GoogleMarkerlessReason. A veto derived by rule, as MarkerlessTypes is for AWS.

View Source
var GoogleTable = map[string]TypeIdentity{}/* 1233 elements not displayed */

GoogleTable is the identity table for hashicorp/google and hashicorp/google-beta 8.6.0 resource types, keyed by type name. One row serves both providers: a type the two serve with different identities has no row.

Every row is derived by tools/gcp-row-gen from the provider's own source at the v8.6.0 tag (live/gcp-identity-evidence.json) and checked against both providers' schemas (live/gcp-schema-facts.json). A type with no row gets what an unrowed AWS type gets: the identity-schema route, or a refusal. Corrections belong in the generator, never here.

View Source
var IDNotProvenWholeTypes = map[string]struct{}{}/* 312 elements not displayed */

IDNotProvenWholeTypes is every provider resource type whose Import section documents a COMPOSITE import string and for which no independent source proves the exported `id` attribute holds that whole string.

It is issue #337's verdict, in the form the record-located mechanism can act on. [LocatedIdentityPlanFor] records a type's `id` and hands that string back to import on the next run, which is correct only when `id` IS the import string. For a type here nothing says it is, so the mechanism refuses rather than recording a possible fragment - a recorded fragment is a wrong identity, and a wrong identity is invisible to every verdict-level check until the NEXT run's import fails.

Membership is UNPROVEN, not disproven. The rule is the Import section's own scraped separator and the Attribute Reference's `id` bullet naming the same join character - two sections of one page, different purposes, different scrapers. A type whose page states no composite `id` form, or documents no `id` attribute at all, is here because its page does not settle the question, not because the answer is known to be no.

A type whose documented import is FLAT is absent: `id` being the whole string is what flat means. So is a type with no scraped Import section, which makes no composite claim for anything to corroborate.

The set is derived on every generator run from live/import-grammar.json. Nothing here is maintained by hand. live/composite-import-roster.json is the same rule rendered as a report over the markerless population, with the per-type evidence and the reason each unproven type is unproven.

View Source
var MarkerlessTypes = map[string]struct{}{}/* 159 elements not displayed */

MarkerlessTypes is every provider resource type tools/row-gen refuses to admit because the provider mints its identity and it carries no tags argument - see MarkerlessReason.

This is a veto, not a gap. A type here is absent from DefaultTable deliberately and by a derived rule, which is what distinguishes it from a type no ratification batch has reached yet. tools/row-gen/rejected.json records the rulings a batch made by hand; this records the one a rule makes, and the two overlap wherever a batch had already reached the same answer about a type.

The set is derived on every generator run from live/survey-full.json's taggability signal and the server-assignment verdict that decides admission for each type. Nothing here is maintained by hand.

View Source
var NotImportableTypes = map[string]struct{}{
	"aws_acmpca_permission":                                   {},
	"aws_ami_copy":                                            {},
	"aws_ami_from_instance":                                   {},
	"aws_appautoscaling_scheduled_action":                     {},
	"aws_appfabric_app_authorization_connection":              {},
	"aws_apprunner_deployment":                                {},
	"aws_autoscaling_attachment":                              {},
	"aws_autoscaling_notification":                            {},
	"aws_cloudcontrolapi_resource":                            {},
	"aws_cloudformation_type":                                 {},
	"aws_codecommit_trigger":                                  {},
	"aws_dataexchange_revision_assets":                        {},
	"aws_dx_bgp_peer":                                         {},
	"aws_dx_connection_association":                           {},
	"aws_dx_connection_confirmation":                          {},
	"aws_dx_hosted_connection":                                {},
	"aws_ebs_snapshot_copy":                                   {},
	"aws_ebs_snapshot_import":                                 {},
	"aws_ec2_image_block_public_access":                       {},
	"aws_ec2_instance_metadata_defaults":                      {},
	"aws_ec2_transit_gateway_default_route_table_association": {},
	"aws_ec2_transit_gateway_default_route_table_propagation": {},
	"aws_ec2_transit_gateway_multicast_domain_association":    {},
	"aws_ec2_transit_gateway_multicast_group_member":          {},
	"aws_ec2_transit_gateway_multicast_group_source":          {},
	"aws_elastic_beanstalk_application_version":               {},
	"aws_elastic_beanstalk_configuration_template":            {},
	"aws_elasticsearch_domain_policy":                         {},
	"aws_elb_attachment":                                      {},
	"aws_grafana_role_association":                            {},
	"aws_grafana_workspace_api_key":                           {},
	"aws_grafana_workspace_service_account_token":             {},
	"aws_guardduty_detector_feature":                          {},
	"aws_guardduty_member_detector_feature":                   {},
	"aws_guardduty_organization_configuration_feature":        {},
	"aws_iam_group_membership":                                {},
	"aws_iam_policy_attachment":                               {},
	"aws_iam_security_token_service_preferences":              {},
	"aws_inspector2_organization_configuration":               {},
	"aws_iot_ca_certificate":                                  {},
	"aws_iot_certificate":                                     {},
	"aws_iot_logging_options":                                 {},
	"aws_iot_policy_attachment":                               {},
	"aws_kms_ciphertext":                                      {},
	"aws_lakeformation_opt_in":                                {},
	"aws_lakeformation_permissions":                           {},
	"aws_lakeformation_resource":                              {},
	"aws_lakeformation_resource_lf_tag":                       {},
	"aws_lakeformation_resource_lf_tags":                      {},
	"aws_lb_cookie_stickiness_policy":                         {},
	"aws_lb_ssl_negotiation_policy":                           {},
	"aws_lightsail_domain":                                    {},
	"aws_lightsail_instance_public_ports":                     {},
	"aws_lightsail_key_pair":                                  {},
	"aws_load_balancer_backend_server_policy":                 {},
	"aws_load_balancer_listener_policy":                       {},
	"aws_load_balancer_policy":                                {},
	"aws_macie2_organization_configuration":                   {},
	"aws_main_route_table_association":                        {},
	"aws_networkmanager_attachment_accepter":                  {},
	"aws_notifications_organizations_access":                  {},
	"aws_proxy_protocol_policy":                               {},
	"aws_qldb_stream":                                         {},
	"aws_quicksight_user":                                     {},
	"aws_s3_object_copy":                                      {},
	"aws_servicecatalog_organizations_access":                 {},
	"aws_ses_domain_identity_verification":                    {},
}

NotImportableTypes is every provider resource type tools/row-gen refuses to admit because the provider reports no classic Importer for it - see NotImportableReason. A wire identity schema, or even taggability, is not proof a real migrate can import the type (issue #331): this is the one signal that asks the question directly, tools/survey-gen's ImportResourceState probe.

This is a veto, not a gap, the same distinction MarkerlessTypes draws: a type here is absent from DefaultTable deliberately and by a derived rule. The set is derived on every generator run from live/survey-full.json's importable signal, with exactly one named, evidenced exception recorded in tools/row-gen/notimportable.go's own notImportableExempt - nothing here is otherwise maintained by hand.

View Source
var VariadicTrailingImportIDTypes = map[string]bool{
	"aws_security_group_rule": true,
}

VariadicTrailingImportIDTypes is the ratified set of resource types whose documented composite import grammar ends in a VARIADIC tail - the provider's own error-message grammar for aws_security_group_rule spells it literally: "SECURITYGROUPID_TYPE_PROTOCOL_FROMPORT_TOPORT_SOURCE [_SOURCE]*" - one token per element of whichever of several SIBLING collection or scalar arguments the configuration set, rather than one token for a single named attribute. [pluralCollectionCollision] already detects the shape generically (a documented segment's singular name collides with a real plural collection attribute on the schema) and refuses it, correctly, for every type NOT in this set: detecting the shape is not the same as knowing it is SAFE to render every family member as a token in a fixed, ratified order, and that second fact is not readable from the schema at all - it is a fact about how the provider's own IMPORT FUNCTION parses the trailing tokens back, which only the provider's source states.

# What has to be true before a type is named here, and how it was checked for aws_security_group_rule

The importer must classify each trailing token independently of its POSITION, so that concatenating the sibling families in a fixed order - this package always uses the order the type's own Component.SoleElement row lists them in, which is how [variadicTrailingGroup] reads the roster - is something the provider can always parse back correctly regardless of which family happens to come first for a given instance. Read directly from hashicorp/aws's internal/service/ec2/vpc_security_group_rule.go (resourceSecurityGroupRuleImport, fetched 2026-08-23, no terraform in the loop):

sources := parts[5:]
...
for _, v := range sources {
	switch {
	case v == "self":
		d.Set("self", true)
	case strings.Contains(v, "sg-"):
		d.Set("source_security_group_id", v)
	case strings.Contains(v, ":"):
		ipv6CIDRBlocks = append(ipv6CIDRBlocks, v)
	case strings.Contains(v, "pl-"):
		prefixListIDs = append(prefixListIDs, v)
	default:
		cidrBlocks = append(cidrBlocks, v)
	}
}

Every trailing token is sorted into its family by the SHAPE of its own content - "sg-", a colon, "pl-", or else a bare CIDR - never by which position it occupies among the other tokens. That is confirmed by the documentation this package already carries in live/import-grammar.json: the Import section's own second example joins four trailing tokens as "10.1.0.0/16_2001:db8::/48_10.2.0.0/16_2002:db8::/48" - an IPv4 CIDR, an IPv6 CIDR, a second IPv4 CIDR and a second IPv6 CIDR, interleaved rather than grouped by family, which the source above shows the importer re-groups on read regardless. So the CROSS-FAMILY order this package picks is free; what is NOT free is the order WITHIN one family's own list, because cidr_blocks/ipv6_cidr_blocks/prefix_list_ids are all schema.TypeList (ordered, not a set) on the real provider schema, and the importer appends each token to its bucket in the order it is encountered - so [locatedVariadicSegments] preserves each family's own element order and never sorts it, unlike Component.PerElement's set case.

Note what this does NOT establish: "self" (a bool, rendered as the literal token "self") is part of the provider's own source-token grammar but is not one of Component.SoleElement's ratified Attrs for this type, so an instance combining self with another family member is unaffected by this mechanism either way - the same pre-existing gap Component.SoleElement's own row has always had, not something this change widens or narrows.

Reach: exactly the population Component.SoleElement itself reaches, because [variadicTrailingGroup] is keyed off that same field and refuses where no row names the colliding attribute as one of a SoleElement family. Measured against internal/live/identity/table_generated.go (`grep -c "SoleElement: true"`), that is ONE row today (aws_security_group_rule); a future SoleElement row reaches this mechanism automatically once its own type is added here after the same verification, never automatically before it.

Functions

func AdmittedTypes

func AdmittedTypes() []string

AdmittedTypes lists every resource type the v0 table covers, sorted. It is DefaultTable's roster and leaves GoogleTable out: its callers enumerate what an AWS or Kubernetes sweep asks for, and nothing sweeps a Google Cloud project yet (#1119).

func AzureDNSSuffix

func AzureDNSSuffix(environment string, which CloudValue) (string, bool)

AzureDNSSuffix is the DNS suffix of one data-plane service in an Azure environment: which is one of CloudKeyVaultDNSSuffix, CloudManagedHSMDNSSuffix, CloudAppConfigurationDNSSuffix and CloudStorageDNSSuffix. ok is false for an environment or a value it has no entry for.

func AzureServerMintedMarkerless

func AzureServerMintedMarkerless(typeName string) bool

AzureServerMintedMarkerless reports whether typeName is a roster member with nowhere to carry a marker: the Azure counterpart of MarkerlessTypes, and the only members whose sole route is the record.

func AzureServerMintedTaggable

func AzureServerMintedTaggable(typeName string) bool

AzureServerMintedTaggable reports whether typeName is a roster member that carries the ARM tags map, so marker discovery can find it.

func CanonicalAzureEnvironment

func CanonicalAzureEnvironment(name string) (string, bool)

CanonicalAzureEnvironment is the lower-case name of an Azure environment this package knows DNS suffixes for, and false for one it does not. Both providers match the name without regard to case.

func FoldParentOf

func FoldParentOf(typeName string) (string, bool)

FoldParentOf reports the admitted parent type whose own identity Components, once rendered, are typeName's whole identity too - the [foldParentTypes] map above. Unlike ParentOf and SingleParentComponent, this is not derived from typeName's own Components at request time: the parent named here may itself be untaggable and [ClassParentDerived] (composite, resolved only once its own parents' live values are known), which is exactly the case SingleParentComponent's "taggable eligible parent" model cannot express, so there is nothing to derive it from short of the hand check the map's own doc comment describes.

func FoldParentTypes

func FoldParentTypes() map[string]string

FoldParentTypes returns the [foldParentTypes] map's own keys, sorted - every type this pass's fold-child leg (internal/live/discovery's foldChildReadSweep) acts on.

func GoogleAssetImportID

func GoogleAssetImportID(typeName, assetName string) (string, bool)

GoogleAssetImportID turns a Cloud Asset Inventory full resource name ("//compute.googleapis.com/projects/p/zones/z/disks/d") of a labelled google type into the import ID that type's importer accepts, by the generated GoogleAssetImports row: the path as it is, or its parts put into an accepted form. ok is false when the type has no row (neither the source at the pin nor Google's documentation says), or the name does not have the row's host and shape; the caller refuses such a hit by name rather than import a guess.

The name is read in the form Google documents for each asset type at https://docs.cloud.google.com/asset-inventory/docs/asset-names (last updated 2026-10-19): a Cloud Storage bucket is //storage.googleapis.com/BUCKET, a Compute Engine instance //compute.googleapis.com/projects/PROJECT_ID/zones/ZONE/instances/INSTANCE. No other spelling is accepted: projects/_/buckets/BUCKET is the Cloud Storage JSON API's and IAM's name for a bucket, not its Asset name.

func GoogleCompositeImportID

func GoogleCompositeImportID(typeName string, components map[string]string) (string, bool)

GoogleCompositeImportID composes the import ID of a google type with no identity-table row from a record's identity Components, by the first GoogleCompositeImports form whose every part the record carries, the order the importer tries them in. ok is false when the type has no form (it is refused by name instead) or the record carries no form's parts.

func GoogleIdentityImportValues

func GoogleIdentityImportValues(typeName string, components map[string]string) (map[string]string, bool)

GoogleIdentityImportValues returns a record's identity Components as the identity values to import a google type by, when the type's importer takes an identity (GoogleIdentityImports) and the record carries every attribute it requires. ok is false otherwise; the caller refuses by name.

func GuessArgNameFromCFNProperty

func GuessArgNameFromCFNProperty(property string) string

GuessArgNameFromCFNProperty converts a CFN PascalCase property name to a GUESSED TF argument name ("GroupId" -> "group_id"), the identical conversion tools/row-gen/argname.go's own snakeCase already makes for its own purpose (a last-resort import-syntax argument name), flagged GUESSED there for the same reason it is here: a CFN property name and a TF argument name are two different providers' vocabularies that happen to often agree, not a fact this function can verify.

ParentByConvention needs it because [scanTypeCloudControl]'s own listed object (internal/live/discovery's Cloud Control leg) carries CFN property names, never the TF schema's own argument names [importIdentityFromResource]'s native-list equivalent reads - found via corpus-ecs-fargate's day2_remove unit, aws_vpc_security_group_egress_rule has no native provider list route at all (Cloud Control is its only enumeration leg), so its own live sighting never carries a "security_ group_id" attribute to match against, only Cloud Control's own "GroupId". A wrong guess is not a hazard here the way it would be for an import ID: ParentByConvention still has to independently confirm the guessed name matches an admitted parent type, and the caller still has to find that parent's own ImportID among this run's own resolutions - a bad guess simply finds nothing, exactly today's "no computed dependency" outcome, never a wrong one asserted with confidence.

func IdentityAttrs

func IdentityAttrs(schema TypeSchema) (required, optional []string)

IdentityAttrs splits a type's identity schema into the attributes the provider requires for import and the ones it accepts optionally, each sorted. Both are nil when the type has no identity schema.

func IdentityCandidates

func IdentityCandidates(schemas SchemaSet, typeName string, schema TypeSchema) (candidates, context []string)

IdentityCandidates splits a type's identity schema into the attributes that name one instance of it and the attributes that are context: the account, the project, the region, whatever the provider fills in from its own ambient configuration. schemas is the provider's whole set, typeName is schema's own key in it.

func IsAzureServerMinted

func IsAzureServerMinted(typeName string) bool

IsAzureServerMinted reports whether typeName is one of the 17.

func IsContextAttr

func IsContextAttr(schemas SchemaSet, typeName string, schema TypeSchema, name string) bool

IsContextAttr reports whether one optional-for-import identity attribute of typeName is context the provider fills in itself, rather than something a configuration supplies to tell one instance from a sibling. The rule, and why it is derived from the schemas rather than from a list of names, is on isContextAttr in schema.go.

func IsMarkerless

func IsMarkerless(typeName string) bool

IsMarkerless reports whether a derived rule refuses typeName because its provider mints the identity and it has nowhere to carry a marker: MarkerlessTypes for AWS, GoogleMarkerlessTypes for hashicorp/google, and the untaggable members of AzureServerMintedTypes for azurerm (GitHub issue #2205). The first two are generated by different tools from different evidence and read the same way.

func MarkerlessReasonFor

func MarkerlessReasonFor(typeName string) string

MarkerlessReasonFor is the ruling that put typeName in the markerless veto, worded for the provider it belongs to: GoogleMarkerlessReason for a GoogleMarkerlessTypes member, MarkerlessReason otherwise.

func NormalizeDocName

func NormalizeDocName(s string) string

NormalizeDocName is [normalizeDocName] for the generator that writes the names this package matches. It is exported for one caller and one purpose: tools/row-gen's TestNormalizeDocNameMatchesTheGenerator asserts the three copies of this reduction agree. A drift between them would be invisible otherwise - the generator would emit names run time could never match, and the whole route would silently reach nothing with every other test still passing.

func NotContext

func NotContext(schemas SchemaSet, typeName string, schema TypeSchema, names []string) []string

NotContext is the names, in the order given, that IsContextAttr says are not context.

func NotImportable

func NotImportable(resourceType string) bool

NotImportable is issue #331's veto, asked as one question by every admission route rather than re-implemented at each of them.

A type here has no classic Importer at all - see NotImportableReason, which tools/survey-gen derives by calling ImportResourceState against the pinned provider, not by inferring it from a schema. That is the fact no other signal carries: a wire identity schema, a taggable marker path and a recordable `id` can each be perfectly correct while the very first ImportResourceState a later run makes answers "resource ... doesn't support import". Admitting such a type is the trade this fork is forbidden to make - a plan refusal traded for an apply refusal - and it is worse than the usual form of that trade, because the apply that fails is not the first one. The first apply creates the object and records it; every plan after that fails permanently, with the object already live.

Why this is a function and not three lookups

The roster arrived (2026-08-20) wired into internal/live/lint's admitted() alone, and an audit the following night found at least two further routes that reach admission without passing through it. The map is easy to read from anywhere and that is exactly the problem: a fourth route added later would look complete without it. So the rule is stated once, here, and the routes call it:

  • the schema fallback, [synthesizeTypeIdentity], which is what internal/live/lint's admitted(), [resolver.lookupType] and internal/live/liveimport's admittedByProviderSchema all ask; and

  • [LocatedType], the record-located route, which asks no schema fallback at all and so has to consult this itself.

live/notimportable_routes_test.go holds the two together by measuring every route against this predicate over the whole provider surface.

Table wins, as it does for the markerless veto

A type with a ratified row is a batch's explicit, evidenced decision, and this predicate must not contradict one that is still standing; retracting such a row is tools/row-gen -emit's job (it retracted two when the roster was first derived). The two sets are disjoint today and this guard is what keeps the ordering safe if that ever stops being true.

func ParentByConvention

func ParentByConvention(attr, self string, parents map[string]bool, service ServiceOf) (string, bool)

parentByConvention reports the admitted type an argument name follows the AWS foreign-key convention for: strip a trailing _id, _arn or _url (or leave the name whole, for the bare-noun style "bucket" and "role" use), then look for the admitted type whose own name is exactly "aws_" plus what is left, or - failing that - ends with an underscore plus it, shortest name winning when several do (the same tie-break tools/survey-gen/classify.go's parentRef uses over the whole provider roster - rule 2 of ParentOf's doc comment).

Unlike [parentByTypeName]'s prefix chain, the types an argument name can match this way are not hierarchically related to each other at all - an argument named "group" matching aws_iam_group, aws_security_group and aws_eks_node_group is three unrelated coincidences of the same English word, not three ancestors of increasing specificity. So the search for "what does this argument name" runs over every admitted type, eligible or not, and picks the single best (shortest, most specific) match on that question alone; only once that one type is fixed does eligibility decide whether the answer is usable. Requiring eligibility during the search itself, as [parentByTypeName] does, would let this rule silently accept a same-suffix stranger whenever the true target is ineligible - concretely, aws_iam_group_policy_attachment's own "group" argument names aws_iam_group (untaggable: IAM has no TagGroup API), and reports no parent link on that argument at all, never aws_security_group or aws_eks_node_group, which only happen to share the "_group" suffix. See [TestParentOfRefusesUnrelatedSuffixMatch].

ParentByConvention is this exact function, exported for a caller that needs the convention alone, decoupled from ParentOf's own restriction to a type whose Components describe its IDENTITY (ParentOf refuses a TypeIdentity.ServerAssigned type outright, since such a type's identity does not derive from arguments at all - found via corpus-ecs-fargate's day2_remove unit: aws_vpc_security_group_egress_rule is ServerAssigned (EC2 assigns its sgr-… id), so ParentOf never names its own security_group_id argument as a link to aws_security_group, even though the SAME convention this function already applies would find it - and a DEPENDENCY question ("which live parent must this be destroyed before") is a different question than an IDENTITY one, answerable from the provider's own schema argument names alone, with no ratified Components row required. See internal/live/discovery's destroyParentDependency, which reaches every type ParentOf can link this way for a record-backed identity's own parent-ordering need; a ServerAssigned type needs the identical ordering for the identical reason (its own live object cannot outlive a parent this run also destroys) but has no Components row to read a link from, only its provider schema's own argument names.

func ParentReadRemovable

func ParentReadRemovable(typeName string) bool

ParentReadRemovable reports whether a parent read for typeName may also remove the child it finds, rather than only report it. False for every type outside the small set above, including one where SingleParentComponent is true - being fully identified by the parent is necessary for removal and is not, on its own, sufficient.

func SameServiceAffinity

func SameServiceAffinity(child, candidate string, service ServiceOf) bool

SameServiceAffinity reports whether a candidate parent is close enough to the child for a name-convention match to be trusted (issue #129).

Exported because tools/survey-gen needs the identical test (#167) and two copies of this rule is how the repository got here: parentByConvention and survey-gen's parentRef were the same shortest-name heuristic, one of them documented as harmless prose, and both wrong in the same way.

The CloudFormation service is the real test, and it is the one that has to be tried first, because it is the only one that gets aws_volume_attachment -> aws_ebs_volume right: AWS::EC2::VolumeAttachment and AWS::EC2::Volume are one service, while the Terraform names share no prefix at all. A rule built on the Terraform prefix alone would drop that link and then need an exception entry to get it back, which is the shape this whole change exists to avoid.

When either side is unmapped - 44 of the untaggable roster's children are, and every logical type is - the Terraform prefix is what is left. It is weaker but not weak: it still refuses aws_autoscaling_group -> aws_api_gateway_domain_name, and it still lets aws_prometheus_alert_manager_definition find aws_prometheus_workspace instead of the shorter aws_grafana_workspace.

func SchemaReproducesRow

func SchemaReproducesRow(row, synthesized TypeIdentity) bool

SchemaReproducesRow reports whether an entry synthesized from the provider's identity schema builds the same identity a hand-written row does. PreferSynthesized is the call that decides which one to use.

Types

type AccountSource

type AccountSource interface {
	AccountID(ctx context.Context) (string, error)
}

AccountSource is how an engine supplies the AWS account ID its provider runs as, for a translation that needs it (CloudControlID.NeedsAccount). The engine decides where the ID comes from and which endpoint it may call; this package never calls one.

type AccountSourceFunc

type AccountSourceFunc func(ctx context.Context) (string, error)

AccountSourceFunc is a function as an AccountSource.

func (AccountSourceFunc) AccountID

func (f AccountSourceFunc) AccountID(ctx context.Context) (string, error)

AccountID calls f.

type Admission

type Admission string

Admission says what admits a type to the identity table with no hand-written row: the provider's schemas on their own, or the schemas plus a configuration that answers the one question they leave open.

const (
	// AdmitSchema: the strict rule settles it. Every attribute the provider
	// requires for import is a *required* argument of the type, so no
	// configuration can decline to name the object. See [Derivable].
	AdmitSchema Admission = "schema"

	// AdmitConfigSignal: the schemas leave it open and a configuration
	// closes it. Every attribute required for import is a settable argument
	// of the type, and every instance in the configuration sets every one
	// of them. See [ConfigSignal].
	AdmitConfigSignal Admission = "config-signal"

	// AdmitNeedsConfigSignal: the schemas leave it open and no
	// configuration here answers. The type is a candidate, and the
	// candidate's terms are known: a configuration that sets these
	// arguments admits it, one that does not leaves it to discovery.
	AdmitNeedsConfigSignal Admission = "needs-config-signal"

	// AdmitConfigDeclined: the schemas leave it open and the configuration
	// says no. Some instance sets none of the identity arguments, or only
	// some of them, so the cloud names these objects and marker discovery
	// is what finds them.
	AdmitConfigDeclined Admission = "config-declined"
)

type Argument

type Argument struct {
	Required bool
	Optional bool
	Computed bool
}

Argument is what this package reads of one resource schema attribute.

type AzureServerMinted

type AzureServerMinted struct {
	// Mints is the part of the id the server supplies, in #2147's words.
	Mints string
	// Taggable: the type's `tags` argument is the ARM tags map, so the
	// ownership marker can be written and a listing can find the object.
	Taggable bool
	// DataPlane: the object lives on a data plane Resource Graph does not
	// index, so marker discovery needs a data-plane listing.
	DataPlane bool
}

AzureServerMinted is one roster entry.

type CloudContext

type CloudContext struct {
	// AccountID is the AWS account ID the run is against.
	AccountID string

	// Region is the region the run is against.
	Region string

	// SubscriptionID is the Azure subscription the run's environment gives
	// a provider block that names none: ARM_SUBSCRIPTION_ID, which azurerm
	// and azapi both read when their block leaves subscription_id unset
	// (GitHub issue #2133, finding 4). The caller fills it from
	// pkg/azsweep.EnvSubscriptionID, the function the Resource
	// Graph sweep's own environment fallback is, so the two cannot read the
	// environment differently. [resolver.cloudValueFor] consults it only
	// for a provider block that does not set subscription_id at all; a
	// block that sets it, statically or not, is never overridden by it,
	// as neither provider overrides it.
	SubscriptionID string

	// Environment is the Azure cloud the run's environment gives a provider
	// block that names none: ARM_ENVIRONMENT, or "public" when that is
	// unset too, which is the default both providers apply. The caller
	// fills it from pkg/azsweep.EnvEnvironment, which answers ""
	// when ARM_METADATA_HOSTNAME names a metadata host (the cloud is then
	// whatever that host serves, which nothing here reads). Like
	// SubscriptionID it is consulted only for a block that does not set
	// `environment` at all (GitHub issue #2206).
	Environment string
}

CloudContext is the set of cloud properties a caller can supply so that identities embedding them become computable. Both fields are optional; an empty one means "not known", never "empty string".

This is the whole of the escape hatch, and its shape is what keeps this package cloud-free and process-free (see the package documentation). Nothing here is discovered: the values arrive from a caller that already has them, and a caller that has none passes the zero value and gets [ClassNeedsDiscovery] for the types that need them rather than an error.

Where the values come from in this fork, when they come from anywhere: the region is the provider configuration's own, which internal/live/discovery.Request.Region already carries and which every signed request the list clients make must have. The account ID first becomes knowable one phase later still - the AWS provider puts an account_id attribute in the resource identity objects it attaches to list results, which internal/live/discovery.TypeScan.AccountID records as the scan runs. Both are therefore behind the provider, and identity resolution runs in front of it, which is why the pipeline as it stands passes the zero value and the two account-derived types below reach their live resources through their markers instead.

func (CloudContext) Value

func (c CloudContext) Value(which CloudValue) (string, bool)

Value returns the context's value for one cloud property, and whether the caller supplied it. An empty value is reported as not supplied.

type CloudControlID

type CloudControlID struct {
	// contains filtered or unexported fields
}

CloudControlID turns one type's Cloud Control identifiers into its Terraform import ids. A nil *CloudControlID is valid and translates nothing: every method on it reports that no model, account or translation is needed.

func CloudControlTranslation

func CloudControlTranslation(tfType, cfnType string, primary []string) (t *CloudControlID, reason string)

CloudControlTranslation is how tfType's Cloud Control identifiers become Terraform ids, for tfType listed through cfnType whose primary identifier properties are primary, in the registry's order. When there is none, the translation is nil and reason says why; the caller leaves the type uncovered with that reason.

func (*CloudControlID) Describe

func (t *CloudControlID) Describe() string

Describe names the id the translation produces, for an error message.

func (*CloudControlID) ModelProperty

func (t *CloudControlID) ModelProperty() string

ModelProperty is the model property a written-out translation reads the id from (an ARN the list or read handler returns), or "" when it reads none.

func (*CloudControlID) NeedsAccount

func (t *CloudControlID) NeedsAccount() bool

NeedsAccount reports whether the translation needs the account ID the provider runs as (CloudControlID.WithAccount, CloudControlID.ResolveAccount).

func (*CloudControlID) ResolveAccount

func (t *CloudControlID) ResolveAccount(ctx context.Context, src AccountSource) (*CloudControlID, error)

ResolveAccount is t with the account ID read from src when t needs one, and t itself otherwise; src is not called then. An error from src is returned as it is, so the engine can tell its own kinds apart (plunge's skipped listing).

func (*CloudControlID) Translate

func (t *CloudControlID) Translate(identifier string, props map[string]any) (id string, ok bool)

Translate is the Terraform id of one listed object: identifier is its Cloud Control identifier, props its model. ok is false when the identifier does not have the parts the translation reads or the model lacks a property it reads.

func (*CloudControlID) WantsModel

func (t *CloudControlID) WantsModel(identifier string, props map[string]any) bool

WantsModel reports whether the caller should read the resource (Cloud Control GetResource) before translating identifier, because the list handler's model props lack what the translation reads.

func (*CloudControlID) WithAccount

func (t *CloudControlID) WithAccount(account string) *CloudControlID

WithAccount is t with the account ID its translation reads.

type CloudValue

type CloudValue string

CloudValue names one property of the cloud a run is against, for a Component whose value the configuration does not carry.

The set is deliberately tiny and closed. It covers the two substitutions the AWS provider's account-embedded import identities need - an SQS queue URL (https://sqs.REGION.amazonaws.com/ACCOUNT/NAME) and an SNS topic ARN (arn:aws:sns:REGION:ACCOUNT:NAME) - and the two more hashicorp/google's id formats need beside the region, its project and its zone (#1119). A third kind is a decision to make deliberately, not a slot to fill: the partition ("aws", "aws-cn", "aws-us-gov") is written as a literal in the templates below because every one of those identities is partition-specific in more places than the partition segment, and a run against a non-commercial partition needs a review rather than a substitution.

const (
	// CloudNone is the zero value: this component reads no cloud property.
	CloudNone CloudValue = ""

	// CloudAccountID is the AWS account ID the run is against, twelve
	// digits, no hyphens.
	CloudAccountID CloudValue = "account-id"

	// CloudRegion is the region the run is against, e.g. "us-east-1".
	CloudRegion CloudValue = "region"

	// CloudSubscriptionID is the Azure subscription the run is against, a
	// GUID (GitHub issue #1120). It is a third kind rather than
	// [CloudAccountID] because the two are answered from different places:
	// an AWS account is never stated in the configuration and comes only
	// from the [CloudContext], while azurerm and azapi take the subscription
	// as the provider block's own subscription_id, which
	// [resolver.cloudValueFor] reads statically the way it reads a
	// provider block's region. Reusing the account value would either answer
	// an AWS account from a provider argument the AWS provider does not have,
	// or describe an Azure subscription as an AWS account ID in the one
	// sentence an operator reads. The spelling is tools/azurerm-id-gen's,
	// so live/azurerm-ids.json's components carry it unchanged.
	CloudSubscriptionID CloudValue = "subscription-id"

	// CloudProject is the Google Cloud project a resource is created in
	// when it does not name one itself: the google provider's own
	// `project` argument. hashicorp/google's ReplaceVars substitutes it
	// into every id format carrying {{project}} the same way, through
	// tpgresource.GetProject, so a row built from that id format carries
	// the same fallback. See [resolver.cloudValueFor] for where a run
	// reads it.
	CloudProject CloudValue = "project"

	// CloudZone is the google provider's own `zone` argument, the fallback
	// tpgresource.GetZone gives an id format carrying {{zone}}.
	CloudZone CloudValue = "zone"

	// CloudLocation is what tpgresource.GetLocation falls back to on a
	// type with no region or zone argument of its own: the google
	// provider's `region`, or its `zone` when it states no region. The
	// hashicorp/google IAM types read their parent's location this way.
	CloudLocation CloudValue = "location"

	// CloudEnvironment is the Azure cloud the run is against: "public",
	// "usgovernment" or "china" (GitHub issue #2206, the maintainer's
	// ruling of 2026-10-23 on #2201 item 5). It is a provider-block value
	// like [CloudSubscriptionID]: azurerm and azapi take it from their
	// block's `environment`, or ARM_ENVIRONMENT when the block sets none,
	// and "public" when neither does. Data-plane ids are URLs whose host
	// is an object's name plus the cloud's DNS suffix, so the suffix
	// values below are all answered from this one.
	CloudEnvironment CloudValue = "environment"

	// The DNS suffixes of the data-plane hosts azurerm names objects by
	// (#2206): "https://{vault}." + CloudKeyVaultDNSSuffix, and the like.
	// Each is answered by [AzureDNSSuffix] from the [CloudEnvironment].
	CloudKeyVaultDNSSuffix         CloudValue = "key-vault-dns-suffix"
	CloudManagedHSMDNSSuffix       CloudValue = "managed-hsm-dns-suffix"
	CloudAppConfigurationDNSSuffix CloudValue = "app-configuration-dns-suffix"
	CloudStorageDNSSuffix          CloudValue = "storage-dns-suffix"
)

func AzureSuffixService

func AzureSuffixService(which CloudValue) (CloudValue, bool)

AzureSuffixService reports whether which is one of the Azure DNS suffix cloud values (CloudKeyVaultDNSSuffix and its siblings), returning it unchanged when it is.

func (CloudValue) Describe

func (c CloudValue) Describe() string

Describe renders a cloud property for an operator-facing sentence.

type Component

type Component struct {
	// Literal is a fixed fragment of the import ID, used for separators. On
	// a component whose Attrs is empty, Literal is the component's whole
	// contribution. On a component whose Attrs is also set, Literal instead
	// prefixes the resolved argument value in the import-ID string only -
	// see OmitIfAbsent below for why a segment needs its own separator
	// glued to it rather than a standalone literal component before it.
	// Never the identity-attribute formula: an aws_lambda_permission
	// qualifier's identity value is "qualifier_name", not ":qualifier_name"
	// - the colon is punctuation in the STRING, not part of the ATTRIBUTE.
	Literal string

	// Attrs names the resource arguments to read, in preference order. The
	// first one present in configuration supplies the value; if none is
	// present, resolution fails naming all of them - unless Default or
	// OmitIfAbsent below says what omission means.
	Attrs []string

	// Block names a singular (max_items: 1) nested list block Attrs is read
	// from, instead of the resource's own top-level body. Empty means Attrs
	// names top-level arguments, the ordinary case.
	//
	// aws_autoscaling_traffic_source_attachment is the type this exists for
	// (GitHub issue #310): its documented import ID is
	// autoscaling_group_name,traffic_source_type,traffic_source_identifier,
	// and the provider's own schema (verified against `terraform providers
	// schema -json` at 6.59.0) confirms every one of those three components
	// is a required, client-specified value - but the second and third are
	// not top-level arguments. They are the `type` and `identifier`
	// attributes of a required, max_items:1 `traffic_source` list block, so
	// {Block: "traffic_source", Attrs: []string{"type"}} is what reads them.
	//
	// This is deliberately narrow, matching [resolver.identityArgs]'s own
	// "everything else in the body, including nested blocks, is ignored"
	// rule for the ordinary case: only a component that names Block reaches
	// into one, and it reaches into exactly the one block it names, so no
	// row that leaves this empty (every row before #310) changes behavior at
	// all. The resolver reads the block's FIRST instance only - correct for
	// a schema that caps it at one, and the same "safe direction" the rest
	// of this package takes when a provider-side invariant the schema
	// enforces makes a further check redundant.
	//
	// A leaf name has to be unique across every Block this type's Components
	// use and the type's own top-level arguments; nothing here namespaces
	// one nested block's "type" apart from another's or from a same-named
	// top-level argument. No committed row needs that yet, so it is a
	// documented constraint rather than an enforced one - the same tradeoff
	// [Component.Attrs] itself makes for a duplicate name across components.
	Block string

	// Path selects INSIDE the argument Attrs names before it is resolved:
	// each step is a key of the object the argument is written as, applied
	// in order (GitHub issue #1079). kubernetes_manifest is the type this
	// exists for: its whole object is one dynamic `manifest` argument, and
	// the natural key that identifies it - apiVersion, kind,
	// metadata.namespace, metadata.name - is four keys inside that one
	// argument's object constructor, so {Attrs: []string{"manifest"}, Path:
	// []string{"metadata", "name"}} is what reads the name.
	//
	// The selection is syntactic, through [resolver.selectStaticExpr]: an
	// object constructor, a merge() of them, or a local or variable defined
	// as one is walked key by key without evaluating the whole manifest,
	// and the leaf expression found there is then resolved exactly as a
	// top-level argument's would be. A manifest that is not written out
	// that way - yamldecode(file(...)), a module output, a for expression -
	// has no key to walk and is refused as not resolvable from
	// configuration, never guessed at by evaluating it. Empty on every row
	// before #1079 and on every hand-written row, so nothing else changes.
	Path []string

	// OmitIfAbsent is true when this component is genuinely optional in the
	// provider's own grammar: not a missing identity, and not a documented
	// substitute value (that is Default), but a segment - and the Literal
	// it carries as a prefix, if any - that the import ID simply does not
	// have when none of Attrs is set.
	//
	// aws_lambda_permission is the archetype. The provider documents two
	// import forms (aws 6.59.0's Import section):
	//
	//	FUNCTION_NAME/STATEMENT_ID
	//	FUNCTION_NAME:QUALIFIER/STATEMENT_ID
	//
	// An unqualified permission's import ID has no colon and no qualifier
	// segment at all - it is not that the segment is present and empty.
	// That is spelled as a component with Attrs: []string{"qualifier"},
	// Literal: ":", OmitIfAbsent: true, immediately after the function_name
	// component: present, it contributes ":" + the qualifier value; absent,
	// it contributes nothing, including its own colon, and the ordinary
	// unqualified "/"-joined form falls out unchanged.
	//
	// Without this, the only components attrs=["qualifier"] could resolve
	// through are Default (which asserts a documented substitute value -
	// wrong here, since an unqualified permission has no qualifier at all,
	// not a default one) or the hard refusal every other missing component
	// gets, which would make every unqualified aws_lambda_permission
	// unimportable. Unlike Default and ServerAssignedIfAbsent, an absent
	// OmitIfAbsent component supplies no value to addTo either: an omitted
	// qualifier is not an empty-string qualifier, so no identity-attribute
	// formula should claim one.
	//
	// Absence is not only syntactic. terraform-aws-modules' own S3
	// notification module writes aws_lambda_permission.allow's qualifier as
	// `try(each.value.qualifier, null)` - the argument line IS present, so
	// [resolver.resolveInstance]'s attr==nil branch never sees it, but a
	// for_each instance whose map entry carries no "qualifier" key
	// statically evaluates that expression to null all the same.
	//
	// The resolver does not evaluate such a component's raw HCL expression
	// itself to check - a generic evaluator has no way to know that
	// `each.value.qualifier` is absent-on-this-instance rather than
	// not-yet-known, and resolveFallbackChain's own try()-arm selection
	// (fallback.go) already answers exactly that question using the
	// resource's real expansion. So the resolver instead runs the FULL,
	// ordinary resolution machinery on the component - the same
	// resolveExpr call every other component goes through, whatever shape
	// the author wrote: try(), a conditional, a bare literal - and only
	// afterward asks what it concluded. When resolution fails for exactly
	// one reason, a clean and wholly-known null (stringValue's "Null
	// identity argument", the same diagnostic the name/name_prefix
	// redirect above exists to avoid for the identical reason), the
	// resolver treats it exactly like a syntactically absent argument.
	// Any other failure - a truly dynamic value, an unresolvable reference,
	// the shape aws_lambda_permission's own qualifier argument takes just
	// as often when it names a live alias's version - stands and still
	// refuses through the ordinary path, because "not known until apply"
	// is a true statement about it and "absent" is not.
	//
	// A google row uses it with no Literal, between two standalone "/"
	// separator components, and then the omission is an EMPTY segment
	// rather than a missing one: stock's ReplaceVars renders an unset
	// optional argument as "", so a google_compute_global_network_endpoint
	// with only ip_address set has the id "p/neg/10.0.0.1//80". The
	// generator emits that only where the argument is optional with no
	// schema default AND every import format the provider's importer
	// accepts captures it with `[^/]*`, so the empty segment parses back
	// (#2145 item 6); see tools/gcp-row-gen's emptySegment.
	OmitIfAbsent bool

	// AtLeastOneOf names the arguments the provider's schema requires at
	// least one of (its AtLeastOneOf), when this component is one of them
	// and is [Component.OmitIfAbsent]. An instance that leaves every one of
	// them unset is refused with "Identity argument not set" naming all of
	// them, as a required argument left out is, rather than rendered with
	// every segment empty: the provider's own validation rejects that
	// configuration. Only components of the same entry are consulted.
	// Empty everywhere except the google network endpoint rows (#2145 item
	// 6), so no other row changes behavior.
	AtLeastOneOf []string

	// Default is the literal value this component contributes when none of
	// Attrs is set in the configuration, for the arguments whose omission
	// the provider itself documents as selecting a server-side default: an
	// omitted event_bus_name means the "default" event bus, and the Import
	// section says so in words ("if you omit `event_bus_name`, the
	// `default` event bus will be used" - scraped into import-grammar's
	// omitted_fallbacks field). The empty string keeps the standing rule:
	// an unset identity argument refuses resolution. A component carrying
	// one still supplies its identity attribute - under the first Attrs
	// name, since no argument was chosen per instance - so identity-object
	// import works the same whether the configuration wrote the default or
	// left it out.
	Default string

	// ServerAssignedIfAbsent is true when the provider itself documents
	// that omitting every name in Attrs is not an absence of identity but
	// a request for one: the provider assigns a fresh value - usually a
	// random, unique one - at create time, the same convention
	// [TypeIdentity.ServerAssigned] already gives a type-wide resolution
	// class to. Component narrows that convention to a single argument
	// ("If omitted, Terraform will assign a random, unique name" on an
	// otherwise ordinarily-identified type, not a type whose identity is
	// server-assigned outright), so a component whose Attrs are all unset
	// classifies [ClassNeedsDiscovery] instead of refusing outright - see
	// [resolver.identityArgs]'s attr-nil branch. Unlike Default, which
	// supplies a known literal, this supplies no value at all: the
	// resolver still cannot build the import ID, it can only say why not
	// yet. Derived by tools/row-gen/emit.go from
	// tools/importdocs-gen's Argument Reference scrape
	// (ArgumentRefEntry.ServerAssignedIfAbsent) for every ratified row, the
	// same way every other field in this file is - see #190.
	ServerAssignedIfAbsent bool

	// Cloud names a value that comes from the cloud the run is pointed at
	// rather than from the configuration: the AWS account ID, the region.
	// [CloudNone] - the zero value - means this component is a literal or an
	// argument.
	//
	// A component like this is why [CloudContext] exists. Nothing in a
	// configuration says which account it will be applied to, so an identity
	// that embeds the account is not computable from configuration alone,
	// and this package will not read it from anywhere: it is handed the
	// values or it says it does not have them. See [ResolveIn].
	//
	// Cloud and Attrs are not exclusive, and a component setting both is the
	// ordinary case rather than a contradiction: the provider documents an
	// argument whose omission means "this cloud property" and whose presence
	// means something else entirely ("If omitted, this defaults to the AWS
	// Account ID" on a Glue catalog_id, which is how a configuration points
	// at another account's Data Catalog). Attrs then wins whenever the
	// configuration sets it and Cloud is the fallback, decided per instance
	// by [resolver.cloudComponentAttr]. The field is filled in by
	// tools/row-gen's mergeCloudDefault from the provider's own Argument
	// Reference rather than ratified by hand, the same way
	// ServerAssignedIfAbsent is.
	Cloud CloudValue

	// IdentityAttr names the attribute of the provider's resource identity
	// schema that this component supplies, and is the inference the schemas
	// themselves do not carry: an identity schema says a route is identified
	// by a route_table_id, not that the route_table_id argument is where
	// that value is written.
	//
	// Three values, and the helpers below spell all three:
	//
	//   - [SameNameIdentity], what [attr] sets, is the ordinary case: the
	//     identity attribute is whichever argument supplied the value, under
	//     its own name. It is a rule rather than a name because the argument
	//     is chosen per instance - aws_route reads whichever of three
	//     destination arguments the route uses, and the provider's identity
	//     schema names all three.
	//   - An explicit attribute name, what [inAttr] sets, for the case where
	//     the two differ. Several components may name one attribute, and
	//     their rendered strings then concatenate in order to form it, which
	//     is how an SNS topic's single "arn" is built out of a literal
	//     prefix, the region, the account and the topic's name.
	//   - Empty: this component supplies no identity attribute at all. That
	//     is every separator between two identity attributes - exactly the
	//     character that stops mattering once a run imports by identity
	//     object - and it is what [idlessAttr] says about an argument whose
	//     value is part of the import string and part of no identity.
	//
	// An entry whose components do not supply every attribute the provider
	// requires for import cannot be imported by identity, and its import-ID
	// string is used instead. aws_route_table_association is the archetype:
	// the provider identifies an association by the rtbassoc- ID it assigns,
	// and the table builds the association's documented import string out of
	// a subnet and a route table. Both are right about different things, and
	// only one of them is an identity object.
	IdentityAttr string

	// SoleElement narrows this component's value before every other rule in
	// this struct's comments applies: when the argument's own expression is
	// a static list or set construct, resolution requires it to carry
	// exactly one element and uses that element's value in place of the
	// whole collection. A scalar-typed argument (another name in the same
	// Attrs list may be scalar, e.g. a mutually exclusive sibling) passes
	// through unchanged - the flag only ever narrows a collection, never
	// touches a value that already is not one.
	//
	// It exists for identities the provider documents as one segment per
	// value in a list-typed argument (aws_security_group_rule's import ID
	// appends one token per entry of whichever of cidr_blocks,
	// ipv6_cidr_blocks or prefix_list_ids is set), where cty's own convert
	// package refuses a list-to-string conversion unconditionally,
	// regardless of length (verified: even a one-element
	// cty.ListVal/cty.SetVal fails convert.Convert(..., cty.String) with
	// "string required, but have list/set of string"). The common,
	// documented single-value case - one CIDR, one prefix list ID - is
	// exactly a one-element list, so unwrapping it is not a guess about
	// which element to keep.
	//
	// More than one element refuses outright rather than picking one: the
	// AWS API, not the configuration's own list order, decides how such a
	// rule's multiple sources compose into (or split across) real
	// SecurityGroupRule objects (the provider's own docs: "Not all rule
	// permissions... need to be imported"), so no configuration-only
	// answer for the segment order would be honest. A non-static list (a
	// variable or function call, rather than a literal `[...]` or `[ref]`
	// construct written in configuration) refuses the same way, for the
	// same reason [resolver.isSymbolic] and [resolver.evalStatic] already
	// refuse other non-static identity arguments elsewhere in this
	// package.
	//
	// Two or more Attrs genuinely non-empty AT ONCE (GitHub issue #384) is
	// the alternation's own name proven wrong for that instance - "sole"
	// promises exactly one is ever populated - and resolver.
	// firstApplicablePresent's conflict return is what this main path does
	// about it: refuse, the same as the zero-element case, rather than
	// pick the first candidate silently. Where the type is also ratified
	// in [VariadicTrailingImportIDTypes], resolver.recordFallback gets a
	// second chance first: [resolveDocumentedImportID]'s variadic tail can
	// record the whole family - one token per element of every Attrs
	// member the instance actually sets, in this list's own order - as a
	// documented import string, dropping the instance to the record rung
	// instead of refusing it outright. That is a fact about the DOCUMENTED
	// IMPORT ID machinery, not about this field: SoleElement keeps meaning
	// exactly what it says above for every other reader of this table.
	SoleElement bool

	// PerElement is [SoleElement]'s opposite number, for the identities the
	// provider documents as one segment PER value of a collection-typed
	// argument rather than one segment total. aws_iam_user_group_membership
	// is the archetype: `user1/group1/group2` is a scalar user followed by
	// one segment for every member of the `groups` set, so the number of
	// segments is a property of the configuration and not of the type.
	//
	// The elements are joined by the separator this component's immediate
	// predecessor supplies - a component whose Attrs are empty and whose
	// Literal is the separator, which is how every other multi-segment row
	// in the table already spells one. That predecessor emits the separator
	// that precedes the FIRST element; this component emits the rest.
	//
	// # Why the elements may be sorted
	//
	// A set has no order, so the configuration's own spelling of `groups`
	// cannot be the answer on its own: two configurations differing only in
	// the order of that list are the same configuration, and must produce
	// the same marker. Sorting supplies the missing determinism, and it is
	// sound here because it changes nothing the provider will see. The
	// provider's schema declares groups as set(string) (aws 6.59.0, verified
	// against `terraform providers schema -json`), so the import ID's tail
	// segments are parsed straight back into a set: any permutation of them
	// yields the identical value, and a plan taken after importing one
	// permutation is the plan taken after importing any other. Order is
	// therefore free to be chosen, and this package chooses the one that is
	// stable across runs.
	//
	// # The all-or-nothing rule
	//
	// Sorting needs a key for every element, and an element that waits on a
	// live value has none: its rendered form is not known until apply, so
	// where it belongs in a sorted sequence is not known either. Sorting the
	// ones that do have keys and leaving the rest where they fell would
	// invent an order that is neither the configuration's nor a canonical
	// one. So the rule is all-or-nothing - canonicalise only when EVERY
	// element yields a key, and otherwise leave the configuration's order
	// untouched. A partial answer would mean "I do not know how to
	// canonicalise this", and the honest way to say that is to not do it.
	//
	// The rule is borrowed from the sibling project chant, which met the
	// same question about the same shape.
	//
	// # What IdentityAttr means on a component like this
	//
	// The rendered value is several segments joined by the separator, and no
	// set-typed attribute has a string for its value, so a PerElement
	// component naming an identity attribute contributes something the
	// provider's identity object could not accept as-is. That is inert
	// today - aws_iam_user_group_membership has no identity schema in aws
	// 6.59.0, so nothing builds an identity object for it and the import-ID
	// string is the whole answer - and the row keeps [SameNameIdentity] for
	// the same reason every other IAM attachment row does: the attribute
	// name is what the import string is built out of. A provider release
	// that gives this type an identity schema is the point at which the
	// question becomes real, and [checkIdentity] is what would raise it.
	PerElement bool

	// ARMTypeSegment is true on the one component of azapi_resource's
	// identity that reads its `type` argument (GitHub issue #1120): the value
	// is an ARM resource type with an API version
	// ("Microsoft.Network/virtualNetworks/subnets@2024-05-01"), and what the
	// component contributes is not that value but the text azapi puts
	// between parent_id and name when it builds the object's id. The rule is
	// [armTypeSegment]'s, read from azapi's own builder. The value has to be
	// known from configuration alone: the joining text depends on how many
	// segments the type has, so a type only a parent's live value supplies
	// is refused rather than rendered later.
	ARMTypeSegment bool

	// ARMSegmentIndex, when positive, says the argument holds an Azure ARM
	// id and this component contributes one path segment of it: the one at
	// position ARMSegmentIndex of the id split on "/" (the id's leading "/"
	// makes position 0 the empty string, so "/subscriptions/s/resourceGroups/g
	// /providers/Microsoft.KeyVault/vaults/kv" has its name at 8).
	// ARMSegmentAfter is the text of the segment before it ("vaults"),
	// compared without regard to case: a value of any other shape is refused
	// by name, never misread. The Azure data-plane ids are the users (GitHub
	// issue #2206): a Key Vault's host is the name segment of key_vault_id
	// and the cloud's suffix. A value only an apply can know, a reference to
	// the parent's id, is carried as that reference with the same two steps
	// applied to it (split on "/", element ARMSegmentIndex), so the id
	// renders once the parent's id is known.
	ARMSegmentIndex int
	ARMSegmentAfter string

	// URLEscape is the escaping azurerm applies to this argument's value
	// when it formats a URL id (GitHub issue #2206): "path" is
	// url.PathEscape, which App Configuration puts on a key, and "query"
	// is url.QueryEscape, which it puts on a label. Empty, the ordinary
	// case, is the value as written. A literal value is escaped here; a
	// value only an apply can know is refused by name, since the escape
	// reads its text and a formula cannot carry it.
	URLEscape string

	// LongForm names a rewrite the provider applies to this argument's
	// value before it becomes part of the id (#2144). Empty, the ordinary
	// case, means the value is substituted as written, which is what the
	// provider does too.
	//
	// Set, resolution composes the provider's own rewrite: see
	// [LongFormRule] for each kind. A value only an apply can know is
	// refused by name, since the rewrite reads the value's text and a
	// formula cannot carry it. Written by tools/gcp-row-gen from the
	// pinned provider source.
	LongForm LongFormRule

	// Qualifiers are the regexes the provider matches a [LongFormIAMParent]
	// or [LongFormComputedName] value against, in the provider's order and
	// spelled as its source spells them. For [LongFormTrimPrefix] it is the
	// one literal prefix trimmed, for [LongFormEnsurePrefix] the one added. Empty on a LongForm component,
	// the generator could not read the list, and a value that needs one
	// refuses the instance by name.
	Qualifiers []string
}

Component is one piece of an import identity: a fixed separator (Literal, with Attrs empty and Cloud unset), a resource argument (Attrs, first present wins), or a property of the cloud the run is against (Cloud).

func Attr

func Attr(names ...string) Component

Attr is the component a row writes for an argument that names the instance under its own name: it reads the first of names the configuration sets, and supplies the identity attribute of that same name.

func InAttr

func InAttr(identityAttr string, c Component) Component

InAttr is c, supplying the named identity attribute rather than the one its own argument name implies. Several components wrapped in the same name concatenate into it, in order.

func (Component) IdentityAttrFor

func (c Component) IdentityAttrFor(argName string) string

IdentityAttrFor is the identity attribute this component supplies for one instance, given which of its alternative arguments (argName) the configuration actually set. The empty string means it supplies none.

type ContentMatchBinding

type ContentMatchBinding struct {
	// Argument is the resource's own top-level configuration argument to
	// read and match on - "name" for every type admitted through this
	// mechanism today.
	Argument string

	// CFNType is the CloudFormation Registry type Cloud Control lists to
	// find candidates, e.g. "AWS::CloudFront::CachePolicy".
	CFNType string

	// PropertyPath is the listed candidate's own Properties path to the
	// matching value, in Cloud Control's own nesting: a one-element path
	// for a top-level property, a two-element path
	// ["CachePolicyConfig", "Name"] for one wrapped in the resource's
	// single mutable-config object - the shape every type admitted through
	// this mechanism today has.
	PropertyPath []string
}

ContentMatchBinding names issue #272's discovery leg for a type this package treats as TypeIdentity.ServerAssigned: the provider mints the identity, so [Resolve] always classifies [ClassNeedsDiscovery], but the type carries no tags argument, so no ownership marker can be written on it either. Ordinarily that conjunction is what MarkerlessTypes vetoes - there is no way to find the object again after creating it. A type here is the one exception: its identity-bearing argument is proven unique within the account by two independent sources agreeing (the provider's own Argument Reference and the CloudFormation Registry's property description - see tools/row-gen's two-source derivation), so matching a live object's own property against that argument's declared value is not a guess. It is reading the identity the configuration already states.

internal/live/discovery's content-match leg is the consumer: given a binding, it lists CFNType through Cloud Control, reads each candidate's PropertyPath, and binds a needs-discovery instance to the one candidate whose value equals the instance's own Argument. Zero matches leaves the instance unbound, same as an ordinary create. Two or more matches is a refusal (internal/live/discovery.ProblemAmbiguousContentMatch), never a guess - the one absolute rule this mechanism holds itself to.

ContentMatchTypes is the generated roster; nothing here is maintained by hand.

type DocumentedImportID

type DocumentedImportID struct {
	// Separator is the documented join character.
	Separator string

	// Parts are the segments in the documented order.
	Parts []DocumentedImportIDPart
}

DocumentedImportID is one type's documented import-ID grammar: the segments the page names, in the documented order, and the character joining them.

DocumentedImportIDs is the generated set. The struct lives here rather than in the generated file because the generated file is data - a type declaration in it would be a definition nothing but a generator could change.

type DocumentedImportIDPart

type DocumentedImportIDPart struct {
	// Name is the segment's name in comparison form - lower case with
	// punctuation removed, so "REST-API-ID" and "rest_api_id" are one
	// name. [normalizeDocName] is the reduction, and it is applied to
	// schema attribute names here so the two sides meet in one form.
	Name string

	// Argument records whether the page's own Argument Reference names
	// this segment as a configuration argument. See
	// [resolveDocumentedImportID] for the one decision it makes.
	Argument bool
}

DocumentedImportIDPart is one documented segment.

type GoogleAssetImport

type GoogleAssetImport struct {
	// Host is the service host the Asset name carries:
	// "compute.googleapis.com".
	Host string

	// Asset is the Asset name's path after the host, in ReplaceVars
	// syntax: "projects/{{project}}/zones/{{zone}}/disks/{{name}}".
	Asset string

	// Import is the import ID the parts parsed out of Asset are put into.
	// Equal to Asset when the importer accepts the Asset path as it is.
	Import GoogleImportFormat

	// Documented is set when the provider source states no Asset name for
	// the type, and Asset is the one Google documents for its asset type
	// (https://docs.cloud.google.com/asset-inventory/docs/asset-names,
	// last updated 2026-10-19, read into live/gcp-asset-names.json). The
	// page's every variable is one path segment, so each placeholder of
	// such an Asset matches exactly one: a bucket's is its bare name.
	Documented bool
}

GoogleAssetImport says how a Cloud Asset Inventory hit of one labelled google type becomes an import ID (GoogleAssetImports).

func (GoogleAssetImport) AsIs

func (a GoogleAssetImport) AsIs() bool

AsIs reports whether the importer takes the Asset path unchanged.

type GoogleIdentityImport

type GoogleIdentityImport struct {
	// Via names the importer code that reads the identity, as
	// tools/gcp-row-gen read it at the pin: "parse_import_id",
	// "importer" or "iam_parent".
	Via string

	// Required are the attributes the importer cannot do without, plus
	// every attribute the identity schema requires for import.
	Required []string

	// Defaulted are the attributes the importer fills from the provider
	// configuration when the identity leaves them unset.
	Defaulted []string
}

GoogleIdentityImport says which identity attributes a record of one composite-identity google type must carry for the type's importer to take them as an identity object (GoogleIdentityImports).

type GoogleImportFormat

type GoogleImportFormat struct {
	// Template is the form, in ReplaceVars syntax:
	// "{{key_ring}}/cryptoKeys/{{name}}".
	Template string

	// Regex is the importer's own ParseImportId regex for Template, when the
	// importer is the generated one. A composed ID must match it. Empty
	// when the importer is custom code and Template comes from the type's
	// documented Import formats instead.
	Regex string
}

GoogleImportFormat is one import ID form a hashicorp/google importer accepts, as tools/gcp-row-gen read it from the provider source at the pinned tag.

type LongFormRule

type LongFormRule string

LongFormRule is the provider-side rewrite a Component.LongForm argument goes through.

const (
	// LongFormIAMParent is an IAM type's parent argument, which the
	// updater producer parses with tpgresource.GetImportIdQualifiers over
	// [Component.Qualifiers]: the first regex that matches anywhere in the
	// value wins, each named group it captures replaces the component of
	// the same name, and the parent is rebuilt from the row's template.
	// So `projects/other/secrets/db-pass` names project "other", and a
	// short `db-pass` keeps the project the row already resolved. No
	// regex matching is the updater's own error, refused by name.
	LongFormIAMParent LongFormRule = "iam-parent"

	// LongFormLastSegment is an argument of a type whose create builds
	// its id with tpgresource.ReplaceVarsForId. That keeps the last path
	// segment of an ordinary argument (GetResourceNameFromSelfLink),
	// trims a leading "projects/" off {{project}}, and reads {{region}}
	// and {{zone}} through GetRegion and GetZone, which take the last
	// segment of the resource's own value, before trimming "regions/" or
	// "zones/". The provider's own project, region or zone is trimmed
	// only.
	LongFormLastSegment LongFormRule = "last-segment"

	// LongFormComputedName is a hand-written IAM updater's parent
	// argument passed through a helper such as pubsub's
	// GetComputedSubscriptionName: a value matching the sole entry of
	// [Component.Qualifiers] is the whole parent as written, and any
	// other value is placed into the parent template the row spells
	// (projects/{{project}}/subscriptions/{{subscription}}).
	LongFormComputedName LongFormRule = "computed-name"

	// LongFormTrimPrefix is an argument a hand-written create trims the
	// sole entry of [Component.Qualifiers] off before it becomes part of
	// the id: resourcemanager.ParseFolderId turns "folders/123" and "123"
	// alike into "123" (#2145 item 1).
	LongFormTrimPrefix LongFormRule = "trim-prefix"

	// LongFormEnsurePrefix is an argument a hand-written IAM updater
	// passes through a helper that adds the sole entry of
	// [Component.Qualifiers] to a value that lacks it before the value
	// becomes the parent id: resourcemanager.CanonicalFolderId turns "123"
	// and "folders/123" alike into "folders/123". The prefix is part of
	// the import ID only; the identity attribute keeps the value as
	// written, as the provider's state does.
	LongFormEnsurePrefix LongFormRule = "ensure-prefix"
)

func (LongFormRule) Describe

func (l LongFormRule) Describe() string

Describe is what the provider does to a value this rule names, as a sentence for a refusal.

type ParentLink struct {
	// Attr is the argument on the child's own resource block that supplies
	// the value - the config argument one of its identity [Component]s
	// reads.
	Attr string

	// Parent is the admitted type whose identity this argument's value
	// matches.
	Parent string
}

ParentLink is one way a type's identity Components read a value that is also how another admitted type identifies itself.

func ParentListRecovered

func ParentListRecovered(typeName string, parents map[string]bool, service ServiceOf) (ParentLink, bool)

ParentListRecovered reports the single parent link for a curated parent-list-recovered child (the [parentListRecovered] map), or false for every other type. Unlike SingleParentComponent it does not reject a second attribute component, because the parent-scoped list recovers it; the read leg (internal/live/discovery's parentListChildSweep) is responsible for taking each result's whole identity rather than assuming it equals the parent's.

func ParentOf

func ParentOf(typeName string, parents map[string]bool, service ServiceOf) []ParentLink

ParentOf reports every parent link typeName's identity Components carry, restricted to the types named true in parents - the caller's answer to "which admitted types are themselves taggable", since a parent that cannot carry an ownership marker cannot anchor a read either, and this package touches no provider schema to find out on its own (see the package doc's "cloud-free and process-free"). Callers with schemas to hand compute it live (a top-level tags argument, [markerCapable] in internal/live/discovery's terms); tools/survey-gen computes the same fact from live/survey-full.json's taggable signal. Both land on the same answer for the admitted table as it stands today; this package asserts neither and takes the set as given.

A ServerAssigned type, or one absent from DefaultTable, has no Components and so no links - every entry here is one of the "Composed identities" or "Client-named" rows.

Two conventions decide a match, tried in order so that a type whose name extends several admitted types' names (aws_iam_role_policy_attachment extends both aws_iam_role and the untaggable aws_iam_role_policy) never picks the wrong one:

  1. typeName's own name begins with an eligible parent's name plus an underscore (aws_s3_bucket_policy begins with aws_s3_bucket_). This is the more specific test - a fact about two whole type names rather than about one argument's spelling - so it is tried first, and the longest matching parent name wins when more than one is a prefix. Once a parent is named this way, the attribute reported is whichever of the type's own Components literally supplies that parent's own IdentityAttrs, or follows the naming convention in rule 2 for it.
  2. Failing that, an individual argument follows the *_id / *_arn / *_url naming convention against an eligible parent's name on its own (aws_route's route_table_id, aws_route53_record's zone_id), AND the candidate shares the child's service (sameServiceAffinity). The affinity requirement is issue #129: without it the rule searched every admitted type and took the shortest name ending in the base, which made every plain "name" argument resolve to aws_api_gateway_domain_name and every "resource" one to null_resource. tools/survey-gen's own parentRef has the same shortest-wins tie-break and says why it is acceptable there - "the named parent is evidence prose, not a wiring claim, so a best-effort pick is enough". Discovery acts on this one.

A literal identity-attribute match ("arn" naming both a component and an eligible parent's IdentityAttrs) is deliberately never tried against every eligible parent at once: "arn" and "name" are common enough across the admitted table that picking whichever type happens to sort first would be a guess, not a derivation. It is only used, in rule 1, to confirm the *argument* once the type-name convention has already named the *parent*.

A child no rule resolves gets no link, and there is deliberately no per-type table of links to add back. Three real cross-service links are lost that way - aws_networkmanager_customer_gateway_association -> aws_customer_gateway and its two NetworkManager siblings, which reach into AWS::EC2 - and they become counted residue rather than hand-wired rows. A number saying "N untaggable types have no derivable parent" is something to act on; an allowlist of N entries is the same information with the pressure removed.

func SingleParentComponent

func SingleParentComponent(typeName string, parents map[string]bool, service ServiceOf) (ParentLink, bool)

SingleParentComponent reports the one case ParentOf's callers can act on without a second per-type judgment: typeName's identity Components carry exactly one attribute-supplying component, and it is the one ParentOf resolves to a parent. That is the "named-singleton child" shape table.go's own comments already name for aws_s3_bucket_policy and aws_sns_topic_policy - a type whose whole identity is the parent's, with nothing else the child chooses - and it is the shape a parent read alone settles the child's identity for, with no further argument to guess at.

A type with a second attribute-supplying component (aws_iam_role_policy's own policy name, aws_route's destination) is excluded even when one of its components does link to a parent: reading the parent answers what the child's parent-half is, not what its other, free-standing half is, so existence still is not fully determined by the parent alone.

type Schema

type Schema struct {
	// Identity maps each identity attribute to whether the provider
	// requires it for import. Nil means the provider serves no identity
	// schema for the type; an empty, non-nil map is an identity schema
	// with no attributes.
	Identity map[string]bool

	// Arguments are the resource schema's top-level attributes. Nil means
	// the provider serves no resource schema.
	Arguments map[string]Argument

	// Nested maps each nested block, and each attribute of a nested type,
	// to the names of the attributes it holds.
	Nested map[string][]string
}

Schema is a plain TypeSchema for a caller that builds the schemas itself rather than adapting a provider's.

func (Schema) Argument

func (s Schema) Argument(name string) (Argument, bool)

Argument implements TypeSchema.

func (Schema) EachIdentityAttribute

func (s Schema) EachIdentityAttribute(fn func(name string, requiredForImport bool))

EachIdentityAttribute implements TypeSchema.

func (Schema) HasIdentity

func (s Schema) HasIdentity() bool

HasIdentity implements TypeSchema.

func (Schema) HasResource

func (s Schema) HasResource() bool

HasResource implements TypeSchema.

func (Schema) NestedAttribute

func (s Schema) NestedAttribute(container, leaf string) bool

NestedAttribute implements TypeSchema.

type SchemaSet

type SchemaSet interface {
	// Each calls fn for every type in the set, in unspecified order, until
	// fn returns false.
	Each(fn func(typeName string, schema TypeSchema) bool)
}

SchemaSet is every managed resource type one provider serves schemas for. Some rules here decide by corroboration across independently authored types (see IsContextAttr), which is why they take the whole set and not one type's schemas.

type Schemas

type Schemas map[string]Schema

Schemas is a plain SchemaSet, keyed by resource type name.

func (Schemas) Each

func (s Schemas) Each(fn func(typeName string, schema TypeSchema) bool)

Each implements SchemaSet.

type ServiceOf

type ServiceOf func(tfType string) (string, bool)

ServiceOf reports the CloudFormation service a Terraform type maps to - "EC2" for aws_volume_attachment, via AWS::EC2::VolumeAttachment - and whether the type is mapped at all.

Supplied by the caller rather than read here, the same way parents is, and for the same reason: this package touches no artifact and no provider (see the package doc). internal/live/discovery has a *registry.Roster in hand at run time and tools/survey-gen loads the embedded one; both answer the same question from live/mapping.json.

A nil ServiceOf, or one reporting ok=false, is not an error. It narrows rule 2 to the weaker Terraform-prefix affinity instead - see sameServiceAffinity.

type TypeIdentity

type TypeIdentity struct {
	// Type is the resource type name, e.g. "aws_route".
	Type string

	// ServerAssigned is true when the identity is minted by the provider's
	// API at create time and appears nowhere in configuration. Instances of
	// such a type always classify as ClassNeedsDiscovery, whatever their
	// arguments say.
	ServerAssigned bool

	// Reason is the operator-facing explanation attached to every
	// ClassNeedsDiscovery resolution of this type. Required when
	// ServerAssigned, unused otherwise.
	Reason string

	// RecordBacked is true when this type's identity is not observed from
	// the cloud at all: the type is one of GitHub issue #73's
	// RECORD_ADMITTED logical types (internal/live/lint), whose whole
	// existence is a persisted micro-state record. Instances of such a
	// type would always classify [ClassRecordBacked], whatever their
	// arguments say, the same way ServerAssigned instances always classify
	// ClassNeedsDiscovery above.
	//
	// Live since #73 phase (d): ten rows of [DefaultTable] set it
	// (null_resource, terraform_data, the random_* and time_* families),
	// and internal/live/projection consumes ClassRecordBacked to hydrate
	// such an instance from the record store without a cloud read. Lint
	// still refuses these types when no record_store is configured, which
	// is why a configuration without one never reaches a row that sets
	// this. [SynthesizeTypeIdentity] never produces it.
	RecordBacked bool

	// SecretMaterial is true when this [RecordBacked] type's schema carries
	// a live sensitive attribute, so the record holding its whole value
	// holds secret material - a generated password, a private key, the
	// contents of a file the provider marks sensitive.
	//
	// It never appears without RecordBacked. The two are derived from one
	// measurement of one artifact (live/logical-schemas.json, through
	// tools/row-gen's recordBackedTypes and secretMaterialTypes): the flag
	// above says the record is where this type's prior state comes from, and
	// this one says what is in it.
	//
	// # What it gates, and where
	//
	// GitHub issue #365's `strict { secrets = ... }`. Such a type resolves
	// [ClassRecordBacked] under the default, [strict.Store], which is what
	// stock OpenTofu does - a stock state file holds random_password.result
	// in clear, and the estate's record store is where this fork keeps what
	// a state file would. Under [strict.Refuse] it does not resolve at all.
	//
	// THREE places branch on it, and the third is the one an audit of this
	// change had to find because the first draft of this comment denied it
	// existed.
	//
	// internal/live/lint is the gate a configuration meets first (its
	// RuleLogicalResource refusal names the setting), and [resolver.resolveInstance]
	// asks the same question again at the layer that acts, so a caller that
	// skipped lint gets a refusal rather than a record full of secrets. That
	// double-asking is the same shape the `markers "record"` selection
	// already has, and it is here for the same reason: the two layers must
	// never disagree about what the operator asked for.
	//
	// internal/live/liveimport's ratifyOne is the third, and it is NOT
	// covered by the other two: `choudoufu live-import` runs no lint pass and
	// builds no resolver at all. What it would write is not an identity and
	// not a residue attribute but the instance's WHOLE prior object, read
	// straight out of the stock state file being migrated - so it is the only
	// thing standing between an operator who set [strict.Refuse] and exactly
	// the outcome they turned off.
	//
	// Every OTHER consumer of RecordBacked - discovery's cloudObservable,
	// projection's write-back, internal/command's live-plan provider set,
	// tools/survey-gen's untaggable render - is asking "does this type have a
	// cloud object", and the answer is no whatever the secrets setting says.
	// The distinction worth carrying forward is the one that hid the defect:
	// a consumer asking about the OBJECT can ignore this flag, and a consumer
	// deciding what to WRITE cannot.
	SecretMaterial bool

	// NonAWSProvider is true when this type is a real, live object - unlike
	// RecordBacked - but belongs to a provider other than AWS, so
	// live/survey-full.json (CloudFormation-registry-backed, and by
	// construction carrying only the AWS provider's roster) has no row for
	// it and never will. Issue #326 is the first time the admission table
	// crossed that line: the four hand-ratified Kubernetes-provider rows
	// (kubernetes_cluster_role_binding, kubernetes_config_map,
	// kubernetes_namespace, kubernetes_storage_class). Unlike RecordBacked,
	// this says nothing about how the type resolves or classifies - it
	// exists only so live/admission_coverage_test.go's
	// TestAdmittedTableNamesOnlyTypesTheProviderServes can ask the row
	// itself which provider-roster artifact backs it, rather than falling
	// back to a hand-maintained exemption list that would go stale the way
	// its own doc comment forbids. tools/row-gen never sets it; only a
	// ratified.json row can.
	//
	// It is also set on a runtime-synthesized entry the moment a type's
	// SCHEMA matches the Kubernetes object-metadata or manifest convention
	// ([synthesizeMetadataIdentity], [synthesizeManifestIdentity]), which is
	// a claim about shape, not about which provider actually serves the
	// type or whether any estate-wide sweep leg reaches it - a type from an
	// unrelated provider can share that shape by coincidence. GitHub issue
	// #1581: [resolver.warnUnsweepableTypes] used to read this field as
	// "the Kubernetes sweep covers it" and ask [internal/live/substrate]
	// instead.
	NonAWSProvider bool

	// Components build the import identity by concatenation, in order.
	// Required unless ServerAssigned or RecordBacked.
	Components []Component

	// ImportSyntax documents the provider's import-ID grammar for this
	// type, in the provider documentation's own notation (e.g.
	// "ROUTETABLEID_DESTINATION"). Documentation only for every other
	// purpose - Components is what the code follows for composing an
	// import ID - with one narrow, deliberate exception:
	// internal/live/discovery's importsWholeARNString reads it to tell a
	// server-assigned type whose provider-documented legacy import ID IS
	// its own ARN (a single ImportSyntax token ending "ARN", e.g.
	// "TASKDEFINITIONARN") apart from one whose ARN-shaped Cloud Control
	// identifier should be stripped to its bare resource-id segment
	// instead (e.g. "WORKSPACEID") - see that function's doc comment and
	// issue #298.
	ImportSyntax string

	// IdentityAttrs are the attribute names whose value equals this type's
	// identity, so that a reference to one of them from another resource
	// can be resolved without a cloud read. A reference to any other
	// attribute of this type is an error rather than a guess.
	//
	// An empty list means no attribute of this type is usable as an
	// identity source, which is the honest answer for types whose "id"
	// attribute is a provider-synthesized value distinct from the import
	// ID.
	IdentityAttrs []string

	// IdentityObjectOnly is true for an entry whose identity has more than
	// one attribute and no separator to join them with, so there is no
	// import-ID string to build. A resolution of such a type carries
	// [Resolution.IdentityValues] and an empty [Resolution.ImportID], and
	// the projection imports it by identity object or not at all.
	//
	// It exists because the alternative is worse than a refusal. Component
	// values are concatenated with nothing between them
	// (internal/live/identity/resolve.go's classify), so a two-attribute
	// composite with cluster="prod" and name="svc" would produce the import
	// ID "prodsvc" - not empty, so every guard that tests for emptiness
	// passes it through, and a run that fell back from the identity object
	// would import "prodsvc" against a real account with a TRACE log to say
	// so. GitHub issue #105 names that as the thing to guard before relaxing
	// the composite refusal at all.
	//
	// Only [SynthesizeTypeIdentity] sets it. A hand-written row states its
	// separator, which is the whole reason those rows are hand-written.
	IdentityObjectOnly bool

	// Synthesized is true when this entry was not written by hand but built
	// from the provider's own identity schema at resolution time. See
	// [SynthesizeTypeIdentity].
	Synthesized bool

	// Admits records what backed a synthesized entry: the schemas alone
	// ([AdmitSchema]) or the schemas plus this configuration
	// ([AdmitConfigSignal]). Empty for a hand-written row, which is asserted
	// rather than derived.
	Admits Admission

	// UniqueName is set when AWS itself guarantees that the name this
	// type's configuration supplies identifies at most one object in the
	// account and region an enumeration covers, so a live object can be
	// recognised by that name instead of by an ownership marker. It is only
	// ever set on a ServerAssigned row: a row whose identity the
	// configuration already states has no need of it.
	//
	// It is the exception internal/live/uniquename's package comment argues
	// for, and it is narrow on purpose. internal/live/foreign matches a live
	// object's content against a declaration and refuses to bind on the
	// result, because "inferring it from a content match would be exactly
	// the guess the marker spec exists to forbid". That refusal is right for
	// content in general - two objects can carry the same CIDR, the same
	// policy document, the same anything. A name AWS refuses to issue twice
	// is not content: "the cache policy called static-assets" names exactly
	// one object in the account, so reading it off a listing is not
	// inference, it is reading the identity the configuration already
	// states.
	//
	// Nothing here is hand-maintained, and the guarantee's absence fails
	// closed: tools/row-gen sets this only where the provider's own argument
	// reference and the CloudFormation registry schema INDEPENDENTLY assert
	// the name is unique (live/import-grammar.json's declared_unique and
	// live/registry.json's unique_name_property, both computed by
	// internal/live/uniquename.Asserted). A type with one source, or none,
	// leaves this zero and never reaches the binding leg at all.
	UniqueName UniqueName

	// ARMResourceID is true when the identity this entry builds is an Azure
	// Resource Manager resource id (GitHub issue #1120, ruled on #1122): a
	// whole address, subscription and resource group and provider namespace
	// included, that ARM compares case-insensitively. Two consequences
	// follow, and [resolver.checkCollisions] applies both: two resolutions
	// with such ids that differ only in letter case name one object, and
	// they name it whatever type or provider declares them, because an
	// azurerm block and an azapi_resource block for one virtual network are
	// one object declared twice. Only the Azure family's synthesis sets it
	// (substrate.Azure.SynthesizeIdentity); no ratified row does.
	ARMResourceID bool
}

TypeIdentity is what this package knows about one resource type's identity: whether the identity exists in configuration at all, how to build it from configuration arguments if it does, and which of the type's attributes carry that identity when another resource refers to it.

func LookupType

func LookupType(typeName string) (TypeIdentity, bool)

LookupType returns the identity knowledge for a resource type, and whether the type is in the table at all: DefaultTable, or for a hashicorp/google or google-beta type, GoogleTable.

func PreferSynthesized

func PreferSynthesized(row, synthesized TypeIdentity) (TypeIdentity, bool)

PreferSynthesized decides between a hand-written row and the entry the provider's own identity schema synthesizes for the same type. When the synthesized entry reproduces the row it returns the entry to use in the row's place and true; otherwise it returns false and the row stands.

func (TypeIdentity) Claim

func (t TypeIdentity) Claim() string

Claim renders what this entry says identifies the type, for a message that has to show both sides.

func (TypeIdentity) ComposesIdentity

func (t TypeIdentity) ComposesIdentity(required []string) bool

ComposesIdentity reports whether this entry can build a whole identity object: every attribute the provider requires for import is supplied by some component.

This is the bar for importing by identity rather than by string. It is deliberately about the *required* attributes only - the optional ones are context the provider fills in itself (account_id, region) or alternatives it needs no particular one of - and it is deliberately checked against a real identity schema rather than asserted here, because the whole point of the exercise is that the provider is the authority on what identifies its own resources.

func (TypeIdentity) HasIdentityAttr

func (t TypeIdentity) HasIdentityAttr(name string) bool

HasIdentityAttr reports whether name is one of the entry's TypeIdentity.IdentityAttrs.

func (TypeIdentity) NamedAttrs

func (t TypeIdentity) NamedAttrs() map[string]bool

NamedAttrs is every attribute name this entry mentions, on either side: the arguments its components read and the attributes it hands out as identity sources. It is the set an identity schema's attribute has to land in for the entry to be talking about the same identity.

func (TypeIdentity) SuppliesIdentityAttr

func (t TypeIdentity) SuppliesIdentityAttr(name string) bool

SuppliesIdentityAttr reports whether some component of this entry can supply the named identity attribute, taking the same-name rule over each component's whole alternative list.

It is the type-level question, so it is answered over every alternative: aws_route can supply destination_cidr_block or destination_ipv6_cidr_block or destination_prefix_list_id, and which one a given route supplies is a per-instance answer [Resolve] makes.

type TypeSchema

type TypeSchema interface {
	// HasIdentity reports whether the provider serves an identity schema
	// for the type.
	HasIdentity() bool

	// EachIdentityAttribute calls fn once for every attribute of the
	// identity schema, with whether the provider requires it for import.
	// The order is unspecified. It calls nothing when HasIdentity is false.
	EachIdentityAttribute(fn func(name string, requiredForImport bool))

	// HasResource reports whether the provider serves a resource schema for
	// the type.
	HasResource() bool

	// Argument returns the resource schema's top-level attribute called
	// name, and false when there is none (or no resource schema at all).
	Argument(name string) (Argument, bool)

	// NestedAttribute reports whether the resource schema has a nested
	// block, or an attribute of a nested type, called container, and that
	// nested shape has an attribute called leaf.
	NestedAttribute(container, leaf string) bool
}

TypeSchema is one managed resource type's two provider schemas - the resource schema and the resource identity schema - as far as this package reads them.

type UniqueName

type UniqueName struct {
	// Attrs names the configuration arguments carrying the name, in
	// preference order, the same convention [Component.Attrs] uses. In
	// practice one entry, "name" - the two-source rule only clears a
	// REQUIRED argument literally called name, because an optional one is a
	// name a configuration may leave out and a name the configuration does
	// not state is a name no listing can be matched against.
	Attrs []string

	// Property is the path, inside a Cloud Control ResourceDescription's
	// Properties map, at which the same name appears: "Name" at the top
	// level, or a "/"-separated descent into a configuration sub-object
	// ("CachePolicyConfig/Name"). It comes from the CloudFormation registry
	// schema, which is also where the second half of the uniqueness evidence
	// comes from - so a type whose registry schema does not carry the claim
	// has no path here either, and cannot be bound by name even if the
	// provider docs alone had said it was unique.
	Property string
}

UniqueName says where a type's account-unique, client-supplied name lives on both sides of the comparison a name bind makes: Attrs in the configuration, Property in the live object.

func (UniqueName) PropertyPath

func (u UniqueName) PropertyPath() []string

PropertyPath is UniqueName.Property split into the segments a reader descends through a Cloud Control Properties map. It returns nil for an unset entry, so a caller that skipped UniqueName.Set still reads nothing rather than reading the map's root.

func (UniqueName) Set

func (u UniqueName) Set() bool

Set reports whether this type may be bound by its name. Both halves are required: an empty Property means no live object can be read, and empty Attrs means no declaration can be compared against one. A caller must test this rather than either field alone, which is what makes a partially populated entry fail closed instead of half-binding.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL