Delete the old IDL compiler (#801)
It is moving to a new location: https://github.com/pulumi/pidlc
This commit is contained in:
parent
aa8f8eadcd
commit
34984ba1cb
8
Gopkg.lock
generated
8
Gopkg.lock
generated
|
@ -210,12 +210,6 @@
|
|||
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
|
||||
revision = "88f656faf3f37f690df1a32515b479415e1a6769"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
packages = ["go/ast/astutil","go/buildutil","go/loader"]
|
||||
revision = "05e91d06384e3a4eb0aba75560d254b8d5a7b431"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/genproto"
|
||||
|
@ -249,6 +243,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "70b9f724d4635dd578c12a951ddcfe2315a569443822eeb162fbba2cbcf7627e"
|
||||
inputs-digest = "d03e73559b3e1af69207eeae805c237c8bef6f836a2322e883d428e0abef1810"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
1
Makefile
1
Makefile
|
@ -15,7 +15,6 @@ TEST_FAST_TIMEOUT := 2m
|
|||
|
||||
build::
|
||||
go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${PROJECT}
|
||||
go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${PROJECT}/cmd/lumidl
|
||||
|
||||
install::
|
||||
GOBIN=$(PULUMI_BIN) go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${PROJECT}
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<ItemGroup>
|
||||
<NodeSdkProtosForBinplace Include="$(NodeSdkDirectory)\proto\nodejs\**\*" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<Copy SourceFiles="@(NodeSdkProtos)"
|
||||
DestinationFolder="$(NodeJSSdkDirectory)\bin\proto" />
|
||||
</Target>
|
||||
|
@ -105,7 +105,6 @@
|
|||
<Target Name="BuildGoCmds">
|
||||
<ItemGroup>
|
||||
<GoCmdsToBuild Include="github.com/pulumi/pulumi" />
|
||||
<GoCmdsToBuild Include="github.com/pulumi/pulumi/cmd/lumidl" />
|
||||
</ItemGroup>
|
||||
|
||||
<Exec Command="git describe --tags 2>nul" ConsoleToMSBuild="true" Condition="'$(Version)' == ''">
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
# Lumi IDL
|
||||
|
||||
The Lumi IDL compiler (LumIDL) compiles an Go-based IDL into Lumi metadata and packages.
|
||||
|
||||
## IDL
|
||||
|
||||
The compiler, `lumidl`, accepts a subset of Go. Please refer to [the IDL design document](/docs/idl.md) for details.
|
||||
|
||||
## Providers
|
||||
|
||||
The primary use case for Lumi IDL is to author resource packages and providers. A resource package is a low level
|
||||
Lumi package with metadata associated with a set of resource type definitions. Its associated provider is a dynamic
|
||||
plugin that implements the behavior associated with those resources, their CRUD functions, and operational semantics.
|
||||
|
||||
The LumIDL toolset cuts down on boilerplate and makes it easy to author new resource packages and providers.
|
||||
|
||||
## Building
|
||||
|
||||
To build the Lumi IDL compiler, run
|
||||
|
||||
$ go install github.com/pulumi/pulumi/cmd/lumidl
|
||||
|
||||
from an enlistment with a proper `GOPATH` set.
|
||||
|
||||
## Running
|
||||
|
||||
To generate code, run:
|
||||
|
||||
$ lumidl pkg-name idl-path [flags]
|
||||
|
||||
where the `pkg-name` is the name of the target Lumi package, `idl-path` is a path to a directory containing IDL, and
|
||||
`flags` are an optional set of flags. All `*.go` files in the target directory are parsed and processed as IDL. To
|
||||
recursively fetch sub-packages, pass the `--recursive` (or `-r`) flag.
|
||||
|
||||
The output includes the following:
|
||||
|
||||
* A Lumi package in LumiJS, containing resource definitions.
|
||||
* An RPC package in Go to aid in the creation of a resource provider, containing:
|
||||
- A base resource provider that handles marshaling goo at the edges.
|
||||
- A marshalable type for each resource type (used for dynamic plugin serialization).
|
||||
|
||||
The two flags, `--out-pack` and `--out-rpc` control if and where the output will be generated, respectively. It is
|
||||
possible to specify just one or the other, or you can generate both simultaneously.
|
||||
|
||||
By default, the IDL Go package is inferred from a combination of `idl-path` and `GOPATH`. This is used to generate
|
||||
inter- and intra-package references. If you are generating RPC code, that too is inferred, based on `--out-rpc` and
|
||||
`GOPATH`. In the event you are running outside of a Go workspace (where `GOPATH` is not set), or need to customize
|
||||
these, the packages can be set by hand using the flags `--pkg-base-idl` and `--pkg-base-rpc`, respectively.
|
||||
|
||||
After generating the code and implementing behavior associated with the resource in the provider, the Lumi package
|
||||
may then be distributed to consumers using LumiPy, LumiRu, LumiJS, and other LumiLangs.
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
"github.com/pulumi/pulumi/pkg/tools/lumidl"
|
||||
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||
)
|
||||
|
||||
func NewIDLCCmd() *cobra.Command {
|
||||
var logToStderr bool
|
||||
var outPack string
|
||||
var outRPC string
|
||||
var pkgBaseIDL string
|
||||
var pkgBaseRPC string
|
||||
var quiet bool
|
||||
var recursive bool
|
||||
var verbose int
|
||||
cmd := &cobra.Command{
|
||||
Use: "lumidl pkg-name idl-path",
|
||||
Short: "The Lumi IDL compiler generates Lumi metadata and RPC stubs from IDL written in Go",
|
||||
Long: "The Lumi IDL compiler generates Lumi metadata and RPC stubs from IDL written in Go.\n" +
|
||||
"\n" +
|
||||
"The tool accepts a subset of Go types and produces packages that can be consumed by\n" +
|
||||
"ordinary Lumi programs and libraries in any language. The pkg-name argument\n" +
|
||||
"controls the output package name and idl-path is the path to the IDL source code.\n" +
|
||||
"\n" +
|
||||
"The --out-pack and --out-rpc flags indicate where generated code is to be saved,\n" +
|
||||
"and pkg-base-idl and --pkg-base-rpc may be used to override the default inferred Go\n" +
|
||||
"package names (which, by default, are based on your GOPATH).",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.InitLogging(logToStderr, verbose, true)
|
||||
},
|
||||
Args: cmdutil.ExactArgs(2),
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
// Now pass the arguments and compile the package.
|
||||
name := args[0] // the name of the Lumi package.
|
||||
path := args[1] // the path to the IDL directory that is compiled recursively.
|
||||
return lumidl.Compile(lumidl.CompileOptions{
|
||||
Name: tokens.PackageName(name),
|
||||
PkgBaseIDL: pkgBaseIDL,
|
||||
PkgBaseRPC: pkgBaseRPC,
|
||||
OutPack: outPack,
|
||||
OutRPC: outRPC,
|
||||
Quiet: quiet,
|
||||
Recursive: recursive,
|
||||
}, path)
|
||||
}),
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
glog.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&logToStderr, "logtostderr", false, "Log to stderr instead of to files")
|
||||
cmd.PersistentFlags().BoolVarP(
|
||||
&recursive, "recursive", "r", false, "Recursively generate code for all sub-packages in the target")
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&outPack, "out-pack", "", "Save generated package metadata to this directory")
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&outRPC, "out-rpc", "", "Save generated RPC provider stubs to this directory")
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&pkgBaseIDL, "pkg-base-idl", "", "Override the base URL where the IDL package is published")
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&pkgBaseRPC, "pkg-base-rpc", "", "Override the base URL where the RPC package is published")
|
||||
cmd.PersistentFlags().BoolVarP(
|
||||
&quiet, "quiet", "q", false, "Suppress non-error output progress messages")
|
||||
cmd.PersistentFlags().IntVarP(
|
||||
&verbose, "verbose", "v", 0, "Enable verbose logging (e.g., v=3); anything >3 is very verbose")
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := NewIDLCCmd().Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "An error occurred: %v\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
|
@ -1,389 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/diag"
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
type Checker struct {
|
||||
Root string
|
||||
Program *loader.Program
|
||||
EnumValues map[types.Type][]string
|
||||
}
|
||||
|
||||
func NewChecker(root string, prog *loader.Program) *Checker {
|
||||
return &Checker{
|
||||
Root: root,
|
||||
Program: prog,
|
||||
}
|
||||
}
|
||||
|
||||
// diag produces a nice diagnostic location (document+position) from a Go element. It should be used for all output
|
||||
// messages to enable easy correlation with the source IDL artifact that triggered an error.
|
||||
func (chk *Checker) diag(elem goPos) diag.Diagable {
|
||||
return goDiag(chk.Program, elem, chk.Root)
|
||||
}
|
||||
|
||||
// Check analyzes a Go program, ensures that it is valid as an IDL, and classifies all of the types that it
|
||||
// encounters. These classifications are returned. If problems are encountered, diagnostic messages will be output
|
||||
// and the returned error will be non-nil.
|
||||
func (chk *Checker) Check(name tokens.PackageName, pkginfo *loader.PackageInfo) (*Package, error) {
|
||||
ok := true
|
||||
|
||||
// First just create a list of the constants and types so we can visit them in the right order. Also maintain a
|
||||
// file map so that we can recover the AST information later on (required for import processing, etc).
|
||||
var goconsts []*types.Const
|
||||
var gotypes []*types.TypeName
|
||||
|
||||
// Enumerate the scope and classify all objects.
|
||||
scope := pkginfo.Pkg.Scope()
|
||||
for _, objname := range scope.Names() {
|
||||
obj := scope.Lookup(objname)
|
||||
switch o := obj.(type) {
|
||||
case *types.Const:
|
||||
goconsts = append(goconsts, o)
|
||||
case *types.TypeName:
|
||||
gotypes = append(gotypes, o)
|
||||
default:
|
||||
ok = false
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("%v is an unrecognized Go declaration type: %v").At(chk.diag(obj)),
|
||||
objname, reflect.TypeOf(obj))
|
||||
}
|
||||
}
|
||||
|
||||
// Start building a package to return.
|
||||
pkg := NewPackage(name, chk.Program, pkginfo)
|
||||
oldenums := chk.EnumValues
|
||||
chk.EnumValues = make(map[types.Type][]string)
|
||||
defer (func() { chk.EnumValues = oldenums })()
|
||||
|
||||
getfile := func(path string) *File {
|
||||
// If the file exists, fetch it.
|
||||
if file, has := pkg.Files[path]; has {
|
||||
return file
|
||||
}
|
||||
// Otherwise, find the AST node, and create a new object.
|
||||
for _, fileast := range pkginfo.Files {
|
||||
if rel := RelFilename(chk.Root, chk.Program, fileast); rel == path {
|
||||
mod := string(name) + ":"
|
||||
if ext := filepath.Ext(rel); ext != "" {
|
||||
mod += rel[:len(rel)-len(ext)]
|
||||
} else {
|
||||
mod += rel
|
||||
}
|
||||
file := NewFile(path, tokens.Module(mod), fileast)
|
||||
pkg.Files[path] = file
|
||||
return file
|
||||
}
|
||||
}
|
||||
contract.Failf("Missing file AST for path %v", path)
|
||||
return nil
|
||||
}
|
||||
getdecl := func(file *File, obj types.Object) ast.Decl {
|
||||
for _, decl := range file.Node.Decls {
|
||||
if gdecl, isgdecl := decl.(*ast.GenDecl); isgdecl {
|
||||
for _, spec := range gdecl.Specs {
|
||||
switch sp := spec.(type) {
|
||||
case *ast.ImportSpec:
|
||||
// ignore
|
||||
case *ast.TypeSpec:
|
||||
if sp.Name.Name == obj.Name() {
|
||||
return decl
|
||||
}
|
||||
case *ast.ValueSpec:
|
||||
for _, name := range sp.Names {
|
||||
if name.Name == obj.Name() {
|
||||
return decl
|
||||
}
|
||||
}
|
||||
default:
|
||||
contract.Failf("Unrecognized GenDecl Spec type: %v", reflect.TypeOf(sp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
contract.Failf("Missing object AST decl for %v in %v", obj.Name(), file)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Now visit all constants so that we can have them handy as we visit enum types.
|
||||
for _, goconst := range goconsts {
|
||||
path := RelFilename(chk.Root, chk.Program, goconst)
|
||||
file := getfile(path)
|
||||
decl := getdecl(file, goconst)
|
||||
if c, cok := chk.CheckConst(goconst, file, decl); cok {
|
||||
nm := tokens.Name(goconst.Name())
|
||||
pkg.AddMember(file, nm, c)
|
||||
} else {
|
||||
contract.Assert(!cmdutil.Diag().Success())
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
// Next, visit all the types.
|
||||
for _, gotype := range gotypes {
|
||||
path := RelFilename(chk.Root, chk.Program, gotype)
|
||||
file := getfile(path)
|
||||
decl := getdecl(file, gotype)
|
||||
if t, tok := chk.CheckType(gotype, file, decl); tok {
|
||||
nm := tokens.Name(gotype.Name())
|
||||
pkg.AddMember(file, nm, t)
|
||||
} else {
|
||||
contract.Assert(!cmdutil.Diag().Success())
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
contract.Assert(!cmdutil.Diag().Success())
|
||||
return nil, errors.New("one or more problems with the input IDL were found; skipping code-generation")
|
||||
}
|
||||
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
func (chk *Checker) CheckConst(c *types.Const, file *File, decl ast.Decl) (*Const, bool) {
|
||||
pt := c.Type()
|
||||
var t types.Type
|
||||
if IsPrimitive(pt) {
|
||||
// A primitive, just use it as-is.
|
||||
t = pt
|
||||
} else if named, isnamed := pt.(*types.Named); isnamed {
|
||||
// A constant of a type alias. This is how IDL enums are defined, so interpret it as such.
|
||||
if basic, isbasic := named.Underlying().(*types.Basic); isbasic && basic.Kind() == types.String {
|
||||
// Use this type and remember the enum value.
|
||||
t = pt
|
||||
chk.EnumValues[t] = append(chk.EnumValues[t], c.Val().String())
|
||||
} else {
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("enums must be string-backed; %v has type %v").At(chk.diag(decl)),
|
||||
c, named,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("only constants of valid primitive types (bool, float64, number, or aliases) supported").At(
|
||||
chk.diag(decl)))
|
||||
}
|
||||
|
||||
if t != nil {
|
||||
return &Const{
|
||||
member: member{
|
||||
tok: tokens.ModuleMember(string(file.Module) + ":" + c.Name()),
|
||||
exported: c.Exported(),
|
||||
pos: c.Pos(),
|
||||
},
|
||||
Type: pt,
|
||||
Value: c.Val(),
|
||||
}, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (chk *Checker) CheckType(t *types.TypeName, file *File, decl ast.Decl) (Member, bool) {
|
||||
memb := member{
|
||||
tok: tokens.ModuleMember(string(file.Module) + ":" + t.Name()),
|
||||
exported: t.Exported(),
|
||||
pos: t.Pos(),
|
||||
}
|
||||
switch typ := t.Type().(type) {
|
||||
case *types.Named:
|
||||
switch s := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
// A type alias, possibly interpreted as an enum if there are constants.
|
||||
if IsPrimitive(s) {
|
||||
if vals, isenum := chk.EnumValues[typ]; isenum {
|
||||
// There are enum values defined, use them to create an enum type.
|
||||
return &Enum{
|
||||
member: memb,
|
||||
Values: vals,
|
||||
}, true
|
||||
}
|
||||
// Otherwise, this is a simple type alias.
|
||||
return &Alias{
|
||||
member: memb,
|
||||
target: s,
|
||||
}, true
|
||||
}
|
||||
|
||||
cmdutil.Diag().Errorf(diag.Message(
|
||||
"type alias %v is not a valid IDL alias type (must be bool, float64, or string)").At(
|
||||
chk.diag(decl)))
|
||||
case *types.Map, *types.Slice:
|
||||
return &Alias{
|
||||
member: memb,
|
||||
target: s,
|
||||
}, true
|
||||
case *types.Struct:
|
||||
// A struct definition, possibly a resource. First, check that all the fields are supported types.
|
||||
isres := IsResource(t, s)
|
||||
if ok, props, opts := chk.CheckStructFields(typ.Obj(), s, isres); ok {
|
||||
// If a resource, return additional information.
|
||||
if isres {
|
||||
return &Resource{
|
||||
member: memb,
|
||||
s: s,
|
||||
props: props,
|
||||
popts: opts,
|
||||
}, true
|
||||
}
|
||||
// Otherwise, it's a plain old ordinary struct.
|
||||
return &Struct{
|
||||
member: memb,
|
||||
s: s,
|
||||
props: props,
|
||||
popts: opts,
|
||||
}, true
|
||||
}
|
||||
contract.Assert(!cmdutil.Diag().Success())
|
||||
default:
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("%v is an illegal underlying type: %v").At(chk.diag(decl)), s, reflect.TypeOf(s))
|
||||
}
|
||||
default:
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("%v is an illegal Go type kind: %v").At(chk.diag(decl)), t.Name(), reflect.TypeOf(typ))
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// CheckStructFields ensures that a struct only contains valid "JSON-like" fields
|
||||
func (chk *Checker) CheckStructFields(t *types.TypeName, s *types.Struct,
|
||||
isres bool) (bool, []*types.Var, []PropertyOptions) {
|
||||
ok := true
|
||||
var allprops []*types.Var
|
||||
var allopts []PropertyOptions
|
||||
for i := 0; i < s.NumFields(); i++ {
|
||||
fld := s.Field(i)
|
||||
if fld.Anonymous() {
|
||||
// If an embedded structure, validate its fields deeply.
|
||||
anon := fld.Type().(*types.Named)
|
||||
embedded := anon.Underlying().(*types.Struct)
|
||||
isembres := IsResource(anon.Obj(), embedded)
|
||||
isok, props, opts := chk.CheckStructFields(anon.Obj(), embedded, isembres)
|
||||
if !isok {
|
||||
ok = false
|
||||
}
|
||||
allprops = append(allprops, props...)
|
||||
allopts = append(allopts, opts...)
|
||||
} else {
|
||||
allprops = append(allprops, fld)
|
||||
opts := ParsePropertyOptions(s.Tag(i))
|
||||
allopts = append(allopts, opts)
|
||||
if opts.Name == "" {
|
||||
ok = false
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("field %v.%v is missing a `pulumi:\"<name>\"` tag directive").At(chk.diag(fld)),
|
||||
t.Name(), fld.Name())
|
||||
}
|
||||
if opts.Out && !isres {
|
||||
ok = false
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("field %v.%v is marked `out` but is not a resource property").At(chk.diag(fld)),
|
||||
t.Name(), fld.Name())
|
||||
}
|
||||
if opts.Replaces && !isres {
|
||||
ok = false
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("field %v.%v is marked `replaces` but is not a resource property").At(chk.diag(fld)),
|
||||
t.Name(), fld.Name())
|
||||
}
|
||||
if _, isptr := fld.Type().(*types.Pointer); !isptr && opts.Optional {
|
||||
ok = false
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("field %v.%v is marked `optional` but is not a pointer in the IDL").At(chk.diag(fld)),
|
||||
t.Name(), fld.Name())
|
||||
}
|
||||
if err := chk.CheckIDLType(fld.Type(), opts); err != nil {
|
||||
ok = false
|
||||
cmdutil.Diag().Errorf(
|
||||
diag.Message("field %v.%v is an not a legal IDL type: %v").At(chk.diag(fld)),
|
||||
t.Name(), fld.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok, allprops, allopts
|
||||
}
|
||||
|
||||
func (chk *Checker) CheckIDLType(t types.Type, opts PropertyOptions) error {
|
||||
// Only these types are legal:
|
||||
// - Primitives: bool, float64, string
|
||||
// - Other structs
|
||||
// - Pointers to any of the above (if-and-only-if an optional property)
|
||||
// - Pointers to other resource types (capabilities)
|
||||
// - Arrays of the above things
|
||||
// - Maps with string keys and any of the above as values
|
||||
switch ft := t.(type) {
|
||||
case *types.Basic:
|
||||
if !IsPrimitive(ft) {
|
||||
return errors.Errorf("bad primitive type %v; must be bool, float64, or string", ft)
|
||||
}
|
||||
case *types.Interface:
|
||||
// interface{} is fine and is interpreted as a weakly typed map.
|
||||
return nil
|
||||
case *types.Named:
|
||||
switch ut := ft.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
// A named type alias of a primitive type. Ensure it is legal.
|
||||
if !IsPrimitive(ut) {
|
||||
return errors.Errorf(
|
||||
"typedef %v backed by bad primitive type %v; must be bool, float64, or string", ft, ut)
|
||||
}
|
||||
case *types.Struct:
|
||||
// Struct types are okay so long as they aren't entities (these are required to be pointers).
|
||||
if isent := IsEntity(ft.Obj(), ut); isent {
|
||||
return errors.Errorf("type %v cannot be referenced by-value; must be a pointer", ft)
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("bad named field type: %v", reflect.TypeOf(ut))
|
||||
}
|
||||
case *types.Pointer:
|
||||
// A pointer is OK so long as the field is either optional or an entity type (asset, resource, etc).
|
||||
if !opts.Optional && !opts.In && !opts.Out {
|
||||
elem := ft.Elem()
|
||||
var ok bool
|
||||
if named, isnamed := elem.(*types.Named); isnamed {
|
||||
ok = IsEntity(named.Obj(), named)
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("bad pointer; must be optional or a resource type")
|
||||
}
|
||||
}
|
||||
case *types.Map:
|
||||
// A map is OK so long as its key is a string (or string-backed type) and its element type is okay.
|
||||
isstr := false
|
||||
switch kt := ft.Key().(type) {
|
||||
case *types.Basic:
|
||||
isstr = (kt.Kind() == types.String)
|
||||
case *types.Named:
|
||||
if bt, isbt := kt.Underlying().(*types.Basic); isbt {
|
||||
isstr = (bt.Kind() == types.String)
|
||||
}
|
||||
}
|
||||
if !isstr {
|
||||
return errors.Errorf("map index type %v must be a string (or string-backed typedef)", ft.Key())
|
||||
}
|
||||
return chk.CheckIDLType(ft.Elem(), PropertyOptions{})
|
||||
|
||||
case *types.Slice:
|
||||
// A slice is OK so long as its element type is also OK.
|
||||
return chk.CheckIDLType(ft.Elem(), PropertyOptions{})
|
||||
default:
|
||||
contract.Failf("Unrecognized field type %v: %v", t, reflect.TypeOf(t))
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
)
|
||||
|
||||
type CompileOptions struct {
|
||||
Name tokens.PackageName // the package name.
|
||||
PkgBaseIDL string // the base Go package URL for the IDL input.
|
||||
PkgBaseRPC string // the base Go package URL for the RPC output.
|
||||
OutPack string // the package output location.
|
||||
OutRPC string // the RPC output location.
|
||||
Quiet bool // true to suppress innocuous output messages.
|
||||
Recursive bool // true to generate code for all sub-packages.
|
||||
}
|
||||
|
||||
// Compile runs the Go compiler against an IDL project and then generates code for the resulting program.
|
||||
func Compile(opts CompileOptions, path string) error {
|
||||
// Ensure we are generating *something*.
|
||||
if opts.OutPack == "" && opts.OutRPC == "" {
|
||||
return errors.New("neither --out-pack nor --out-rpc specified; no code to generate")
|
||||
}
|
||||
|
||||
// Adjust settings to their defaults and adjust any paths to be absolute.
|
||||
if path == "" {
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
path = wd
|
||||
}
|
||||
} else {
|
||||
if absPath, err := filepath.Abs(path); err == nil {
|
||||
path = absPath
|
||||
}
|
||||
}
|
||||
if opts.PkgBaseIDL == "" {
|
||||
// The default IDL package base is just the GOPATH package path for the target IDL path.
|
||||
pkgpath, err := goPackagePath(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.PkgBaseIDL = pkgpath
|
||||
}
|
||||
if opts.OutPack != "" {
|
||||
if outpack, err := filepath.Abs(opts.OutPack); err == nil {
|
||||
opts.OutPack = outpack
|
||||
}
|
||||
}
|
||||
if opts.OutRPC != "" {
|
||||
if outrpc, err := filepath.Abs(opts.OutRPC); err == nil {
|
||||
opts.OutRPC = outrpc
|
||||
}
|
||||
|
||||
// If there is no package base, pick a default based on GOPATH.
|
||||
if opts.PkgBaseRPC == "" {
|
||||
// The default RPC package base, like the IDL package base, defaults to the GOPATH package path.
|
||||
pkgpath, err := goPackagePath(opts.OutRPC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.PkgBaseRPC = pkgpath
|
||||
}
|
||||
}
|
||||
|
||||
var inputs []string
|
||||
if opts.Recursive {
|
||||
inp, err := gatherGoPackages(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inputs = inp
|
||||
} else {
|
||||
inputs = []string{opts.PkgBaseIDL}
|
||||
}
|
||||
|
||||
// First point the Go compiler at the target packages to compile. Note that this runs both parsing and semantic
|
||||
// analysis, and will yield an error if anything with the Go program is wrong.
|
||||
var conf loader.Config
|
||||
if _, err := conf.FromArgs(inputs, false); err != nil {
|
||||
return err
|
||||
}
|
||||
conf.ParserMode |= parser.ParseComments // ensure doc comments are retained.
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now create in-memory IDL packages, validating contents as we go. The result contains classified elements
|
||||
// such as resources, structs, enum types, and anything required in order to perform subsequent code-generation.
|
||||
chk := NewChecker(path, prog)
|
||||
var packgen *PackGenerator
|
||||
if out := opts.OutPack; out != "" {
|
||||
packgen = NewPackGenerator(prog, path, opts.PkgBaseIDL, out)
|
||||
}
|
||||
var rpcgen *RPCGenerator
|
||||
if out := opts.OutRPC; out != "" {
|
||||
rpcgen = NewRPCGenerator(path, opts.PkgBaseIDL, opts.PkgBaseRPC, out)
|
||||
}
|
||||
|
||||
// Enumerate all packages (in a deterministic order).
|
||||
var pkgs []*types.Package
|
||||
for pkg := range prog.AllPackages {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
sort.Slice(pkgs, func(i, j int) bool {
|
||||
return pkgs[i].Path() < pkgs[j].Path()
|
||||
})
|
||||
for _, pkg := range pkgs {
|
||||
// Only emit packages that are underneath the base IDL package.
|
||||
if !strings.HasPrefix(pkg.Path(), opts.PkgBaseIDL) {
|
||||
continue
|
||||
}
|
||||
|
||||
pkginfo := prog.AllPackages[pkg]
|
||||
if !opts.Quiet {
|
||||
fmt.Printf("Processing package %v\n", pkginfo.Pkg.Path())
|
||||
}
|
||||
|
||||
outpkg, err := chk.Check(opts.Name, pkginfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now generate the package output.
|
||||
if packgen != nil {
|
||||
if err = packgen.Generate(outpkg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Next generate the RPC stubs output.
|
||||
if rpcgen != nil {
|
||||
if err = rpcgen.Generate(outpkg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// goPackagePath takes a path to a filesystem location and returns its Go package path, based on GOPATH. Given a path
|
||||
// referring to a source location of the form, `$GOPATH/src/...`, the function returns the `...` part.
|
||||
func goPackagePath(path string) (string, error) {
|
||||
// Fetch the GOPATH; it must be set, else we bail out.
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if gopath == "" {
|
||||
return "", errors.New("GOPATH is not set, so package paths cannot be inferred (see --pkg-base-x)")
|
||||
}
|
||||
gopath = filepath.Join(gopath, "src")
|
||||
|
||||
// Now ensure that the package path is a proper subset within it.
|
||||
if !strings.HasPrefix(path, gopath+string(os.PathSeparator)) {
|
||||
return "", errors.Errorf(
|
||||
"Package root '%v' is not underneath $GOPATH/src, so its package cannot be inferred", path)
|
||||
}
|
||||
|
||||
// Finally, strip off the GOPATH/src prefix, and return the remainder.
|
||||
return path[len(gopath)+1:], nil
|
||||
}
|
||||
|
||||
// gatherGoPackages recurses into a given path and fetches all of its inferred Go packages. The algorithm considers
|
||||
// any sub-directory containing a *.go file, recursively, to be a package. It could, of course, be wrong.
|
||||
func gatherGoPackages(path string) ([]string, error) {
|
||||
var pkgs []string
|
||||
|
||||
// First, if this path contains Go files, append it.
|
||||
var dirs []string
|
||||
hasGoFiles := false
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
dirs = append(dirs, file.Name())
|
||||
} else if filepath.Ext(file.Name()) == ".go" {
|
||||
hasGoFiles = true
|
||||
}
|
||||
}
|
||||
if hasGoFiles {
|
||||
pkg, err := goPackagePath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
|
||||
// Next, enumerate all directories recursively, to find all Go sub-packages.
|
||||
for _, dir := range dirs {
|
||||
subpkgs, err := gatherGoPackages(filepath.Join(path, dir))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgs = append(pkgs, subpkgs...)
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/diag"
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
type goPos interface {
|
||||
Pos() token.Pos
|
||||
}
|
||||
|
||||
// goDiag produces a diagnostics object out of a Go type artifact.
|
||||
func goDiag(prog *loader.Program, elem goPos, relto string) diag.Diagable {
|
||||
pos := prog.Fset.Position(elem.Pos())
|
||||
file := pos.Filename
|
||||
if relto != "" {
|
||||
var err error
|
||||
file, err = filepath.Rel(relto, file)
|
||||
contract.Assertf(err == nil, "error: %v", err)
|
||||
}
|
||||
return &goDiagable{
|
||||
doc: diag.NewDocument(file),
|
||||
loc: &diag.Location{
|
||||
Start: diag.Pos{
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type goDiagable struct {
|
||||
doc *diag.Document
|
||||
loc *diag.Location
|
||||
}
|
||||
|
||||
func (d *goDiagable) Where() (*diag.Document, *diag.Location) {
|
||||
return d.doc, d.loc
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tools"
|
||||
)
|
||||
|
||||
const lumidl = "the Lumi IDL Compiler (LUMIDL)" // used in generated files.
|
||||
|
||||
// mirrorDirLayout ensures a target output directory contains the same layout as the input package.
|
||||
func mirrorDirLayout(pkg *Package, out string) error {
|
||||
for relpath := range pkg.Files {
|
||||
// Make the target file by concatening the output with the relative path, and ensure the directory exists.
|
||||
path := filepath.Join(out, relpath)
|
||||
if err := tools.EnsureFileDir(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func forEachField(t TypeMember, action func(*types.Var, PropertyOptions)) int {
|
||||
return forEachStructField(t.Struct(), t.PropertyOptions(), action)
|
||||
}
|
||||
|
||||
func forEachStructField(s *types.Struct, opts []PropertyOptions, action func(*types.Var, PropertyOptions)) int {
|
||||
n := 0
|
||||
for i, j := 0, 0; i < s.NumFields(); i++ {
|
||||
fld := s.Field(i)
|
||||
if fld.Anonymous() {
|
||||
// For anonymous types, recurse.
|
||||
named := fld.Type().(*types.Named)
|
||||
embedded := named.Underlying().(*types.Struct)
|
||||
k := forEachStructField(embedded, opts[j:], action)
|
||||
j += k
|
||||
n += k
|
||||
} else {
|
||||
// For actual fields, invoke the action, and bump the counters.
|
||||
if action != nil {
|
||||
action(s.Field(i), opts[j])
|
||||
}
|
||||
j++
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
|
@ -1,445 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
"github.com/pulumi/pulumi/pkg/tools"
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
// TODO[pulumi/pulumi#139]: preserve GoDocs in the generated code.
|
||||
|
||||
type PackGenerator struct {
|
||||
Program *loader.Program // the compiled Go program.
|
||||
IDLRoot string // the path to the IDL on disk.
|
||||
IDLPkgBase string // the IDL's base package path.
|
||||
Out string // where to store the output package.
|
||||
CurrPkg *Package // the package currently being visited.
|
||||
CurrFile string // the file currently being visited.
|
||||
FileHadRes bool // true if the file had at least one resource.
|
||||
FileImports map[string]MemberImports // a map of imported package paths and members.
|
||||
}
|
||||
|
||||
type MemberImports map[tokens.Name]string
|
||||
|
||||
func NewPackGenerator(prog *loader.Program, root string, pkgBase string, out string) *PackGenerator {
|
||||
return &PackGenerator{
|
||||
Program: prog,
|
||||
IDLRoot: root,
|
||||
IDLPkgBase: pkgBase,
|
||||
Out: out,
|
||||
}
|
||||
}
|
||||
|
||||
// Filename gets the source filename for a given Go element.
|
||||
func (g *PackGenerator) Filename(elem goPos) string {
|
||||
pos := elem.Pos()
|
||||
fset := g.Program.Fset
|
||||
return fset.Position(pos).Filename
|
||||
}
|
||||
|
||||
// Generate generates a Lumi package's source code from a given compiled IDL program.
|
||||
func (g *PackGenerator) Generate(pkg *Package) error {
|
||||
// Ensure the directory structure exists in the target.
|
||||
if err := mirrorDirLayout(pkg, g.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install context about the current entity being visited.
|
||||
oldpkg, oldfile := g.CurrPkg, g.CurrFile
|
||||
g.CurrPkg = pkg
|
||||
defer (func() {
|
||||
g.CurrPkg, g.CurrFile = oldpkg, oldfile
|
||||
})()
|
||||
|
||||
// Now walk through the package, file by file, and generate the contents.
|
||||
for relpath, file := range pkg.Files {
|
||||
var members []Member
|
||||
for _, nm := range file.MemberNames {
|
||||
members = append(members, file.Members[nm])
|
||||
}
|
||||
g.CurrFile = relpath
|
||||
path := filepath.Join(g.Out, relpath)
|
||||
if err := g.EmitFile(path, members); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *PackGenerator) EmitFile(file string, members []Member) error {
|
||||
// Set up context.
|
||||
oldHadRes, oldImports := g.FileHadRes, g.FileImports
|
||||
g.FileHadRes, g.FileImports = false, make(map[string]MemberImports)
|
||||
defer (func() {
|
||||
g.FileHadRes = oldHadRes
|
||||
g.FileImports = oldImports
|
||||
})()
|
||||
|
||||
// First, generate the body. This is required first so we know which imports to emit.
|
||||
body := g.genFileBody(members)
|
||||
|
||||
// Next actually open up the file and emit the header, imports, and the body of the module.
|
||||
return g.emitFileContents(file, body)
|
||||
}
|
||||
|
||||
func (g *PackGenerator) emitFileContents(file string, body string) error {
|
||||
// The output is TypeScript, so alter the extension.
|
||||
if dotindex := strings.LastIndex(file, "."); dotindex != -1 {
|
||||
file = file[:dotindex]
|
||||
}
|
||||
file += ".ts"
|
||||
|
||||
// Open up a writer that overwrites whatever file contents already exist.
|
||||
w, err := tools.NewGenWriter(lumidl, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer contract.IgnoreClose(w)
|
||||
|
||||
// Emit a header into the file.
|
||||
w.EmitHeaderWarning()
|
||||
w.Writefmtln("/* tslint:disable:ordered-imports variable-name */")
|
||||
|
||||
// If there are any resources, import the Lumi package.
|
||||
if g.FileHadRes {
|
||||
w.Writefmtln("import * as fabric from \"@pulumi/pulumi\";")
|
||||
w.Writefmtln("")
|
||||
}
|
||||
if len(g.FileImports) > 0 {
|
||||
// First, sort the imported package names, to ensure determinism.
|
||||
var ipkgs []string
|
||||
for ipkg := range g.FileImports {
|
||||
ipkgs = append(ipkgs, ipkg)
|
||||
}
|
||||
sort.Strings(ipkgs)
|
||||
|
||||
for _, ipkg := range ipkgs {
|
||||
// Produce a map of filename to the members in that file that have been imported.
|
||||
importMap := make(map[string][]tokens.Name)
|
||||
for member, file := range g.FileImports[ipkg] {
|
||||
importMap[file] = append(importMap[file], member)
|
||||
}
|
||||
|
||||
// Now sort them to ensure determinism.
|
||||
var importFiles []string
|
||||
for file := range importMap {
|
||||
importFiles = append(importFiles, file)
|
||||
}
|
||||
sort.Strings(importFiles)
|
||||
|
||||
// Next, walk each imported file and import all members from within it.
|
||||
for _, file := range importFiles {
|
||||
// Make a relative import path from the current file.
|
||||
contract.Assertf(strings.HasPrefix(file, g.IDLRoot),
|
||||
"Inter-IDL package references not yet supported (%v is not part of %v)", file, g.IDLRoot)
|
||||
dir := filepath.Dir(g.CurrFile)
|
||||
relimp, err := filepath.Rel(dir, file[len(g.IDLRoot)+1:])
|
||||
contract.Assertf(err == nil, "Unexpected filepath.Rel error: %v", err)
|
||||
var impname string
|
||||
if strings.HasPrefix(relimp, ".") {
|
||||
impname = relimp
|
||||
} else {
|
||||
impname = "./" + relimp
|
||||
}
|
||||
if filepath.Ext(impname) != "" {
|
||||
lastdot := strings.LastIndex(impname, ".")
|
||||
impname = impname[:lastdot]
|
||||
}
|
||||
|
||||
// Now produce a sorted list of imported members, again to ensure determinism.
|
||||
members := importMap[file]
|
||||
contract.Assert(len(members) > 0)
|
||||
sort.Slice(members, func(i, j int) bool {
|
||||
return string(members[i]) < string(members[j])
|
||||
})
|
||||
|
||||
// Finally, go through and produce the import clause.
|
||||
w.Writefmt("import {")
|
||||
for i, member := range members {
|
||||
if i > 0 {
|
||||
w.Writefmt(", ")
|
||||
}
|
||||
w.Writefmt(string(member))
|
||||
}
|
||||
w.Writefmtln("} from \"%v\";", impname)
|
||||
}
|
||||
}
|
||||
w.Writefmtln("")
|
||||
}
|
||||
|
||||
w.Writefmt("%v", body)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *PackGenerator) genFileBody(members []Member) string {
|
||||
// Accumulate the buffer in a string.
|
||||
w, err := tools.NewGenWriter(lumidl, "")
|
||||
contract.IgnoreError(err)
|
||||
|
||||
// Now go ahead and emit the code for all members of this package.
|
||||
for i, m := range members {
|
||||
if i > 0 {
|
||||
// Allow aliases and consts to pile up without line breaks.
|
||||
_, isalias := m.(*Alias)
|
||||
_, isconst := m.(*Const)
|
||||
if (!isalias && !isconst) || reflect.TypeOf(m) != reflect.TypeOf(members[i-1]) {
|
||||
w.Writefmtln("")
|
||||
}
|
||||
}
|
||||
switch t := m.(type) {
|
||||
case *Alias:
|
||||
g.EmitAlias(w, t)
|
||||
case *Const:
|
||||
g.EmitConst(w, t)
|
||||
case *Enum:
|
||||
g.EmitEnum(w, t)
|
||||
case *Resource:
|
||||
g.EmitResource(w, t)
|
||||
case *Struct:
|
||||
g.EmitStruct(w, t)
|
||||
default:
|
||||
contract.Failf("Unrecognized package member type: %v", reflect.TypeOf(m))
|
||||
}
|
||||
}
|
||||
|
||||
w.Writefmtln("")
|
||||
err = w.Flush()
|
||||
contract.IgnoreError(err)
|
||||
return w.Buffer()
|
||||
}
|
||||
|
||||
func (g *PackGenerator) EmitAlias(w *tools.GenWriter, alias *Alias) {
|
||||
w.Writefmtln("export type %v = %v;",
|
||||
alias.Name(), g.GenTypeName(alias.Target(), NormalField))
|
||||
}
|
||||
|
||||
func (g *PackGenerator) EmitConst(w *tools.GenWriter, konst *Const) {
|
||||
w.Writefmtln("export let %v: %v = %v;",
|
||||
konst.Name(), g.GenTypeName(konst.Type, NormalField), konst.Value.String())
|
||||
}
|
||||
|
||||
func (g *PackGenerator) EmitEnum(w *tools.GenWriter, enum *Enum) {
|
||||
w.Writefmtln("export type %v =", enum.Name())
|
||||
contract.Assert(len(enum.Values) > 0)
|
||||
for i, value := range enum.Values {
|
||||
if i > 0 {
|
||||
w.Writefmtln(" |")
|
||||
}
|
||||
w.Writefmt(" %v", value)
|
||||
}
|
||||
w.Writefmtln(";")
|
||||
}
|
||||
|
||||
func (g *PackGenerator) EmitResource(w *tools.GenWriter, res *Resource) {
|
||||
// Emit the full resource class definition, including constructor, etc.
|
||||
g.emitResourceClass(w, res)
|
||||
w.Writefmtln("")
|
||||
|
||||
// Finally, emit an entire struct type for the args interface.
|
||||
g.emitStructType(w, res, res.Name()+tokens.Name("Args"))
|
||||
|
||||
// Remember we had a resource in this file so we can import the right stuff.
|
||||
g.FileHadRes = true
|
||||
}
|
||||
|
||||
func (g *PackGenerator) emitResourceClass(w *tools.GenWriter, res *Resource) {
|
||||
// Emit the class definition itself.
|
||||
name := res.Name()
|
||||
w.Writefmtln("export class %s extends fabric.Resource {", name)
|
||||
|
||||
// First, emit all fields definitions.
|
||||
hasArgs := false
|
||||
hasRequiredArgs := false
|
||||
fn := forEachField(res, func(fld *types.Var, opt PropertyOptions) {
|
||||
g.emitField(w, fld, ComputedField, opt, " public ")
|
||||
if !opt.Out {
|
||||
hasArgs = true
|
||||
if !opt.Optional {
|
||||
hasRequiredArgs = true
|
||||
}
|
||||
}
|
||||
})
|
||||
if fn > 0 {
|
||||
w.Writefmtln("")
|
||||
}
|
||||
|
||||
// Add the standard "factory" functions: get and query. These are static, so they go before the constructor.
|
||||
w.Writefmtln(" public static get(id: fabric.ID): %s {", name)
|
||||
w.Writefmtln(" return <any>undefined; // functionality provided by the runtime")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln(" public static query(q: any): %s[] {", name)
|
||||
w.Writefmtln(" return <any>undefined; // functionality provided by the runtime")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln("")
|
||||
|
||||
// Next, a constructor that validates arguments and chains to the base constructor.
|
||||
var opt string
|
||||
if !hasRequiredArgs {
|
||||
opt = "?"
|
||||
}
|
||||
w.Writefmtln(" constructor(name: string, args%s: %sArgs) {", opt, name)
|
||||
|
||||
// First, validate that required parameters exist, and store all arguments on the object.
|
||||
argLinePrefix := " "
|
||||
needsArgsCheck := hasArgs && !hasRequiredArgs
|
||||
if needsArgsCheck {
|
||||
w.Writefmtln(" if (args !== undefined) {")
|
||||
argLinePrefix += " "
|
||||
}
|
||||
forEachField(res, func(fld *types.Var, opt PropertyOptions) {
|
||||
if !opt.Out && !opt.Optional {
|
||||
w.Writefmtln("%sif (args.%s === undefined) {", argLinePrefix, opt.Name)
|
||||
w.Writefmtln("%s throw new Error(\"Missing required property '%s'\");", argLinePrefix, opt.Name)
|
||||
w.Writefmtln("%s}", argLinePrefix)
|
||||
}
|
||||
})
|
||||
if needsArgsCheck {
|
||||
w.Writefmtln(" }")
|
||||
}
|
||||
|
||||
// Now invoke the base.
|
||||
w.Writefmtln(" super(\"%s\", name, {", res.Tok())
|
||||
forEachField(res, func(fld *types.Var, opt PropertyOptions) {
|
||||
w.Writefmt(" \"%s\": ", opt.Name)
|
||||
if opt.Out {
|
||||
w.Writefmtln("undefined,")
|
||||
} else {
|
||||
w.Writefmtln("args.%s,", opt.Name)
|
||||
}
|
||||
})
|
||||
|
||||
w.Writefmtln(" });")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln("}")
|
||||
}
|
||||
|
||||
func (g *PackGenerator) EmitStruct(w *tools.GenWriter, s *Struct) {
|
||||
g.emitStructType(w, s, s.Name())
|
||||
}
|
||||
|
||||
func (g *PackGenerator) emitStructType(w *tools.GenWriter, t TypeMember, name tokens.Name) {
|
||||
w.Writefmtln(fmt.Sprintf("export interface %s {", name))
|
||||
forEachField(t, func(fld *types.Var, opt PropertyOptions) {
|
||||
if opt.Out {
|
||||
return // skip output properties, since those exist solely on the resource class.
|
||||
}
|
||||
g.emitField(w, fld, MaybeComputedField, opt, " ")
|
||||
})
|
||||
w.Writefmtln("}")
|
||||
}
|
||||
|
||||
type FieldKind int
|
||||
|
||||
const (
|
||||
NormalField FieldKind = iota
|
||||
ComputedField
|
||||
MaybeComputedField
|
||||
)
|
||||
|
||||
func (g *PackGenerator) emitField(w *tools.GenWriter, fld *types.Var,
|
||||
kind FieldKind, opt PropertyOptions, prefix string) {
|
||||
var mods string
|
||||
if opt.Replaces {
|
||||
mods += "readonly "
|
||||
}
|
||||
if opt.Out {
|
||||
mods += "/*out*/ "
|
||||
}
|
||||
var optional string
|
||||
if opt.Optional {
|
||||
optional = "?"
|
||||
}
|
||||
typ := g.GenTypeName(fld.Type(), kind)
|
||||
if kind == ComputedField {
|
||||
typ = fmt.Sprintf("fabric.Computed<%v>", typ)
|
||||
}
|
||||
w.Writefmtln("%v%v%v%v: %v;", prefix, mods, opt.Name, optional, typ)
|
||||
}
|
||||
|
||||
func (g *PackGenerator) GenTypeName(t types.Type, kind FieldKind) string {
|
||||
var ret string
|
||||
var skipwrap bool
|
||||
switch u := t.(type) {
|
||||
case *types.Basic:
|
||||
switch k := u.Kind(); k {
|
||||
case types.Bool:
|
||||
ret = "boolean"
|
||||
case types.String:
|
||||
ret = "string"
|
||||
case types.Float64:
|
||||
ret = "number"
|
||||
default:
|
||||
contract.Failf("Unrecognized GenTypeName basic type: %v", k)
|
||||
}
|
||||
case *types.Interface:
|
||||
ret = "any"
|
||||
case *types.Named:
|
||||
obj := u.Obj()
|
||||
if spec, kind := IsSpecial(obj); spec {
|
||||
switch kind {
|
||||
case SpecialArchiveType:
|
||||
ret = "fabric.asset.Archive"
|
||||
case SpecialAssetType:
|
||||
ret = "fabric.asset.Asset"
|
||||
case SpecialResourceType:
|
||||
ret = "fabric.Resource"
|
||||
default:
|
||||
contract.Failf("Unrecognized special type: %v", kind)
|
||||
}
|
||||
} else {
|
||||
// Our import logic will have arranged for the type name to be available.
|
||||
// IDEA: consider auto-generated import names to avoid conflicts between imported and local names.
|
||||
g.trackNameReference(obj)
|
||||
ret = obj.Name()
|
||||
}
|
||||
case *types.Map:
|
||||
ret = fmt.Sprintf("{[key: %s]: %s}", g.GenTypeName(u.Key(), kind), g.GenTypeName(u.Elem(), kind))
|
||||
case *types.Pointer:
|
||||
ret = g.GenTypeName(u.Elem(), kind) // no pointers in TypeScript, just emit the underlying type.
|
||||
skipwrap = true // don't doubly emit wrapper types
|
||||
case *types.Slice:
|
||||
ret = fmt.Sprintf("%s[]", g.GenTypeName(u.Elem(), kind)) // postfix syntax for arrays.
|
||||
default:
|
||||
contract.Failf("Unrecognized GenTypeName type: %s", reflect.TypeOf(u))
|
||||
}
|
||||
|
||||
if !skipwrap && kind == MaybeComputedField {
|
||||
ret = fmt.Sprintf("fabric.MaybeComputed<%s>", ret)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// trackNameReference registers that we have seen a foreign package and requests that the imports be emitted for it.
|
||||
func (g *PackGenerator) trackNameReference(obj *types.TypeName) {
|
||||
// If a reference to a type within the same package and file, there is no need to register anything.
|
||||
pkg := obj.Pkg()
|
||||
member := tokens.Name(obj.Name())
|
||||
if pkg == g.CurrPkg.Pkginfo.Pkg &&
|
||||
g.CurrPkg.MemberFiles[member].Path == g.CurrFile {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we need to track the member so that we can import it later on. Make sure not to add duplicates
|
||||
// because we want to ensure we don't import the same thing twice.
|
||||
path := pkg.Path()
|
||||
members, has := g.FileImports[path]
|
||||
if !has {
|
||||
members = make(MemberImports)
|
||||
g.FileImports[path] = members
|
||||
}
|
||||
members[member] = g.Filename(obj)
|
||||
}
|
|
@ -1,547 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
"github.com/pulumi/pulumi/pkg/tools"
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
type RPCGenerator struct {
|
||||
IDLRoot string // the root where IDL is loaded from.
|
||||
IDLPkgBase string // the IDL's base package path.
|
||||
RPCPkgBase string // the RPC's base package path.
|
||||
Out string // where RPC stub outputs will be saved.
|
||||
CurrPkg *Package // the package currently being visited.
|
||||
CurrFile string // the file currently being visited.
|
||||
FileHadRes bool // true if the file had at least one resource.
|
||||
FileImports map[string]string // a map of foreign packages used in a file.
|
||||
}
|
||||
|
||||
func NewRPCGenerator(root, idlPkgBase, rpcPkgBase, out string) *RPCGenerator {
|
||||
return &RPCGenerator{
|
||||
IDLRoot: root,
|
||||
IDLPkgBase: idlPkgBase,
|
||||
RPCPkgBase: rpcPkgBase,
|
||||
Out: out,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *RPCGenerator) Generate(pkg *Package) error {
|
||||
// Ensure the directory structure exists in the target.
|
||||
if err := mirrorDirLayout(pkg, g.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install context about the current entity being visited.
|
||||
oldpkg, oldfile := g.CurrPkg, g.CurrFile
|
||||
g.CurrPkg = pkg
|
||||
defer (func() {
|
||||
g.CurrPkg = oldpkg
|
||||
g.CurrFile = oldfile
|
||||
})()
|
||||
|
||||
// Now walk through the package, file by file, and generate the contents.
|
||||
for relpath, file := range pkg.Files {
|
||||
g.CurrFile = relpath
|
||||
var members []Member
|
||||
for _, nm := range file.MemberNames {
|
||||
members = append(members, file.Members[nm])
|
||||
}
|
||||
path := filepath.Join(g.Out, relpath)
|
||||
if err := g.EmitFile(path, pkg, members); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *RPCGenerator) EmitFile(file string, pkg *Package, members []Member) error {
|
||||
oldHadRes, oldImports := g.FileHadRes, g.FileImports
|
||||
g.FileHadRes, g.FileImports = false, make(map[string]string)
|
||||
defer (func() {
|
||||
g.FileHadRes = oldHadRes
|
||||
g.FileImports = oldImports
|
||||
})()
|
||||
|
||||
// First, generate the body. This is required first so we know which imports to emit.
|
||||
body := g.genFileBody(file, pkg, members)
|
||||
|
||||
// Open up a writer that overwrites whatever file contents already exist.
|
||||
w, err := tools.NewGenWriter(lumidl, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer contract.IgnoreClose(w)
|
||||
|
||||
// Emit a header into the file.
|
||||
w.EmitHeaderWarning()
|
||||
|
||||
// Now emit the package name at the top-level.
|
||||
w.Writefmtln("package %v", pkg.Pkginfo.Pkg.Name())
|
||||
w.Writefmtln("")
|
||||
|
||||
// And all of the imports that we're going to need.
|
||||
if g.FileHadRes || len(g.FileImports) > 0 {
|
||||
w.Writefmtln("import (")
|
||||
|
||||
if g.FileHadRes {
|
||||
w.Writefmtln(` pbempty "github.com/golang/protobuf/ptypes/empty"`)
|
||||
w.Writefmtln(` pbstruct "github.com/golang/protobuf/ptypes/struct"`)
|
||||
w.Writefmtln(` "golang.org/x/net/context"`)
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/resource"`)
|
||||
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/resource/plugin"`)
|
||||
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/tokens"`)
|
||||
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/util/contract"`)
|
||||
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/util/mapper"`)
|
||||
w.Writefmtln(` lumirpc "github.com/pulumi/pulumi/sdk/proto/go"`)
|
||||
}
|
||||
|
||||
if len(g.FileImports) > 0 {
|
||||
if g.FileHadRes {
|
||||
w.Writefmtln("")
|
||||
}
|
||||
// Sort the imports so they are in a correct, deterministic order.
|
||||
var imports []string
|
||||
for imp := range g.FileImports {
|
||||
imports = append(imports, imp)
|
||||
}
|
||||
sort.Strings(imports)
|
||||
|
||||
// Now just emit a list of imports with their given names.
|
||||
for _, imp := range imports {
|
||||
name := g.FileImports[imp]
|
||||
|
||||
// If the import referenced one of the IDL packages, we must rewrite it to an RPC package.
|
||||
contract.Assertf(strings.HasPrefix(imp, g.IDLPkgBase),
|
||||
"Inter-IDL package references not yet supported (%v is not part of %v)", imp, g.IDLPkgBase)
|
||||
var imppath string
|
||||
if imp == g.IDLPkgBase {
|
||||
imppath = g.RPCPkgBase
|
||||
} else {
|
||||
relimp := imp[len(g.IDLPkgBase)+1:]
|
||||
imppath = g.RPCPkgBase + "/" + relimp
|
||||
}
|
||||
|
||||
w.Writefmtln(` %v "%v"`, name, imppath)
|
||||
}
|
||||
}
|
||||
|
||||
w.Writefmtln(")")
|
||||
w.Writefmtln("")
|
||||
}
|
||||
|
||||
// Now finally emit the actual body and close out the file.
|
||||
w.Writefmtln("%v", body)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func (g *RPCGenerator) genFileBody(file string, pkg *Package, members []Member) string {
|
||||
w, err := tools.NewGenWriter(lumidl, "")
|
||||
contract.IgnoreError(err)
|
||||
|
||||
// First, for each RPC struct/resource member, emit its appropriate generated code.
|
||||
var typedefs []Typedef
|
||||
var consts []*Const
|
||||
module := g.getFileModule(file)
|
||||
for _, m := range members {
|
||||
switch t := m.(type) {
|
||||
case *Alias:
|
||||
typedefs = append(typedefs, t)
|
||||
case *Const:
|
||||
consts = append(consts, t)
|
||||
case *Enum:
|
||||
typedefs = append(typedefs, t)
|
||||
case *Resource:
|
||||
g.EmitResource(w, module, pkg, t)
|
||||
g.EmitStructType(w, module, pkg, t)
|
||||
case *Struct:
|
||||
g.EmitStructType(w, module, pkg, t)
|
||||
default:
|
||||
contract.Failf("Unrecognized package member type: %v", reflect.TypeOf(t))
|
||||
}
|
||||
}
|
||||
|
||||
// Next emit all supporting types. First, aliases and enum types.
|
||||
if len(typedefs) > 0 {
|
||||
g.EmitTypedefs(w, typedefs)
|
||||
}
|
||||
|
||||
// Finally, emit any consts at the very end.
|
||||
if len(consts) > 0 {
|
||||
g.EmitConstants(w, consts)
|
||||
}
|
||||
|
||||
err = w.Flush()
|
||||
contract.IgnoreError(err)
|
||||
return w.Buffer()
|
||||
}
|
||||
|
||||
// getFileModule generates a module name from a filename. To do so, we simply find the path part after the root and
|
||||
// remove any file extensions, to get the underlying package's module token.
|
||||
func (g *RPCGenerator) getFileModule(file string) tokens.Module {
|
||||
module, _ := filepath.Rel(g.Out, file)
|
||||
if ext := filepath.Ext(module); ext != "" {
|
||||
extix := strings.LastIndex(module, ext)
|
||||
module = module[:extix]
|
||||
}
|
||||
return tokens.Module(module)
|
||||
}
|
||||
|
||||
func (g *RPCGenerator) EmitResource(w *tools.GenWriter, module tokens.Module, pkg *Package, res *Resource) {
|
||||
name := res.Name()
|
||||
w.Writefmtln("/* RPC stubs for %v resource provider */", name)
|
||||
w.Writefmtln("")
|
||||
|
||||
// Remember when we encounter resources so we can import the right packages.
|
||||
g.FileHadRes = true
|
||||
|
||||
propopts := res.PropertyOptions()
|
||||
var hasinputs bool
|
||||
for _, propopt := range propopts {
|
||||
if !propopt.Out {
|
||||
hasinputs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Emit a type token.
|
||||
token := fmt.Sprintf("%v:%v:%v", pkg.Name, module, name)
|
||||
w.Writefmtln("// %[1]vToken is the type token corresponding to the %[1]v package type.", name)
|
||||
w.Writefmtln(`const %vToken = tokens.Type("%v")`, name, token)
|
||||
w.Writefmtln("")
|
||||
|
||||
// Now, generate an ops interface that the real provider will implement.
|
||||
w.Writefmtln("// %[1]vProviderOps is a pluggable interface for %[1]v-related management functionality.", name)
|
||||
w.Writefmtln("type %vProviderOps interface {", name)
|
||||
w.Writefmtln(" Configure(ctx context.Context, vars map[tokens.ModuleMember]string) error")
|
||||
w.Writefmtln(" Check(ctx context.Context, obj *%v, property string) error", name)
|
||||
w.Writefmtln(" Diff(ctx context.Context, id resource.ID,")
|
||||
w.Writefmtln(" old *%[1]v, new *%[1]v, diff *resource.ObjectDiff) ([]string, error)", name)
|
||||
w.Writefmtln(" Create(ctx context.Context, obj *%v) (resource.ID, error)", name)
|
||||
w.Writefmtln(" Update(ctx context.Context, id resource.ID,")
|
||||
w.Writefmtln(" old *%[1]v, new *%[1]v, diff *resource.ObjectDiff) error", name)
|
||||
w.Writefmtln(" Delete(ctx context.Context, id resource.ID, obj %v) error", name)
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
|
||||
// Next generate all the RPC scaffolding goo
|
||||
w.Writefmtln("// %[1]vProvider is a dynamic gRPC-based plugin for managing %[1]v resources.", name)
|
||||
w.Writefmtln("type %vProvider struct {", name)
|
||||
w.Writefmtln(" ops %vProviderOps", name)
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln("// New%vProvider allocates a resource provider that delegates to a ops instance.", name)
|
||||
w.Writefmtln("func New%[1]vProvider(ops %[1]vProviderOps) lumirpc.ResourceProviderServer {", name)
|
||||
w.Writefmtln(" contract.Assert(ops != nil)")
|
||||
w.Writefmtln(" return &%vProvider{ops: ops}", name)
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln("func (p *%vProvider) Configure(", name)
|
||||
w.Writefmtln(" ctx context.Context, req *lumirpc.ConfigureRequest) (*pbempty.Empty, error) {")
|
||||
w.Writefmtln(" vars := make(map[tokens.ModuleMember]string)")
|
||||
w.Writefmtln(" for k, v := range req.GetVariables() {")
|
||||
w.Writefmtln(" vars[tokens.ModuleMember(k)] = v")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" if err := p.ops.Configure(ctx, vars); err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" return &pbempty.Empty{}, nil")
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln("func (p *%vProvider) Check(", name)
|
||||
w.Writefmtln(" ctx context.Context, req *lumirpc.CheckRequest) (*lumirpc.CheckResponse, error) {")
|
||||
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
|
||||
w.Writefmtln(" obj, _, err := p.Unmarshal(req.GetProperties(), true)")
|
||||
w.Writefmtln(" if obj == nil || err != nil {")
|
||||
w.Writefmtln(" return plugin.NewCheckResponse(err), nil")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" var failures []error")
|
||||
// check global properties:
|
||||
w.Writefmtln(" if failure := p.ops.Check(ctx, obj, \"\"); failure != nil {")
|
||||
w.Writefmtln(" failures = append(failures, failure)")
|
||||
w.Writefmtln(" }")
|
||||
// check each input property:
|
||||
if hasinputs {
|
||||
for _, opts := range propopts {
|
||||
if !opts.Out {
|
||||
w.Writefmtln(" if failure := p.ops.Check(ctx, obj, \"%v\"); failure != nil {", opts.Name)
|
||||
w.Writefmtln(" failures = append(failures,")
|
||||
w.Writefmtln(" resource.NewPropertyError(\"%v\", \"%v\", failure))", name, opts.Name)
|
||||
w.Writefmtln(" }")
|
||||
}
|
||||
}
|
||||
w.Writefmtln(" if len(failures) > 0 {")
|
||||
w.Writefmtln(" return plugin.NewCheckResponse(resource.NewErrors(failures)), nil")
|
||||
w.Writefmtln(" }")
|
||||
}
|
||||
w.Writefmtln(" return plugin.NewCheckResponse(nil), nil")
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln("func (p *%vProvider) Create(", name)
|
||||
w.Writefmtln(" ctx context.Context, req *lumirpc.CreateRequest) (*lumirpc.CreateResponse, error) {")
|
||||
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
|
||||
w.Writefmtln(" obj, _, err := p.Unmarshal(req.GetProperties(), false)")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" id, err := p.ops.Create(ctx, obj)")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" props, err := plugin.MarshalProperties(")
|
||||
w.Writefmtln(" resource.NewPropertyMap(obj), plugin.MarshalOptions{})")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" return &lumirpc.CreateResponse{Id: string(id), Properties: props}, nil")
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln("func (p *%vProvider) Diff(", name)
|
||||
w.Writefmtln(" ctx context.Context, req *lumirpc.DiffRequest) (*lumirpc.DiffResponse, error) {")
|
||||
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
|
||||
w.Writefmtln(" id := resource.ID(req.GetId())")
|
||||
w.Writefmtln(" old, oldprops, err := p.Unmarshal(req.GetOlds(), false)")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" new, newprops, err := p.Unmarshal(req.GetNews(), true)")
|
||||
w.Writefmtln(" if new == nil || err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" var replaces []string")
|
||||
w.Writefmtln(" diff := oldprops.Diff(newprops)")
|
||||
w.Writefmtln(" if diff != nil {")
|
||||
for _, opts := range propopts {
|
||||
if opts.Replaces {
|
||||
w.Writefmtln(" if diff.Changed(\"%v\") {", opts.Name)
|
||||
w.Writefmtln(" replaces = append(replaces, \"%v\")", opts.Name)
|
||||
w.Writefmtln(" }")
|
||||
}
|
||||
}
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" more, err := p.ops.Diff(ctx, id, old, new, diff)")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" return &lumirpc.DiffResponse{")
|
||||
w.Writefmtln(" Replaces: append(replaces, more...),")
|
||||
w.Writefmtln(" }, err")
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln("func (p *%vProvider) Update(", name)
|
||||
w.Writefmtln(" ctx context.Context, req *lumirpc.UpdateRequest) (*lumirpc.UpdateResponse, error) {")
|
||||
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
|
||||
w.Writefmtln(" id := resource.ID(req.GetId())")
|
||||
w.Writefmtln(" old, oldprops, err := p.Unmarshal(req.GetOlds(), false)")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" new, newprops, err := p.Unmarshal(req.GetNews(), false)")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" diff := oldprops.Diff(newprops)")
|
||||
w.Writefmtln(" if err := p.ops.Update(ctx, id, old, new, diff); err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" props, err := plugin.MarshalProperties(")
|
||||
w.Writefmtln(" resource.NewPropertyMap(new), plugin.MarshalOptions{})")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" return &lumirpc.UpdateResponse{Properties: props}, nil")
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln("func (p *%vProvider) Delete(", name)
|
||||
w.Writefmtln(" ctx context.Context, req *lumirpc.DeleteRequest) (*pbempty.Empty, error) {")
|
||||
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
|
||||
w.Writefmtln(" id := resource.ID(req.GetId())")
|
||||
w.Writefmtln(" obj, _, err := p.Unmarshal(req.GetProperties(), false)")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" if err := p.ops.Delete(ctx, id, *obj); err != nil {")
|
||||
w.Writefmtln(" return nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" return &pbempty.Empty{}, nil")
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
w.Writefmtln("func (p *%vProvider) Unmarshal(", name)
|
||||
w.Writefmtln(" v *pbstruct.Struct, allowUnknowns bool) (*%v, resource.PropertyMap, error) {", name)
|
||||
w.Writefmtln(" opts := plugin.MarshalOptions{AllowUnknowns: allowUnknowns}")
|
||||
w.Writefmtln(" props, err := plugin.UnmarshalProperties(v, opts)")
|
||||
w.Writefmtln(" if err != nil {")
|
||||
w.Writefmtln(" return nil, nil, err")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" if allowUnknowns && props.ContainsUnknowns() {")
|
||||
w.Writefmtln(" return nil, props, nil")
|
||||
w.Writefmtln(" }")
|
||||
w.Writefmtln(" var obj %v", name)
|
||||
w.Writefmtln(" return &obj, props, mapper.MapIU(props.Mappable(), &obj)")
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
}
|
||||
|
||||
func (g *RPCGenerator) EmitStructType(w *tools.GenWriter, module tokens.Module, pkg *Package, t TypeMember) {
|
||||
name := t.Name()
|
||||
w.Writefmtln("/* Marshalable %v structure(s) */", name)
|
||||
w.Writefmtln("")
|
||||
|
||||
props := t.Properties()
|
||||
propopts := t.PropertyOptions()
|
||||
w.Writefmtln("// %v is a marshalable representation of its corresponding IDL type.", name)
|
||||
w.Writefmtln("type %v struct {", name)
|
||||
for i, prop := range props {
|
||||
opts := propopts[i]
|
||||
// Make a JSON tag for this so we can serialize; note that outputs are always optional in this position.
|
||||
jsontag := makeLumiTag(opts)
|
||||
w.Writefmtln(" %v %v %v",
|
||||
prop.Name(), g.GenTypeName(prop.Type(), opts.Optional || opts.In || opts.Out), jsontag)
|
||||
}
|
||||
w.Writefmtln("}")
|
||||
w.Writefmtln("")
|
||||
|
||||
if len(props) > 0 {
|
||||
w.Writefmtln("// %v's properties have constants to make dealing with diffs and property bags easier.", name)
|
||||
w.Writefmtln("const (")
|
||||
for i, prop := range props {
|
||||
opts := propopts[i]
|
||||
w.Writefmtln(" %v_%v = \"%v\"", name, prop.Name(), opts.Name)
|
||||
}
|
||||
w.Writefmtln(")")
|
||||
w.Writefmtln("")
|
||||
}
|
||||
}
|
||||
|
||||
// makeLumiTag turns a set of property options into a serializable JSON tag.
|
||||
func makeLumiTag(opts PropertyOptions) string {
|
||||
var flags string
|
||||
if opts.Optional || opts.In || opts.Out {
|
||||
flags = ",optional"
|
||||
}
|
||||
return fmt.Sprintf("`pulumi:\"%v%v\"`", opts.Name, flags)
|
||||
}
|
||||
|
||||
func (g *RPCGenerator) GenTypeName(t types.Type, opt bool) string {
|
||||
switch u := t.(type) {
|
||||
case *types.Basic:
|
||||
switch k := u.Kind(); k {
|
||||
case types.Bool:
|
||||
return "bool"
|
||||
case types.String:
|
||||
return "string"
|
||||
case types.Float64:
|
||||
return "float64"
|
||||
default:
|
||||
contract.Failf("Unrecognized GenTypeName basic type: %v", k)
|
||||
}
|
||||
case *types.Interface:
|
||||
return "interface{}"
|
||||
case *types.Named:
|
||||
obj := u.Obj()
|
||||
// For resource types, simply emit an ID, since that is what will have been serialized.
|
||||
if IsResource(obj, u) {
|
||||
return "resource.ID"
|
||||
}
|
||||
|
||||
// For references to the special predefined types, use the runtime provider representation.
|
||||
if spec, kind := IsSpecial(obj); spec {
|
||||
switch kind {
|
||||
case SpecialArchiveType:
|
||||
return "resource.Archive"
|
||||
case SpecialAssetType:
|
||||
return "resource.Asset"
|
||||
default:
|
||||
contract.Failf("Unexpected special kind: %v", kind)
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, see how to reference the type, based on imports.
|
||||
pkg := obj.Pkg()
|
||||
name := obj.Name()
|
||||
|
||||
// If this came from the same package, Go can access it without qualification.
|
||||
if pkg == g.CurrPkg.Pkginfo.Pkg {
|
||||
return name
|
||||
}
|
||||
|
||||
// Otherwise, we will need to refer to a qualified import name.
|
||||
impname := g.registerImport(pkg)
|
||||
return fmt.Sprintf("%v.%v", impname, name)
|
||||
case *types.Map:
|
||||
return fmt.Sprintf("map[%v]%v", g.GenTypeName(u.Key(), false), g.GenTypeName(u.Elem(), false))
|
||||
case *types.Pointer:
|
||||
// If this isn't an optional property, and the underlying type is a resource or special type, unpointerize it.
|
||||
elem := u.Elem()
|
||||
unptr := false
|
||||
if !opt {
|
||||
if elnm, iselnm := elem.(*types.Named); iselnm {
|
||||
if IsResource(elnm.Obj(), elnm) {
|
||||
unptr = true
|
||||
} else if spec, _ := IsSpecial(elnm.Obj()); spec {
|
||||
unptr = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if unptr {
|
||||
return g.GenTypeName(elem, false)
|
||||
}
|
||||
return fmt.Sprintf("*%v", g.GenTypeName(u.Elem(), false))
|
||||
case *types.Slice:
|
||||
return fmt.Sprintf("[]%v", g.GenTypeName(u.Elem(), false)) // postfix syntax for arrays.
|
||||
default:
|
||||
contract.Failf("Unrecognized GenTypeName type: %v", reflect.TypeOf(u))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// registerImport registers that we have seen a foreign package and requests that the imports be emitted for it.
|
||||
func (g *RPCGenerator) registerImport(pkg *types.Package) string {
|
||||
path := pkg.Path()
|
||||
if impname, has := g.FileImports[path]; has {
|
||||
return impname
|
||||
}
|
||||
|
||||
// If we haven't seen this yet, allocate an import name for it. For now, we just use the package name with two
|
||||
// leading underscores, to avoid accidental collisions with other names in the file.
|
||||
name := "__" + pkg.Name()
|
||||
g.FileImports[path] = name
|
||||
return name
|
||||
}
|
||||
|
||||
func (g *RPCGenerator) EmitTypedefs(w *tools.GenWriter, typedefs []Typedef) {
|
||||
w.Writefmtln("/* Typedefs */")
|
||||
w.Writefmtln("")
|
||||
|
||||
w.Writefmtln("type (")
|
||||
for _, td := range typedefs {
|
||||
w.Writefmtln(" %v %v", td.Name(), td.Target())
|
||||
}
|
||||
w.Writefmtln(")")
|
||||
|
||||
w.Writefmtln("")
|
||||
}
|
||||
|
||||
func (g *RPCGenerator) EmitConstants(w *tools.GenWriter, consts []*Const) {
|
||||
w.Writefmtln("/* Constants */")
|
||||
w.Writefmtln("")
|
||||
|
||||
w.Writefmtln("const (")
|
||||
for _, konst := range consts {
|
||||
w.Writefmtln(" %v %v = %v", konst.Name(), g.GenTypeName(konst.Type, false), konst.Value)
|
||||
}
|
||||
w.Writefmtln(")")
|
||||
|
||||
w.Writefmtln("")
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/diag"
|
||||
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||
)
|
||||
|
||||
// PropertyOptionsTag is the field tag the IDL compiler uses to find property options.
|
||||
const PropertyOptionsTag = "pulumi"
|
||||
|
||||
// PropertyOptions represents a parsed field tag, controlling how properties are treated.
|
||||
type PropertyOptions struct {
|
||||
Name string // the property name to emit into the package.
|
||||
Optional bool // true if this is an optional property.
|
||||
Replaces bool // true if changing this property triggers a replacement of this resource.
|
||||
In bool // true if this is part of the resource's input, but not its output, properties.
|
||||
Out bool // true if the property is part of the resource's output, rather than input, properties.
|
||||
}
|
||||
|
||||
// ParsePropertyOptions parses a tag into a structured set of options.
|
||||
func ParsePropertyOptions(tag string) PropertyOptions {
|
||||
opts := PropertyOptions{}
|
||||
if lumi, has := reflect.StructTag(tag).Lookup(PropertyOptionsTag); has {
|
||||
// The first element is the name; all others are optional flags. All are delimited by commas.
|
||||
if keys := strings.Split(lumi, ","); len(keys) > 0 {
|
||||
opts.Name = keys[0]
|
||||
for _, key := range keys[1:] {
|
||||
switch key {
|
||||
case "optional":
|
||||
opts.Optional = true
|
||||
case "replaces":
|
||||
opts.Replaces = true
|
||||
case "in":
|
||||
opts.In = true
|
||||
case "out":
|
||||
opts.Out = true
|
||||
default:
|
||||
cmdutil.Diag().Errorf(diag.Message("unrecognized tag `pulumi:\"%v\"`"), key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
type Package struct {
|
||||
Name tokens.PackageName // the package name.
|
||||
Program *loader.Program // the fully parsed/analyzed Go program.
|
||||
Pkginfo *loader.PackageInfo // the Go package information.
|
||||
Files map[string]*File // the files inside of this package.
|
||||
MemberFiles map[tokens.Name]*File // a map from member to the file containing it.
|
||||
}
|
||||
|
||||
func NewPackage(name tokens.PackageName, prog *loader.Program, pkginfo *loader.PackageInfo) *Package {
|
||||
return &Package{
|
||||
Name: name,
|
||||
Program: prog,
|
||||
Pkginfo: pkginfo,
|
||||
Files: make(map[string]*File),
|
||||
MemberFiles: make(map[tokens.Name]*File),
|
||||
}
|
||||
}
|
||||
|
||||
func (pkg *Package) AddMember(file *File, nm tokens.Name, m Member) {
|
||||
_, has := file.Members[nm]
|
||||
contract.Assertf(!has, "Unexpected duplicate member %v", nm)
|
||||
file.Members[nm] = m
|
||||
file.MemberNames = append(file.MemberNames, nm)
|
||||
pkg.MemberFiles[nm] = file
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Path string // a relative path to the file.
|
||||
Module tokens.Module // the module token for this file.
|
||||
Node *ast.File // the Go file object.
|
||||
Members map[tokens.Name]Member // a map of all members, membered and internal.
|
||||
MemberNames []tokens.Name // the list of member keys in the order in which they were encountered.
|
||||
}
|
||||
|
||||
func NewFile(path string, mod tokens.Module, node *ast.File) *File {
|
||||
return &File{
|
||||
Path: path,
|
||||
Module: mod,
|
||||
Node: node,
|
||||
Members: make(map[tokens.Name]Member),
|
||||
}
|
||||
}
|
||||
|
||||
type Member interface {
|
||||
Tok() tokens.ModuleMember // the member's token.
|
||||
Name() tokens.Name // the name of the member.
|
||||
Exported() bool // true if this member is membered.
|
||||
Pos() token.Pos // the file defining this member.
|
||||
}
|
||||
|
||||
type member struct {
|
||||
tok tokens.ModuleMember
|
||||
exported bool
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
func (m *member) Tok() tokens.ModuleMember { return m.tok }
|
||||
func (m *member) Name() tokens.Name { return tokens.Name(m.tok.Name()) }
|
||||
func (m *member) Exported() bool { return m.exported }
|
||||
func (m *member) Pos() token.Pos { return m.pos }
|
||||
|
||||
type TypeMember interface {
|
||||
Member
|
||||
Struct() *types.Struct // the raw underlying struct.
|
||||
Properties() []*types.Var // a flattened list of all properties (including embedded ones).
|
||||
PropertyOptions() []PropertyOptions // a flattened list of all property options.
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
member
|
||||
s *types.Struct // the underlying Go struct node.
|
||||
props []*types.Var
|
||||
popts []PropertyOptions
|
||||
}
|
||||
|
||||
func (r *Resource) Struct() *types.Struct { return r.s }
|
||||
func (r *Resource) Properties() []*types.Var { return r.props }
|
||||
func (r *Resource) PropertyOptions() []PropertyOptions { return r.popts }
|
||||
|
||||
type Struct struct {
|
||||
member
|
||||
s *types.Struct
|
||||
props []*types.Var
|
||||
popts []PropertyOptions
|
||||
}
|
||||
|
||||
func (r *Struct) Struct() *types.Struct { return r.s }
|
||||
func (r *Struct) Properties() []*types.Var { return r.props }
|
||||
func (r *Struct) PropertyOptions() []PropertyOptions { return r.popts }
|
||||
|
||||
type Typedef interface {
|
||||
Member
|
||||
Target() types.Type
|
||||
}
|
||||
|
||||
type Alias struct {
|
||||
member
|
||||
target types.Type
|
||||
}
|
||||
|
||||
func (a *Alias) Target() types.Type { return a.target }
|
||||
|
||||
type Enum struct {
|
||||
member
|
||||
Values []string
|
||||
}
|
||||
|
||||
func (a *Enum) Target() types.Type { return types.Typ[types.String] }
|
||||
|
||||
type Const struct {
|
||||
member
|
||||
Type types.Type
|
||||
Value constant.Value
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
// RelFilename gets the target filename for any given position relative to the root.
|
||||
func RelFilename(root string, prog *loader.Program, p goPos) string {
|
||||
pos := p.Pos()
|
||||
source := prog.Fset.Position(pos).Filename // the source filename.`
|
||||
rel, err := filepath.Rel(root, source) // make it relative to the root.
|
||||
contract.AssertNoError(err)
|
||||
return rel
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package lumidl
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/resource/idl"
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
func IsPrimitive(t types.Type) bool {
|
||||
if basic, isbasic := t.(*types.Basic); isbasic {
|
||||
switch basic.Kind() {
|
||||
case types.Bool, types.Float64, types.String:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsEntity checks whether a type is an entity that can be used by-reference (asset, resource, etc).
|
||||
func IsEntity(obj *types.TypeName, t types.Type) bool {
|
||||
if IsResource(obj, t) {
|
||||
return true
|
||||
}
|
||||
spec, _ := IsSpecial(obj)
|
||||
return spec
|
||||
}
|
||||
|
||||
// IsResource returns true if a type is a special IDL resource.
|
||||
func IsResource(obj *types.TypeName, t types.Type) bool {
|
||||
contract.Assert(obj != nil)
|
||||
|
||||
// If this is a resource type itself, then we're done.
|
||||
if IsSpecialResource(obj) {
|
||||
return true
|
||||
}
|
||||
|
||||
// If a named type, fetch the underlying.
|
||||
if n, is := t.(*types.Named); is {
|
||||
t = n.Underlying()
|
||||
}
|
||||
|
||||
if s, is := t.(*types.Struct); is {
|
||||
// Otherwise, it's a resource if it has an embedded resource field.
|
||||
for i := 0; i < s.NumFields(); i++ {
|
||||
fld := s.Field(i)
|
||||
if fld.Anonymous() {
|
||||
if named, ok := fld.Type().(*types.Named); ok {
|
||||
if IsSpecialResource(named.Obj()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SpecialType int
|
||||
|
||||
const (
|
||||
NotSpecialType = iota
|
||||
SpecialResourceType
|
||||
SpecialAssetType
|
||||
SpecialArchiveType
|
||||
)
|
||||
|
||||
var (
|
||||
idlArchiveType = reflect.TypeOf(idl.Archive{})
|
||||
idlAssetType = reflect.TypeOf(idl.Asset{})
|
||||
idlResourceType = reflect.TypeOf(idl.Resource{})
|
||||
)
|
||||
|
||||
// pkgMatch compares two packages. If the first is a vendored version of match, it still returns true.
|
||||
func pkgMatch(pkg string, match string) bool {
|
||||
ix := strings.LastIndex(pkg, match)
|
||||
return ix != -1 && ix+len(match) == len(pkg)
|
||||
}
|
||||
|
||||
func IsSpecial(obj *types.TypeName) (bool, SpecialType) {
|
||||
if obj != nil && pkgMatch(obj.Pkg().Path(), idlResourceType.PkgPath()) {
|
||||
switch obj.Name() {
|
||||
case idlArchiveType.Name():
|
||||
return true, SpecialArchiveType
|
||||
case idlAssetType.Name():
|
||||
return true, SpecialAssetType
|
||||
case idlResourceType.Name():
|
||||
return true, SpecialResourceType
|
||||
}
|
||||
}
|
||||
return false, NotSpecialType
|
||||
}
|
||||
|
||||
func IsSpecialResource(obj *types.TypeName) bool {
|
||||
spec, kind := IsSpecial(obj)
|
||||
return (spec && kind == SpecialResourceType)
|
||||
}
|
Loading…
Reference in a new issue