package addrs import ( "fmt" "github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/terraform/tfdiags" ) // Target describes a targeted address with source location information. type Target struct { Subject Targetable SourceRange tfdiags.SourceRange } // ParseTarget attempts to interpret the given traversal as a targetable // address. The given traversal must be absolute, or this function will // panic. // // If no error diagnostics are returned, the returned target includes the // address that was extracted and the source range it was extracted from. // // If error diagnostics are returned then the Target value is invalid and // must not be used. func ParseTarget(traversal hcl.Traversal) (*Target, tfdiags.Diagnostics) { path, remain, diags := parseModuleInstancePrefix(traversal) if diags.HasErrors() { return nil, diags } rng := tfdiags.SourceRangeFromHCL(traversal.SourceRange()) if len(remain) == 0 { return &Target{ Subject: path, SourceRange: rng, }, diags } mode := ManagedResourceMode if remain.RootName() == "data" { mode = DataResourceMode remain = remain[1:] } if len(remain) < 2 { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "Resource specification must include a resource type and name.", Subject: remain.SourceRange().Ptr(), }) return nil, diags } var typeName, name string switch tt := remain[0].(type) { case hcl.TraverseRoot: typeName = tt.Name case hcl.TraverseAttr: typeName = tt.Name default: switch mode { case ManagedResourceMode: diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "A resource type name is required.", Subject: remain[0].SourceRange().Ptr(), }) case DataResourceMode: diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "A data source name is required.", Subject: remain[0].SourceRange().Ptr(), }) default: panic("unknown mode") } return nil, diags } switch tt := remain[1].(type) { case hcl.TraverseAttr: name = tt.Name default: diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "A resource name is required.", Subject: remain[1].SourceRange().Ptr(), }) return nil, diags } var subject Targetable remain = remain[2:] switch len(remain) { case 0: subject = path.Resource(mode, typeName, name) case 1: if tt, ok := remain[0].(hcl.TraverseIndex); ok { key, err := ParseInstanceKey(tt.Key) if err != nil { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: fmt.Sprintf("Invalid resource instance key: %s.", err), Subject: remain[0].SourceRange().Ptr(), }) return nil, diags } subject = path.ResourceInstance(mode, typeName, name, key) } else { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "Resource instance key must be given in square brackets.", Subject: remain[0].SourceRange().Ptr(), }) return nil, diags } default: diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "Unexpected extra operators after address.", Subject: remain[1].SourceRange().Ptr(), }) return nil, diags } return &Target{ Subject: subject, SourceRange: rng, }, diags } // ParseAbsResource attempts to interpret the given traversal as an absolute // resource address, using the same syntax as expected by ParseTarget. // // If no error diagnostics are returned, the returned target includes the // address that was extracted and the source range it was extracted from. // // If error diagnostics are returned then the AbsResource value is invalid and // must not be used. func ParseAbsResource(traversal hcl.Traversal) (AbsResource, tfdiags.Diagnostics) { addr, diags := ParseTarget(traversal) if diags.HasErrors() { return AbsResource{}, diags } switch tt := addr.Subject.(type) { case AbsResource: return tt, diags case AbsResourceInstance: // Catch likely user error with specialized message // Assume that the last element of the traversal must be the index, // since that's required for a valid resource instance address. indexStep := traversal[len(traversal)-1] diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "A resource address is required. This instance key identifies a specific resource instance, which is not expected here.", Subject: indexStep.SourceRange().Ptr(), }) return AbsResource{}, diags case ModuleInstance: // Catch likely user error with specialized message diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "A resource address is required here. The module path must be followed by a resource specification.", Subject: traversal.SourceRange().Ptr(), }) return AbsResource{}, diags default: // Generic message for other address types diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "A resource address is required here.", Subject: traversal.SourceRange().Ptr(), }) return AbsResource{}, diags } } // ParseAbsResourceInstance attempts to interpret the given traversal as an // absolute resource instance address, using the same syntax as expected by // ParseTarget. // // If no error diagnostics are returned, the returned target includes the // address that was extracted and the source range it was extracted from. // // If error diagnostics are returned then the AbsResource value is invalid and // must not be used. func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) { addr, diags := ParseTarget(traversal) if diags.HasErrors() { return AbsResourceInstance{}, diags } switch tt := addr.Subject.(type) { case AbsResource: return tt.Instance(NoKey), diags case AbsResourceInstance: return tt, diags case ModuleInstance: // Catch likely user error with specialized message diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "A resource instance address is required here. The module path must be followed by a resource instance specification.", Subject: traversal.SourceRange().Ptr(), }) return AbsResourceInstance{}, diags default: // Generic message for other address types diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "A resource address is required here.", Subject: traversal.SourceRange().Ptr(), }) return AbsResourceInstance{}, diags } }