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:
parent
164d4db30a
commit
3f54c672be
2 changed files with 47 additions and 22 deletions
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue