github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/addrs/parse_target.go (about)

     1  package addrs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2/hclsyntax"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    10  )
    11  
    12  // Target describes a targeted address with source location information.
    13  type Target struct {
    14  	Subject     Targetable
    15  	SourceRange tfdiags.SourceRange
    16  }
    17  
    18  // ParseTarget attempts to interpret the given traversal as a targetable
    19  // address. The given traversal must be absolute, or this function will
    20  // panic.
    21  //
    22  // If no error diagnostics are returned, the returned target includes the
    23  // address that was extracted and the source range it was extracted from.
    24  //
    25  // If error diagnostics are returned then the Target value is invalid and
    26  // must not be used.
    27  func ParseTarget(traversal hcl.Traversal) (*Target, tfdiags.Diagnostics) {
    28  	path, remain, diags := parseModuleInstancePrefix(traversal)
    29  	if diags.HasErrors() {
    30  		return nil, diags
    31  	}
    32  
    33  	rng := tfdiags.SourceRangeFromHCL(traversal.SourceRange())
    34  
    35  	if len(remain) == 0 {
    36  		return &Target{
    37  			Subject:     path,
    38  			SourceRange: rng,
    39  		}, diags
    40  	}
    41  
    42  	mode := ManagedResourceMode
    43  	if remain.RootName() == "data" {
    44  		mode = DataResourceMode
    45  		remain = remain[1:]
    46  	}
    47  
    48  	if len(remain) < 2 {
    49  		diags = diags.Append(&hcl.Diagnostic{
    50  			Severity: hcl.DiagError,
    51  			Summary:  "Invalid address",
    52  			Detail:   "Resource specification must include a resource type and name.",
    53  			Subject:  remain.SourceRange().Ptr(),
    54  		})
    55  		return nil, diags
    56  	}
    57  
    58  	var typeName, name string
    59  	switch tt := remain[0].(type) {
    60  	case hcl.TraverseRoot:
    61  		typeName = tt.Name
    62  	case hcl.TraverseAttr:
    63  		typeName = tt.Name
    64  	default:
    65  		switch mode {
    66  		case ManagedResourceMode:
    67  			diags = diags.Append(&hcl.Diagnostic{
    68  				Severity: hcl.DiagError,
    69  				Summary:  "Invalid address",
    70  				Detail:   "A resource type name is required.",
    71  				Subject:  remain[0].SourceRange().Ptr(),
    72  			})
    73  		case DataResourceMode:
    74  			diags = diags.Append(&hcl.Diagnostic{
    75  				Severity: hcl.DiagError,
    76  				Summary:  "Invalid address",
    77  				Detail:   "A data source name is required.",
    78  				Subject:  remain[0].SourceRange().Ptr(),
    79  			})
    80  		default:
    81  			panic("unknown mode")
    82  		}
    83  		return nil, diags
    84  	}
    85  
    86  	switch tt := remain[1].(type) {
    87  	case hcl.TraverseAttr:
    88  		name = tt.Name
    89  	default:
    90  		diags = diags.Append(&hcl.Diagnostic{
    91  			Severity: hcl.DiagError,
    92  			Summary:  "Invalid address",
    93  			Detail:   "A resource name is required.",
    94  			Subject:  remain[1].SourceRange().Ptr(),
    95  		})
    96  		return nil, diags
    97  	}
    98  
    99  	var subject Targetable
   100  	remain = remain[2:]
   101  	switch len(remain) {
   102  	case 0:
   103  		subject = path.Resource(mode, typeName, name)
   104  	case 1:
   105  		if tt, ok := remain[0].(hcl.TraverseIndex); ok {
   106  			key, err := ParseInstanceKey(tt.Key)
   107  			if err != nil {
   108  				diags = diags.Append(&hcl.Diagnostic{
   109  					Severity: hcl.DiagError,
   110  					Summary:  "Invalid address",
   111  					Detail:   fmt.Sprintf("Invalid resource instance key: %s.", err),
   112  					Subject:  remain[0].SourceRange().Ptr(),
   113  				})
   114  				return nil, diags
   115  			}
   116  
   117  			subject = path.ResourceInstance(mode, typeName, name, key)
   118  		} else {
   119  			diags = diags.Append(&hcl.Diagnostic{
   120  				Severity: hcl.DiagError,
   121  				Summary:  "Invalid address",
   122  				Detail:   "Resource instance key must be given in square brackets.",
   123  				Subject:  remain[0].SourceRange().Ptr(),
   124  			})
   125  			return nil, diags
   126  		}
   127  	default:
   128  		diags = diags.Append(&hcl.Diagnostic{
   129  			Severity: hcl.DiagError,
   130  			Summary:  "Invalid address",
   131  			Detail:   "Unexpected extra operators after address.",
   132  			Subject:  remain[1].SourceRange().Ptr(),
   133  		})
   134  		return nil, diags
   135  	}
   136  
   137  	return &Target{
   138  		Subject:     subject,
   139  		SourceRange: rng,
   140  	}, diags
   141  }
   142  
   143  // ParseTargetStr is a helper wrapper around ParseTarget that takes a string
   144  // and parses it with the HCL native syntax traversal parser before
   145  // interpreting it.
   146  //
   147  // This should be used only in specialized situations since it will cause the
   148  // created references to not have any meaningful source location information.
   149  // If a target string is coming from a source that should be identified in
   150  // error messages then the caller should instead parse it directly using a
   151  // suitable function from the HCL API and pass the traversal itself to
   152  // ParseTarget.
   153  //
   154  // Error diagnostics are returned if either the parsing fails or the analysis
   155  // of the traversal fails. There is no way for the caller to distinguish the
   156  // two kinds of diagnostics programmatically. If error diagnostics are returned
   157  // the returned target may be nil or incomplete.
   158  func ParseTargetStr(str string) (*Target, tfdiags.Diagnostics) {
   159  	var diags tfdiags.Diagnostics
   160  
   161  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   162  	diags = diags.Append(parseDiags)
   163  	if parseDiags.HasErrors() {
   164  		return nil, diags
   165  	}
   166  
   167  	target, targetDiags := ParseTarget(traversal)
   168  	diags = diags.Append(targetDiags)
   169  	return target, diags
   170  }
   171  
   172  // ParseAbsResourceInstance attempts to interpret the given traversal as an
   173  // absolute resource instance address, using the same syntax as expected by
   174  // ParseTarget.
   175  //
   176  // If no error diagnostics are returned, the returned target includes the
   177  // address that was extracted and the source range it was extracted from.
   178  //
   179  // If error diagnostics are returned then the AbsResource value is invalid and
   180  // must not be used.
   181  func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) {
   182  	addr, diags := ParseTarget(traversal)
   183  	if diags.HasErrors() {
   184  		return AbsResourceInstance{}, diags
   185  	}
   186  
   187  	switch tt := addr.Subject.(type) {
   188  
   189  	case AbsResource:
   190  		return tt.Instance(NoKey), diags
   191  
   192  	case AbsResourceInstance:
   193  		return tt, diags
   194  
   195  	case ModuleInstance: // Catch likely user error with specialized message
   196  		diags = diags.Append(&hcl.Diagnostic{
   197  			Severity: hcl.DiagError,
   198  			Summary:  "Invalid address",
   199  			Detail:   "A resource instance address is required here. The module path must be followed by a resource instance specification.",
   200  			Subject:  traversal.SourceRange().Ptr(),
   201  		})
   202  		return AbsResourceInstance{}, diags
   203  
   204  	default: // Generic message for other address types
   205  		diags = diags.Append(&hcl.Diagnostic{
   206  			Severity: hcl.DiagError,
   207  			Summary:  "Invalid address",
   208  			Detail:   "A resource address is required here.",
   209  			Subject:  traversal.SourceRange().Ptr(),
   210  		})
   211  		return AbsResourceInstance{}, diags
   212  
   213  	}
   214  }
   215  
   216  // ParseAbsResourceInstanceStr is a helper wrapper around
   217  // ParseAbsResourceInstance that takes a string and parses it with the HCL
   218  // native syntax traversal parser before interpreting it.
   219  //
   220  // Error diagnostics are returned if either the parsing fails or the analysis
   221  // of the traversal fails. There is no way for the caller to distinguish the
   222  // two kinds of diagnostics programmatically. If error diagnostics are returned
   223  // the returned address may be incomplete.
   224  //
   225  // Since this function has no context about the source of the given string,
   226  // any returned diagnostics will not have meaningful source location
   227  // information.
   228  func ParseAbsResourceInstanceStr(str string) (AbsResourceInstance, tfdiags.Diagnostics) {
   229  	var diags tfdiags.Diagnostics
   230  
   231  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   232  	diags = diags.Append(parseDiags)
   233  	if parseDiags.HasErrors() {
   234  		return AbsResourceInstance{}, diags
   235  	}
   236  
   237  	addr, addrDiags := ParseAbsResourceInstance(traversal)
   238  	diags = diags.Append(addrDiags)
   239  	return addr, diags
   240  }