kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/provider_requirements.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	version "github.com/hashicorp/go-version"
     7  	"github.com/hashicorp/hcl/v2"
     8  	"kubeform.dev/terraform-backend-sdk/addrs"
     9  	"github.com/zclconf/go-cty/cty"
    10  )
    11  
    12  // RequiredProvider represents a declaration of a dependency on a particular
    13  // provider version or source without actually configuring that provider. This
    14  // is used in child modules that expect a provider to be passed in from their
    15  // parent.
    16  type RequiredProvider struct {
    17  	Name        string
    18  	Source      string
    19  	Type        addrs.Provider
    20  	Requirement VersionConstraint
    21  	DeclRange   hcl.Range
    22  	Aliases     []addrs.LocalProviderConfig
    23  }
    24  
    25  type RequiredProviders struct {
    26  	RequiredProviders map[string]*RequiredProvider
    27  	DeclRange         hcl.Range
    28  }
    29  
    30  func decodeRequiredProvidersBlock(block *hcl.Block) (*RequiredProviders, hcl.Diagnostics) {
    31  	attrs, diags := block.Body.JustAttributes()
    32  	if diags.HasErrors() {
    33  		return nil, diags
    34  	}
    35  
    36  	ret := &RequiredProviders{
    37  		RequiredProviders: make(map[string]*RequiredProvider),
    38  		DeclRange:         block.DefRange,
    39  	}
    40  
    41  	for name, attr := range attrs {
    42  		rp := &RequiredProvider{
    43  			Name:      name,
    44  			DeclRange: attr.Expr.Range(),
    45  		}
    46  
    47  		// Look for a single static string, in case we have the legacy version-only
    48  		// format in the configuration.
    49  		if expr, err := attr.Expr.Value(nil); err == nil && expr.Type().IsPrimitiveType() {
    50  			vc, reqDiags := decodeVersionConstraint(attr)
    51  			diags = append(diags, reqDiags...)
    52  
    53  			pType, err := addrs.ParseProviderPart(rp.Name)
    54  			if err != nil {
    55  				diags = append(diags, &hcl.Diagnostic{
    56  					Severity: hcl.DiagError,
    57  					Summary:  "Invalid provider name",
    58  					Detail:   err.Error(),
    59  					Subject:  attr.Expr.Range().Ptr(),
    60  				})
    61  				continue
    62  			}
    63  
    64  			rp.Requirement = vc
    65  			rp.Type = addrs.ImpliedProviderForUnqualifiedType(pType)
    66  			ret.RequiredProviders[name] = rp
    67  
    68  			continue
    69  		}
    70  
    71  		// verify that the local name is already localized or produce an error.
    72  		nameDiags := checkProviderNameNormalized(name, attr.Expr.Range())
    73  		if nameDiags.HasErrors() {
    74  			diags = append(diags, nameDiags...)
    75  			continue
    76  		}
    77  
    78  		kvs, mapDiags := hcl.ExprMap(attr.Expr)
    79  		if mapDiags.HasErrors() {
    80  			diags = append(diags, &hcl.Diagnostic{
    81  				Severity: hcl.DiagError,
    82  				Summary:  "Invalid required_providers object",
    83  				Detail:   "required_providers entries must be strings or objects.",
    84  				Subject:  attr.Expr.Range().Ptr(),
    85  			})
    86  			continue
    87  		}
    88  
    89  	LOOP:
    90  		for _, kv := range kvs {
    91  			key, keyDiags := kv.Key.Value(nil)
    92  			if keyDiags.HasErrors() {
    93  				diags = append(diags, keyDiags...)
    94  				continue
    95  			}
    96  
    97  			if key.Type() != cty.String {
    98  				diags = append(diags, &hcl.Diagnostic{
    99  					Severity: hcl.DiagError,
   100  					Summary:  "Invalid Attribute",
   101  					Detail:   fmt.Sprintf("Invalid attribute value for provider requirement: %#v", key),
   102  					Subject:  kv.Key.Range().Ptr(),
   103  				})
   104  				continue
   105  			}
   106  
   107  			switch key.AsString() {
   108  			case "version":
   109  				vc := VersionConstraint{
   110  					DeclRange: attr.Range,
   111  				}
   112  
   113  				constraint, valDiags := kv.Value.Value(nil)
   114  				if valDiags.HasErrors() || !constraint.Type().Equals(cty.String) {
   115  					diags = append(diags, &hcl.Diagnostic{
   116  						Severity: hcl.DiagError,
   117  						Summary:  "Invalid version constraint",
   118  						Detail:   "Version must be specified as a string.",
   119  						Subject:  kv.Value.Range().Ptr(),
   120  					})
   121  					continue
   122  				}
   123  
   124  				constraintStr := constraint.AsString()
   125  				constraints, err := version.NewConstraint(constraintStr)
   126  				if err != nil {
   127  					// NewConstraint doesn't return user-friendly errors, so we'll just
   128  					// ignore the provided error and produce our own generic one.
   129  					diags = append(diags, &hcl.Diagnostic{
   130  						Severity: hcl.DiagError,
   131  						Summary:  "Invalid version constraint",
   132  						Detail:   "This string does not use correct version constraint syntax.",
   133  						Subject:  kv.Value.Range().Ptr(),
   134  					})
   135  					continue
   136  				}
   137  
   138  				vc.Required = constraints
   139  				rp.Requirement = vc
   140  
   141  			case "source":
   142  				source, err := kv.Value.Value(nil)
   143  				if err != nil || !source.Type().Equals(cty.String) {
   144  					diags = append(diags, &hcl.Diagnostic{
   145  						Severity: hcl.DiagError,
   146  						Summary:  "Invalid source",
   147  						Detail:   "Source must be specified as a string.",
   148  						Subject:  kv.Value.Range().Ptr(),
   149  					})
   150  					continue
   151  				}
   152  
   153  				fqn, sourceDiags := addrs.ParseProviderSourceString(source.AsString())
   154  				if sourceDiags.HasErrors() {
   155  					hclDiags := sourceDiags.ToHCL()
   156  					// The diagnostics from ParseProviderSourceString don't contain
   157  					// source location information because it has no context to compute
   158  					// them from, and so we'll add those in quickly here before we
   159  					// return.
   160  					for _, diag := range hclDiags {
   161  						if diag.Subject == nil {
   162  							diag.Subject = kv.Value.Range().Ptr()
   163  						}
   164  					}
   165  					diags = append(diags, hclDiags...)
   166  					continue
   167  				}
   168  
   169  				rp.Source = source.AsString()
   170  				rp.Type = fqn
   171  
   172  			case "configuration_aliases":
   173  				exprs, listDiags := hcl.ExprList(kv.Value)
   174  				if listDiags.HasErrors() {
   175  					diags = append(diags, listDiags...)
   176  					continue
   177  				}
   178  
   179  				for _, expr := range exprs {
   180  					traversal, travDiags := hcl.AbsTraversalForExpr(expr)
   181  					if travDiags.HasErrors() {
   182  						diags = append(diags, travDiags...)
   183  						continue
   184  					}
   185  
   186  					addr, cfgDiags := ParseProviderConfigCompact(traversal)
   187  					if cfgDiags.HasErrors() {
   188  						diags = append(diags, &hcl.Diagnostic{
   189  							Severity: hcl.DiagError,
   190  							Summary:  "Invalid configuration_aliases value",
   191  							Detail:   `Configuration aliases can only contain references to local provider configuration names in the format of provider.alias`,
   192  							Subject:  kv.Value.Range().Ptr(),
   193  						})
   194  						continue
   195  					}
   196  
   197  					if addr.LocalName != name {
   198  						diags = append(diags, &hcl.Diagnostic{
   199  							Severity: hcl.DiagError,
   200  							Summary:  "Invalid configuration_aliases value",
   201  							Detail:   fmt.Sprintf(`Configuration aliases must be prefixed with the provider name. Expected %q, but found %q.`, name, addr.LocalName),
   202  							Subject:  kv.Value.Range().Ptr(),
   203  						})
   204  						continue
   205  					}
   206  
   207  					rp.Aliases = append(rp.Aliases, addr)
   208  				}
   209  
   210  			default:
   211  				diags = append(diags, &hcl.Diagnostic{
   212  					Severity: hcl.DiagError,
   213  					Summary:  "Invalid required_providers object",
   214  					Detail:   `required_providers objects can only contain "version", "source" and "configuration_aliases" attributes. To configure a provider, use a "provider" block.`,
   215  					Subject:  kv.Key.Range().Ptr(),
   216  				})
   217  				break LOOP
   218  			}
   219  
   220  		}
   221  
   222  		if diags.HasErrors() {
   223  			continue
   224  		}
   225  
   226  		// We can add the required provider when there are no errors.
   227  		// If a source was not given, create an implied type.
   228  		if rp.Type.IsZero() {
   229  			pType, err := addrs.ParseProviderPart(rp.Name)
   230  			if err != nil {
   231  				diags = append(diags, &hcl.Diagnostic{
   232  					Severity: hcl.DiagError,
   233  					Summary:  "Invalid provider name",
   234  					Detail:   err.Error(),
   235  					Subject:  attr.Expr.Range().Ptr(),
   236  				})
   237  			} else {
   238  				rp.Type = addrs.ImpliedProviderForUnqualifiedType(pType)
   239  			}
   240  		}
   241  
   242  		ret.RequiredProviders[rp.Name] = rp
   243  	}
   244  
   245  	return ret, diags
   246  }