github.com/opentofu/opentofu@v1.7.1/internal/configs/import.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package configs 7 8 import ( 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/hclsyntax" 11 "github.com/opentofu/opentofu/internal/addrs" 12 "github.com/opentofu/opentofu/internal/tfdiags" 13 ) 14 15 type Import struct { 16 ID hcl.Expression 17 18 // To is the address HCL expression given in the `import` block configuration. 19 // It supports the following address formats: 20 // - aws_s3_bucket.my_bucket 21 // - module.my_module.aws_s3_bucket.my_bucket 22 // - aws_s3_bucket.my_bucket["static_key"] 23 // - module.my_module[0].aws_s3_bucket.my_buckets["static_key"] 24 // - aws_s3_bucket.my_bucket[expression] 25 // - module.my_module[expression].aws_s3_bucket.my_buckets[expression] 26 // A dynamic instance key supports a dynamic expression like - a variable, a local, a condition (for example, 27 // ternary), a resource block attribute, a data block attribute, etc. 28 To hcl.Expression 29 // StaticTo is the corresponding resource and module that the address is referring to. When decoding, as long 30 // as the `to` field is in the accepted format, we could determine the actual modules and resource that the 31 // address represents. However, we do not yet know for certain what module instance and resource instance this 32 // address refers to. So, Static import is mainly used to figure out the Module and Resource, and Provider of the 33 // import target resource 34 // If we could not determine the StaticTo when decoding the block, then the address is in an unacceptable format 35 StaticTo addrs.ConfigResource 36 // ResolvedTo will be a reference to the resource instance of the import target, if it can be resolved when decoding 37 // the `import` block. If the `to` field does not represent a static address 38 // (for example: module.my_module[var.var1].aws_s3_bucket.bucket), then this will be nil. 39 // However, if the address is static and can be fully resolved at decode time 40 // (for example: module.my_module[2].aws_s3_bucket.bucket), then this will be a reference to the resource instance's 41 // address 42 // Mainly used for early validations on the import block address, for example making sure there are no duplicate 43 // import blocks targeting the same resource 44 ResolvedTo *addrs.AbsResourceInstance 45 46 ForEach hcl.Expression 47 48 ProviderConfigRef *ProviderConfigRef 49 Provider addrs.Provider 50 51 DeclRange hcl.Range 52 ProviderDeclRange hcl.Range 53 } 54 55 func decodeImportBlock(block *hcl.Block) (*Import, hcl.Diagnostics) { 56 var diags hcl.Diagnostics 57 imp := &Import{ 58 DeclRange: block.DefRange, 59 } 60 61 content, moreDiags := block.Body.Content(importBlockSchema) 62 diags = append(diags, moreDiags...) 63 64 if attr, exists := content.Attributes["id"]; exists { 65 imp.ID = attr.Expr 66 } 67 68 if attr, exists := content.Attributes["to"]; exists { 69 imp.To = attr.Expr 70 staticAddress, addressDiags := staticImportAddress(attr.Expr) 71 diags = append(diags, addressDiags.ToHCL()...) 72 73 // Exit early if there are issues resolving the static address part. We wouldn't be able to validate the provider in such a case 74 if addressDiags.HasErrors() { 75 return imp, diags 76 } 77 imp.StaticTo = staticAddress 78 79 imp.ResolvedTo = resolvedImportAddress(imp.To) 80 } 81 82 if attr, exists := content.Attributes["provider"]; exists { 83 if len(imp.StaticTo.Module) > 0 { 84 diags = append(diags, &hcl.Diagnostic{ 85 Severity: hcl.DiagError, 86 Summary: "Invalid import provider argument", 87 Detail: "The provider argument can only be specified in import blocks that will generate configuration.\n\nUse the providers argument within the module block to configure providers for all resources within a module, including imported resources.", 88 Subject: attr.Range.Ptr(), 89 }) 90 } 91 92 var providerDiags hcl.Diagnostics 93 imp.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider") 94 imp.ProviderDeclRange = attr.Range 95 diags = append(diags, providerDiags...) 96 } 97 98 if attr, exists := content.Attributes["for_each"]; exists { 99 imp.ForEach = attr.Expr 100 } 101 102 return imp, diags 103 } 104 105 var importBlockSchema = &hcl.BodySchema{ 106 Attributes: []hcl.AttributeSchema{ 107 { 108 Name: "provider", 109 }, 110 { 111 Name: "id", 112 Required: true, 113 }, 114 { 115 Name: "to", 116 Required: true, 117 }, 118 { 119 Name: "for_each", 120 }, 121 }, 122 } 123 124 // absTraversalForImportToExpr returns a static traversal of an import block's "to" field. 125 // It is inspired by hcl.AbsTraversalForExpr and by tofu.triggersExprToTraversal 126 // The use-case here is different - we want to also allow for hclsyntax.IndexExpr to be allowed, 127 // but we don't really care about the key part of it. We just want a traversal that could be converted to an address 128 // of a resource, so we could determine the module + resource + provider 129 // 130 // Currently, there are 4 types of HCL epressions that support AsTraversal: 131 // - hclsyntax.ScopeTraversalExpr - Simply returns the Traversal. Same for our use-case here 132 // - hclsyntax.RelativeTraversalExpr - Calculates hcl.AbsTraversalForExpr for the Source, and adds the Traversal to it. Same here, with absTraversalForImportToExpr instead 133 // - hclsyntax.LiteralValueExpr - Mainly for null/false/true values. Not relevant in our use-case, as it's could not really be part of a reference (unless it is inside of an index, which is irrelevant here anyway) 134 // - hclsyntax.ObjectConsKeyExpr - Not relevant here 135 // 136 // In addition to these, we need to also support hclsyntax.IndexExpr. For it - we do not care about what's in the index. 137 // We need only know the traversal parts of it the "Collection", as the index doesn't affect which resource/module this is 138 func absTraversalForImportToExpr(expr hcl.Expression) (traversal hcl.Traversal, diags tfdiags.Diagnostics) { 139 switch e := expr.(type) { 140 case *hclsyntax.IndexExpr: 141 t, d := absTraversalForImportToExpr(e.Collection) 142 diags = diags.Append(d) 143 traversal = append(traversal, t...) 144 case *hclsyntax.RelativeTraversalExpr: 145 t, d := absTraversalForImportToExpr(e.Source) 146 diags = diags.Append(d) 147 traversal = append(traversal, t...) 148 traversal = append(traversal, e.Traversal...) 149 case *hclsyntax.ScopeTraversalExpr: 150 traversal = append(traversal, e.Traversal...) 151 default: 152 diags = diags.Append(&hcl.Diagnostic{ 153 Severity: hcl.DiagError, 154 Summary: "Invalid import address expression", 155 Detail: "Import address must be a reference to a resource's address, and only allows for indexing with dynamic keys. For example: module.my_module[expression1].aws_s3_bucket.my_buckets[expression2] for resources inside of modules, or simply aws_s3_bucket.my_bucket for a resource in the root module", 156 Subject: expr.Range().Ptr(), 157 }) 158 } 159 return 160 } 161 162 // staticImportAddress returns an addrs.ConfigResource representing the module and resource of the import target. 163 // If the address is of an unacceptable format, the function will return error diags 164 func staticImportAddress(expr hcl.Expression) (addrs.ConfigResource, tfdiags.Diagnostics) { 165 traversal, diags := absTraversalForImportToExpr(expr) 166 if diags.HasErrors() { 167 return addrs.ConfigResource{}, diags 168 } 169 170 absResourceInstance, diags := addrs.ParseAbsResourceInstance(traversal) 171 return absResourceInstance.ConfigResource(), diags 172 } 173 174 // resolvedImportAddress attempts to find the resource instance of the import target, if possible. 175 // Here, we attempt to resolve the address as though it is a static absolute traversal, if that's possible. 176 // This would only be possible if the `import` block's "to" field does not rely on any data that is dynamic 177 func resolvedImportAddress(expr hcl.Expression) *addrs.AbsResourceInstance { 178 var diags hcl.Diagnostics 179 traversal, traversalDiags := hcl.AbsTraversalForExpr(expr) 180 diags = append(diags, traversalDiags...) 181 if diags.HasErrors() { 182 return nil 183 } 184 185 to, toDiags := addrs.ParseAbsResourceInstance(traversal) 186 diags = append(diags, toDiags.ToHCL()...) 187 if diags.HasErrors() { 188 return nil 189 } 190 return &to 191 }