github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/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/cycloidio/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  	riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
    43  	diags = diags.Append(moreDiags)
    44  	if diags.HasErrors() {
    45  		return nil, diags
    46  	}
    47  
    48  	var subject Targetable
    49  	switch {
    50  	case riAddr.Resource.Key == NoKey:
    51  		// We always assume that a no-key instance is meant to
    52  		// be referring to the whole resource, because the distinction
    53  		// doesn't really matter for targets anyway.
    54  		subject = riAddr.ContainingResource()
    55  	default:
    56  		subject = riAddr
    57  	}
    58  
    59  	return &Target{
    60  		Subject:     subject,
    61  		SourceRange: rng,
    62  	}, diags
    63  }
    64  
    65  func parseResourceInstanceUnderModule(moduleAddr ModuleInstance, remain hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) {
    66  	// Note that this helper is used as part of both ParseTarget and
    67  	// ParseMoveEndpoint, so its error messages should be generic
    68  	// enough to suit both situations.
    69  
    70  	var diags tfdiags.Diagnostics
    71  
    72  	mode := ManagedResourceMode
    73  	if remain.RootName() == "data" {
    74  		mode = DataResourceMode
    75  		remain = remain[1:]
    76  	}
    77  
    78  	if len(remain) < 2 {
    79  		diags = diags.Append(&hcl.Diagnostic{
    80  			Severity: hcl.DiagError,
    81  			Summary:  "Invalid address",
    82  			Detail:   "Resource specification must include a resource type and name.",
    83  			Subject:  remain.SourceRange().Ptr(),
    84  		})
    85  		return AbsResourceInstance{}, diags
    86  	}
    87  
    88  	var typeName, name string
    89  	switch tt := remain[0].(type) {
    90  	case hcl.TraverseRoot:
    91  		typeName = tt.Name
    92  	case hcl.TraverseAttr:
    93  		typeName = tt.Name
    94  	default:
    95  		switch mode {
    96  		case ManagedResourceMode:
    97  			diags = diags.Append(&hcl.Diagnostic{
    98  				Severity: hcl.DiagError,
    99  				Summary:  "Invalid address",
   100  				Detail:   "A resource type name is required.",
   101  				Subject:  remain[0].SourceRange().Ptr(),
   102  			})
   103  		case DataResourceMode:
   104  			diags = diags.Append(&hcl.Diagnostic{
   105  				Severity: hcl.DiagError,
   106  				Summary:  "Invalid address",
   107  				Detail:   "A data source name is required.",
   108  				Subject:  remain[0].SourceRange().Ptr(),
   109  			})
   110  		default:
   111  			panic("unknown mode")
   112  		}
   113  		return AbsResourceInstance{}, diags
   114  	}
   115  
   116  	switch tt := remain[1].(type) {
   117  	case hcl.TraverseAttr:
   118  		name = tt.Name
   119  	default:
   120  		diags = diags.Append(&hcl.Diagnostic{
   121  			Severity: hcl.DiagError,
   122  			Summary:  "Invalid address",
   123  			Detail:   "A resource name is required.",
   124  			Subject:  remain[1].SourceRange().Ptr(),
   125  		})
   126  		return AbsResourceInstance{}, diags
   127  	}
   128  
   129  	remain = remain[2:]
   130  	switch len(remain) {
   131  	case 0:
   132  		return moduleAddr.ResourceInstance(mode, typeName, name, NoKey), diags
   133  	case 1:
   134  		if tt, ok := remain[0].(hcl.TraverseIndex); ok {
   135  			key, err := ParseInstanceKey(tt.Key)
   136  			if err != nil {
   137  				diags = diags.Append(&hcl.Diagnostic{
   138  					Severity: hcl.DiagError,
   139  					Summary:  "Invalid address",
   140  					Detail:   fmt.Sprintf("Invalid resource instance key: %s.", err),
   141  					Subject:  remain[0].SourceRange().Ptr(),
   142  				})
   143  				return AbsResourceInstance{}, diags
   144  			}
   145  
   146  			return moduleAddr.ResourceInstance(mode, typeName, name, key), diags
   147  		} else {
   148  			diags = diags.Append(&hcl.Diagnostic{
   149  				Severity: hcl.DiagError,
   150  				Summary:  "Invalid address",
   151  				Detail:   "Resource instance key must be given in square brackets.",
   152  				Subject:  remain[0].SourceRange().Ptr(),
   153  			})
   154  			return AbsResourceInstance{}, diags
   155  		}
   156  	default:
   157  		diags = diags.Append(&hcl.Diagnostic{
   158  			Severity: hcl.DiagError,
   159  			Summary:  "Invalid address",
   160  			Detail:   "Unexpected extra operators after address.",
   161  			Subject:  remain[1].SourceRange().Ptr(),
   162  		})
   163  		return AbsResourceInstance{}, diags
   164  	}
   165  }
   166  
   167  // ParseTargetStr is a helper wrapper around ParseTarget that takes a string
   168  // and parses it with the HCL native syntax traversal parser before
   169  // interpreting it.
   170  //
   171  // This should be used only in specialized situations since it will cause the
   172  // created references to not have any meaningful source location information.
   173  // If a target string is coming from a source that should be identified in
   174  // error messages then the caller should instead parse it directly using a
   175  // suitable function from the HCL API and pass the traversal itself to
   176  // ParseTarget.
   177  //
   178  // Error diagnostics are returned if either the parsing fails or the analysis
   179  // of the traversal fails. There is no way for the caller to distinguish the
   180  // two kinds of diagnostics programmatically. If error diagnostics are returned
   181  // the returned target may be nil or incomplete.
   182  func ParseTargetStr(str string) (*Target, tfdiags.Diagnostics) {
   183  	var diags tfdiags.Diagnostics
   184  
   185  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   186  	diags = diags.Append(parseDiags)
   187  	if parseDiags.HasErrors() {
   188  		return nil, diags
   189  	}
   190  
   191  	target, targetDiags := ParseTarget(traversal)
   192  	diags = diags.Append(targetDiags)
   193  	return target, diags
   194  }
   195  
   196  // ParseAbsResource attempts to interpret the given traversal as an absolute
   197  // resource address, using the same syntax as expected by ParseTarget.
   198  //
   199  // If no error diagnostics are returned, the returned target includes the
   200  // address that was extracted and the source range it was extracted from.
   201  //
   202  // If error diagnostics are returned then the AbsResource value is invalid and
   203  // must not be used.
   204  func ParseAbsResource(traversal hcl.Traversal) (AbsResource, tfdiags.Diagnostics) {
   205  	addr, diags := ParseTarget(traversal)
   206  	if diags.HasErrors() {
   207  		return AbsResource{}, diags
   208  	}
   209  
   210  	switch tt := addr.Subject.(type) {
   211  
   212  	case AbsResource:
   213  		return tt, diags
   214  
   215  	case AbsResourceInstance: // Catch likely user error with specialized message
   216  		// Assume that the last element of the traversal must be the index,
   217  		// since that's required for a valid resource instance address.
   218  		indexStep := traversal[len(traversal)-1]
   219  		diags = diags.Append(&hcl.Diagnostic{
   220  			Severity: hcl.DiagError,
   221  			Summary:  "Invalid address",
   222  			Detail:   "A resource address is required. This instance key identifies a specific resource instance, which is not expected here.",
   223  			Subject:  indexStep.SourceRange().Ptr(),
   224  		})
   225  		return AbsResource{}, diags
   226  
   227  	case ModuleInstance: // Catch likely user error with specialized message
   228  		diags = diags.Append(&hcl.Diagnostic{
   229  			Severity: hcl.DiagError,
   230  			Summary:  "Invalid address",
   231  			Detail:   "A resource address is required here. The module path must be followed by a resource specification.",
   232  			Subject:  traversal.SourceRange().Ptr(),
   233  		})
   234  		return AbsResource{}, diags
   235  
   236  	default: // Generic message for other address types
   237  		diags = diags.Append(&hcl.Diagnostic{
   238  			Severity: hcl.DiagError,
   239  			Summary:  "Invalid address",
   240  			Detail:   "A resource address is required here.",
   241  			Subject:  traversal.SourceRange().Ptr(),
   242  		})
   243  		return AbsResource{}, diags
   244  
   245  	}
   246  }
   247  
   248  // ParseAbsResourceStr is a helper wrapper around ParseAbsResource that takes a
   249  // string and parses it with the HCL native syntax traversal parser before
   250  // interpreting it.
   251  //
   252  // Error diagnostics are returned if either the parsing fails or the analysis
   253  // of the traversal fails. There is no way for the caller to distinguish the
   254  // two kinds of diagnostics programmatically. If error diagnostics are returned
   255  // the returned address may be incomplete.
   256  //
   257  // Since this function has no context about the source of the given string,
   258  // any returned diagnostics will not have meaningful source location
   259  // information.
   260  func ParseAbsResourceStr(str string) (AbsResource, tfdiags.Diagnostics) {
   261  	var diags tfdiags.Diagnostics
   262  
   263  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   264  	diags = diags.Append(parseDiags)
   265  	if parseDiags.HasErrors() {
   266  		return AbsResource{}, diags
   267  	}
   268  
   269  	addr, addrDiags := ParseAbsResource(traversal)
   270  	diags = diags.Append(addrDiags)
   271  	return addr, diags
   272  }
   273  
   274  // ParseAbsResourceInstance attempts to interpret the given traversal as an
   275  // absolute resource instance address, using the same syntax as expected by
   276  // ParseTarget.
   277  //
   278  // If no error diagnostics are returned, the returned target includes the
   279  // address that was extracted and the source range it was extracted from.
   280  //
   281  // If error diagnostics are returned then the AbsResource value is invalid and
   282  // must not be used.
   283  func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) {
   284  	addr, diags := ParseTarget(traversal)
   285  	if diags.HasErrors() {
   286  		return AbsResourceInstance{}, diags
   287  	}
   288  
   289  	switch tt := addr.Subject.(type) {
   290  
   291  	case AbsResource:
   292  		return tt.Instance(NoKey), diags
   293  
   294  	case AbsResourceInstance:
   295  		return tt, diags
   296  
   297  	case ModuleInstance: // Catch likely user error with specialized message
   298  		diags = diags.Append(&hcl.Diagnostic{
   299  			Severity: hcl.DiagError,
   300  			Summary:  "Invalid address",
   301  			Detail:   "A resource instance address is required here. The module path must be followed by a resource instance specification.",
   302  			Subject:  traversal.SourceRange().Ptr(),
   303  		})
   304  		return AbsResourceInstance{}, diags
   305  
   306  	default: // Generic message for other address types
   307  		diags = diags.Append(&hcl.Diagnostic{
   308  			Severity: hcl.DiagError,
   309  			Summary:  "Invalid address",
   310  			Detail:   "A resource address is required here.",
   311  			Subject:  traversal.SourceRange().Ptr(),
   312  		})
   313  		return AbsResourceInstance{}, diags
   314  
   315  	}
   316  }
   317  
   318  // ParseAbsResourceInstanceStr is a helper wrapper around
   319  // ParseAbsResourceInstance that takes a string and parses it with the HCL
   320  // native syntax traversal parser before interpreting it.
   321  //
   322  // Error diagnostics are returned if either the parsing fails or the analysis
   323  // of the traversal fails. There is no way for the caller to distinguish the
   324  // two kinds of diagnostics programmatically. If error diagnostics are returned
   325  // the returned address may be incomplete.
   326  //
   327  // Since this function has no context about the source of the given string,
   328  // any returned diagnostics will not have meaningful source location
   329  // information.
   330  func ParseAbsResourceInstanceStr(str string) (AbsResourceInstance, tfdiags.Diagnostics) {
   331  	var diags tfdiags.Diagnostics
   332  
   333  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   334  	diags = diags.Append(parseDiags)
   335  	if parseDiags.HasErrors() {
   336  		return AbsResourceInstance{}, diags
   337  	}
   338  
   339  	addr, addrDiags := ParseAbsResourceInstance(traversal)
   340  	diags = diags.Append(addrDiags)
   341  	return addr, diags
   342  }
   343  
   344  // ModuleAddr returns the module address portion of the subject of
   345  // the recieving target.
   346  //
   347  // Regardless of specific address type, all targets always include
   348  // a module address. They might also include something in that
   349  // module, which this method always discards if so.
   350  func (t *Target) ModuleAddr() ModuleInstance {
   351  	switch addr := t.Subject.(type) {
   352  	case ModuleInstance:
   353  		return addr
   354  	case Module:
   355  		// We assume that a module address is really
   356  		// referring to a module path containing only
   357  		// single-instance modules.
   358  		return addr.UnkeyedInstanceShim()
   359  	case AbsResourceInstance:
   360  		return addr.Module
   361  	case AbsResource:
   362  		return addr.Module
   363  	default:
   364  		// The above cases should be exhaustive for all
   365  		// implementations of Targetable.
   366  		panic(fmt.Sprintf("unsupported target address type %T", addr))
   367  	}
   368  }