github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/configs/provider.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/hcl/v2/gohcl"
     8  	"github.com/hashicorp/hcl/v2/hclsyntax"
     9  
    10  	"github.com/hashicorp/terraform/internal/addrs"
    11  	"github.com/hashicorp/terraform/internal/tfdiags"
    12  )
    13  
    14  // Provider represents a "provider" block in a module or file. A provider
    15  // block is a provider configuration, and there can be zero or more
    16  // configurations for each actual provider.
    17  type Provider struct {
    18  	Name       string
    19  	NameRange  hcl.Range
    20  	Alias      string
    21  	AliasRange *hcl.Range // nil if no alias set
    22  
    23  	Version VersionConstraint
    24  
    25  	Config hcl.Body
    26  
    27  	DeclRange hcl.Range
    28  
    29  	// TODO: this may not be set in some cases, so it is not yet suitable for
    30  	// use outside of this package. We currently only use it for internal
    31  	// validation, but once we verify that this can be set in all cases, we can
    32  	// export this so providers don't need to be re-resolved.
    33  	// This same field is also added to the ProviderConfigRef struct.
    34  	providerType addrs.Provider
    35  }
    36  
    37  func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) {
    38  	var diags hcl.Diagnostics
    39  
    40  	content, config, moreDiags := block.Body.PartialContent(providerBlockSchema)
    41  	diags = append(diags, moreDiags...)
    42  
    43  	// Provider names must be localized. Produce an error with a message
    44  	// indicating the action the user can take to fix this message if the local
    45  	// name is not localized.
    46  	name := block.Labels[0]
    47  	nameDiags := checkProviderNameNormalized(name, block.DefRange)
    48  	diags = append(diags, nameDiags...)
    49  	if nameDiags.HasErrors() {
    50  		// If the name is invalid then we mustn't produce a result because
    51  		// downstreams could try to use it as a provider type and then crash.
    52  		return nil, diags
    53  	}
    54  
    55  	provider := &Provider{
    56  		Name:      name,
    57  		NameRange: block.LabelRanges[0],
    58  		Config:    config,
    59  		DeclRange: block.DefRange,
    60  	}
    61  
    62  	if attr, exists := content.Attributes["alias"]; exists {
    63  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &provider.Alias)
    64  		diags = append(diags, valDiags...)
    65  		provider.AliasRange = attr.Expr.Range().Ptr()
    66  
    67  		if !hclsyntax.ValidIdentifier(provider.Alias) {
    68  			diags = append(diags, &hcl.Diagnostic{
    69  				Severity: hcl.DiagError,
    70  				Summary:  "Invalid provider configuration alias",
    71  				Detail:   fmt.Sprintf("An alias must be a valid name. %s", badIdentifierDetail),
    72  			})
    73  		}
    74  	}
    75  
    76  	if attr, exists := content.Attributes["version"]; exists {
    77  		diags = append(diags, &hcl.Diagnostic{
    78  			Severity: hcl.DiagWarning,
    79  			Summary:  "Version constraints inside provider configuration blocks are deprecated",
    80  			Detail:   "Terraform 0.13 and earlier allowed provider version constraints inside the provider configuration block, but that is now deprecated and will be removed in a future version of Terraform. To silence this warning, move the provider version constraint into the required_providers block.",
    81  			Subject:  attr.Expr.Range().Ptr(),
    82  		})
    83  		var versionDiags hcl.Diagnostics
    84  		provider.Version, versionDiags = decodeVersionConstraint(attr)
    85  		diags = append(diags, versionDiags...)
    86  	}
    87  
    88  	// Reserved attribute names
    89  	for _, name := range []string{"count", "depends_on", "for_each", "source"} {
    90  		if attr, exists := content.Attributes[name]; exists {
    91  			diags = append(diags, &hcl.Diagnostic{
    92  				Severity: hcl.DiagError,
    93  				Summary:  "Reserved argument name in provider block",
    94  				Detail:   fmt.Sprintf("The provider argument name %q is reserved for use by Terraform in a future version.", name),
    95  				Subject:  &attr.NameRange,
    96  			})
    97  		}
    98  	}
    99  
   100  	var seenEscapeBlock *hcl.Block
   101  	for _, block := range content.Blocks {
   102  		switch block.Type {
   103  		case "_":
   104  			if seenEscapeBlock != nil {
   105  				diags = append(diags, &hcl.Diagnostic{
   106  					Severity: hcl.DiagError,
   107  					Summary:  "Duplicate escaping block",
   108  					Detail: fmt.Sprintf(
   109  						"The special block type \"_\" can be used to force particular arguments to be interpreted as provider-specific rather than as meta-arguments, but each provider block can have only one such block. The first escaping block was at %s.",
   110  						seenEscapeBlock.DefRange,
   111  					),
   112  					Subject: &block.DefRange,
   113  				})
   114  				continue
   115  			}
   116  			seenEscapeBlock = block
   117  
   118  			// When there's an escaping block its content merges with the
   119  			// existing config we extracted earlier, so later decoding
   120  			// will see a blend of both.
   121  			provider.Config = hcl.MergeBodies([]hcl.Body{provider.Config, block.Body})
   122  
   123  		default:
   124  			// All of the other block types in our schema are reserved for
   125  			// future expansion.
   126  			diags = append(diags, &hcl.Diagnostic{
   127  				Severity: hcl.DiagError,
   128  				Summary:  "Reserved block type name in provider block",
   129  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   130  				Subject:  &block.TypeRange,
   131  			})
   132  		}
   133  	}
   134  
   135  	return provider, diags
   136  }
   137  
   138  // Addr returns the address of the receiving provider configuration, relative
   139  // to its containing module.
   140  func (p *Provider) Addr() addrs.LocalProviderConfig {
   141  	return addrs.LocalProviderConfig{
   142  		LocalName: p.Name,
   143  		Alias:     p.Alias,
   144  	}
   145  }
   146  
   147  func (p *Provider) moduleUniqueKey() string {
   148  	if p.Alias != "" {
   149  		return fmt.Sprintf("%s.%s", p.Name, p.Alias)
   150  	}
   151  	return p.Name
   152  }
   153  
   154  // ParseProviderConfigCompact parses the given absolute traversal as a relative
   155  // provider address in compact form. The following are examples of traversals
   156  // that can be successfully parsed as compact relative provider configuration
   157  // addresses:
   158  //
   159  //   - aws
   160  //   - aws.foo
   161  //
   162  // This function will panic if given a relative traversal.
   163  //
   164  // If the returned diagnostics contains errors then the result value is invalid
   165  // and must not be used.
   166  func ParseProviderConfigCompact(traversal hcl.Traversal) (addrs.LocalProviderConfig, tfdiags.Diagnostics) {
   167  	var diags tfdiags.Diagnostics
   168  	ret := addrs.LocalProviderConfig{
   169  		LocalName: traversal.RootName(),
   170  	}
   171  
   172  	if len(traversal) < 2 {
   173  		// Just a type name, then.
   174  		return ret, diags
   175  	}
   176  
   177  	aliasStep := traversal[1]
   178  	switch ts := aliasStep.(type) {
   179  	case hcl.TraverseAttr:
   180  		ret.Alias = ts.Name
   181  		return ret, diags
   182  	default:
   183  		diags = diags.Append(&hcl.Diagnostic{
   184  			Severity: hcl.DiagError,
   185  			Summary:  "Invalid provider configuration address",
   186  			Detail:   "The provider type name must either stand alone or be followed by an alias name separated with a dot.",
   187  			Subject:  aliasStep.SourceRange().Ptr(),
   188  		})
   189  	}
   190  
   191  	if len(traversal) > 2 {
   192  		diags = diags.Append(&hcl.Diagnostic{
   193  			Severity: hcl.DiagError,
   194  			Summary:  "Invalid provider configuration address",
   195  			Detail:   "Extraneous extra operators after provider configuration address.",
   196  			Subject:  traversal[2:].SourceRange().Ptr(),
   197  		})
   198  	}
   199  
   200  	return ret, diags
   201  }
   202  
   203  // ParseProviderConfigCompactStr is a helper wrapper around ParseProviderConfigCompact
   204  // that takes a string and parses it with the HCL native syntax traversal parser
   205  // before interpreting it.
   206  //
   207  // This should be used only in specialized situations since it will cause the
   208  // created references to not have any meaningful source location information.
   209  // If a reference string is coming from a source that should be identified in
   210  // error messages then the caller should instead parse it directly using a
   211  // suitable function from the HCL API and pass the traversal itself to
   212  // ParseProviderConfigCompact.
   213  //
   214  // Error diagnostics are returned if either the parsing fails or the analysis
   215  // of the traversal fails. There is no way for the caller to distinguish the
   216  // two kinds of diagnostics programmatically. If error diagnostics are returned
   217  // then the returned address is invalid.
   218  func ParseProviderConfigCompactStr(str string) (addrs.LocalProviderConfig, tfdiags.Diagnostics) {
   219  	var diags tfdiags.Diagnostics
   220  
   221  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   222  	diags = diags.Append(parseDiags)
   223  	if parseDiags.HasErrors() {
   224  		return addrs.LocalProviderConfig{}, diags
   225  	}
   226  
   227  	addr, addrDiags := ParseProviderConfigCompact(traversal)
   228  	diags = diags.Append(addrDiags)
   229  	return addr, diags
   230  }
   231  
   232  var providerBlockSchema = &hcl.BodySchema{
   233  	Attributes: []hcl.AttributeSchema{
   234  		{
   235  			Name: "alias",
   236  		},
   237  		{
   238  			Name: "version",
   239  		},
   240  
   241  		// Attribute names reserved for future expansion.
   242  		{Name: "count"},
   243  		{Name: "depends_on"},
   244  		{Name: "for_each"},
   245  		{Name: "source"},
   246  	},
   247  	Blocks: []hcl.BlockHeaderSchema{
   248  		{Type: "_"}, // meta-argument escaping block
   249  
   250  		// The rest of these are reserved for future expansion.
   251  		{Type: "lifecycle"},
   252  		{Type: "locals"},
   253  	},
   254  }
   255  
   256  // checkProviderNameNormalized verifies that the given string is already
   257  // normalized and returns an error if not.
   258  func checkProviderNameNormalized(name string, declrange hcl.Range) hcl.Diagnostics {
   259  	var diags hcl.Diagnostics
   260  	// verify that the provider local name is normalized
   261  	normalized, err := addrs.IsProviderPartNormalized(name)
   262  	if err != nil {
   263  		diags = append(diags, &hcl.Diagnostic{
   264  			Severity: hcl.DiagError,
   265  			Summary:  "Invalid provider local name",
   266  			Detail:   fmt.Sprintf("%s is an invalid provider local name: %s", name, err),
   267  			Subject:  &declrange,
   268  		})
   269  		return diags
   270  	}
   271  	if !normalized {
   272  		// we would have returned this error already
   273  		normalizedProvider, _ := addrs.ParseProviderPart(name)
   274  		diags = append(diags, &hcl.Diagnostic{
   275  			Severity: hcl.DiagError,
   276  			Summary:  "Invalid provider local name",
   277  			Detail:   fmt.Sprintf("Provider names must be normalized. Replace %q with %q to fix this error.", name, normalizedProvider),
   278  			Subject:  &declrange,
   279  		})
   280  	}
   281  	return diags
   282  }