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 (
|
|
|
|
"fmt"
|
2020-05-19 02:35:11 +02:00
|
|
|
"sync"
|
2020-03-17 04:04:32 +01:00
|
|
|
|
2020-05-19 02:35:11 +02:00
|
|
|
"github.com/blang/semver"
|
2020-03-17 04:04:32 +01:00
|
|
|
"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"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/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 {
|
2020-05-19 02:35:11 +02:00
|
|
|
m sync.RWMutex
|
|
|
|
|
2020-04-30 22:22:24 +02:00
|
|
|
entries map[string]*packageSchema
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPackageCache() *PackageCache {
|
|
|
|
return &PackageCache{
|
|
|
|
entries: map[string]*packageSchema{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 02:35:11 +02:00
|
|
|
func (c *PackageCache) getPackageSchema(name string) (*packageSchema, bool) {
|
|
|
|
c.m.RLock()
|
|
|
|
defer c.m.RUnlock()
|
|
|
|
|
|
|
|
schema, ok := c.entries[name]
|
|
|
|
return schema, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// loadPackageSchema loads the schema for a given package by loading the corresponding provider and calling its
|
|
|
|
// GetSchema method.
|
|
|
|
//
|
|
|
|
// TODO: schema and provider versions
|
2020-07-07 23:45:18 +02:00
|
|
|
func (c *PackageCache) loadPackageSchema(loader schema.Loader, name string) (*packageSchema, error) {
|
2020-05-19 02:35:11 +02:00
|
|
|
if s, ok := c.getPackageSchema(name); ok {
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
2020-07-07 23:45:18 +02:00
|
|
|
version := (*semver.Version)(nil)
|
|
|
|
pkg, err := loader.LoadPackage(name, version)
|
2020-05-19 02:35:11 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, 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
|
|
|
|
}
|
|
|
|
|
|
|
|
schema := &packageSchema{
|
|
|
|
schema: pkg,
|
|
|
|
resources: resources,
|
|
|
|
functions: functions,
|
|
|
|
}
|
|
|
|
|
|
|
|
c.m.Lock()
|
|
|
|
defer c.m.Unlock()
|
|
|
|
|
|
|
|
if s, ok := c.entries[name]; ok {
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
c.entries[name] = schema
|
|
|
|
|
|
|
|
return schema, nil
|
|
|
|
}
|
|
|
|
|
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() {
|
2020-05-15 19:18:07 +02:00
|
|
|
if _, ok := b.referencedPackages[name]; ok {
|
2020-05-01 19:56:31 +02:00
|
|
|
continue
|
|
|
|
}
|
2020-07-07 23:45:18 +02:00
|
|
|
pkg, err := b.options.packageCache.loadPackageSchema(b.options.loader, name)
|
2020-05-19 02:35:11 +02:00
|
|
|
if err != nil {
|
2020-03-17 04:04:32 +01:00
|
|
|
return err
|
|
|
|
}
|
2020-05-19 02:35:11 +02:00
|
|
|
b.referencedPackages[name] = pkg.schema
|
2020-03-17 04:04:32 +01:00
|
|
|
}
|
|
|
|
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) {
|
2020-09-19 02:29:21 +02:00
|
|
|
return b.schemaTypeToTypeImpl(src, map[schema.Type]model.Type{})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binder) schemaTypeToTypeImpl(src schema.Type, seen map[schema.Type]model.Type) (result model.Type) {
|
2020-04-21 18:18:09 +02:00
|
|
|
defer func() {
|
|
|
|
b.typeSchemas[result] = src
|
|
|
|
}()
|
|
|
|
|
2020-09-19 02:29:21 +02:00
|
|
|
if already, ok := seen[src]; ok {
|
|
|
|
return already
|
|
|
|
}
|
|
|
|
|
2020-03-17 04:04:32 +01:00
|
|
|
switch src := src.(type) {
|
|
|
|
case *schema.ArrayType:
|
2020-09-19 02:29:21 +02:00
|
|
|
return model.NewListType(b.schemaTypeToTypeImpl(src.ElementType, seen))
|
2020-03-17 04:04:32 +01:00
|
|
|
case *schema.MapType:
|
2020-09-19 02:29:21 +02:00
|
|
|
return model.NewMapType(b.schemaTypeToTypeImpl(src.ElementType, seen))
|
2020-03-17 04:04:32 +01:00
|
|
|
case *schema.ObjectType:
|
|
|
|
properties := map[string]model.Type{}
|
2020-09-19 02:29:21 +02:00
|
|
|
objType := model.NewObjectType(properties, src)
|
|
|
|
seen[src] = objType
|
2020-03-17 04:04:32 +01:00
|
|
|
for _, prop := range src.Properties {
|
2020-09-19 02:29:21 +02:00
|
|
|
t := b.schemaTypeToTypeImpl(prop.Type, seen)
|
2020-03-17 04:04:32 +01:00
|
|
|
if !prop.IsRequired {
|
|
|
|
t = model.NewOptionalType(t)
|
|
|
|
}
|
|
|
|
properties[prop.Name] = t
|
|
|
|
}
|
2020-09-19 02:29:21 +02:00
|
|
|
return objType
|
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-09-19 02:29:21 +02:00
|
|
|
underlyingType := b.schemaTypeToTypeImpl(src.UnderlyingType, seen)
|
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-09-19 02:29:21 +02:00
|
|
|
types[i] = b.schemaTypeToTypeImpl(src, seen)
|
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
|
2020-05-19 11:41:06 +02:00
|
|
|
case schema.JSONType:
|
|
|
|
fallthrough
|
2020-03-17 04:04:32 +01:00
|
|
|
case schema.AnyType:
|
|
|
|
return model.DynamicType
|
|
|
|
default:
|
|
|
|
return model.NoneType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-11 20:17:36 +02:00
|
|
|
|
2020-05-21 19:23:33 +02:00
|
|
|
var schemaArrayTypes = make(map[schema.Type]*schema.ArrayType)
|
|
|
|
|
2020-05-11 20:17:36 +02:00
|
|
|
// GetSchemaForType extracts the schema.Type associated with a model.Type, if any.
|
|
|
|
//
|
2020-08-07 00:09:23 +02:00
|
|
|
// The result may be a *schema.UnionType if multiple schema types are associated with the input type.
|
2020-05-11 20:17:36 +02:00
|
|
|
func GetSchemaForType(t model.Type) (schema.Type, bool) {
|
|
|
|
switch t := t.(type) {
|
2020-05-21 19:23:33 +02:00
|
|
|
case *model.ListType:
|
|
|
|
element, ok := GetSchemaForType(t.ElementType)
|
|
|
|
if !ok {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
if t, ok := schemaArrayTypes[element]; ok {
|
|
|
|
return t, true
|
|
|
|
}
|
|
|
|
schemaArrayTypes[element] = &schema.ArrayType{ElementType: element}
|
|
|
|
return schemaArrayTypes[element], true
|
2020-05-11 20:17:36 +02:00
|
|
|
case *model.ObjectType:
|
|
|
|
if len(t.Annotations) == 0 {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
for _, a := range t.Annotations {
|
|
|
|
if t, ok := a.(schema.Type); ok {
|
|
|
|
return t, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
case *model.OutputType:
|
|
|
|
return GetSchemaForType(t.ElementType)
|
|
|
|
case *model.PromiseType:
|
|
|
|
return GetSchemaForType(t.ElementType)
|
|
|
|
case *model.UnionType:
|
|
|
|
schemas := codegen.Set{}
|
|
|
|
for _, t := range t.ElementTypes {
|
|
|
|
if s, ok := GetSchemaForType(t); ok {
|
|
|
|
if union, ok := s.(*schema.UnionType); ok {
|
|
|
|
for _, s := range union.ElementTypes {
|
|
|
|
schemas.Add(s)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
schemas.Add(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(schemas) == 0 {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
schemaTypes := make([]schema.Type, 0, len(schemas))
|
|
|
|
for t := range schemas {
|
|
|
|
schemaTypes = append(schemaTypes, t.(schema.Type))
|
|
|
|
}
|
|
|
|
if len(schemaTypes) == 1 {
|
|
|
|
return schemaTypes[0], true
|
|
|
|
}
|
|
|
|
return &schema.UnionType{ElementTypes: schemaTypes}, true
|
|
|
|
default:
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
}
|