github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-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/muratcelep/terraform/not-internal/addrs"
    11  	"github.com/muratcelep/terraform/not-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 not-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  
    50  	provider := &Provider{
    51  		Name:      name,
    52  		NameRange: block.LabelRanges[0],
    53  		Config:    config,
    54  		DeclRange: block.DefRange,
    55  	}
    56  
    57  	if attr, exists := content.Attributes["alias"]; exists {
    58  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &provider.Alias)
    59  		diags = append(diags, valDiags...)
    60  		provider.AliasRange = attr.Expr.Range().Ptr()
    61  
    62  		if !hclsyntax.ValidIdentifier(provider.Alias) {
    63  			diags = append(diags, &hcl.Diagnostic{
    64  				Severity: hcl.DiagError,
    65  				Summary:  "Invalid provider configuration alias",
    66  				Detail:   fmt.Sprintf("An alias must be a valid name. %s", badIdentifierDetail),
    67  			})
    68  		}
    69  	}
    70  
    71  	if attr, exists := content.Attributes["version"]; exists {
    72  		diags = append(diags, &hcl.Diagnostic{
    73  			Severity: hcl.DiagWarning,
    74  			Summary:  "Version constraints inside provider configuration blocks are deprecated",
    75  			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.",
    76  			Subject:  attr.Expr.Range().Ptr(),
    77  		})
    78  		var versionDiags hcl.Diagnostics
    79  		provider.Version, versionDiags = decodeVersionConstraint(attr)
    80  		diags = append(diags, versionDiags...)
    81  	}
    82  
    83  	// Reserved attribute names
    84  	for _, name := range []string{"count", "depends_on", "for_each", "source"} {
    85  		if attr, exists := content.Attributes[name]; exists {
    86  			diags = append(diags, &hcl.Diagnostic{
    87  				Severity: hcl.DiagError,
    88  				Summary:  "Reserved argument name in provider block",
    89  				Detail:   fmt.Sprintf("The provider argument name %q is reserved for use by Terraform in a future version.", name),
    90  				Subject:  &attr.NameRange,
    91  			})
    92  		}
    93  	}
    94  
    95  	var seenEscapeBlock *hcl.Block
    96  	for _, block := range content.Blocks {
    97  		switch block.Type {
    98  		case "_":
    99  			if seenEscapeBlock != nil {
   100  				diags = append(diags, &hcl.Diagnostic{
   101  					Severity: hcl.DiagError,
   102  					Summary:  "Duplicate escaping block",
   103  					Detail: fmt.Sprintf(
   104  						"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.",
   105  						seenEscapeBlock.DefRange,
   106  					),
   107  					Subject: &block.DefRange,
   108  				})
   109  				continue
   110  			}
   111  			seenEscapeBlock = block
   112  
   113  			// When there's an escaping block its content merges with the
   114  			// existing config we extracted earlier, so later decoding
   115  			// will see a blend of both.
   116  			provider.Config = hcl.MergeBodies([]hcl.Body{provider.Config, block.Body})
   117  
   118  		default:
   119  			// All of the other block types in our schema are reserved for
   120  			// future expansion.
   121  			diags = append(diags, &hcl.Diagnostic{
   122  				Severity: hcl.DiagError,
   123  				Summary:  "Reserved block type name in provider block",
   124  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   125  				Subject:  &block.TypeRange,
   126  			})
   127  		}
   128  	}
   129  
   130  	return provider, diags
   131  }
   132  
   133  // Addr returns the address of the receiving provider configuration, relative
   134  // to its containing module.
   135  func (p *Provider) Addr() addrs.LocalProviderConfig {
   136  	return addrs.LocalProviderConfig{
   137  		LocalName: p.Name,
   138  		Alias:     p.Alias,
   139  	}
   140  }
   141  
   142  func (p *Provider) moduleUniqueKey() string {
   143  	if p.Alias != "" {
   144  		return fmt.Sprintf("%s.%s", p.Name, p.Alias)
   145  	}
   146  	return p.Name
   147  }
   148  
   149  // ParseProviderConfigCompact parses the given absolute traversal as a relative
   150  // provider address in compact form. The following are examples of traversals
   151  // that can be successfully parsed as compact relative provider configuration
   152  // addresses:
   153  //
   154  //     aws
   155  //     aws.foo
   156  //
   157  // This function will panic if given a relative traversal.
   158  //
   159  // If the returned diagnostics contains errors then the result value is invalid
   160  // and must not be used.
   161  func ParseProviderConfigCompact(traversal hcl.Traversal) (addrs.LocalProviderConfig, tfdiags.Diagnostics) {
   162  	var diags tfdiags.Diagnostics
   163  	ret := addrs.LocalProviderConfig{
   164  		LocalName: traversal.RootName(),
   165  	}
   166  
   167  	if len(traversal) < 2 {
   168  		// Just a type name, then.
   169  		return ret, diags
   170  	}
   171  
   172  	aliasStep := traversal[1]
   173  	switch ts := aliasStep.(type) {
   174  	case hcl.TraverseAttr:
   175  		ret.Alias = ts.Name
   176  		return ret, diags
   177  	default:
   178  		diags = diags.Append(&hcl.Diagnostic{
   179  			Severity: hcl.DiagError,
   180  			Summary:  "Invalid provider configuration address",
   181  			Detail:   "The provider type name must either stand alone or be followed by an alias name separated with a dot.",
   182  			Subject:  aliasStep.SourceRange().Ptr(),
   183  		})
   184  	}
   185  
   186  	if len(traversal) > 2 {
   187  		diags = diags.Append(&hcl.Diagnostic{
   188  			Severity: hcl.DiagError,
   189  			Summary:  "Invalid provider configuration address",
   190  			Detail:   "Extraneous extra operators after provider configuration address.",
   191  			Subject:  traversal[2:].SourceRange().Ptr(),
   192  		})
   193  	}
   194  
   195  	return ret, diags
   196  }
   197  
   198  // ParseProviderConfigCompactStr is a helper wrapper around ParseProviderConfigCompact
   199  // that takes a string and parses it with the HCL native syntax traversal parser
   200  // before interpreting it.
   201  //
   202  // This should be used only in specialized situations since it will cause the
   203  // created references to not have any meaningful source location information.
   204  // If a reference string is coming from a source that should be identified in
   205  // error messages then the caller should instead parse it directly using a
   206  // suitable function from the HCL API and pass the traversal itself to
   207  // ParseProviderConfigCompact.
   208  //
   209  // Error diagnostics are returned if either the parsing fails or the analysis
   210  // of the traversal fails. There is no way for the caller to distinguish the
   211  // two kinds of diagnostics programmatically. If error diagnostics are returned
   212  // then the returned address is invalid.
   213  func ParseProviderConfigCompactStr(str string) (addrs.LocalProviderConfig, tfdiags.Diagnostics) {
   214  	var diags tfdiags.Diagnostics
   215  
   216  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   217  	diags = diags.Append(parseDiags)
   218  	if parseDiags.HasErrors() {
   219  		return addrs.LocalProviderConfig{}, diags
   220  	}
   221  
   222  	addr, addrDiags := ParseProviderConfigCompact(traversal)
   223  	diags = diags.Append(addrDiags)
   224  	return addr, diags
   225  }
   226  
   227  var providerBlockSchema = &hcl.BodySchema{
   228  	Attributes: []hcl.AttributeSchema{
   229  		{
   230  			Name: "alias",
   231  		},
   232  		{
   233  			Name: "version",
   234  		},
   235  
   236  		// Attribute names reserved for future expansion.
   237  		{Name: "count"},
   238  		{Name: "depends_on"},
   239  		{Name: "for_each"},
   240  		{Name: "source"},
   241  	},
   242  	Blocks: []hcl.BlockHeaderSchema{
   243  		{Type: "_"}, // meta-argument escaping block
   244  
   245  		// The rest of these are reserved for future expansion.
   246  		{Type: "lifecycle"},
   247  		{Type: "locals"},
   248  	},
   249  }
   250  
   251  // checkProviderNameNormalized verifies that the given string is already
   252  // normalized and returns an error if not.
   253  func checkProviderNameNormalized(name string, declrange hcl.Range) hcl.Diagnostics {
   254  	var diags hcl.Diagnostics
   255  	// verify that the provider local name is normalized
   256  	normalized, err := addrs.IsProviderPartNormalized(name)
   257  	if err != nil {
   258  		diags = append(diags, &hcl.Diagnostic{
   259  			Severity: hcl.DiagError,
   260  			Summary:  "Invalid provider local name",
   261  			Detail:   fmt.Sprintf("%s is an invalid provider local name: %s", name, err),
   262  			Subject:  &declrange,
   263  		})
   264  		return diags
   265  	}
   266  	if !normalized {
   267  		// we would have returned this error already
   268  		normalizedProvider, _ := addrs.ParseProviderPart(name)
   269  		diags = append(diags, &hcl.Diagnostic{
   270  			Severity: hcl.DiagError,
   271  			Summary:  "Invalid provider local name",
   272  			Detail:   fmt.Sprintf("Provider names must be normalized. Replace %q with %q to fix this error.", name, normalizedProvider),
   273  			Subject:  &declrange,
   274  		})
   275  	}
   276  	return diags
   277  }