github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/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  	"github.com/iaas-resource-provision/iaas-rpc/internal/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  		for _, kv := range kvs {
    90  			key, keyDiags := kv.Key.Value(nil)
    91  			if keyDiags.HasErrors() {
    92  				diags = append(diags, keyDiags...)
    93  				continue
    94  			}
    95  
    96  			if key.Type() != cty.String {
    97  				diags = append(diags, &hcl.Diagnostic{
    98  					Severity: hcl.DiagError,
    99  					Summary:  "Invalid Attribute",
   100  					Detail:   fmt.Sprintf("Invalid attribute value for provider requirement: %#v", key),
   101  					Subject:  kv.Key.Range().Ptr(),
   102  				})
   103  				continue
   104  			}
   105  
   106  			switch key.AsString() {
   107  			case "version":
   108  				vc := VersionConstraint{
   109  					DeclRange: attr.Range,
   110  				}
   111  
   112  				constraint, valDiags := kv.Value.Value(nil)
   113  				if valDiags.HasErrors() || !constraint.Type().Equals(cty.String) {
   114  					diags = append(diags, &hcl.Diagnostic{
   115  						Severity: hcl.DiagError,
   116  						Summary:  "Invalid version constraint",
   117  						Detail:   "Version must be specified as a string.",
   118  						Subject:  kv.Value.Range().Ptr(),
   119  					})
   120  					continue
   121  				}
   122  
   123  				constraintStr := constraint.AsString()
   124  				constraints, err := version.NewConstraint(constraintStr)
   125  				if err != nil {
   126  					// NewConstraint doesn't return user-friendly errors, so we'll just
   127  					// ignore the provided error and produce our own generic one.
   128  					diags = append(diags, &hcl.Diagnostic{
   129  						Severity: hcl.DiagError,
   130  						Summary:  "Invalid version constraint",
   131  						Detail:   "This string does not use correct version constraint syntax.",
   132  						Subject:  kv.Value.Range().Ptr(),
   133  					})
   134  					continue
   135  				}
   136  
   137  				vc.Required = constraints
   138  				rp.Requirement = vc
   139  
   140  			case "source":
   141  				source, err := kv.Value.Value(nil)
   142  				if err != nil || !source.Type().Equals(cty.String) {
   143  					diags = append(diags, &hcl.Diagnostic{
   144  						Severity: hcl.DiagError,
   145  						Summary:  "Invalid source",
   146  						Detail:   "Source must be specified as a string.",
   147  						Subject:  kv.Value.Range().Ptr(),
   148  					})
   149  					continue
   150  				}
   151  
   152  				fqn, sourceDiags := addrs.ParseProviderSourceString(source.AsString())
   153  				if sourceDiags.HasErrors() {
   154  					hclDiags := sourceDiags.ToHCL()
   155  					// The diagnostics from ParseProviderSourceString don't contain
   156  					// source location information because it has no context to compute
   157  					// them from, and so we'll add those in quickly here before we
   158  					// return.
   159  					for _, diag := range hclDiags {
   160  						if diag.Subject == nil {
   161  							diag.Subject = kv.Value.Range().Ptr()
   162  						}
   163  					}
   164  					diags = append(diags, hclDiags...)
   165  					continue
   166  				}
   167  
   168  				rp.Source = source.AsString()
   169  				rp.Type = fqn
   170  
   171  			case "configuration_aliases":
   172  				exprs, listDiags := hcl.ExprList(kv.Value)
   173  				if listDiags.HasErrors() {
   174  					diags = append(diags, listDiags...)
   175  					continue
   176  				}
   177  
   178  				for _, expr := range exprs {
   179  					traversal, travDiags := hcl.AbsTraversalForExpr(expr)
   180  					if travDiags.HasErrors() {
   181  						diags = append(diags, travDiags...)
   182  						continue
   183  					}
   184  
   185  					addr, cfgDiags := ParseProviderConfigCompact(traversal)
   186  					if cfgDiags.HasErrors() {
   187  						diags = append(diags, &hcl.Diagnostic{
   188  							Severity: hcl.DiagError,
   189  							Summary:  "Invalid configuration_aliases value",
   190  							Detail:   `Configuration aliases can only contain references to local provider configuration names in the format of provider.alias`,
   191  							Subject:  kv.Value.Range().Ptr(),
   192  						})
   193  						continue
   194  					}
   195  
   196  					if addr.LocalName != name {
   197  						diags = append(diags, &hcl.Diagnostic{
   198  							Severity: hcl.DiagError,
   199  							Summary:  "Invalid configuration_aliases value",
   200  							Detail:   fmt.Sprintf(`Configuration aliases must be prefixed with the provider name. Expected %q, but found %q.`, name, addr.LocalName),
   201  							Subject:  kv.Value.Range().Ptr(),
   202  						})
   203  						continue
   204  					}
   205  
   206  					rp.Aliases = append(rp.Aliases, addr)
   207  				}
   208  
   209  			default:
   210  				diags = append(diags, &hcl.Diagnostic{
   211  					Severity: hcl.DiagError,
   212  					Summary:  "Invalid required_providers object",
   213  					Detail:   `required_providers objects can only contain "version", "source" and "configuration_aliases" attributes. To configure a provider, use a "provider" block.`,
   214  					Subject:  kv.Key.Range().Ptr(),
   215  				})
   216  				break
   217  			}
   218  
   219  		}
   220  
   221  		if diags.HasErrors() {
   222  			continue
   223  		}
   224  
   225  		// We can add the required provider when there are no errors.
   226  		// If a source was not given, create an implied type.
   227  		if rp.Type.IsZero() {
   228  			pType, err := addrs.ParseProviderPart(rp.Name)
   229  			if err != nil {
   230  				diags = append(diags, &hcl.Diagnostic{
   231  					Severity: hcl.DiagError,
   232  					Summary:  "Invalid provider name",
   233  					Detail:   err.Error(),
   234  					Subject:  attr.Expr.Range().Ptr(),
   235  				})
   236  			} else {
   237  				rp.Type = addrs.ImpliedProviderForUnqualifiedType(pType)
   238  			}
   239  		}
   240  
   241  		ret.RequiredProviders[rp.Name] = rp
   242  	}
   243  
   244  	return ret, diags
   245  }