mirror of https://github.com/pulumi/pulumi.git
695 lines
21 KiB
Go
695 lines
21 KiB
Go
// Copyright 2016-2020, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pcl
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"github.com/blang/semver"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
const (
|
|
pulumiPackage = "pulumi"
|
|
LogicalNamePropertyKey = "__logicalName"
|
|
)
|
|
|
|
type ComponentProgramBinderArgs struct {
|
|
AllowMissingVariables bool
|
|
AllowMissingProperties bool
|
|
SkipResourceTypecheck bool
|
|
SkipInvokeTypecheck bool
|
|
SkipRangeTypecheck bool
|
|
PreferOutputVersionedInvokes bool
|
|
BinderDirPath string
|
|
BinderLoader schema.Loader
|
|
ComponentSource string
|
|
ComponentNodeRange hcl.Range
|
|
PackageCache *PackageCache
|
|
}
|
|
|
|
type ComponentProgramBinder = func(ComponentProgramBinderArgs) (*Program, hcl.Diagnostics, error)
|
|
|
|
type bindOptions struct {
|
|
allowMissingVariables bool
|
|
allowMissingProperties bool
|
|
skipResourceTypecheck bool
|
|
skipInvokeTypecheck bool
|
|
skipRangeTypecheck bool
|
|
preferOutputVersionedInvokes bool
|
|
loader schema.Loader
|
|
packageCache *PackageCache
|
|
// the directory path of the PCL program being bound
|
|
// we use this to locate the source of the component blocks
|
|
// which refer to a component resource in a relative directory
|
|
dirPath string
|
|
componentProgramBinder ComponentProgramBinder
|
|
}
|
|
|
|
func (opts bindOptions) modelOptions() []model.BindOption {
|
|
var options []model.BindOption
|
|
if opts.allowMissingVariables {
|
|
options = append(options, model.AllowMissingVariables)
|
|
}
|
|
if opts.skipRangeTypecheck {
|
|
options = append(options, model.SkipRangeTypechecking)
|
|
}
|
|
return options
|
|
}
|
|
|
|
type binder struct {
|
|
options bindOptions
|
|
|
|
packageDescriptors map[string]*schema.PackageDescriptor
|
|
referencedPackages map[string]schema.PackageReference
|
|
schemaTypes map[schema.Type]model.Type
|
|
|
|
tokens syntax.TokenMap
|
|
nodes []Node
|
|
root *model.Scope
|
|
}
|
|
|
|
type BindOption func(*bindOptions)
|
|
|
|
func AllowMissingVariables(options *bindOptions) {
|
|
options.allowMissingVariables = true
|
|
}
|
|
|
|
func AllowMissingProperties(options *bindOptions) {
|
|
options.allowMissingProperties = true
|
|
}
|
|
|
|
func SkipResourceTypechecking(options *bindOptions) {
|
|
options.skipResourceTypecheck = true
|
|
}
|
|
|
|
func SkipRangeTypechecking(options *bindOptions) {
|
|
options.skipRangeTypecheck = true
|
|
}
|
|
|
|
func PreferOutputVersionedInvokes(options *bindOptions) {
|
|
options.preferOutputVersionedInvokes = true
|
|
}
|
|
|
|
func SkipInvokeTypechecking(options *bindOptions) {
|
|
options.skipInvokeTypecheck = true
|
|
}
|
|
|
|
func PluginHost(host plugin.Host) BindOption {
|
|
return Loader(schema.NewPluginLoader(host))
|
|
}
|
|
|
|
func Loader(loader schema.Loader) BindOption {
|
|
return func(options *bindOptions) {
|
|
options.loader = loader
|
|
}
|
|
}
|
|
|
|
func Cache(cache *PackageCache) BindOption {
|
|
return func(options *bindOptions) {
|
|
options.packageCache = cache
|
|
}
|
|
}
|
|
|
|
func DirPath(path string) BindOption {
|
|
return func(options *bindOptions) {
|
|
options.dirPath = path
|
|
}
|
|
}
|
|
|
|
func ComponentBinder(binder ComponentProgramBinder) BindOption {
|
|
return func(options *bindOptions) {
|
|
options.componentProgramBinder = binder
|
|
}
|
|
}
|
|
|
|
// NonStrictBindOptions returns a set of bind options that make the binder lenient about type checking.
|
|
// Changing errors into warnings when possible
|
|
func NonStrictBindOptions() []BindOption {
|
|
return []BindOption{
|
|
AllowMissingVariables,
|
|
AllowMissingProperties,
|
|
SkipResourceTypechecking,
|
|
SkipInvokeTypechecking,
|
|
SkipRangeTypechecking,
|
|
}
|
|
}
|
|
|
|
// BindProgram performs semantic analysis on the given set of HCL2 files that represent a single program. The given
|
|
// host, if any, is used for loading any resource plugins necessary to extract schema information.
|
|
func BindProgram(files []*syntax.File, opts ...BindOption) (*Program, hcl.Diagnostics, error) {
|
|
var options bindOptions
|
|
for _, o := range opts {
|
|
o(&options)
|
|
}
|
|
|
|
if options.loader == nil {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ctx, err := plugin.NewContext(nil, nil, nil, nil, cwd, nil, false, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
options.loader = schema.NewPluginLoader(ctx.Host)
|
|
|
|
defer contract.IgnoreClose(ctx)
|
|
}
|
|
|
|
if options.packageCache == nil {
|
|
options.packageCache = NewPackageCache()
|
|
}
|
|
|
|
b := &binder{
|
|
options: options,
|
|
tokens: syntax.NewTokenMapForFiles(files),
|
|
packageDescriptors: map[string]*schema.PackageDescriptor{},
|
|
referencedPackages: map[string]schema.PackageReference{},
|
|
schemaTypes: map[schema.Type]model.Type{},
|
|
root: model.NewRootScope(syntax.None),
|
|
}
|
|
|
|
// Define null.
|
|
b.root.Define("null", &model.Constant{
|
|
Name: "null",
|
|
ConstantValue: cty.NullVal(cty.DynamicPseudoType),
|
|
})
|
|
// Define builtin functions.
|
|
for name, fn := range pulumiBuiltins(options) {
|
|
b.root.DefineFunction(name, fn)
|
|
}
|
|
// Define the invoke function.
|
|
b.root.DefineFunction(Invoke, model.NewFunction(model.GenericFunctionSignature(b.bindInvokeSignature)))
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
// Load package descriptors from the files
|
|
for _, file := range files {
|
|
packageDescriptors, diags := ReadPackageDescriptors(file)
|
|
diagnostics = append(diagnostics, diags...)
|
|
for packageName, descriptor := range packageDescriptors {
|
|
if _, ok := b.packageDescriptors[packageName]; ok {
|
|
diagnostics = append(diagnostics, errorf(file.Body.Range(), "package %q was already defined", packageName))
|
|
continue
|
|
}
|
|
b.packageDescriptors[packageName] = descriptor
|
|
}
|
|
}
|
|
|
|
// Sort files in source order, then declare all top-level nodes in each.
|
|
sort.Slice(files, func(i, j int) bool {
|
|
return files[i].Name < files[j].Name
|
|
})
|
|
for _, f := range files {
|
|
fileDiags, err := b.declareNodes(f)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
diagnostics = append(diagnostics, fileDiags...)
|
|
}
|
|
|
|
// Now bind the nodes.
|
|
for _, n := range b.nodes {
|
|
diagnostics = append(diagnostics, b.bindNode(n)...)
|
|
}
|
|
|
|
if diagnostics.HasErrors() {
|
|
return nil, diagnostics, diagnostics
|
|
}
|
|
|
|
return &Program{
|
|
Nodes: b.nodes,
|
|
files: files,
|
|
binder: b,
|
|
}, diagnostics, nil
|
|
}
|
|
|
|
// Used by language plugins to bind a PCL program in the given directory.
|
|
func BindDirectory(
|
|
directory string,
|
|
loader schema.ReferenceLoader,
|
|
extraOptions ...BindOption,
|
|
) (*Program, hcl.Diagnostics, error) {
|
|
parser := syntax.NewParser()
|
|
// Load all .pp files in the directory
|
|
files, err := os.ReadDir(directory)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var parseDiagnostics hcl.Diagnostics
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
fileName := file.Name()
|
|
path := filepath.Join(directory, fileName)
|
|
|
|
if filepath.Ext(path) == ".pp" {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
err = parser.ParseFile(file, filepath.Base(path))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
parseDiagnostics = append(parseDiagnostics, parser.Diagnostics...)
|
|
}
|
|
}
|
|
|
|
if parseDiagnostics.HasErrors() {
|
|
return nil, parseDiagnostics, nil
|
|
}
|
|
|
|
opts := []BindOption{
|
|
Loader(loader),
|
|
DirPath(directory),
|
|
ComponentBinder(ComponentProgramBinderFromFileSystem()),
|
|
}
|
|
|
|
opts = append(opts, extraOptions...)
|
|
|
|
program, bindDiagnostics, err := BindProgram(parser.Files, opts...)
|
|
|
|
// err will be the same as bindDiagnostics if there are errors, but we don't want to return that here.
|
|
// err _could_ also be a context setup error in which case bindDiagnotics will be nil and that we do want to return.
|
|
if bindDiagnostics != nil {
|
|
err = nil
|
|
}
|
|
|
|
allDiagnostics := append(parseDiagnostics, bindDiagnostics...)
|
|
return program, allDiagnostics, err
|
|
}
|
|
|
|
func makeObjectPropertiesOptional(objectType *model.ObjectType) *model.ObjectType {
|
|
for property, propertyType := range objectType.Properties {
|
|
if !model.IsOptionalType(propertyType) {
|
|
objectType.Properties[property] = model.NewOptionalType(propertyType)
|
|
}
|
|
}
|
|
|
|
return objectType
|
|
}
|
|
|
|
// declareNodes declares all of the top-level nodes in the given file. This includes config, resources, outputs, and
|
|
// locals.
|
|
// Temporarily, we load all resources first, as convert sets the highest package version seen
|
|
// under all resources' options. Once this is supported for invokes, the order of declaration will not
|
|
// impact which package is actually loaded.
|
|
func (b *binder) declareNodes(file *syntax.File) (hcl.Diagnostics, error) {
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
for _, item := range model.SourceOrderBody(file.Body) {
|
|
switch item := item.(type) {
|
|
case *hclsyntax.Block:
|
|
switch item.Type {
|
|
case "resource":
|
|
if len(item.Labels) != 2 {
|
|
diagnostics = append(diagnostics, labelsErrorf(item, "resource variables must have exactly two labels"))
|
|
}
|
|
|
|
resource := &Resource{
|
|
syntax: item,
|
|
}
|
|
declareDiags := b.declareNode(item.Labels[0], resource)
|
|
diagnostics = append(diagnostics, declareDiags...)
|
|
|
|
if err := b.loadReferencedPackageSchemas(resource); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, item := range model.SourceOrderBody(file.Body) {
|
|
switch item := item.(type) {
|
|
case *hclsyntax.Attribute:
|
|
v := &LocalVariable{syntax: item}
|
|
attrDiags := b.declareNode(item.Name, v)
|
|
diagnostics = append(diagnostics, attrDiags...)
|
|
|
|
if err := b.loadReferencedPackageSchemas(v); err != nil {
|
|
return nil, err
|
|
}
|
|
case *hclsyntax.Block:
|
|
switch item.Type {
|
|
case "config":
|
|
name, typ := "<unnamed>", model.Type(model.DynamicType)
|
|
switch len(item.Labels) {
|
|
case 1:
|
|
name = item.Labels[0]
|
|
case 2:
|
|
name = item.Labels[0]
|
|
|
|
typeExpr, diags := model.BindExpressionText(item.Labels[1], model.TypeScope, item.LabelRanges[1].Start)
|
|
diagnostics = append(diagnostics, diags...)
|
|
if typeExpr == nil {
|
|
return diagnostics, fmt.Errorf("cannot bind expression: %v", diagnostics)
|
|
}
|
|
typ = typeExpr.Type()
|
|
switch configType := typ.(type) {
|
|
case *model.ObjectType:
|
|
typ = makeObjectPropertiesOptional(configType)
|
|
case *model.ListType:
|
|
switch elementType := configType.ElementType.(type) {
|
|
case *model.ObjectType:
|
|
modifiedElementType := makeObjectPropertiesOptional(elementType)
|
|
typ = model.NewListType(modifiedElementType)
|
|
}
|
|
case *model.MapType:
|
|
switch elementType := configType.ElementType.(type) {
|
|
case *model.ObjectType:
|
|
modifiedElementType := makeObjectPropertiesOptional(elementType)
|
|
typ = model.NewMapType(modifiedElementType)
|
|
}
|
|
}
|
|
default:
|
|
diagnostics = append(diagnostics, labelsErrorf(item, "config variables must have exactly one or two labels"))
|
|
}
|
|
|
|
// TODO(pdg): check body for valid contents
|
|
|
|
v := &ConfigVariable{
|
|
typ: typ,
|
|
syntax: item,
|
|
}
|
|
diags := b.declareNode(name, v)
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
if err := b.loadReferencedPackageSchemas(v); err != nil {
|
|
return nil, err
|
|
}
|
|
case "output":
|
|
name, typ := "<unnamed>", model.Type(model.DynamicType)
|
|
switch len(item.Labels) {
|
|
case 1:
|
|
name = item.Labels[0]
|
|
case 2:
|
|
name = item.Labels[0]
|
|
|
|
typeExpr, diags := model.BindExpressionText(item.Labels[1], model.TypeScope, item.LabelRanges[1].Start)
|
|
diagnostics = append(diagnostics, diags...)
|
|
typ = typeExpr.Type()
|
|
default:
|
|
diagnostics = append(diagnostics, labelsErrorf(item, "config variables must have exactly one or two labels"))
|
|
}
|
|
|
|
v := &OutputVariable{
|
|
typ: typ,
|
|
syntax: item,
|
|
}
|
|
diags := b.declareNode(name, v)
|
|
diagnostics = append(diagnostics, diags...)
|
|
|
|
if err := b.loadReferencedPackageSchemas(v); err != nil {
|
|
return nil, err
|
|
}
|
|
case "component":
|
|
if len(item.Labels) != 2 {
|
|
diagnostics = append(diagnostics, labelsErrorf(item, "components must have exactly two labels"))
|
|
continue
|
|
}
|
|
name := item.Labels[0]
|
|
source := item.Labels[1]
|
|
|
|
v := &Component{
|
|
name: name,
|
|
syntax: item,
|
|
source: source,
|
|
VariableType: model.DynamicType,
|
|
}
|
|
diags := b.declareNode(name, v)
|
|
diagnostics = append(diagnostics, diags...)
|
|
}
|
|
}
|
|
}
|
|
|
|
return diagnostics, nil
|
|
}
|
|
|
|
// evaluateLiteralExpr evaluates a syntax expression to a string if it is a literal string expression.
|
|
func evaluateLiteralExpr(expr hclsyntax.Expression) (string, error) {
|
|
switch expr := expr.(type) {
|
|
case *hclsyntax.LiteralValueExpr:
|
|
if expr.Val.Type() != cty.String {
|
|
return "", fmt.Errorf("expected string literal, got %v", expr.Val.Type())
|
|
}
|
|
return expr.Val.AsString(), nil
|
|
case *hclsyntax.TemplateExpr:
|
|
if len(expr.Parts) != 1 {
|
|
return "", fmt.Errorf("expected string literal, got %v", expr)
|
|
}
|
|
|
|
part, ok := expr.Parts[0].(*hclsyntax.LiteralValueExpr)
|
|
if !ok || part.Val.Type() != cty.String {
|
|
return "", fmt.Errorf("expected string literal, got %v", part.Val.Type())
|
|
}
|
|
return part.Val.AsString(), nil
|
|
default:
|
|
return "", fmt.Errorf("expected string literal, got %T", expr)
|
|
}
|
|
}
|
|
|
|
// Evaluate a constant string attribute that's internal to Pulumi, e.g.: the Logical Name on a resource or output.
|
|
func getStringAttrValue(attr *model.Attribute) (string, *hcl.Diagnostic) {
|
|
if literal, err := evaluateLiteralExpr(attr.Syntax.Expr); err == nil {
|
|
return literal, nil
|
|
}
|
|
|
|
return "", stringAttributeError(attr)
|
|
}
|
|
|
|
// Returns the value of constant boolean attribute
|
|
func getBooleanAttributeValue(attr *model.Attribute) (bool, *hcl.Diagnostic) {
|
|
switch lit := attr.Syntax.Expr.(type) {
|
|
case *hclsyntax.LiteralValueExpr:
|
|
if lit.Val.Type() != cty.Bool {
|
|
return false, boolAttributeError(attr)
|
|
}
|
|
return lit.Val.True(), nil
|
|
default:
|
|
return false, boolAttributeError(attr)
|
|
}
|
|
}
|
|
|
|
// readParameterizationDescriptor parses a map of attributes that match contents of a parameterization block in the
|
|
// form of:
|
|
//
|
|
// parameterization {
|
|
// name = <name>
|
|
// version = <version>
|
|
// value = <base64 encoded string>
|
|
// }
|
|
func readParameterizationDescriptor(
|
|
packageName string,
|
|
attributes map[string]hclsyntax.Expression,
|
|
) (*schema.ParameterizationDescriptor, *hcl.Diagnostic) {
|
|
descriptor := &schema.ParameterizationDescriptor{}
|
|
|
|
// read name, version, and value attributes
|
|
for key, value := range attributes {
|
|
switch key {
|
|
case "name":
|
|
name, err := evaluateLiteralExpr(value)
|
|
if err != nil {
|
|
return nil, errorf(value.Range(), "invalid name attribute of parameterization of %q: %v", packageName, err)
|
|
}
|
|
if name == "" {
|
|
return nil, errorf(value.Range(), "name attribute of parameterization of %q cannot be empty", packageName)
|
|
}
|
|
descriptor.Name = name
|
|
case "version":
|
|
version, _ := evaluateLiteralExpr(value)
|
|
parsedVersion, err := semver.Make(version)
|
|
if err != nil {
|
|
return nil, errorf(value.Range(),
|
|
"invalid version %q of parameterization for package %q: %v", version, packageName, err)
|
|
}
|
|
descriptor.Version = parsedVersion
|
|
|
|
case "value":
|
|
// value must be a base64 encoded string
|
|
base64EncodedValue, err := evaluateLiteralExpr(value)
|
|
if err != nil {
|
|
return nil, errorf(value.Range(), "invalid base64 encoded value for parameterization of %q: %v", packageName, err)
|
|
}
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(base64EncodedValue)
|
|
if err != nil {
|
|
return nil, errorf(value.Range(), "invalid base64 encoded value for parameterization of %q: %v", packageName, err)
|
|
}
|
|
|
|
descriptor.Value = decoded
|
|
}
|
|
}
|
|
|
|
return descriptor, nil
|
|
}
|
|
|
|
// ReadPackageDescriptors reads the package blocks in the given file and returns a map of package names to the package.
|
|
// The descriptors blocks are top-level program blocks have the following format:
|
|
//
|
|
// package <name> {
|
|
// baseProviderName = <name>
|
|
// baseProviderVersion = <version>
|
|
// baseProviderDownloadUrl = <url>
|
|
// parameterization {
|
|
// name = <name>
|
|
// version = <version>
|
|
// value = <base64 encoded string>
|
|
// }
|
|
// }
|
|
//
|
|
// Notes:
|
|
// - parameterization block is optional.
|
|
// - the value of the parameterization is base64-encoded string because we want to specify binary data in PCL form
|
|
//
|
|
// These package descriptors allow the binder loader to load package schema information from a package source.
|
|
func ReadPackageDescriptors(file *syntax.File) (map[string]*schema.PackageDescriptor, hcl.Diagnostics) {
|
|
var diagnostics hcl.Diagnostics
|
|
packageDescriptors := map[string]*schema.PackageDescriptor{}
|
|
for _, node := range model.SourceOrderBody(file.Body) {
|
|
switch node := node.(type) {
|
|
case *hclsyntax.Block:
|
|
if node.Type != "package" {
|
|
// we only care about blocks of the form:
|
|
// package <name> { ... }
|
|
continue
|
|
}
|
|
|
|
labels := node.Labels
|
|
if len(labels) != 1 {
|
|
diagnostics = append(diagnostics,
|
|
labelsErrorf(node, "package blocks must have exactly one label (the package name)"))
|
|
continue
|
|
}
|
|
|
|
packageName := labels[0]
|
|
// make sure we don't declare the same package twice
|
|
if _, ok := packageDescriptors[packageName]; ok {
|
|
diagnostics = append(diagnostics,
|
|
errorf(node.Range(), "package %q was already defined", packageName))
|
|
continue
|
|
}
|
|
|
|
// read the attributes of the package block to fill in the package descriptor data
|
|
packageDescriptor := &schema.PackageDescriptor{}
|
|
|
|
if node.Body != nil {
|
|
for _, attribute := range node.Body.Attributes {
|
|
switch attribute.Name {
|
|
case "baseProviderName":
|
|
baseProviderName, err := evaluateLiteralExpr(attribute.Expr)
|
|
if err != nil {
|
|
diagnostics = append(diagnostics,
|
|
errorf(attribute.Range(), "invalid base provider name for %q: %v", packageName, err))
|
|
continue
|
|
}
|
|
|
|
packageDescriptor.Name = baseProviderName
|
|
case "baseProviderVersion":
|
|
version, _ := evaluateLiteralExpr(attribute.Expr)
|
|
parsedVersion, err := semver.Make(version)
|
|
if err != nil {
|
|
// parsing the version failed, error out and skip this package
|
|
diagnostics = append(diagnostics,
|
|
errorf(attribute.Range(),
|
|
"invalid baseProviderVersion %q for %q: %v", version, packageName, err))
|
|
continue
|
|
}
|
|
packageDescriptor.Version = &parsedVersion
|
|
case "baseProviderDownloadUrl":
|
|
downloadURLValue, err := evaluateLiteralExpr(attribute.Expr)
|
|
if err != nil {
|
|
diagnostics = append(diagnostics,
|
|
errorf(attribute.Range(), "invalid download URL for %q: %v", packageName, err))
|
|
continue
|
|
}
|
|
|
|
if _, err := url.ParseRequestURI(downloadURLValue); err != nil {
|
|
diagnostics = append(diagnostics,
|
|
errorf(attribute.Range(), "invalid download URL for %q: %v", packageName, err))
|
|
continue
|
|
}
|
|
packageDescriptor.DownloadURL = downloadURLValue
|
|
}
|
|
}
|
|
for _, block := range node.Body.Blocks {
|
|
switch block.Type {
|
|
case "parameterization":
|
|
attributes := map[string]hclsyntax.Expression{}
|
|
for _, item := range block.Body.Attributes {
|
|
attributes[item.Name] = item.Expr
|
|
}
|
|
descriptor, diag := readParameterizationDescriptor(packageName, attributes)
|
|
if diag != nil {
|
|
diagnostics = append(diagnostics, diag)
|
|
continue
|
|
}
|
|
packageDescriptor.Parameterization = descriptor
|
|
}
|
|
}
|
|
}
|
|
|
|
if packageDescriptor.Name == "" {
|
|
// baseProviderName was not provided
|
|
// default to the package name
|
|
packageDescriptor.Name = packageName
|
|
}
|
|
packageDescriptors[packageName] = packageDescriptor
|
|
}
|
|
}
|
|
return packageDescriptors, diagnostics
|
|
}
|
|
|
|
// declareNode declares a single top-level node. If a node with the same name has already been declared, it returns an
|
|
// appropriate diagnostic.
|
|
func (b *binder) declareNode(name string, n Node) hcl.Diagnostics {
|
|
if !b.root.Define(name, n) {
|
|
existing, _ := b.root.BindReference(name)
|
|
return hcl.Diagnostics{errorf(existing.SyntaxNode().Range(), "%q already declared", name)}
|
|
}
|
|
b.nodes = append(b.nodes, n)
|
|
return nil
|
|
}
|
|
|
|
func (b *binder) bindExpression(node hclsyntax.Node) (model.Expression, hcl.Diagnostics) {
|
|
return model.BindExpression(node, b.root, b.tokens, b.options.modelOptions()...)
|
|
}
|
|
|
|
func modelExprToString(expr *model.Expression) string {
|
|
if expr == nil {
|
|
return ""
|
|
}
|
|
return (*expr).(*model.TemplateExpression).
|
|
Parts[0].(*model.LiteralValueExpression).Value.AsString()
|
|
}
|