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 (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
2020-04-14 10:30:25 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/model"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
|
2020-03-17 04:04:32 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type packageSchema struct {
|
|
|
|
schema *schema.Package
|
|
|
|
resources map[string]*schema.Resource
|
|
|
|
functions map[string]*schema.Function
|
|
|
|
}
|
|
|
|
|
2020-04-30 22:22:24 +02:00
|
|
|
type PackageCache struct {
|
|
|
|
entries map[string]*packageSchema
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPackageCache() *PackageCache {
|
|
|
|
return &PackageCache{
|
|
|
|
entries: map[string]*packageSchema{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-17 04:04:32 +01:00
|
|
|
// canonicalizeToken converts a Pulumi token into its canonical "pkg:module:member" form.
|
|
|
|
func canonicalizeToken(tok string, pkg *schema.Package) string {
|
2020-04-07 04:43:16 +02:00
|
|
|
_, _, member, _ := DecomposeToken(tok, hcl.Range{})
|
2020-03-17 04:04:32 +01:00
|
|
|
return fmt.Sprintf("%s:%s:%s", pkg.Name, pkg.TokenToModule(tok), member)
|
|
|
|
}
|
|
|
|
|
|
|
|
// loadReferencedPackageSchemas loads the schemas for any pacakges referenced by a given node.
|
|
|
|
func (b *binder) loadReferencedPackageSchemas(n Node) error {
|
|
|
|
// TODO: package versions
|
|
|
|
packageNames := codegen.StringSet{}
|
|
|
|
|
|
|
|
if r, ok := n.(*Resource); ok {
|
|
|
|
token, tokenRange := getResourceToken(r)
|
2020-04-07 04:43:16 +02:00
|
|
|
packageName, _, _, _ := DecomposeToken(token, tokenRange)
|
|
|
|
if packageName != "pulumi" {
|
|
|
|
packageNames.Add(packageName)
|
|
|
|
}
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
diags := hclsyntax.VisitAll(n.SyntaxNode(), func(node hclsyntax.Node) hcl.Diagnostics {
|
|
|
|
call, ok := node.(*hclsyntax.FunctionCallExpr)
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
token, tokenRange, ok := getInvokeToken(call)
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
2020-04-07 04:43:16 +02:00
|
|
|
packageName, _, _, _ := DecomposeToken(token, tokenRange)
|
|
|
|
if packageName != "pulumi" {
|
|
|
|
packageNames.Add(packageName)
|
|
|
|
}
|
2020-03-17 04:04:32 +01:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
contract.Assert(len(diags) == 0)
|
|
|
|
|
2020-05-01 19:56:31 +02:00
|
|
|
for _, name := range packageNames.SortedValues() {
|
|
|
|
if _, ok := b.options.packageCache.entries[name]; ok {
|
|
|
|
continue
|
|
|
|
}
|
2020-03-17 04:04:32 +01:00
|
|
|
if err := b.loadPackageSchema(name); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-01 19:56:31 +02:00
|
|
|
b.referencedPackages = append(b.referencedPackages, b.options.packageCache.entries[name].schema)
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// loadPackageSchema loads the schema for a given package by loading the corresponding provider and calling its
|
|
|
|
// GetSchema method.
|
|
|
|
//
|
|
|
|
// TODO: schema and provider versions
|
|
|
|
func (b *binder) loadPackageSchema(name string) error {
|
2020-04-30 22:22:24 +02:00
|
|
|
if _, ok := b.options.packageCache.entries[name]; ok {
|
2020-03-17 04:04:32 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-30 22:22:24 +02:00
|
|
|
provider, err := b.options.host.Provider(tokens.Package(name), nil)
|
2020-03-17 04:04:32 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
schemaBytes, err := provider.GetSchema(0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var spec schema.PackageSpec
|
|
|
|
if err := json.Unmarshal(schemaBytes, &spec); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-04-21 01:36:05 +02:00
|
|
|
pkg, err := schema.ImportSpec(spec, nil)
|
2020-03-17 04:04:32 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
resources := map[string]*schema.Resource{}
|
|
|
|
for _, r := range pkg.Resources {
|
|
|
|
resources[canonicalizeToken(r.Token, pkg)] = r
|
|
|
|
}
|
|
|
|
functions := map[string]*schema.Function{}
|
|
|
|
for _, f := range pkg.Functions {
|
|
|
|
functions[canonicalizeToken(f.Token, pkg)] = f
|
|
|
|
}
|
|
|
|
|
2020-04-30 22:22:24 +02:00
|
|
|
b.options.packageCache.entries[name] = &packageSchema{
|
2020-03-17 04:04:32 +01:00
|
|
|
schema: pkg,
|
|
|
|
resources: resources,
|
|
|
|
functions: functions,
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// schemaTypeToType converts a schema.Type to a model Type.
|
2020-04-21 18:18:09 +02:00
|
|
|
func (b *binder) schemaTypeToType(src schema.Type) (result model.Type) {
|
|
|
|
defer func() {
|
|
|
|
b.typeSchemas[result] = src
|
|
|
|
}()
|
|
|
|
|
2020-03-17 04:04:32 +01:00
|
|
|
switch src := src.(type) {
|
|
|
|
case *schema.ArrayType:
|
2020-04-21 18:18:09 +02:00
|
|
|
return model.NewListType(b.schemaTypeToType(src.ElementType))
|
2020-03-17 04:04:32 +01:00
|
|
|
case *schema.MapType:
|
2020-04-21 18:18:09 +02:00
|
|
|
return model.NewMapType(b.schemaTypeToType(src.ElementType))
|
2020-03-17 04:04:32 +01:00
|
|
|
case *schema.ObjectType:
|
|
|
|
properties := map[string]model.Type{}
|
|
|
|
for _, prop := range src.Properties {
|
2020-04-21 18:18:09 +02:00
|
|
|
t := b.schemaTypeToType(prop.Type)
|
2020-03-17 04:04:32 +01:00
|
|
|
if !prop.IsRequired {
|
|
|
|
t = model.NewOptionalType(t)
|
|
|
|
}
|
|
|
|
properties[prop.Name] = t
|
|
|
|
}
|
2020-04-21 18:18:09 +02:00
|
|
|
return model.NewObjectType(properties, src)
|
2020-03-17 04:04:32 +01:00
|
|
|
case *schema.TokenType:
|
|
|
|
t, ok := model.GetOpaqueType(src.Token)
|
|
|
|
if !ok {
|
|
|
|
tt, err := model.NewOpaqueType(src.Token)
|
|
|
|
contract.IgnoreError(err)
|
|
|
|
t = tt
|
|
|
|
}
|
|
|
|
|
|
|
|
if src.UnderlyingType != nil {
|
2020-04-21 18:18:09 +02:00
|
|
|
underlyingType := b.schemaTypeToType(src.UnderlyingType)
|
2020-03-17 04:04:32 +01:00
|
|
|
return model.NewUnionType(t, underlyingType)
|
|
|
|
}
|
|
|
|
return t
|
|
|
|
case *schema.UnionType:
|
|
|
|
types := make([]model.Type, len(src.ElementTypes))
|
|
|
|
for i, src := range src.ElementTypes {
|
2020-04-21 18:18:09 +02:00
|
|
|
types[i] = b.schemaTypeToType(src)
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
return model.NewUnionType(types...)
|
|
|
|
default:
|
|
|
|
switch src {
|
|
|
|
case schema.BoolType:
|
|
|
|
return model.BoolType
|
|
|
|
case schema.IntType:
|
|
|
|
return model.IntType
|
|
|
|
case schema.NumberType:
|
|
|
|
return model.NumberType
|
|
|
|
case schema.StringType:
|
|
|
|
return model.StringType
|
|
|
|
case schema.ArchiveType:
|
|
|
|
return ArchiveType
|
|
|
|
case schema.AssetType:
|
|
|
|
return AssetType
|
|
|
|
case schema.AnyType:
|
|
|
|
return model.DynamicType
|
|
|
|
default:
|
|
|
|
return model.NoneType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|