Compare commits

...

6 commits

Author SHA1 Message Date
Justin Van Patten 139cbd60fd wip 2021-02-12 10:36:38 -08:00
Justin Van Patten bc4b198070 wip 2021-02-08 13:59:53 -08:00
Justin Van Patten 8e60459861 Fix bug 2021-02-08 13:59:53 -08:00
Justin Van Patten 000c9ee301 [codegen/python] Avoid panic when referencing external nested types 2021-02-08 13:59:53 -08:00
Justin Van Patten 4676f4332b Fix circular import 2021-02-08 13:59:53 -08:00
Justin Van Patten fa49622ca6 wip 2021-02-08 13:59:53 -08:00
10 changed files with 572 additions and 82 deletions

View file

@ -7,7 +7,7 @@ import pulumi
import pulumi.runtime
from typing import Any, Mapping, Optional, Sequence, Union
from . import _utilities, _tables
from pulumi_random import RandomPet
import pulumi_random
__all__ = [
'PetArgs',
@ -17,7 +17,7 @@ __all__ = [
class PetArgs:
def __init__(__self__, *,
age: Optional[pulumi.Input[int]] = None,
name: Optional[pulumi.Input['RandomPet']] = None):
name: Optional[pulumi.Input['pulumi_random.RandomPet']] = None):
if age is not None:
pulumi.set(__self__, "age", age)
if name is not None:
@ -34,11 +34,11 @@ class PetArgs:
@property
@pulumi.getter
def name(self) -> Optional[pulumi.Input['RandomPet']]:
def name(self) -> Optional[pulumi.Input['pulumi_random.RandomPet']]:
return pulumi.get(self, "name")
@name.setter
def name(self, value: Optional[pulumi.Input['RandomPet']]):
def name(self, value: Optional[pulumi.Input['pulumi_random.RandomPet']]):
pulumi.set(self, "name", value)

View file

@ -7,7 +7,7 @@ import pulumi
import pulumi.runtime
from typing import Any, Mapping, Optional, Sequence, Union
from . import _utilities, _tables
from pulumi_random import RandomPet
import pulumi_random
__all__ = [
'ArgFunctionResult',
@ -37,7 +37,7 @@ class AwaitableArgFunctionResult(ArgFunctionResult):
age=self.age)
def arg_function(name: Optional['RandomPet'] = None,
def arg_function(name: Optional['pulumi_random.RandomPet'] = None,
opts: Optional[pulumi.InvokeOptions] = None) -> AwaitableArgFunctionResult:
"""
Use this data source to access information about an existing resource.

View file

@ -8,7 +8,7 @@ import pulumi.runtime
from typing import Any, Mapping, Optional, Sequence, Union
from . import _utilities, _tables
from ._inputs import *
from pulumi_random import RandomPet
import pulumi_random
__all__ = ['Cat']

View file

@ -7,9 +7,7 @@ import pulumi
import pulumi.runtime
from typing import Any, Mapping, Optional, Sequence, Union
from . import _utilities, _tables
from pulumi_aws import _ec2_securitygroup.SecurityGroup
from pulumi_kubernetes import _storage_k8s_io_v1.StorageClass
from pulumi_kubernetes import meta_v1 as _meta_v1
import pulumi_aws
import pulumi_kubernetes
__all__ = ['Component']
@ -19,7 +17,7 @@ class Component(pulumi.CustomResource):
def __init__(__self__,
resource_name: str,
opts: Optional[pulumi.ResourceOptions] = None,
metadata: Optional[pulumi.Input[pulumi.InputType['_meta_v1.ObjectMetaArgs']]] = None,
metadata: Optional[pulumi.Input[pulumi.InputType['pulumi_kubernetes.meta.v1.ObjectMetaArgs']]] = None,
__props__=None,
__name__=None,
__opts__=None):
@ -80,12 +78,12 @@ class Component(pulumi.CustomResource):
@property
@pulumi.getter(name="securityGroup")
def security_group(self) -> pulumi.Output[Optional['_ec2_securitygroup.SecurityGroup']]:
def security_group(self) -> pulumi.Output[Optional['pulumi_aws.ec2.SecurityGroup']]:
return pulumi.get(self, "security_group")
@property
@pulumi.getter(name="storageClasses")
def storage_classes(self) -> pulumi.Output[Optional[Mapping[str, '_storage_k8s_io_v1.StorageClass']]]:
def storage_classes(self) -> pulumi.Output[Optional[Mapping[str, 'pulumi_kubernetes.storage.v1.StorageClass']]]:
return pulumi.get(self, "storage_classes")
def translate_output_property(self, prop):

View file

@ -7,8 +7,7 @@ import pulumi
import pulumi.runtime
from typing import Any, Mapping, Optional, Sequence, Union
from . import _utilities, _tables
from pulumi_kubernetes import core_v1 as _core_v1
from pulumi_kubernetes import meta_v1 as _meta_v1
import pulumi_kubernetes
__all__ = ['Workload']
@ -69,7 +68,7 @@ class Workload(pulumi.CustomResource):
@property
@pulumi.getter
def pod(self) -> pulumi.Output[Optional['_core_v1.outputs.Pod']]:
def pod(self) -> pulumi.Output[Optional['pulumi_kubernetes.core.v1.outputs.Pod']]:
return pulumi.get(self, "pod")
def translate_output_property(self, prop):

View file

@ -7,7 +7,7 @@ import pulumi
import pulumi.runtime
from typing import Any, Mapping, Optional, Sequence, Union
from . import _utilities, _tables
from . import Resource
from .resource import Resource
__all__ = [
'ArgFunctionResult',

View file

@ -7,7 +7,7 @@ import pulumi
import pulumi.runtime
from typing import Any, Mapping, Optional, Sequence, Union
from . import _utilities, _tables
from . import Resource
from .resource import Resource
__all__ = ['OtherResource']

View file

@ -58,12 +58,12 @@ func (ss stringSet) has(s string) bool {
type imports stringSet
func (imports imports) addType(mod *modContext, tok string, input bool) {
imports.addTypeIf(mod, tok, input, nil /*predicate*/)
func (imports imports) addType(mod *modContext, t *schema.ObjectType, input bool) {
imports.addTypeIf(mod, t, input, nil /*predicate*/)
}
func (imports imports) addTypeIf(mod *modContext, tok string, input bool, predicate func(imp string) bool) {
if imp := mod.importTypeFromToken(tok, input); imp != "" && (predicate == nil || predicate(imp)) {
func (imports imports) addTypeIf(mod *modContext, t *schema.ObjectType, input bool, predicate func(imp string) bool) {
if imp := mod.importObjectType(t, input); imp != "" && (predicate == nil || predicate(imp)) {
stringSet(imports).add(imp)
}
}
@ -74,8 +74,8 @@ func (imports imports) addEnum(mod *modContext, tok string) {
}
}
func (imports imports) addResource(mod *modContext, tok string) {
if imp := mod.importResourceFromToken(tok); imp != "" {
func (imports imports) addResource(mod *modContext, r *schema.ResourceType) {
if imp := mod.importResourceType(r); imp != "" {
stringSet(imports).add(imp)
}
}
@ -129,8 +129,13 @@ func (mod *modContext) details(t *schema.ObjectType) *typeDetails {
return details
}
func (mod *modContext) tokenToType(tok string, input, functionType bool) string {
modName, name := mod.tokenToModule(tok), tokenToName(tok)
func (mod *modContext) objectType(t *schema.ObjectType, input, functionType bool) string {
modName, name := mod.tokenToModule(t.Token), tokenToName(t.Token)
var prefix string
if !input {
prefix = "outputs."
}
var suffix string
switch {
@ -140,6 +145,26 @@ func (mod *modContext) tokenToType(tok string, input, functionType bool) string
suffix = "Result"
}
// If it's an external type, reference it via fully qualified name.
if t.Package != mod.pkg {
pkg := t.Package
tok := t.Token
var info PackageInfo
contract.AssertNoError(pkg.ImportLanguages(map[string]schema.Language{"python": Importer}))
if v, ok := pkg.Language["python"].(PackageInfo); ok {
info = v
}
modName, name := tokenToModule(tok, pkg, info.ModuleNameOverrides), tokenToName(tok)
if modName == mod.mod {
modName = ""
}
if modName != "" {
modName = strings.ReplaceAll(modName, "/", ".") + "."
}
return fmt.Sprintf("'%s.%s%s%s%s'", pyPack(pkg.Name), modName, prefix, name, suffix)
}
if modName == "" && modName != mod.mod {
rootModName := "_root_outputs."
if input {
@ -155,11 +180,6 @@ func (mod *modContext) tokenToType(tok string, input, functionType bool) string
modName = "_" + strings.ReplaceAll(modName, "/", ".") + "."
}
var prefix string
if !input {
prefix = "outputs."
}
return fmt.Sprintf("'%s%s%s%s'", modName, prefix, name, suffix)
}
@ -180,6 +200,38 @@ func (mod *modContext) tokenToEnum(tok string) string {
return fmt.Sprintf("'%s%s'", modName, name)
}
func (mod *modContext) resourceType(r *schema.ResourceType) string {
if r.Resource == nil || r.Resource.Package == mod.pkg {
return mod.tokenToResource(r.Token)
}
// Provider. TODO share code.
components := strings.Split(r.Token, ":")
contract.Assertf(len(components) == 3, "malformed token %v", r.Token)
// Is it a provider resource?
if components[0] == "pulumi" && components[1] == "providers" {
return fmt.Sprintf("pulumi_%s.Provider", components[2])
}
pkg := r.Resource.Package
tok := r.Token
var info PackageInfo
contract.AssertNoError(pkg.ImportLanguages(map[string]schema.Language{"python": Importer}))
if v, ok := pkg.Language["python"].(PackageInfo); ok {
info = v
}
modName, name := tokenToModule(tok, pkg, info.ModuleNameOverrides), tokenToName(tok)
if modName == mod.mod {
modName = ""
}
if modName != "" {
modName = strings.ReplaceAll(modName, "/", ".") + "."
}
return fmt.Sprintf("%s.%s%s", pyPack(pkg.Name), modName, name)
}
func (mod *modContext) tokenToResource(tok string) string {
// token := pkg : module : member
// module := path/to/module
@ -583,7 +635,12 @@ func (mod *modContext) genResourceModule(w io.Writer) {
fmt.Fprintf(w, "_register_module()\n")
}
func (mod *modContext) importTypeFromToken(tok string, input bool) string {
func (mod *modContext) importObjectType(t *schema.ObjectType, input bool) string {
if t.Package != mod.pkg {
return fmt.Sprintf("import %s", pyPack(t.Package.Name))
}
tok := t.Token
parts := strings.Split(tok, ":")
contract.Assert(len(parts) == 3)
refPkgName := parts[0]
@ -629,7 +686,12 @@ func (mod *modContext) importEnumFromToken(tok string) string {
return fmt.Sprintf("from %s import %s", importPath, components[0])
}
func (mod *modContext) importResourceFromToken(tok string) string {
func (mod *modContext) importResourceType(r *schema.ResourceType) string {
if r.Resource != nil && r.Resource.Package != mod.pkg {
return fmt.Sprintf("import %s", pyPack(r.Resource.Package.Name))
}
tok := r.Token
parts := strings.Split(tok, ":")
contract.Assert(len(parts) == 3)
@ -647,8 +709,18 @@ func (mod *modContext) importResourceFromToken(tok string) string {
importPath = fmt.Sprintf("pulumi_%s", refPkgName)
}
name := PyName(tokenToName(r.Token))
if mod.compatibility == kubernetes20 {
// To maintain backward compatibility for kubernetes, the file names
// need to be CamelCase instead of the standard snake_case.
name = tokenToName(r.Token)
}
if r.Resource != nil && r.Resource.IsProvider {
name = "provider"
}
components := strings.Split(modName, "/")
return fmt.Sprintf("from %s import %s", importPath, components[0])
return fmt.Sprintf("from %s%s import %s", importPath, name, components[0])
}
// emitConfigVariables emits all config variables in the given module, returning the resulting file.
@ -659,11 +731,11 @@ func (mod *modContext) genConfig(variables []*schema.Property) (string, error) {
visitObjectTypesFromProperties(variables, seen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
imports.addType(mod, T.Token, false /*input*/)
imports.addType(mod, T, false /*input*/)
case *schema.EnumType:
imports.addEnum(mod, T.Token)
case *schema.ResourceType:
imports.addResource(mod, T.Token)
imports.addResource(mod, T)
}
})
@ -711,14 +783,14 @@ func (mod *modContext) genTypes(dir string, fs fs) error {
visitObjectTypesFromProperties(t.Properties, inputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
imports.addTypeIf(mod, T.Token, true /*input*/, func(imp string) bool {
imports.addTypeIf(mod, T, true /*input*/, func(imp string) bool {
// No need to import `._inputs` inside _inputs.py.
return imp != "from ._inputs import *"
})
case *schema.EnumType:
imports.addEnum(mod, T.Token)
case *schema.ResourceType:
imports.addResource(mod, T.Token)
imports.addResource(mod, T)
}
})
}
@ -726,11 +798,11 @@ func (mod *modContext) genTypes(dir string, fs fs) error {
visitObjectTypesFromProperties(t.Properties, outputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
imports.addType(mod, T.Token, false /*input*/)
imports.addType(mod, T, false /*input*/)
case *schema.EnumType:
imports.addEnum(mod, T.Token)
case *schema.ResourceType:
imports.addResource(mod, T.Token)
imports.addResource(mod, T)
}
})
}
@ -866,32 +938,32 @@ func (mod *modContext) genResource(res *schema.Resource) (string, error) {
visitObjectTypesFromProperties(res.Properties, outputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
imports.addType(mod, T.Token, false /*input*/)
imports.addType(mod, T, false /*input*/)
case *schema.EnumType:
imports.addEnum(mod, T.Token)
case *schema.ResourceType:
imports.addResource(mod, T.Token)
imports.addResource(mod, T)
}
})
visitObjectTypesFromProperties(res.InputProperties, inputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
imports.addType(mod, T.Token, true /*input*/)
imports.addType(mod, T, true /*input*/)
case *schema.EnumType:
imports.addEnum(mod, T.Token)
case *schema.ResourceType:
imports.addResource(mod, T.Token)
imports.addResource(mod, T)
}
})
if res.StateInputs != nil {
visitObjectTypesFromProperties(res.StateInputs.Properties, inputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
imports.addType(mod, T.Token, true /*input*/)
imports.addType(mod, T, true /*input*/)
case *schema.EnumType:
imports.addEnum(mod, T.Token)
case *schema.ResourceType:
imports.addResource(mod, T.Token)
imports.addResource(mod, T)
}
})
}
@ -1202,11 +1274,11 @@ func (mod *modContext) genFunction(fun *schema.Function) (string, error) {
visitObjectTypesFromProperties(fun.Inputs.Properties, inputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
imports.addType(mod, T.Token, true /*input*/)
imports.addType(mod, T, true /*input*/)
case *schema.EnumType:
imports.addEnum(mod, T.Token)
case *schema.ResourceType:
imports.addResource(mod, T.Token)
imports.addResource(mod, T)
}
})
}
@ -1214,11 +1286,11 @@ func (mod *modContext) genFunction(fun *schema.Function) (string, error) {
visitObjectTypesFromProperties(fun.Outputs.Properties, outputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
imports.addType(mod, T.Token, false /*input*/)
imports.addType(mod, T, false /*input*/)
case *schema.EnumType:
imports.addEnum(mod, T.Token)
case *schema.ResourceType:
imports.addResource(mod, T.Token)
imports.addResource(mod, T)
}
})
}
@ -1681,7 +1753,8 @@ func buildCaseMappingTables(pkg *schema.Package, snakeCaseToCamelCase, camelCase
func recordProperty(prop *schema.Property, snakeCaseToCamelCase, camelCaseToSnakeCase map[string]string, seenTypes codegen.Set) {
mapCase := true
if python, ok := prop.Language["python"]; ok {
mapCase = python.(PropertyInfo).MapCase
v, ok := python.(PropertyInfo)
mapCase = ok && v.MapCase
}
if mapCase {
snakeCaseName := PyNameLegacy(prop.Name)
@ -1823,12 +1896,12 @@ func (mod *modContext) typeString(t schema.Type, input, wrapInput, optional, acc
case *schema.MapType:
typ = fmt.Sprintf("Mapping[str, %s]", mod.typeString(t.ElementType, input, wrapInput, false, acceptMapping))
case *schema.ObjectType:
typ = mod.tokenToType(t.Token, input, mod.details(t).functionType)
typ = mod.objectType(t, input, mod.details(t).functionType)
if acceptMapping {
typ = fmt.Sprintf("pulumi.InputType[%s]", typ)
}
case *schema.ResourceType:
typ = fmt.Sprintf("'%s'", mod.tokenToResource(t.Token))
typ = fmt.Sprintf("'%s'", mod.resourceType(t))
case *schema.TokenType:
// Use the underlying type for now.
if t.UnderlyingType != nil {
@ -1916,7 +1989,7 @@ func (mod *modContext) pyType(typ schema.Type) string {
case *schema.MapType, *schema.ObjectType, *schema.UnionType:
return "dict"
case *schema.ResourceType:
return mod.tokenToResource(typ.Token)
return mod.resourceType(typ)
case *schema.TokenType:
if typ.UnderlyingType != nil {
return mod.pyType(typ.UnderlyingType)
@ -2166,12 +2239,12 @@ func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo
// group resources, types, and functions into modules
modules := map[string]*modContext{}
var getMod func(modName string) *modContext
getMod = func(modName string) *modContext {
var getMod func(modName string, p *schema.Package) *modContext
getMod = func(modName string, p *schema.Package) *modContext {
mod, ok := modules[modName]
if !ok {
mod = &modContext{
pkg: pkg,
pkg: p,
mod: modName,
tool: tool,
snakeCaseToCamelCase: snakeCaseToCamelCase,
@ -2180,29 +2253,33 @@ func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo
compatibility: info.Compatibility,
}
if modName != "" {
if modName != "" && p == pkg {
parentName := path.Dir(modName)
if parentName == "." {
parentName = ""
}
parent := getMod(parentName)
parent := getMod(parentName, p)
parent.children = append(parent.children, mod)
}
modules[modName] = mod
// Save the module only if it's for the current package.
// This way, modules for external packages are not saved.
if p == pkg {
modules[modName] = mod
}
}
return mod
}
getModFromToken := func(tok string) *modContext {
modName := tokenToModule(tok, pkg, info.ModuleNameOverrides)
return getMod(modName)
getModFromToken := func(tok string, p *schema.Package) *modContext {
modName := tokenToModule(tok, p, info.ModuleNameOverrides)
return getMod(modName, p)
}
// Create the config module if necessary.
if len(pkg.Config) > 0 &&
info.Compatibility != kubernetes20 { // k8s SDK doesn't use config.
configMod := getMod("config")
configMod := getMod("config", pkg)
configMod.isConfig = true
}
@ -2210,36 +2287,36 @@ func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo
visitObjectTypesFromProperties(pkg.Config, outputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
getModFromToken(T.Token).details(T).outputType = true
getModFromToken(T.Token, T.Package).details(T).outputType = true
}
})
// Find input and output types referenced by resources.
scanResource := func(r *schema.Resource) {
mod := getModFromToken(r.Token)
mod := getModFromToken(r.Token, pkg)
mod.resources = append(mod.resources, r)
visitObjectTypesFromProperties(r.Properties, outputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
getModFromToken(T.Token).details(T).outputType = true
getModFromToken(T.Token, T.Package).details(T).outputType = true
}
})
visitObjectTypesFromProperties(r.InputProperties, inputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
if r.IsProvider {
getModFromToken(T.Token).details(T).outputType = true
getModFromToken(T.Token, T.Package).details(T).outputType = true
}
getModFromToken(T.Token).details(T).inputType = true
getModFromToken(T.Token, T.Package).details(T).inputType = true
}
})
if r.StateInputs != nil {
visitObjectTypes(r.StateInputs, inputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
getModFromToken(T.Token).details(T).inputType = true
getModFromToken(T.Token, T.Package).details(T).inputType = true
case *schema.ResourceType:
getModFromToken(T.Token)
getModFromToken(T.Token, T.Resource.Package)
}
})
}
@ -2252,16 +2329,16 @@ func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo
// Find input and output types referenced by functions.
for _, f := range pkg.Functions {
mod := getModFromToken(f.Token)
mod := getModFromToken(f.Token, f.Package)
mod.functions = append(mod.functions, f)
if f.Inputs != nil {
visitObjectTypes(f.Inputs, inputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
getModFromToken(T.Token).details(T).inputType = true
getModFromToken(T.Token).details(T).functionType = true
getModFromToken(T.Token, T.Package).details(T).inputType = true
getModFromToken(T.Token, T.Package).details(T).functionType = true
case *schema.ResourceType:
getModFromToken(T.Token)
getModFromToken(T.Token, T.Resource.Package)
}
})
}
@ -2269,10 +2346,10 @@ func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo
visitObjectTypes(f.Outputs, outputSeen, func(t interface{}) {
switch T := t.(type) {
case *schema.ObjectType:
getModFromToken(T.Token).details(T).outputType = true
getModFromToken(T.Token).details(T).functionType = true
getModFromToken(T.Token, T.Package).details(T).outputType = true
getModFromToken(T.Token, T.Package).details(T).functionType = true
case *schema.ResourceType:
getModFromToken(T.Token)
getModFromToken(T.Token, T.Resource.Package)
}
})
}
@ -2282,13 +2359,13 @@ func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo
for _, t := range pkg.Types {
switch typ := t.(type) {
case *schema.ObjectType:
mod := getModFromToken(typ.Token)
mod := getModFromToken(typ.Token, typ.Package)
d := mod.details(typ)
if d.inputType || d.outputType {
mod.types = append(mod.types, typ)
}
case *schema.EnumType:
mod := getModFromToken(typ.Token)
mod := getModFromToken(typ.Token, pkg)
mod.enums = append(mod.enums, typ)
default:
continue
@ -2306,7 +2383,7 @@ func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo
if modName == "/" || modName == "." {
modName = ""
}
mod := getMod(modName)
mod := getMod(modName, pkg)
mod.extraSourceFiles = append(mod.extraSourceFiles, p)
}

View file

@ -423,7 +423,7 @@ func (p *providerServer) Construct(ctx context.Context,
aliases[i] = resource.URN(urn)
}
dependencies := make([]resource.URN, len(req.GetDependencies()))
for i, urn := range req.GetAliases() {
for i, urn := range req.GetDependencies() {
dependencies[i] = resource.URN(urn)
}
propertyDependencies := map[resource.PropertyKey][]resource.URN{}

416
sdk/go/pulumi/provider.go Normal file
View file

@ -0,0 +1,416 @@
// Copyright 2016-2021, 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 pulumi
import (
"context"
"flag"
"fmt"
"reflect"
"sort"
"strings"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/cmdutil"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/logging"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/rpcutil"
pbempty "github.com/golang/protobuf/ptypes/empty"
pulumirpc "github.com/pulumi/pulumi/sdk/v2/proto/go"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func ProviderMain(provider ProviderArgs) error {
if provider.Name == "" {
return errors.New("provider.Name must not be empty")
}
if provider.Version == "" {
return errors.New("provider.Version must not be empty")
}
var tracing string
flag.StringVar(&tracing, "tracing", "", "Emit tracing to a Zipkin-compatible tracing endpoint")
flag.Parse()
// Initialize loggers before going any further.
logging.InitLogging(false, 0, false)
cmdutil.InitTracing(provider.Name, provider.Name, tracing)
// Read the non-flags args and connect to the engine.
args := flag.Args()
if len(args) == 0 {
return errors.New("fatal: could not connect to host RPC; missing argument")
}
prov := &providerServer{
provider: provider,
engineAddr: args[0],
}
// Fire up a gRPC server, letting the kernel choose a free port for us.
port, done, err := rpcutil.Serve(0, nil, []func(*grpc.Server) error{
func(srv *grpc.Server) error {
pulumirpc.RegisterResourceProviderServer(srv, prov)
return nil
},
}, nil)
if err != nil {
return errors.Errorf("fatal: %v", err)
}
// The resource provider protocol requires that we now write out the port we have chosen to listen on.
fmt.Printf("%d\n", port)
// Finally, wait for the server to stop serving.
if err := <-done; err != nil {
return errors.Errorf("fatal: %v", err)
}
return nil
}
type providerServer struct {
provider ProviderArgs
engineAddr string
}
type ProviderArgs struct {
Name string
Version string
Schema []byte
ConstructF func(ctx *Context, typ, name string, inputs *ConstructInputs,
options ResourceOption) (ConstructResult, error)
// TODO add all the other gRPC methods.
}
type constructInput struct {
value interface{}
secret bool
deps []Resource
}
type ConstructInputs struct {
inputs map[string]constructInput
}
func (inputs *ConstructInputs) Map() Map {
result := Map{}
for k, v := range inputs.inputs {
output := newOutput(anyOutputType, v.deps...)
output.resolve(v.value, true /*known*/, v.secret, nil)
result[k] = output
}
return result
}
func (inputs *ConstructInputs) SetArgs(args interface{}) error {
if args == nil {
return errors.New("args must not be nil")
}
argsV := reflect.ValueOf(args)
typ := argsV.Type()
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
return errors.New("args must be a pointer to a struct")
}
argsV, typ = argsV.Elem(), typ.Elem()
for k, v := range inputs.inputs {
for i := 0; i < typ.NumField(); i++ {
fieldV := argsV.Field(i)
if !fieldV.CanSet() {
continue
}
field := typ.Field(i)
tag, has := field.Tag.Lookup("pulumi")
if !has || tag != k {
continue
}
toOutputMethodName := "To" + strings.TrimSuffix(field.Type.Name(), "Input") + "OutputWithContext"
toOutputMethod, found := field.Type.MethodByName(toOutputMethodName)
if !found {
continue
}
mt := toOutputMethod.Type
if mt.NumIn() != 1 || mt.In(0) != contextType || mt.NumOut() != 1 {
continue
}
outputType := mt.Out(0)
if !outputType.Implements(reflect.TypeOf((*Output)(nil)).Elem()) {
continue
}
output := newOutput(outputType, v.deps...)
output.resolve(v.value, true /*known*/, v.secret, nil)
fieldV.Set(reflect.ValueOf(output))
}
}
return nil
}
type ConstructResult struct {
URN URNInput
State Input
}
// Construct creates a new instance of the provided component resource and returns its state.
func (p *providerServer) Construct(ctx context.Context,
req *pulumirpc.ConstructRequest) (*pulumirpc.ConstructResponse, error) {
if p.provider.ConstructF == nil {
return nil, errors.Errorf("unknown resource type %s", req.GetType())
}
// Configure the RunInfo.
runInfo := RunInfo{
Project: req.GetProject(),
Stack: req.GetStack(),
Config: req.GetConfig(),
Parallel: int(req.GetParallel()),
DryRun: req.GetDryRun(),
MonitorAddr: req.GetMonitorEndpoint(),
EngineAddr: p.engineAddr,
Mocks: nil,
}
pulumiCtx, err := NewContext(ctx, runInfo)
if err != nil {
return nil, errors.Wrap(err, "constructing run context")
}
// Deserialize the inputs and apply appropriate dependencies.
inputs := &ConstructInputs{inputs: map[string]constructInput{}}
inputDependencies := req.GetInputDependencies()
deserializedInputs, err := plugin.UnmarshalProperties(
req.GetInputs(),
plugin.MarshalOptions{KeepSecrets: true, KeepResources: true, KeepUnknowns: req.GetDryRun()},
)
if err != nil {
return nil, errors.Wrap(err, "unmarshaling inputs")
}
for key, input := range deserializedInputs {
k := string(key)
var deps []Resource
if inputDeps, ok := inputDependencies[k]; ok {
deps = make([]Resource, len(inputDeps.GetUrns()))
for i, depURN := range inputDeps.GetUrns() {
deps[i] = newDependencyResource(URN(depURN))
}
}
val, secret, err := unmarshalPropertyValue(pulumiCtx, input)
if err != nil {
return nil, errors.Wrapf(err, "unmarshaling input %s", k)
}
inputs.inputs[k] = constructInput{
value: val,
secret: secret,
deps: deps,
}
}
// Rebuild the resource options.
aliases := make([]Alias, len(req.GetAliases()))
for i, urn := range req.GetAliases() {
aliases[i] = Alias{URN: URN(urn)}
}
dependencies := make([]Resource, len(req.GetDependencies()))
for i, urn := range req.GetDependencies() {
dependencies[i] = newDependencyResource(URN(urn))
}
providers := make(map[string]ProviderResource, len(req.GetProviders()))
for pkg, ref := range req.GetProviders() {
// Parse the URN and ID out of the provider reference.
lastSep := strings.LastIndex(ref, "::")
if lastSep == -1 {
return nil, errors.Errorf("expected '::' in provider reference %s", ref)
}
urn := ref[0:lastSep]
id := ref[lastSep+2:]
providers[pkg] = newDependencyProviderResource(URN(urn), ID(id))
}
var parent Resource
if req.GetParent() != "" {
parent = newDependencyResource(URN(req.GetParent()))
}
opts := resourceOption(func(ro *resourceOptions) {
ro.Aliases = aliases
ro.DependsOn = dependencies
ro.Protect = req.GetProtect()
ro.Providers = providers
ro.Parent = parent
})
result, err := p.provider.ConstructF(pulumiCtx, req.GetType(), req.GetName(), inputs, opts)
if err != nil {
return nil, err
}
// Ensure all outstanding RPCs have completed before proceeding. Also, prevent any new RPCs from happening.
pulumiCtx.waitForRPCs()
if pulumiCtx.rpcError != nil {
return nil, errors.Wrap(pulumiCtx.rpcError, "waiting for RPCs")
}
rpcURN, _, _, err := result.URN.ToURNOutput().awaitURN(ctx)
if err != nil {
return nil, err
}
// Serialize all state properties, first by awaiting them, and then marshaling them to the requisite gRPC values.
resolvedProps, propertyDeps, _, err := marshalInputs(result.State)
if err != nil {
return nil, errors.Wrap(err, "marshaling properties")
}
// Marshal all properties for the RPC call.
keepUnknowns := req.GetDryRun()
rpcProps, err := plugin.MarshalProperties(
resolvedProps,
plugin.MarshalOptions{KeepSecrets: true, KeepUnknowns: keepUnknowns, KeepResources: pulumiCtx.keepResources})
if err != nil {
return nil, errors.Wrap(err, "marshaling properties")
}
// Convert the property dependencies map for RPC and remove duplicates.
rpcPropertyDeps := make(map[string]*pulumirpc.ConstructResponse_PropertyDependencies)
for k, deps := range propertyDeps {
sort.Slice(deps, func(i, j int) bool { return deps[i] < deps[j] })
urns := make([]string, 0, len(deps))
for i, d := range deps {
if i > 0 && urns[i-1] == string(d) {
continue
}
urns = append(urns, string(d))
}
rpcPropertyDeps[k] = &pulumirpc.ConstructResponse_PropertyDependencies{
Urns: urns,
}
}
return &pulumirpc.ConstructResponse{
Urn: string(rpcURN),
State: rpcProps,
StateDependencies: rpcPropertyDeps,
}, nil
}
// GetSchema returns the JSON-encoded schema for this provider's package.
func (p *providerServer) GetSchema(ctx context.Context,
req *pulumirpc.GetSchemaRequest) (*pulumirpc.GetSchemaResponse, error) {
if v := req.GetVersion(); v != 0 {
return nil, errors.Errorf("unsupported schema version %d", v)
}
return &pulumirpc.GetSchemaResponse{Schema: string(p.provider.Schema)}, nil
}
// CheckConfig validates the configuration for this provider.
func (p *providerServer) CheckConfig(ctx context.Context,
req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
return nil, status.Error(codes.Unimplemented, "CheckConfig is not yet implemented")
}
// DiffConfig diffs the configuration for this provider.
func (p *providerServer) DiffConfig(ctx context.Context, req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
return nil, status.Error(codes.Unimplemented, "DiffConfig is not yet implemented")
}
// Configure configures the resource provider with "globals" that control its behavior.
func (p *providerServer) Configure(ctx context.Context,
req *pulumirpc.ConfigureRequest) (*pulumirpc.ConfigureResponse, error) {
return &pulumirpc.ConfigureResponse{
AcceptSecrets: true,
SupportsPreview: true,
AcceptResources: true,
}, nil
}
// Invoke dynamically executes a built-in function in the provider.
func (p *providerServer) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error) {
tok := req.GetTok()
return nil, errors.Errorf("unknown Invoke token %q", tok)
}
// StreamInvoke dynamically executes a built-in function in the provider. The result is streamed
// back as a series of messages.
func (p *providerServer) StreamInvoke(req *pulumirpc.InvokeRequest,
server pulumirpc.ResourceProvider_StreamInvokeServer) error {
tok := req.GetTok()
return errors.Errorf("unknown StreamInvoke token %q", tok)
}
// Check validates that the given property bag is valid for a resource of the given type and returns
// the inputs that should be passed to successive calls to Diff, Create, or Update for this
// resource. As a rule, the provider inputs returned by a call to Check should preserve the original
// representation of the properties as present in the program inputs. Though this rule is not
// required for correctness, violations thereof can negatively impact the end-user experience, as
// the provider inputs are using for detecting and rendering diffs.
func (p *providerServer) Check(ctx context.Context, req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
return nil, status.Error(codes.Unimplemented, "Check is not yet implemented")
}
// Diff checks what impacts a hypothetical update will have on the resource's properties.
func (p *providerServer) Diff(ctx context.Context, req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
return nil, status.Error(codes.Unimplemented, "Diff is not yet implemented")
}
// Create allocates a new instance of the provided resource and returns its unique ID afterwards.
// (The input ID must be blank.) If this call fails, the resource must not have been created (i.e.,
// it is "transactional").
func (p *providerServer) Create(ctx context.Context, req *pulumirpc.CreateRequest) (*pulumirpc.CreateResponse, error) {
return nil, status.Error(codes.Unimplemented, "Create is not yet implemented")
}
// Read the current live state associated with a resource. Enough state must be include in the
// inputs to uniquely identify the resource; this is typically just the resource ID, but may also
// include some properties.
func (p *providerServer) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*pulumirpc.ReadResponse, error) {
return nil, status.Error(codes.Unimplemented, "Read is not yet implemented")
}
// Update updates an existing resource with new values.
func (p *providerServer) Update(ctx context.Context, req *pulumirpc.UpdateRequest) (*pulumirpc.UpdateResponse, error) {
return nil, status.Error(codes.Unimplemented, "Update is not yet implemented")
}
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed
// to still exist.
func (p *providerServer) Delete(ctx context.Context, req *pulumirpc.DeleteRequest) (*pbempty.Empty, error) {
return &pbempty.Empty{}, nil
}
// GetPluginInfo returns generic information about this plugin, like its version.
func (p *providerServer) GetPluginInfo(context.Context, *pbempty.Empty) (*pulumirpc.PluginInfo, error) {
return &pulumirpc.PluginInfo{
Version: p.provider.Version,
}, nil
}
// Cancel signals the provider to gracefully shut down and abort any ongoing resource operations.
// Operations aborted in this way will return an error (e.g., `Update` and `Create` will either a
// creation error or an initialization error). Since Cancel is advisory and non-blocking, it is up
// to the host to decide how long to wait after Cancel is called before (e.g.)
// hard-closing any gRPC connection.
func (p *providerServer) Cancel(context.Context, *pbempty.Empty) (*pbempty.Empty, error) {
return &pbempty.Empty{}, nil
}