Fix/alter a few aspects of RPC code-generation

* Use --out-rpc, rather than --out-provider, since rpc/ is a peer to provider/.

* Use strongly typed tokens in more places.

* Append "rpc" to the generated RPC package names to avoid conflicts.

* Change the Check function to return []mapper.FieldError, rather than
  mapper.DecodeError, to make the common "no errors" case easier (and to eliminate
  boilerplate resulting in needing to conditionally construct a mapper.DecodeError).

* Rename the diffs argument to just diff, matching the existing convention.

* Automatically detect changes to "replaces" properties in the PreviewUpdate
  function.  This eliminates tons of boilerplate in the providers and handles the
  90% common case for resource recreation.  It's still possible to override the
  PreviewUpdate logic, of course, in case there is more sophisticated recreation
  logic necessary than just whether a property changed or not.

* Add some comments on some generated types.

* Generate property constants for the names as they will appear in weakly typed
  property bags.  Although the new RPC interfaces are almost entirely strongly
  typed, in the event that diffs must be inspected, this often devolves into using
  maps and so on.  It's much nicer to say `if diff.Changed(SecurityGroup_Description)`
  than `if diff.Changed("description")` (and catches more errors at compile-time).

* Fix resource ID generation logic to properly fetch the Underlying() type on
  named types (this would sometimes miss resources during property analysis, emitting
  for example `*VPC` instead of `*resource.ID`).
This commit is contained in:
joeduffy 2017-04-27 10:36:22 -07:00
parent 164d4db30a
commit 3f54c672be
2 changed files with 47 additions and 22 deletions

View file

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/pulumi/coconut/pkg/tokens"
"github.com/pulumi/coconut/pkg/tools/cidlc"
"github.com/pulumi/coconut/pkg/util/cmdutil"
)
@ -18,7 +19,7 @@ func NewCIDLCCmd() *cobra.Command {
var logToStderr bool
var name string
var outPack string
var outProvider string
var outRPC string
var root string
var verbose int
cmd := &cobra.Command{
@ -39,15 +40,15 @@ func NewCIDLCCmd() *cobra.Command {
if outPack != "" {
outPack, _ = filepath.Abs(outPack)
}
if outProvider != "" {
outProvider, _ = filepath.Abs(outProvider)
if outRPC != "" {
outRPC, _ = filepath.Abs(outRPC)
}
return cidlc.Compile(cidlc.CompileOptions{
Name: name,
Root: root,
OutPack: outPack,
OutProvider: outProvider,
Name: tokens.PackageName(name),
Root: root,
OutPack: outPack,
OutRPC: outRPC,
}, args...)
}),
PersistentPostRun: func(cmd *cobra.Command, args []string) {
@ -62,7 +63,7 @@ func NewCIDLCCmd() *cobra.Command {
cmd.PersistentFlags().StringVar(
&outPack, "out-pack", "", "Save generated package metadata to this directory")
cmd.PersistentFlags().StringVar(
&outProvider, "out-provider", "", "Save generated RPC provider stubs to this directory")
&outRPC, "out-rpc", "", "Save generated RPC provider stubs to this directory")
cmd.PersistentFlags().StringVarP(
&root, "root", "r", "", "Pick a different root directory than `pwd` (the default)")
cmd.PersistentFlags().IntVarP(

View file

@ -69,7 +69,7 @@ func (g *RPCGenerator) EmitFile(file string, pkg *Package, members []Member) err
emitHeaderWarning(w)
// Now emit the package name at the top-level.
writefmtln(w, "package %v", pkg.Name)
writefmtln(w, "package %vrpc", pkg.Name)
writefmtln(w, "")
// And all of the imports that we're going to need.
@ -135,11 +135,12 @@ func (g *RPCGenerator) getFileModule(file string) tokens.Module {
func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg *Package, res *Resource) {
name := res.Name()
writefmtln(w, "/* RPC %v resource provider */", name)
writefmtln(w, "/* RPC stubs for %v resource provider */", name)
writefmtln(w, "")
hasouts := false
for _, opts := range res.PropertyOptions() {
propopts := res.PropertyOptions()
for _, opts := range propopts {
if opts.Out {
hasouts = true
break
@ -155,7 +156,7 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg *
// Now, generate an ops interface that the real provider will implement.
writefmtln(w, "// %[1]vProviderOps is a pluggable interface for %[1]v-related management functionality.", name)
writefmtln(w, "type %vProviderOps interface {", name)
writefmtln(w, " Check(ctx context.Context, obj *%v) (mapper.DecodeError, error)", name)
writefmtln(w, " Check(ctx context.Context, obj *%v) ([]mapper.FieldError, error)", name)
if !res.Named {
writefmtln(w, " Name(ctx context.Context, obj *%v) (string, error)", name)
}
@ -166,9 +167,9 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg *
writefmtln(w, "error)")
writefmtln(w, " Get(ctx context.Context, id string) (*%v, error)", name)
writefmtln(w, " PreviewUpdate(ctx context.Context,")
writefmtln(w, " id string, old *%[1]v, new *%[1]v, diffs *resource.ObjectDiff) ([]string, error)", name)
writefmtln(w, " id string, old *%[1]v, new *%[1]v, diff *resource.ObjectDiff) ([]string, error)", name)
writefmtln(w, " Update(ctx context.Context,")
writefmtln(w, " id string, old *%[1]v, new *%[1]v, diffs *resource.ObjectDiff) error", name)
writefmtln(w, " id string, old *%[1]v, new *%[1]v, diff *resource.ObjectDiff) error", name)
writefmtln(w, " Delete(ctx context.Context, id string) error")
writefmtln(w, "}")
writefmtln(w, "")
@ -190,11 +191,13 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg *
writefmtln(w, " contract.Assert(req.GetType() == string(%vToken))", name)
writefmtln(w, " obj, _, decerr := p.Unmarshal(req.GetProperties())")
writefmtln(w, " if decerr == nil || len(decerr.Failures()) == 0 {")
writefmtln(w, " var err error")
writefmtln(w, " decerr, err = p.ops.Check(ctx, obj)")
writefmtln(w, " failures, err := p.ops.Check(ctx, obj)")
writefmtln(w, " if err != nil {")
writefmtln(w, " return nil, err")
writefmtln(w, " }")
writefmtln(w, " if len(failures) > 0 {")
writefmtln(w, " decerr = mapper.NewDecodeErr(failures)")
writefmtln(w, " }")
writefmtln(w, " }")
writefmtln(w, " return resource.NewCheckResponse(decerr), nil")
writefmtln(w, "}")
@ -270,13 +273,21 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg *
writefmtln(w, " if decerr != nil {")
writefmtln(w, " return nil, decerr")
writefmtln(w, " }")
writefmtln(w, " diffs := oldprops.Diff(newprops)")
writefmtln(w, " replaces, err := p.ops.PreviewUpdate(ctx, req.GetId(), old, new, diffs)")
writefmtln(w, " diff := oldprops.Diff(newprops)")
writefmtln(w, " var replaces []string")
for _, opts := range propopts {
if opts.Replaces {
writefmtln(w, " if diff.Changed(\"%v\") {", opts.Name)
writefmtln(w, " replaces = append(replaces, \"%v\")", opts.Name)
writefmtln(w, " }")
}
}
writefmtln(w, " more, err := p.ops.PreviewUpdate(ctx, req.GetId(), old, new, diff)")
writefmtln(w, " if err != nil {")
writefmtln(w, " return nil, err")
writefmtln(w, " }")
writefmtln(w, " return &cocorpc.PreviewUpdateResponse{")
writefmtln(w, " Replaces: replaces,")
writefmtln(w, " Replaces: append(replaces, more...),")
writefmtln(w, " }, err")
writefmtln(w, "}")
writefmtln(w, "")
@ -291,8 +302,8 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg *
writefmtln(w, " if err != nil {")
writefmtln(w, " return nil, err")
writefmtln(w, " }")
writefmtln(w, " diffs := oldprops.Diff(newprops)")
writefmtln(w, " if err := p.ops.Update(ctx, req.GetId(), old, new, diffs); err != nil {")
writefmtln(w, " diff := oldprops.Diff(newprops)")
writefmtln(w, " if err := p.ops.Update(ctx, req.GetId(), old, new, diff); err != nil {")
writefmtln(w, " return nil, err")
writefmtln(w, " }")
writefmtln(w, " return &pbempty.Empty{}, nil")
@ -325,6 +336,7 @@ func (g *RPCGenerator) EmitStructType(w *bufio.Writer, module tokens.Module, pkg
var outs []int
props := t.Properties()
propopts := t.PropertyOptions()
writefmtln(w, "// %v is a marshalable representation of its corresponding IDL type.", name)
writefmtln(w, "type %v struct {", name)
for i, prop := range props {
opts := propopts[i]
@ -339,6 +351,7 @@ func (g *RPCGenerator) EmitStructType(w *bufio.Writer, module tokens.Module, pkg
writefmtln(w, "")
if len(outs) > 0 {
writefmtln(w, "// %vOuts is a marshalable representation of its IDL type's output properties.", name)
writefmtln(w, "type %vOuts struct {", name)
for _, out := range outs {
prop := props[out]
@ -349,6 +362,17 @@ func (g *RPCGenerator) EmitStructType(w *bufio.Writer, module tokens.Module, pkg
writefmtln(w, "}")
writefmtln(w, "")
}
if len(props) > 0 {
writefmtln(w, "// %v's properties have constants to make dealing with diffs and property bags easier.", name)
writefmtln(w, "const (")
for i, prop := range props {
opts := propopts[i]
writefmtln(w, " %v_%v = \"%v\"", name, prop.Name(), opts.Name)
}
writefmtln(w, ")")
writefmtln(w, "")
}
}
// makeJSONTag turns a set of property options into a serializable JSON tag.
@ -376,7 +400,7 @@ func (g *RPCGenerator) GenTypeName(t types.Type) string {
case *types.Named:
obj := u.Obj()
// For resource types, simply emit an ID, since that is what will have been serialized.
if isres, _ := IsResource(obj, u); isres {
if isres, _ := IsResource(obj, u.Underlying()); isres {
return "resource.ID"
}