package configs import ( "fmt" version "github.com/hashicorp/go-version" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/addrs" "github.com/zclconf/go-cty/cty" ) // RequiredProvider represents a declaration of a dependency on a particular // provider version or source without actually configuring that provider. This // is used in child modules that expect a provider to be passed in from their // parent. type RequiredProvider struct { Name string Source string Type addrs.Provider Requirement VersionConstraint DeclRange hcl.Range Aliases []addrs.LocalProviderConfig } type RequiredProviders struct { RequiredProviders map[string]*RequiredProvider DeclRange hcl.Range } func decodeRequiredProvidersBlock(block *hcl.Block) (*RequiredProviders, hcl.Diagnostics) { attrs, diags := block.Body.JustAttributes() if diags.HasErrors() { return nil, diags } ret := &RequiredProviders{ RequiredProviders: make(map[string]*RequiredProvider), DeclRange: block.DefRange, } for name, attr := range attrs { rp := &RequiredProvider{ Name: name, DeclRange: attr.Expr.Range(), } // Look for a single static string, in case we have the legacy version-only // format in the configuration. if expr, err := attr.Expr.Value(nil); err == nil && expr.Type().IsPrimitiveType() { vc, reqDiags := decodeVersionConstraint(attr) diags = append(diags, reqDiags...) pType, err := addrs.ParseProviderPart(rp.Name) if err != nil { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid provider name", Detail: err.Error(), Subject: attr.Expr.Range().Ptr(), }) continue } rp.Requirement = vc rp.Type = addrs.ImpliedProviderForUnqualifiedType(pType) ret.RequiredProviders[name] = rp continue } // verify that the local name is already localized or produce an error. nameDiags := checkProviderNameNormalized(name, attr.Expr.Range()) if nameDiags.HasErrors() { diags = append(diags, nameDiags...) continue } kvs, mapDiags := hcl.ExprMap(attr.Expr) if mapDiags.HasErrors() { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid required_providers object", Detail: "required_providers entries must be strings or objects.", Subject: attr.Expr.Range().Ptr(), }) continue } for _, kv := range kvs { key, keyDiags := kv.Key.Value(nil) if keyDiags.HasErrors() { diags = append(diags, keyDiags...) continue } if key.Type() != cty.String { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid Attribute", Detail: fmt.Sprintf("Invalid attribute value for provider requirement: %#v", key), Subject: kv.Key.Range().Ptr(), }) continue } switch key.AsString() { case "version": vc := VersionConstraint{ DeclRange: attr.Range, } constraint, valDiags := kv.Value.Value(nil) if valDiags.HasErrors() || !constraint.Type().Equals(cty.String) { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid version constraint", Detail: "Version must be specified as a string.", Subject: kv.Value.Range().Ptr(), }) continue } constraintStr := constraint.AsString() constraints, err := version.NewConstraint(constraintStr) if err != nil { // NewConstraint doesn't return user-friendly errors, so we'll just // ignore the provided error and produce our own generic one. diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid version constraint", Detail: "This string does not use correct version constraint syntax.", Subject: kv.Value.Range().Ptr(), }) continue } vc.Required = constraints rp.Requirement = vc case "source": source, err := kv.Value.Value(nil) if err != nil || !source.Type().Equals(cty.String) { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid source", Detail: "Source must be specified as a string.", Subject: kv.Value.Range().Ptr(), }) continue } fqn, sourceDiags := addrs.ParseProviderSourceString(source.AsString()) if sourceDiags.HasErrors() { hclDiags := sourceDiags.ToHCL() // The diagnostics from ParseProviderSourceString don't contain // source location information because it has no context to compute // them from, and so we'll add those in quickly here before we // return. for _, diag := range hclDiags { if diag.Subject == nil { diag.Subject = kv.Value.Range().Ptr() } } diags = append(diags, hclDiags...) continue } rp.Source = source.AsString() rp.Type = fqn case "configuration_aliases": exprs, listDiags := hcl.ExprList(kv.Value) if listDiags.HasErrors() { diags = append(diags, listDiags...) continue } for _, expr := range exprs { traversal, travDiags := hcl.AbsTraversalForExpr(expr) if travDiags.HasErrors() { diags = append(diags, travDiags...) continue } addr, cfgDiags := ParseProviderConfigCompact(traversal) if cfgDiags.HasErrors() { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid configuration_aliases value", Detail: `Configuration aliases can only contain references to local provider configuration names in the format of provider.alias`, Subject: kv.Value.Range().Ptr(), }) continue } if addr.LocalName != name { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid configuration_aliases value", Detail: fmt.Sprintf(`Configuration aliases must be prefixed with the provider name. Expected %q, but found %q.`, name, addr.LocalName), Subject: kv.Value.Range().Ptr(), }) continue } rp.Aliases = append(rp.Aliases, addr) } default: diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid required_providers object", Detail: `required_providers objects can only contain "version", "source" and "configuration_aliases" attributes. To configure a provider, use a "provider" block.`, Subject: kv.Key.Range().Ptr(), }) break } } // finally add the required provider as long as there were no errors if !diags.HasErrors() { // if a source was not given, create an implied type if rp.Type.IsZero() { pType, err := addrs.ParseProviderPart(rp.Name) if err != nil { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid provider name", Detail: err.Error(), Subject: attr.Expr.Range().Ptr(), }) } else { rp.Type = addrs.ImpliedProviderForUnqualifiedType(pType) } } ret.RequiredProviders[rp.Name] = rp } } return ret, diags }