2020-03-17 04:04:32 +01:00
|
|
|
// 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 hcl2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
2021-03-17 14:20:05 +01:00
|
|
|
"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"
|
2020-04-17 01:44:34 +02:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2020-03-17 04:04:32 +01:00
|
|
|
)
|
|
|
|
|
2020-04-30 22:22:24 +02:00
|
|
|
type bindOptions struct {
|
2021-06-15 18:09:16 +02:00
|
|
|
allowMissingVariables bool
|
|
|
|
allowMissingProperties bool
|
2021-06-15 19:22:57 +02:00
|
|
|
skipResourceTypecheck bool
|
2021-06-15 18:09:16 +02:00
|
|
|
loader schema.Loader
|
|
|
|
packageCache *PackageCache
|
2020-04-30 22:22:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (opts bindOptions) modelOptions() []model.BindOption {
|
|
|
|
if opts.allowMissingVariables {
|
|
|
|
return []model.BindOption{model.AllowMissingVariables}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-17 04:04:32 +01:00
|
|
|
type binder struct {
|
2020-04-30 22:22:24 +02:00
|
|
|
options bindOptions
|
2020-03-17 04:04:32 +01:00
|
|
|
|
2020-05-15 19:18:07 +02:00
|
|
|
referencedPackages map[string]*schema.Package
|
2020-04-30 22:22:24 +02:00
|
|
|
typeSchemas map[model.Type]schema.Type
|
2020-03-17 04:04:32 +01:00
|
|
|
|
|
|
|
tokens syntax.TokenMap
|
|
|
|
nodes []Node
|
|
|
|
root *model.Scope
|
|
|
|
}
|
|
|
|
|
2020-04-30 22:22:24 +02:00
|
|
|
type BindOption func(*bindOptions)
|
|
|
|
|
|
|
|
func AllowMissingVariables(options *bindOptions) {
|
|
|
|
options.allowMissingVariables = true
|
|
|
|
}
|
|
|
|
|
2021-06-15 18:09:16 +02:00
|
|
|
func AllowMissingProperties(options *bindOptions) {
|
|
|
|
options.allowMissingProperties = true
|
|
|
|
}
|
|
|
|
|
2021-06-15 19:22:57 +02:00
|
|
|
func SkipResourceTypechecking(options *bindOptions) {
|
|
|
|
options.skipResourceTypecheck = true
|
|
|
|
}
|
|
|
|
|
2020-04-30 22:22:24 +02:00
|
|
|
func PluginHost(host plugin.Host) BindOption {
|
2020-07-07 23:45:18 +02:00
|
|
|
return Loader(schema.NewPluginLoader(host))
|
|
|
|
}
|
|
|
|
|
|
|
|
func Loader(loader schema.Loader) BindOption {
|
2020-04-30 22:22:24 +02:00
|
|
|
return func(options *bindOptions) {
|
2020-07-07 23:45:18 +02:00
|
|
|
options.loader = loader
|
2020-04-30 22:22:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Cache(cache *PackageCache) BindOption {
|
|
|
|
return func(options *bindOptions) {
|
|
|
|
options.packageCache = cache
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-17 04:04:32 +01:00
|
|
|
// 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.
|
2020-04-30 22:22:24 +02:00
|
|
|
func BindProgram(files []*syntax.File, opts ...BindOption) (*Program, hcl.Diagnostics, error) {
|
|
|
|
var options bindOptions
|
|
|
|
for _, o := range opts {
|
|
|
|
o(&options)
|
|
|
|
}
|
|
|
|
|
2021-06-24 18:17:55 +02:00
|
|
|
// TODO: remove this once the latest pulumi-terraform-bridge has been rolled out
|
|
|
|
options.skipResourceTypecheck = true
|
|
|
|
|
2020-07-07 23:45:18 +02:00
|
|
|
if options.loader == nil {
|
2020-03-17 04:04:32 +01:00
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2020-10-09 22:13:55 +02:00
|
|
|
ctx, err := plugin.NewContext(nil, nil, nil, nil, cwd, nil, false, nil)
|
2020-03-17 04:04:32 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2020-07-07 23:45:18 +02:00
|
|
|
options.loader = schema.NewPluginLoader(ctx.Host)
|
2020-03-17 04:04:32 +01:00
|
|
|
|
|
|
|
defer contract.IgnoreClose(ctx)
|
|
|
|
}
|
|
|
|
|
2020-04-30 22:22:24 +02:00
|
|
|
if options.packageCache == nil {
|
|
|
|
options.packageCache = NewPackageCache()
|
|
|
|
}
|
|
|
|
|
2020-03-17 04:04:32 +01:00
|
|
|
b := &binder{
|
2020-05-15 19:18:07 +02:00
|
|
|
options: options,
|
|
|
|
tokens: syntax.NewTokenMapForFiles(files),
|
|
|
|
referencedPackages: map[string]*schema.Package{},
|
|
|
|
typeSchemas: map[model.Type]schema.Type{},
|
|
|
|
root: model.NewRootScope(syntax.None),
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
|
2020-04-17 01:44:34 +02:00
|
|
|
// Define null.
|
|
|
|
b.root.Define("null", &model.Constant{
|
|
|
|
Name: "null",
|
|
|
|
ConstantValue: cty.NullVal(cty.DynamicPseudoType),
|
|
|
|
})
|
2020-03-17 04:04:32 +01:00
|
|
|
// Define builtin functions.
|
|
|
|
for name, fn := range pulumiBuiltins {
|
|
|
|
b.root.DefineFunction(name, fn)
|
|
|
|
}
|
2020-04-07 04:43:16 +02:00
|
|
|
// Define the invoke function.
|
2020-05-11 19:21:56 +02:00
|
|
|
b.root.DefineFunction(Invoke, model.NewFunction(model.GenericFunctionSignature(b.bindInvokeSignature)))
|
2020-03-17 04:04:32 +01:00
|
|
|
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
// 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)...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Program{
|
|
|
|
Nodes: b.nodes,
|
|
|
|
files: files,
|
|
|
|
binder: b,
|
|
|
|
}, diagnostics, nil
|
|
|
|
}
|
|
|
|
|
2020-08-07 00:09:23 +02:00
|
|
|
// declareNodes declares all of the top-level nodes in the given file. This includes config, resources, outputs, and
|
2020-03-17 04:04:32 +01:00
|
|
|
// locals.
|
|
|
|
func (b *binder) declareNodes(file *syntax.File) (hcl.Diagnostics, error) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
// Declare body items in source order.
|
|
|
|
for _, item := range model.SourceOrderBody(file.Body) {
|
|
|
|
switch item := item.(type) {
|
|
|
|
case *hclsyntax.Attribute:
|
2020-05-01 19:56:31 +02:00
|
|
|
v := &LocalVariable{syntax: item}
|
|
|
|
attrDiags := b.declareNode(item.Name, v)
|
2020-03-17 04:04:32 +01:00
|
|
|
diagnostics = append(diagnostics, attrDiags...)
|
2020-05-01 19:56:31 +02:00
|
|
|
|
|
|
|
if err := b.loadReferencedPackageSchemas(v); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-03-17 04:04:32 +01:00
|
|
|
case *hclsyntax.Block:
|
|
|
|
switch item.Type {
|
|
|
|
case "config":
|
2020-04-03 02:38:03 +02:00
|
|
|
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"))
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
|
2020-04-03 02:38:03 +02:00
|
|
|
// TODO(pdg): check body for valid contents
|
|
|
|
|
2020-05-01 19:56:31 +02:00
|
|
|
v := &ConfigVariable{
|
2020-04-21 19:24:42 +02:00
|
|
|
typ: typ,
|
|
|
|
syntax: item,
|
2020-05-01 19:56:31 +02:00
|
|
|
}
|
|
|
|
diags := b.declareNode(name, v)
|
2020-04-03 02:38:03 +02:00
|
|
|
diagnostics = append(diagnostics, diags...)
|
2020-05-01 19:56:31 +02:00
|
|
|
|
|
|
|
if err := b.loadReferencedPackageSchemas(v); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-03-17 04:04:32 +01:00
|
|
|
case "resource":
|
|
|
|
if len(item.Labels) != 2 {
|
|
|
|
diagnostics = append(diagnostics, labelsErrorf(item, "resource variables must have exactly two labels"))
|
|
|
|
}
|
|
|
|
|
|
|
|
resource := &Resource{
|
2020-04-21 19:24:42 +02:00
|
|
|
syntax: item,
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
declareDiags := b.declareNode(item.Labels[0], resource)
|
|
|
|
diagnostics = append(diagnostics, declareDiags...)
|
|
|
|
|
|
|
|
if err := b.loadReferencedPackageSchemas(resource); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-04-03 02:38:03 +02:00
|
|
|
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"))
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
|
2020-04-03 02:38:03 +02:00
|
|
|
// TODO(pdg): check body for valid contents
|
|
|
|
|
2020-05-01 19:56:31 +02:00
|
|
|
v := &OutputVariable{
|
2020-04-21 19:24:42 +02:00
|
|
|
typ: typ,
|
|
|
|
syntax: item,
|
2020-05-01 19:56:31 +02:00
|
|
|
}
|
|
|
|
diags := b.declareNode(name, v)
|
2020-04-03 02:38:03 +02:00
|
|
|
diagnostics = append(diagnostics, diags...)
|
2020-05-01 19:56:31 +02:00
|
|
|
|
|
|
|
if err := b.loadReferencedPackageSchemas(v); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return diagnostics, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2020-04-30 22:22:24 +02:00
|
|
|
return model.BindExpression(node, b.root, b.tokens, b.options.modelOptions()...)
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|