github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/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/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  // ParseAbsResource attempts to interpret the given traversal as an absolute
   173  // resource address, using the same syntax as expected by ParseTarget.
   174  //
   175  // If no error diagnostics are returned, the returned target includes the
   176  // address that was extracted and the source range it was extracted from.
   177  //
   178  // If error diagnostics are returned then the AbsResource value is invalid and
   179  // must not be used.
   180  func ParseAbsResource(traversal hcl.Traversal) (AbsResource, tfdiags.Diagnostics) {
   181  	addr, diags := ParseTarget(traversal)
   182  	if diags.HasErrors() {
   183  		return AbsResource{}, diags
   184  	}
   185  
   186  	switch tt := addr.Subject.(type) {
   187  
   188  	case AbsResource:
   189  		return tt, diags
   190  
   191  	case AbsResourceInstance: // Catch likely user error with specialized message
   192  		// Assume that the last element of the traversal must be the index,
   193  		// since that's required for a valid resource instance address.
   194  		indexStep := traversal[len(traversal)-1]
   195  		diags = diags.Append(&hcl.Diagnostic{
   196  			Severity: hcl.DiagError,
   197  			Summary:  "Invalid address",
   198  			Detail:   "A resource address is required. This instance key identifies a specific resource instance, which is not expected here.",
   199  			Subject:  indexStep.SourceRange().Ptr(),
   200  		})
   201  		return AbsResource{}, diags
   202  
   203  	case ModuleInstance: // Catch likely user error with specialized message
   204  		diags = diags.Append(&hcl.Diagnostic{
   205  			Severity: hcl.DiagError,
   206  			Summary:  "Invalid address",
   207  			Detail:   "A resource address is required here. The module path must be followed by a resource specification.",
   208  			Subject:  traversal.SourceRange().Ptr(),
   209  		})
   210  		return AbsResource{}, diags
   211  
   212  	default: // Generic message for other address types
   213  		diags = diags.Append(&hcl.Diagnostic{
   214  			Severity: hcl.DiagError,
   215  			Summary:  "Invalid address",
   216  			Detail:   "A resource address is required here.",
   217  			Subject:  traversal.SourceRange().Ptr(),
   218  		})
   219  		return AbsResource{}, diags
   220  
   221  	}
   222  }
   223  
   224  // ParseAbsResourceStr is a helper wrapper around ParseAbsResource that takes a
   225  // string and parses it with the HCL native syntax traversal parser before
   226  // interpreting it.
   227  //
   228  // Error diagnostics are returned if either the parsing fails or the analysis
   229  // of the traversal fails. There is no way for the caller to distinguish the
   230  // two kinds of diagnostics programmatically. If error diagnostics are returned
   231  // the returned address may be incomplete.
   232  //
   233  // Since this function has no context about the source of the given string,
   234  // any returned diagnostics will not have meaningful source location
   235  // information.
   236  func ParseAbsResourceStr(str string) (AbsResource, tfdiags.Diagnostics) {
   237  	var diags tfdiags.Diagnostics
   238  
   239  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   240  	diags = diags.Append(parseDiags)
   241  	if parseDiags.HasErrors() {
   242  		return AbsResource{}, diags
   243  	}
   244  
   245  	addr, addrDiags := ParseAbsResource(traversal)
   246  	diags = diags.Append(addrDiags)
   247  	return addr, diags
   248  }
   249  
   250  // ParseAbsResourceInstance attempts to interpret the given traversal as an
   251  // absolute resource instance address, using the same syntax as expected by
   252  // ParseTarget.
   253  //
   254  // If no error diagnostics are returned, the returned target includes the
   255  // address that was extracted and the source range it was extracted from.
   256  //
   257  // If error diagnostics are returned then the AbsResource value is invalid and
   258  // must not be used.
   259  func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) {
   260  	addr, diags := ParseTarget(traversal)
   261  	if diags.HasErrors() {
   262  		return AbsResourceInstance{}, diags
   263  	}
   264  
   265  	switch tt := addr.Subject.(type) {
   266  
   267  	case AbsResource:
   268  		return tt.Instance(NoKey), diags
   269  
   270  	case AbsResourceInstance:
   271  		return tt, diags
   272  
   273  	case ModuleInstance: // Catch likely user error with specialized message
   274  		diags = diags.Append(&hcl.Diagnostic{
   275  			Severity: hcl.DiagError,
   276  			Summary:  "Invalid address",
   277  			Detail:   "A resource instance address is required here. The module path must be followed by a resource instance specification.",
   278  			Subject:  traversal.SourceRange().Ptr(),
   279  		})
   280  		return AbsResourceInstance{}, diags
   281  
   282  	default: // Generic message for other address types
   283  		diags = diags.Append(&hcl.Diagnostic{
   284  			Severity: hcl.DiagError,
   285  			Summary:  "Invalid address",
   286  			Detail:   "A resource address is required here.",
   287  			Subject:  traversal.SourceRange().Ptr(),
   288  		})
   289  		return AbsResourceInstance{}, diags
   290  
   291  	}
   292  }
   293  
   294  // ParseAbsResourceInstanceStr is a helper wrapper around
   295  // ParseAbsResourceInstance that takes a string and parses it with the HCL
   296  // native syntax traversal parser before interpreting it.
   297  //
   298  // Error diagnostics are returned if either the parsing fails or the analysis
   299  // of the traversal fails. There is no way for the caller to distinguish the
   300  // two kinds of diagnostics programmatically. If error diagnostics are returned
   301  // the returned address may be incomplete.
   302  //
   303  // Since this function has no context about the source of the given string,
   304  // any returned diagnostics will not have meaningful source location
   305  // information.
   306  func ParseAbsResourceInstanceStr(str string) (AbsResourceInstance, tfdiags.Diagnostics) {
   307  	var diags tfdiags.Diagnostics
   308  
   309  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   310  	diags = diags.Append(parseDiags)
   311  	if parseDiags.HasErrors() {
   312  		return AbsResourceInstance{}, diags
   313  	}
   314  
   315  	addr, addrDiags := ParseAbsResourceInstance(traversal)
   316  	diags = diags.Append(addrDiags)
   317  	return addr, diags
   318  }