diff --git a/cmd/lumi/deploy.go b/cmd/lumi/deploy.go index 40cabddeb..9e16f00bc 100644 --- a/cmd/lumi/deploy.go +++ b/cmd/lumi/deploy.go @@ -16,10 +16,18 @@ package main import ( + "bytes" + "fmt" + "time" + "github.com/spf13/cobra" + "github.com/pulumi/lumi/pkg/compiler/errors" + "github.com/pulumi/lumi/pkg/diag/colors" + "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/tokens" "github.com/pulumi/lumi/pkg/util/cmdutil" + "github.com/pulumi/lumi/pkg/util/contract" ) func newDeployCmd() *cobra.Command { @@ -52,7 +60,7 @@ func newDeployCmd() *cobra.Command { return err } defer info.Close() - apply(cmd, info, applyOptions{ + deploy(cmd, info, deployOptions{ Delete: false, DryRun: dryRun, Analyzers: analyzers, @@ -93,3 +101,139 @@ func newDeployCmd() *cobra.Command { return cmd } + +func deploy(cmd *cobra.Command, info *envCmdInfo, opts deployOptions) { + if result := plan(cmd, info, opts); result != nil { + // Now based on whether a dry run was specified, or not, either print or perform the planned operations. + if opts.DryRun { + // If no output file was requested, or "-", print to stdout; else write to that file. + if opts.Output == "" || opts.Output == "-" { + printPlan(info.Ctx.Diag, result, opts) + } else { + saveEnv(info.Env, result.New, opts.Output, true /*overwrite*/) + } + } else { + // If show unchanged was requested, print them first, along with a header. + var header bytes.Buffer + printPrelude(&header, result, opts) + header.WriteString(fmt.Sprintf("%vDeploying changes:%v\n", colors.SpecUnimportant, colors.Reset)) + fmt.Printf(colors.Colorize(&header)) + + // Print a nice message if the update is an empty one. + empty := checkEmpty(info.Ctx.Diag, result.Plan) + + // Create an object to track progress and perform the actual operations. + start := time.Now() + progress := newProgress(info.Ctx, opts.Summary) + checkpoint, err, _, _ := result.Plan.Apply(progress) + if err != nil { + contract.Assert(!info.Ctx.Diag.Success()) // an error should have been emitted. + } + + var summary bytes.Buffer + if !empty { + // Print out the total number of steps performed (and their kinds), the duration, and any summary info. + printSummary(&summary, progress.Ops, opts.ShowReplaceSteps, false) + summary.WriteString(fmt.Sprintf("%vDeployment duration: %v%v\n", + colors.SpecUnimportant, time.Since(start), colors.Reset)) + } + + if progress.MaybeCorrupt { + summary.WriteString(fmt.Sprintf( + "%vA catastrophic error occurred; resources states may be unknown%v\n", + colors.SpecAttention, colors.Reset)) + } + + // Now save the updated snapshot to the specified output file, if any, or the standard location otherwise. + // Note that if a failure has occurred, the Apply routine above will have returned a safe checkpoint. + env := result.Info.Env + saveEnv(env, checkpoint, opts.Output, true /*overwrite*/) + + fmt.Printf(colors.Colorize(&summary)) + } + } +} + +type deployOptions struct { + Create bool // true if we are creating resources. + Delete bool // true if we are deleting resources. + DryRun bool // true if we should just print the plan without performing it. + Analyzers []string // an optional set of analyzers to run as part of this deployment. + ShowConfig bool // true to show the configuration variables being used. + ShowReplaceSteps bool // true to show the replacement steps in the plan. + ShowUnchanged bool // true to show the resources that aren't updated, in addition to those that are. + Summary bool // true if we should only summarize resources and operations. + DOT bool // true if we should print the DOT file for this plan. + Output string // the place to store the output, if any. +} + +// deployProgress pretty-prints the plan application process as it goes. +type deployProgress struct { + Ctx *resource.Context + Steps int + Ops map[resource.StepOp]int + MaybeCorrupt bool + Summary bool +} + +func newProgress(ctx *resource.Context, summary bool) *deployProgress { + return &deployProgress{ + Ctx: ctx, + Steps: 0, + Ops: make(map[resource.StepOp]int), + Summary: summary, + } +} + +func (prog *deployProgress) Before(step resource.Step) { + // Print the step. + stepop := step.Op() + stepnum := prog.Steps + 1 + + var extra string + if stepop == resource.OpReplaceCreate || stepop == resource.OpReplaceDelete { + extra = " (part of a replacement change)" + } + + var b bytes.Buffer + b.WriteString(fmt.Sprintf("Applying step #%v [%v]%v\n", stepnum, stepop, extra)) + printStep(&b, step, prog.Summary, "") + fmt.Printf(colors.Colorize(&b)) +} + +func (prog *deployProgress) After(step resource.Step, state resource.State, err error) { + if err == nil { + // Increment the counters. + prog.Steps++ + prog.Ops[step.Op()]++ + + // Print out any output properties that got created as a result of this operation. + if step.Op() == resource.OpCreate { + var b bytes.Buffer + printResourceOutputProperties(&b, step, "") + fmt.Printf(colors.Colorize(&b)) + } + } else { + // Issue a true, bonafide error. + prog.Ctx.Diag.Errorf(errors.ErrorPlanApplyFailed, err) + + // Print the state of the resource; we don't issue the error, because the deploy above will do that. + var b bytes.Buffer + stepnum := prog.Steps + 1 + b.WriteString(fmt.Sprintf("Step #%v failed [%v]: ", stepnum, step.Op())) + switch state { + case resource.StateOK: + b.WriteString(colors.SpecNote) + b.WriteString("provider successfully recovered from this failure") + case resource.StateUnknown: + b.WriteString(colors.SpecAttention) + b.WriteString("this failure was catastrophic and the provider cannot guarantee recovery") + prog.MaybeCorrupt = true + default: + contract.Failf("Unrecognized resource state: %v", state) + } + b.WriteString(colors.Reset) + b.WriteString("\n") + fmt.Printf(colors.Colorize(&b)) + } +} diff --git a/cmd/lumi/destroy.go b/cmd/lumi/destroy.go index 1d2307db4..780cecccd 100644 --- a/cmd/lumi/destroy.go +++ b/cmd/lumi/destroy.go @@ -46,7 +46,7 @@ func newDestroyCmd() *cobra.Command { defer info.Close() if dryRun || yes || confirmPrompt("This will permanently destroy all resources in the '%v' environment!", info.Env.Name) { - apply(cmd, info, applyOptions{ + deploy(cmd, info, deployOptions{ Delete: true, DryRun: dryRun, Summary: summary, diff --git a/cmd/lumi/env.go b/cmd/lumi/env.go index 785c72c89..2c52c4718 100644 --- a/cmd/lumi/env.go +++ b/cmd/lumi/env.go @@ -17,29 +17,18 @@ package main import ( "bufio" - "bytes" "fmt" "io/ioutil" "os" "path/filepath" - "sort" - "strconv" - "time" goerr "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/pulumi/lumi/pkg/compiler" "github.com/pulumi/lumi/pkg/compiler/core" "github.com/pulumi/lumi/pkg/compiler/errors" - "github.com/pulumi/lumi/pkg/compiler/symbols" - "github.com/pulumi/lumi/pkg/diag" "github.com/pulumi/lumi/pkg/diag/colors" "github.com/pulumi/lumi/pkg/encoding" - "github.com/pulumi/lumi/pkg/eval/heapstate" - "github.com/pulumi/lumi/pkg/eval/rt" - "github.com/pulumi/lumi/pkg/graph/dotconv" - "github.com/pulumi/lumi/pkg/pack" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/tokens" "github.com/pulumi/lumi/pkg/util/cmdutil" @@ -186,234 +175,6 @@ func removeEnv(env *resource.Env) { fmt.Printf(colors.ColorizeText(msg)) } -func prepareCompiler(cmd *cobra.Command, args []string) (compiler.Compiler, *pack.Package) { - // If there's a --, we need to separate out the command args from the stack args. - flags := cmd.Flags() - dashdash := flags.ArgsLenAtDash() - var packArgs []string - if dashdash != -1 { - packArgs = args[dashdash:] - args = args[0:dashdash] - } - - // Create a compiler options object and map any flags and arguments to settings on it. - opts := core.DefaultOptions() - opts.Args = dashdashArgsToMap(packArgs) - - // In the case of an argument, load that specific package and new up a compiler based on its base path. - // Otherwise, use the default workspace and package logic (which consults the current working directory). - var comp compiler.Compiler - var pkg *pack.Package - if len(args) == 0 { - var err error - comp, err = compiler.Newwd(opts) - if err != nil { - // Create a temporary diagnostics sink so that we can issue an error and bail out. - cmdutil.Sink().Errorf(errors.ErrorCantCreateCompiler, err) - } - } else { - fn := args[0] - if pkg = cmdutil.ReadPackageFromArg(fn); pkg != nil { - var err error - if fn == "-" { - comp, err = compiler.Newwd(opts) - } else { - comp, err = compiler.New(filepath.Dir(fn), opts) - } - if err != nil { - cmdutil.Sink().Errorf(errors.ErrorCantReadPackage, fn, err) - } - } - } - - return comp, pkg -} - -// compile just uses the standard logic to parse arguments, options, and to locate/compile a package. It returns the -// LumiGL graph that is produced, or nil if an error occurred (in which case, we would expect non-0 errors). -func compile(cmd *cobra.Command, args []string, config resource.ConfigMap) *compileResult { - // Prepare the compiler info and, provided it succeeds, perform the compilation. - if comp, pkg := prepareCompiler(cmd, args); comp != nil { - // Create the preexec hook if the config map is non-nil. - var preexec compiler.Preexec - configVars := make(map[tokens.Token]*rt.Object) - if config != nil { - preexec = config.ConfigApplier(configVars) - } - - // Now perform the compilation and extract the heap snapshot. - var heap *heapstate.Heap - var pkgsym *symbols.Package - if pkg == nil { - pkgsym, heap = comp.Compile(preexec) - } else { - pkgsym, heap = comp.CompilePackage(pkg, preexec) - } - - return &compileResult{ - C: comp, - Pkg: pkgsym, - Heap: heap, - ConfigVars: configVars, - } - } - - return nil -} - -type compileResult struct { - C compiler.Compiler - Pkg *symbols.Package - Heap *heapstate.Heap - ConfigVars map[tokens.Token]*rt.Object -} - -// verify creates a compiler, much like compile, but only performs binding and verification on it. If verification -// succeeds, the return value is true; if verification fails, errors will have been output, and the return is false. -func verify(cmd *cobra.Command, args []string) bool { - // Prepare the compiler info and, provided it succeeds, perform the verification. - if comp, pkg := prepareCompiler(cmd, args); comp != nil { - // Now perform the compilation and extract the heap snapshot. - if pkg == nil { - return comp.Verify() - } - return comp.VerifyPackage(pkg) - } - - return false -} - -// plan just uses the standard logic to parse arguments, options, and to create a snapshot and plan. -func plan(cmd *cobra.Command, info *envCmdInfo, opts applyOptions) *planResult { - // If deleting, there is no need to create a new snapshot; otherwise, we will need to compile the package. - var new resource.Snapshot - var result *compileResult - var analyzers []tokens.QName - if !opts.Delete { - // First, compile; if that yields errors or an empty heap, exit early. - if result = compile(cmd, info.Args, info.Env.Config); result == nil || result.Heap == nil { - return nil - } - - // Next, if a DOT output is requested, generate it and quite right now. - // TODO: generate this DOT from the snapshot/diff, not the raw object graph. - if opts.DOT { - // Convert the output to a DOT file. - if err := dotconv.Print(result.Heap.G, os.Stdout); err != nil { - cmdutil.Sink().Errorf(errors.ErrorIO, - goerr.Errorf("failed to write DOT file to output: %v", err)) - } - return nil - } - - // Create a resource snapshot from the compiled/evaluated object graph. - var err error - new, err = resource.NewGraphSnapshot( - info.Ctx, info.Env.Name, result.Pkg.Tok, result.C.Ctx().Opts.Args, result.Heap, info.Old) - if err != nil { - result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err) - return nil - } else if !info.Ctx.Diag.Success() { - return nil - } - - // If there are any analyzers to run, queue them up. - for _, a := range opts.Analyzers { - analyzers = append(analyzers, tokens.QName(a)) // from the command line. - } - if as := result.Pkg.Node.Analyzers; as != nil { - for _, a := range *as { - analyzers = append(analyzers, a) // from the project file. - } - } - } - - // Generate a plan; this API handles all interesting cases (create, update, delete). - plan, err := resource.NewPlan(info.Ctx, info.Old, new, analyzers) - if err != nil { - result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err) - return nil - } - if !info.Ctx.Diag.Success() { - return nil - } - return &planResult{ - compileResult: result, - Info: info, - New: new, - Plan: plan, - } -} - -type planResult struct { - *compileResult - Info *envCmdInfo // plan command information. - Old resource.Snapshot // the existing snapshot (if any). - New resource.Snapshot // the new snapshot for this plan (if any). - Plan resource.Plan // the plan created by this command. -} - -func apply(cmd *cobra.Command, info *envCmdInfo, opts applyOptions) { - if result := plan(cmd, info, opts); result != nil { - // Now based on whether a dry run was specified, or not, either print or perform the planned operations. - if opts.DryRun { - // If no output file was requested, or "-", print to stdout; else write to that file. - if opts.Output == "" || opts.Output == "-" { - printPlan(info.Ctx.Diag, result, opts) - } else { - saveEnv(info.Env, result.New, opts.Output, true /*overwrite*/) - } - } else { - // If show unchanged was requested, print them first, along with a header. - var header bytes.Buffer - printPrelude(&header, result, opts) - header.WriteString(fmt.Sprintf("%vDeploying changes:%v\n", colors.SpecUnimportant, colors.Reset)) - fmt.Printf(colors.Colorize(&header)) - - // Print a nice message if the update is an empty one. - empty := checkEmpty(info.Ctx.Diag, result.Plan) - - // Create an object to track progress and perform the actual operations. - start := time.Now() - progress := newProgress(info.Ctx, opts.Summary) - checkpoint, err, _, _ := result.Plan.Apply(progress) - if err != nil { - contract.Assert(!info.Ctx.Diag.Success()) // an error should have been emitted. - } - - var summary bytes.Buffer - if !empty { - // Print out the total number of steps performed (and their kinds), the duration, and any summary info. - printSummary(&summary, progress.Ops, opts.ShowReplaceSteps, false) - summary.WriteString(fmt.Sprintf("%vDeployment duration: %v%v\n", - colors.SpecUnimportant, time.Since(start), colors.Reset)) - } - - if progress.MaybeCorrupt { - summary.WriteString(fmt.Sprintf( - "%vA catastrophic error occurred; resources states may be unknown%v\n", - colors.SpecAttention, colors.Reset)) - } - - // Now save the updated snapshot to the specified output file, if any, or the standard location otherwise. - // Note that if a failure has occurred, the Apply routine above will have returned a safe checkpoint. - env := result.Info.Env - saveEnv(env, checkpoint, opts.Output, true /*overwrite*/) - - fmt.Printf(colors.Colorize(&summary)) - } - } -} - -func checkEmpty(d diag.Sink, plan resource.Plan) bool { - // If we are doing an empty update, say so. - if plan.Empty() { - d.Infof(diag.Message("no resources need to be updated")) - return true - } - return false -} - // backupEnv makes a backup of an existing file, in preparation for writing a new one. Instead of a copy, it // simply renames the file, which is simpler, more efficient, etc. func backupEnv(file string) { @@ -533,459 +294,3 @@ func saveEnv(env *resource.Env, snap resource.Snapshot, file string, existok boo return true } - -type applyOptions struct { - Create bool // true if we are creating resources. - Delete bool // true if we are deleting resources. - DryRun bool // true if we should just print the plan without performing it. - Analyzers []string // an optional set of analyzers to run as part of this deployment. - ShowConfig bool // true to show the configuration variables being used. - ShowReplaceSteps bool // true to show the replacement steps in the plan. - ShowUnchanged bool // true to show the resources that aren't updated, in addition to those that are. - Summary bool // true if we should only summarize resources and operations. - DOT bool // true if we should print the DOT file for this plan. - Output string // the place to store the output, if any. -} - -// applyProgress pretty-prints the plan application process as it goes. -type applyProgress struct { - Ctx *resource.Context - Steps int - Ops map[resource.StepOp]int - MaybeCorrupt bool - Summary bool -} - -func newProgress(ctx *resource.Context, summary bool) *applyProgress { - return &applyProgress{ - Ctx: ctx, - Steps: 0, - Ops: make(map[resource.StepOp]int), - Summary: summary, - } -} - -func (prog *applyProgress) Before(step resource.Step) { - // Print the step. - stepop := step.Op() - stepnum := prog.Steps + 1 - - var extra string - if stepop == resource.OpReplaceCreate || stepop == resource.OpReplaceDelete { - extra = " (part of a replacement change)" - } - - var b bytes.Buffer - b.WriteString(fmt.Sprintf("Applying step #%v [%v]%v\n", stepnum, stepop, extra)) - printStep(&b, step, prog.Summary, " ") - fmt.Printf(colors.Colorize(&b)) -} - -func (prog *applyProgress) After(step resource.Step, state resource.State, err error) { - if err == nil { - // Increment the counters. - prog.Steps++ - prog.Ops[step.Op()]++ - } else { - // Issue a true, bonafide error. - prog.Ctx.Diag.Errorf(errors.ErrorPlanApplyFailed, err) - - // Print the state of the resource; we don't issue the error, because the apply above will do that. - var b bytes.Buffer - stepnum := prog.Steps + 1 - b.WriteString(fmt.Sprintf("Step #%v failed [%v]: ", stepnum, step.Op())) - switch state { - case resource.StateOK: - b.WriteString(colors.SpecNote) - b.WriteString("provider successfully recovered from this failure") - case resource.StateUnknown: - b.WriteString(colors.SpecAttention) - b.WriteString("this failure was catastrophic and the provider cannot guarantee recovery") - prog.MaybeCorrupt = true - default: - contract.Failf("Unrecognized resource state: %v", state) - } - b.WriteString(colors.Reset) - b.WriteString("\n") - fmt.Printf(colors.Colorize(&b)) - } -} - -func printPlan(d diag.Sink, result *planResult, opts applyOptions) { - // First print config/unchanged/etc. if necessary. - var prelude bytes.Buffer - printPrelude(&prelude, result, opts) - - // Now walk the plan's steps and and pretty-print them out. - prelude.WriteString(fmt.Sprintf("%vPlanned changes:%v\n", colors.SpecUnimportant, colors.Reset)) - fmt.Printf(colors.Colorize(&prelude)) - - // Print a nice message if the update is an empty one. - if empty := checkEmpty(d, result.Plan); !empty { - var summary bytes.Buffer - step := result.Plan.Steps() - counts := make(map[resource.StepOp]int) - for step != nil { - op := step.Op() - // Print this step information (resource and all its properties). - // TODO: it would be nice if, in the output, we showed the dependencies a la `git log --graph`. - if opts.ShowReplaceSteps || (op != resource.OpReplaceCreate && op != resource.OpReplaceDelete) { - printStep(&summary, step, opts.Summary, "") - } - counts[step.Op()]++ - step = step.Next() - } - - // Print a summary of operation counts. - printSummary(&summary, counts, opts.ShowReplaceSteps, true) - fmt.Printf(colors.Colorize(&summary)) - } -} - -func printPrelude(b *bytes.Buffer, result *planResult, opts applyOptions) { - // If there are configuration variables, show them. - if opts.ShowConfig { - printConfig(b, result.compileResult) - } - - // If show-sames was requested, walk the sames and print them. - if opts.ShowUnchanged { - printUnchanged(b, result.Plan, opts.Summary) - } -} - -func printConfig(b *bytes.Buffer, result *compileResult) { - b.WriteString(fmt.Sprintf("%vConfiguration:%v\n", colors.SpecUnimportant, colors.Reset)) - if result != nil && result.ConfigVars != nil { - var toks []string - for tok := range result.ConfigVars { - toks = append(toks, string(tok)) - } - sort.Strings(toks) - for _, tok := range toks { - b.WriteString(fmt.Sprintf("%v%v: %v\n", detailsIndent, tok, result.ConfigVars[tokens.Token(tok)])) - } - } -} - -func printSummary(b *bytes.Buffer, counts map[resource.StepOp]int, showReplaceSteps bool, plan bool) { - total := 0 - for op, c := range counts { - if !showReplaceSteps && (op == resource.OpReplaceCreate || op == resource.OpReplaceDelete) { - continue // skip counting replacement steps unless explicitly requested. - } - total += c - } - - var planned string - if plan { - planned = "planned " - } - var colon string - if total != 0 { - colon = ":" - } - b.WriteString(fmt.Sprintf("%v total %v%v%v\n", total, planned, plural("change", total), colon)) - - var planTo string - var pastTense string - if plan { - planTo = "to " - } else { - pastTense = "d" - } - - for _, op := range resource.StepOps() { - if !showReplaceSteps && (op == resource.OpReplaceCreate || op == resource.OpReplaceDelete) { - // Unless the user requested it, don't show the fine-grained replacement steps; just the logical ones. - continue - } - if c := counts[op]; c > 0 { - b.WriteString(fmt.Sprintf(" %v%v %v %v%v%v%v\n", - op.Prefix(), c, plural("resource", c), planTo, op, pastTense, colors.Reset)) - } - } -} - -func plural(s string, c int) string { - if c != 1 { - s += "s" - } - return s -} - -const detailsIndent = " " // 4 spaces, plus 2 for "+ ", "- ", and " " leaders - -func printUnchanged(b *bytes.Buffer, plan resource.Plan, summary bool) { - b.WriteString(fmt.Sprintf("%vUnchanged resources:%v\n", colors.SpecUnimportant, colors.Reset)) - for _, res := range plan.Unchanged() { - b.WriteString(" ") // simulate the 2 spaces for +, -, etc. - printResourceHeader(b, res, nil, "") - printResourceProperties(b, res, nil, nil, nil, summary, "") - } -} - -func printStep(b *bytes.Buffer, step resource.Step, summary bool, indent string) { - // First print out the operation's prefix. - b.WriteString(step.Op().Prefix()) - - // Next print the resource URN, properties, etc. - printResourceHeader(b, step.Old(), step.New(), indent) - b.WriteString(step.Op().Suffix()) - var replaces []resource.PropertyKey - if step.Old() != nil { - m := step.Old().URN() - replaceMap := step.Plan().Replaces() - replaces = replaceMap[m] - } - printResourceProperties(b, step.Old(), step.New(), step.NewProps(), replaces, summary, indent) - - // Finally make sure to reset the color. - b.WriteString(colors.Reset) -} - -func printResourceHeader(b *bytes.Buffer, old resource.Resource, new resource.Resource, indent string) { - var t tokens.Type - if old == nil { - t = new.Type() - } else { - t = old.Type() - } - - // The primary header is the resource type (since it is easy on the eyes). - b.WriteString(fmt.Sprintf("%s:\n", string(t))) -} - -func printResourceProperties(b *bytes.Buffer, old resource.Resource, new resource.Resource, - computed resource.PropertyMap, replaces []resource.PropertyKey, summary bool, indent string) { - indent += detailsIndent - - // Print out the URN and, if present, the ID, as "pseudo-properties". - var id resource.ID - var URN resource.URN - if old == nil { - id = new.ID() - URN = new.URN() - } else { - id = old.ID() - URN = old.URN() - } - if id != "" { - b.WriteString(fmt.Sprintf("%s[id=%s]\n", indent, string(id))) - } - b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, URN.Name())) - - if !summary { - // Print all of the properties associated with this resource. - if old == nil && new != nil { - printObject(b, new.Properties(), indent) - } else if new == nil && old != nil { - printObject(b, old.Properties(), indent) - } else { - contract.Assert(computed != nil) // use computed properties for diffs. - printOldNewDiffs(b, old.Properties(), computed, replaces, indent) - } - } -} - -func printObject(b *bytes.Buffer, props resource.PropertyMap, indent string) { - // Compute the maximum with of property keys so we can justify everything. - keys := resource.StablePropertyKeys(props) - maxkey := 0 - for _, k := range keys { - if len(k) > maxkey { - maxkey = len(k) - } - } - - // Now print out the values intelligently based on the type. - for _, k := range keys { - if v := props[k]; shouldPrintPropertyValue(v) { - printPropertyTitle(b, k, maxkey, indent) - printPropertyValue(b, v, indent) - } - } -} - -func shouldPrintPropertyValue(v resource.PropertyValue) bool { - return !v.IsNull() // by default, don't print nulls (they just clutter up the output) -} - -func printPropertyTitle(b *bytes.Buffer, k resource.PropertyKey, align int, indent string) { - b.WriteString(fmt.Sprintf("%s%-"+strconv.Itoa(align)+"s: ", indent, k)) -} - -func printPropertyValue(b *bytes.Buffer, v resource.PropertyValue, indent string) { - if v.IsNull() { - b.WriteString("") - } else if v.IsBool() { - b.WriteString(fmt.Sprintf("%t", v.BoolValue())) - } else if v.IsNumber() { - b.WriteString(fmt.Sprintf("%v", v.NumberValue())) - } else if v.IsString() { - b.WriteString(fmt.Sprintf("%q", v.StringValue())) - } else if v.IsResource() { - b.WriteString(fmt.Sprintf("-> *%s", v.ResourceValue())) - } else if v.IsArray() { - b.WriteString(fmt.Sprintf("[\n")) - for i, elem := range v.ArrayValue() { - newIndent := printArrayElemHeader(b, i, indent) - printPropertyValue(b, elem, newIndent) - } - b.WriteString(fmt.Sprintf("%s]", indent)) - } else { - contract.Assert(v.IsObject()) - b.WriteString("{\n") - printObject(b, v.ObjectValue(), indent+" ") - b.WriteString(fmt.Sprintf("%s}", indent)) - } - b.WriteString("\n") -} - -func getArrayElemHeader(b *bytes.Buffer, i int, indent string) (string, string) { - prefix := fmt.Sprintf(" %s[%d]: ", indent, i) - return prefix, fmt.Sprintf("%-"+strconv.Itoa(len(prefix))+"s", "") -} - -func printArrayElemHeader(b *bytes.Buffer, i int, indent string) string { - prefix, newIndent := getArrayElemHeader(b, i, indent) - b.WriteString(prefix) - return newIndent -} - -func printOldNewDiffs(b *bytes.Buffer, olds resource.PropertyMap, news resource.PropertyMap, - replaces []resource.PropertyKey, indent string) { - // Get the full diff structure between the two, and print it (recursively). - if diff := olds.Diff(news); diff != nil { - printObjectDiff(b, *diff, replaces, false, indent) - } else { - printObject(b, news, indent) - } -} - -func printObjectDiff(b *bytes.Buffer, diff resource.ObjectDiff, - replaces []resource.PropertyKey, causedReplace bool, indent string) { - contract.Assert(len(indent) > 2) - - // Compute the maximum with of property keys so we can justify everything. - keys := diff.Keys() - maxkey := 0 - for _, k := range keys { - if len(k) > maxkey { - maxkey = len(k) - } - } - - // If a list of what causes a resource to get replaced exist, create a handy map. - var replaceMap map[resource.PropertyKey]bool - if len(replaces) > 0 { - replaceMap = make(map[resource.PropertyKey]bool) - for _, k := range replaces { - replaceMap[k] = true - } - } - - // To print an object diff, enumerate the keys in stable order, and print each property independently. - for _, k := range keys { - title := func(id string) { printPropertyTitle(b, k, maxkey, id) } - if add, isadd := diff.Adds[k]; isadd { - if shouldPrintPropertyValue(add) { - b.WriteString(colors.SpecAdded) - title(addIndent(indent)) - printPropertyValue(b, add, addIndent(indent)) - b.WriteString(colors.Reset) - } - } else if delete, isdelete := diff.Deletes[k]; isdelete { - if shouldPrintPropertyValue(delete) { - b.WriteString(colors.SpecDeleted) - title(deleteIndent(indent)) - printPropertyValue(b, delete, deleteIndent(indent)) - b.WriteString(colors.Reset) - } - } else if update, isupdate := diff.Updates[k]; isupdate { - if !causedReplace && replaceMap != nil { - causedReplace = replaceMap[k] - } - printPropertyValueDiff(b, title, update, causedReplace, indent) - } else if same := diff.Sames[k]; shouldPrintPropertyValue(same) { - title(indent) - printPropertyValue(b, diff.Sames[k], indent) - } - } -} - -func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.ValueDiff, - causedReplace bool, indent string) { - contract.Assert(len(indent) > 2) - - if diff.Array != nil { - title(indent) - b.WriteString("[\n") - - a := diff.Array - for i := 0; i < a.Len(); i++ { - _, newIndent := getArrayElemHeader(b, i, indent) - title := func(id string) { printArrayElemHeader(b, i, id) } - if add, isadd := a.Adds[i]; isadd { - b.WriteString(resource.OpCreate.Color()) - title(addIndent(indent)) - printPropertyValue(b, add, addIndent(newIndent)) - b.WriteString(colors.Reset) - } else if delete, isdelete := a.Deletes[i]; isdelete { - b.WriteString(resource.OpDelete.Color()) - title(deleteIndent(indent)) - printPropertyValue(b, delete, deleteIndent(newIndent)) - b.WriteString(colors.Reset) - } else if update, isupdate := a.Updates[i]; isupdate { - title(indent) - printPropertyValueDiff(b, func(string) {}, update, causedReplace, newIndent) - } else { - title(indent) - printPropertyValue(b, a.Sames[i], newIndent) - } - } - b.WriteString(fmt.Sprintf("%s]\n", indent)) - } else if diff.Object != nil { - title(indent) - b.WriteString("{\n") - printObjectDiff(b, *diff.Object, nil, causedReplace, indent+" ") - b.WriteString(fmt.Sprintf("%s}\n", indent)) - } else if diff.Old.IsResource() && diff.New.IsResource() && diff.New.ResourceValue().Replacement() { - // If the old and new are both resources, and the new is a replacement, show this in a special way (+-). - b.WriteString(resource.OpReplace.Color()) - title(updateIndent(indent)) - printPropertyValue(b, diff.Old, updateIndent(indent)) - b.WriteString(colors.Reset) - } else { - // If we ended up here, the two values either differ by type, or they have different primitive values. We will - // simply emit a deletion line followed by an addition line. - if shouldPrintPropertyValue(diff.Old) { - var color string - if causedReplace { - color = resource.OpDelete.Color() // this property triggered replacement; color as a delete - } else { - color = resource.OpUpdate.Color() - } - b.WriteString(color) - title(deleteIndent(indent)) - printPropertyValue(b, diff.Old, deleteIndent(indent)) - b.WriteString(colors.Reset) - } - if shouldPrintPropertyValue(diff.New) { - var color string - if causedReplace { - color = resource.OpCreate.Color() // this property triggered replacement; color as a create - } else { - color = resource.OpUpdate.Color() - } - b.WriteString(color) - title(addIndent(indent)) - printPropertyValue(b, diff.New, addIndent(indent)) - b.WriteString(colors.Reset) - } - } -} - -func addIndent(indent string) string { return indent[:len(indent)-2] + "+ " } -func deleteIndent(indent string) string { return indent[:len(indent)-2] + "- " } -func updateIndent(indent string) string { return indent[:len(indent)-2] + "+-" } diff --git a/cmd/lumi/lumi.go b/cmd/lumi/lumi.go index 87dacef76..23df0fdea 100644 --- a/cmd/lumi/lumi.go +++ b/cmd/lumi/lumi.go @@ -23,19 +23,21 @@ import ( ) func NewLumiCmd() *cobra.Command { + var logFlow bool var logToStderr bool var verbose int cmd := &cobra.Command{ Use: "lumi", Short: "Lumi is a framework and toolset for reusable stacks of services", PersistentPreRun: func(cmd *cobra.Command, args []string) { - cmdutil.InitLogging(logToStderr, verbose) + cmdutil.InitLogging(logToStderr, verbose, logFlow) }, PersistentPostRun: func(cmd *cobra.Command, args []string) { glog.Flush() }, } + cmd.PersistentFlags().BoolVar(&logFlow, "logflow", false, "Flow log settings to child processes (like plugins)") cmd.PersistentFlags().BoolVar(&logToStderr, "logtostderr", false, "Log to stderr instead of to files") cmd.PersistentFlags().IntVarP( &verbose, "verbose", "v", 0, "Enable verbose logging (e.g., v=3); anything >3 is very verbose") diff --git a/cmd/lumi/pack_info.go b/cmd/lumi/pack_info.go index 6c9b39236..2e84574db 100644 --- a/cmd/lumi/pack_info.go +++ b/cmd/lumi/pack_info.go @@ -273,6 +273,11 @@ func printClass(tok tokens.Type, class *ast.Class, exportOnly bool, indent strin if class.Interface != nil && *class.Interface { mods = append(mods, "interface") } + if class.Attributes != nil { + for _, att := range *class.Attributes { + mods = append(mods, "@"+att.Decorator.Tok.String()) + } + } fmt.Printf(modString(mods)) if class.Extends != nil { @@ -303,16 +308,16 @@ func printClassMember(tok tokens.ClassMember, member ast.ClassMember, exportOnly if !exportOnly || (acc != nil && *acc == tokens.PublicAccessibility) { switch member.GetKind() { case ast.ClassPropertyKind: - printClassProperty(tok, member.(*ast.ClassProperty), indent) + printClassProperty(tok.Name(), member.(*ast.ClassProperty), indent) case ast.ClassMethodKind: - printClassMethod(tok, member.(*ast.ClassMethod), indent) + printClassMethod(tok.Name(), member.(*ast.ClassMethod), indent) default: contract.Failf("Unexpected ClassMember kind: %v\n", member.GetKind()) } } } -func printClassProperty(tok tokens.ClassMember, prop *ast.ClassProperty, indent string) { +func printClassProperty(name tokens.ClassMemberName, prop *ast.ClassProperty, indent string) { var mods []string if prop.Access != nil { mods = append(mods, string(*prop.Access)) @@ -323,14 +328,31 @@ func printClassProperty(tok tokens.ClassMember, prop *ast.ClassProperty, indent if prop.Readonly != nil && *prop.Readonly { mods = append(mods, "readonly") } - fmt.Printf("%vproperty \"%v\"%v", indent, tok.Name(), modString(mods)) + if prop.Attributes != nil { + for _, att := range *prop.Attributes { + mods = append(mods, "@"+att.Decorator.Tok.String()) + } + } + fmt.Printf("%vproperty \"%v\"%v", indent, name, modString(mods)) if prop.Type != nil { fmt.Printf(": %v", prop.Type.Tok) } - fmt.Printf("\n") + + if prop.Getter != nil || prop.Setter != nil { + fmt.Printf(" {\n") + if prop.Getter != nil { + printClassMethod(tokens.ClassMemberName("get"), prop.Getter, indent+" ") + } + if prop.Setter != nil { + printClassMethod(tokens.ClassMemberName("set"), prop.Setter, indent+" ") + } + fmt.Printf("%v}\n", indent) + } else { + fmt.Printf("\n") + } } -func printClassMethod(tok tokens.ClassMember, meth *ast.ClassMethod, indent string) { +func printClassMethod(name tokens.ClassMemberName, meth *ast.ClassMethod, indent string) { var mods []string if meth.Access != nil { mods = append(mods, string(*meth.Access)) @@ -344,7 +366,12 @@ func printClassMethod(tok tokens.ClassMember, meth *ast.ClassMethod, indent stri if meth.Abstract != nil && *meth.Abstract { mods = append(mods, "abstract") } - fmt.Printf("%vmethod \"%v\"%v: %v\n", indent, tok.Name(), modString(mods), funcSig(meth)) + if meth.Attributes != nil { + for _, att := range *meth.Attributes { + mods = append(mods, "@"+att.Decorator.Tok.String()) + } + } + fmt.Printf("%vmethod \"%v\"%v: %v\n", indent, name, modString(mods), funcSig(meth)) } func printModuleMethod(tok tokens.ModuleMember, meth *ast.ModuleMethod, indent string) { @@ -397,6 +424,15 @@ func funcSig(fun ast.Function) string { sig += ", " } sig += string(param.Name.Ident) + + var mods []string + if param.Attributes != nil { + for _, att := range *param.Attributes { + mods = append(mods, "@"+att.Decorator.Tok.String()) + } + } + sig += modString(mods) + if param.Type != nil { sig += ": " + string(param.Type.Tok) } diff --git a/cmd/lumi/plan.go b/cmd/lumi/plan.go index c6d3d292e..b5fcade5a 100644 --- a/cmd/lumi/plan.go +++ b/cmd/lumi/plan.go @@ -16,10 +16,30 @@ package main import ( + "bytes" + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + + goerr "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/pulumi/lumi/pkg/compiler" + "github.com/pulumi/lumi/pkg/compiler/core" + "github.com/pulumi/lumi/pkg/compiler/errors" + "github.com/pulumi/lumi/pkg/compiler/symbols" + "github.com/pulumi/lumi/pkg/diag" + "github.com/pulumi/lumi/pkg/diag/colors" + "github.com/pulumi/lumi/pkg/eval/heapstate" + "github.com/pulumi/lumi/pkg/eval/rt" + "github.com/pulumi/lumi/pkg/graph/dotconv" + "github.com/pulumi/lumi/pkg/pack" + "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/tokens" "github.com/pulumi/lumi/pkg/util/cmdutil" + "github.com/pulumi/lumi/pkg/util/contract" ) func newPlanCmd() *cobra.Command { @@ -51,7 +71,7 @@ func newPlanCmd() *cobra.Command { return err } defer info.Close() - apply(cmd, info, applyOptions{ + deploy(cmd, info, deployOptions{ Delete: false, DryRun: true, Analyzers: analyzers, @@ -93,3 +113,589 @@ func newPlanCmd() *cobra.Command { return cmd } + +// plan just uses the standard logic to parse arguments, options, and to create a snapshot and plan. +func plan(cmd *cobra.Command, info *envCmdInfo, opts deployOptions) *planResult { + // If deleting, there is no need to create a new snapshot; otherwise, we will need to compile the package. + var new resource.Snapshot + var result *compileResult + var analyzers []tokens.QName + if !opts.Delete { + // First, compile; if that yields errors or an empty heap, exit early. + if result = compile(cmd, info.Args, info.Env.Config); result == nil || result.Heap == nil { + return nil + } + + // Next, if a DOT output is requested, generate it and quite right now. + // TODO: generate this DOT from the snapshot/diff, not the raw object graph. + if opts.DOT { + // Convert the output to a DOT file. + if err := dotconv.Print(result.Heap.G, os.Stdout); err != nil { + cmdutil.Sink().Errorf(errors.ErrorIO, + goerr.Errorf("failed to write DOT file to output: %v", err)) + } + return nil + } + + // Create a resource snapshot from the compiled/evaluated object graph. + var err error + new, err = resource.NewGraphSnapshot( + info.Ctx, info.Env.Name, result.Pkg.Tok, result.C.Ctx().Opts.Args, result.Heap, info.Old) + if err != nil { + result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err) + return nil + } else if !info.Ctx.Diag.Success() { + return nil + } + + // If there are any analyzers to run, queue them up. + for _, a := range opts.Analyzers { + analyzers = append(analyzers, tokens.QName(a)) // from the command line. + } + if as := result.Pkg.Node.Analyzers; as != nil { + for _, a := range *as { + analyzers = append(analyzers, a) // from the project file. + } + } + } + + // Generate a plan; this API handles all interesting cases (create, update, delete). + plan, err := resource.NewPlan(info.Ctx, info.Old, new, analyzers) + if err != nil { + result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err) + return nil + } + if !info.Ctx.Diag.Success() { + return nil + } + return &planResult{ + compileResult: result, + Info: info, + New: new, + Plan: plan, + } +} + +type planResult struct { + *compileResult + Info *envCmdInfo // plan command information. + Old resource.Snapshot // the existing snapshot (if any). + New resource.Snapshot // the new snapshot for this plan (if any). + Plan resource.Plan // the plan created by this command. +} + +func checkEmpty(d diag.Sink, plan resource.Plan) bool { + // If we are doing an empty update, say so. + if plan.Empty() { + d.Infof(diag.Message("no resources need to be updated")) + return true + } + return false +} + +func prepareCompiler(cmd *cobra.Command, args []string) (compiler.Compiler, *pack.Package) { + // If there's a --, we need to separate out the command args from the stack args. + flags := cmd.Flags() + dashdash := flags.ArgsLenAtDash() + var packArgs []string + if dashdash != -1 { + packArgs = args[dashdash:] + args = args[0:dashdash] + } + + // Create a compiler options object and map any flags and arguments to settings on it. + opts := core.DefaultOptions() + opts.Args = dashdashArgsToMap(packArgs) + + // In the case of an argument, load that specific package and new up a compiler based on its base path. + // Otherwise, use the default workspace and package logic (which consults the current working directory). + var comp compiler.Compiler + var pkg *pack.Package + if len(args) == 0 { + var err error + comp, err = compiler.Newwd(opts) + if err != nil { + // Create a temporary diagnostics sink so that we can issue an error and bail out. + cmdutil.Sink().Errorf(errors.ErrorCantCreateCompiler, err) + } + } else { + fn := args[0] + if pkg = cmdutil.ReadPackageFromArg(fn); pkg != nil { + var err error + if fn == "-" { + comp, err = compiler.Newwd(opts) + } else { + comp, err = compiler.New(filepath.Dir(fn), opts) + } + if err != nil { + cmdutil.Sink().Errorf(errors.ErrorCantReadPackage, fn, err) + } + } + } + + return comp, pkg +} + +// compile just uses the standard logic to parse arguments, options, and to locate/compile a package. It returns the +// LumiGL graph that is produced, or nil if an error occurred (in which case, we would expect non-0 errors). +func compile(cmd *cobra.Command, args []string, config resource.ConfigMap) *compileResult { + // Prepare the compiler info and, provided it succeeds, perform the compilation. + if comp, pkg := prepareCompiler(cmd, args); comp != nil { + // Create the preexec hook if the config map is non-nil. + var preexec compiler.Preexec + configVars := make(map[tokens.Token]*rt.Object) + if config != nil { + preexec = config.ConfigApplier(configVars) + } + + // Now perform the compilation and extract the heap snapshot. + var heap *heapstate.Heap + var pkgsym *symbols.Package + if pkg == nil { + pkgsym, heap = comp.Compile(preexec) + } else { + pkgsym, heap = comp.CompilePackage(pkg, preexec) + } + + return &compileResult{ + C: comp, + Pkg: pkgsym, + Heap: heap, + ConfigVars: configVars, + } + } + + return nil +} + +type compileResult struct { + C compiler.Compiler + Pkg *symbols.Package + Heap *heapstate.Heap + ConfigVars map[tokens.Token]*rt.Object +} + +// verify creates a compiler, much like compile, but only performs binding and verification on it. If verification +// succeeds, the return value is true; if verification fails, errors will have been output, and the return is false. +func verify(cmd *cobra.Command, args []string) bool { + // Prepare the compiler info and, provided it succeeds, perform the verification. + if comp, pkg := prepareCompiler(cmd, args); comp != nil { + // Now perform the compilation and extract the heap snapshot. + if pkg == nil { + return comp.Verify() + } + return comp.VerifyPackage(pkg) + } + + return false +} + +func printPlan(d diag.Sink, result *planResult, opts deployOptions) { + // First print config/unchanged/etc. if necessary. + var prelude bytes.Buffer + printPrelude(&prelude, result, opts) + + // Now walk the plan's steps and and pretty-print them out. + prelude.WriteString(fmt.Sprintf("%vPlanned changes:%v\n", colors.SpecUnimportant, colors.Reset)) + fmt.Printf(colors.Colorize(&prelude)) + + // Print a nice message if the update is an empty one. + if empty := checkEmpty(d, result.Plan); !empty { + var summary bytes.Buffer + step := result.Plan.Steps() + counts := make(map[resource.StepOp]int) + for step != nil { + op := step.Op() + // Print this step information (resource and all its properties). + // TODO: it would be nice if, in the output, we showed the dependencies a la `git log --graph`. + if opts.ShowReplaceSteps || (op != resource.OpReplaceCreate && op != resource.OpReplaceDelete) { + printStep(&summary, step, opts.Summary, "") + } + counts[step.Op()]++ + step = step.Next() + } + + // Print a summary of operation counts. + printSummary(&summary, counts, opts.ShowReplaceSteps, true) + fmt.Printf(colors.Colorize(&summary)) + } +} + +func printPrelude(b *bytes.Buffer, result *planResult, opts deployOptions) { + // If there are configuration variables, show them. + if opts.ShowConfig { + printConfig(b, result.compileResult) + } + + // If show-sames was requested, walk the sames and print them. + if opts.ShowUnchanged { + printUnchanged(b, result.Plan, opts.Summary) + } +} + +func printConfig(b *bytes.Buffer, result *compileResult) { + b.WriteString(fmt.Sprintf("%vConfiguration:%v\n", colors.SpecUnimportant, colors.Reset)) + if result != nil && result.ConfigVars != nil { + var toks []string + for tok := range result.ConfigVars { + toks = append(toks, string(tok)) + } + sort.Strings(toks) + for _, tok := range toks { + b.WriteString(fmt.Sprintf("%v%v: %v\n", detailsIndent, tok, result.ConfigVars[tokens.Token(tok)])) + } + } +} + +func printSummary(b *bytes.Buffer, counts map[resource.StepOp]int, showReplaceSteps bool, plan bool) { + total := 0 + for op, c := range counts { + if !showReplaceSteps && (op == resource.OpReplaceCreate || op == resource.OpReplaceDelete) { + continue // skip counting replacement steps unless explicitly requested. + } + total += c + } + + var planned string + if plan { + planned = "planned " + } + var colon string + if total != 0 { + colon = ":" + } + b.WriteString(fmt.Sprintf("%v total %v%v%v\n", total, planned, plural("change", total), colon)) + + var planTo string + var pastTense string + if plan { + planTo = "to " + } else { + pastTense = "d" + } + + for _, op := range resource.StepOps() { + if !showReplaceSteps && (op == resource.OpReplaceCreate || op == resource.OpReplaceDelete) { + // Unless the user requested it, don't show the fine-grained replacement steps; just the logical ones. + continue + } + if c := counts[op]; c > 0 { + b.WriteString(fmt.Sprintf(" %v%v %v %v%v%v%v\n", + op.Prefix(), c, plural("resource", c), planTo, op, pastTense, colors.Reset)) + } + } +} + +func plural(s string, c int) string { + if c != 1 { + s += "s" + } + return s +} + +const detailsIndent = " " // 4 spaces, plus 2 for "+ ", "- ", and " " leaders + +func printUnchanged(b *bytes.Buffer, plan resource.Plan, summary bool) { + b.WriteString(fmt.Sprintf("%vUnchanged resources:%v\n", colors.SpecUnimportant, colors.Reset)) + for _, res := range plan.Unchanged() { + b.WriteString(" ") // simulate the 2 spaces for +, -, etc. + printResourceHeader(b, res, nil, "") + printResourceProperties(b, res, nil, nil, nil, summary, "") + } +} + +func printStep(b *bytes.Buffer, step resource.Step, summary bool, indent string) { + // First print out the operation's prefix. + b.WriteString(step.Op().Prefix()) + + // Next print the resource URN, properties, etc. + printResourceHeader(b, step.Old(), step.New(), indent) + b.WriteString(step.Op().Suffix()) + + var replaces []resource.PropertyKey + if step.Old() != nil { + m := step.Old().URN() + replaceMap := step.Plan().Replaces() + replaces = replaceMap[m] + } + printResourceProperties(b, step.Old(), step.New(), step.NewProps(), replaces, summary, indent) + + // Finally make sure to reset the color. + b.WriteString(colors.Reset) +} + +func printResourceHeader(b *bytes.Buffer, old resource.Resource, new resource.Resource, indent string) { + var t tokens.Type + if old == nil { + t = new.Type() + } else { + t = old.Type() + } + + // The primary header is the resource type (since it is easy on the eyes). + b.WriteString(fmt.Sprintf("%s:\n", string(t))) +} + +func printResourceProperties(b *bytes.Buffer, old resource.Resource, new resource.Resource, + computed resource.PropertyMap, replaces []resource.PropertyKey, summary bool, indent string) { + indent += detailsIndent + + // Print out the URN and, if present, the ID, as "pseudo-properties". + var id resource.ID + var URN resource.URN + if old == nil { + id = new.ID() + URN = new.URN() + } else { + id = old.ID() + URN = old.URN() + } + if id != "" { + b.WriteString(fmt.Sprintf("%s[id=%s]\n", indent, string(id))) + } + b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, URN.Name())) + + if !summary { + // Print all of the properties associated with this resource. + if old == nil && new != nil { + printObject(b, new.Properties(), indent) + } else if new == nil && old != nil { + printObject(b, old.Properties(), indent) + } else { + contract.Assert(computed != nil) // use computed properties for diffs. + printOldNewDiffs(b, old.Properties(), computed, replaces, indent) + } + } +} + +func maxKey(keys []resource.PropertyKey) int { + maxkey := 0 + for _, k := range keys { + if len(k) > maxkey { + maxkey = len(k) + } + } + return maxkey +} + +func printObject(b *bytes.Buffer, props resource.PropertyMap, indent string) { + // Compute the maximum with of property keys so we can justify everything. + keys := resource.StablePropertyKeys(props) + maxkey := maxKey(keys) + + // Now print out the values intelligently based on the type. + for _, k := range keys { + if v := props[k]; shouldPrintPropertyValue(v, false) { + printPropertyTitle(b, k, maxkey, indent) + printPropertyValue(b, v, indent) + } + } +} + +func printResourceOutputProperties(b *bytes.Buffer, step resource.Step, indent string) { + indent += detailsIndent + b.WriteString(step.Op().Color()) + b.WriteString(step.Op().Suffix()) + + olds := step.Old().Properties() + news := step.New().Properties() + keys := resource.StablePropertyKeys(olds) + maxkey := maxKey(keys) + for _, k := range keys { + v := news[k] + if olds.NeedsValue(k) && shouldPrintPropertyValue(v, true) { + printPropertyTitle(b, k, maxkey, indent) + printPropertyValue(b, v, indent) + } + } + + b.WriteString(colors.Reset) +} + +func shouldPrintPropertyValue(v resource.PropertyValue, outs bool) bool { + if v.IsNull() { + // by default, don't print nulls (they just clutter up the output) + return false + } + if v.IsOutput() && !outs { + // also don't show output properties until the outs parameter tells us to. + return false + } + return true +} + +func printPropertyTitle(b *bytes.Buffer, k resource.PropertyKey, align int, indent string) { + b.WriteString(fmt.Sprintf("%s%-"+strconv.Itoa(align)+"s: ", indent, k)) +} + +func printPropertyValue(b *bytes.Buffer, v resource.PropertyValue, indent string) { + if v.IsNull() { + b.WriteString("") + } else if v.IsBool() { + b.WriteString(fmt.Sprintf("%t", v.BoolValue())) + } else if v.IsNumber() { + b.WriteString(fmt.Sprintf("%v", v.NumberValue())) + } else if v.IsString() { + b.WriteString(fmt.Sprintf("%q", v.StringValue())) + } else if v.IsResource() { + b.WriteString(fmt.Sprintf("&%s", v.ResourceValue())) + } else if v.IsArray() { + b.WriteString(fmt.Sprintf("[\n")) + for i, elem := range v.ArrayValue() { + newIndent := printArrayElemHeader(b, i, indent) + printPropertyValue(b, elem, newIndent) + } + b.WriteString(fmt.Sprintf("%s]", indent)) + } else if v.IsComputed() || v.IsOutput() { + b.WriteString(v.TypeString()) + } else { + contract.Assert(v.IsObject()) + b.WriteString("{\n") + printObject(b, v.ObjectValue(), indent+" ") + b.WriteString(fmt.Sprintf("%s}", indent)) + } + b.WriteString("\n") +} + +func getArrayElemHeader(b *bytes.Buffer, i int, indent string) (string, string) { + prefix := fmt.Sprintf(" %s[%d]: ", indent, i) + return prefix, fmt.Sprintf("%-"+strconv.Itoa(len(prefix))+"s", "") +} + +func printArrayElemHeader(b *bytes.Buffer, i int, indent string) string { + prefix, newIndent := getArrayElemHeader(b, i, indent) + b.WriteString(prefix) + return newIndent +} + +func printOldNewDiffs(b *bytes.Buffer, olds resource.PropertyMap, news resource.PropertyMap, + replaces []resource.PropertyKey, indent string) { + // Get the full diff structure between the two, and print it (recursively). + if diff := olds.Diff(news); diff != nil { + printObjectDiff(b, *diff, replaces, false, indent) + } else { + printObject(b, news, indent) + } +} + +func printObjectDiff(b *bytes.Buffer, diff resource.ObjectDiff, + replaces []resource.PropertyKey, causedReplace bool, indent string) { + contract.Assert(len(indent) > 2) + + // Compute the maximum with of property keys so we can justify everything. + keys := diff.Keys() + maxkey := maxKey(keys) + + // If a list of what causes a resource to get replaced exist, create a handy map. + var replaceMap map[resource.PropertyKey]bool + if len(replaces) > 0 { + replaceMap = make(map[resource.PropertyKey]bool) + for _, k := range replaces { + replaceMap[k] = true + } + } + + // To print an object diff, enumerate the keys in stable order, and print each property independently. + for _, k := range keys { + title := func(id string) { printPropertyTitle(b, k, maxkey, id) } + if add, isadd := diff.Adds[k]; isadd { + if shouldPrintPropertyValue(add, false) { + b.WriteString(colors.SpecAdded) + title(addIndent(indent)) + printPropertyValue(b, add, addIndent(indent)) + b.WriteString(colors.Reset) + } + } else if delete, isdelete := diff.Deletes[k]; isdelete { + if shouldPrintPropertyValue(delete, false) { + b.WriteString(colors.SpecDeleted) + title(deleteIndent(indent)) + printPropertyValue(b, delete, deleteIndent(indent)) + b.WriteString(colors.Reset) + } + } else if update, isupdate := diff.Updates[k]; isupdate { + if !causedReplace && replaceMap != nil { + causedReplace = replaceMap[k] + } + printPropertyValueDiff(b, title, update, causedReplace, indent) + } else if same := diff.Sames[k]; shouldPrintPropertyValue(same, false) { + title(indent) + printPropertyValue(b, diff.Sames[k], indent) + } + } +} + +func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.ValueDiff, + causedReplace bool, indent string) { + contract.Assert(len(indent) > 2) + + if diff.Array != nil { + title(indent) + b.WriteString("[\n") + + a := diff.Array + for i := 0; i < a.Len(); i++ { + _, newIndent := getArrayElemHeader(b, i, indent) + title := func(id string) { printArrayElemHeader(b, i, id) } + if add, isadd := a.Adds[i]; isadd { + b.WriteString(resource.OpCreate.Color()) + title(addIndent(indent)) + printPropertyValue(b, add, addIndent(newIndent)) + b.WriteString(colors.Reset) + } else if delete, isdelete := a.Deletes[i]; isdelete { + b.WriteString(resource.OpDelete.Color()) + title(deleteIndent(indent)) + printPropertyValue(b, delete, deleteIndent(newIndent)) + b.WriteString(colors.Reset) + } else if update, isupdate := a.Updates[i]; isupdate { + title(indent) + printPropertyValueDiff(b, func(string) {}, update, causedReplace, newIndent) + } else { + title(indent) + printPropertyValue(b, a.Sames[i], newIndent) + } + } + b.WriteString(fmt.Sprintf("%s]\n", indent)) + } else if diff.Object != nil { + title(indent) + b.WriteString("{\n") + printObjectDiff(b, *diff.Object, nil, causedReplace, indent+" ") + b.WriteString(fmt.Sprintf("%s}\n", indent)) + } else if diff.Old.IsResource() && diff.New.IsResource() && diff.New.ResourceValue().Replacement() { + // If the old and new are both resources, and the new is a replacement, show this in a special way (+-). + b.WriteString(resource.OpReplace.Color()) + title(updateIndent(indent)) + printPropertyValue(b, diff.Old, updateIndent(indent)) + b.WriteString(colors.Reset) + } else { + // If we ended up here, the two values either differ by type, or they have different primitive values. We will + // simply emit a deletion line followed by an addition line. + if shouldPrintPropertyValue(diff.Old, false) { + var color string + if causedReplace { + color = resource.OpDelete.Color() // this property triggered replacement; color as a delete + } else { + color = resource.OpUpdate.Color() + } + b.WriteString(color) + title(deleteIndent(indent)) + printPropertyValue(b, diff.Old, deleteIndent(indent)) + b.WriteString(colors.Reset) + } + if shouldPrintPropertyValue(diff.New, false) { + var color string + if causedReplace { + color = resource.OpCreate.Color() // this property triggered replacement; color as a create + } else { + color = resource.OpUpdate.Color() + } + b.WriteString(color) + title(addIndent(indent)) + printPropertyValue(b, diff.New, addIndent(indent)) + b.WriteString(colors.Reset) + } + } +} + +func addIndent(indent string) string { return indent[:len(indent)-2] + "+ " } +func deleteIndent(indent string) string { return indent[:len(indent)-2] + "- " } +func updateIndent(indent string) string { return indent[:len(indent)-2] + "+-" } diff --git a/cmd/lumidl/lumidl.go b/cmd/lumidl/lumidl.go index 50f763d2b..9d38c612a 100644 --- a/cmd/lumidl/lumidl.go +++ b/cmd/lumidl/lumidl.go @@ -47,7 +47,7 @@ func NewIDLCCmd() *cobra.Command { "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) + cmdutil.InitLogging(logToStderr, verbose, true) }, Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { if len(args) == 0 { diff --git a/cmd/lumijs/lib/compiler/transform.ts b/cmd/lumijs/lib/compiler/transform.ts index a55494134..b8a3f80fe 100644 --- a/cmd/lumijs/lib/compiler/transform.ts +++ b/cmd/lumijs/lib/compiler/transform.ts @@ -1648,7 +1648,6 @@ export class Transformer { private async transformClassDeclaration( modtok: tokens.ModuleToken, node: ts.ClassDeclaration): Promise { // TODO(joe): generics. - // TODO(joe): decorators. // First transform the name into an identifier. In the absence of a name, we will proceed under the assumption // that it is the default export. This should be verified later on. @@ -1664,6 +1663,9 @@ export class Transformer { log.out(7).info(`Transforming class declaration: ${name.ident}`); } + // Pluck out any decorators and store them in the metadata as attributes. + let attributes: ast.Attribute[] | undefined = await this.transformDecorators(node.decorators); + // Next, make a class token to use during this class's transformations. let classtok: tokens.ModuleMemberToken = this.createModuleMemberToken(modtok, name.ident); let priorClassToken: tokens.TypeToken | undefined = this.currentClassToken; @@ -1846,6 +1848,7 @@ export class Transformer { return this.withLocation(node, { kind: ast.classKind, name: name, + attributes: attributes, members: members, abstract: !!(mods & ts.ModifierFlags.Abstract), extends: extend, @@ -1998,7 +2001,6 @@ export class Transformer { private async transformInterfaceDeclaration( modtok: tokens.ModuleToken, node: ts.InterfaceDeclaration): Promise { // TODO(joe): generics. - // TODO(joe): decorators. // TODO(joe): extends/implements. // Create a name and token for the LumiIL class representing this. @@ -2008,6 +2010,9 @@ export class Transformer { log.out(7).info(`Transforming interface declaration: ${name.ident}`); } + // Pluck out any decorators and store them in the metadata as attributes. + let attributes: ast.Attribute[] | undefined = await this.transformDecorators(node.decorators); + // Next, make a class token to use during this class's transformations. let classtok: tokens.ModuleMemberToken = this.createModuleMemberToken(modtok, name.ident); let priorClassToken: tokens.TypeToken | undefined = this.currentClassToken; @@ -2045,6 +2050,7 @@ export class Transformer { return this.withLocation(node, { kind: ast.classKind, name: name, + attributes: attributes, members: members, interface: true, // permit multi-inheritance. record: true, // enable on-the-fly creation. @@ -2063,23 +2069,14 @@ export class Transformer { } private getDecoratorSymbol(decorator: ts.Decorator): ts.Symbol { - contract.assert(decorator.expression.kind === ts.SyntaxKind.Identifier, - "Only simple @decorator annotations are currently supported"); return this.checker().getSymbolAtLocation(decorator.expression); } - private async transformParameterDeclaration( - node: ts.ParameterDeclaration): Promise> { - // Validate that we're dealing with the supported subset. - if (!!node.dotDotDotToken) { - this.diagnostics.push(this.dctx.newRestParamsNotSupportedError(node.dotDotDotToken)); - } - - // Pluck out any decorators and store them in the metadata as attributes. + private async transformDecorators(decorators?: ts.NodeArray): Promise { let attributes: ast.Attribute[] | undefined; - if (node.decorators) { + if (decorators) { attributes = []; - for (let decorator of node.decorators) { + for (let decorator of decorators) { let sym: ts.Symbol = this.getDecoratorSymbol(decorator); attributes.push({ kind: ast.attributeKind, @@ -2090,6 +2087,18 @@ export class Transformer { }); } } + return attributes; + } + + private async transformParameterDeclaration( + node: ts.ParameterDeclaration): Promise> { + // Validate that we're dealing with the supported subset. + if (!!node.dotDotDotToken) { + this.diagnostics.push(this.dctx.newRestParamsNotSupportedError(node.dotDotDotToken)); + } + + // Pluck out any decorators and store them in the metadata as attributes. + let attributes: ast.Attribute[] | undefined = await this.transformDecorators(node.decorators); // TODO[pulumi/lumi#43]: parameters can be any binding name, including destructuring patterns. For now, // however, we only support the identifier forms. @@ -2312,9 +2321,11 @@ export class Transformer { private async transformFunctionLikeDeclaration(node: ts.FunctionLikeDeclaration): Promise { let mods: ts.ModifierFlags = ts.getCombinedModifierFlags(node); let decl: FunctionLikeDeclaration = await this.transformFunctionLikeCommon(node); + let attributes: ast.Attribute[] | undefined = await this.transformDecorators(node.decorators); return this.withLocation(node, { kind: ast.classMethodKind, name: decl.name, + attributes: attributes, access: this.getClassAccessibility(node), parameters: decl.parameters, body: decl.body, @@ -2333,18 +2344,20 @@ export class Transformer { } let mods: ts.ModifierFlags = ts.getCombinedModifierFlags(node); let name: ast.Identifier = this.transformPropertyName(node.name); + let attributes: ast.Attribute[] | undefined = await this.transformDecorators(node.decorators); // TODO: primary properties. return new VariableDeclaration( node, this.createClassMemberToken(classtok, name.ident), { - kind: ast.classPropertyKind, - name: name, - access: this.getClassAccessibility(node), - readonly: !!(mods & ts.ModifierFlags.Readonly), - optional: !!(node.questionToken), - static: !!(mods & ts.ModifierFlags.Static), - type: await this.resolveTypeTokenFromTypeLike(node), + kind: ast.classPropertyKind, + name: name, + attributes: attributes, + access: this.getClassAccessibility(node), + readonly: !!(mods & ts.ModifierFlags.Readonly), + optional: !!(node.questionToken), + static: !!(mods & ts.ModifierFlags.Static), + type: await this.resolveTypeTokenFromTypeLike(node), }, false, initializer, @@ -2353,9 +2366,11 @@ export class Transformer { private async transformMethodSignature(node: ts.MethodSignature): Promise { let decl: FunctionLikeDeclaration = await this.transformFunctionLikeOrSignatureCommon(node, false); + let attributes: ast.Attribute[] | undefined = await this.transformDecorators(node.decorators); return this.withLocation(node, { kind: ast.classMethodKind, name: decl.name, + attributes: attributes, access: this.getClassAccessibility(node), parameters: decl.parameters, returnType: decl.returnType, diff --git a/cmd/lumijs/tests/output/basic/decors/Lumi.json b/cmd/lumijs/tests/output/basic/decors/Lumi.json new file mode 100644 index 000000000..38c593f0e --- /dev/null +++ b/cmd/lumijs/tests/output/basic/decors/Lumi.json @@ -0,0 +1,4 @@ +{ + "name": "basic/decorators" +} + diff --git a/cmd/lumijs/tests/output/basic/decors/Lumipack.json b/cmd/lumijs/tests/output/basic/decors/Lumipack.json new file mode 100644 index 000000000..3e85e0282 --- /dev/null +++ b/cmd/lumijs/tests/output/basic/decors/Lumipack.json @@ -0,0 +1,3512 @@ +{ + "name": "basic/decorators", + "modules": { + "decors": { + "kind": "Module", + "name": { + "kind": "Identifier", + "ident": "decors" + }, + "exports": { + "classDecorate": { + "kind": "Export", + "name": { + "kind": "Identifier", + "ident": "classDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 17 + }, + "end": { + "line": 2, + "column": 30 + } + } + }, + "referent": { + "kind": "Token", + "tok": "basic/decorators:decors:classDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 17 + }, + "end": { + "line": 2, + "column": 30 + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 49 + } + } + }, + "propertyDecorate": { + "kind": "Export", + "name": { + "kind": "Identifier", + "ident": "propertyDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 33 + } + } + }, + "referent": { + "kind": "Token", + "tok": "basic/decorators:decors:propertyDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 33 + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 73 + } + } + }, + "methodDecorate": { + "kind": "Export", + "name": { + "kind": "Identifier", + "ident": "methodDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 17 + }, + "end": { + "line": 4, + "column": 31 + } + } + }, + "referent": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 17 + }, + "end": { + "line": 4, + "column": 31 + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 85 + } + } + }, + "parameterDecorate": { + "kind": "Export", + "name": { + "kind": "Identifier", + "ident": "parameterDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 17 + }, + "end": { + "line": 5, + "column": 34 + } + } + }, + "referent": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 17 + }, + "end": { + "line": 5, + "column": 34 + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 98 + } + } + } + }, + "members": { + "classDecorate": { + "kind": "ModuleMethod", + "name": { + "kind": "Identifier", + "ident": "classDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 17 + }, + "end": { + "line": 2, + "column": 30 + } + } + }, + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "target", + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 31 + }, + "end": { + "line": 2, + "column": 37 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "object", + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 31 + }, + "end": { + "line": 2, + "column": 45 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 47 + }, + "end": { + "line": 2, + "column": 49 + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 49 + } + } + }, + "propertyDecorate": { + "kind": "ModuleMethod", + "name": { + "kind": "Identifier", + "ident": "propertyDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 33 + } + } + }, + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "target", + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 34 + }, + "end": { + "line": 3, + "column": 40 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "object", + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 34 + }, + "end": { + "line": 3, + "column": 48 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "propertyKey", + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 50 + }, + "end": { + "line": 3, + "column": 61 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 50 + }, + "end": { + "line": 3, + "column": 69 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 71 + }, + "end": { + "line": 3, + "column": 73 + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 73 + } + } + }, + "methodDecorate": { + "kind": "ModuleMethod", + "name": { + "kind": "Identifier", + "ident": "methodDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 17 + }, + "end": { + "line": 4, + "column": 31 + } + } + }, + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "target", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 32 + }, + "end": { + "line": 4, + "column": 38 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "object", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 32 + }, + "end": { + "line": 4, + "column": 46 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "propertyKey", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 48 + }, + "end": { + "line": 4, + "column": 59 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 48 + }, + "end": { + "line": 4, + "column": 64 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "descriptor", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 66 + }, + "end": { + "line": 4, + "column": 76 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 66 + }, + "end": { + "line": 4, + "column": 81 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 83 + }, + "end": { + "line": 4, + "column": 85 + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 85 + } + } + }, + "parameterDecorate": { + "kind": "ModuleMethod", + "name": { + "kind": "Identifier", + "ident": "parameterDecorate", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 17 + }, + "end": { + "line": 5, + "column": 34 + } + } + }, + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "target", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 35 + }, + "end": { + "line": 5, + "column": 41 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "object", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 35 + }, + "end": { + "line": 5, + "column": 49 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "propertyKey", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 51 + }, + "end": { + "line": 5, + "column": 62 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 51 + }, + "end": { + "line": 5, + "column": 70 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "parameterIndex", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 72 + }, + "end": { + "line": 5, + "column": 86 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "number", + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 72 + }, + "end": { + "line": 5, + "column": 94 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 96 + }, + "end": { + "line": 5, + "column": 98 + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 98 + } + } + }, + ".main": { + "kind": "ModuleMethod", + "name": { + "kind": "Identifier", + "ident": ".main" + }, + "body": { + "kind": "Block", + "statements": [] + } + } + }, + "loc": { + "file": "decors.ts", + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 7, + "column": 1 + } + } + }, + "index": { + "kind": "Module", + "name": { + "kind": "Identifier", + "ident": "index" + }, + "exports": {}, + "members": { + "TestSimpleDecorators": { + "kind": "Class", + "name": { + "kind": "Identifier", + "ident": "TestSimpleDecorators", + "loc": { + "file": "index.ts", + "start": { + "line": 8, + "column": 7 + }, + "end": { + "line": 8, + "column": 27 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:classDecorate" + } + } + ], + "members": { + "m1": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "m1", + "loc": { + "file": "index.ts", + "start": { + "line": 14, + "column": 5 + }, + "end": { + "line": 14, + "column": 7 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 14, + "column": 27 + }, + "end": { + "line": 14, + "column": 29 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 14, + "column": 20 + }, + "end": { + "line": 14, + "column": 30 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 14, + "column": 18 + }, + "end": { + "line": 14, + "column": 32 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 13, + "column": 5 + }, + "end": { + "line": 14, + "column": 32 + } + } + }, + "m2": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "m2", + "loc": { + "file": "index.ts", + "start": { + "line": 16, + "column": 12 + }, + "end": { + "line": 16, + "column": 14 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 16, + "column": 34 + }, + "end": { + "line": 16, + "column": 36 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 16, + "column": 27 + }, + "end": { + "line": 16, + "column": 37 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 16, + "column": 25 + }, + "end": { + "line": 16, + "column": 39 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 15, + "column": 5 + }, + "end": { + "line": 16, + "column": 39 + } + } + }, + "mparam1": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "mparam1", + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 5 + }, + "end": { + "line": 27, + "column": 12 + } + } + }, + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "x", + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 32 + }, + "end": { + "line": 27, + "column": 33 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 13 + }, + "end": { + "line": 27, + "column": 33 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate" + } + } + ] + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "y", + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 35 + }, + "end": { + "line": 27, + "column": 36 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 35 + }, + "end": { + "line": 27, + "column": 36 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "z", + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 57 + }, + "end": { + "line": 27, + "column": 58 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 38 + }, + "end": { + "line": 27, + "column": 58 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate" + } + } + ] + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 66 + }, + "end": { + "line": 27, + "column": 69 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 27, + "column": 5 + }, + "end": { + "line": 27, + "column": 69 + } + } + }, + "mparam2": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "mparam2", + "loc": { + "file": "index.ts", + "start": { + "line": 29, + "column": 5 + }, + "end": { + "line": 29, + "column": 12 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "x", + "loc": { + "file": "index.ts", + "start": { + "line": 29, + "column": 32 + }, + "end": { + "line": 29, + "column": 33 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 29, + "column": 13 + }, + "end": { + "line": 29, + "column": 33 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate" + } + } + ] + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "y", + "loc": { + "file": "index.ts", + "start": { + "line": 29, + "column": 35 + }, + "end": { + "line": 29, + "column": 36 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 29, + "column": 35 + }, + "end": { + "line": 29, + "column": 36 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "z", + "loc": { + "file": "index.ts", + "start": { + "line": 29, + "column": 57 + }, + "end": { + "line": 29, + "column": 58 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 29, + "column": 38 + }, + "end": { + "line": 29, + "column": 58 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate" + } + } + ] + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 29, + "column": 66 + }, + "end": { + "line": 29, + "column": 69 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 28, + "column": 5 + }, + "end": { + "line": 29, + "column": 69 + } + } + }, + "p1": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "p1", + "loc": { + "file": "index.ts", + "start": { + "line": 18, + "column": 9 + }, + "end": { + "line": 18, + "column": 11 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string" + }, + "access": "public", + "loc": { + "file": "index.ts", + "start": { + "line": 17, + "column": 5 + }, + "end": { + "line": 18, + "column": 36 + } + }, + "getter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "get_p1" + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 18, + "column": 31 + }, + "end": { + "line": 18, + "column": 33 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 18, + "column": 24 + }, + "end": { + "line": 18, + "column": 34 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 18, + "column": 22 + }, + "end": { + "line": 18, + "column": 36 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 17, + "column": 5 + }, + "end": { + "line": 18, + "column": 36 + } + } + }, + "setter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "set_p1" + }, + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "v", + "loc": { + "file": "index.ts", + "start": { + "line": 19, + "column": 12 + }, + "end": { + "line": 19, + "column": 13 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 19, + "column": 12 + }, + "end": { + "line": 19, + "column": 21 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 19, + "column": 23 + }, + "end": { + "line": 19, + "column": 25 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 19, + "column": 5 + }, + "end": { + "line": 19, + "column": 25 + } + } + } + }, + "p2": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "p2", + "loc": { + "file": "index.ts", + "start": { + "line": 20, + "column": 9 + }, + "end": { + "line": 20, + "column": 11 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string" + }, + "access": "public", + "loc": { + "file": "index.ts", + "start": { + "line": 20, + "column": 5 + }, + "end": { + "line": 20, + "column": 36 + } + }, + "getter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "get_p2" + }, + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 20, + "column": 31 + }, + "end": { + "line": 20, + "column": 33 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 20, + "column": 24 + }, + "end": { + "line": 20, + "column": 34 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 20, + "column": 22 + }, + "end": { + "line": 20, + "column": 36 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 20, + "column": 5 + }, + "end": { + "line": 20, + "column": 36 + } + } + }, + "setter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "set_p2" + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "v", + "loc": { + "file": "index.ts", + "start": { + "line": 22, + "column": 12 + }, + "end": { + "line": 22, + "column": 13 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 22, + "column": 12 + }, + "end": { + "line": 22, + "column": 21 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 22, + "column": 23 + }, + "end": { + "line": 22, + "column": 25 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 21, + "column": 5 + }, + "end": { + "line": 22, + "column": 25 + } + } + } + }, + "p3": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "p3", + "loc": { + "file": "index.ts", + "start": { + "line": 24, + "column": 16 + }, + "end": { + "line": 24, + "column": 18 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string" + }, + "access": "public", + "loc": { + "file": "index.ts", + "start": { + "line": 23, + "column": 5 + }, + "end": { + "line": 24, + "column": 34 + } + }, + "getter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "get_p3" + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 24, + "column": 30 + }, + "end": { + "line": 24, + "column": 32 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 24, + "column": 23 + }, + "end": { + "line": 24, + "column": 32 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 24, + "column": 21 + }, + "end": { + "line": 24, + "column": 34 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 23, + "column": 5 + }, + "end": { + "line": 24, + "column": 34 + } + } + }, + "setter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "set_p3" + }, + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "v", + "loc": { + "file": "index.ts", + "start": { + "line": 25, + "column": 19 + }, + "end": { + "line": 25, + "column": 20 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 25, + "column": 19 + }, + "end": { + "line": 25, + "column": 28 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 25, + "column": 30 + }, + "end": { + "line": 25, + "column": 32 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 25, + "column": 5 + }, + "end": { + "line": 25, + "column": 32 + } + } + } + }, + "a": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "a", + "loc": { + "file": "index.ts", + "start": { + "line": 9, + "column": 23 + }, + "end": { + "line": 9, + "column": 24 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:propertyDecorate" + } + } + ], + "access": "public", + "readonly": false, + "optional": false, + "static": false, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 9, + "column": 5 + }, + "end": { + "line": 9, + "column": 33 + } + } + } + }, + "b": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "b", + "loc": { + "file": "index.ts", + "start": { + "line": 10, + "column": 30 + }, + "end": { + "line": 10, + "column": 31 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:propertyDecorate" + } + } + ], + "access": "public", + "readonly": false, + "optional": false, + "static": false, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 10, + "column": 5 + }, + "end": { + "line": 10, + "column": 40 + } + } + } + }, + "c": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "c", + "loc": { + "file": "index.ts", + "start": { + "line": 11, + "column": 30 + }, + "end": { + "line": 11, + "column": 31 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:propertyDecorate" + } + } + ], + "access": "public", + "readonly": false, + "optional": false, + "static": false, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 11, + "column": 5 + }, + "end": { + "line": 11, + "column": 49 + } + } + } + }, + ".ctor": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": ".ctor" + }, + "access": "public", + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ExpressionStatement", + "expression": { + "kind": "BinaryOperatorExpression", + "left": { + "kind": "LoadLocationExpression", + "object": { + "kind": "LoadLocationExpression", + "name": { + "kind": "Token", + "tok": ".this" + } + }, + "name": { + "kind": "Token", + "tok": "basic/decorators:index:TestSimpleDecorators:c", + "loc": { + "file": "index.ts", + "start": { + "line": 11, + "column": 30 + }, + "end": { + "line": 11, + "column": 31 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 11, + "column": 42 + }, + "end": { + "line": 11, + "column": 48 + } + } + }, + "operator": "=", + "right": { + "kind": "StringLiteral", + "raw": "test", + "value": "test", + "loc": { + "file": "index.ts", + "start": { + "line": 11, + "column": 42 + }, + "end": { + "line": 11, + "column": 48 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 11, + "column": 42 + }, + "end": { + "line": 11, + "column": 48 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 11, + "column": 42 + }, + "end": { + "line": 11, + "column": 48 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 30, + "column": 2 + } + } + } + } + }, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 30, + "column": 2 + } + } + }, + "TestQualifiedDecorators": { + "kind": "Class", + "name": { + "kind": "Identifier", + "ident": "TestQualifiedDecorators", + "loc": { + "file": "index.ts", + "start": { + "line": 34, + "column": 7 + }, + "end": { + "line": 34, + "column": 30 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:classDecorate" + } + } + ], + "members": { + "m1": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "m1", + "loc": { + "file": "index.ts", + "start": { + "line": 40, + "column": 5 + }, + "end": { + "line": 40, + "column": 7 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 40, + "column": 27 + }, + "end": { + "line": 40, + "column": 29 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 40, + "column": 20 + }, + "end": { + "line": 40, + "column": 30 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 40, + "column": 18 + }, + "end": { + "line": 40, + "column": 32 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 39, + "column": 5 + }, + "end": { + "line": 40, + "column": 32 + } + } + }, + "m2": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "m2", + "loc": { + "file": "index.ts", + "start": { + "line": 42, + "column": 12 + }, + "end": { + "line": 42, + "column": 14 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 42, + "column": 34 + }, + "end": { + "line": 42, + "column": 36 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 42, + "column": 27 + }, + "end": { + "line": 42, + "column": 37 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 42, + "column": 25 + }, + "end": { + "line": 42, + "column": 39 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 41, + "column": 5 + }, + "end": { + "line": 42, + "column": 39 + } + } + }, + "mparam1": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "mparam1", + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 5 + }, + "end": { + "line": 53, + "column": 12 + } + } + }, + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "x", + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 39 + }, + "end": { + "line": 53, + "column": 40 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 13 + }, + "end": { + "line": 53, + "column": 40 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate" + } + } + ] + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "y", + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 42 + }, + "end": { + "line": 53, + "column": 43 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 42 + }, + "end": { + "line": 53, + "column": 43 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "z", + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 71 + }, + "end": { + "line": 53, + "column": 72 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 45 + }, + "end": { + "line": 53, + "column": 72 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate" + } + } + ] + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 80 + }, + "end": { + "line": 53, + "column": 83 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 53, + "column": 5 + }, + "end": { + "line": 53, + "column": 83 + } + } + }, + "mparam2": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "mparam2", + "loc": { + "file": "index.ts", + "start": { + "line": 55, + "column": 5 + }, + "end": { + "line": 55, + "column": 12 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "x", + "loc": { + "file": "index.ts", + "start": { + "line": 55, + "column": 39 + }, + "end": { + "line": 55, + "column": 40 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 55, + "column": 13 + }, + "end": { + "line": 55, + "column": 40 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate" + } + } + ] + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "y", + "loc": { + "file": "index.ts", + "start": { + "line": 55, + "column": 42 + }, + "end": { + "line": 55, + "column": 43 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 55, + "column": 42 + }, + "end": { + "line": 55, + "column": 43 + } + } + } + }, + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "z", + "loc": { + "file": "index.ts", + "start": { + "line": 55, + "column": 71 + }, + "end": { + "line": 55, + "column": 72 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "dynamic", + "loc": { + "file": "index.ts", + "start": { + "line": 55, + "column": 45 + }, + "end": { + "line": 55, + "column": 72 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:parameterDecorate" + } + } + ] + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 55, + "column": 80 + }, + "end": { + "line": 55, + "column": 83 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 54, + "column": 5 + }, + "end": { + "line": 55, + "column": 83 + } + } + }, + "p1": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "p1", + "loc": { + "file": "index.ts", + "start": { + "line": 44, + "column": 9 + }, + "end": { + "line": 44, + "column": 11 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string" + }, + "access": "public", + "loc": { + "file": "index.ts", + "start": { + "line": 43, + "column": 5 + }, + "end": { + "line": 44, + "column": 36 + } + }, + "getter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "get_p1" + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 44, + "column": 31 + }, + "end": { + "line": 44, + "column": 33 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 44, + "column": 24 + }, + "end": { + "line": 44, + "column": 34 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 44, + "column": 22 + }, + "end": { + "line": 44, + "column": 36 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 43, + "column": 5 + }, + "end": { + "line": 44, + "column": 36 + } + } + }, + "setter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "set_p1" + }, + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "v", + "loc": { + "file": "index.ts", + "start": { + "line": 45, + "column": 12 + }, + "end": { + "line": 45, + "column": 13 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 45, + "column": 12 + }, + "end": { + "line": 45, + "column": 21 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 45, + "column": 23 + }, + "end": { + "line": 45, + "column": 25 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 45, + "column": 5 + }, + "end": { + "line": 45, + "column": 25 + } + } + } + }, + "p2": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "p2", + "loc": { + "file": "index.ts", + "start": { + "line": 46, + "column": 9 + }, + "end": { + "line": 46, + "column": 11 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string" + }, + "access": "public", + "loc": { + "file": "index.ts", + "start": { + "line": 46, + "column": 5 + }, + "end": { + "line": 46, + "column": 36 + } + }, + "getter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "get_p2" + }, + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 46, + "column": 31 + }, + "end": { + "line": 46, + "column": 33 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 46, + "column": 24 + }, + "end": { + "line": 46, + "column": 34 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 46, + "column": 22 + }, + "end": { + "line": 46, + "column": 36 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 46, + "column": 5 + }, + "end": { + "line": 46, + "column": 36 + } + } + }, + "setter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "set_p2" + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "v", + "loc": { + "file": "index.ts", + "start": { + "line": 48, + "column": 12 + }, + "end": { + "line": 48, + "column": 13 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 48, + "column": 12 + }, + "end": { + "line": 48, + "column": 21 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 48, + "column": 23 + }, + "end": { + "line": 48, + "column": 25 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 47, + "column": 5 + }, + "end": { + "line": 48, + "column": 25 + } + } + } + }, + "p3": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "p3", + "loc": { + "file": "index.ts", + "start": { + "line": 50, + "column": 16 + }, + "end": { + "line": 50, + "column": 18 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string" + }, + "access": "public", + "loc": { + "file": "index.ts", + "start": { + "line": 49, + "column": 5 + }, + "end": { + "line": 50, + "column": 34 + } + }, + "getter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "get_p3" + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:methodDecorate" + } + } + ], + "access": "public", + "parameters": [], + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ReturnStatement", + "expression": { + "kind": "StringLiteral", + "raw": "", + "value": "", + "loc": { + "file": "index.ts", + "start": { + "line": 50, + "column": 30 + }, + "end": { + "line": 50, + "column": 32 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 50, + "column": 23 + }, + "end": { + "line": 50, + "column": 32 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 50, + "column": 21 + }, + "end": { + "line": 50, + "column": 34 + } + } + }, + "returnType": { + "kind": "TypeToken", + "tok": "string" + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 49, + "column": 5 + }, + "end": { + "line": 50, + "column": 34 + } + } + }, + "setter": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": "set_p3" + }, + "access": "public", + "parameters": [ + { + "kind": "LocalVariable", + "name": { + "kind": "Identifier", + "ident": "v", + "loc": { + "file": "index.ts", + "start": { + "line": 51, + "column": 19 + }, + "end": { + "line": 51, + "column": 20 + } + } + }, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 51, + "column": 19 + }, + "end": { + "line": 51, + "column": 28 + } + } + } + } + ], + "body": { + "kind": "Block", + "statements": [], + "loc": { + "file": "index.ts", + "start": { + "line": 51, + "column": 30 + }, + "end": { + "line": 51, + "column": 32 + } + } + }, + "static": false, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 51, + "column": 5 + }, + "end": { + "line": 51, + "column": 32 + } + } + } + }, + "a": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "a", + "loc": { + "file": "index.ts", + "start": { + "line": 35, + "column": 30 + }, + "end": { + "line": 35, + "column": 31 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:propertyDecorate" + } + } + ], + "access": "public", + "readonly": false, + "optional": false, + "static": false, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 35, + "column": 5 + }, + "end": { + "line": 35, + "column": 40 + } + } + } + }, + "b": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "b", + "loc": { + "file": "index.ts", + "start": { + "line": 36, + "column": 37 + }, + "end": { + "line": 36, + "column": 38 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:propertyDecorate" + } + } + ], + "access": "public", + "readonly": false, + "optional": false, + "static": false, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 36, + "column": 5 + }, + "end": { + "line": 36, + "column": 47 + } + } + } + }, + "c": { + "kind": "ClassProperty", + "name": { + "kind": "Identifier", + "ident": "c", + "loc": { + "file": "index.ts", + "start": { + "line": 37, + "column": 37 + }, + "end": { + "line": 37, + "column": 38 + } + } + }, + "attributes": [ + { + "kind": "Attribute", + "decorator": { + "kind": "Token", + "tok": "basic/decorators:decors:propertyDecorate" + } + } + ], + "access": "public", + "readonly": false, + "optional": false, + "static": false, + "type": { + "kind": "TypeToken", + "tok": "string", + "loc": { + "file": "index.ts", + "start": { + "line": 37, + "column": 5 + }, + "end": { + "line": 37, + "column": 56 + } + } + } + }, + ".ctor": { + "kind": "ClassMethod", + "name": { + "kind": "Identifier", + "ident": ".ctor" + }, + "access": "public", + "body": { + "kind": "Block", + "statements": [ + { + "kind": "ExpressionStatement", + "expression": { + "kind": "BinaryOperatorExpression", + "left": { + "kind": "LoadLocationExpression", + "object": { + "kind": "LoadLocationExpression", + "name": { + "kind": "Token", + "tok": ".this" + } + }, + "name": { + "kind": "Token", + "tok": "basic/decorators:index:TestQualifiedDecorators:c", + "loc": { + "file": "index.ts", + "start": { + "line": 37, + "column": 37 + }, + "end": { + "line": 37, + "column": 38 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 37, + "column": 49 + }, + "end": { + "line": 37, + "column": 55 + } + } + }, + "operator": "=", + "right": { + "kind": "StringLiteral", + "raw": "test", + "value": "test", + "loc": { + "file": "index.ts", + "start": { + "line": 37, + "column": 49 + }, + "end": { + "line": 37, + "column": 55 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 37, + "column": 49 + }, + "end": { + "line": 37, + "column": 55 + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 37, + "column": 49 + }, + "end": { + "line": 37, + "column": 55 + } + } + } + ], + "loc": { + "file": "index.ts", + "start": { + "line": 33, + "column": 1 + }, + "end": { + "line": 56, + "column": 2 + } + } + } + } + }, + "abstract": false, + "loc": { + "file": "index.ts", + "start": { + "line": 33, + "column": 1 + }, + "end": { + "line": 56, + "column": 2 + } + } + }, + ".init": { + "kind": "ModuleMethod", + "name": { + "kind": "Identifier", + "ident": ".init" + }, + "body": { + "kind": "Block", + "statements": [ + { + "kind": "Import", + "referent": { + "kind": "Token", + "tok": "basic/decorators:decors", + "loc": { + "file": "index.ts", + "start": { + "line": 1, + "column": 82 + }, + "end": { + "line": 1, + "column": 92 + } + } + } + }, + { + "kind": "Import", + "referent": { + "kind": "Token", + "tok": "basic/decorators:decors", + "loc": { + "file": "index.ts", + "start": { + "line": 2, + "column": 25 + }, + "end": { + "line": 2, + "column": 35 + } + } + } + } + ] + } + }, + ".main": { + "kind": "ModuleMethod", + "name": { + "kind": "Identifier", + "ident": ".main" + }, + "body": { + "kind": "Block", + "statements": [] + } + } + }, + "loc": { + "file": "index.ts", + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 58, + "column": 1 + } + } + } + }, + "aliases": { + ".default": "index" + } +} diff --git a/cmd/lumijs/tests/output/basic/decors/decors.ts b/cmd/lumijs/tests/output/basic/decors/decors.ts new file mode 100644 index 000000000..081e0679b --- /dev/null +++ b/cmd/lumijs/tests/output/basic/decors/decors.ts @@ -0,0 +1,6 @@ +// Define a bunch of no-op decorators. +export function classDecorate(target: Object) {} +export function propertyDecorate(target: Object, propertyKey: string) {} +export function methodDecorate(target: Object, propertyKey: any, descriptor: any) {} +export function parameterDecorate(target: Object, propertyKey: string, parameterIndex: number) {} + diff --git a/cmd/lumijs/tests/output/basic/decors/index.ts b/cmd/lumijs/tests/output/basic/decors/index.ts new file mode 100644 index 000000000..93e7b01bc --- /dev/null +++ b/cmd/lumijs/tests/output/basic/decors/index.ts @@ -0,0 +1,57 @@ +import {classDecorate, propertyDecorate, methodDecorate, parameterDecorate} from "./decors"; +import * as decors from "./decors"; + +// Test that each of the cases works and leads to the right attributes in the resulting metadata. + +// First, using "simple" names. +@classDecorate +class TestSimpleDecorators { + @propertyDecorate a: string; + @propertyDecorate public b: string; + @propertyDecorate public c: string = "test"; + + @methodDecorate + m1(): string { return ""; } + @methodDecorate + public m2(): string { return ""; } + @methodDecorate + get p1(): string { return ""; } + set p1(v: string) {} + get p2(): string { return ""; } + @methodDecorate + set p2(v: string) {} + @methodDecorate + public get p3() { return "" } + public set p3(v: string) {} + + mparam1(@parameterDecorate x, y, @parameterDecorate z): void { } + @methodDecorate + mparam2(@parameterDecorate x, y, @parameterDecorate z): void { } +} + +// Next, using "qualified" names. +@decors.classDecorate +class TestQualifiedDecorators { + @decors.propertyDecorate a: string; + @decors.propertyDecorate public b: string; + @decors.propertyDecorate public c: string = "test"; + + @decors.methodDecorate + m1(): string { return ""; } + @decors.methodDecorate + public m2(): string { return ""; } + @decors.methodDecorate + get p1(): string { return ""; } + set p1(v: string) {} + get p2(): string { return ""; } + @decors.methodDecorate + set p2(v: string) {} + @decors.methodDecorate + public get p3() { return "" } + public set p3(v: string) {} + + mparam1(@decors.parameterDecorate x, y, @decors.parameterDecorate z): void { } + @decors.methodDecorate + mparam2(@decors.parameterDecorate x, y, @decors.parameterDecorate z): void { } +} + diff --git a/cmd/lumijs/tests/output/basic/decors/tsconfig.json b/cmd/lumijs/tests/output/basic/decors/tsconfig.json new file mode 100644 index 000000000..da32855a0 --- /dev/null +++ b/cmd/lumijs/tests/output/basic/decors/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "target": "es5" + }, + "files": [ + "index.ts", + "decors.ts" + ] +} + diff --git a/examples/analyzers/infosec/main.go b/examples/analyzers/infosec/main.go index 916a403ce..2230bf75a 100644 --- a/examples/analyzers/infosec/main.go +++ b/examples/analyzers/infosec/main.go @@ -72,7 +72,7 @@ func (a *analyzer) AnalyzeResource(ctx context.Context, } func (a *analyzer) analyzeAWSEC2Instance(bag *pbstruct.Struct) []*lumirpc.AnalyzeResourceFailure { - props := resource.UnmarshalProperties(bag) + props := resource.UnmarshalProperties(nil, bag, resource.MarshalOptions{RawResources: true}) image := props["imageId"] // TODO: do a real check. For now, we make something up. return []*lumirpc.AnalyzeResourceFailure{ diff --git a/lib/aws/idl/ec2/securityGroup.go b/lib/aws/idl/ec2/securityGroup.go index 897dd7206..115e9c953 100644 --- a/lib/aws/idl/ec2/securityGroup.go +++ b/lib/aws/idl/ec2/securityGroup.go @@ -25,6 +25,10 @@ type SecurityGroup struct { idl.NamedResource // A required description about the security group. GroupDescription string `lumi:"groupDescription,replaces"` + // An optional name for the security group. If you don't specify one, a unique physical ID will be generated and + // used instead. If you specify a name, you cannot perform updates that require replacement of this resource. You + // can perform updates that require no or some interruption. If you must replace the resource, specify a new name. + GroupName *string `lumi:"groupName,optional,replaces"` // The VPC in which this security group resides (or blank if the default VPC). VPC *VPC `lumi:"vpc,optional,replaces"` // A list of Amazon EC2 security group egress rules. diff --git a/lib/aws/idl/elasticbeanstalk/applicationVersion.go b/lib/aws/idl/elasticbeanstalk/applicationVersion.go index ee801f60a..4ddd4a68c 100644 --- a/lib/aws/idl/elasticbeanstalk/applicationVersion.go +++ b/lib/aws/idl/elasticbeanstalk/applicationVersion.go @@ -27,6 +27,10 @@ type ApplicationVersion struct { idl.NamedResource // Name of the Elastic Beanstalk application that is associated with this application version. Application *Application `lumi:"application,replaces"` + // An optional version label name. If you don't specify one, a unique physical ID will be generated and + // used instead. If you specify a name, you cannot perform updates that require replacement of this resource. You + // can perform updates that require no or some interruption. If you must replace the resource, specify a new name. + VersionLabel *string `lumi:"versionLabel,optional,replaces"` // A description of this application version. Description *string `lumi:"description,optional"` // The source bundle for this application version. This supports all the usual Lumi asset schemes, in addition diff --git a/lib/aws/pack/ec2/instance.ts b/lib/aws/pack/ec2/instance.ts index c8f11a480..ce5fc80e3 100644 --- a/lib/aws/pack/ec2/instance.ts +++ b/lib/aws/pack/ec2/instance.ts @@ -69,11 +69,11 @@ export class Instance extends lumi.Resource implements InstanceArgs { public instanceType?: InstanceType; public securityGroups?: SecurityGroup[]; public keyName?: string; - public availabilityZone: string; - public privateDNSName?: string; - public publicDNSName?: string; - public privateIP?: string; - public publicIP?: string; + @lumi.out public availabilityZone: string; + @lumi.out public privateDNSName?: string; + @lumi.out public publicDNSName?: string; + @lumi.out public privateIP?: string; + @lumi.out public publicIP?: string; constructor(name: string, args: InstanceArgs) { super(); diff --git a/lib/aws/pack/ec2/securityGroup.ts b/lib/aws/pack/ec2/securityGroup.ts index d8a562252..0d6122551 100644 --- a/lib/aws/pack/ec2/securityGroup.ts +++ b/lib/aws/pack/ec2/securityGroup.ts @@ -8,10 +8,11 @@ import {VPC} from "./vpc"; export class SecurityGroup extends lumi.Resource implements SecurityGroupArgs { public readonly name: string; public readonly groupDescription: string; + public readonly groupName?: string; public readonly vpc?: VPC; public securityGroupEgress?: SecurityGroupRule[]; public securityGroupIngress?: SecurityGroupRule[]; - public groupID: string; + @lumi.out public groupID: string; constructor(name: string, args: SecurityGroupArgs) { super(); @@ -23,6 +24,7 @@ export class SecurityGroup extends lumi.Resource implements SecurityGroupArgs { throw new Error("Missing required argument 'groupDescription'"); } this.groupDescription = args.groupDescription; + this.groupName = args.groupName; this.vpc = args.vpc; this.securityGroupEgress = args.securityGroupEgress; this.securityGroupIngress = args.securityGroupIngress; @@ -31,6 +33,7 @@ export class SecurityGroup extends lumi.Resource implements SecurityGroupArgs { export interface SecurityGroupArgs { readonly groupDescription: string; + readonly groupName?: string; readonly vpc?: VPC; securityGroupEgress?: SecurityGroupRule[]; securityGroupIngress?: SecurityGroupRule[]; diff --git a/lib/aws/pack/elasticbeanstalk/applicationVersion.ts b/lib/aws/pack/elasticbeanstalk/applicationVersion.ts index d6585d84c..26c7fe310 100644 --- a/lib/aws/pack/elasticbeanstalk/applicationVersion.ts +++ b/lib/aws/pack/elasticbeanstalk/applicationVersion.ts @@ -9,6 +9,7 @@ import {Object} from "../s3/object"; export class ApplicationVersion extends lumi.Resource implements ApplicationVersionArgs { public readonly name: string; public readonly application: Application; + public readonly versionLabel?: string; public description?: string; public readonly sourceBundle: Object; @@ -22,6 +23,7 @@ export class ApplicationVersion extends lumi.Resource implements ApplicationVers throw new Error("Missing required argument 'application'"); } this.application = args.application; + this.versionLabel = args.versionLabel; this.description = args.description; if (args.sourceBundle === undefined) { throw new Error("Missing required argument 'sourceBundle'"); @@ -32,6 +34,7 @@ export class ApplicationVersion extends lumi.Resource implements ApplicationVers export interface ApplicationVersionArgs { readonly application: Application; + readonly versionLabel?: string; description?: string; readonly sourceBundle: Object; } diff --git a/lib/aws/pack/elasticbeanstalk/environment.ts b/lib/aws/pack/elasticbeanstalk/environment.ts index be4b3090f..639c56650 100644 --- a/lib/aws/pack/elasticbeanstalk/environment.ts +++ b/lib/aws/pack/elasticbeanstalk/environment.ts @@ -21,7 +21,7 @@ export class Environment extends lumi.Resource implements EnvironmentArgs { public templateName?: string; public readonly tier?: Tier; public version?: ApplicationVersion; - public endpointURL: string; + @lumi.out public endpointURL: string; constructor(name: string, args: EnvironmentArgs) { super(); diff --git a/lib/aws/pack/iam/role.ts b/lib/aws/pack/iam/role.ts index 9f378611d..263dcf8fe 100644 --- a/lib/aws/pack/iam/role.ts +++ b/lib/aws/pack/iam/role.ts @@ -13,7 +13,7 @@ export class Role extends lumi.Resource implements RoleArgs { public readonly roleName?: string; public managedPolicyARNs?: ARN[]; public policies?: InlinePolicy[]; - public arn: ARN; + @lumi.out public arn: ARN; constructor(name: string, args: RoleArgs) { super(); diff --git a/lib/aws/pack/lambda/function.ts b/lib/aws/pack/lambda/function.ts index 8b7eb9e67..adba1e2c5 100644 --- a/lib/aws/pack/lambda/function.ts +++ b/lib/aws/pack/lambda/function.ts @@ -37,7 +37,7 @@ export class Function extends lumi.Resource implements FunctionArgs { public memorySize?: number; public timeout?: number; public vpcConfig?: VPCConfig; - public arn: ARN; + @lumi.out public arn: ARN; constructor(name: string, args: FunctionArgs) { super(); diff --git a/lib/aws/provider/arn/arn.go b/lib/aws/provider/arn/arn.go new file mode 100644 index 000000000..56390e881 --- /dev/null +++ b/lib/aws/provider/arn/arn.go @@ -0,0 +1,202 @@ +// Licensed to Pulumi Corporation ("Pulumi") under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// Pulumi licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package arn + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/pulumi/lumi/pkg/resource" + + aws "github.com/pulumi/lumi/lib/aws/rpc" +) + +const ( + arnPrefix = "arn" + arnDefaultPartition = "aws" + arnDefaultResourceSeparator = ":" + arnAlternativeResourceSeparator = "/" + arnPathDelimiter = "/" +) + +// ARN is a string representation of an Amazon Resource Name (ARN). +type ARN string + +// New creates a new AWS ARN string from the given account and service information. For more information about the ARN +// format, see http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html. +func New(service string, region string, accountID string, res string) ARN { + parts := ARNParts{ + Partition: arnDefaultPartition, + Service: service, + Region: region, + AccountID: accountID, + Resource: res, + } + return parts.ARN() +} + +// NewResource creates a new AWS ARN string from the given service information. This handles the canonical use case +// of delimiting the resource type and name with a ":". For "/" delimiters, see the NewResourceAltARN function. +func NewResource(service string, region string, accountID string, restype string, resname string) ARN { + return New(service, region, accountID, restype+arnDefaultResourceSeparator+resname) +} + +// NewResourceAlt creates a new AWS ARN string from the given service information. This handles the canonical use +// case of delimiting the resource type, but, unlike NewResourceARN, uses "/" as the delimiter instead of ":". +func NewResourceAlt(service string, region string, accountID string, restype string, resname string) ARN { + return New(service, region, accountID, restype+arnAlternativeResourceSeparator+resname) +} + +// NewID is the same as New except that it returns a string suitable as a Lumi resource ID. +func NewID(service string, region string, accountID string, res string) resource.ID { + return resource.ID(New(service, region, accountID, res)) +} + +// NewResourceID is the same as NewResource except that it returns a string suitable as a Lumi resource ID. +func NewResourceID(service string, region string, accountID string, restype string, resname string) resource.ID { + return resource.ID(NewResource(service, region, accountID, restype, resname)) +} + +// NewResourceAltID is the same as NewResourceAltARN except that it returns a string suitable as a Lumi resource ID. +func NewResourceAltID(service string, region string, accountID string, restype string, resname string) resource.ID { + return resource.ID(NewResourceAlt(service, region, accountID, restype, resname)) +} + +// RPC turns an ARN into its marshalable form. +func (arn ARN) RPC() aws.ARN { return aws.ARN(arn) } + +// Parse turns a string formatted ARN into the consistuent ARN parts for inspection purposes. +func (arn ARN) Parse() (ARNParts, error) { + var parts ARNParts + ps := strings.Split(string(arn), ":") + if len(ps) == 0 { + return parts, errors.Errorf("Missing ARN prefix of '%v:'", arnPrefix) + } else if ps[0] != arnPrefix { + return parts, errors.Errorf("Unexpected ARN prefix of '%v'; expected '%v'", ps[0], arnPrefix) + } + if len(ps) > 1 { + parts.Partition = ps[1] + } + if len(ps) > 2 { + parts.Service = ps[2] + } + if len(ps) > 3 { + parts.Region = ps[3] + } + if len(ps) > 4 { + parts.AccountID = ps[4] + } + if len(ps) > 5 { + parts.Resource = ps[5] + } + if len(ps) > 6 { + parts.Resource = parts.Resource + ":" + ps[6] + } + return parts, nil +} + +// ParseResourceName parses an entire ARN and extracts its resource name part, returning an error if the process +// fails or if the resource name is missing. +func (arn ARN) ParseResourceName() (string, error) { + parts, err := arn.Parse() + if err != nil { + return "", err + } + resname := parts.ResourceName() + if resname == "" { + return "", errors.Errorf("Missing resource name in ARN '%v'", arn) + } + return resname, nil +} + +// ParseResourceNamePair parses an entire ARN and extracts its resource name part, returning an error if the process +// fails or if the resource name is missing. +func (arn ARN) ParseResourceNamePair() (string, string, error) { + parts, err := arn.Parse() + if err != nil { + return "", "", err + } + name1, name2 := parts.ResourceNamePair() + if name1 == "" || name2 == "" { + return "", "", errors.Errorf("ARN did not contain a name pair '%v'", arn) + } + return name1, name2, nil +} + +// ARNParts is a structure containing an ARN's distinct parts. Normally ARNs flow around as strings in the format +// described at http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html, however, when it comes time +// to creating or inspecting them, this first class structure can come in handy. +type ARNParts struct { + Partition string + Service string + Region string + AccountID string + Resource string +} + +// ARN turns the ARN parts into a single string in the canonical ARN format. Some or all parts may be missing. +func (a ARNParts) ARN() ARN { + return ARN(fmt.Sprintf("%v:%v:%v:%v:%v:%v", + arnPrefix, a.Partition, a.Service, a.Region, a.AccountID, a.Resource)) +} + +// ResourceType parses an ARN and returns the resource type. This detects both kinds of resource delimiters (":" and +// "/"), although, if called on an ARN not formatted as type, delimiter, and then ID, this may return the wrong thing. +func (a ARNParts) ResourceType() string { + res := a.Resource + if idx := strings.Index(res, arnDefaultResourceSeparator); idx != -1 { + return res[:idx] + } + if idx := strings.Index(res, arnAlternativeResourceSeparator); idx != -1 { + return res[:idx] + } + return res +} + +// ResourceName parses an ARN and returns the resource name. This detects both kinds of resource delimiters (":" and +// "/"); if called on an ARN not formatted as type, delimiter, followed by ID, this may return the wrong thing. +func (a ARNParts) ResourceName() string { + res := a.Resource + if idx := strings.Index(res, arnDefaultResourceSeparator); idx != -1 { + return res[idx+1:] + } + if idx := strings.Index(res, arnAlternativeResourceSeparator); idx != -1 { + return res[idx+1:] + } + return res +} + +// ResourceNamePair parses an ARN in the format of a name pair delimited by "/". An example is an S3 object, whose +// whose ARN is of the form "arn:aws:s3:::bucket_name/key_name". This function will return the "bucket_name" and +// "key_name" parts as independent parts, for convenient parsing as a single atomic operation. +func (a ARNParts) ResourceNamePair() (string, string) { + name := a.ResourceName() + if ix := strings.Index(name, "/"); ix != -1 { + return name[:ix], name[ix:] + } + return name, "" +} + +// ParseResourceName parses a resource ID to obtain its resource name which, for the entire AWS package, is the ARN. +func ParseResourceName(id resource.ID) (string, error) { + return ARN(id).ParseResourceName() +} + +// ParseResourceNamePair parses a resource ID to obtain a pair of resource names. See ResourceNamePair for details. +func ParseResourceNamePair(id resource.ID) (string, string, error) { + return ARN(id).ParseResourceNamePair() +} diff --git a/lib/aws/provider/arn/resources.go b/lib/aws/provider/arn/resources.go new file mode 100644 index 000000000..1814df8ba --- /dev/null +++ b/lib/aws/provider/arn/resources.go @@ -0,0 +1,108 @@ +// Licensed to Pulumi Corporation ("Pulumi") under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// Pulumi licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package arn + +import ( + "github.com/pulumi/lumi/pkg/resource" +) + +// This file contains constants and factories for all sorts of AWS resource ARNs. In the fullness of time, it should +// contain all of those listed at http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html. We are +// implementing them "as we go", however, so please feel free to add any that you need and which are presently missing. + +const ( + EC2 = "ec2" + EC2Instance = "intance" + EC2SecurityGroup = "security-group" + EC2VPC = "vpc" +) + +func NewEC2Instance(region, accountID, id string) ARN { + return NewResource(EC2, region, accountID, EC2Instance, id) +} + +func NewEC2InstanceID(region, accountID, id string) resource.ID { + return resource.ID(NewEC2Instance(region, accountID, id)) +} + +func NewEC2SecurityGroup(region, accountID, id string) ARN { + return NewResource(EC2, region, accountID, EC2SecurityGroup, id) +} + +func NewEC2SecurityGroupID(region, accountID, id string) resource.ID { + return resource.ID(NewEC2SecurityGroup(region, accountID, id)) +} + +func NewEC2VPC(region, accountID, id string) ARN { + return NewResource(EC2, region, accountID, EC2VPC, id) +} + +func NewEC2VPCID(region, accountID, id string) resource.ID { + return resource.ID(NewEC2VPC(region, accountID, id)) +} + +const ( + ElasticBeanstalk = "elasticbeanstalk" + ElasticBeanstalkApplication = "application" + ElasticBeanstalkApplicationVersion = "applicationversion" + ElasticBeanstalkEnvironment = "environment" +) + +func NewElasticBeanstalkApplication(region, accountID, name string) ARN { + return NewResourceAlt(ElasticBeanstalk, region, accountID, ElasticBeanstalkApplication, name) +} + +func NewElasticBeanstalkApplicationID(region, accountID, name string) resource.ID { + return resource.ID(NewElasticBeanstalkApplication(region, accountID, name)) +} + +func NewElasticBeanstalkApplicationVersion(region, accountID, appname, versionlabel string) ARN { + return NewResourceAlt(ElasticBeanstalk, region, accountID, + ElasticBeanstalkApplicationVersion, appname+arnPathDelimiter+versionlabel) +} + +func NewElasticBeanstalkApplicationVersionID(region, accountID, appname, versionlabel string) resource.ID { + return resource.ID(NewElasticBeanstalkApplicationVersion(region, accountID, appname, versionlabel)) +} + +func NewElasticBeanstalkEnvironment(region, accountID, appname, envname string) ARN { + return NewResourceAlt(ElasticBeanstalk, region, accountID, + ElasticBeanstalkEnvironment, appname+arnPathDelimiter+envname) +} + +const ( + S3 = "S3" +) + +func NewElasticBeanstalkEnvironmentID(region, accountID, appname, envname string) resource.ID { + return resource.ID(NewElasticBeanstalkApplicationVersion(region, accountID, appname, envname)) +} + +func NewS3Bucket(bucket string) ARN { + return NewResource(S3, "", "", "", bucket) +} + +func NewS3BucketID(bucket string) resource.ID { + return resource.ID(NewS3Bucket(bucket)) +} + +func NewS3Object(bucket, key string) ARN { + return NewResource(S3, "", "", "", bucket+arnPathDelimiter+key) +} + +func NewS3ObjectID(bucket, key string) resource.ID { + return resource.ID(NewS3Object(bucket, key)) +} diff --git a/lib/aws/provider/awsctx/context.go b/lib/aws/provider/awsctx/context.go index 84a8d6db9..3805f1bec 100644 --- a/lib/aws/provider/awsctx/context.go +++ b/lib/aws/provider/awsctx/context.go @@ -25,13 +25,20 @@ import ( "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/lambda" "github.com/aws/aws-sdk-go/service/s3" + "github.com/golang/glog" "github.com/pulumi/lumi/pkg/util/contract" + + "github.com/pulumi/lumi/lib/aws/provider/arn" ) // Context represents state shared amongst all parties in this process. In particular, it wraps an AWS session // object and offers convenient wrappers for creating connections to the various sub-services (EC2, S3, etc). type Context struct { - sess *session.Session + sess *session.Session // a global session object, shared amongst all service connections. + accountID string // the currently authenticated account's ID. + accountRole string // the currently authenticated account's IAM role. + + // per-service connections (lazily allocated and reused); dynamodb *dynamodb.DynamoDB ec2 *ec2.EC2 elasticbeanstalk *elasticbeanstalk.ElasticBeanstalk @@ -42,6 +49,7 @@ type Context struct { func New() (*Context, error) { // Create an AWS session; note that this is safe to share among many operations. + glog.V(5).Infof("Creating a new AWS session object w/ default credentials") // TODO: consider verifying credentials, region, etc. here. // TODO: currently we just inherit the standard AWS SDK credentials logic; eventually we will want more // flexibility, I assume, including possibly reading from configuration dynamically. @@ -51,12 +59,36 @@ func New() (*Context, error) { } contract.Assert(sess != nil) - // Allocate a new global context with this session; note that all other connections are lazily allocated. - return &Context{ - sess: sess, - }, nil + // Allocate the context early since we are about to use it to access the IAM service. Its usage is inherently + // limited until we have finished construction (in other words, completion of the present function). + ctx := &Context{sess: sess} + + // Query the IAM service to fetch the IAM user and role information. + glog.V(5).Infof("Querying AWS IAM service for profile metadata") + iaminfo, err := ctx.IAM().GetUser(nil) + if err != nil { + return nil, err + } + contract.Assert(iaminfo != nil) + contract.Assert(iaminfo.User != nil) + contract.Assert(iaminfo.User.Arn != nil) + userARN := arn.ARN(*iaminfo.User.Arn) + + // Parse and store the ARN information on the context for convenient access. + parsedARN, err := userARN.Parse() + if err != nil { + return nil, err + } + ctx.accountID = parsedARN.AccountID + ctx.accountRole = parsedARN.Resource + glog.V(7).Infof("AWS IAM profile ARN received: %v (id=%v role=%v)", userARN, ctx.accountID, ctx.accountRole) + + return ctx, nil } +func (ctx *Context) AccountID() string { return ctx.accountID } +func (ctx *Context) Region() string { return *ctx.sess.Config.Region } + func (ctx *Context) DynamoDB() *dynamodb.DynamoDB { contract.Assert(ctx.sess != nil) if ctx.dynamodb == nil { diff --git a/lib/aws/provider/awsctx/errors.go b/lib/aws/provider/awsctx/errors.go new file mode 100644 index 000000000..7bd06b3d2 --- /dev/null +++ b/lib/aws/provider/awsctx/errors.go @@ -0,0 +1,46 @@ +// Licensed to Pulumi Corporation ("Pulumi") under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// Pulumi licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package awsctx + +import ( + "github.com/aws/aws-sdk-go/aws/awserr" +) + +// IsAWSError returns true if the given error is an AWS error; if codes is non-zero in length, an AWS error with any one +// of these codes will return true; if codes is empty, then any AWS error is accepted. +func IsAWSError(err error, codes ...string) bool { + if erraws, iserraws := err.(awserr.Error); iserraws { + if len(codes) == 0 { + return true + } + for _, code := range codes { + if erraws.Code() == code { + return true + } + } + } + return false +} + +// IsAWSErrorMessage returns true if the given error is an AWS error with the given code and message. +func IsAWSErrorMessage(err error, code string, message string) bool { + if erraws, iserraws := err.(awserr.Error); iserraws { + if erraws.Code() == code && erraws.Message() == message { + return true + } + } + return false +} diff --git a/lib/aws/provider/dynamodb/table.go b/lib/aws/provider/dynamodb/table.go index 194b7d3a9..1ff5a35cc 100644 --- a/lib/aws/provider/dynamodb/table.go +++ b/lib/aws/provider/dynamodb/table.go @@ -17,7 +17,6 @@ package dynamodb import ( "crypto/sha1" - "errors" "fmt" "reflect" @@ -25,10 +24,12 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" awsdynamodb "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/pulumi/lumi/pkg/resource" + "github.com/pulumi/lumi/pkg/util/contract" "github.com/pulumi/lumi/pkg/util/mapper" "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" "github.com/pulumi/lumi/lib/aws/rpc/dynamodb" ) @@ -46,6 +47,16 @@ const ( maxGlobalSecondaryIndexes = 5 ) +const ( + // hashKeyAttribute is a partition key, also known as its hash attribute. The term "hash attribute" derives from + // DynamoDB's usage of an internal hash function to evenly distribute data items across partitions based on their + // partition key values. + hashKeyAttribute = "HASH" + // rangeKeyType is a sort key, also known as its range attribute. The term "range attribute" derives from the way + // DynamoDB stores items with the same partition key physically close together, sorted by the sort key value. + rangeKeyAttribute = "RANGE" +) + // NewTableProvider creates a provider that handles DynamoDB Table operations. func NewTableProvider(ctx *awsctx.Context) lumirpc.ResourceProviderServer { ops := &tableProvider{ctx} @@ -145,13 +156,11 @@ func (p *tableProvider) Check(ctx context.Context, obj *dynamodb.Table) ([]mappe // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). func (p *tableProvider) Create(ctx context.Context, obj *dynamodb.Table) (resource.ID, error) { // If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name. - // TODO: use the URN, not just the name, to enhance global uniqueness. - // TODO: even for explicit names, we should consider mangling it somehow, to reduce multi-instancing conflicts. - var id resource.ID + var name string if obj.TableName != nil { - id = resource.ID(*obj.TableName) + name = *obj.TableName } else { - id = resource.NewUniqueHexID(obj.Name+"-", maxTableName, sha1.Size) + name = resource.NewUniqueHex(*obj.Name+"-", maxTableName, sha1.Size) } var attributeDefinitions []*awsdynamodb.AttributeDefinition @@ -162,21 +171,21 @@ func (p *tableProvider) Create(ctx context.Context, obj *dynamodb.Table) (resour }) } - fmt.Printf("Creating DynamoDB Table '%v' with name '%v'\n", obj.Name, id) + fmt.Printf("Creating DynamoDB Table '%v' with name '%v'\n", obj.Name, name) keySchema := []*awsdynamodb.KeySchemaElement{ { AttributeName: aws.String(obj.HashKey), - KeyType: aws.String("HASH"), + KeyType: aws.String(hashKeyAttribute), }, } if obj.RangeKey != nil { keySchema = append(keySchema, &awsdynamodb.KeySchemaElement{ AttributeName: obj.RangeKey, - KeyType: aws.String("RANGE"), + KeyType: aws.String(rangeKeyAttribute), }) } create := &awsdynamodb.CreateTableInput{ - TableName: id.StringPtr(), + TableName: aws.String(name), AttributeDefinitions: attributeDefinitions, KeySchema: keySchema, ProvisionedThroughput: &awsdynamodb.ProvisionedThroughput{ @@ -190,13 +199,13 @@ func (p *tableProvider) Create(ctx context.Context, obj *dynamodb.Table) (resour keySchema := []*awsdynamodb.KeySchemaElement{ { AttributeName: aws.String(gsi.HashKey), - KeyType: aws.String("HASH"), + KeyType: aws.String(hashKeyAttribute), }, } if gsi.RangeKey != nil { keySchema = append(keySchema, &awsdynamodb.KeySchemaElement{ AttributeName: gsi.RangeKey, - KeyType: aws.String("RANGE"), + KeyType: aws.String(rangeKeyAttribute), }) } gsis = append(gsis, &awsdynamodb.GlobalSecondaryIndex{ @@ -216,21 +225,97 @@ func (p *tableProvider) Create(ctx context.Context, obj *dynamodb.Table) (resour } // Now go ahead and perform the action. - if _, err := p.ctx.DynamoDB().CreateTable(create); err != nil { + var arn string + if resp, err := p.ctx.DynamoDB().CreateTable(create); err != nil { return "", err + } else { + contract.Assert(resp != nil) + contract.Assert(resp.TableDescription != nil) + contract.Assert(resp.TableDescription.TableArn != nil) + arn = *resp.TableDescription.TableArn } // Wait for the table to be ready and then return the ID (just its name). - fmt.Printf("DynamoDB Table created: %v; waiting for it to become active\n", id) - if err := p.waitForTableState(id, true); err != nil { + fmt.Printf("DynamoDB Table created: %v; waiting for it to become active\n", name) + if err := p.waitForTableState(name, true); err != nil { return "", err } - return id, nil + return resource.ID(arn), nil } // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *tableProvider) Get(ctx context.Context, id resource.ID) (*dynamodb.Table, error) { - return nil, errors.New("Not yet implemented") + name, err := arn.ParseResourceName(id) + if err != nil { + if awsctx.IsAWSError(err, "ResourceNotFoundException") { + return nil, nil + } + return nil, err + } + resp, err := p.ctx.DynamoDB().DescribeTable(&awsdynamodb.DescribeTableInput{TableName: aws.String(name)}) + if err != nil { + return nil, err + } + + // The object was found, we need to reverse map a bunch of properties into the structure form. + contract.Assert(resp != nil) + contract.Assert(resp.Table != nil) + tab := resp.Table + + var attributes []dynamodb.Attribute + for _, attr := range tab.AttributeDefinitions { + attributes = append(attributes, dynamodb.Attribute{ + Name: *attr.AttributeName, + Type: dynamodb.AttributeType(*attr.AttributeType), + }) + } + + hashKey, rangeKey := getHashRangeKeys(tab.KeySchema) + + var gsis *[]dynamodb.GlobalSecondaryIndex + if len(tab.GlobalSecondaryIndexes) > 0 { + var gis []dynamodb.GlobalSecondaryIndex + for _, gsid := range tab.GlobalSecondaryIndexes { + hk, rk := getHashRangeKeys(gsid.KeySchema) + gis = append(gis, dynamodb.GlobalSecondaryIndex{ + IndexName: *gsid.IndexName, + HashKey: hk, + ReadCapacity: float64(*gsid.ProvisionedThroughput.ReadCapacityUnits), + WriteCapacity: float64(*gsid.ProvisionedThroughput.WriteCapacityUnits), + RangeKey: rk, + NonKeyAttributes: aws.StringValueSlice(gsid.Projection.NonKeyAttributes), + ProjectionType: dynamodb.ProjectionType(*gsid.Projection.ProjectionType), + }) + } + gsis = &gis + } + + return &dynamodb.Table{ + HashKey: hashKey, + Attributes: attributes, + ReadCapacity: float64(*tab.ProvisionedThroughput.ReadCapacityUnits), + WriteCapacity: float64(*tab.ProvisionedThroughput.WriteCapacityUnits), + RangeKey: rangeKey, + TableName: tab.TableName, + GlobalSecondaryIndexes: gsis, + }, nil +} + +func getHashRangeKeys(schema []*awsdynamodb.KeySchemaElement) (string, *string) { + var hashKey *string + var rangeKey *string + for _, elem := range schema { + switch *elem.KeyType { + case hashKeyAttribute: + hashKey = elem.AttributeName + case rangeKeyAttribute: + rangeKey = elem.AttributeName + default: + contract.Failf("Unexpected key schema attribute type: %v", *elem.KeyType) + } + } + contract.Assertf(hashKey != nil, "Expected to discover a hash partition key") + return *hashKey, rangeKey } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -243,6 +328,10 @@ func (p *tableProvider) InspectChange(ctx context.Context, id resource.ID, // to new values. The resource ID is returned and may be different if the resource had to be recreated. func (p *tableProvider) Update(ctx context.Context, id resource.ID, old *dynamodb.Table, new *dynamodb.Table, diff *resource.ObjectDiff) error { + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } // Note: Changing dynamodb.Table_Attributes alone does not trigger an update on the resource, it must be changed // along with using the new attributes in an index. The latter will process the update. @@ -261,15 +350,15 @@ func (p *tableProvider) Update(ctx context.Context, id resource.ID, // First modify provisioned throughput if needed. if diff.Changed(dynamodb.Table_ReadCapacity) || diff.Changed(dynamodb.Table_WriteCapacity) { - fmt.Printf("Updating provisioned capacity for DynamoDB Table %v\n", id.String()) + fmt.Printf("Updating provisioned capacity for DynamoDB Table %v\n", aws.String(name)) update := &awsdynamodb.UpdateTableInput{ - TableName: id.StringPtr(), + TableName: aws.String(name), ProvisionedThroughput: &awsdynamodb.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(int64(new.ReadCapacity)), WriteCapacityUnits: aws.Int64(int64(new.WriteCapacity)), }, } - if err := p.updateTable(id, update); err != nil { + if err := p.updateTable(name, update); err != nil { return err } } @@ -282,17 +371,17 @@ func (p *tableProvider) Update(ctx context.Context, id resource.ID, // First, add any new indexes for _, o := range d.Adds() { gsi := o.(globalSecondaryIndexHash).item - fmt.Printf("Adding new global secondary index %v for DynamoDB Table %v\n", gsi.IndexName, id.String()) + fmt.Printf("Adding new global secondary index %v for DynamoDB Table %v\n", gsi.IndexName, name) keySchema := []*awsdynamodb.KeySchemaElement{ { AttributeName: aws.String(gsi.HashKey), - KeyType: aws.String("HASH"), + KeyType: aws.String(hashKeyAttribute), }, } if gsi.RangeKey != nil { keySchema = append(keySchema, &awsdynamodb.KeySchemaElement{ AttributeName: gsi.RangeKey, - KeyType: aws.String("RANGE"), + KeyType: aws.String(rangeKeyAttribute), }) } var attributeDefinitions []*awsdynamodb.AttributeDefinition @@ -303,7 +392,7 @@ func (p *tableProvider) Update(ctx context.Context, id resource.ID, }) } update := &awsdynamodb.UpdateTableInput{ - TableName: aws.String(id.String()), + TableName: aws.String(name), AttributeDefinitions: attributeDefinitions, GlobalSecondaryIndexUpdates: []*awsdynamodb.GlobalSecondaryIndexUpdate{ { @@ -322,16 +411,16 @@ func (p *tableProvider) Update(ctx context.Context, id resource.ID, }, }, } - if err := p.updateTable(id, update); err != nil { + if err := p.updateTable(name, update); err != nil { return err } } // Next, modify provisioned throughput on any updated indexes for _, o := range d.Updates() { gsi := o.(globalSecondaryIndexHash).item - fmt.Printf("Updating capacity for global secondary index %v for DynamoDB Table %v\n", gsi.IndexName, id.String()) + fmt.Printf("Updating capacity for global secondary index %v for DynamoDB Table %v\n", gsi.IndexName, name) update := &awsdynamodb.UpdateTableInput{ - TableName: aws.String(id.String()), + TableName: aws.String(name), GlobalSecondaryIndexUpdates: []*awsdynamodb.GlobalSecondaryIndexUpdate{ { Update: &awsdynamodb.UpdateGlobalSecondaryIndexAction{ @@ -344,16 +433,16 @@ func (p *tableProvider) Update(ctx context.Context, id resource.ID, }, }, } - if err := p.updateTable(id, update); err != nil { + if err := p.updateTable(name, update); err != nil { return err } } // Finally, delete and removed indexes for _, o := range d.Deletes() { gsi := o.(globalSecondaryIndexHash).item - fmt.Printf("Deleting global secondary index %v for DynamoDB Table %v\n", gsi.IndexName, id.String()) + fmt.Printf("Deleting global secondary index %v for DynamoDB Table %v\n", gsi.IndexName, name) update := &awsdynamodb.UpdateTableInput{ - TableName: aws.String(id.String()), + TableName: aws.String(name), GlobalSecondaryIndexUpdates: []*awsdynamodb.GlobalSecondaryIndexUpdate{ { Delete: &awsdynamodb.DeleteGlobalSecondaryIndexAction{ @@ -362,61 +451,38 @@ func (p *tableProvider) Update(ctx context.Context, id resource.ID, }, }, } - if err := p.updateTable(id, update); err != nil { + if err := p.updateTable(name, update); err != nil { return err } } + + if err := p.waitForTableState(name, true); err != nil { + return err + } } return nil } // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *tableProvider) Delete(ctx context.Context, id resource.ID) error { + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } + // First, perform the deletion. - fmt.Printf("Deleting DynamoDB Table '%v'\n", id) + fmt.Printf("Deleting DynamoDB Table '%v'\n", name) succ, err := awsctx.RetryUntilLong( p.ctx, func() (bool, error) { _, err := p.ctx.DynamoDB().DeleteTable(&awsdynamodb.DeleteTableInput{ - TableName: id.StringPtr(), + TableName: aws.String(name), }) if err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "ResourceNotFoundException" { - return true, nil - } - if erraws.Code() == "ResourceInUseException" { - return false, nil - } - } - return false, err // anything other than "ResourceNotFoundException" is a real error; propagate it. - } - return true, nil - }, - ) - if err != nil { - return err - } - if !succ { - return fmt.Errorf("DynamoDB table '%v' could not be deleted", id) - } - - // Wait for the table to actually become deleted before returning. - fmt.Printf("DynamoDB Table delete request submitted; waiting for it to delete\n") - return p.waitForTableState(id, false) -} - -func (p *tableProvider) updateTable(id resource.ID, update *awsdynamodb.UpdateTableInput) error { - succ, err := awsctx.RetryUntil( - p.ctx, - func() (bool, error) { - _, err := p.ctx.DynamoDB().UpdateTable(update) - if err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "ResourceNotFoundException" || erraws.Code() == "ResourceInUseException" { - fmt.Printf("Waiting to update resource '%v': %v", id, erraws.Message()) - return false, nil - } + if awsctx.IsAWSError(err, awsdynamodb.ErrCodeResourceNotFoundException) { + return true, nil + } else if awsctx.IsAWSError(err, awsdynamodb.ErrCodeResourceInUseException) { + return false, nil } return false, err // anything else is a real error; propagate it. } @@ -427,28 +493,53 @@ func (p *tableProvider) updateTable(id resource.ID, update *awsdynamodb.UpdateTa return err } if !succ { - return fmt.Errorf("DynamoDB table '%v' could not be updated", id) + return fmt.Errorf("DynamoDB table '%v' could not be deleted", name) } - if err := p.waitForTableState(id, true); err != nil { + + // Wait for the table to actually become deleted before returning. + fmt.Printf("DynamoDB Table delete request submitted; waiting for it to delete\n") + return p.waitForTableState(name, false) +} + +func (p *tableProvider) updateTable(name string, update *awsdynamodb.UpdateTableInput) error { + succ, err := awsctx.RetryUntil( + p.ctx, + func() (bool, error) { + _, err := p.ctx.DynamoDB().UpdateTable(update) + if err != nil { + if awsctx.IsAWSError(err, "ResourceNotFoundException", "ResourceInUseException") { + fmt.Printf("Waiting to update resource '%v': %v", name, err.(awserr.Error).Message()) + return false, nil + } + return false, err // anything else is a real error; propagate it. + } + return true, nil + }, + ) + if err != nil { + return err + } + if !succ { + return fmt.Errorf("DynamoDB table '%v' could not be updated", name) + } + if err := p.waitForTableState(name, true); err != nil { return err } return nil } -func (p *tableProvider) waitForTableState(id resource.ID, exist bool) error { +func (p *tableProvider) waitForTableState(name string, exist bool) error { succ, err := awsctx.RetryUntilLong( p.ctx, func() (bool, error) { description, err := p.ctx.DynamoDB().DescribeTable(&awsdynamodb.DescribeTableInput{ - TableName: id.StringPtr(), + TableName: aws.String(name), }) if err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "ResourceNotFoundException" { - // The table is missing; if exist==false, we're good, otherwise keep retrying. - return !exist, nil - } + if awsctx.IsAWSError(err, "ResourceNotFoundException") { + // The table is missing; if exist==false, we're good, otherwise keep retrying. + return !exist, nil } return false, err // anything other than "ResourceNotFoundException" is a real error; propagate it. } @@ -471,7 +562,7 @@ func (p *tableProvider) waitForTableState(id resource.ID, exist bool) error { } else { reason = "deleted" } - return fmt.Errorf("DynamoDB table '%v' did not become %v", id, reason) + return fmt.Errorf("DynamoDB table '%v' did not become %v", name, reason) } return nil } diff --git a/lib/aws/provider/dynamodb/table_test.go b/lib/aws/provider/dynamodb/table_test.go index a0d5e3f4f..2bc809734 100644 --- a/lib/aws/provider/dynamodb/table_test.go +++ b/lib/aws/provider/dynamodb/table_test.go @@ -61,8 +61,9 @@ func Test(t *testing.T) { defer cleanup(ctx) // Table to create + tablename := TABLENAMEPREFIX table := dynamodb.Table{ - Name: TABLENAMEPREFIX, + Name: &tablename, Attributes: []dynamodb.Attribute{ {Name: "Album", Type: "S"}, {Name: "Artist", Type: "S"}, @@ -113,8 +114,9 @@ func Test(t *testing.T) { assert.Contains(t, id, "lumitest", "expected resource ID to contain `lumitest`") // Table for update + tablename2 := "lumitest" table2 := dynamodb.Table{ - Name: "lumitest", + Name: &tablename2, Attributes: []dynamodb.Attribute{ {Name: "Album", Type: "S"}, {Name: "Artist", Type: "S"}, @@ -160,7 +162,7 @@ func Test(t *testing.T) { assert.Equal(t, 0, len(checkResp.Failures), "expected no check failures") // Invoke Update request - _, err = tableProvider.Update(nil, &lumirpc.ChangeRequest{ + _, err = tableProvider.Update(nil, &lumirpc.UpdateRequest{ Type: string(TableToken), Id: id, Olds: props, diff --git a/lib/aws/provider/ec2/instance.go b/lib/aws/provider/ec2/instance.go index 0b21ef076..eb5757b06 100644 --- a/lib/aws/provider/ec2/instance.go +++ b/lib/aws/provider/ec2/instance.go @@ -29,6 +29,7 @@ import ( "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" "github.com/pulumi/lumi/lib/aws/rpc/ec2" ) @@ -71,12 +72,16 @@ func (p *instanceProvider) Check(ctx context.Context, obj *ec2.Instance) ([]mapp // Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). -func (p *instanceProvider) Create(ctx context.Context, obj *ec2.Instance) (resource.ID, *ec2.InstanceOuts, error) { +func (p *instanceProvider) Create(ctx context.Context, obj *ec2.Instance) (resource.ID, error) { // Create the create instances request object. var secgrpIDs []*string if obj.SecurityGroups != nil { - for _, id := range *obj.SecurityGroups { - secgrpIDs = append(secgrpIDs, id.StringPtr()) + for _, secgrpARN := range *obj.SecurityGroups { + secgrpID, err := arn.ParseResourceName(secgrpARN) + if err != nil { + return "", err + } + secgrpIDs = append(secgrpIDs, aws.String(secgrpID)) } } var instanceType *string @@ -97,35 +102,68 @@ func (p *instanceProvider) Create(ctx context.Context, obj *ec2.Instance) (resou fmt.Fprintf(os.Stdout, "Creating new EC2 instance resource\n") result, err := p.ctx.EC2().RunInstances(create) if err != nil { - return "", nil, err + return "", err } + + // Get the unique ID from the created instance. contract.Assert(result != nil) contract.Assert(len(result.Instances) == 1) inst := result.Instances[0] contract.Assert(inst != nil) - id := inst.InstanceId contract.Assert(inst.InstanceId != nil) + id := *inst.InstanceId // Before returning that all is okay, wait for the instance to reach the running state. - fmt.Fprintf(os.Stdout, "EC2 instance '%v' created; now waiting for it to become 'running'\n", *id) + fmt.Fprintf(os.Stdout, "EC2 instance '%v' created; now waiting for it to become 'running'\n", id) // TODO: if this fails, but the creation succeeded, we will have an orphaned resource; report this differently. if err = p.ctx.EC2().WaitUntilInstanceRunning( - &awsec2.DescribeInstancesInput{InstanceIds: []*string{id}}); err != nil { - return "", nil, err + &awsec2.DescribeInstancesInput{InstanceIds: []*string{aws.String(id)}}); err != nil { + return "", err } - // Fetch the availability zone for the instance. - status, err := p.ctx.EC2().DescribeInstanceStatus( - &awsec2.DescribeInstanceStatusInput{InstanceIds: []*string{id}}) + return arn.NewEC2InstanceID(p.ctx.Region(), p.ctx.AccountID(), id), nil +} + +// Get reads the instance state identified by ID, returning a populated resource object, or an nil if not found. +func (p *instanceProvider) Get(ctx context.Context, id resource.ID) (*ec2.Instance, error) { + idarn, err := arn.ARN(id).Parse() if err != nil { - return "", nil, err + return nil, err + } + iid := idarn.ResourceName() + resp, err := p.ctx.EC2().DescribeInstances( + &awsec2.DescribeInstancesInput{InstanceIds: []*string{aws.String(iid)}}) + if err != nil { + if awsctx.IsAWSError(err, "InvalidInstanceID.NotFound") { + return nil, nil + } + return nil, err + } else if resp == nil || len(resp.Reservations) == 0 { + return nil, nil } - contract.Assert(status != nil) - contract.Assert(len(status.InstanceStatuses) == 1) - // Manufacture the output properties structure. - return resource.ID(*id), &ec2.InstanceOuts{ - AvailabilityZone: *status.InstanceStatuses[0].AvailabilityZone, + // If we are here, we know that there is a reservation that matched; read its fields and populate the object. + contract.Assert(len(resp.Reservations) == 1) + resv := resp.Reservations[0] + contract.Assert(len(resp.Reservations[0].Instances) == 1) + inst := resv.Instances[0] + + var secgrpIDs *[]resource.ID + if len(inst.SecurityGroups) > 0 { + var ids []resource.ID + for _, group := range inst.SecurityGroups { + ids = append(ids, arn.NewEC2SecurityGroupID(idarn.Region, idarn.AccountID, *group.GroupId)) + } + secgrpIDs = &ids + } + + instanceType := ec2.InstanceType(*inst.InstanceType) + return &ec2.Instance{ + ImageID: *inst.ImageId, + InstanceType: &instanceType, + SecurityGroups: secgrpIDs, + KeyName: inst.KeyName, + AvailabilityZone: *inst.Placement.AvailabilityZone, PrivateDNSName: inst.PrivateDnsName, PublicDNSName: inst.PublicDnsName, PrivateIP: inst.PrivateIpAddress, @@ -133,11 +171,6 @@ func (p *instanceProvider) Create(ctx context.Context, obj *ec2.Instance) (resou }, nil } -// Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. -func (p *instanceProvider) Get(ctx context.Context, id resource.ID) (*ec2.Instance, error) { - return nil, errors.New("Not yet implemented") -} - // InspectChange checks what impacts a hypothetical update will have on the resource's properties. func (p *instanceProvider) InspectChange(ctx context.Context, id resource.ID, old *ec2.Instance, new *ec2.Instance, diff *resource.ObjectDiff) ([]string, error) { @@ -154,8 +187,11 @@ func (p *instanceProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *instanceProvider) Delete(ctx context.Context, id resource.ID) error { - delete := &awsec2.TerminateInstancesInput{InstanceIds: []*string{id.StringPtr()}} - + iid, err := arn.ParseResourceName(id) + if err != nil { + return err + } + delete := &awsec2.TerminateInstancesInput{InstanceIds: []*string{aws.String(iid)}} fmt.Fprintf(os.Stdout, "Terminating EC2 instance '%v'\n", id) if _, err := p.ctx.EC2().TerminateInstances(delete); err != nil { return err @@ -163,5 +199,5 @@ func (p *instanceProvider) Delete(ctx context.Context, id resource.ID) error { fmt.Fprintf(os.Stdout, "EC2 instance termination request submitted; waiting for it to terminate\n") return p.ctx.EC2().WaitUntilInstanceTerminated( - &awsec2.DescribeInstancesInput{InstanceIds: []*string{id.StringPtr()}}) + &awsec2.DescribeInstancesInput{InstanceIds: []*string{aws.String(iid)}}) } diff --git a/lib/aws/provider/ec2/security_group.go b/lib/aws/provider/ec2/security_group.go index 944573f7d..038ce305e 100644 --- a/lib/aws/provider/ec2/security_group.go +++ b/lib/aws/provider/ec2/security_group.go @@ -17,19 +17,19 @@ package ec2 import ( "crypto/sha1" - "errors" "fmt" "reflect" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" awsec2 "github.com/aws/aws-sdk-go/service/ec2" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/util/contract" + "github.com/pulumi/lumi/pkg/util/convutil" "github.com/pulumi/lumi/pkg/util/mapper" "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" "github.com/pulumi/lumi/lib/aws/rpc/ec2" ) @@ -65,38 +65,44 @@ func (p *sgProvider) Check(ctx context.Context, obj *ec2.SecurityGroup) ([]mappe // Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). -func (p *sgProvider) Create(ctx context.Context, obj *ec2.SecurityGroup) (resource.ID, *ec2.SecurityGroupOuts, error) { - // Make the security group creation parameters. The name of the group is auto-generated using a random hash so - // that we can avoid conflicts with existing similarly named groups. For readability, we prefix the real name. - name := resource.NewUniqueHex(obj.Name+"-", maxSecurityGroupName, sha1.Size) +func (p *sgProvider) Create(ctx context.Context, obj *ec2.SecurityGroup) (resource.ID, error) { + // Make the security group creation parameters. If the developer specified a name, we will honor it, although we + // prefer to auto-generate it from the Lumi resource name, suffixed with a hash, to avoid collisions. + var name string + if obj.GroupName == nil { + name = resource.NewUniqueHex(*obj.Name+"-", maxSecurityGroupName, sha1.Size) + } else { + name = *obj.GroupName + } + var vpcID *string + if obj.VPC != nil { + vpc, err := arn.ParseResourceName(*obj.VPC) + if err != nil { + return "", err + } + vpcID = &vpc + } + fmt.Printf("Creating EC2 security group with name '%v'\n", name) create := &awsec2.CreateSecurityGroupInput{ GroupName: aws.String(name), Description: &obj.GroupDescription, - VpcId: obj.VPC.StringPtr(), + VpcId: vpcID, } // Now go ahead and perform the action. result, err := p.ctx.EC2().CreateSecurityGroup(create) if err != nil { - return "", nil, err + return "", err } contract.Assert(result != nil) contract.Assert(result.GroupId != nil) - out := &ec2.SecurityGroupOuts{GroupID: *result.GroupId} - - // For security groups in a default VPC, we return the ID; otherwise, the name. - var id resource.ID - if obj.VPC == nil { - id = resource.ID(out.GroupID) - } else { - id = resource.ID(name) - } - fmt.Printf("EC2 security group created: %v; waiting for it to become active\n", id) + id := *result.GroupId // Don't proceed until the security group exists. + fmt.Printf("EC2 security group created: %v; waiting for it to become active\n", id) if err = p.waitForSecurityGroupState(id, true); err != nil { - return "", nil, err + return "", err } // Authorize ingress rules if any exist. @@ -104,7 +110,7 @@ func (p *sgProvider) Create(ctx context.Context, obj *ec2.SecurityGroup) (resour fmt.Printf("Authorizing %v security group ingress (inbound) rules\n", len(*ingress)) for _, rule := range *ingress { if err := p.createSecurityGroupIngressRule(id, rule); err != nil { - return "", nil, err + return "", err } } } @@ -114,17 +120,78 @@ func (p *sgProvider) Create(ctx context.Context, obj *ec2.SecurityGroup) (resour fmt.Printf("Authorizing %v security group egress (outbound) rules\n", len(*egress)) for _, rule := range *egress { if err := p.createSecurityGroupEgressRule(id, rule); err != nil { - return "", nil, err + return "", err } } } - return id, out, nil + return arn.NewEC2SecurityGroupID(p.ctx.Region(), p.ctx.AccountID(), id), nil +} + +func createSecurityGroupRulesFromIPPermissions(perms []*awsec2.IpPermission) *[]ec2.SecurityGroupRule { + var ret *[]ec2.SecurityGroupRule + if len(perms) > 0 { + var rules []ec2.SecurityGroupRule + for _, perm := range perms { + rule := ec2.SecurityGroupRule{ + IPProtocol: *perm.IpProtocol, + FromPort: convutil.Int64PToFloat64P(perm.FromPort), + ToPort: convutil.Int64PToFloat64P(perm.FromPort), + } + + // Although each unique entry is authorized individually, describe groups them together. We must ungroup + // them here in order for the output and input sets to match (i.e., one entry per IP address). + contract.Assertf(len(perm.Ipv6Ranges) == 0, "IPv6 ranges not yet supported") + if len(perm.IpRanges) > 0 { + for _, rang := range perm.IpRanges { + rule.CIDRIP = rang.CidrIp + rules = append(rules, rule) + } + } else { + rules = append(rules, rule) + } + } + ret = &rules + } + return ret } // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *sgProvider) Get(ctx context.Context, id resource.ID) (*ec2.SecurityGroup, error) { - return nil, errors.New("Not yet implemented") + gid, err := arn.ParseResourceName(id) + if err != nil { + return nil, err + } + resp, err := p.ctx.EC2().DescribeSecurityGroups(&awsec2.DescribeSecurityGroupsInput{ + GroupIds: []*string{aws.String(gid)}, + }) + if err != nil { + if awsctx.IsAWSError(err, "InvalidSecurityGroupID.NotFound") { + return nil, nil + } + return nil, err + } else if resp == nil || len(resp.SecurityGroups) == 0 { + return nil, nil + } + + // If we found one, fetch all the requisite properties and store them on the output. + contract.Assert(len(resp.SecurityGroups) == 1) + grp := resp.SecurityGroups[0] + + var vpcID *resource.ID + if grp.VpcId != nil { + vpc := arn.NewEC2VPCID(p.ctx.Region(), p.ctx.AccountID(), *grp.VpcId) + vpcID = &vpc + } + + return &ec2.SecurityGroup{ + GroupID: *grp.GroupId, + GroupName: grp.GroupName, + GroupDescription: *grp.Description, + VPC: vpcID, + SecurityGroupEgress: createSecurityGroupRulesFromIPPermissions(grp.IpPermissionsEgress), + SecurityGroupIngress: createSecurityGroupRulesFromIPPermissions(grp.IpPermissions), + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -137,13 +204,18 @@ func (p *sgProvider) InspectChange(ctx context.Context, id resource.ID, // to new values. The resource ID is returned and may be different if the resource had to be recreated. func (p *sgProvider) Update(ctx context.Context, id resource.ID, old *ec2.SecurityGroup, new *ec2.SecurityGroup, diff *resource.ObjectDiff) error { + gid, err := arn.ParseResourceName(id) + if err != nil { + return err + } + // If only the ingress and/or egress rules changed, we can incrementally apply the updates. gresses := []struct { key resource.PropertyKey olds *[]ec2.SecurityGroupRule news *[]ec2.SecurityGroupRule - create func(resource.ID, ec2.SecurityGroupRule) error - delete func(resource.ID, ec2.SecurityGroupRule) error + create func(string, ec2.SecurityGroupRule) error + delete func(string, ec2.SecurityGroupRule) error }{ { ec2.SecurityGroup_SecurityGroupIngress, @@ -213,12 +285,12 @@ func (p *sgProvider) Update(ctx context.Context, id resource.ID, // And now actually perform the create and delete operations. for _, delete := range deletes { - if err := p.deleteSecurityGroupIngressRule(id, delete); err != nil { + if err := p.deleteSecurityGroupIngressRule(gid, delete); err != nil { return err } } for _, create := range creates { - if err := p.createSecurityGroupIngressRule(id, create); err != nil { + if err := p.createSecurityGroupIngressRule(gid, create); err != nil { return err } } @@ -230,9 +302,14 @@ func (p *sgProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *sgProvider) Delete(ctx context.Context, id resource.ID) error { + gid, err := arn.ParseResourceName(id) + if err != nil { + return err + } + // First, perform the deletion. fmt.Printf("Terminating EC2 SecurityGroup '%v'\n", id) - delete := &awsec2.DeleteSecurityGroupInput{GroupId: id.StringPtr()} + delete := &awsec2.DeleteSecurityGroupInput{GroupId: aws.String(gid)} if _, err := p.ctx.EC2().DeleteSecurityGroup(delete); err != nil { return err } @@ -240,7 +317,7 @@ func (p *sgProvider) Delete(ctx context.Context, id resource.ID) error { fmt.Printf("EC2 Security Group delete request submitted; waiting for it to terminate\n") // Don't finish the operation until the security group exists. - return p.waitForSecurityGroupState(id, false) + return p.waitForSecurityGroupState(gid, false) } func (p *sgProvider) crudSecurityGroupRule(prefix, kind string, rule ec2.SecurityGroupRule, @@ -250,28 +327,24 @@ func (p *sgProvider) crudSecurityGroupRule(prefix, kind string, rule ec2.Securit if rule.CIDRIP != nil { fmt.Printf(", CIDRIP=%v", *rule.CIDRIP) } - var from *int64 - if rule.FromPort != nil { - fromPort := int64(*rule.FromPort) - fmt.Printf(", FromPort=%v", fromPort) - from = &fromPort + fromPort := convutil.Float64PToInt64P(rule.FromPort) + if fromPort != nil { + fmt.Printf(", FromPort=%v", *fromPort) } - var to *int64 - if rule.ToPort != nil { - toPort := int64(*rule.ToPort) - fmt.Printf(", ToPort=%v", toPort) - to = &toPort + toPort := convutil.Float64PToInt64P(rule.ToPort) + if toPort != nil { + fmt.Printf(", ToPort=%v", *toPort) } fmt.Printf("\n") // Now perform the action and return its error (or nil) as our result. - return action(from, to) + return action(fromPort, toPort) } -func (p *sgProvider) createSecurityGroupIngressRule(groupID resource.ID, rule ec2.SecurityGroupRule) error { +func (p *sgProvider) createSecurityGroupIngressRule(groupID string, rule ec2.SecurityGroupRule) error { return p.crudSecurityGroupRule("Authorizing", "ingress (inbound)", rule, func(from *int64, to *int64) error { _, err := p.ctx.EC2().AuthorizeSecurityGroupIngress(&awsec2.AuthorizeSecurityGroupIngressInput{ - GroupId: groupID.StringPtr(), + GroupId: aws.String(groupID), IpProtocol: aws.String(rule.IPProtocol), CidrIp: rule.CIDRIP, FromPort: from, @@ -281,10 +354,10 @@ func (p *sgProvider) createSecurityGroupIngressRule(groupID resource.ID, rule ec }) } -func (p *sgProvider) deleteSecurityGroupIngressRule(groupID resource.ID, rule ec2.SecurityGroupRule) error { +func (p *sgProvider) deleteSecurityGroupIngressRule(groupID string, rule ec2.SecurityGroupRule) error { return p.crudSecurityGroupRule("Revoking", "ingress (inbound)", rule, func(from *int64, to *int64) error { _, err := p.ctx.EC2().RevokeSecurityGroupIngress(&awsec2.RevokeSecurityGroupIngressInput{ - GroupId: groupID.StringPtr(), + GroupId: aws.String(groupID), IpProtocol: aws.String(rule.IPProtocol), CidrIp: rule.CIDRIP, FromPort: from, @@ -294,10 +367,10 @@ func (p *sgProvider) deleteSecurityGroupIngressRule(groupID resource.ID, rule ec }) } -func (p *sgProvider) createSecurityGroupEgressRule(groupID resource.ID, rule ec2.SecurityGroupRule) error { +func (p *sgProvider) createSecurityGroupEgressRule(groupID string, rule ec2.SecurityGroupRule) error { return p.crudSecurityGroupRule("Authorizing", "egress (outbound)", rule, func(from *int64, to *int64) error { _, err := p.ctx.EC2().AuthorizeSecurityGroupEgress(&awsec2.AuthorizeSecurityGroupEgressInput{ - GroupId: groupID.StringPtr(), + GroupId: aws.String(groupID), IpProtocol: aws.String(rule.IPProtocol), CidrIp: rule.CIDRIP, FromPort: from, @@ -307,10 +380,10 @@ func (p *sgProvider) createSecurityGroupEgressRule(groupID resource.ID, rule ec2 }) } -func (p *sgProvider) deleteSecurityGroupEgressRule(groupID resource.ID, rule ec2.SecurityGroupRule) error { +func (p *sgProvider) deleteSecurityGroupEgressRule(groupID string, rule ec2.SecurityGroupRule) error { return p.crudSecurityGroupRule("Revoking", "egress (outbound)", rule, func(from *int64, to *int64) error { _, err := p.ctx.EC2().RevokeSecurityGroupEgress(&awsec2.RevokeSecurityGroupEgressInput{ - GroupId: groupID.StringPtr(), + GroupId: aws.String(groupID), IpProtocol: aws.String(rule.IPProtocol), CidrIp: rule.CIDRIP, FromPort: from, @@ -320,11 +393,11 @@ func (p *sgProvider) deleteSecurityGroupEgressRule(groupID resource.ID, rule ec2 }) } -func (p *sgProvider) waitForSecurityGroupState(id resource.ID, exist bool) error { +func (p *sgProvider) waitForSecurityGroupState(id string, exist bool) error { succ, err := awsctx.RetryUntil( p.ctx, func() (bool, error) { - req := &awsec2.DescribeSecurityGroupsInput{GroupIds: []*string{id.StringPtr()}} + req := &awsec2.DescribeSecurityGroupsInput{GroupIds: []*string{aws.String(id)}} missing := true res, err := p.ctx.EC2().DescribeSecurityGroups(req) if err != nil { @@ -361,17 +434,12 @@ func (p *sgProvider) waitForSecurityGroupState(id resource.ID, exist bool) error } func isSecurityGroupNotExistErr(err error) bool { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "InvalidGroup.NotFound" { - // The specified security group does not eixst; this error can occur because the ID of a recently created - // security group has not propagated through the system. - return true - } - if erraws.Code() == "InvalidSecurityGroupID.NotFound" { - // The specified security group does not exist; if you are creating a network interface, ensure that - // you specify a VPC security group, and not an EC2-Classic security group. - return true - } - } - return false + return awsctx.IsAWSError(err, + // The specified security group does not eixst; this error can occur because the ID of a recently created + // security group has not propagated through the system. + "InvalidGroup.NotFound", + // The specified security group does not exist; if you are creating a network interface, ensure that + // you specify a VPC security group, and not an EC2-Classic security group. + "InvalidSecurityGroupID.NotFound", + ) } diff --git a/lib/aws/provider/elasticbeanstalk/application.go b/lib/aws/provider/elasticbeanstalk/application.go index 07589ee8a..637ebfb73 100644 --- a/lib/aws/provider/elasticbeanstalk/application.go +++ b/lib/aws/provider/elasticbeanstalk/application.go @@ -22,12 +22,13 @@ import ( "github.com/aws/aws-sdk-go/aws" awselasticbeanstalk "github.com/aws/aws-sdk-go/service/elasticbeanstalk" - "github.com/pkg/errors" "github.com/pulumi/lumi/pkg/resource" + "github.com/pulumi/lumi/pkg/util/contract" "github.com/pulumi/lumi/pkg/util/mapper" "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" "github.com/pulumi/lumi/lib/aws/rpc/elasticbeanstalk" ) @@ -80,13 +81,11 @@ func (p *applicationProvider) Check(ctx context.Context, obj *elasticbeanstalk.A // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). func (p *applicationProvider) Create(ctx context.Context, obj *elasticbeanstalk.Application) (resource.ID, error) { // If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name. - // TODO: use the URN, not just the name, to enhance global uniqueness. - // TODO: even for explicit names, we should consider mangling it somehow, to reduce multi-instancing conflicts. var name string if obj.ApplicationName != nil { name = *obj.ApplicationName } else { - name = resource.NewUniqueHex(obj.Name+"-", maxApplicationName, sha1.Size) + name = resource.NewUniqueHex(*obj.Name+"-", maxApplicationName, sha1.Size) } fmt.Printf("Creating ElasticBeanstalk Application '%v' with name '%v'\n", obj.Name, name) create := &awselasticbeanstalk.CreateApplicationInput{ @@ -97,12 +96,30 @@ func (p *applicationProvider) Create(ctx context.Context, obj *elasticbeanstalk. if err != nil { return "", err } - return resource.ID(name), nil + return arn.NewElasticBeanstalkApplicationID(p.ctx.Region(), p.ctx.AccountID(), name), nil } // Read reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *applicationProvider) Get(ctx context.Context, id resource.ID) (*elasticbeanstalk.Application, error) { - return nil, errors.New("Not yet implemented") + name, err := arn.ParseResourceName(id) + if err != nil { + return nil, err + } + resp, err := p.ctx.ElasticBeanstalk().DescribeApplications(&awselasticbeanstalk.DescribeApplicationsInput{ + ApplicationNames: []*string{aws.String(name)}, + }) + if err != nil { + return nil, err + } else if len(resp.Applications) == 0 { + return nil, nil + } + contract.Assert(len(resp.Applications) == 1) + app := resp.Applications[0] + contract.Assert(*app.ApplicationName == name) + return &elasticbeanstalk.Application{ + ApplicationName: app.ApplicationName, + Description: app.Description, + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -115,6 +132,10 @@ func (p *applicationProvider) InspectChange(ctx context.Context, id resource.ID, // to new values. The resource ID is returned and may be different if the resource had to be recreated. func (p *applicationProvider) Update(ctx context.Context, id resource.ID, old *elasticbeanstalk.Application, new *elasticbeanstalk.Application, diff *resource.ObjectDiff) error { + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } if new.Description != old.Description { description := new.Description if description == nil { @@ -123,7 +144,7 @@ func (p *applicationProvider) Update(ctx context.Context, id resource.ID, description = aws.String("") } _, err := p.ctx.ElasticBeanstalk().UpdateApplication(&awselasticbeanstalk.UpdateApplicationInput{ - ApplicationName: id.StringPtr(), + ApplicationName: aws.String(name), Description: description, }) return err @@ -134,13 +155,17 @@ func (p *applicationProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *applicationProvider) Delete(ctx context.Context, id resource.ID) error { fmt.Printf("Deleting ElasticBeanstalk Application '%v'\n", id) + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } if _, err := p.ctx.ElasticBeanstalk().DeleteApplication(&awselasticbeanstalk.DeleteApplicationInput{ - ApplicationName: id.StringPtr(), + ApplicationName: aws.String(name), }); err != nil { return err } succ, err := awsctx.RetryUntilLong(p.ctx, func() (bool, error) { - resp, err := p.getApplication(id.StringPtr()) + resp, err := p.getApplication(name) if err != nil { return false, err } @@ -158,9 +183,9 @@ func (p *applicationProvider) Delete(ctx context.Context, id resource.ID) error return nil } -func (p *applicationProvider) getApplication(name *string) (*awselasticbeanstalk.ApplicationDescription, error) { +func (p *applicationProvider) getApplication(name string) (*awselasticbeanstalk.ApplicationDescription, error) { resp, err := p.ctx.ElasticBeanstalk().DescribeApplications(&awselasticbeanstalk.DescribeApplicationsInput{ - ApplicationNames: []*string{name}, + ApplicationNames: []*string{aws.String(name)}, }) if err != nil { return nil, err diff --git a/lib/aws/provider/elasticbeanstalk/applicationVersion.go b/lib/aws/provider/elasticbeanstalk/applicationVersion.go index a6b8be767..858f93c47 100644 --- a/lib/aws/provider/elasticbeanstalk/applicationVersion.go +++ b/lib/aws/provider/elasticbeanstalk/applicationVersion.go @@ -22,15 +22,13 @@ import ( "github.com/aws/aws-sdk-go/aws" awselasticbeanstalk "github.com/aws/aws-sdk-go/service/elasticbeanstalk" - "github.com/pkg/errors" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/util/contract" "github.com/pulumi/lumi/pkg/util/mapper" "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" - "strings" - + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" "github.com/pulumi/lumi/lib/aws/rpc/elasticbeanstalk" ) @@ -48,7 +46,8 @@ type applicationVersionProvider struct { } // Check validates that the given property bag is valid for a resource of the given type. -func (p *applicationVersionProvider) Check(ctx context.Context, obj *elasticbeanstalk.ApplicationVersion) ([]mapper.FieldError, error) { +func (p *applicationVersionProvider) Check(ctx context.Context, + obj *elasticbeanstalk.ApplicationVersion) ([]mapper.FieldError, error) { var failures []mapper.FieldError if description := obj.Description; description != nil { if len(*description) > maxDescription { @@ -63,56 +62,103 @@ func (p *applicationVersionProvider) Check(ctx context.Context, obj *elasticbean // Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). -func (p *applicationVersionProvider) Create(ctx context.Context, obj *elasticbeanstalk.ApplicationVersion) (resource.ID, error) { - // TODO: use the URN, not just the name, to enhance global uniqueness. - versionLabel := resource.NewUniqueHex(obj.Name+"-", maxApplicationName, sha1.Size) - - s3ObjectID := obj.SourceBundle.String() - s3Parts := strings.SplitN(s3ObjectID, "/", 2) - contract.Assertf(len(s3Parts) == 2, "Expected S3 Object resource ID to be of the form /") - create := &awselasticbeanstalk.CreateApplicationVersionInput{ - ApplicationName: obj.Application.StringPtr(), - Description: obj.Description, - SourceBundle: &awselasticbeanstalk.S3Location{ - S3Bucket: aws.String(s3Parts[0]), - S3Key: aws.String(s3Parts[1]), - }, - VersionLabel: aws.String(versionLabel), - } - fmt.Printf("Creating ElasticBeanstalk ApplicationVersion '%v' with version label '%v'\n", obj.Name, versionLabel) - _, err := p.ctx.ElasticBeanstalk().CreateApplicationVersion(create) +func (p *applicationVersionProvider) Create(ctx context.Context, + obj *elasticbeanstalk.ApplicationVersion) (resource.ID, error) { + appname, err := arn.ParseResourceName(obj.Application) if err != nil { return "", err } - return resource.ID(versionLabel), nil + + // Autogenerate a version label that is unique. + var versionLabel string + if obj.VersionLabel != nil { + versionLabel = *obj.VersionLabel + } else { + versionLabel = resource.NewUniqueHex(*obj.Name+"-", maxApplicationName, sha1.Size) + } + + // Parse out the S3 bucket and key components so we can create the source bundle. + s3buck, s3key, err := arn.ParseResourceNamePair(obj.SourceBundle) + if err != nil { + return "", err + } + + fmt.Printf("Creating ElasticBeanstalk ApplicationVersion '%v' with version label '%v'\n", obj.Name, versionLabel) + if _, err := p.ctx.ElasticBeanstalk().CreateApplicationVersion( + &awselasticbeanstalk.CreateApplicationVersionInput{ + ApplicationName: aws.String(appname), + Description: obj.Description, + SourceBundle: &awselasticbeanstalk.S3Location{ + S3Bucket: aws.String(s3buck), + S3Key: aws.String(s3key), + }, + VersionLabel: aws.String(versionLabel), + }, + ); err != nil { + return "", err + } + + return arn.NewElasticBeanstalkApplicationVersionID(p.ctx.Region(), p.ctx.AccountID(), appname, versionLabel), nil } // Read reads the instance state identified by ID, returning a populated resource object, or an error if not found. -func (p *applicationVersionProvider) Get(ctx context.Context, id resource.ID) (*elasticbeanstalk.ApplicationVersion, error) { - // TODO: Can almost just use p.getApplicationVersion to implement this, but there is no way to get the `resource.ID` - // for the SourceBundle S3 object returned from the AWS API. - return nil, errors.New("Not yet implemented") +func (p *applicationVersionProvider) Get(ctx context.Context, + id resource.ID) (*elasticbeanstalk.ApplicationVersion, error) { + idarn, err := arn.ARN(id).Parse() + if err != nil { + return nil, err + } + appname, version := idarn.ResourceNamePair() + resp, err := p.ctx.ElasticBeanstalk().DescribeApplicationVersions( + &awselasticbeanstalk.DescribeApplicationVersionsInput{ + ApplicationName: aws.String(appname), + VersionLabels: []*string{aws.String(version)}, + }, + ) + if err != nil { + return nil, err + } else if len(resp.ApplicationVersions) == 0 { + return nil, nil + } + contract.Assert(len(resp.ApplicationVersions) == 1) + vers := resp.ApplicationVersions[0] + contract.Assert(*vers.ApplicationName == appname) + appid := arn.NewElasticBeanstalkApplication(idarn.Region, idarn.AccountID, appname) + contract.Assert(*vers.VersionLabel == version) + + return &elasticbeanstalk.ApplicationVersion{ + VersionLabel: vers.VersionLabel, + Application: resource.ID(appid), + Description: vers.Description, + SourceBundle: arn.NewS3ObjectID(*vers.SourceBundle.S3Bucket, *vers.SourceBundle.S3Key), + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. func (p *applicationVersionProvider) InspectChange(ctx context.Context, id resource.ID, - old *elasticbeanstalk.ApplicationVersion, new *elasticbeanstalk.ApplicationVersion, diff *resource.ObjectDiff) ([]string, error) { + old *elasticbeanstalk.ApplicationVersion, new *elasticbeanstalk.ApplicationVersion, + diff *resource.ObjectDiff) ([]string, error) { return nil, nil } // Update updates an existing resource with new values. Only those values in the provided property bag are updated // to new values. The resource ID is returned and may be different if the resource had to be recreated. func (p *applicationVersionProvider) Update(ctx context.Context, id resource.ID, - old *elasticbeanstalk.ApplicationVersion, new *elasticbeanstalk.ApplicationVersion, diff *resource.ObjectDiff) error { + old *elasticbeanstalk.ApplicationVersion, new *elasticbeanstalk.ApplicationVersion, + diff *resource.ObjectDiff) error { + appname, version, err := arn.ParseResourceNamePair(id) + if err != nil { + return err + } if new.Description != old.Description { description := new.Description if description == nil { description = aws.String("") } _, err := p.ctx.ElasticBeanstalk().UpdateApplicationVersion(&awselasticbeanstalk.UpdateApplicationVersionInput{ - ApplicationName: new.Application.StringPtr(), + ApplicationName: aws.String(appname), Description: description, - VersionLabel: id.StringPtr(), + VersionLabel: aws.String(version), }) return err } @@ -121,31 +167,16 @@ func (p *applicationVersionProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *applicationVersionProvider) Delete(ctx context.Context, id resource.ID) error { - applicationVersion, err := p.getApplicationVersion(id) + appname, version, err := arn.ParseResourceNamePair(id) if err != nil { return err } fmt.Printf("Deleting ElasticBeanstalk ApplicationVersion '%v'\n", id) - _, err = p.ctx.ElasticBeanstalk().DeleteApplicationVersion(&awselasticbeanstalk.DeleteApplicationVersionInput{ - ApplicationName: applicationVersion.ApplicationName, - VersionLabel: id.StringPtr(), - }) + _, err = p.ctx.ElasticBeanstalk().DeleteApplicationVersion( + &awselasticbeanstalk.DeleteApplicationVersionInput{ + ApplicationName: aws.String(appname), + VersionLabel: aws.String(version), + }, + ) return err } - -func (p *applicationVersionProvider) getApplicationVersion(id resource.ID) (*awselasticbeanstalk.ApplicationVersionDescription, error) { - resp, err := p.ctx.ElasticBeanstalk().DescribeApplicationVersions(&awselasticbeanstalk.DescribeApplicationVersionsInput{ - VersionLabels: []*string{id.StringPtr()}, - }) - if err != nil { - return nil, err - } - applicationVersions := resp.ApplicationVersions - if len(applicationVersions) > 1 { - return nil, fmt.Errorf("More than one application version found with version label %v", id.String()) - } else if len(applicationVersions) == 0 { - return nil, fmt.Errorf("No application version found with version label %v", id.String()) - } - applicationVersion := applicationVersions[0] - return applicationVersion, nil -} diff --git a/lib/aws/provider/elasticbeanstalk/environment.go b/lib/aws/provider/elasticbeanstalk/environment.go index 8a6d82802..ea3c4d0c5 100644 --- a/lib/aws/provider/elasticbeanstalk/environment.go +++ b/lib/aws/provider/elasticbeanstalk/environment.go @@ -21,12 +21,13 @@ import ( "github.com/aws/aws-sdk-go/aws" awselasticbeanstalk "github.com/aws/aws-sdk-go/service/elasticbeanstalk" - "github.com/pkg/errors" "github.com/pulumi/lumi/pkg/resource" + "github.com/pulumi/lumi/pkg/util/contract" "github.com/pulumi/lumi/pkg/util/mapper" "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" "github.com/pulumi/lumi/lib/aws/rpc/elasticbeanstalk" ) @@ -60,19 +61,19 @@ func (p *environmentProvider) Check(ctx context.Context, obj *elasticbeanstalk.E // Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). -func (p *environmentProvider) Create(ctx context.Context, obj *elasticbeanstalk.Environment) (resource.ID, *elasticbeanstalk.EnvironmentOuts, error) { +func (p *environmentProvider) Create(ctx context.Context, obj *elasticbeanstalk.Environment) (resource.ID, error) { if obj.CNAMEPrefix != nil || obj.Tags != nil || obj.TemplateName != nil || obj.Tier != nil { - return "", nil, fmt.Errorf("Properties not yet supported: CNAMEPrefix, Tags, TemplateName, Tier") + return "", fmt.Errorf("Properties not yet supported: CNAMEPrefix, Tags, TemplateName, Tier") } + // If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name. - // TODO: use the URN, not just the name, to enhance global uniqueness. - // TODO: even for explicit names, we should consider mangling it somehow, to reduce multi-instancing conflicts. var name string if obj.EnvironmentName != nil { name = *obj.EnvironmentName } else { - name = resource.NewUniqueHex(obj.Name+"-", maxEnvironmentName, sha1.Size) + name = resource.NewUniqueHex(*obj.Name+"-", maxEnvironmentName, sha1.Size) } + var optionSettings []*awselasticbeanstalk.ConfigurationOptionSetting if obj.OptionSettings != nil { for _, setting := range *obj.OptionSettings { @@ -83,22 +84,36 @@ func (p *environmentProvider) Create(ctx context.Context, obj *elasticbeanstalk. }) } } + + appname, err := arn.ParseResourceName(obj.Application) + if err != nil { + return "", err + } + var versionLabel *string + if obj.Version != nil { + version, err := arn.ParseResourceName(*obj.Version) + if err != nil { + return "", err + } + versionLabel = &version + } + fmt.Printf("Creating ElasticBeanstalk Environment '%v' with name '%v'\n", obj.Name, name) create := &awselasticbeanstalk.CreateEnvironmentInput{ EnvironmentName: aws.String(name), - ApplicationName: obj.Application.StringPtr(), + ApplicationName: aws.String(appname), Description: obj.Description, OptionSettings: optionSettings, - VersionLabel: obj.Version.StringPtr(), + VersionLabel: versionLabel, SolutionStackName: obj.SolutionStackName, } if _, err := p.ctx.ElasticBeanstalk().CreateEnvironment(create); err != nil { - return "", nil, err + return "", err } var endpointURL *string succ, err := awsctx.RetryUntilLong(p.ctx, func() (bool, error) { fmt.Printf("Waiting for environment %v to become Ready\n", name) - resp, err := p.getEnvironment(&name) + resp, err := p.getEnvironment(appname, name) if err != nil { return false, err } @@ -112,21 +127,75 @@ func (p *environmentProvider) Create(ctx context.Context, obj *elasticbeanstalk. return false, nil }) if err != nil { - return "", nil, err - } - if !succ { - return "", nil, fmt.Errorf("Timed out waiting for environment to become ready") - } - outs := &elasticbeanstalk.EnvironmentOuts{ - EndpointURL: *endpointURL, + return "", err + } else if !succ { + return "", fmt.Errorf("Timed out waiting for environment to become ready") } + fmt.Printf("Created ElasticBeanstalk Environment '%v' with EndpointURL: %v\n", name, *endpointURL) - return resource.ID(name), outs, nil + return arn.NewElasticBeanstalkEnvironmentID(p.ctx.Region(), p.ctx.AccountID(), appname, name), nil } // Read reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *environmentProvider) Get(ctx context.Context, id resource.ID) (*elasticbeanstalk.Environment, error) { - return nil, errors.New("Not yet implemented") + appname, envname, err := arn.ParseResourceNamePair(id) + if err != nil { + return nil, err + } + envresp, err := p.ctx.ElasticBeanstalk().DescribeEnvironments( + &awselasticbeanstalk.DescribeEnvironmentsInput{ + ApplicationName: aws.String(appname), + EnvironmentNames: []*string{aws.String(envname)}, + }, + ) + if err != nil { + return nil, err + } else if envresp.Environments == nil || len(envresp.Environments) == 0 { + return nil, nil + } + contract.Assert(len(envresp.Environments) == 1) + + // Successfully found the environment, now map all of its properties onto the struct. + env := envresp.Environments[0] + if env.CNAME != nil || env.TemplateName != nil || env.Tier != nil { + return nil, fmt.Errorf("Properties not yet supported: CNAMEPrefix, TemplateName, Tier") + } + var versionLabel *resource.ID + if env.VersionLabel != nil { + version := arn.NewElasticBeanstalkApplicationVersionID( + p.ctx.Region(), p.ctx.AccountID(), appname, *env.VersionLabel) + versionLabel = &version + } + envobj := &elasticbeanstalk.Environment{ + Application: arn.NewElasticBeanstalkApplicationID(p.ctx.Region(), p.ctx.AccountID(), appname), + Description: env.Description, + EnvironmentName: env.EnvironmentName, + SolutionStackName: env.SolutionStackName, + Version: versionLabel, + EndpointURL: *env.EndpointURL, + } + + // Next see if there are any configuration option settings and, if so, set them on the return. + confresp, err := p.ctx.ElasticBeanstalk().DescribeConfigurationSettings( + &awselasticbeanstalk.DescribeConfigurationSettingsInput{EnvironmentName: aws.String(envname)}) + if err != nil { + return nil, err + } + if confresp != nil && len(confresp.ConfigurationSettings) > 0 { + var options []elasticbeanstalk.OptionSetting + for _, setting := range confresp.ConfigurationSettings { + for _, option := range setting.OptionSettings { + options = append(options, elasticbeanstalk.OptionSetting{ + Namespace: *option.Namespace, + OptionName: *option.OptionName, + Value: *option.Value, + }) + } + } + envobj.OptionSettings = &options + } + + return envobj, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -142,8 +211,13 @@ func (p *environmentProvider) Update(ctx context.Context, id resource.ID, if new.CNAMEPrefix != nil || new.Tags != nil || new.TemplateName != nil || new.Tier != nil { return fmt.Errorf("Properties not yet supported: CNAMEPrefix, Tags, TemplateName, Tier") } + appname, envname, err := arn.ParseResourceNamePair(id) + if err != nil { + return err + } envUpdate := awselasticbeanstalk.UpdateEnvironmentInput{ - EnvironmentName: id.StringPtr(), + ApplicationName: aws.String(appname), + EnvironmentName: aws.String(envname), } if diff.Changed(elasticbeanstalk.Environment_Description) { envUpdate.Description = new.Description @@ -176,7 +250,7 @@ func (p *environmentProvider) Update(ctx context.Context, id resource.ID, } succ, err := awsctx.RetryUntilLong(p.ctx, func() (bool, error) { fmt.Printf("Waiting for environment %v to become Ready\n", id.String()) - resp, err := p.getEnvironment(id.StringPtr()) + resp, err := p.getEnvironment(appname, envname) if err != nil { return false, err } @@ -200,14 +274,18 @@ func (p *environmentProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *environmentProvider) Delete(ctx context.Context, id resource.ID) error { fmt.Printf("Deleting ElasticBeanstalk Environment '%v'\n", id) + appname, envname, err := arn.ParseResourceNamePair(id) + if err != nil { + return err + } if _, err := p.ctx.ElasticBeanstalk().TerminateEnvironment(&awselasticbeanstalk.TerminateEnvironmentInput{ - EnvironmentName: id.StringPtr(), + EnvironmentName: aws.String(envname), }); err != nil { return err } succ, err := awsctx.RetryUntilLong(p.ctx, func() (bool, error) { fmt.Printf("Waiting for environment %v to become Terminated\n", id.String()) - resp, err := p.getEnvironment(id.StringPtr()) + resp, err := p.getEnvironment(appname, envname) if err != nil { return false, err } @@ -225,9 +303,11 @@ func (p *environmentProvider) Delete(ctx context.Context, id resource.ID) error return nil } -func (p *environmentProvider) getEnvironment(name *string) (*awselasticbeanstalk.EnvironmentDescription, error) { +func (p *environmentProvider) getEnvironment( + appname, name string) (*awselasticbeanstalk.EnvironmentDescription, error) { resp, err := p.ctx.ElasticBeanstalk().DescribeEnvironments(&awselasticbeanstalk.DescribeEnvironmentsInput{ - EnvironmentNames: []*string{name}, + ApplicationName: aws.String(appname), + EnvironmentNames: []*string{aws.String(name)}, }) if err != nil { return nil, err diff --git a/lib/aws/provider/iam/role.go b/lib/aws/provider/iam/role.go index f82ce4b0e..19cb2b992 100644 --- a/lib/aws/provider/iam/role.go +++ b/lib/aws/provider/iam/role.go @@ -18,11 +18,9 @@ package iam import ( "crypto/sha1" "encoding/json" - "errors" "fmt" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" awsiam "github.com/aws/aws-sdk-go/service/iam" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/util/contract" @@ -30,6 +28,7 @@ import ( "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" awscommon "github.com/pulumi/lumi/lib/aws/rpc" "github.com/pulumi/lumi/lib/aws/rpc/iam" @@ -59,62 +58,106 @@ func (p *roleProvider) Check(ctx context.Context, obj *iam.Role) ([]mapper.Field // Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). -func (p *roleProvider) Create(ctx context.Context, obj *iam.Role) (resource.ID, *iam.RoleOuts, error) { +func (p *roleProvider) Create(ctx context.Context, obj *iam.Role) (resource.ID, error) { contract.Assertf(obj.Policies == nil, "Inline policies not yet supported") - // If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name. - // TODO: use the URN, not just the name, to enhance global uniqueness. - // TODO: even for explicit names, we should consider mangling it somehow, to reduce multi-instancing conflicts. - var id resource.ID + // A role uses its name as the unique ID, since the GetRole function uses it. If an explicit name is given, use + // it directly (at the risk of conflicts). Otherwise, auto-generate a name in part based on the resource name. + var name string if obj.RoleName != nil { - id = resource.ID(*obj.RoleName) + name = *obj.RoleName } else { - id = resource.NewUniqueHexID(obj.Name+"-", maxRoleName, sha1.Size) + name = resource.NewUniqueHex(*obj.Name+"-", maxRoleName, sha1.Size) } // Serialize the policy document into a JSON blob. policyDocument, err := json.Marshal(obj.AssumeRolePolicyDocument) if err != nil { - return "", nil, err + return "", err } // Now go ahead and perform the action. - fmt.Printf("Creating IAM Role '%v' with name '%v'\n", obj.Name, id) - var out *iam.RoleOuts + fmt.Printf("Creating IAM Role '%v' with name '%v'\n", obj.Name, name) result, err := p.ctx.IAM().CreateRole(&awsiam.CreateRoleInput{ AssumeRolePolicyDocument: aws.String(string(policyDocument)), Path: obj.Path, - RoleName: id.StringPtr(), + RoleName: aws.String(name), }) if err != nil { - return "", nil, err + return "", err } contract.Assert(result != nil) - out = &iam.RoleOuts{ARN: awscommon.ARN(*result.Role.Arn)} + contract.Assert(result.Role != nil) + contract.Assert(result.Role.Arn != nil) if obj.ManagedPolicyARNs != nil { for _, policyARN := range *obj.ManagedPolicyARNs { _, err := p.ctx.IAM().AttachRolePolicy(&awsiam.AttachRolePolicyInput{ - RoleName: id.StringPtr(), + RoleName: aws.String(name), PolicyArn: aws.String(string(policyARN)), }) if err != nil { - return "", nil, err + return "", err } } } // Wait for the role to be ready and then return the ID (just its name). - fmt.Printf("IAM Role created: %v; waiting for it to become active\n", id) - if err = p.waitForRoleState(id, true); err != nil { - return "", nil, err + fmt.Printf("IAM Role created: %v; waiting for it to become active\n", name) + if err = p.waitForRoleState(name, true); err != nil { + return "", err } - return id, out, nil + return resource.ID(*result.Role.Arn), nil } // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *roleProvider) Get(ctx context.Context, id resource.ID) (*iam.Role, error) { - return nil, errors.New("Not yet implemented") + name, err := arn.ParseResourceName(id) + if err != nil { + return nil, err + } + getrole, err := p.ctx.IAM().GetRole(&awsiam.GetRoleInput{RoleName: aws.String(name)}) + if err != nil { + if awsctx.IsAWSError(err, "NotFound", "NoSuchEntity") { + return nil, nil + } + return nil, err + } else if getrole == nil { + return nil, nil + } + + // If we got here, we found the role; populate the data structure accordingly. + role := getrole.Role + + // Policy is a JSON blob, parse it. + var policyDocument map[string]interface{} + if err := json.Unmarshal([]byte(*role.AssumeRolePolicyDocument), &policyDocument); err != nil { + return nil, err + } + + // Now get a list of attached role policies. + getpols, err := p.ctx.IAM().ListAttachedRolePolicies(&awsiam.ListAttachedRolePoliciesInput{ + RoleName: aws.String(name), + }) + if err != nil { + return nil, err + } + var managedPolicies *[]awscommon.ARN + if len(getpols.AttachedPolicies) > 0 { + var policies []awscommon.ARN + for _, policy := range getpols.AttachedPolicies { + policies = append(policies, awscommon.ARN(*policy.PolicyArn)) + } + managedPolicies = &policies + } + + return &iam.Role{ + AssumeRolePolicyDocument: role.AssumeRolePolicyDocument, + Path: role.Path, + RoleName: role.RoleName, + ManagedPolicyARNs: managedPolicies, + ARN: awscommon.ARN(*role.Arn), + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -128,6 +171,10 @@ func (p *roleProvider) InspectChange(ctx context.Context, id resource.ID, func (p *roleProvider) Update(ctx context.Context, id resource.ID, old *iam.Role, new *iam.Role, diff *resource.ObjectDiff) error { contract.Assertf(new.Policies == nil, "Inline policies not yet supported") + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } if diff.Changed(iam.Role_AssumeRolePolicyDocument) { // Serialize the policy document into a JSON blob. @@ -137,10 +184,10 @@ func (p *roleProvider) Update(ctx context.Context, id resource.ID, } // Now go ahead and perform the action. - fmt.Printf("Updating IAM Role '%v' with name '%v'\n", new.Name, id) + fmt.Printf("Updating IAM Role '%v' with name '%v'\n", new.Name, name) _, err = p.ctx.IAM().UpdateAssumeRolePolicy(&awsiam.UpdateAssumeRolePolicyInput{ PolicyDocument: aws.String(string(policyDocument)), - RoleName: id.StringPtr(), + RoleName: aws.String(name), }) if err != nil { return err @@ -176,7 +223,7 @@ func (p *roleProvider) Update(ctx context.Context, id resource.ID, for _, policy := range detaches { _, err := p.ctx.IAM().DetachRolePolicy(&awsiam.DetachRolePolicyInput{ PolicyArn: aws.String(string(policy)), - RoleName: id.StringPtr(), + RoleName: aws.String(name), }) if err != nil { return err @@ -185,7 +232,7 @@ func (p *roleProvider) Update(ctx context.Context, id resource.ID, for _, policy := range attaches { _, err := p.ctx.IAM().AttachRolePolicy(&awsiam.AttachRolePolicyInput{ PolicyArn: aws.String(string(policy)), - RoleName: id.StringPtr(), + RoleName: aws.String(name), }) if err != nil { return err @@ -198,10 +245,14 @@ func (p *roleProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *roleProvider) Delete(ctx context.Context, id resource.ID) error { + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } // Get and detach all attached policies before deleteing attachedRolePolicies, err := p.ctx.IAM().ListAttachedRolePolicies(&awsiam.ListAttachedRolePoliciesInput{ - RoleName: id.StringPtr(), + RoleName: aws.String(name), }) if err != nil { return err @@ -209,7 +260,7 @@ func (p *roleProvider) Delete(ctx context.Context, id resource.ID) error { if attachedRolePolicies != nil { for _, policy := range attachedRolePolicies.AttachedPolicies { if _, err := p.ctx.IAM().DetachRolePolicy(&awsiam.DetachRolePolicyInput{ - RoleName: id.StringPtr(), + RoleName: aws.String(name), PolicyArn: policy.PolicyArn, }); err != nil { return err @@ -218,26 +269,24 @@ func (p *roleProvider) Delete(ctx context.Context, id resource.ID) error { } // Perform the deletion. - fmt.Printf("Deleting IAM Role '%v'\n", id) - if _, err := p.ctx.IAM().DeleteRole(&awsiam.DeleteRoleInput{RoleName: id.StringPtr()}); err != nil { + fmt.Printf("Deleting IAM Role '%v'\n", name) + if _, err := p.ctx.IAM().DeleteRole(&awsiam.DeleteRoleInput{RoleName: aws.String(name)}); err != nil { return err } // Wait for the role to actually become deleted before the operation is complete. fmt.Printf("IAM Role delete request submitted; waiting for it to delete\n") - return p.waitForRoleState(id, false) + return p.waitForRoleState(name, false) } -func (p *roleProvider) waitForRoleState(id resource.ID, exist bool) error { +func (p *roleProvider) waitForRoleState(name string, exist bool) error { succ, err := awsctx.RetryUntil( p.ctx, func() (bool, error) { - if _, err := p.ctx.IAM().GetRole(&awsiam.GetRoleInput{RoleName: id.StringPtr()}); err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "NotFound" || erraws.Code() == "NoSuchEntity" { - // The role is missing; if exist==false, we're good, otherwise keep retrying. - return !exist, nil - } + if _, err := p.ctx.IAM().GetRole(&awsiam.GetRoleInput{RoleName: aws.String(name)}); err != nil { + if awsctx.IsAWSError(err, "NotFound", "NoSuchEntity") { + // The role is missing; if exist==false, we're good, otherwise keep retrying. + return !exist, nil } return false, err // anything other than "role missing" is a real error; propagate it. } @@ -255,7 +304,7 @@ func (p *roleProvider) waitForRoleState(id resource.ID, exist bool) error { } else { reason = "deleted" } - return fmt.Errorf("IAM role '%v' did not become %v", id, reason) + return fmt.Errorf("IAM role '%v' did not become %v", name, reason) } return nil } diff --git a/lib/aws/provider/lambda/function.go b/lib/aws/provider/lambda/function.go index 5e073487a..e413ce1e9 100644 --- a/lib/aws/provider/lambda/function.go +++ b/lib/aws/provider/lambda/function.go @@ -22,16 +22,15 @@ import ( "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - awsiam "github.com/aws/aws-sdk-go/service/iam" awslambda "github.com/aws/aws-sdk-go/service/lambda" - "github.com/pkg/errors" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/util/contract" + "github.com/pulumi/lumi/pkg/util/convutil" "github.com/pulumi/lumi/pkg/util/mapper" "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" awscommon "github.com/pulumi/lumi/lib/aws/rpc" "github.com/pulumi/lumi/lib/aws/rpc/lambda" @@ -92,33 +91,23 @@ func (p *funcProvider) Check(ctx context.Context, obj *lambda.Function) ([]mappe // Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). -func (p *funcProvider) Create(ctx context.Context, obj *lambda.Function) (resource.ID, *lambda.FunctionOuts, error) { +func (p *funcProvider) Create(ctx context.Context, obj *lambda.Function) (resource.ID, error) { contract.Assertf(obj.DeadLetterConfig == nil, "Dead letter config not yet supported") contract.Assertf(obj.VPCConfig == nil, "VPC config not yet supported") // If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name. - // TODO: use the URN, not just the name, to enhance global uniqueness. - // TODO: even for explicit names, we should consider mangling it somehow, to reduce multi-instancing conflicts. - var id resource.ID + var name string if obj.FunctionName != nil { - id = resource.ID(*obj.FunctionName) + name = *obj.FunctionName } else { - id = resource.NewUniqueHexID(obj.Name+"-", maxFunctionName, sha1.Size) - } - - roleARN, err := p.getRoleARN(obj.Role) - if err != nil { - return "", nil, err + name = resource.NewUniqueHex(*obj.Name+"-", maxFunctionName, sha1.Size) } code, err := p.getCode(obj.Code) if err != nil { - return "", nil, err + return "", err } - var dlqcfg *awslambda.DeadLetterConfig - var vpccfg *awslambda.VpcConfig - // Convert float fields to in64 if they are non-nil. var memsize *int64 if obj.MemorySize != nil { @@ -139,60 +128,97 @@ func (p *funcProvider) Create(ctx context.Context, obj *lambda.Function) (resour // Now go ahead and create the resource. Note that IAM profiles can take several seconds to propagate; see // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role. - fmt.Printf("Creating Lambda Function '%v' with name '%v'\n", obj.Name, id) + fmt.Printf("Creating Lambda Function '%v' with name '%v'\n", obj.Name, name) create := &awslambda.CreateFunctionInput{ - Code: code, - DeadLetterConfig: dlqcfg, - Description: obj.Description, - Environment: env, - FunctionName: id.StringPtr(), - Handler: aws.String(obj.Handler), - KMSKeyArn: obj.KMSKey.StringPtr(), - MemorySize: memsize, - Publish: nil, // ??? - Role: roleARN, - Runtime: aws.String(string(obj.Runtime)), - Timeout: timeout, - VpcConfig: vpccfg, + Code: code, + Description: obj.Description, + Environment: env, + FunctionName: aws.String(name), + Handler: aws.String(obj.Handler), + KMSKeyArn: obj.KMSKey.StringPtr(), + MemorySize: memsize, + Role: aws.String(string(obj.Role)), + Runtime: aws.String(string(obj.Runtime)), + Timeout: timeout, } - var out *lambda.FunctionOuts + var arn resource.ID if succ, err := awsctx.RetryProgUntil( p.ctx, func() (bool, error) { - result, err := p.ctx.Lambda().CreateFunction(create) + resp, err := p.ctx.Lambda().CreateFunction(create) if err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "InvalidParameterValueException" && - erraws.Message() == "The role defined for the function cannot be assumed by Lambda." { - return false, nil // retry the condition. - } + if awsctx.IsAWSErrorMessage(err, + "InvalidParameterValueException", + "The role defined for the function cannot be assumed by Lambda.") { + return false, nil // retry the condition. } return false, err } - out = &lambda.FunctionOuts{ARN: awscommon.ARN(*result.FunctionArn)} + contract.Assert(resp != nil) + contract.Assert(resp.FunctionArn != nil) + arn = resource.ID(*resp.FunctionArn) return true, nil }, func(n int) bool { - fmt.Printf("Lambda IAM role '%v' not yet ready; waiting for it to become usable...\n", roleARN) + fmt.Printf("Lambda IAM role '%v' not yet ready; waiting for it to become usable...\n", obj.Role) return true }, ); err != nil { - return "", nil, err + return "", err } else if !succ { - return "", nil, fmt.Errorf("Lambda IAM role '%v' did not become useable", roleARN) + return "", fmt.Errorf("Lambda IAM role '%v' did not become useable", obj.Role) } // Wait for the function to be ready and then return the function name as the ID. - fmt.Printf("Lambda Function created: %v; waiting for it to become active\n", id) - if err := p.waitForFunctionState(id, true); err != nil { - return "", nil, err + fmt.Printf("Lambda Function created: %v (ARN %v); waiting for it to become active\n", name, arn) + if err := p.waitForFunctionState(name, true); err != nil { + return "", err } - return id, out, nil + return arn, nil } // Read reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *funcProvider) Get(ctx context.Context, id resource.ID) (*lambda.Function, error) { - return nil, errors.New("Not yet implemented") + name, err := arn.ParseResourceName(id) + if err != nil { + return nil, err + } + funcresp, err := p.ctx.Lambda().GetFunction(&awslambda.GetFunctionInput{FunctionName: aws.String(name)}) + if err != nil { + if awsctx.IsAWSError(err, awslambda.ErrCodeResourceNotFoundException) { + return nil, nil + } + return nil, err + } + + // Deserialize the code URL into an archive/asset that the system can use. + contract.Assert(funcresp != nil) + contract.Assert(funcresp.Code != nil) + // TODO: unclear if we need to consult RepositoryType. + code := resource.NewURIArchive(*funcresp.Code.Location) + + // Deserialize all configuration properties into prompt objects. + config := funcresp.Configuration + contract.Assert(config != nil) + var env *lambda.Environment + if config.Environment != nil { + envmap := lambda.Environment(aws.StringValueMap(config.Environment.Variables)) + env = &envmap + } + + return &lambda.Function{ + ARN: awscommon.ARN(*config.FunctionArn), + Code: code, + Handler: *config.Handler, + Role: resource.ID(*config.Role), + Runtime: lambda.Runtime(*config.Runtime), + FunctionName: config.FunctionName, + Description: config.Description, + Environment: env, + KMSKey: resource.MaybeID(config.KMSKeyArn), + MemorySize: convutil.Int64PToFloat64P(config.MemorySize), + Timeout: convutil.Int64PToFloat64P(config.Timeout), + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -208,13 +234,18 @@ func (p *funcProvider) Update(ctx context.Context, id resource.ID, contract.Assertf(new.DeadLetterConfig == nil, "Dead letter config not yet supported") contract.Assertf(new.VPCConfig == nil, "VPC config not yet supported") + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } + if diff.Changed(lambda.Function_Description) || diff.Changed(lambda.Function_Environment) || diff.Changed(lambda.Function_Runtime) || diff.Changed(lambda.Function_Role) || diff.Changed(lambda.Function_MemorySize) || diff.Changed(lambda.Function_Timeout) || diff.Changed(lambda.Function_Environment) { update := &awslambda.UpdateFunctionConfigurationInput{ - FunctionName: id.StringPtr(), // Okay to use the ARN as the FunctionName + FunctionName: aws.String(name), } if diff.Changed(lambda.Function_Description) { update.Description = new.Description @@ -226,11 +257,7 @@ func (p *funcProvider) Update(ctx context.Context, id resource.ID, update.Runtime = aws.String(string(new.Runtime)) } if diff.Changed(lambda.Function_Role) { - roleARN, err := p.getRoleARN(new.Role) - if err != nil { - return err - } - update.Role = roleARN + update.Role = aws.String(string(new.Role)) } if diff.Changed(lambda.Function_MemorySize) { if new.MemorySize != nil { @@ -256,16 +283,16 @@ func (p *funcProvider) Update(ctx context.Context, id resource.ID, } } - fmt.Printf("Updating Lambda function configuration '%v'\n", id) + fmt.Printf("Updating Lambda function configuration '%v'\n", name) var out *awslambda.FunctionConfiguration var err error _, err = awsctx.RetryUntil(p.ctx, func() (bool, error) { out, err = p.ctx.Lambda().UpdateFunctionConfiguration(update) if err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Message() == "The role defined for the function cannot be assumed by Lambda." { - return false, nil - } + if awsctx.IsAWSErrorMessage(err, + "InvalidParameterValueException", + "The role defined for the function cannot be assumed by Lambda.") { + return false, nil } return true, err } @@ -280,11 +307,10 @@ func (p *funcProvider) Update(ctx context.Context, id resource.ID, func() (bool, error) { _, err := p.ctx.Lambda().UpdateFunctionConfiguration(update) if err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "InvalidParameterValueException" && - erraws.Message() == "The role defined for the function cannot be assumed by Lambda." { - return false, nil // retry the condition. - } + if awsctx.IsAWSErrorMessage(err, + "InvalidParameterValueException", + "The role defined for the function cannot be assumed by Lambda.") { + return false, nil // retry the condition. } return false, err } @@ -307,13 +333,13 @@ func (p *funcProvider) Update(ctx context.Context, id resource.ID, return err } update := &awslambda.UpdateFunctionCodeInput{ - FunctionName: id.StringPtr(), // Okay to use the ARN as the FunctionName + FunctionName: aws.String(name), S3Bucket: code.S3Bucket, S3Key: code.S3Key, S3ObjectVersion: code.S3ObjectVersion, ZipFile: code.ZipFile, } - fmt.Printf("Updating Lambda function code '%v'\n", id) + fmt.Printf("Updating Lambda function code '%v'\n", name) if _, err := p.ctx.Lambda().UpdateFunctionCode(update); err != nil { return err } @@ -323,31 +349,34 @@ func (p *funcProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *funcProvider) Delete(ctx context.Context, id resource.ID) error { + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } + // First, perform the deletion. - fmt.Printf("Deleting Lambda Function '%v'\n", id) + fmt.Printf("Deleting Lambda Function '%v'\n", name) if _, err := p.ctx.Lambda().DeleteFunction(&awslambda.DeleteFunctionInput{ - FunctionName: id.StringPtr(), + FunctionName: aws.String(name), }); err != nil { return err } // Wait for the function to actually become deleted before returning. fmt.Printf("Lambda Function delete request submitted; waiting for it to delete\n") - return p.waitForFunctionState(id, false) + return p.waitForFunctionState(name, false) } -func (p *funcProvider) waitForFunctionState(id resource.ID, exist bool) error { +func (p *funcProvider) waitForFunctionState(name string, exist bool) error { succ, err := awsctx.RetryUntil( p.ctx, func() (bool, error) { if _, err := p.ctx.Lambda().GetFunction(&awslambda.GetFunctionInput{ - FunctionName: id.StringPtr(), + FunctionName: aws.String(name), }); err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "NotFound" || erraws.Code() == "ResourceNotFoundException" { - // The function is missing; if exist==false, we're good, otherwise keep retrying. - return !exist, nil - } + if awsctx.IsAWSError(err, "NotFound", "ResourceNotFoundException") { + // The function is missing; if exist==false, we're good, otherwise keep retrying. + return !exist, nil } return false, err // anything other than "function missing" is a real error; propagate it. } @@ -365,7 +394,7 @@ func (p *funcProvider) waitForFunctionState(id resource.ID, exist bool) error { } else { reason = "deleted" } - return fmt.Errorf("Lambda Function '%v' did not become %v", id, reason) + return fmt.Errorf("Lambda Function '%v' did not become %v", name, reason) } return nil } @@ -375,7 +404,6 @@ func (p *funcProvider) getCode(codeArchive resource.Archive) (*awslambda.Functio if uri, isuri, err := codeArchive.GetURIURL(); err != nil { return nil, err } else if isuri && uri.Scheme == "s3" { - // TODO: it's odd that an S3 reference must *already* be a zipfile, whereas others are zipped on the fly. return &awslambda.FunctionCode{ S3Bucket: aws.String(uri.Host), S3Key: aws.String(uri.Path), @@ -386,13 +414,3 @@ func (p *funcProvider) getCode(codeArchive resource.Archive) (*awslambda.Functio return &awslambda.FunctionCode{ZipFile: zip}, nil } } - -func (p *funcProvider) getRoleARN(role resource.ID) (*string, error) { - // Fetch the IAM role's ARN. - // TODO[lumi/pulumi#90]: as soon as we can read output properties, this shouldn't be necessary. - if role, err := p.ctx.IAM().GetRole(&awsiam.GetRoleInput{RoleName: role.StringPtr()}); err != nil { - return nil, err - } else { - return role.Role.Arn, nil - } -} diff --git a/lib/aws/provider/main.go b/lib/aws/provider/main.go index a3ca084c8..5065f0e4a 100644 --- a/lib/aws/provider/main.go +++ b/lib/aws/provider/main.go @@ -27,8 +27,7 @@ import ( func main() { // Initialize loggers before going any further. - // TODO: consider parsing flags and letting the Lumi harness propagate them. - cmdutil.InitLogging(false, 0) + cmdutil.InitLogging(false, 0, false) // Fire up a gRPC server, letting the kernel choose a free port for us. port, done, err := rpcutil.Serve(0, []func(*grpc.Server) error{ diff --git a/lib/aws/provider/provider.go b/lib/aws/provider/provider.go index 7d5a1c3e0..3af4d64fd 100644 --- a/lib/aws/provider/provider.go +++ b/lib/aws/provider/provider.go @@ -102,7 +102,7 @@ func (p *Provider) Get(ctx context.Context, req *lumirpc.GetRequest) (*lumirpc.G // InspectChange checks what impacts a hypothetical update will have on the resource's properties. func (p *Provider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { t := tokens.Type(req.GetType()) if prov, has := p.impls[t]; has { return prov.InspectChange(ctx, req) @@ -112,7 +112,7 @@ func (p *Provider) InspectChange( // Update updates an existing resource with new values. Only those values in the provided property bag are updated // to new values. The resource ID is returned and may be different if the resource had to be recreated. -func (p *Provider) Update(ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { +func (p *Provider) Update(ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { t := tokens.Type(req.GetType()) if prov, has := p.impls[t]; has { return prov.Update(ctx, req) diff --git a/lib/aws/provider/s3/bucket.go b/lib/aws/provider/s3/bucket.go index a5f945a20..abe0d2b20 100644 --- a/lib/aws/provider/s3/bucket.go +++ b/lib/aws/provider/s3/bucket.go @@ -22,13 +22,13 @@ import ( "reflect" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" awss3 "github.com/aws/aws-sdk-go/service/s3" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/util/mapper" "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" "github.com/pulumi/lumi/lib/aws/rpc/s3" ) @@ -75,21 +75,19 @@ func (p *buckProvider) Check(ctx context.Context, obj *s3.Bucket) ([]mapper.Fiel // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). func (p *buckProvider) Create(ctx context.Context, obj *s3.Bucket) (resource.ID, error) { // If an explicit name is given, use it. Otherwise, auto-generate a name in part based on the resource name. - // TODO: use the URN, not just the name, to enhance global uniqueness. - // TODO: even for explicit names, we should consider mangling it somehow, to reduce multi-instancing conflicts. - var id resource.ID + var name string if obj.BucketName != nil { - id = resource.ID(*obj.BucketName) + name = *obj.BucketName } else { - id = resource.NewUniqueHexID(obj.Name+"-", maxBucketName, sha1.Size) + name = resource.NewUniqueHex(*obj.Name+"-", maxBucketName, sha1.Size) } var acl *string if obj.AccessControl != nil { acl = aws.String(string(*obj.AccessControl)) } - fmt.Printf("Creating S3 Bucket '%v' with name '%v'\n", obj.Name, id) + fmt.Printf("Creating S3 Bucket '%v' with name '%v'\n", obj.Name, name) create := &awss3.CreateBucketInput{ - Bucket: id.StringPtr(), + Bucket: aws.String(name), ACL: acl, } @@ -99,16 +97,32 @@ func (p *buckProvider) Create(ctx context.Context, obj *s3.Bucket) (resource.ID, } // Wait for the bucket to be ready and then return the ID (just its name). - fmt.Printf("S3 Bucket created: %v; waiting for it to become active\n", id) - if err := p.waitForBucketState(id, true); err != nil { + fmt.Printf("S3 Bucket created: %v; waiting for it to become active\n", name) + if err := p.waitForBucketState(name, true); err != nil { return "", err } - return id, nil + return arn.NewS3BucketID(name), nil } // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *buckProvider) Get(ctx context.Context, id resource.ID) (*s3.Bucket, error) { - return nil, errors.New("Not yet implemented") + name, err := arn.ParseResourceName(id) + if err != nil { + return nil, err + } + + if _, err := p.ctx.S3().GetBucketAcl(&awss3.GetBucketAclInput{Bucket: aws.String(name)}); err != nil { + if awsctx.IsAWSError(err, "NotFound", "NoSuchBucket") { + return nil, nil + } + return nil, err + } + + // Note that the canned ACL cannot be recreated from the GetBucketAclInput call, because it will have been expanded + // out into its constituent grants/owner parts; so we just retain the existing value on the receiver's side. + return &s3.Bucket{ + BucketName: &name, + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -126,31 +140,34 @@ func (p *buckProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *buckProvider) Delete(ctx context.Context, id resource.ID) error { + name, err := arn.ParseResourceName(id) + if err != nil { + return err + } + // First, perform the deletion. - fmt.Printf("Deleting S3 Bucket '%v'\n", id) + fmt.Printf("Deleting S3 Bucket '%v'\n", name) if _, err := p.ctx.S3().DeleteBucket(&awss3.DeleteBucketInput{ - Bucket: id.StringPtr(), + Bucket: aws.String(name), }); err != nil { return err } // Wait for the bucket to actually become deleted before returning. fmt.Printf("S3 Bucket delete request submitted; waiting for it to delete\n") - return p.waitForBucketState(id, false) + return p.waitForBucketState(name, false) } -func (p *buckProvider) waitForBucketState(id resource.ID, exist bool) error { +func (p *buckProvider) waitForBucketState(name string, exist bool) error { succ, err := awsctx.RetryUntil( p.ctx, func() (bool, error) { if _, err := p.ctx.S3().HeadBucket(&awss3.HeadBucketInput{ - Bucket: id.StringPtr(), + Bucket: aws.String(name), }); err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "NotFound" || erraws.Code() == "NoSuchBucket" { - // The bucket is missing; if exist==false, we're good, otherwise keep retrying. - return !exist, nil - } + if awsctx.IsAWSError(err, "NotFound", "NoSuchBucket") { + // The bucket is missing; if exist==false, we're good, otherwise keep retrying. + return !exist, nil } return false, err // anything other than "bucket missing" is a real error; propagate it. } @@ -168,7 +185,7 @@ func (p *buckProvider) waitForBucketState(id resource.ID, exist bool) error { } else { reason = "deleted" } - return fmt.Errorf("S3 bucket '%v' did not become %v", id, reason) + return fmt.Errorf("S3 bucket '%v' did not become %v", name, reason) } return nil } diff --git a/lib/aws/provider/s3/object.go b/lib/aws/provider/s3/object.go index 14c5d266f..e8f4154fa 100644 --- a/lib/aws/provider/s3/object.go +++ b/lib/aws/provider/s3/object.go @@ -19,26 +19,21 @@ import ( "fmt" "reflect" "regexp" - "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" awss3 "github.com/aws/aws-sdk-go/service/s3" "github.com/pkg/errors" "github.com/pulumi/lumi/pkg/resource" - "github.com/pulumi/lumi/pkg/util/contract" "github.com/pulumi/lumi/pkg/util/mapper" "github.com/pulumi/lumi/sdk/go/pkg/lumirpc" "golang.org/x/net/context" + "github.com/pulumi/lumi/lib/aws/provider/arn" "github.com/pulumi/lumi/lib/aws/provider/awsctx" "github.com/pulumi/lumi/lib/aws/rpc/s3" ) -const ( - ObjectToken = s3.ObjectToken - ObjectIDDelim = "/" // the delimiter between bucket and key name. -) +const ObjectToken = s3.ObjectToken // constants for the various object constraints. const ( @@ -93,7 +88,10 @@ func (p *objProvider) Create(ctx context.Context, obj *s3.Object) (resource.ID, defer body.Close() // Now go ahead and perform the creation. - buck := obj.Bucket.String() + buck, err := arn.ParseResourceName(obj.Bucket) + if err != nil { + return "", err + } fmt.Printf("Creating S3 Object '%v' in bucket '%v'\n", obj.Key, buck) if _, err := p.ctx.S3().PutObject(&awss3.PutObjectInput{ Bucket: aws.String(buck), @@ -108,12 +106,29 @@ func (p *objProvider) Create(ctx context.Context, obj *s3.Object) (resource.ID, if err := p.waitForObjectState(buck, obj.Key, true); err != nil { return "", err } - return resource.ID(buck + ObjectIDDelim + obj.Key), nil + return arn.NewS3ObjectID(buck, obj.Key), nil } // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. func (p *objProvider) Get(ctx context.Context, id resource.ID) (*s3.Object, error) { - return nil, errors.New("Not yet implemented") + buck, key, err := arn.ParseResourceNamePair(id) + if err != nil { + return nil, err + } + if _, err := p.ctx.S3().GetObject(&awss3.GetObjectInput{ + Bucket: aws.String(buck), + Key: aws.String(key), + }); err != nil { + if awsctx.IsAWSError(err, "NotFound", "NoSuchKey") { + return nil, nil + } + return nil, err + } + return &s3.Object{ + Bucket: resource.ID(arn.NewS3Bucket(buck)), + Key: key, + Source: resource.NewURIAsset(fmt.Sprintf("s3://%v/%v", buck, key)), + }, nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. @@ -131,15 +146,15 @@ func (p *objProvider) Update(ctx context.Context, id resource.ID, // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. func (p *objProvider) Delete(ctx context.Context, id resource.ID) error { + buck, key, err := arn.ParseResourceNamePair(id) + if err != nil { + return err + } + // First, perform the deletion. fmt.Printf("Deleting S3 Object '%v'\n", id) - ids := string(id) - delim := strings.Index(ids, ObjectIDDelim) - contract.Assertf(delim != -1, "Missing object ID delimiter (`%v`)", ObjectIDDelim) - bucket := ids[:delim] - key := ids[delim+1:] if _, err := p.ctx.S3().DeleteObject(&awss3.DeleteObjectInput{ - Bucket: aws.String(bucket), + Bucket: aws.String(buck), Key: aws.String(key), }); err != nil { return err @@ -147,7 +162,7 @@ func (p *objProvider) Delete(ctx context.Context, id resource.ID) error { // Wait for the bucket to actually become deleted before returning. fmt.Printf("S3 Object delete request submitted; waiting for it to delete\n") - return p.waitForObjectState(bucket, key, false) + return p.waitForObjectState(buck, key, false) } func (p *objProvider) waitForObjectState(bucket string, key string, exist bool) error { @@ -158,11 +173,9 @@ func (p *objProvider) waitForObjectState(bucket string, key string, exist bool) Bucket: aws.String(bucket), Key: aws.String(key), }); err != nil { - if erraws, iserraws := err.(awserr.Error); iserraws { - if erraws.Code() == "NotFound" || erraws.Code() == "NoSuchKey" { - // The object is missing; if exist==false, we're good, otherwise keep retrying. - return !exist, nil - } + if awsctx.IsAWSError(err, "NotFound", "NoSuchKey") { + // The object is missing; if exist==false, we're good, otherwise keep retrying. + return !exist, nil } return false, err // anything other than "object missing" is a real error; propagate it. } diff --git a/lib/aws/rpc/apigateway/account.go b/lib/aws/rpc/apigateway/account.go index b95a41e78..71ee8a126 100644 --- a/lib/aws/rpc/apigateway/account.go +++ b/lib/aws/rpc/apigateway/account.go @@ -68,10 +68,13 @@ func (p *AccountProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Account_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *AccountProvider) Create( @@ -85,9 +88,7 @@ func (p *AccountProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *AccountProvider) Get( @@ -105,7 +106,7 @@ func (p *AccountProvider) Get( } func (p *AccountProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(AccountToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -133,7 +134,7 @@ func (p *AccountProvider) InspectChange( } func (p *AccountProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(AccountToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -164,7 +165,7 @@ func (p *AccountProvider) Delete( func (p *AccountProvider) Unmarshal( v *pbstruct.Struct) (*Account, resource.PropertyMap, mapper.DecodeError) { var obj Account - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -173,7 +174,7 @@ func (p *AccountProvider) Unmarshal( // Account is a marshalable representation of its corresponding IDL type. type Account struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` CloudWatchRole *resource.ID `json:"cloudWatchRole,omitempty"` } diff --git a/lib/aws/rpc/apigateway/apiKey.go b/lib/aws/rpc/apigateway/apiKey.go index e2ebee984..7f07167e0 100644 --- a/lib/aws/rpc/apigateway/apiKey.go +++ b/lib/aws/rpc/apigateway/apiKey.go @@ -68,10 +68,13 @@ func (p *APIKeyProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[APIKey_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *APIKeyProvider) Create( @@ -85,9 +88,7 @@ func (p *APIKeyProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *APIKeyProvider) Get( @@ -105,7 +106,7 @@ func (p *APIKeyProvider) Get( } func (p *APIKeyProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(APIKeyToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -136,7 +137,7 @@ func (p *APIKeyProvider) InspectChange( } func (p *APIKeyProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(APIKeyToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -167,7 +168,7 @@ func (p *APIKeyProvider) Delete( func (p *APIKeyProvider) Unmarshal( v *pbstruct.Struct) (*APIKey, resource.PropertyMap, mapper.DecodeError) { var obj APIKey - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -176,7 +177,7 @@ func (p *APIKeyProvider) Unmarshal( // APIKey is a marshalable representation of its corresponding IDL type. type APIKey struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` KeyName *string `json:"keyName,omitempty"` Description *string `json:"description,omitempty"` Enabled *bool `json:"enabled,omitempty"` diff --git a/lib/aws/rpc/apigateway/authorizer.go b/lib/aws/rpc/apigateway/authorizer.go index 56e3f6d1d..932db4d87 100644 --- a/lib/aws/rpc/apigateway/authorizer.go +++ b/lib/aws/rpc/apigateway/authorizer.go @@ -68,10 +68,13 @@ func (p *AuthorizerProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Authorizer_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *AuthorizerProvider) Create( @@ -85,9 +88,7 @@ func (p *AuthorizerProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *AuthorizerProvider) Get( @@ -105,7 +106,7 @@ func (p *AuthorizerProvider) Get( } func (p *AuthorizerProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(AuthorizerToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -133,7 +134,7 @@ func (p *AuthorizerProvider) InspectChange( } func (p *AuthorizerProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(AuthorizerToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -164,7 +165,7 @@ func (p *AuthorizerProvider) Delete( func (p *AuthorizerProvider) Unmarshal( v *pbstruct.Struct) (*Authorizer, resource.PropertyMap, mapper.DecodeError) { var obj Authorizer - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -173,7 +174,7 @@ func (p *AuthorizerProvider) Unmarshal( // Authorizer is a marshalable representation of its corresponding IDL type. type Authorizer struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Type AuthorizerType `json:"type"` AuthorizerCredentials *resource.ID `json:"authorizerCredentials,omitempty"` AuthorizerResultTTLInSeconds *float64 `json:"authorizerResultTTLInSeconds,omitempty"` diff --git a/lib/aws/rpc/apigateway/basePathMapping.go b/lib/aws/rpc/apigateway/basePathMapping.go index 3f485d42d..632ae21d5 100644 --- a/lib/aws/rpc/apigateway/basePathMapping.go +++ b/lib/aws/rpc/apigateway/basePathMapping.go @@ -68,10 +68,13 @@ func (p *BasePathMappingProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[BasePathMapping_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *BasePathMappingProvider) Create( @@ -85,9 +88,7 @@ func (p *BasePathMappingProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *BasePathMappingProvider) Get( @@ -105,7 +106,7 @@ func (p *BasePathMappingProvider) Get( } func (p *BasePathMappingProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(BasePathMappingToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -133,7 +134,7 @@ func (p *BasePathMappingProvider) InspectChange( } func (p *BasePathMappingProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(BasePathMappingToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -164,7 +165,7 @@ func (p *BasePathMappingProvider) Delete( func (p *BasePathMappingProvider) Unmarshal( v *pbstruct.Struct) (*BasePathMapping, resource.PropertyMap, mapper.DecodeError) { var obj BasePathMapping - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -173,7 +174,7 @@ func (p *BasePathMappingProvider) Unmarshal( // BasePathMapping is a marshalable representation of its corresponding IDL type. type BasePathMapping struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` DomainName string `json:"domainName"` RestAPI resource.ID `json:"restAPI"` BasePath *string `json:"basePath,omitempty"` diff --git a/lib/aws/rpc/apigateway/clientCertificate.go b/lib/aws/rpc/apigateway/clientCertificate.go index 02f79c8f1..466b24f5d 100644 --- a/lib/aws/rpc/apigateway/clientCertificate.go +++ b/lib/aws/rpc/apigateway/clientCertificate.go @@ -68,10 +68,13 @@ func (p *ClientCertificateProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[ClientCertificate_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *ClientCertificateProvider) Create( @@ -85,9 +88,7 @@ func (p *ClientCertificateProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *ClientCertificateProvider) Get( @@ -105,7 +106,7 @@ func (p *ClientCertificateProvider) Get( } func (p *ClientCertificateProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(ClientCertificateToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -133,7 +134,7 @@ func (p *ClientCertificateProvider) InspectChange( } func (p *ClientCertificateProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(ClientCertificateToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -164,7 +165,7 @@ func (p *ClientCertificateProvider) Delete( func (p *ClientCertificateProvider) Unmarshal( v *pbstruct.Struct) (*ClientCertificate, resource.PropertyMap, mapper.DecodeError) { var obj ClientCertificate - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -173,7 +174,7 @@ func (p *ClientCertificateProvider) Unmarshal( // ClientCertificate is a marshalable representation of its corresponding IDL type. type ClientCertificate struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` } diff --git a/lib/aws/rpc/apigateway/deployment.go b/lib/aws/rpc/apigateway/deployment.go index 537d8a9fe..038e116f2 100644 --- a/lib/aws/rpc/apigateway/deployment.go +++ b/lib/aws/rpc/apigateway/deployment.go @@ -68,10 +68,13 @@ func (p *DeploymentProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Deployment_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *DeploymentProvider) Create( @@ -85,9 +88,7 @@ func (p *DeploymentProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *DeploymentProvider) Get( @@ -105,7 +106,7 @@ func (p *DeploymentProvider) Get( } func (p *DeploymentProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(DeploymentToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -133,7 +134,7 @@ func (p *DeploymentProvider) InspectChange( } func (p *DeploymentProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(DeploymentToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -164,7 +165,7 @@ func (p *DeploymentProvider) Delete( func (p *DeploymentProvider) Unmarshal( v *pbstruct.Struct) (*Deployment, resource.PropertyMap, mapper.DecodeError) { var obj Deployment - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -173,7 +174,7 @@ func (p *DeploymentProvider) Unmarshal( // Deployment is a marshalable representation of its corresponding IDL type. type Deployment struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` RestAPI resource.ID `json:"restAPI"` Description *string `json:"description,omitempty"` StageDescription *StageDescription `json:"stageDescription,omitempty"` diff --git a/lib/aws/rpc/apigateway/method.go b/lib/aws/rpc/apigateway/method.go index 6f14d5dff..c9946ef12 100644 --- a/lib/aws/rpc/apigateway/method.go +++ b/lib/aws/rpc/apigateway/method.go @@ -116,10 +116,13 @@ func (p *MethodProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Method_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *MethodProvider) Create( @@ -133,9 +136,7 @@ func (p *MethodProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *MethodProvider) Get( @@ -153,7 +154,7 @@ func (p *MethodProvider) Get( } func (p *MethodProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(MethodToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -181,7 +182,7 @@ func (p *MethodProvider) InspectChange( } func (p *MethodProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(MethodToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -212,7 +213,7 @@ func (p *MethodProvider) Delete( func (p *MethodProvider) Unmarshal( v *pbstruct.Struct) (*Method, resource.PropertyMap, mapper.DecodeError) { var obj Method - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -221,7 +222,7 @@ func (p *MethodProvider) Unmarshal( // Method is a marshalable representation of its corresponding IDL type. type Method struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` HTTPMethod string `json:"httpMethod"` APIResource resource.ID `json:"apiResource"` RestAPI resource.ID `json:"restAPI"` diff --git a/lib/aws/rpc/apigateway/model.go b/lib/aws/rpc/apigateway/model.go index 67759be4f..d527d5e40 100644 --- a/lib/aws/rpc/apigateway/model.go +++ b/lib/aws/rpc/apigateway/model.go @@ -68,10 +68,13 @@ func (p *ModelProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Model_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *ModelProvider) Create( @@ -85,9 +88,7 @@ func (p *ModelProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *ModelProvider) Get( @@ -105,7 +106,7 @@ func (p *ModelProvider) Get( } func (p *ModelProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(ModelToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -142,7 +143,7 @@ func (p *ModelProvider) InspectChange( } func (p *ModelProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(ModelToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -173,7 +174,7 @@ func (p *ModelProvider) Delete( func (p *ModelProvider) Unmarshal( v *pbstruct.Struct) (*Model, resource.PropertyMap, mapper.DecodeError) { var obj Model - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -182,7 +183,7 @@ func (p *ModelProvider) Unmarshal( // Model is a marshalable representation of its corresponding IDL type. type Model struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` ContentType string `json:"contentType"` RestAPI resource.ID `json:"restAPI"` Schema interface{} `json:"schema"` diff --git a/lib/aws/rpc/apigateway/resource.go b/lib/aws/rpc/apigateway/resource.go index af2320693..20b1dc29a 100644 --- a/lib/aws/rpc/apigateway/resource.go +++ b/lib/aws/rpc/apigateway/resource.go @@ -68,10 +68,13 @@ func (p *ResourceProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Resource_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *ResourceProvider) Create( @@ -85,9 +88,7 @@ func (p *ResourceProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *ResourceProvider) Get( @@ -105,7 +106,7 @@ func (p *ResourceProvider) Get( } func (p *ResourceProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(ResourceToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -142,7 +143,7 @@ func (p *ResourceProvider) InspectChange( } func (p *ResourceProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(ResourceToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -173,7 +174,7 @@ func (p *ResourceProvider) Delete( func (p *ResourceProvider) Unmarshal( v *pbstruct.Struct) (*Resource, resource.PropertyMap, mapper.DecodeError) { var obj Resource - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -182,7 +183,7 @@ func (p *ResourceProvider) Unmarshal( // Resource is a marshalable representation of its corresponding IDL type. type Resource struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Parent resource.ID `json:"parent"` PathPart string `json:"pathPart"` RestAPI resource.ID `json:"restAPI"` diff --git a/lib/aws/rpc/apigateway/restAPI.go b/lib/aws/rpc/apigateway/restAPI.go index 8d970ad0d..197037402 100644 --- a/lib/aws/rpc/apigateway/restAPI.go +++ b/lib/aws/rpc/apigateway/restAPI.go @@ -68,10 +68,13 @@ func (p *RestAPIProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[RestAPI_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *RestAPIProvider) Create( @@ -85,9 +88,7 @@ func (p *RestAPIProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *RestAPIProvider) Get( @@ -105,7 +106,7 @@ func (p *RestAPIProvider) Get( } func (p *RestAPIProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(RestAPIToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -133,7 +134,7 @@ func (p *RestAPIProvider) InspectChange( } func (p *RestAPIProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(RestAPIToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -164,7 +165,7 @@ func (p *RestAPIProvider) Delete( func (p *RestAPIProvider) Unmarshal( v *pbstruct.Struct) (*RestAPI, resource.PropertyMap, mapper.DecodeError) { var obj RestAPI - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -173,7 +174,7 @@ func (p *RestAPIProvider) Unmarshal( // RestAPI is a marshalable representation of its corresponding IDL type. type RestAPI struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Body *interface{} `json:"body,omitempty"` BodyS3Location *S3Location `json:"bodyS3Location,omitempty"` CloneFrom *resource.ID `json:"cloneFrom,omitempty"` diff --git a/lib/aws/rpc/apigateway/stage.go b/lib/aws/rpc/apigateway/stage.go index 20ab1d561..bfb503b3e 100644 --- a/lib/aws/rpc/apigateway/stage.go +++ b/lib/aws/rpc/apigateway/stage.go @@ -68,10 +68,13 @@ func (p *StageProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Stage_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *StageProvider) Create( @@ -85,9 +88,7 @@ func (p *StageProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *StageProvider) Get( @@ -105,7 +106,7 @@ func (p *StageProvider) Get( } func (p *StageProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(StageToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -139,7 +140,7 @@ func (p *StageProvider) InspectChange( } func (p *StageProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(StageToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -170,7 +171,7 @@ func (p *StageProvider) Delete( func (p *StageProvider) Unmarshal( v *pbstruct.Struct) (*Stage, resource.PropertyMap, mapper.DecodeError) { var obj Stage - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -179,7 +180,7 @@ func (p *StageProvider) Unmarshal( // Stage is a marshalable representation of its corresponding IDL type. type Stage struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` RestAPI resource.ID `json:"restAPI"` StageName string `json:"stageName"` Deployment resource.ID `json:"deployment"` diff --git a/lib/aws/rpc/apigateway/usagePlan.go b/lib/aws/rpc/apigateway/usagePlan.go index b60df7454..4c32ac96f 100644 --- a/lib/aws/rpc/apigateway/usagePlan.go +++ b/lib/aws/rpc/apigateway/usagePlan.go @@ -112,10 +112,13 @@ func (p *UsagePlanProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[UsagePlan_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *UsagePlanProvider) Create( @@ -129,9 +132,7 @@ func (p *UsagePlanProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *UsagePlanProvider) Get( @@ -149,7 +150,7 @@ func (p *UsagePlanProvider) Get( } func (p *UsagePlanProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(UsagePlanToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -177,7 +178,7 @@ func (p *UsagePlanProvider) InspectChange( } func (p *UsagePlanProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(UsagePlanToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -208,7 +209,7 @@ func (p *UsagePlanProvider) Delete( func (p *UsagePlanProvider) Unmarshal( v *pbstruct.Struct) (*UsagePlan, resource.PropertyMap, mapper.DecodeError) { var obj UsagePlan - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -217,7 +218,7 @@ func (p *UsagePlanProvider) Unmarshal( // UsagePlan is a marshalable representation of its corresponding IDL type. type UsagePlan struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` APIStages *[]APIStage `json:"apiStages,omitempty"` Description *string `json:"description,omitempty"` Quota *QuotaSettings `json:"quota,omitempty"` diff --git a/lib/aws/rpc/apigateway/usagePlanKey.go b/lib/aws/rpc/apigateway/usagePlanKey.go index 94efa03b6..e59104556 100644 --- a/lib/aws/rpc/apigateway/usagePlanKey.go +++ b/lib/aws/rpc/apigateway/usagePlanKey.go @@ -68,10 +68,13 @@ func (p *UsagePlanKeyProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[UsagePlanKey_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *UsagePlanKeyProvider) Create( @@ -85,9 +88,7 @@ func (p *UsagePlanKeyProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *UsagePlanKeyProvider) Get( @@ -105,7 +106,7 @@ func (p *UsagePlanKeyProvider) Get( } func (p *UsagePlanKeyProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(UsagePlanKeyToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -139,7 +140,7 @@ func (p *UsagePlanKeyProvider) InspectChange( } func (p *UsagePlanKeyProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(UsagePlanKeyToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -170,7 +171,7 @@ func (p *UsagePlanKeyProvider) Delete( func (p *UsagePlanKeyProvider) Unmarshal( v *pbstruct.Struct) (*UsagePlanKey, resource.PropertyMap, mapper.DecodeError) { var obj UsagePlanKey - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -179,7 +180,7 @@ func (p *UsagePlanKeyProvider) Unmarshal( // UsagePlanKey is a marshalable representation of its corresponding IDL type. type UsagePlanKey struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Key resource.ID `json:"key"` UsagePlan resource.ID `json:"usagePlan"` } diff --git a/lib/aws/rpc/cloudwatch/alarm.go b/lib/aws/rpc/cloudwatch/alarm.go index 2a1e306c5..a0597b07c 100644 --- a/lib/aws/rpc/cloudwatch/alarm.go +++ b/lib/aws/rpc/cloudwatch/alarm.go @@ -70,10 +70,13 @@ func (p *ActionTargetProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[ActionTarget_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *ActionTargetProvider) Create( @@ -87,9 +90,7 @@ func (p *ActionTargetProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *ActionTargetProvider) Get( @@ -107,7 +108,7 @@ func (p *ActionTargetProvider) Get( } func (p *ActionTargetProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(ActionTargetToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -138,7 +139,7 @@ func (p *ActionTargetProvider) InspectChange( } func (p *ActionTargetProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(ActionTargetToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -169,7 +170,7 @@ func (p *ActionTargetProvider) Delete( func (p *ActionTargetProvider) Unmarshal( v *pbstruct.Struct) (*ActionTarget, resource.PropertyMap, mapper.DecodeError) { var obj ActionTarget - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -178,7 +179,7 @@ func (p *ActionTargetProvider) Unmarshal( // ActionTarget is a marshalable representation of its corresponding IDL type. type ActionTarget struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` TopicName *string `json:"topicName,omitempty"` DisplayName *string `json:"displayName,omitempty"` Subscription *[]__sns.TopicSubscription `json:"subscription,omitempty"` @@ -243,10 +244,13 @@ func (p *AlarmProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Alarm_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *AlarmProvider) Create( @@ -260,9 +264,7 @@ func (p *AlarmProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *AlarmProvider) Get( @@ -280,7 +282,7 @@ func (p *AlarmProvider) Get( } func (p *AlarmProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(AlarmToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -311,7 +313,7 @@ func (p *AlarmProvider) InspectChange( } func (p *AlarmProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(AlarmToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -342,7 +344,7 @@ func (p *AlarmProvider) Delete( func (p *AlarmProvider) Unmarshal( v *pbstruct.Struct) (*Alarm, resource.PropertyMap, mapper.DecodeError) { var obj Alarm - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -351,7 +353,7 @@ func (p *AlarmProvider) Unmarshal( // Alarm is a marshalable representation of its corresponding IDL type. type Alarm struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` ComparisonOperator AlarmComparisonOperator `json:"comparisonOperator"` EvaluationPeriods float64 `json:"evaluationPerids"` MetricName string `json:"metricName"` diff --git a/lib/aws/rpc/dynamodb/table.go b/lib/aws/rpc/dynamodb/table.go index 746e9c076..b10c7d2e9 100644 --- a/lib/aws/rpc/dynamodb/table.go +++ b/lib/aws/rpc/dynamodb/table.go @@ -106,10 +106,13 @@ func (p *TableProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Table_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *TableProvider) Create( @@ -123,9 +126,7 @@ func (p *TableProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *TableProvider) Get( @@ -143,7 +144,7 @@ func (p *TableProvider) Get( } func (p *TableProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(TableToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -180,7 +181,7 @@ func (p *TableProvider) InspectChange( } func (p *TableProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(TableToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -211,7 +212,7 @@ func (p *TableProvider) Delete( func (p *TableProvider) Unmarshal( v *pbstruct.Struct) (*Table, resource.PropertyMap, mapper.DecodeError) { var obj Table - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -220,7 +221,7 @@ func (p *TableProvider) Unmarshal( // Table is a marshalable representation of its corresponding IDL type. type Table struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` HashKey string `json:"hashKey"` Attributes []Attribute `json:"attributes"` ReadCapacity float64 `json:"readCapacity"` diff --git a/lib/aws/rpc/ec2/instance.go b/lib/aws/rpc/ec2/instance.go index 0d0dc1c20..acc61b5b7 100644 --- a/lib/aws/rpc/ec2/instance.go +++ b/lib/aws/rpc/ec2/instance.go @@ -25,7 +25,7 @@ const InstanceToken = tokens.Type("aws:ec2/instance:Instance") // InstanceProviderOps is a pluggable interface for Instance-related management functionality. type InstanceProviderOps interface { Check(ctx context.Context, obj *Instance) ([]mapper.FieldError, error) - Create(ctx context.Context, obj *Instance) (resource.ID, *InstanceOuts, error) + Create(ctx context.Context, obj *Instance) (resource.ID, error) Get(ctx context.Context, id resource.ID) (*Instance, error) InspectChange(ctx context.Context, id resource.ID, old *Instance, new *Instance, diff *resource.ObjectDiff) ([]string, error) @@ -68,10 +68,13 @@ func (p *InstanceProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Instance_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *InstanceProvider) Create( @@ -81,16 +84,11 @@ func (p *InstanceProvider) Create( if decerr != nil { return nil, decerr } - id, outs, err := p.ops.Create(ctx, obj) + id, err := p.ops.Create(ctx, obj) if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - Outputs: resource.MarshalProperties( - nil, resource.NewPropertyMap(outs), resource.MarshalOptions{}, - ), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *InstanceProvider) Get( @@ -108,7 +106,7 @@ func (p *InstanceProvider) Get( } func (p *InstanceProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(InstanceToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -136,7 +134,7 @@ func (p *InstanceProvider) InspectChange( } func (p *InstanceProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(InstanceToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -167,7 +165,7 @@ func (p *InstanceProvider) Delete( func (p *InstanceProvider) Unmarshal( v *pbstruct.Struct) (*Instance, resource.PropertyMap, mapper.DecodeError) { var obj Instance - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -176,7 +174,7 @@ func (p *InstanceProvider) Unmarshal( // Instance is a marshalable representation of its corresponding IDL type. type Instance struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` ImageID string `json:"imageId"` InstanceType *InstanceType `json:"instanceType,omitempty"` SecurityGroups *[]resource.ID `json:"securityGroups,omitempty"` @@ -188,15 +186,6 @@ type Instance struct { PublicIP *string `json:"publicIP,omitempty"` } -// InstanceOuts is a marshalable representation of its IDL type's output properties. -type InstanceOuts struct { - AvailabilityZone string `json:"availabilityZone"` - PrivateDNSName *string `json:"privateDNSName,omitempty"` - PublicDNSName *string `json:"publicDNSName,omitempty"` - PrivateIP *string `json:"privateIP,omitempty"` - PublicIP *string `json:"publicIP,omitempty"` -} - // Instance's properties have constants to make dealing with diffs and property bags easier. const ( Instance_Name = "name" diff --git a/lib/aws/rpc/ec2/internetGateway.go b/lib/aws/rpc/ec2/internetGateway.go index 0c8997aa4..9dfd5291c 100644 --- a/lib/aws/rpc/ec2/internetGateway.go +++ b/lib/aws/rpc/ec2/internetGateway.go @@ -68,10 +68,13 @@ func (p *InternetGatewayProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[InternetGateway_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *InternetGatewayProvider) Create( @@ -85,9 +88,7 @@ func (p *InternetGatewayProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *InternetGatewayProvider) Get( @@ -105,7 +106,7 @@ func (p *InternetGatewayProvider) Get( } func (p *InternetGatewayProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(InternetGatewayToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -133,7 +134,7 @@ func (p *InternetGatewayProvider) InspectChange( } func (p *InternetGatewayProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(InternetGatewayToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -164,7 +165,7 @@ func (p *InternetGatewayProvider) Delete( func (p *InternetGatewayProvider) Unmarshal( v *pbstruct.Struct) (*InternetGateway, resource.PropertyMap, mapper.DecodeError) { var obj InternetGateway - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -173,7 +174,7 @@ func (p *InternetGatewayProvider) Unmarshal( // InternetGateway is a marshalable representation of its corresponding IDL type. type InternetGateway struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` } // InternetGateway's properties have constants to make dealing with diffs and property bags easier. diff --git a/lib/aws/rpc/ec2/route.go b/lib/aws/rpc/ec2/route.go index 207fb225d..7c89ac3b2 100644 --- a/lib/aws/rpc/ec2/route.go +++ b/lib/aws/rpc/ec2/route.go @@ -68,10 +68,13 @@ func (p *RouteProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Route_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *RouteProvider) Create( @@ -85,9 +88,7 @@ func (p *RouteProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *RouteProvider) Get( @@ -105,7 +106,7 @@ func (p *RouteProvider) Get( } func (p *RouteProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(RouteToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -145,7 +146,7 @@ func (p *RouteProvider) InspectChange( } func (p *RouteProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(RouteToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -176,7 +177,7 @@ func (p *RouteProvider) Delete( func (p *RouteProvider) Unmarshal( v *pbstruct.Struct) (*Route, resource.PropertyMap, mapper.DecodeError) { var obj Route - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -185,7 +186,7 @@ func (p *RouteProvider) Unmarshal( // Route is a marshalable representation of its corresponding IDL type. type Route struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` DestinationCidrBlock string `json:"destinationCidrBlock"` RouteTable resource.ID `json:"routeTable"` InternetGateway resource.ID `json:"internetGateway"` diff --git a/lib/aws/rpc/ec2/routeTable.go b/lib/aws/rpc/ec2/routeTable.go index 051fc3783..52c267fce 100644 --- a/lib/aws/rpc/ec2/routeTable.go +++ b/lib/aws/rpc/ec2/routeTable.go @@ -68,10 +68,13 @@ func (p *RouteTableProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[RouteTable_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *RouteTableProvider) Create( @@ -85,9 +88,7 @@ func (p *RouteTableProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *RouteTableProvider) Get( @@ -105,7 +106,7 @@ func (p *RouteTableProvider) Get( } func (p *RouteTableProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(RouteTableToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -136,7 +137,7 @@ func (p *RouteTableProvider) InspectChange( } func (p *RouteTableProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(RouteTableToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -167,7 +168,7 @@ func (p *RouteTableProvider) Delete( func (p *RouteTableProvider) Unmarshal( v *pbstruct.Struct) (*RouteTable, resource.PropertyMap, mapper.DecodeError) { var obj RouteTable - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -176,7 +177,7 @@ func (p *RouteTableProvider) Unmarshal( // RouteTable is a marshalable representation of its corresponding IDL type. type RouteTable struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` VPC resource.ID `json:"vpc"` } diff --git a/lib/aws/rpc/ec2/securityGroup.go b/lib/aws/rpc/ec2/securityGroup.go index c6eb354e6..517e0b8cd 100644 --- a/lib/aws/rpc/ec2/securityGroup.go +++ b/lib/aws/rpc/ec2/securityGroup.go @@ -25,7 +25,7 @@ const SecurityGroupToken = tokens.Type("aws:ec2/securityGroup:SecurityGroup") // SecurityGroupProviderOps is a pluggable interface for SecurityGroup-related management functionality. type SecurityGroupProviderOps interface { Check(ctx context.Context, obj *SecurityGroup) ([]mapper.FieldError, error) - Create(ctx context.Context, obj *SecurityGroup) (resource.ID, *SecurityGroupOuts, error) + Create(ctx context.Context, obj *SecurityGroup) (resource.ID, error) Get(ctx context.Context, id resource.ID) (*SecurityGroup, error) InspectChange(ctx context.Context, id resource.ID, old *SecurityGroup, new *SecurityGroup, diff *resource.ObjectDiff) ([]string, error) @@ -68,10 +68,13 @@ func (p *SecurityGroupProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[SecurityGroup_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *SecurityGroupProvider) Create( @@ -81,16 +84,11 @@ func (p *SecurityGroupProvider) Create( if decerr != nil { return nil, decerr } - id, outs, err := p.ops.Create(ctx, obj) + id, err := p.ops.Create(ctx, obj) if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - Outputs: resource.MarshalProperties( - nil, resource.NewPropertyMap(outs), resource.MarshalOptions{}, - ), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *SecurityGroupProvider) Get( @@ -108,7 +106,7 @@ func (p *SecurityGroupProvider) Get( } func (p *SecurityGroupProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(SecurityGroupToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -128,6 +126,9 @@ func (p *SecurityGroupProvider) InspectChange( if diff.Changed("groupDescription") { replaces = append(replaces, "groupDescription") } + if diff.Changed("groupName") { + replaces = append(replaces, "groupName") + } if diff.Changed("vpc") { replaces = append(replaces, "vpc") } @@ -142,7 +143,7 @@ func (p *SecurityGroupProvider) InspectChange( } func (p *SecurityGroupProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(SecurityGroupToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -173,7 +174,7 @@ func (p *SecurityGroupProvider) Delete( func (p *SecurityGroupProvider) Unmarshal( v *pbstruct.Struct) (*SecurityGroup, resource.PropertyMap, mapper.DecodeError) { var obj SecurityGroup - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -182,23 +183,20 @@ func (p *SecurityGroupProvider) Unmarshal( // SecurityGroup is a marshalable representation of its corresponding IDL type. type SecurityGroup struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` GroupDescription string `json:"groupDescription"` + GroupName *string `json:"groupName,omitempty"` VPC *resource.ID `json:"vpc,omitempty"` SecurityGroupEgress *[]SecurityGroupRule `json:"securityGroupEgress,omitempty"` SecurityGroupIngress *[]SecurityGroupRule `json:"securityGroupIngress,omitempty"` GroupID string `json:"groupID,omitempty"` } -// SecurityGroupOuts is a marshalable representation of its IDL type's output properties. -type SecurityGroupOuts struct { - GroupID string `json:"groupID"` -} - // SecurityGroup's properties have constants to make dealing with diffs and property bags easier. const ( SecurityGroup_Name = "name" SecurityGroup_GroupDescription = "groupDescription" + SecurityGroup_GroupName = "groupName" SecurityGroup_VPC = "vpc" SecurityGroup_SecurityGroupEgress = "securityGroupEgress" SecurityGroup_SecurityGroupIngress = "securityGroupIngress" diff --git a/lib/aws/rpc/ec2/securityGroupEgress.go b/lib/aws/rpc/ec2/securityGroupEgress.go index b269a81d5..f7fdea0df 100644 --- a/lib/aws/rpc/ec2/securityGroupEgress.go +++ b/lib/aws/rpc/ec2/securityGroupEgress.go @@ -68,10 +68,13 @@ func (p *SecurityGroupEgressProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[SecurityGroupEgress_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *SecurityGroupEgressProvider) Create( @@ -85,9 +88,7 @@ func (p *SecurityGroupEgressProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *SecurityGroupEgressProvider) Get( @@ -105,7 +106,7 @@ func (p *SecurityGroupEgressProvider) Get( } func (p *SecurityGroupEgressProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(SecurityGroupEgressToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -157,7 +158,7 @@ func (p *SecurityGroupEgressProvider) InspectChange( } func (p *SecurityGroupEgressProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(SecurityGroupEgressToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -188,7 +189,7 @@ func (p *SecurityGroupEgressProvider) Delete( func (p *SecurityGroupEgressProvider) Unmarshal( v *pbstruct.Struct) (*SecurityGroupEgress, resource.PropertyMap, mapper.DecodeError) { var obj SecurityGroupEgress - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -197,7 +198,7 @@ func (p *SecurityGroupEgressProvider) Unmarshal( // SecurityGroupEgress is a marshalable representation of its corresponding IDL type. type SecurityGroupEgress struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` FromPort float64 `json:"fromPort"` Group resource.ID `json:"group"` IPProtocol string `json:"ipProtocol"` diff --git a/lib/aws/rpc/ec2/securityGroupIngress.go b/lib/aws/rpc/ec2/securityGroupIngress.go index 392a7acb5..af733e7f5 100644 --- a/lib/aws/rpc/ec2/securityGroupIngress.go +++ b/lib/aws/rpc/ec2/securityGroupIngress.go @@ -68,10 +68,13 @@ func (p *SecurityGroupIngressProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[SecurityGroupIngress_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *SecurityGroupIngressProvider) Create( @@ -85,9 +88,7 @@ func (p *SecurityGroupIngressProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *SecurityGroupIngressProvider) Get( @@ -105,7 +106,7 @@ func (p *SecurityGroupIngressProvider) Get( } func (p *SecurityGroupIngressProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(SecurityGroupIngressToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -163,7 +164,7 @@ func (p *SecurityGroupIngressProvider) InspectChange( } func (p *SecurityGroupIngressProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(SecurityGroupIngressToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -194,7 +195,7 @@ func (p *SecurityGroupIngressProvider) Delete( func (p *SecurityGroupIngressProvider) Unmarshal( v *pbstruct.Struct) (*SecurityGroupIngress, resource.PropertyMap, mapper.DecodeError) { var obj SecurityGroupIngress - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -203,7 +204,7 @@ func (p *SecurityGroupIngressProvider) Unmarshal( // SecurityGroupIngress is a marshalable representation of its corresponding IDL type. type SecurityGroupIngress struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` IPProtocol string `json:"ipProtocol"` CIDRIP *string `json:"cidrIp,omitempty"` CIDRIPv6 *string `json:"cidrIpv6,omitempty"` diff --git a/lib/aws/rpc/ec2/subnet.go b/lib/aws/rpc/ec2/subnet.go index d08754da2..a942f9581 100644 --- a/lib/aws/rpc/ec2/subnet.go +++ b/lib/aws/rpc/ec2/subnet.go @@ -68,10 +68,13 @@ func (p *SubnetProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Subnet_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *SubnetProvider) Create( @@ -85,9 +88,7 @@ func (p *SubnetProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *SubnetProvider) Get( @@ -105,7 +106,7 @@ func (p *SubnetProvider) Get( } func (p *SubnetProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(SubnetToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -142,7 +143,7 @@ func (p *SubnetProvider) InspectChange( } func (p *SubnetProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(SubnetToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -173,7 +174,7 @@ func (p *SubnetProvider) Delete( func (p *SubnetProvider) Unmarshal( v *pbstruct.Struct) (*Subnet, resource.PropertyMap, mapper.DecodeError) { var obj Subnet - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -182,7 +183,7 @@ func (p *SubnetProvider) Unmarshal( // Subnet is a marshalable representation of its corresponding IDL type. type Subnet struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` CIDRBlock string `json:"cidrBlock"` VPC resource.ID `json:"vpc"` AvailabilityZone *string `json:"availabilityZone,omitempty"` diff --git a/lib/aws/rpc/ec2/vpc.go b/lib/aws/rpc/ec2/vpc.go index 77ea16848..d8d4834e3 100644 --- a/lib/aws/rpc/ec2/vpc.go +++ b/lib/aws/rpc/ec2/vpc.go @@ -68,10 +68,13 @@ func (p *VPCProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[VPC_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *VPCProvider) Create( @@ -85,9 +88,7 @@ func (p *VPCProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *VPCProvider) Get( @@ -105,7 +106,7 @@ func (p *VPCProvider) Get( } func (p *VPCProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(VPCToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -139,7 +140,7 @@ func (p *VPCProvider) InspectChange( } func (p *VPCProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(VPCToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -170,7 +171,7 @@ func (p *VPCProvider) Delete( func (p *VPCProvider) Unmarshal( v *pbstruct.Struct) (*VPC, resource.PropertyMap, mapper.DecodeError) { var obj VPC - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -179,7 +180,7 @@ func (p *VPCProvider) Unmarshal( // VPC is a marshalable representation of its corresponding IDL type. type VPC struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` CIDRBlock string `json:"cidrBlock"` InstanceTenancy *InstanceTenancy `json:"instanceTenancy,omitempty"` EnableDNSSupport *bool `json:"enableDnsSupport,omitempty"` diff --git a/lib/aws/rpc/ec2/vpcGatewayAttachment.go b/lib/aws/rpc/ec2/vpcGatewayAttachment.go index dc784da36..96750ab34 100644 --- a/lib/aws/rpc/ec2/vpcGatewayAttachment.go +++ b/lib/aws/rpc/ec2/vpcGatewayAttachment.go @@ -68,10 +68,13 @@ func (p *VPCGatewayAttachmentProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[VPCGatewayAttachment_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *VPCGatewayAttachmentProvider) Create( @@ -85,9 +88,7 @@ func (p *VPCGatewayAttachmentProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *VPCGatewayAttachmentProvider) Get( @@ -105,7 +106,7 @@ func (p *VPCGatewayAttachmentProvider) Get( } func (p *VPCGatewayAttachmentProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(VPCGatewayAttachmentToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -139,7 +140,7 @@ func (p *VPCGatewayAttachmentProvider) InspectChange( } func (p *VPCGatewayAttachmentProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(VPCGatewayAttachmentToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -170,7 +171,7 @@ func (p *VPCGatewayAttachmentProvider) Delete( func (p *VPCGatewayAttachmentProvider) Unmarshal( v *pbstruct.Struct) (*VPCGatewayAttachment, resource.PropertyMap, mapper.DecodeError) { var obj VPCGatewayAttachment - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -179,7 +180,7 @@ func (p *VPCGatewayAttachmentProvider) Unmarshal( // VPCGatewayAttachment is a marshalable representation of its corresponding IDL type. type VPCGatewayAttachment struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` VPC resource.ID `json:"vpc"` InternetGateway resource.ID `json:"internetGateway"` } diff --git a/lib/aws/rpc/ec2/vpcPeeringConnection.go b/lib/aws/rpc/ec2/vpcPeeringConnection.go index 07ee1c38c..4d7b304df 100644 --- a/lib/aws/rpc/ec2/vpcPeeringConnection.go +++ b/lib/aws/rpc/ec2/vpcPeeringConnection.go @@ -68,10 +68,13 @@ func (p *VPCPeeringConnectionProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[VPCPeeringConnection_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *VPCPeeringConnectionProvider) Create( @@ -85,9 +88,7 @@ func (p *VPCPeeringConnectionProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *VPCPeeringConnectionProvider) Get( @@ -105,7 +106,7 @@ func (p *VPCPeeringConnectionProvider) Get( } func (p *VPCPeeringConnectionProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(VPCPeeringConnectionToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -139,7 +140,7 @@ func (p *VPCPeeringConnectionProvider) InspectChange( } func (p *VPCPeeringConnectionProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(VPCPeeringConnectionToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -170,7 +171,7 @@ func (p *VPCPeeringConnectionProvider) Delete( func (p *VPCPeeringConnectionProvider) Unmarshal( v *pbstruct.Struct) (*VPCPeeringConnection, resource.PropertyMap, mapper.DecodeError) { var obj VPCPeeringConnection - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -179,7 +180,7 @@ func (p *VPCPeeringConnectionProvider) Unmarshal( // VPCPeeringConnection is a marshalable representation of its corresponding IDL type. type VPCPeeringConnection struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` PeerVPC resource.ID `json:"peerVpc"` VPC resource.ID `json:"vpc"` } diff --git a/lib/aws/rpc/elasticbeanstalk/application.go b/lib/aws/rpc/elasticbeanstalk/application.go index 753e8ec7a..ab5246b95 100644 --- a/lib/aws/rpc/elasticbeanstalk/application.go +++ b/lib/aws/rpc/elasticbeanstalk/application.go @@ -68,10 +68,13 @@ func (p *ApplicationProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Application_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *ApplicationProvider) Create( @@ -85,9 +88,7 @@ func (p *ApplicationProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *ApplicationProvider) Get( @@ -105,7 +106,7 @@ func (p *ApplicationProvider) Get( } func (p *ApplicationProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(ApplicationToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -136,7 +137,7 @@ func (p *ApplicationProvider) InspectChange( } func (p *ApplicationProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(ApplicationToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -167,7 +168,7 @@ func (p *ApplicationProvider) Delete( func (p *ApplicationProvider) Unmarshal( v *pbstruct.Struct) (*Application, resource.PropertyMap, mapper.DecodeError) { var obj Application - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -176,7 +177,7 @@ func (p *ApplicationProvider) Unmarshal( // Application is a marshalable representation of its corresponding IDL type. type Application struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` ApplicationName *string `json:"applicationName,omitempty"` Description *string `json:"description,omitempty"` } diff --git a/lib/aws/rpc/elasticbeanstalk/applicationVersion.go b/lib/aws/rpc/elasticbeanstalk/applicationVersion.go index 9cb466065..4810303c9 100644 --- a/lib/aws/rpc/elasticbeanstalk/applicationVersion.go +++ b/lib/aws/rpc/elasticbeanstalk/applicationVersion.go @@ -68,10 +68,13 @@ func (p *ApplicationVersionProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[ApplicationVersion_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *ApplicationVersionProvider) Create( @@ -85,9 +88,7 @@ func (p *ApplicationVersionProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *ApplicationVersionProvider) Get( @@ -105,7 +106,7 @@ func (p *ApplicationVersionProvider) Get( } func (p *ApplicationVersionProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(ApplicationVersionToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -125,6 +126,9 @@ func (p *ApplicationVersionProvider) InspectChange( if diff.Changed("application") { replaces = append(replaces, "application") } + if diff.Changed("versionLabel") { + replaces = append(replaces, "versionLabel") + } if diff.Changed("sourceBundle") { replaces = append(replaces, "sourceBundle") } @@ -139,7 +143,7 @@ func (p *ApplicationVersionProvider) InspectChange( } func (p *ApplicationVersionProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(ApplicationVersionToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -170,7 +174,7 @@ func (p *ApplicationVersionProvider) Delete( func (p *ApplicationVersionProvider) Unmarshal( v *pbstruct.Struct) (*ApplicationVersion, resource.PropertyMap, mapper.DecodeError) { var obj ApplicationVersion - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -179,8 +183,9 @@ func (p *ApplicationVersionProvider) Unmarshal( // ApplicationVersion is a marshalable representation of its corresponding IDL type. type ApplicationVersion struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Application resource.ID `json:"application"` + VersionLabel *string `json:"versionLabel,omitempty"` Description *string `json:"description,omitempty"` SourceBundle resource.ID `json:"sourceBundle"` } @@ -189,6 +194,7 @@ type ApplicationVersion struct { const ( ApplicationVersion_Name = "name" ApplicationVersion_Application = "application" + ApplicationVersion_VersionLabel = "versionLabel" ApplicationVersion_Description = "description" ApplicationVersion_SourceBundle = "sourceBundle" ) diff --git a/lib/aws/rpc/elasticbeanstalk/environment.go b/lib/aws/rpc/elasticbeanstalk/environment.go index c21358fb7..99f8b03a1 100644 --- a/lib/aws/rpc/elasticbeanstalk/environment.go +++ b/lib/aws/rpc/elasticbeanstalk/environment.go @@ -25,7 +25,7 @@ const EnvironmentToken = tokens.Type("aws:elasticbeanstalk/environment:Environme // EnvironmentProviderOps is a pluggable interface for Environment-related management functionality. type EnvironmentProviderOps interface { Check(ctx context.Context, obj *Environment) ([]mapper.FieldError, error) - Create(ctx context.Context, obj *Environment) (resource.ID, *EnvironmentOuts, error) + Create(ctx context.Context, obj *Environment) (resource.ID, error) Get(ctx context.Context, id resource.ID) (*Environment, error) InspectChange(ctx context.Context, id resource.ID, old *Environment, new *Environment, diff *resource.ObjectDiff) ([]string, error) @@ -68,10 +68,13 @@ func (p *EnvironmentProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Environment_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *EnvironmentProvider) Create( @@ -81,16 +84,11 @@ func (p *EnvironmentProvider) Create( if decerr != nil { return nil, decerr } - id, outs, err := p.ops.Create(ctx, obj) + id, err := p.ops.Create(ctx, obj) if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - Outputs: resource.MarshalProperties( - nil, resource.NewPropertyMap(outs), resource.MarshalOptions{}, - ), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *EnvironmentProvider) Get( @@ -108,7 +106,7 @@ func (p *EnvironmentProvider) Get( } func (p *EnvironmentProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(EnvironmentToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -154,7 +152,7 @@ func (p *EnvironmentProvider) InspectChange( } func (p *EnvironmentProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(EnvironmentToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -185,7 +183,7 @@ func (p *EnvironmentProvider) Delete( func (p *EnvironmentProvider) Unmarshal( v *pbstruct.Struct) (*Environment, resource.PropertyMap, mapper.DecodeError) { var obj Environment - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -194,7 +192,7 @@ func (p *EnvironmentProvider) Unmarshal( // Environment is a marshalable representation of its corresponding IDL type. type Environment struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Application resource.ID `json:"application"` CNAMEPrefix *string `json:"cnamePrefix,omitempty"` Description *string `json:"description,omitempty"` @@ -208,11 +206,6 @@ type Environment struct { EndpointURL string `json:"endpointURL,omitempty"` } -// EnvironmentOuts is a marshalable representation of its IDL type's output properties. -type EnvironmentOuts struct { - EndpointURL string `json:"endpointURL"` -} - // Environment's properties have constants to make dealing with diffs and property bags easier. const ( Environment_Name = "name" diff --git a/lib/aws/rpc/iam/group.go b/lib/aws/rpc/iam/group.go index f78d7157d..96cdffb4d 100644 --- a/lib/aws/rpc/iam/group.go +++ b/lib/aws/rpc/iam/group.go @@ -68,10 +68,13 @@ func (p *GroupProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Group_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *GroupProvider) Create( @@ -85,9 +88,7 @@ func (p *GroupProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *GroupProvider) Get( @@ -105,7 +106,7 @@ func (p *GroupProvider) Get( } func (p *GroupProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(GroupToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -136,7 +137,7 @@ func (p *GroupProvider) InspectChange( } func (p *GroupProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(GroupToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -167,7 +168,7 @@ func (p *GroupProvider) Delete( func (p *GroupProvider) Unmarshal( v *pbstruct.Struct) (*Group, resource.PropertyMap, mapper.DecodeError) { var obj Group - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -176,7 +177,7 @@ func (p *GroupProvider) Unmarshal( // Group is a marshalable representation of its corresponding IDL type. type Group struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` GroupName *string `json:"groupName,omitempty"` ManagedPolicies *[]resource.ID `json:"managedPolicies,omitempty"` Path *string `json:"path,omitempty"` diff --git a/lib/aws/rpc/iam/policy.go b/lib/aws/rpc/iam/policy.go index 2e136d77f..522da1ba8 100644 --- a/lib/aws/rpc/iam/policy.go +++ b/lib/aws/rpc/iam/policy.go @@ -82,10 +82,13 @@ func (p *PolicyProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Policy_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *PolicyProvider) Create( @@ -99,9 +102,7 @@ func (p *PolicyProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *PolicyProvider) Get( @@ -119,7 +120,7 @@ func (p *PolicyProvider) Get( } func (p *PolicyProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(PolicyToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -147,7 +148,7 @@ func (p *PolicyProvider) InspectChange( } func (p *PolicyProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(PolicyToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -178,7 +179,7 @@ func (p *PolicyProvider) Delete( func (p *PolicyProvider) Unmarshal( v *pbstruct.Struct) (*Policy, resource.PropertyMap, mapper.DecodeError) { var obj Policy - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -187,7 +188,7 @@ func (p *PolicyProvider) Unmarshal( // Policy is a marshalable representation of its corresponding IDL type. type Policy struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` PolicyDocument interface{} `json:"policyDocument"` PolicyName string `json:"policyName"` Groups *[]resource.ID `json:"groups,omitempty"` diff --git a/lib/aws/rpc/iam/role.go b/lib/aws/rpc/iam/role.go index 57650bad7..f3354c315 100644 --- a/lib/aws/rpc/iam/role.go +++ b/lib/aws/rpc/iam/role.go @@ -27,7 +27,7 @@ const RoleToken = tokens.Type("aws:iam/role:Role") // RoleProviderOps is a pluggable interface for Role-related management functionality. type RoleProviderOps interface { Check(ctx context.Context, obj *Role) ([]mapper.FieldError, error) - Create(ctx context.Context, obj *Role) (resource.ID, *RoleOuts, error) + Create(ctx context.Context, obj *Role) (resource.ID, error) Get(ctx context.Context, id resource.ID) (*Role, error) InspectChange(ctx context.Context, id resource.ID, old *Role, new *Role, diff *resource.ObjectDiff) ([]string, error) @@ -70,10 +70,13 @@ func (p *RoleProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Role_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *RoleProvider) Create( @@ -83,16 +86,11 @@ func (p *RoleProvider) Create( if decerr != nil { return nil, decerr } - id, outs, err := p.ops.Create(ctx, obj) + id, err := p.ops.Create(ctx, obj) if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - Outputs: resource.MarshalProperties( - nil, resource.NewPropertyMap(outs), resource.MarshalOptions{}, - ), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *RoleProvider) Get( @@ -110,7 +108,7 @@ func (p *RoleProvider) Get( } func (p *RoleProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(RoleToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -144,7 +142,7 @@ func (p *RoleProvider) InspectChange( } func (p *RoleProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(RoleToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -175,7 +173,7 @@ func (p *RoleProvider) Delete( func (p *RoleProvider) Unmarshal( v *pbstruct.Struct) (*Role, resource.PropertyMap, mapper.DecodeError) { var obj Role - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -184,7 +182,7 @@ func (p *RoleProvider) Unmarshal( // Role is a marshalable representation of its corresponding IDL type. type Role struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` AssumeRolePolicyDocument interface{} `json:"assumeRolePolicyDocument"` Path *string `json:"path,omitempty"` RoleName *string `json:"roleName,omitempty"` @@ -193,11 +191,6 @@ type Role struct { ARN __aws.ARN `json:"arn,omitempty"` } -// RoleOuts is a marshalable representation of its IDL type's output properties. -type RoleOuts struct { - ARN __aws.ARN `json:"arn"` -} - // Role's properties have constants to make dealing with diffs and property bags easier. const ( Role_Name = "name" diff --git a/lib/aws/rpc/iam/user.go b/lib/aws/rpc/iam/user.go index fc53ab828..76f692908 100644 --- a/lib/aws/rpc/iam/user.go +++ b/lib/aws/rpc/iam/user.go @@ -82,10 +82,13 @@ func (p *UserProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[User_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *UserProvider) Create( @@ -99,9 +102,7 @@ func (p *UserProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *UserProvider) Get( @@ -119,7 +120,7 @@ func (p *UserProvider) Get( } func (p *UserProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(UserToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -150,7 +151,7 @@ func (p *UserProvider) InspectChange( } func (p *UserProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(UserToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -181,7 +182,7 @@ func (p *UserProvider) Delete( func (p *UserProvider) Unmarshal( v *pbstruct.Struct) (*User, resource.PropertyMap, mapper.DecodeError) { var obj User - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -190,7 +191,7 @@ func (p *UserProvider) Unmarshal( // User is a marshalable representation of its corresponding IDL type. type User struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` UserName *string `json:"userName,omitempty"` Groups *[]resource.ID `json:"groups,omitempty"` LoginProfile *LoginProfile `json:"loginProfile,omitempty"` diff --git a/lib/aws/rpc/kms/key.go b/lib/aws/rpc/kms/key.go index 63967adc1..ba41225f9 100644 --- a/lib/aws/rpc/kms/key.go +++ b/lib/aws/rpc/kms/key.go @@ -68,10 +68,13 @@ func (p *KeyProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Key_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *KeyProvider) Create( @@ -85,9 +88,7 @@ func (p *KeyProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *KeyProvider) Get( @@ -105,7 +106,7 @@ func (p *KeyProvider) Get( } func (p *KeyProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(KeyToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -133,7 +134,7 @@ func (p *KeyProvider) InspectChange( } func (p *KeyProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(KeyToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -164,7 +165,7 @@ func (p *KeyProvider) Delete( func (p *KeyProvider) Unmarshal( v *pbstruct.Struct) (*Key, resource.PropertyMap, mapper.DecodeError) { var obj Key - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -173,7 +174,7 @@ func (p *KeyProvider) Unmarshal( // Key is a marshalable representation of its corresponding IDL type. type Key struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` KeyPolicy interface{} `json:"keyPolicy"` Description *string `json:"description,omitempty"` Enabled *bool `json:"enabled,omitempty"` diff --git a/lib/aws/rpc/lambda/function.go b/lib/aws/rpc/lambda/function.go index 317be78f0..d11bbd768 100644 --- a/lib/aws/rpc/lambda/function.go +++ b/lib/aws/rpc/lambda/function.go @@ -39,7 +39,7 @@ const FunctionToken = tokens.Type("aws:lambda/function:Function") // FunctionProviderOps is a pluggable interface for Function-related management functionality. type FunctionProviderOps interface { Check(ctx context.Context, obj *Function) ([]mapper.FieldError, error) - Create(ctx context.Context, obj *Function) (resource.ID, *FunctionOuts, error) + Create(ctx context.Context, obj *Function) (resource.ID, error) Get(ctx context.Context, id resource.ID) (*Function, error) InspectChange(ctx context.Context, id resource.ID, old *Function, new *Function, diff *resource.ObjectDiff) ([]string, error) @@ -82,10 +82,13 @@ func (p *FunctionProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Function_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *FunctionProvider) Create( @@ -95,16 +98,11 @@ func (p *FunctionProvider) Create( if decerr != nil { return nil, decerr } - id, outs, err := p.ops.Create(ctx, obj) + id, err := p.ops.Create(ctx, obj) if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - Outputs: resource.MarshalProperties( - nil, resource.NewPropertyMap(outs), resource.MarshalOptions{}, - ), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *FunctionProvider) Get( @@ -122,7 +120,7 @@ func (p *FunctionProvider) Get( } func (p *FunctionProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(FunctionToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -150,7 +148,7 @@ func (p *FunctionProvider) InspectChange( } func (p *FunctionProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(FunctionToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -181,7 +179,7 @@ func (p *FunctionProvider) Delete( func (p *FunctionProvider) Unmarshal( v *pbstruct.Struct) (*Function, resource.PropertyMap, mapper.DecodeError) { var obj Function - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -190,7 +188,7 @@ func (p *FunctionProvider) Unmarshal( // Function is a marshalable representation of its corresponding IDL type. type Function struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Code resource.Archive `json:"code"` Handler string `json:"handler"` Role resource.ID `json:"role"` @@ -206,11 +204,6 @@ type Function struct { ARN __aws.ARN `json:"arn,omitempty"` } -// FunctionOuts is a marshalable representation of its IDL type's output properties. -type FunctionOuts struct { - ARN __aws.ARN `json:"arn"` -} - // Function's properties have constants to make dealing with diffs and property bags easier. const ( Function_Name = "name" diff --git a/lib/aws/rpc/lambda/permission.go b/lib/aws/rpc/lambda/permission.go index 135f151b9..2680bf4f7 100644 --- a/lib/aws/rpc/lambda/permission.go +++ b/lib/aws/rpc/lambda/permission.go @@ -70,10 +70,13 @@ func (p *PermissionProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Permission_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *PermissionProvider) Create( @@ -87,9 +90,7 @@ func (p *PermissionProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *PermissionProvider) Get( @@ -107,7 +108,7 @@ func (p *PermissionProvider) Get( } func (p *PermissionProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(PermissionToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -150,7 +151,7 @@ func (p *PermissionProvider) InspectChange( } func (p *PermissionProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(PermissionToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -181,7 +182,7 @@ func (p *PermissionProvider) Delete( func (p *PermissionProvider) Unmarshal( v *pbstruct.Struct) (*Permission, resource.PropertyMap, mapper.DecodeError) { var obj Permission - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -190,7 +191,7 @@ func (p *PermissionProvider) Unmarshal( // Permission is a marshalable representation of its corresponding IDL type. type Permission struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` Action string `json:"action"` Function resource.ID `json:"function"` Principal string `json:"principal"` diff --git a/lib/aws/rpc/s3/bucket.go b/lib/aws/rpc/s3/bucket.go index 4460d128e..64d931600 100644 --- a/lib/aws/rpc/s3/bucket.go +++ b/lib/aws/rpc/s3/bucket.go @@ -68,10 +68,13 @@ func (p *BucketProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Bucket_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *BucketProvider) Create( @@ -85,9 +88,7 @@ func (p *BucketProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *BucketProvider) Get( @@ -105,7 +106,7 @@ func (p *BucketProvider) Get( } func (p *BucketProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(BucketToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -136,7 +137,7 @@ func (p *BucketProvider) InspectChange( } func (p *BucketProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(BucketToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -167,7 +168,7 @@ func (p *BucketProvider) Delete( func (p *BucketProvider) Unmarshal( v *pbstruct.Struct) (*Bucket, resource.PropertyMap, mapper.DecodeError) { var obj Bucket - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -176,7 +177,7 @@ func (p *BucketProvider) Unmarshal( // Bucket is a marshalable representation of its corresponding IDL type. type Bucket struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` BucketName *string `json:"bucketName,omitempty"` AccessControl *CannedACL `json:"accessControl,omitempty"` } diff --git a/lib/aws/rpc/s3/object.go b/lib/aws/rpc/s3/object.go index b18ee340f..96f122781 100644 --- a/lib/aws/rpc/s3/object.go +++ b/lib/aws/rpc/s3/object.go @@ -82,9 +82,7 @@ func (p *ObjectProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *ObjectProvider) Get( @@ -102,7 +100,7 @@ func (p *ObjectProvider) Get( } func (p *ObjectProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(ObjectToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -136,7 +134,7 @@ func (p *ObjectProvider) InspectChange( } func (p *ObjectProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(ObjectToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -167,7 +165,7 @@ func (p *ObjectProvider) Delete( func (p *ObjectProvider) Unmarshal( v *pbstruct.Struct) (*Object, resource.PropertyMap, mapper.DecodeError) { var obj Object - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } diff --git a/lib/aws/rpc/sns/topic.go b/lib/aws/rpc/sns/topic.go index 671a1aab0..c804a6e64 100644 --- a/lib/aws/rpc/sns/topic.go +++ b/lib/aws/rpc/sns/topic.go @@ -68,10 +68,13 @@ func (p *TopicProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Topic_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *TopicProvider) Create( @@ -85,9 +88,7 @@ func (p *TopicProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *TopicProvider) Get( @@ -105,7 +106,7 @@ func (p *TopicProvider) Get( } func (p *TopicProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(TopicToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -136,7 +137,7 @@ func (p *TopicProvider) InspectChange( } func (p *TopicProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(TopicToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -167,7 +168,7 @@ func (p *TopicProvider) Delete( func (p *TopicProvider) Unmarshal( v *pbstruct.Struct) (*Topic, resource.PropertyMap, mapper.DecodeError) { var obj Topic - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -176,7 +177,7 @@ func (p *TopicProvider) Unmarshal( // Topic is a marshalable representation of its corresponding IDL type. type Topic struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` TopicName *string `json:"topicName,omitempty"` DisplayName *string `json:"displayName,omitempty"` Subscription *[]TopicSubscription `json:"subscription,omitempty"` diff --git a/lib/aws/rpc/sqs/queue.go b/lib/aws/rpc/sqs/queue.go index 42106867a..34ec58629 100644 --- a/lib/aws/rpc/sqs/queue.go +++ b/lib/aws/rpc/sqs/queue.go @@ -68,10 +68,13 @@ func (p *QueueProvider) Name( if decerr != nil { return nil, decerr } - if obj.Name == "" { + if obj.Name == nil || *obj.Name == "" { + if req.Unknowns[Queue_Name] { + return nil, errors.New("Name property cannot be computed from unknown outputs") + } return nil, errors.New("Name property cannot be empty") } - return &lumirpc.NameResponse{Name: obj.Name}, nil + return &lumirpc.NameResponse{Name: *obj.Name}, nil } func (p *QueueProvider) Create( @@ -85,9 +88,7 @@ func (p *QueueProvider) Create( if err != nil { return nil, err } - return &lumirpc.CreateResponse{ - Id: string(id), - }, nil + return &lumirpc.CreateResponse{Id: string(id)}, nil } func (p *QueueProvider) Get( @@ -105,7 +106,7 @@ func (p *QueueProvider) Get( } func (p *QueueProvider) InspectChange( - ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) { + ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) { contract.Assert(req.GetType() == string(QueueToken)) id := resource.ID(req.GetId()) old, oldprops, decerr := p.Unmarshal(req.GetOlds()) @@ -139,7 +140,7 @@ func (p *QueueProvider) InspectChange( } func (p *QueueProvider) Update( - ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) { + ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) { contract.Assert(req.GetType() == string(QueueToken)) id := resource.ID(req.GetId()) old, oldprops, err := p.Unmarshal(req.GetOlds()) @@ -170,7 +171,7 @@ func (p *QueueProvider) Delete( func (p *QueueProvider) Unmarshal( v *pbstruct.Struct) (*Queue, resource.PropertyMap, mapper.DecodeError) { var obj Queue - props := resource.UnmarshalProperties(v) + props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true}) result := mapper.MapIU(props.Mappable(), &obj) return &obj, props, result } @@ -179,7 +180,7 @@ func (p *QueueProvider) Unmarshal( // Queue is a marshalable representation of its corresponding IDL type. type Queue struct { - Name string `json:"name"` + Name *string `json:"name,omitempty"` FIFOQueue *bool `json:"fifoQueue,omitempty"` QueueName *string `json:"queueName,omitempty"` ContentBasedDeduplication *bool `json:"contentBasedDeduplication,omitempty"` diff --git a/lib/lumi/resource.ts b/lib/lumi/resource.ts index c9e20a10b..c99b2c3d7 100644 --- a/lib/lumi/resource.ts +++ b/lib/lumi/resource.ts @@ -13,11 +13,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Context } from './context'; - // Resource represents a class whose CRUD operations are implemented by a provider plugin. export abstract class Resource { constructor() { } } +// out indicates that a property is an output from the resource provider. Such properties are treated differently by +// the runtime because their values come from outside of the Lumi type system. Furthermore, the runtime permits +// speculative evaluation of code that depends upon them, in some circumstances, before the real value is known. +export function out(target: Object, propertyKey: string) { + // nothing to do here; this is purely a decorative metadata token. +} + diff --git a/pkg/compiler/ast/expressions.go b/pkg/compiler/ast/expressions.go index 11e75b1a9..7354c4afc 100644 --- a/pkg/compiler/ast/expressions.go +++ b/pkg/compiler/ast/expressions.go @@ -291,18 +291,28 @@ const ( // Prefix-only operators: OpDereference UnaryOperator = "*" - OpAddressof = "&" - OpUnaryPlus = "+" - OpUnaryMinus = "-" - OpLogicalNot = "!" - OpBitwiseNot = "~" + OpAddressof UnaryOperator = "&" + OpUnaryPlus UnaryOperator = "+" + OpUnaryMinus UnaryOperator = "-" + OpLogicalNot UnaryOperator = "!" + OpBitwiseNot UnaryOperator = "~" // These are permitted to be prefix or postfix: - OpPlusPlus = "++" - OpMinusMinus = "--" + OpPlusPlus UnaryOperator = "++" + OpMinusMinus UnaryOperator = "--" ) +func IsPrefixUnaryOperator(op UnaryOperator) bool { + return op == OpDereference || op == OpAddressof || + op == OpUnaryPlus || op == OpUnaryMinus || + op == OpLogicalNot || op == OpBitwiseNot +} + +func IsPreOrPostfixUnaryOperator(op UnaryOperator) bool { + return op == OpPlusPlus || op == OpMinusMinus +} + // BinaryOperatorExpression is the usual C-like binary operator (assignment, logical, operator, or relational). type BinaryOperatorExpression struct { ExpressionNode @@ -323,50 +333,79 @@ const ( // Arithmetic operators: OpAdd BinaryOperator = "+" - OpSubtract = "-" - OpMultiply = "*" - OpDivide = "/" - OpRemainder = "%" - OpExponentiate = "**" + OpSubtract BinaryOperator = "-" + OpMultiply BinaryOperator = "*" + OpDivide BinaryOperator = "/" + OpRemainder BinaryOperator = "%" + OpExponentiate BinaryOperator = "**" // Bitwise operators: - OpBitwiseShiftLeft = "<<" - OpBitwiseShiftRight = ">>" - OpBitwiseAnd = "&" - OpBitwiseOr = "|" - OpBitwiseXor = "^" + OpBitwiseShiftLeft BinaryOperator = "<<" + OpBitwiseShiftRight BinaryOperator = ">>" + OpBitwiseAnd BinaryOperator = "&" + OpBitwiseOr BinaryOperator = "|" + OpBitwiseXor BinaryOperator = "^" // Assignment operators: - OpAssign = "=" - OpAssignSum = "+=" - OpAssignDifference = "-=" - OpAssignProduct = "*=" - OpAssignQuotient = "/=" - OpAssignRemainder = "%=" - OpAssignExponentiation = "**=" - OpAssignBitwiseShiftLeft = "<<=" - OpAssignBitwiseShiftRight = ">>=" - OpAssignBitwiseAnd = "&=" - OpAssignBitwiseOr = "|=" - OpAssignBitwiseXor = "^=" + OpAssign BinaryOperator = "=" + OpAssignSum BinaryOperator = "+=" + OpAssignDifference BinaryOperator = "-=" + OpAssignProduct BinaryOperator = "*=" + OpAssignQuotient BinaryOperator = "/=" + OpAssignRemainder BinaryOperator = "%=" + OpAssignExponentiation BinaryOperator = "**=" + OpAssignBitwiseShiftLeft BinaryOperator = "<<=" + OpAssignBitwiseShiftRight BinaryOperator = ">>=" + OpAssignBitwiseAnd BinaryOperator = "&=" + OpAssignBitwiseOr BinaryOperator = "|=" + OpAssignBitwiseXor BinaryOperator = "^=" // Conditional operators: - OpLogicalAnd = "&&" - OpLogicalOr = "||" + OpLogicalAnd BinaryOperator = "&&" + OpLogicalOr BinaryOperator = "||" // Relational operators: - OpLt = "<" - OpLtEquals = "<=" - OpGt = ">" - OpGtEquals = ">=" - OpEquals = "==" - OpNotEquals = "!=" + OpLt BinaryOperator = "<" + OpLtEquals BinaryOperator = "<=" + OpGt BinaryOperator = ">" + OpGtEquals BinaryOperator = ">=" + OpEquals BinaryOperator = "==" + OpNotEquals BinaryOperator = "!=" ) +func IsArithmeticBinaryOperator(op BinaryOperator) bool { + return op == OpAdd || op == OpSubtract || + op == OpMultiply || op == OpDivide || + op == OpRemainder || op == OpExponentiate +} + +func IsBitwiseBinaryOperator(op BinaryOperator) bool { + return op == OpBitwiseShiftLeft || op == OpBitwiseShiftRight || + op == OpBitwiseAnd || op == OpBitwiseOr || op == OpBitwiseXor +} + +func IsAssignmentBinaryOperator(op BinaryOperator) bool { + return op == OpAssign || op == OpAssignSum || + op == OpAssignDifference || op == OpAssignProduct || + op == OpAssignQuotient || op == OpAssignRemainder || op == OpAssignExponentiation || + op == OpAssignBitwiseShiftLeft || op == OpAssignBitwiseShiftRight || + op == OpAssignBitwiseAnd || op == OpAssignBitwiseOr || op == OpAssignBitwiseXor +} + +func IsConditionalBinaryOperator(op BinaryOperator) bool { + return op == OpLogicalAnd || op == OpLogicalOr +} + +func IsRelationalBinaryOperator(op BinaryOperator) bool { + return op == OpLt || op == OpLtEquals || + op == OpGt || op == OpGtEquals || + op == OpEquals || op == OpNotEquals +} + /* Type Testing */ // CastExpression handles both nominal and structural casts, and will throw an exception upon failure. diff --git a/pkg/compiler/binder/class.go b/pkg/compiler/binder/class.go index 4488e0c26..c958e4f6c 100644 --- a/pkg/compiler/binder/class.go +++ b/pkg/compiler/binder/class.go @@ -21,6 +21,7 @@ import ( "github.com/pulumi/lumi/pkg/compiler/ast" "github.com/pulumi/lumi/pkg/compiler/errors" "github.com/pulumi/lumi/pkg/compiler/symbols" + "github.com/pulumi/lumi/pkg/compiler/types/predef" "github.com/pulumi/lumi/pkg/util/contract" ) @@ -127,8 +128,11 @@ func (b *binder) bindClassProperty(node *ast.ClassProperty, parent *symbols.Clas } } + // If this is a resource property, mark it as latent so that we can speculate before true evaluation. + latent := predef.IsLatentResourceProperty(parent, typ) + // Now inject this into the symbol table and return it. - sym := symbols.NewClassPropertySym(node, parent, typ, get, set) + sym := symbols.NewClassPropertySym(node, parent, typ, get, set, latent) b.ctx.RegisterSymbol(node, sym) return sym } diff --git a/pkg/compiler/symbols/class.go b/pkg/compiler/symbols/class.go index 28cceed38..af3477f04 100644 --- a/pkg/compiler/symbols/class.go +++ b/pkg/compiler/symbols/class.go @@ -66,6 +66,8 @@ func (node *Class) Sealed() bool { return node.Node.Sealed != nil && *node.No func (node *Class) Abstract() bool { return node.Node.Abstract != nil && *node.Node.Abstract } func (node *Class) Record() bool { return node.Node.Record != nil && *node.Node.Record } func (node *Class) Interface() bool { return node.Node.Interface != nil && *node.Node.Interface } +func (node *Class) Latent() bool { return false } +func (node *Class) HasValue() bool { return true } func (node *Class) String() string { return string(node.Token()) } // HasInit returns true if this module has an initialzer associated with it. @@ -142,6 +144,7 @@ type ClassProperty struct { Ty Type Get *ClassMethod Set *ClassMethod + Lat bool } var _ Symbol = (*ClassProperty)(nil) @@ -171,6 +174,7 @@ func (node *ClassProperty) Readonly() bool { return node.Node.Reado func (node *ClassProperty) Static() bool { return node.Node.Static != nil && *node.Node.Static } func (node *ClassProperty) Primary() bool { return node.Node.Primary != nil && *node.Node.Primary } func (node *ClassProperty) Default() *interface{} { return node.Node.Default } +func (node *ClassProperty) Latent() bool { return node.Lat } func (node *ClassProperty) Type() Type { return node.Ty } func (node *ClassProperty) MemberNode() ast.ClassMember { return node.Node } func (node *ClassProperty) MemberName() tokens.ClassMemberName { @@ -182,13 +186,14 @@ func (node *ClassProperty) String() string { return string(node.Token()) // NewClassPropertySym returns a new ClassProperty symbol with the given node and parent. func NewClassPropertySym(node *ast.ClassProperty, parent *Class, ty Type, - get *ClassMethod, set *ClassMethod) *ClassProperty { + get *ClassMethod, set *ClassMethod, latent bool) *ClassProperty { return &ClassProperty{ Node: node, Parent: parent, Ty: ty, Get: get, Set: set, + Lat: latent, } } diff --git a/pkg/compiler/symbols/module.go b/pkg/compiler/symbols/module.go index 384bc5acc..07ffb6f06 100644 --- a/pkg/compiler/symbols/module.go +++ b/pkg/compiler/symbols/module.go @@ -147,6 +147,7 @@ func (node *ModuleProperty) MemberParent() *Module { return node.Parent } func (node *ModuleProperty) ModuleMemberProperty() {} func (node *ModuleProperty) MemberType() Type { return node.Ty } func (node *ModuleProperty) Default() *interface{} { return node.Node.Default } +func (node *ModuleProperty) Latent() bool { return false } func (node *ModuleProperty) Readonly() bool { return node.Node.Readonly != nil && *node.Node.Readonly } func (node *ModuleProperty) Type() Type { return node.Ty } func (node *ModuleProperty) VarNode() ast.Variable { return node.Node } diff --git a/pkg/compiler/symbols/types.go b/pkg/compiler/symbols/types.go index efe08b502..ae8aafff9 100644 --- a/pkg/compiler/symbols/types.go +++ b/pkg/compiler/symbols/types.go @@ -30,6 +30,8 @@ type Type interface { Ctor() Function // this type's constructor (or nil if none). Record() bool // true if this is a record type. Interface() bool // true if this is an interface type. + Latent() bool // true if this is a "latent" type (triggering deferred evaluation). + HasValue() bool // true if this kind of type carries a concrete value. } // Types is a list of type symbols. @@ -37,7 +39,8 @@ type Types []Type // PrimitiveType is an internal representation of a primitive type symbol (any, bool, number, string). type PrimitiveType struct { - Nm tokens.TypeName + Nm tokens.TypeName + Null bool } var _ Symbol = (*PrimitiveType)(nil) @@ -54,10 +57,15 @@ func (node *PrimitiveType) TypeMembers() ClassMemberMap { return noClassMembers func (node *PrimitiveType) Ctor() Function { return nil } func (node *PrimitiveType) Record() bool { return false } func (node *PrimitiveType) Interface() bool { return false } +func (node *PrimitiveType) Latent() bool { return false } +func (node *PrimitiveType) HasValue() bool { return !node.Null } func (node *PrimitiveType) String() string { return string(node.Token()) } -func NewPrimitiveType(nm tokens.TypeName) *PrimitiveType { - return &PrimitiveType{nm} +func NewPrimitiveType(nm tokens.TypeName, null bool) *PrimitiveType { + return &PrimitiveType{ + Nm: nm, + Null: null, + } } // PointerType represents a pointer to any other type. @@ -81,6 +89,8 @@ func (node *PointerType) TypeMembers() ClassMemberMap { return noClassMembers } func (node *PointerType) Ctor() Function { return nil } func (node *PointerType) Record() bool { return false } func (node *PointerType) Interface() bool { return false } +func (node *PointerType) Latent() bool { return false } +func (node *PointerType) HasValue() bool { return true } func (node *PointerType) String() string { return string(node.Token()) } // pointerTypeCache is a cache keyed by token, helping to avoid creating superfluous symbol objects. @@ -99,6 +109,51 @@ func NewPointerType(elem Type) *PointerType { return ptr } +// LatentType is a wrapper over an ordinary type that indicates a particular expression's value is not yet known and +// that it will remain unknown until some future condition is met. In many cases, the interpreter can speculate beyond +// a latent value, producing even more derived latent values. Eventually, of course, the real value must be known in +// order to proceed (e.g., for conditionals), however even in these cases, the interpreter may choose to proceed. +type LatentType struct { + Element Type // the real underlying type. +} + +var _ Symbol = (*LatentType)(nil) +var _ Type = (*LatentType)(nil) + +func (node *LatentType) Name() tokens.Name { + return tokens.Name(string(node.Element.Name())) + ".latent" +} +func (node *LatentType) Token() tokens.Token { + return tokens.Token(string(node.Element.Token())) + ".latent" +} +func (node *LatentType) Special() bool { return false } +func (node *LatentType) Tree() diag.Diagable { return nil } +func (node *LatentType) Base() Type { return nil } +func (node *LatentType) TypeName() tokens.TypeName { return tokens.TypeName(node.Name()) } +func (node *LatentType) TypeToken() tokens.Type { return tokens.Type(node.Token()) } +func (node *LatentType) TypeMembers() ClassMemberMap { return noClassMembers } +func (node *LatentType) Ctor() Function { return nil } +func (node *LatentType) Record() bool { return false } +func (node *LatentType) Interface() bool { return false } +func (node *LatentType) Latent() bool { return true } +func (node *LatentType) HasValue() bool { return false } +func (node *LatentType) String() string { return string(node.Token()) } + +// latentTypeCache is a cache keyed by token, helping to avoid creating superfluous symbol objects. +var latentTypeCache = make(map[tokens.Type]*LatentType) + +// NewLatentType returns an existing type symbol from the cache, if one exists, or allocates a new one otherwise. +func NewLatentType(elem Type) *LatentType { + tok := elem.TypeToken() + if ev, has := latentTypeCache[tok]; has { + return ev + } + + ev := &LatentType{Element: elem} + latentTypeCache[tok] = ev + return ev +} + // ArrayType is an array whose elements are of some other type. type ArrayType struct { Nm tokens.TypeName @@ -120,6 +175,8 @@ func (node *ArrayType) TypeMembers() ClassMemberMap { return noClassMembers } func (node *ArrayType) Ctor() Function { return nil } func (node *ArrayType) Record() bool { return false } func (node *ArrayType) Interface() bool { return false } +func (node *ArrayType) Latent() bool { return false } +func (node *ArrayType) HasValue() bool { return true } func (node *ArrayType) String() string { return string(node.Token()) } // arrayTypeCache is a cache keyed by token, helping to avoid creating superfluous symbol objects. @@ -160,6 +217,8 @@ func (node *MapType) TypeMembers() ClassMemberMap { return noClassMembers } func (node *MapType) Ctor() Function { return nil } func (node *MapType) Record() bool { return false } func (node *MapType) Interface() bool { return false } +func (node *MapType) Latent() bool { return false } +func (node *MapType) HasValue() bool { return true } func (node *MapType) String() string { return string(node.Token()) } // mapTypeCache is a cache keyed by token, helping to avoid creating superfluous symbol objects. @@ -200,6 +259,8 @@ func (node *FunctionType) TypeMembers() ClassMemberMap { return noClassMembers } func (node *FunctionType) Ctor() Function { return nil } func (node *FunctionType) Record() bool { return false } func (node *FunctionType) Interface() bool { return false } +func (node *FunctionType) Latent() bool { return false } +func (node *FunctionType) HasValue() bool { return true } func (node *FunctionType) String() string { return string(node.Token()) } // functionTypeCache is a cache keyed by token, helping to avoid creating superfluous symbol objects. @@ -256,6 +317,8 @@ func (node *ModuleType) TypeMembers() ClassMemberMap { return noClassMembers } func (node *ModuleType) Ctor() Function { return nil } func (node *ModuleType) Record() bool { return false } func (node *ModuleType) Interface() bool { return false } +func (node *ModuleType) Latent() bool { return false } +func (node *ModuleType) HasValue() bool { return true } func (node *ModuleType) String() string { return string(node.Token()) } // moduleTypeCache is a cache keyed by module, helping to avoid creating superfluous symbol objects. @@ -293,6 +356,8 @@ func (node *PrototypeType) TypeMembers() ClassMemberMap { return noClassMembers func (node *PrototypeType) Ctor() Function { return nil } func (node *PrototypeType) Record() bool { return false } func (node *PrototypeType) Interface() bool { return false } +func (node *PrototypeType) Latent() bool { return false } +func (node *PrototypeType) HasValue() bool { return true } func (node *PrototypeType) String() string { return string(node.Token()) } // prototypeTypeCache is a cache keyed by token, helping to avoid creating superfluous symbol objects. diff --git a/pkg/compiler/symbols/vars.go b/pkg/compiler/symbols/vars.go index 6d459988f..2b0642ca5 100644 --- a/pkg/compiler/symbols/vars.go +++ b/pkg/compiler/symbols/vars.go @@ -26,6 +26,7 @@ type Variable interface { Symbol Type() Type Default() *interface{} + Latent() bool Readonly() bool VarNode() ast.Variable } @@ -52,6 +53,7 @@ func (node *LocalVariable) Readonly() bool { } func (node *LocalVariable) Type() Type { return node.Ty } func (node *LocalVariable) Default() *interface{} { return nil } +func (node *LocalVariable) Latent() bool { return false } func (node *LocalVariable) VarNode() ast.Variable { return node.Node } func (node *LocalVariable) String() string { return string(node.Token()) } diff --git a/pkg/compiler/types/convert.go b/pkg/compiler/types/convert.go index 572758518..9fe8a68b1 100644 --- a/pkg/compiler/types/convert.go +++ b/pkg/compiler/types/convert.go @@ -27,6 +27,7 @@ const ( NoConversion Conversion = iota // there is no known conversion (though a cast could work) ImplicitConversion // an implicit conversion exists (including identity) AutoDynamicConversion // an automatic dynamic cast should be used (with possible runtime failure) + LatentConversion // the type converts but the precise value cannot be known yet. ) // Convert returns the sort of conversion available from a given type to another. @@ -66,6 +67,11 @@ func Convert(from symbols.Type, to symbols.Type) Conversion { return ImplicitConversion } } + case *symbols.LatentType: + // A latent type can convert to a regular type provided the underlying type converts. + if c := Convert(t.Element, to); c != NoConversion { + return LatentConversion + } case *symbols.PointerType: // A pointer type can be converted to a non-pointer type, since (like Go), dereferences are implicit. if c := Convert(t.Element, to); c != NoConversion { @@ -138,6 +144,11 @@ func Convert(from symbols.Type, to symbols.Type) Conversion { } } + // If the from is the underlying element type of a latent type, this converts implicitly. + if ev, isev := to.(*symbols.LatentType); isev { + return Convert(from, ev.Element) + } + // Otherwise, we cannot convert. return NoConversion } diff --git a/pkg/compiler/types/predef/resource.go b/pkg/compiler/types/predef/resource.go new file mode 100644 index 000000000..b8eb3f291 --- /dev/null +++ b/pkg/compiler/types/predef/resource.go @@ -0,0 +1,40 @@ +// Licensed to Pulumi Corporation ("Pulumi") under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// Pulumi licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package predef contains a set of tokens and/or symbols that are so-called "predefined"; they map to real abstractions +// defined elsewhere, like the Lumi standard library, but don't actually define them. +package predef + +import ( + "github.com/pulumi/lumi/pkg/compiler/symbols" + "github.com/pulumi/lumi/pkg/compiler/types" +) + +// IsResourceType returns true if the given type symbol represents the standard resource class. +func IsResourceType(t symbols.Type) bool { + return types.HasBaseName(t, LumiStdlibResourceClass) +} + +// IsLatentResourceProperty returns true if the given type symbol t represents the standard resource class and the +// property type symbol pt represents a latent property of that resource type. A latent property is one whose value may +// not be known during certain phases like planning; the interpreter attempts to proceed despite this. +func IsLatentResourceProperty(t symbols.Type, pt symbols.Type) bool { + if IsResourceType(t) { + if _, isfunc := pt.(*symbols.FunctionType); !isfunc { + return true + } + } + return false +} diff --git a/pkg/compiler/types/primitives.go b/pkg/compiler/types/primitives.go index afba49fe2..aa6c1f054 100644 --- a/pkg/compiler/types/primitives.go +++ b/pkg/compiler/types/primitives.go @@ -22,12 +22,12 @@ import ( // All of the primitive types. var ( - Object = symbols.NewPrimitiveType("object") // the base of all types. - Bool = symbols.NewPrimitiveType("bool") // a bool (true or false) primitive. - Number = symbols.NewPrimitiveType("number") // a 64-bit IEEE754 floating point primitive. - String = symbols.NewPrimitiveType("string") // a UTF8 encoded string. - Null = symbols.NewPrimitiveType("null") // the special null literal type. - Dynamic = symbols.NewPrimitiveType("dynamic") // a type that opts into automatic dynamic conversions. + Object = symbols.NewPrimitiveType("object", false) // the base of all types. + Bool = symbols.NewPrimitiveType("bool", false) // a bool (true or false) primitive. + Number = symbols.NewPrimitiveType("number", false) // a 64-bit IEEE754 floating point primitive. + String = symbols.NewPrimitiveType("string", false) // a UTF8 encoded string. + Null = symbols.NewPrimitiveType("null", true) // the special null literal type. + Dynamic = symbols.NewPrimitiveType("dynamic", false) // a type that opts into automatic dynamic conversions. ) // Primitives contains a map of all primitive types, keyed by their token/name. @@ -42,5 +42,5 @@ var Primitives = map[tokens.TypeName]symbols.Type{ // Special types that aren't intended for public use. var ( - Error = symbols.NewPrimitiveType("") // a type for internal compiler errors; not for direct use. + Error = symbols.NewPrimitiveType("", false) // a type for internal compiler errors; not for direct use. ) diff --git a/pkg/eval/alloc.go b/pkg/eval/alloc.go index d322d9a61..c0be56859 100644 --- a/pkg/eval/alloc.go +++ b/pkg/eval/alloc.go @@ -103,8 +103,15 @@ func (a *Allocator) NewFunction(tree diag.Diagable, fnc symbols.Function, this * } // NewPointer allocates a new pointer-like object that wraps the given reference. -func (a *Allocator) NewPointer(tree diag.Diagable, t symbols.Type, ptr *rt.Pointer) *rt.Object { - obj := rt.NewPointerObject(t, ptr) +func (a *Allocator) NewPointer(tree diag.Diagable, elem symbols.Type, ptr *rt.Pointer) *rt.Object { + obj := rt.NewPointerObject(elem, ptr) + a.onNewObject(tree, obj) + return obj +} + +// NewLatent creates a new latent object; that is, one whose value is not yet known. +func (a *Allocator) NewLatent(tree diag.Diagable, elem symbols.Type) *rt.Object { + obj := rt.NewLatentObject(elem) a.onNewObject(tree, obj) return obj } diff --git a/pkg/eval/eval.go b/pkg/eval/eval.go index 9891c4214..c906c0cf8 100644 --- a/pkg/eval/eval.go +++ b/pkg/eval/eval.go @@ -293,18 +293,29 @@ func (e *evaluator) initProperty(this *rt.Object, properties *rt.PropertyMap, // A variable could have a default object; if so, use that; otherwise, null will be substituted automatically. var obj *rt.Object if m.Default() != nil { - decl := m.Tree() - obj = e.alloc.NewConstant(decl, *m.Default()) - if this != nil && e.hooks != nil { - e.hooks.OnVariableAssign(decl, this, tokens.Name(key), nil, obj) - } + // If the variable has a default object, substitute it. + obj = e.alloc.NewConstant(m.Tree(), *m.Default()) + } else if this != nil && m.Latent() { + // If the variable is a latent variable -- that is, assignments may come from outside the system at a later + // date and we want to be able to speculate beyond it -- then stick a latent object in the slot. + obj = e.alloc.NewLatent(m.Tree(), m.Type()) } + + // Ensure the initialized value is advertised to the heap hooks, if any. + if this != nil && obj != nil && e.hooks != nil { + e.hooks.OnVariableAssign(m.Tree(), this, tokens.Name(key), nil, obj) + } + + // If this is a class property, it may have a getter and/or setter; flow them. var get symbols.Function var set symbols.Function if prop, isprop := m.(*symbols.ClassProperty); isprop { get = prop.Getter() set = prop.Setter() } + contract.Assertf(obj == nil || (get == nil && set == nil), "Inits, getters, and setters cannot be mixed") + + // Finally allocate the slot and return the resulting pointer to that slot. ptr := properties.InitAddr(key, obj, false, get, set) return ptr, m.Readonly() case *symbols.Module: @@ -571,6 +582,15 @@ func (e *evaluator) issueUnhandledException(uw *rt.Unwind, err *diag.Diag, args e.Diag().Errorf(err, args...) } +// rejectLatent checks an object's value and, if it latent (not apparent), returns an exception unwind. +func (e *evaluator) rejectLatent(tree diag.Diagable, obj *rt.Object) *rt.Unwind { + if obj != nil && obj.Type().Latent() { + // TODO[pulumi/lumi#170]: support multi-stage planning that speculates beyond conditionals. + return e.NewUnexpectedLatentValueException(tree, obj) + } + return nil +} + // pushModuleScope establishes a new module-wide scope. It returns a function that restores the prior value. func (e *evaluator) pushModuleScope(m *symbols.Module) func() { return e.ctx.PushModule(m) @@ -940,7 +960,7 @@ func (e *evaluator) evalContinueStatement(node *ast.ContinueStatement) *rt.Unwin func (e *evaluator) evalIfStatement(node *ast.IfStatement) *rt.Unwind { // Evaluate the branches explicitly based on the result of the condition node. - cond, uw := e.evalExpression(node.Condition) + cond, uw := e.requireExpressionValue(node.Condition) if uw != nil { return uw } @@ -1037,7 +1057,7 @@ func (e *evaluator) evalLoop(condition *ast.Expression, body ast.Statement, post var test *rt.Object if condition != nil { var uw *rt.Unwind - if test, uw = e.evalExpression(*condition); uw != nil { + if test, uw = e.requireExpressionValue(*condition); uw != nil { return uw } } @@ -1154,6 +1174,20 @@ func (e *evaluator) evalExpression(node ast.Expression) (*rt.Object, *rt.Unwind) } } +// requireExpressionValue evaluates an expression and ensures that it isn't latent; that is, it has a concrete value. +// If it is latent, the function returns a non-nil exception unwind object. +func (e *evaluator) requireExpressionValue(node ast.Expression) (*rt.Object, *rt.Unwind) { + // TODO[pulumi/lumi#170]: eventually we should audit all uses of this routine and, for most if not all of them, + // make them work. Doing so requires a fair bit of machinery around stepwise application of deployments. + obj, uw := e.evalExpression(node) + if uw != nil { + return nil, uw + } else if uw = e.rejectLatent(node, obj); uw != nil { + return nil, uw + } + return obj, nil +} + // evalLValueExpression evaluates an expression for use as an l-value; in particular, this loads the target as a // pointer/reference object, rather than as an ordinary value, so that it can be used in an assignment. This is only // valid on the subset of AST nodes that are legal l-values (very few of them, it turns out). @@ -1204,8 +1238,10 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *rt.Un sze, uw := e.evalExpression(*node.Size) if uw != nil { return nil, uw + } else if sze.Type().Latent() { + // If the array size isn't known, then we will propagate a latent value in its place. + return e.alloc.NewLatent(node, ty), nil } - // TODO: this really ought to be an int, not a float... sz := int(sze.NumberValue()) if sz < 0 { // If the size is less than zero, raise a new error. @@ -1281,6 +1317,11 @@ func (e *evaluator) evalObjectLiteral(node *ast.ObjectLiteral) (*rt.Object, *rt. name, uw := e.evalExpression(p.Property) if uw != nil { return nil, uw + } else if name.Type().Latent() { + // If we are setting a property on the object, and that property's name is dynamically computed + // using a string whose value is not known, then we can't let the object be seen in a concrete + // state. Doing so would mean we can't assure latent propagation to operations on such properties. + return e.alloc.NewLatent(node, ty), nil } property = rt.PropertyKey(name.StringValue()) addr = obj.GetPropertyAddr(property, true, true) @@ -1385,7 +1426,18 @@ type Location struct { Setter symbols.Function // the setter function, if any. } -func (loc *Location) Assign(node diag.Diagable, val *rt.Object) *rt.Unwind { +func (loc *Location) Get(node diag.Diagable) (*rt.Object, *rt.Unwind) { + if loc.Getter != nil { + // If there is a getter, invoke it. + contract.Assert(loc.This != nil) + return loc.e.evalCallSymbol(node, loc.Getter, loc.This) + } + + // Otherwise, just return the object directly. + return loc.Obj, nil +} + +func (loc *Location) Set(node diag.Diagable, val *rt.Object) *rt.Unwind { if loc.Setter != nil { // If the location has a setter, use that for the assignment. contract.Assert(loc.This != nil) @@ -1596,43 +1648,50 @@ func (e *evaluator) evalLoadDynamicCore(node ast.Node, objexpr *ast.Expression, return nil, uw } - // Now go ahead and search the object for a property with the given name. var pv *rt.Pointer var key tokens.Name - if name.Type() == types.Number { - _, isarr := this.Type().(*symbols.ArrayType) - contract.Assertf(isarr, "Expected an array for numeric dynamic load index") - ix := int(name.NumberValue()) - arrv := this.ArrayValue() - // TODO[pulumi/lumi#70]: Although storing arrays as arrays is fine for many circumstances, there are two - // situations that could cause us troubles with ECMAScript compliance. First, negative indices are fine in - // ECMAScript. Second, sparse arrays can be represented more efficiently as a "bag of properties" than as a - // true array that needs to be resized (possibly growing to become enormous in memory usage). - // TODO[pulumi/lumi#70]: We are emulating "ECMAScript-like" array accesses, where -- just like ordinary - // property accesses below -- we will permit indexes that we've never seen before. Out of bounds should - // yield `undefined`, rather than the usual case of throwing an exception, for example. And such - // assignments are to be permitted. This will cause troubles down the road when we do other languages that - // reject out of bounds accesses e.g. Python. An alternative approach would be to require ECMAScript to - // use a runtime library anytime an array is accessed, translating exceptions like this into `undefined`s. - if ix >= len(*arrv) && (lval || try) { - newarr := make([]*rt.Pointer, ix+1) - copy(*arrv, newarr) - *arrv = newarr - } - if ix < len(*arrv) { - pv = (*arrv)[ix] - if pv == nil && (lval || try) { - pv = rt.NewPointer(e.alloc.NewNull(node), false, nil, nil) - (*arrv)[ix] = pv - } - } + if (this != nil && this.Type().Latent()) || name.Type().Latent() { + // If the object or name are latent, we can't possibly proceed, because we do not know what to lookup. + lat := e.alloc.NewLatent(node, types.Dynamic) + pv = rt.NewPointer(lat, false, nil, nil) } else { - contract.Assertf(name.Type() == types.String, "Expected dynamic load name to be a string") - key = tokens.Name(name.StringValue()) - if thisexpr == nil { - pv = e.getDynamicNameAddr(key, lval) + // Otherwise, go ahead and search the object for a property with the given name. + if name.Type() == types.Number { + _, isarr := this.Type().(*symbols.ArrayType) + contract.Assertf(isarr, "Expected an array for numeric dynamic load index") + ix := int(name.NumberValue()) + arrv := this.ArrayValue() + // TODO[pulumi/lumi#70]: Although storing arrays as arrays is fine for many circumstances, there are two + // situations that could cause us troubles with ECMAScript compliance. First, negative indices are fine in + // ECMAScript. Second, sparse arrays can be represented more efficiently as a "bag of properties" than as a + // true array that needs to be resized (possibly growing to become enormous in memory usage). + // TODO[pulumi/lumi#70]: We are emulating "ECMAScript-like" array accesses, where -- just like ordinary + // property accesses below -- we will permit indexes that we've never seen before. Out of bounds should + // yield `undefined`, rather than the usual case of throwing an exception, for example. And such + // assignments are to be permitted. This will cause troubles down the road when we do other languages that + // reject out of bounds accesses e.g. Python. An alternative approach would be to require ECMAScript to + // use a runtime library anytime an array is accessed, translating exceptions like this into `undefined`s. + if ix >= len(*arrv) && (lval || try) { + newarr := make([]*rt.Pointer, ix+1) + copy(*arrv, newarr) + *arrv = newarr + } + if ix < len(*arrv) { + pv = (*arrv)[ix] + if pv == nil && (lval || try) { + nul := e.alloc.NewNull(node) + pv = rt.NewPointer(nul, false, nil, nil) + (*arrv)[ix] = pv + } + } } else { - pv = e.getObjectOrSuperProperty(this, thisexpr, rt.PropertyKey(key), lval || try, lval) + contract.Assertf(name.Type() == types.String, "Expected dynamic load name to be a string") + key = tokens.Name(name.StringValue()) + if thisexpr == nil { + pv = e.getDynamicNameAddr(key, lval) + } else { + pv = e.getObjectOrSuperProperty(this, thisexpr, rt.PropertyKey(key), lval || try, lval) + } } } @@ -1754,8 +1813,10 @@ func (e *evaluator) evalNew(node diag.Diagable, t symbols.Type, args *[]*ast.Cal } func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpression) (*rt.Object, *rt.Unwind) { - // Evaluate the function that we are meant to invoke. - fncobj, uw := e.evalExpression(node.Function) + // Evaluate the function that we are meant to invoke. Note that at the moment we reject latent types; we could + // simply propagate a latent value with the expected return type, however that would risk covering up code paths + // that contain conditionals, something that we don't permit until pulumi/lumi#170 is handled. + fncobj, uw := e.requireExpressionValue(node.Function) if uw != nil { return nil, uw } @@ -1817,8 +1878,8 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres // Evaluate the operand and prepare to use it. var opand *rt.Object var opandloc *Location - if node.Operator == ast.OpAddressof || - node.Operator == ast.OpPlusPlus || node.Operator == ast.OpMinusMinus { + op := node.Operator + if op == ast.OpAddressof || op == ast.OpPlusPlus || op == ast.OpMinusMinus { // These operators require an l-value; so we bind the expression a bit differently. loc, uw := e.evalLValueExpression(node.Operand) if uw != nil { @@ -1837,8 +1898,33 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres } } - // Now switch on the operator and perform its specific operation. - switch node.Operator { + // See if the operand is a latent type. If yes, treat it differently. + if opand.Type().Latent() { + switch op { + case ast.OpDereference: + // The target is a pointer; return the underlying pointer element. + pt := opand.Type().(*symbols.LatentType).Element + et := pt.(*symbols.PointerType).Element + return e.alloc.NewLatent(node, et), nil + case ast.OpAddressof: + // The target is a pointer; return the actual pointer. + pt := opand.Type().(*symbols.LatentType).Element + return e.alloc.NewLatent(node, pt), nil + case ast.OpLogicalNot: + // The target is a boolean; propagate a latent boolean. + return e.alloc.NewLatent(node, types.Bool), nil + case ast.OpUnaryPlus, ast.OpUnaryMinus, ast.OpBitwiseNot, + ast.OpPlusPlus, ast.OpMinusMinus: + // All these operators deal with numbers; so, propagate a latent number. + return e.alloc.NewLatent(node, types.Number), nil + default: + contract.Failf("Unrecognized unary operator: %v", op) + return nil, nil + } + } + + // The value is known; switch on the operator and perform its specific operation. + switch op { case ast.OpDereference: // The target is a pointer. If this is for an l-value, just return it as-is; otherwise, dereference it. ptr := opand.PointerValue() @@ -1869,7 +1955,7 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres old := ptr.Obj() val := old.NumberValue() new := e.alloc.NewNumber(node, val+1) - if uw := opandloc.Assign(node.Operand, new); uw != nil { + if uw := opandloc.Set(node.Operand, new); uw != nil { return nil, uw } if node.Postfix { @@ -1882,7 +1968,7 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres old := ptr.Obj() val := old.NumberValue() new := e.alloc.NewNumber(node, val-1) - if uw := opandloc.Assign(node.Operand, new); uw != nil { + if uw := opandloc.Set(node.Operand, new); uw != nil { return nil, uw } if node.Postfix { @@ -1890,7 +1976,7 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres } return new, nil default: - contract.Failf("Unrecognized unary operator: %v", node.Operator) + contract.Failf("Unrecognized unary operator: %v", op) return nil, nil } } @@ -1899,7 +1985,8 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // Evaluate the operands and prepare to use them. First left, then right. var lhs *rt.Object var lhsloc *Location - if isBinaryAssignmentOperator(node.Operator) { + op := node.Operator + if ast.IsAssignmentBinaryOperator(op) { var uw *rt.Unwind if lhsloc, uw = e.evalLValueExpression(node.Left); uw != nil { return nil, uw @@ -1916,7 +2003,10 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress } // For the logical && and ||, we will only evaluate the rhs it if the lhs was true. - if node.Operator == ast.OpLogicalAnd || node.Operator == ast.OpLogicalOr { + if ast.IsConditionalBinaryOperator(op) { + if uw := e.rejectLatent(node.Left, lhs); uw != nil { + return nil, uw + } if lhs.BoolValue() { return e.evalExpression(node.Right) } @@ -1929,6 +2019,31 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress return nil, uw } + // Check to see if the operator involves latent operations and, if so, return a latent of the right type. + if lhs.Type().Latent() || rhs.Type().Latent() { + if ast.IsArithmeticBinaryOperator(op) || ast.IsBitwiseBinaryOperator(op) { + if op == ast.OpAdd && types.CanConvert(rhs.Type(), types.String) { + // + can involve strings for concatenation. + return e.alloc.NewLatent(node, types.String), nil + } + // All other arithmetic operators deal in terms of numbers. + return e.alloc.NewLatent(node, types.Number), nil + } else if ast.IsAssignmentBinaryOperator(op) { + if op == ast.OpAssign { + // = is an arbitrary type, just use the rhs. + return e.alloc.NewLatent(node, rhs.Type().(*symbols.LatentType).Element), nil + } else if op == ast.OpAssignSum && types.CanConvert(rhs.Type(), types.String) { + // += can involve strings for concatenation. + return e.alloc.NewLatent(node, types.String), nil + } + // All other assignment operators deal in terms of numbers. + return e.alloc.NewLatent(node, types.Number), nil + } else if ast.IsRelationalBinaryOperator(op) { + return e.alloc.NewLatent(node, types.Bool), nil + } + contract.Failf("Expected to resolve latent binary operator expression for operator: %v", op) + } + // Switch on operator to perform the operator's effects. // TODO: anywhere there is type coercion to/from float64/int64/etc., we should be skeptical. Because our numeric // type system is float64-based -- i.e., "JSON-like" -- we often find ourselves doing operations on floats that @@ -1937,11 +2052,11 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // consider integer types in LumiIL, and/or verify that numbers aren't outside of the legal range as part of // verification, and then push the responsibility for presenting valid LumiIL with any required conversions back // up to the LumiLang compilers (compile-time, runtime, or othwerwise, per the language semantics). - switch node.Operator { + switch op { // Arithmetic operators case ast.OpAdd: // If the lhs/rhs are strings, concatenate them; if numbers, + them. - if lhs.Type() == types.String { + if types.CanConvert(rhs.Type(), types.String) { return e.alloc.NewString(node, lhs.StringValue()+rhs.StringValue()), nil } return e.alloc.NewNumber(node, lhs.NumberValue()+rhs.NumberValue()), nil @@ -1986,7 +2101,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // Assignment operators case ast.OpAssign: // The target is an l-value; just overwrite its value, and yield the new value as the result. - if uw := lhsloc.Assign(node.Left, rhs); uw != nil { + if uw := lhsloc.Set(node.Left, rhs); uw != nil { return nil, uw } return rhs, nil @@ -2000,7 +2115,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // Otherwise, the target is a numeric l-value; just += to it, and yield the new value as the result. val = e.alloc.NewNumber(node, ptr.Obj().NumberValue()+rhs.NumberValue()) } - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2008,7 +2123,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just -= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, ptr.Obj().NumberValue()-rhs.NumberValue()) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2016,7 +2131,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just *= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, ptr.Obj().NumberValue()*rhs.NumberValue()) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2024,7 +2139,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just /= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, ptr.Obj().NumberValue()/rhs.NumberValue()) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2032,7 +2147,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just %= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())%int64(rhs.NumberValue()))) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2040,7 +2155,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just raise to rhs as a power, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, math.Pow(ptr.Obj().NumberValue(), rhs.NumberValue())) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2048,7 +2163,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just <<= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())<>= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())>>uint(rhs.NumberValue()))) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2064,7 +2179,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just &= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())&int64(rhs.NumberValue()))) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2072,7 +2187,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just |= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())|int64(rhs.NumberValue()))) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2080,7 +2195,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress // The target is a numeric l-value; just ^= rhs to it, and yield the new value as the result. ptr := lhs.PointerValue() val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())^int64(rhs.NumberValue()))) - if uw := lhsloc.Assign(node.Left, val); uw != nil { + if uw := lhsloc.Set(node.Left, val); uw != nil { return nil, uw } return val, nil @@ -2106,22 +2221,11 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress return e.alloc.NewBool(node, !e.evalBinaryOperatorEquals(lhs, rhs)), nil default: - contract.Failf("Unrecognized binary operator: %v", node.Operator) + contract.Failf("Unrecognized binary operator: %v", op) return nil, nil } } -func isBinaryAssignmentOperator(op ast.BinaryOperator) bool { - switch op { - case ast.OpAssign, ast.OpAssignSum, ast.OpAssignDifference, ast.OpAssignProduct, ast.OpAssignQuotient, - ast.OpAssignRemainder, ast.OpAssignExponentiation, ast.OpAssignBitwiseShiftLeft, ast.OpAssignBitwiseShiftRight, - ast.OpAssignBitwiseAnd, ast.OpAssignBitwiseOr, ast.OpAssignBitwiseXor: - return true - default: - return false - } -} - func (e *evaluator) evalBinaryOperatorEquals(lhs *rt.Object, rhs *rt.Object) bool { if lhs == rhs { return true @@ -2186,7 +2290,7 @@ func (e *evaluator) evalTypeOfExpression(node *ast.TypeOfExpression) (*rt.Object func (e *evaluator) evalConditionalExpression(node *ast.ConditionalExpression) (*rt.Object, *rt.Unwind) { // Evaluate the branches explicitly based on the result of the condition node. - cond, uw := e.evalExpression(node.Condition) + cond, uw := e.requireExpressionValue(node.Condition) if uw != nil { return nil, uw } diff --git a/pkg/eval/exceptions.go b/pkg/eval/exceptions.go index 86c775216..2ecf77eb7 100644 --- a/pkg/eval/exceptions.go +++ b/pkg/eval/exceptions.go @@ -54,3 +54,7 @@ func (e *evaluator) NewInvalidCastException(node diag.Diagable, from symbols.Typ func (e *evaluator) NewIllegalInvokeTargetException(node diag.Diagable, target symbols.Type) *rt.Unwind { return e.NewException(node, "Expected a function as the target of an invoke; got '%v'", target) } + +func (e *evaluator) NewUnexpectedLatentValueException(node diag.Diagable, obj *rt.Object) *rt.Unwind { + return e.NewException(node, "Unexpected latent '%v' value encountered; a concrete value is required", obj.Type()) +} diff --git a/pkg/eval/heapstate/generator.go b/pkg/eval/heapstate/generator.go index db25cddb5..e907ef43a 100644 --- a/pkg/eval/heapstate/generator.go +++ b/pkg/eval/heapstate/generator.go @@ -21,7 +21,6 @@ import ( "github.com/pulumi/lumi/pkg/compiler/core" "github.com/pulumi/lumi/pkg/compiler/symbols" - "github.com/pulumi/lumi/pkg/compiler/types" "github.com/pulumi/lumi/pkg/diag" "github.com/pulumi/lumi/pkg/eval" "github.com/pulumi/lumi/pkg/eval/rt" @@ -171,16 +170,16 @@ func (g *generator) OnVariableAssign(tree diag.Diagable, o *rt.Object, name toke } contract.Assert(deps != nil) - // If the old object is a resource, drop a ref-count. - if old != nil && old.Type() != types.Null { + // If there was an old value, drop the ref-count. + if old != nil && old.Type().HasValue() { c, has := deps[old] contract.Assertf(has, "Expected old resource property to exist in dependency map") contract.Assertf(c > 0, "Expected old resource property ref-count to be > 0 in dependency map") deps[old] = c - 1 } - // If the new object is non-nil, add a ref-count (or a whole new entry if needed). - if nw != nil && nw.Type() != types.Null { + // Add a ref-count for the new value (or a whole new entry if needed). + if nw != nil { if c, has := deps[nw]; has { contract.Assertf(c >= 0, "Expected old resource property ref-count to be >= 0 in dependency map") deps[nw] = c + 1 diff --git a/pkg/eval/rt/object.go b/pkg/eval/rt/object.go index 146694879..e7032b5a8 100644 --- a/pkg/eval/rt/object.go +++ b/pkg/eval/rt/object.go @@ -420,12 +420,19 @@ type FuncStub struct { } // NewPointerObject allocates a new pointer-like object that wraps the given reference. -func NewPointerObject(t symbols.Type, ptr *Pointer) *Object { +func NewPointerObject(elem symbols.Type, ptr *Pointer) *Object { + contract.Require(elem != nil, "elem") contract.Require(ptr != nil, "ptr") - ptrt := symbols.NewPointerType(t) + ptrt := symbols.NewPointerType(elem) return NewPrimitiveObject(ptrt, ptr) } +// NewLatentObject allocates a new pointer-like object that wraps the given reference. +func NewLatentObject(elem symbols.Type) *Object { + contract.Require(elem != nil, "elem") + return NewObject(symbols.NewLatentType(elem), nil, nil, nil) +} + // NewConstantObject returns a new object with the right type and value, based on some constant data. func NewConstantObject(v interface{}) *Object { if v == nil { diff --git a/pkg/resource/analyzer_plugin.go b/pkg/resource/analyzer_plugin.go index 1202b1d89..acad1433c 100644 --- a/pkg/resource/analyzer_plugin.go +++ b/pkg/resource/analyzer_plugin.go @@ -82,12 +82,14 @@ func (a *analyzer) Analyze(url pack.PackageURL) ([]AnalyzeFailure, error) { // AnalyzeResource analyzes a single resource object, and returns any errors that it finds. func (a *analyzer) AnalyzeResource(t tokens.Type, props PropertyMap) ([]AnalyzeResourceFailure, error) { glog.V(7).Infof("analyzer[%v].AnalyzeResource(t=%v,#props=%v) executing", a.name, t, len(props)) + pstr, unks := MarshalPropertiesWithUnknowns(a.ctx, props, MarshalOptions{ + OldURNs: true, // permit old URNs, since this is pre-update. + RawResources: true, // often used during URN creation; IDs won't be ready. + }) req := &lumirpc.AnalyzeResourceRequest{ - Type: string(t), - Properties: MarshalProperties(a.ctx, props, MarshalOptions{ - PermitOlds: true, // permit old URNs, since this is pre-update. - RawURNs: true, // often used during URN creation; IDs won't be ready. - }), + Type: string(t), + Properties: pstr, + Unknowns: unks, } resp, err := a.client.AnalyzeResource(a.ctx.Request(), req) diff --git a/pkg/resource/asset.go b/pkg/resource/asset.go index fb1cbca0d..d575f6820 100644 --- a/pkg/resource/asset.go +++ b/pkg/resource/asset.go @@ -42,6 +42,10 @@ type Asset struct { URI *string `json:"uri,omitempty"` // a URI to a reference fetched (file://, http://, https://, or custom). } +func NewTextAsset(text string) Asset { return Asset{Text: &text} } +func NewPathAsset(path string) Asset { return Asset{Path: &path} } +func NewURIAsset(uri string) Asset { return Asset{URI: &uri} } + func (a Asset) IsText() bool { return a.Text != nil } func (a Asset) IsPath() bool { return a.Path != nil } func (a Asset) IsURI() bool { return a.URI != nil } @@ -212,6 +216,10 @@ type Archive struct { URI *string `json:"uri,omitempty"` // a URI to a remote archive (file://, http://, https://, etc). } +func NewAssetArchive(assets map[string]*Asset) Archive { return Archive{Assets: &assets} } +func NewPathArchive(path string) Archive { return Archive{Path: &path} } +func NewURIArchive(uri string) Archive { return Archive{URI: &uri} } + func (a Archive) IsMap() bool { return a.Assets != nil } func (a Archive) IsPath() bool { return a.Path != nil } func (a Archive) IsURI() bool { return a.URI != nil } diff --git a/pkg/resource/config.go b/pkg/resource/config.go index 0901b4517..c05fd3300 100644 --- a/pkg/resource/config.go +++ b/pkg/resource/config.go @@ -87,7 +87,7 @@ func (cfg *ConfigMap) ApplyConfig(ctx *binder.Context, pkg *symbols.Package, // Allocate a new constant for the value we are about to assign, and assign it to the location. v := (*cfg)[tok] obj := e.NewConstantObject(nil, v) - loc.Assign(tree, obj) + loc.Set(tree, obj) vars[tok] = obj } } diff --git a/pkg/resource/context.go b/pkg/resource/context.go index 84004be6f..a11d015d9 100644 --- a/pkg/resource/context.go +++ b/pkg/resource/context.go @@ -33,13 +33,15 @@ type Context struct { Analyzers map[tokens.QName]Analyzer // a cache of analyzer plugins and their processes. Providers map[tokens.Package]Provider // a cache of provider plugins and their processes. ObjRes objectResourceMap // the resources held inside of this snapshot. - ObjURN objectURNMap // a convenient lookup map for object to urn. - URNRes urnResourceMap // a convenient lookup map for urn to resource. - URNOldIDs urnIDMap // a convenient lookup map for urns to old IDs. + ObjURN objectURNMap // a convenient lookup map for object to URN. + IDURN idURNMap // a convenient lookup map for ID to URN. + URNRes urnResourceMap // a convenient lookup map for URN to resource. + URNOldIDs urnIDMap // a convenient lookup map for URNs to old IDs. } type objectURNMap map[*rt.Object]URN type objectResourceMap map[*rt.Object]Resource +type idURNMap map[ID]URN type urnResourceMap map[URN]Resource type urnIDMap map[URN]ID @@ -50,6 +52,7 @@ func NewContext(d diag.Sink) *Context { Providers: make(map[tokens.Package]Provider), ObjRes: make(objectResourceMap), ObjURN: make(objectURNMap), + IDURN: make(idURNMap), URNRes: make(urnResourceMap), URNOldIDs: make(urnIDMap), } diff --git a/pkg/resource/deployment.go b/pkg/resource/deployment.go index 2dc6df694..51d870fde 100644 --- a/pkg/resource/deployment.go +++ b/pkg/resource/deployment.go @@ -45,6 +45,7 @@ type Deployment struct { ID *ID `json:"id,omitempty"` // the provider ID for this resource, if any. Type tokens.Type `json:"type"` // this resource's full type token. Properties *DeployedPropertyMap `json:"properties,omitempty"` // an untyped bag of properties. + Outputs *[]string `json:"outputs,omitempty"` // an array of properties output by the provider. } // DeployedPropertyMap is a property map from resource key to the underlying property value. @@ -98,66 +99,86 @@ func serializeDeployment(res Resource, reftag string) *Deployment { // Serialize all properties recursively, and add them if non-empty. var props *DeployedPropertyMap - if result, use := serializeProperties(res.Properties(), reftag); use { - props = &result + var outs *[]string + if pmap, pout := serializeProperties(res.Properties(), res.Outputs(), reftag); pmap != nil { + props = &pmap + outs = pout } return &Deployment{ ID: idp, Type: res.Type(), Properties: props, + Outputs: outs, } } // serializeProperties serializes a resource property bag so that it's suitable for serialization. -func serializeProperties(props PropertyMap, reftag string) (DeployedPropertyMap, bool) { - dst := make(DeployedPropertyMap) +func serializeProperties(props PropertyMap, inferred map[PropertyKey]bool, + reftag string) (DeployedPropertyMap, *[]string) { + var dst DeployedPropertyMap + var inf []string for _, k := range StablePropertyKeys(props) { - if v, use := serializeProperty(props[k], reftag); use { - dst[string(k)] = v + if v := serializeProperty(props[k], reftag); v != nil { + ks := string(k) + if dst == nil { + dst = make(DeployedPropertyMap) + } + dst[ks] = v + if inferred != nil && inferred[k] { + inf = append(inf, ks) + } } } - if len(dst) > 0 { - return dst, true + + var pinf *[]string + if len(inf) > 0 { + pinf = &inf } - return nil, false + + return dst, pinf } // serializeProperty serializes a resource property value so that it's suitable for serialization. -func serializeProperty(prop PropertyValue, reftag string) (interface{}, bool) { +func serializeProperty(prop PropertyValue, reftag string) interface{} { + contract.Assert(!prop.IsComputed()) + contract.Assert(!prop.IsOutput()) + // Skip nulls. if prop.IsNull() { - return nil, false + return nil } // For arrays, make sure to recurse. if prop.IsArray() { var arr []interface{} for _, elem := range prop.ArrayValue() { - if v, use := serializeProperty(elem, reftag); use { + if v := serializeProperty(elem, reftag); v != nil { arr = append(arr, v) } } if len(arr) > 0 { - return arr, true + return arr } - return nil, false + return nil } // Also for objects, recurse and use naked properties. if prop.IsObject() { - return serializeProperties(prop.ObjectValue(), reftag) + ps, pinf := serializeProperties(prop.ObjectValue(), nil, reftag) + contract.Assert(pinf == nil) + return ps } // Morph resources into their equivalent `{ "#ref": "" }` form. if prop.IsResource() { return map[string]string{ reftag: string(prop.ResourceValue()), - }, true + } } // All others are returned as-is. - return prop.V, true + return prop.V } func deserializeProperties(props DeployedPropertyMap, reftag string) PropertyMap { @@ -172,36 +193,36 @@ func deserializeProperty(v interface{}, reftag string) PropertyValue { if v != nil { switch w := v.(type) { case bool: - return NewPropertyBool(w) + return NewBoolProperty(w) case float64: - return NewPropertyNumber(w) + return NewNumberProperty(w) case string: - return NewPropertyString(w) + return NewStringProperty(w) case []interface{}: var arr []PropertyValue for _, elem := range w { arr = append(arr, deserializeProperty(elem, reftag)) } - return NewPropertyArray(arr) + return NewArrayProperty(arr) case map[string]interface{}: // If the map has a single entry and it is the reftag, this is a URN. if len(w) == 1 { if tag, has := w[reftag]; has { if tagstr, isstring := tag.(string); isstring { - return NewPropertyResource(URN(tagstr)) + return NewResourceProperty(URN(tagstr)) } } } // Otherwise, this is an arbitrary object value. obj := deserializeProperties(DeployedPropertyMap(w), reftag) - return NewPropertyObject(obj) + return NewObjectProperty(obj) default: contract.Failf("Unrecognized property type: %v", reflect.ValueOf(v)) } } - return NewPropertyNull() + return NewNullProperty() } // DeploymentMap is a map of URN to resource, that also preserves a stable order of its keys. This ensures diff --git a/pkg/resource/env.go b/pkg/resource/env.go index ba443b51b..ecd9a1f03 100644 --- a/pkg/resource/env.go +++ b/pkg/resource/env.go @@ -90,8 +90,16 @@ func DeserializeEnvfile(ctx *Context, envfile *Envfile) (*Env, Snapshot) { if res.ID != nil { id = *res.ID } + resobj := NewResource(id, kvp.Key, res.Type, props) - resources = append(resources, NewResource(id, kvp.Key, res.Type, props)) + // Mark any inferred properties so we know how and when to diff them appropriately. + if res.Outputs != nil { + for _, k := range *res.Outputs { + resobj.MarkOutput(PropertyKey(k)) + } + } + + resources = append(resources, resobj) } } diff --git a/pkg/resource/idl/resource.go b/pkg/resource/idl/resource.go index 5f4a09b9e..d1f8d426c 100644 --- a/pkg/resource/idl/resource.go +++ b/pkg/resource/idl/resource.go @@ -21,5 +21,5 @@ type Resource struct { // NamedResource is a marker struct to indicate that an IDL struct is a named resource. type NamedResource struct { - Name string `lumi:"name,replaces"` + Name *string `lumi:"name,replaces,in"` } diff --git a/pkg/resource/plan.go b/pkg/resource/plan.go index 923543591..2751365cf 100644 --- a/pkg/resource/plan.go +++ b/pkg/resource/plan.go @@ -28,7 +28,6 @@ import ( ) // TODO: concurrency. -// TODO: handle output dependencies // Plan is the output of analyzing resource graphs and contains the steps necessary to perform an infrastructure // deployment. A plan can be generated out of whole cloth from a resource graph -- in the case of new deployments -- @@ -179,7 +178,10 @@ func (p *plan) Apply(prog Progress) (Snapshot, Step, State, error) { for old, new := range p.unchanged { contract.Assert(old.HasID()) contract.Assert(!new.HasID()) - new.SetID(old.ID()) + id := old.ID() + new.SetID(id) + p.ctx.IDURN[id] = new.URN() + old.PropagateOutputs(new) } // Next, walk the plan linked list and apply each step. @@ -303,7 +305,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot, analyzers []tokens.QName) if err != nil { return nil, err } - failures, err := prov.Check(t, props) + failures, err := prov.Check(new) if err != nil { return nil, err } @@ -349,20 +351,31 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot, analyzers []tokens.QName) for _, new := range pb.NewRes { m := new.URN() if old, hasold := pb.Olds[m]; hasold { + contract.Assert(old.Type() == new.Type()) + // The resource exists in both new and old; it could be an update. This resource is an update if one of // these two conditions exist: 1) either the old and new properties don't match or 2) the update impact // is assessed as having to replace the resource, in which case the ID will change. This might have a // cascading impact on subsequent updates too, since those IDs must trigger recreations, etc. - contract.Assert(old.Type() == new.Type()) computed := new.Properties().ReplaceResources(func(r URN) URN { if pb.Replace(r) { // If the resource is being replaced, simply mangle the URN so that it's different; this value // won't actually be used for anything other than the diffing algorithms below. + // TODO[pulumi/lumi#90]: replace this entirely by computed properties and corresponding diffing. r = r.Replace() glog.V(7).Infof("Patched resource '%v's URN property: %v", m, r) } return r }) + + // Propagate old outputs, provided they aren't overwritten in new, for purposes of the diff. + olds := old.Properties() + for k := range old.Outputs() { + if computed.NeedsValue(k) { + computed[k] = olds[k] + } + } + if !old.Properties().DeepEquals(computed) { // See if this update has the effect of deleting and recreating the resource. If so, we need to make // sure to insert the right replacement steps into the graph (a create, replace, and delete). @@ -371,7 +384,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot, analyzers []tokens.QName) if err != nil { return nil, err } - replacements, _, err := prov.InspectChange(old.ID(), old.Type(), old.Properties(), computed) + replacements, _, err := prov.InspectChange(old, new, computed) if err != nil { return nil, err } @@ -680,36 +693,48 @@ func (s *step) Apply() (State, error) { contract.Assert(s.old == nil) contract.Assert(s.new != nil) contract.Assertf(!s.new.HasID(), "Resources being created must not have IDs already") - id, _, rst, err := prov.Create(s.new.Type(), s.new.Properties()) + rst, err := prov.Create(s.new) if err != nil { return rst, err } - s.new.SetID(id) - // TODO[pulumi/lumi#90]: reenable setting outputs when this is fully functional. - // s.new.SetPropertiesFrom(outs) + id := s.new.ID() + contract.Assert(id != "") + + // Copy the old resource, set the new ID, read the resource state back (to fetch outputs), and store them. + s.old = s.new.ShallowClone() + s.p.ctx.IDURN[id] = s.new.URN() + if err := prov.Get(s.new); err != nil { + return StateUnknown, err + } case OpDelete, OpReplaceDelete: // Invoke the Delete RPC function for this provider: contract.Assert(s.old != nil) contract.Assert(s.new == nil) contract.Assertf(s.old.HasID(), "Resources being deleted must have IDs") - if rst, err := prov.Delete(s.old.ID(), s.old.Type()); err != nil { + if rst, err := prov.Delete(s.old); err != nil { return rst, err } case OpUpdate: + // First copy over any old properties that should carry over. + s.old.PropagateOutputs(s.new) + // Invoke the Update RPC function for this provider: contract.Assert(s.old != nil) contract.Assert(s.new != nil) contract.Assert(s.old.Type() == s.new.Type()) contract.Assertf(s.old.HasID(), "Resources being updated must have IDs") id := s.old.ID() - if rst, err := prov.Update(id, s.old.Type(), s.old.Properties(), s.new.Properties()); err != nil { + if rst, err := prov.Update(s.old, s.new); err != nil { return rst, err } + contract.Assert(s.new.ID() == id) - // Propagate the old ID on the new resource, so that the resulting snapshot is correct. - s.new.SetID(id) + // Now read the resource state back in case the update triggered cascading updates to other properties. + if err := prov.Get(s.new); err != nil { + return StateUnknown, err + } case OpReplace: contract.Assert(s.old != nil) diff --git a/pkg/resource/plugin.go b/pkg/resource/plugin.go index 9a3ff18fc..7bcea8ac8 100644 --- a/pkg/resource/plugin.go +++ b/pkg/resource/plugin.go @@ -131,11 +131,13 @@ func newPlugin(ctx *Context, bins []string, prefix string) (*plugin, error) { func execPlugin(bin string) (*plugin, error) { // Flow the logging information if set. var args []string - if cmdutil.LogToStderr { - args = append(args, "--logtostderr") - } - if cmdutil.Verbose > 0 { - args = append(args, "-v="+strconv.Itoa(cmdutil.Verbose)) + if cmdutil.LogFlow { + if cmdutil.LogToStderr { + args = append(args, "--logtostderr") + } + if cmdutil.Verbose > 0 { + args = append(args, "-v="+strconv.Itoa(cmdutil.Verbose)) + } } cmd := exec.Command(bin, args...) diff --git a/pkg/resource/properties.go b/pkg/resource/properties.go index a1b8055d7..4d14b27bd 100644 --- a/pkg/resource/properties.go +++ b/pkg/resource/properties.go @@ -19,7 +19,6 @@ import ( "fmt" "reflect" - "github.com/fatih/structs" "github.com/pkg/errors" "github.com/pulumi/lumi/pkg/tokens" @@ -30,12 +29,15 @@ import ( // PropertyKey is the name of a property. type PropertyKey tokens.Name +// PropertySet is a simple set keyed by property name. +type PropertySet map[PropertyKey]bool + // PropertyMap is a simple map keyed by property name with "JSON-like" values. type PropertyMap map[PropertyKey]PropertyValue // NewPropertyMap turns a struct into a property map, using any JSON tags inside to determine naming. func NewPropertyMap(s interface{}) PropertyMap { - m := structs.Map(s) + m := mapper.Unmap(s) return NewPropertyMapFromMap(m) } @@ -52,6 +54,25 @@ type PropertyValue struct { V interface{} } +// Computed represents the absence of a property value, because it will be computed at some point in the future. It +// contains a property value which represents the underlying expected type of the eventual property value. +type Computed PropertyValue + +// Eventual reflects the eventual type of property value that a computed property will contain. +func (v Computed) Eventual() PropertyValue { + return PropertyValue(v) +} + +// Output is a property value that will eventually be computed by the resource provider. If an output property is +// encountered, it means the resource has not yet been created, and so the output value is unavailable. Note that an +// output property is a special case of computed, but carries additional semantic meaning. +type Output PropertyValue + +// Eventual reflects the eventual type of property value that an output property will contain. +func (v Output) Eventual() PropertyValue { + return PropertyValue(v) +} + type ReqError struct { K PropertyKey } @@ -195,6 +216,34 @@ func (m PropertyMap) ResourceOrErr(k PropertyKey, req bool) (*URN, error) { return nil, nil } +// ComputedOrErr checks that the given property is computed, issuing an error if not; req indicates if required. +func (m PropertyMap) ComputedOrErr(k PropertyKey, req bool) (*Computed, error) { + if v, has := m[k]; has && !v.IsNull() { + if !v.IsComputed() { + return nil, errors.Errorf("property '%v' is not an object (%v)", k, reflect.TypeOf(v.V)) + } + m := v.ComputedValue() + return &m, nil + } else if req { + return nil, &ReqError{k} + } + return nil, nil +} + +// OutputOrErr checks that the given property is an output, issuing an error if not; req indicates if required. +func (m PropertyMap) OutputOrErr(k PropertyKey, req bool) (*Output, error) { + if v, has := m[k]; has && !v.IsNull() { + if !v.IsOutput() { + return nil, errors.Errorf("property '%v' is not an object (%v)", k, reflect.TypeOf(v.V)) + } + m := v.OutputValue() + return &m, nil + } else if req { + return nil, &ReqError{k} + } + return nil, nil +} + // ReqBoolOrErr checks that the given property exists and has the type bool. func (m PropertyMap) ReqBoolOrErr(k PropertyKey) (bool, error) { b, err := m.BoolOrErr(k, true) @@ -267,6 +316,24 @@ func (m PropertyMap) ReqResourceOrErr(k PropertyKey) (URN, error) { return *r, nil } +// ReqComputedOrErr checks that the given property exists and is computed. +func (m PropertyMap) ReqComputedOrErr(k PropertyKey) (Computed, error) { + v, err := m.ComputedOrErr(k, true) + if err != nil { + return Computed{}, err + } + return *v, nil +} + +// ReqOutputOrErr checks that the given property exists and is an output property. +func (m PropertyMap) ReqOutputOrErr(k PropertyKey) (Output, error) { + v, err := m.OutputOrErr(k, true) + if err != nil { + return Output{}, err + } + return *v, nil +} + // OptBoolOrErr checks that the given property has the type bool, if it exists. func (m PropertyMap) OptBoolOrErr(k PropertyKey) (*bool, error) { return m.BoolOrErr(k, false) @@ -307,6 +374,24 @@ func (m PropertyMap) OptResourceOrErr(k PropertyKey) (*URN, error) { return m.ResourceOrErr(k, false) } +// OptComputedOrErr checks that the given property is computed, if it exists. +func (m PropertyMap) OptComputedOrErr(k PropertyKey) (*Computed, error) { + return m.ComputedOrErr(k, false) +} + +// OptOutputOrErr checks that the given property is an output property, if it exists. +func (m PropertyMap) OptOutputOrErr(k PropertyKey) (*Output, error) { + return m.OutputOrErr(k, false) +} + +// NeedsValue returns true if the slot associated with the given property key is missing, contains a null, or is an +// output property that is eagerly awaiting a value to be assigned. That is to say, NeedsValue indicates a semantically +// meaningful value is present (even if it's a computed one whose concrete value isn't yet evaluated). +func (m PropertyMap) NeedsValue(k PropertyKey) bool { + v, has := m[k] + return !has || v.IsNull() || v.IsOutput() +} + // Mappable returns a mapper-compatible object map, suitable for deserialization into structures. func (m PropertyMap) Mappable() mapper.Object { obj := make(mapper.Object) @@ -337,38 +422,59 @@ func (m PropertyMap) ReplaceResources(updater func(URN) URN) PropertyMap { return result } -func NewPropertyNull() PropertyValue { return PropertyValue{nil} } -func NewPropertyBool(v bool) PropertyValue { return PropertyValue{v} } -func NewPropertyNumber(v float64) PropertyValue { return PropertyValue{v} } -func NewPropertyString(v string) PropertyValue { return PropertyValue{v} } -func NewPropertyArray(v []PropertyValue) PropertyValue { return PropertyValue{v} } -func NewPropertyObject(v PropertyMap) PropertyValue { return PropertyValue{v} } -func NewPropertyResource(v URN) PropertyValue { return PropertyValue{v} } +func (m PropertyMap) ShallowClone() PropertyMap { + copy := make(PropertyMap) + for k, v := range m { + copy[k] = v + } + return copy +} + +func (s PropertySet) ShallowClone() PropertySet { + copy := make(PropertySet) + for k, v := range s { + copy[k] = v + } + return copy +} + +func NewNullProperty() PropertyValue { return PropertyValue{nil} } +func NewBoolProperty(v bool) PropertyValue { return PropertyValue{v} } +func NewNumberProperty(v float64) PropertyValue { return PropertyValue{v} } +func NewStringProperty(v string) PropertyValue { return PropertyValue{v} } +func NewArrayProperty(v []PropertyValue) PropertyValue { return PropertyValue{v} } +func NewObjectProperty(v PropertyMap) PropertyValue { return PropertyValue{v} } +func NewResourceProperty(v URN) PropertyValue { return PropertyValue{v} } +func NewComputedProperty(v Computed) PropertyValue { return PropertyValue{v} } +func NewOutputProperty(v Output) PropertyValue { return PropertyValue{v} } + +func MakeComputed(v PropertyValue) PropertyValue { return NewComputedProperty(Computed(v)) } +func MakeOutput(v PropertyValue) PropertyValue { return NewOutputProperty(Output(v)) } // NewPropertyValue turns a value into a property value, provided it is of a legal "JSON-like" kind. func NewPropertyValue(v interface{}) PropertyValue { // If nil, easy peasy, just return a null. if v == nil { - return NewPropertyNull() + return NewNullProperty() } // Else, check for some known primitive types. switch t := v.(type) { case bool: - return NewPropertyBool(t) + return NewBoolProperty(t) case float64: - return NewPropertyNumber(t) + return NewNumberProperty(t) case string: - return NewPropertyString(t) + return NewStringProperty(t) case URN: - return NewPropertyResource(t) + return NewResourceProperty(t) + case Computed: + return NewComputedProperty(t) } // Next, see if it's an array, slice, pointer or struct, and handle each accordingly. rv := reflect.ValueOf(v) switch rk := rv.Type().Kind(); rk { - case reflect.String: - return NewPropertyString(rv.String()) case reflect.Array, reflect.Slice: // If an array or slice, just create an array out of it. var arr []PropertyValue @@ -376,65 +482,232 @@ func NewPropertyValue(v interface{}) PropertyValue { elem := rv.Index(i) arr = append(arr, NewPropertyValue(elem.Interface())) } - return NewPropertyArray(arr) + return NewArrayProperty(arr) case reflect.Ptr: // If a pointer, recurse and return the underlying value. if rv.IsNil() { - return NewPropertyNull() + return NewNullProperty() } return NewPropertyValue(rv.Elem().Interface()) - case reflect.Struct: - obj := NewPropertyMap(rv.Interface()) - return NewPropertyObject(obj) case reflect.Map: - m := map[string]interface{}{} - for _, kv := range rv.MapKeys() { - m[kv.String()] = rv.MapIndex(kv).Interface() + // If a map, create a new property map, provided the keys and values are okay. + obj := PropertyMap{} + for _, key := range rv.MapKeys() { + var pk PropertyKey + switch k := key.Interface().(type) { + case string: + pk = PropertyKey(k) + case PropertyKey: + pk = k + default: + contract.Failf("Unrecognized PropertyMap key type: %v", reflect.TypeOf(key)) + } + val := rv.MapIndex(key) + pv := NewPropertyValue(val.Interface()) + obj[pk] = pv } - obj := NewPropertyMapFromMap(m) - return NewPropertyObject(obj) + return NewObjectProperty(obj) + case reflect.Struct: + obj := NewPropertyMap(v) + return NewObjectProperty(obj) default: - contract.Failf("Unrecognized value type: %v", rk) + contract.Failf("Unrecognized value type: type=%v kind=%v", rv.Type(), rk) } - return NewPropertyNull() + return NewNullProperty() } -func (v PropertyValue) BoolValue() bool { return v.V.(bool) } -func (v PropertyValue) NumberValue() float64 { return v.V.(float64) } -func (v PropertyValue) StringValue() string { return v.V.(string) } -func (v PropertyValue) ArrayValue() []PropertyValue { return v.V.([]PropertyValue) } -func (v PropertyValue) ObjectValue() PropertyMap { return v.V.(PropertyMap) } -func (v PropertyValue) ResourceValue() URN { return v.V.(URN) } +// BoolValue fetches the underlying bool value (panicking if it isn't a bool). +func (v PropertyValue) BoolValue() bool { return v.V.(bool) } +// NumberValue fetches the underlying number value (panicking if it isn't a number). +func (v PropertyValue) NumberValue() float64 { return v.V.(float64) } + +// StringValue fetches the underlying string value (panicking if it isn't a string). +func (v PropertyValue) StringValue() string { return v.V.(string) } + +// ArrayValue fetches the underlying array value (panicking if it isn't a array). +func (v PropertyValue) ArrayValue() []PropertyValue { return v.V.([]PropertyValue) } + +// ObjectValue fetches the underlying object value (panicking if it isn't a object). +func (v PropertyValue) ObjectValue() PropertyMap { return v.V.(PropertyMap) } + +// ResourceValue fetches the underlying resource value (panicking if it isn't a resource). +func (v PropertyValue) ResourceValue() URN { return v.V.(URN) } + +// ComputedValue fetches the underlying computed value (panicking if it isn't a computed). +func (v PropertyValue) ComputedValue() Computed { return v.V.(Computed) } + +// OutputValue fetches the underlying output value (panicking if it isn't a output). +func (v PropertyValue) OutputValue() Output { return v.V.(Output) } + +// IsNull returns true if the underlying value is a null. func (v PropertyValue) IsNull() bool { return v.V == nil } + +// IsBool returns true if the underlying value is a bool. func (v PropertyValue) IsBool() bool { _, is := v.V.(bool) return is } + +// IsNumber returns true if the underlying value is a number. func (v PropertyValue) IsNumber() bool { _, is := v.V.(float64) return is } + +// IsString returns true if the underlying value is a string. func (v PropertyValue) IsString() bool { _, is := v.V.(string) return is } + +// IsArray returns true if the underlying value is an array. func (v PropertyValue) IsArray() bool { _, is := v.V.([]PropertyValue) return is } + +// IsObject returns true if the underlying value is an object. func (v PropertyValue) IsObject() bool { _, is := v.V.(PropertyMap) return is } + +// IsResource returns true if the underlying value is a resource. func (v PropertyValue) IsResource() bool { _, is := v.V.(URN) return is } +// IsComputed returns true if the underlying value is a computed value. +func (v PropertyValue) IsComputed() bool { + _, is := v.V.(Computed) + return is +} + +// IsOutput returns true if the underlying value is an output value. +func (v PropertyValue) IsOutput() bool { + _, is := v.V.(Output) + return is +} + +// CanNull returns true if the target property is capable of holding a null value. +func (v PropertyValue) CanNull() bool { + return true // all properties can be null +} + +// CanBool returns true if the target property is capable of holding a bool value. +func (v PropertyValue) CanBool() bool { + if v.IsNull() || v.IsBool() { + return true + } + if v.IsComputed() { + return v.ComputedValue().Eventual().CanBool() + } + if v.IsOutput() { + return v.OutputValue().Eventual().CanBool() + } + return false +} + +// CanNumber returns true if the target property is capable of holding a number value. +func (v PropertyValue) CanNumber() bool { + if v.IsNull() || v.IsNumber() { + return true + } + if v.IsComputed() { + return v.ComputedValue().Eventual().CanNumber() + } + if v.IsOutput() { + return v.OutputValue().Eventual().CanNumber() + } + return false +} + +// CanString returns true if the target property is capable of holding a string value. +func (v PropertyValue) CanString() bool { + if v.IsNull() || v.IsString() { + return true + } + if v.IsComputed() { + return v.ComputedValue().Eventual().CanString() + } + if v.IsOutput() { + return v.OutputValue().Eventual().CanString() + } + return false +} + +// CanArray returns true if the target property is capable of holding an array value. +func (v PropertyValue) CanArray() bool { + if v.IsNull() || v.IsArray() { + return true + } + if v.IsComputed() { + return v.ComputedValue().Eventual().CanArray() + } + if v.IsOutput() { + return v.OutputValue().Eventual().CanArray() + } + return false +} + +// CanObject returns true if the target property is capable of holding an object value. +func (v PropertyValue) CanObject() bool { + if v.IsNull() || v.IsObject() { + return true + } + if v.IsComputed() { + return v.ComputedValue().Eventual().CanObject() + } + if v.IsOutput() { + return v.OutputValue().Eventual().CanObject() + } + return false +} + +// CanResource returns true if the target property is capable of holding a resource value. +func (v PropertyValue) CanResource() bool { + if v.IsNull() || v.IsResource() { + return true + } + if v.IsComputed() { + return v.ComputedValue().Eventual().CanResource() + } + if v.IsOutput() { + return v.OutputValue().Eventual().CanResource() + } + return false +} + +// TypeString returns a type representation of the property value's holder type. +func (v PropertyValue) TypeString() string { + if v.IsNull() { + return "null" + } else if v.IsBool() { + return "bool" + } else if v.IsNumber() { + return "number" + } else if v.IsString() { + return "string" + } else if v.IsArray() { + return "[]" + } else if v.IsObject() { + return "object" + } else if v.IsResource() { + return "resource" + } else if v.IsComputed() { + return "computed<" + v.ComputedValue().Eventual().TypeString() + ">" + } else if v.IsOutput() { + return "output<" + v.OutputValue().Eventual().TypeString() + ">" + } + contract.Failf("Unrecognized PropertyValue type") + return "" +} + // Mappable returns a mapper-compatible value, suitable for deserialization into structures. func (v PropertyValue) Mappable() mapper.Value { if v.IsNull() { @@ -456,6 +729,16 @@ func (v PropertyValue) Mappable() mapper.Value { return v.ObjectValue().Mappable() } +// String implements the fmt.Stringer interface to add slightly more information to the output. +func (v PropertyValue) String() string { + if v.IsComputed() || v.IsOutput() { + // For computed and output properties, show their type followed by an empty object string. + return fmt.Sprintf("%v{}", v.TypeString()) + } + // For all others, just display the underlying property value. + return fmt.Sprintf("{%v}", v.V) +} + // AllResources finds all resource URNs, transitively throughout the property value, and returns them. func (v PropertyValue) AllResources() map[URN]bool { URNs := make(map[URN]bool) @@ -480,17 +763,17 @@ func (v PropertyValue) AllResources() map[URN]bool { func (v PropertyValue) ReplaceResources(updater func(URN) URN) PropertyValue { if v.IsResource() { m := v.ResourceValue() - return NewPropertyResource(updater(m)) + return NewResourceProperty(updater(m)) } else if v.IsArray() { arr := v.ArrayValue() elems := make([]PropertyValue, len(arr)) for i, elem := range arr { elems[i] = elem.ReplaceResources(updater) } - return NewPropertyArray(elems) + return NewArrayProperty(elems) } else if v.IsObject() { rep := v.ObjectValue().ReplaceResources(updater) - return NewPropertyObject(rep) + return NewObjectProperty(rep) } return v } diff --git a/pkg/resource/properties_diff.go b/pkg/resource/properties_diff.go index ef5950363..4fc23d608 100644 --- a/pkg/resource/properties_diff.go +++ b/pkg/resource/properties_diff.go @@ -127,7 +127,10 @@ func (props PropertyMap) Diff(other PropertyMap) *ObjectDiff { // First find any updates or deletes. for k, old := range props { if new, has := other[k]; has { - if diff := old.Diff(new); diff != nil { + // If a new exists, use it; for output properties, however, ignore differences. + if new.IsOutput() { + sames[k] = old + } else if diff := old.Diff(new); diff != nil { if old.IsNull() { adds[k] = new } else if new.IsNull() { @@ -139,6 +142,8 @@ func (props PropertyMap) Diff(other PropertyMap) *ObjectDiff { sames[k] = old } } else if !new.IsNull() { + // If there was no new property, it has been deleted. + // TODO: differentiate between real deletions. deletes[k] = old } } diff --git a/pkg/resource/provider.go b/pkg/resource/provider.go index 69741f6d3..3bbefefff 100644 --- a/pkg/resource/provider.go +++ b/pkg/resource/provider.go @@ -37,21 +37,21 @@ type Provider interface { // Pkg fetches this provider's package. Pkg() tokens.Package // Check validates that the given property bag is valid for a resource of the given type. - Check(t tokens.Type, props PropertyMap) ([]CheckFailure, error) + Check(res Resource) ([]CheckFailure, error) // Name names a given resource. Sometimes this will be assigned by a developer, and so the provider // simply fetches it from the property bag; other times, the provider will assign this based on its own algorithm. // In any case, resources with the same name must be safe to use interchangeably with one another. - Name(t tokens.Type, props PropertyMap) (tokens.QName, error) - // Create allocates a new instance of the provided resource and returns its unique ID afterwards. - Create(t tokens.Type, props PropertyMap) (ID, PropertyMap, State, error) - // Get reads the instance state identified by id/t, and returns a bag of properties. - Get(id ID, t tokens.Type) (PropertyMap, error) + Name(res Resource) (tokens.QName, error) + // Create allocates a new instance of the provided resource and assigns its unique ID afterwards. + Create(res Resource) (State, error) + // Get reads the instance state identified by res, and copies it into the target resource object. + Get(res Resource) error // InspectChange checks what impacts a hypothetical update will have on the resource's properties. - InspectChange(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) ([]string, PropertyMap, error) + InspectChange(old Resource, new Resource, computed PropertyMap) ([]string, PropertyMap, error) // Update updates an existing resource with new values. - Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) (State, error) + Update(ols Resource, new Resource) (State, error) // Delete tears down an existing resource. - Delete(id ID, t tokens.Type) (State, error) + Delete(res Resource) (State, error) } // CheckFailure indicates that a call to check failed; it contains the property and reason for the failure. diff --git a/pkg/resource/provider_plugin.go b/pkg/resource/provider_plugin.go index fb9ef208a..4bf439c8f 100644 --- a/pkg/resource/provider_plugin.go +++ b/pkg/resource/provider_plugin.go @@ -67,14 +67,18 @@ func NewProvider(ctx *Context, pkg tokens.Package) (Provider, error) { func (p *provider) Pkg() tokens.Package { return p.pkg } // Check validates that the given property bag is valid for a resource of the given type. -func (p *provider) Check(t tokens.Type, props PropertyMap) ([]CheckFailure, error) { +func (p *provider) Check(res Resource) ([]CheckFailure, error) { + t := res.Type() + props := res.Properties() glog.V(7).Infof("resource[%v].Check(t=%v,#props=%v) executing", p.pkg, t, len(props)) + pstr, unks := MarshalPropertiesWithUnknowns(p.ctx, props, MarshalOptions{ + OldURNs: true, // permit old URNs, since this is pre-update. + RawResources: true, // pre-create, IDs won't be ready, just ship over the URNs. + }) req := &lumirpc.CheckRequest{ - Type: string(t), - Properties: MarshalProperties(p.ctx, props, MarshalOptions{ - PermitOlds: true, // permit old URNs, since this is pre-update. - RawURNs: true, // often used during URN creation; IDs won't be ready. - }), + Type: string(t), + Properties: pstr, + Unknowns: unks, } resp, err := p.client.Check(p.ctx.Request(), req) @@ -92,14 +96,18 @@ func (p *provider) Check(t tokens.Type, props PropertyMap) ([]CheckFailure, erro } // Name names a given resource. -func (p *provider) Name(t tokens.Type, props PropertyMap) (tokens.QName, error) { +func (p *provider) Name(res Resource) (tokens.QName, error) { + t := res.Type() + props := res.Properties() glog.V(7).Infof("resource[%v].Name(t=%v,#props=%v) executing", p.pkg, t, len(props)) + pstr, unks := MarshalPropertiesWithUnknowns(p.ctx, props, MarshalOptions{ + OldURNs: true, // permit old URNs, since this is pre-update. + RawResources: true, // pre-create, IDs won't be ready, just ship over the URNs. + }) req := &lumirpc.NameRequest{ - Type: string(t), - Properties: MarshalProperties(p.ctx, props, MarshalOptions{ - PermitOlds: true, // permit old URNs, since this is pre-update. - RawURNs: true, // often used during URN creation; IDs won't be ready. - }), + Type: string(t), + Properties: pstr, + Unknowns: unks, } resp, err := p.client.Name(p.ctx.Request(), req) @@ -113,8 +121,10 @@ func (p *provider) Name(t tokens.Type, props PropertyMap) (tokens.QName, error) return name, nil } -// Create allocates a new instance of the provided resource and returns its unique ID afterwards. -func (p *provider) Create(t tokens.Type, props PropertyMap) (ID, PropertyMap, State, error) { +// Create allocates a new instance of the provided resource and assigns its unique ID afterwards. +func (p *provider) Create(res Resource) (State, error) { + t := res.Type() + props := res.Properties() glog.V(7).Infof("resource[%v].Create(t=%v,#props=%v) executing", p.pkg, t, len(props)) req := &lumirpc.CreateRequest{ Type: string(t), @@ -124,22 +134,25 @@ func (p *provider) Create(t tokens.Type, props PropertyMap) (ID, PropertyMap, St resp, err := p.client.Create(p.ctx.Request(), req) if err != nil { glog.V(7).Infof("resource[%v].Create(t=%v,...) failed: err=%v", p.pkg, t, err) - return ID(""), nil, StateUnknown, err + return StateUnknown, err } id := ID(resp.GetId()) - outs := resp.GetOutputs() - outprops := UnmarshalProperties(outs) - glog.V(7).Infof("resource[%v].Create(t=%v,...) success: id=%v,#outs=%v", p.pkg, t, id, len(outprops)) + glog.V(7).Infof("resource[%v].Create(t=%v,...) success: id=%v", p.pkg, t, id) if id == "" { - return id, outprops, StateUnknown, + return StateUnknown, errors.Errorf("plugin for package '%v' returned empty ID from create '%v'", p.pkg, t) } - return id, outprops, StateOK, nil + res.SetID(id) + return StateOK, nil } -// Get reads the instance state identified by id/t, and returns a bag of properties. -func (p *provider) Get(id ID, t tokens.Type) (PropertyMap, error) { +// Get reads the instance state identified by res, and copies into the resource object. +func (p *provider) Get(res Resource) error { + id := res.ID() + contract.Assert(id != "") + t := res.Type() + props := res.Properties() glog.V(7).Infof("resource[%v].Get(id=%v,t=%v) executing", p.pkg, id, t) req := &lumirpc.GetRequest{ Id: string(id), @@ -149,31 +162,49 @@ func (p *provider) Get(id ID, t tokens.Type) (PropertyMap, error) { resp, err := p.client.Get(p.ctx.Request(), req) if err != nil { glog.V(7).Infof("resource[%v].Get(id=%v,t=%v) failed: err=%v", p.pkg, id, t, err) - return nil, err + return err + } + + if outs := UnmarshalPropertiesInto(p.ctx, resp.GetProperties(), props, MarshalOptions{}); outs != nil { + for out := range outs { + res.MarkOutput(out) + } } - props := UnmarshalProperties(resp.GetProperties()) glog.V(7).Infof("resource[%v].Get(id=%v,t=%v) success: #props=%v", p.pkg, t, id, len(props)) - return props, nil + return nil } // InspectChange checks what impacts a hypothetical update will have on the resource's properties. -func (p *provider) InspectChange(id ID, t tokens.Type, - olds PropertyMap, news PropertyMap) ([]string, PropertyMap, error) { - contract.Requiref(id != "", "id", "not empty") - contract.Requiref(t != "", "t", "not empty") +func (p *provider) InspectChange(old Resource, new Resource, computed PropertyMap) ([]string, PropertyMap, error) { + id := old.ID() + contract.Assert(id != "") + t := old.Type() + contract.Assert(t != "") + contract.Assert(t == new.Type()) + olds := old.Properties() + var news PropertyMap + if computed != nil { + // If a computed map has been supplied, use that for purposes of diffing. + news = computed + } else { + // Otherwise, simply use the raw properties from the new resource itself. + news = new.Properties() + } glog.V(7).Infof("resource[%v].InspectChange(id=%v,t=%v,#olds=%v,#news=%v) executing", p.pkg, id, t, len(olds), len(news)) - req := &lumirpc.ChangeRequest{ + newpstr, newunks := MarshalPropertiesWithUnknowns(p.ctx, news, MarshalOptions{ + RawResources: true, // pre-change, IDs won't be ready, ship over URNs. + }) + req := &lumirpc.InspectChangeRequest{ Id: string(id), Type: string(t), Olds: MarshalProperties(p.ctx, olds, MarshalOptions{ - RawURNs: true, // often used during URN creation; IDs won't be ready. - }), - News: MarshalProperties(p.ctx, news, MarshalOptions{ - RawURNs: true, // often used during URN creation; IDs won't be ready. + RawResources: true, // just leave these as-is, so they match the news. }), + News: newpstr, + Unknowns: newunks, } resp, err := p.client.InspectChange(p.ctx.Request(), req) @@ -183,24 +214,34 @@ func (p *provider) InspectChange(id ID, t tokens.Type, } replaces := resp.GetReplaces() - changes := UnmarshalProperties(resp.GetChanges()) + changes := UnmarshalProperties(p.ctx, resp.GetChanges(), MarshalOptions{RawResources: true}) glog.V(7).Infof("resource[%v].Update(id=%v,t=%v,...) success: #replaces=%v #changes=%v", p.pkg, id, t, len(replaces), len(changes)) + if glog.V(9) { + for i, repl := range replaces { + glog.V(9).Infof("resource[%v].Update(id=%v,t=%v,...) repace #%v: %v", p.pkg, id, t, i, repl) + } + } return replaces, changes, nil } // Update updates an existing resource with new values. -func (p *provider) Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) (State, error) { - contract.Requiref(id != "", "id", "not empty") - contract.Requiref(t != "", "t", "not empty") +func (p *provider) Update(old Resource, new Resource) (State, error) { + id := old.ID() + contract.Assert(id != "") + t := old.Type() + contract.Assert(t != "") + contract.Assert(t == new.Type()) + olds := old.Properties() + news := new.Properties() glog.V(7).Infof("resource[%v].Update(id=%v,t=%v,#olds=%v,#news=%v) executing", p.pkg, id, t, len(olds), len(news)) - req := &lumirpc.ChangeRequest{ + req := &lumirpc.UpdateRequest{ Id: string(id), Type: string(t), Olds: MarshalProperties(p.ctx, olds, MarshalOptions{ - PermitOlds: true, // permit old URNs since these are the old values. + OldURNs: true, // permit old URNs since these are the old values. }), News: MarshalProperties(p.ctx, news, MarshalOptions{}), } @@ -212,13 +253,16 @@ func (p *provider) Update(id ID, t tokens.Type, olds PropertyMap, news PropertyM } glog.V(7).Infof("resource[%v].Update(id=%v,t=%v,...) success", p.pkg, id, t) + new.SetID(id) return StateOK, nil } // Delete tears down an existing resource. -func (p *provider) Delete(id ID, t tokens.Type) (State, error) { - contract.Requiref(id != "", "id", "not empty") - contract.Requiref(t != "", "t", "not empty") +func (p *provider) Delete(res Resource) (State, error) { + id := res.ID() + contract.Assert(id != "") + t := res.Type() + contract.Assert(t != "") glog.V(7).Infof("resource[%v].Delete(id=%v,t=%v) executing", p.pkg, id, t) req := &lumirpc.DeleteRequest{ diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index 4ab71873f..ebb2a666a 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -34,6 +34,15 @@ import ( // ID is a unique resource identifier; it is managed by the provider and is mostly opaque to Lumi. type ID string +func MaybeID(s *string) *ID { + var ret *ID + if s != nil { + id := ID(*s) + ret = &id + } + return ret +} + func (id ID) String() string { return string(id) } func (id *ID) StringPtr() *string { if id == nil { @@ -56,11 +65,15 @@ type Resource interface { URN() URN // the resource's object urn, a human-friendly, unique name for the resource. Type() tokens.Type // the resource's type. Properties() PropertyMap // the resource's property map. + Outputs() PropertySet // the set of properties that were set via outputs from the provider. + ClearOutputs() // clears the outputs set in preparation for an operation that marks them. + MarkOutput(k PropertyKey) // marks a property as an output from the provider. HasID() bool // returns true if the resource has been assigned an ID. SetID(id ID) // assignes an ID to this resource, for those under creation. - SetPropertiesFrom(m PropertyMap) // copies properties from the given map to the resource. HasURN() bool // returns true if the resource has been assigned URN. SetURN(m URN) // assignes a URN to this resource, for those under creation. + ShallowClone() Resource // make a shallow clone of the resource. + PropagateOutputs(other Resource) // copy any required output properties onto the target object. } // State is returned when an error has occurred during a resource provider operation. It indicates whether the @@ -72,39 +85,72 @@ const ( StateUnknown ) -func IsResourceType(t symbols.Type) bool { return types.HasBaseName(t, predef.LumiStdlibResourceClass) } -func IsResourceVertex(v *heapstate.ObjectVertex) bool { return IsResourceType(v.Obj().Type()) } +// IsResourceVertex returns true if the heap graph vertex has an object whose type is the standard resource class. +func IsResourceVertex(v *heapstate.ObjectVertex) bool { + return predef.IsResourceType(v.Obj().Type()) +} type resource struct { id ID // the resource's unique ID, assigned by the resource provider (or blank if uncreated). urn URN // the resource's object urn, a human-friendly, unique name for the resource. t tokens.Type // the resource's type. properties PropertyMap // the resource's property map. + outs PropertySet // the set of properties that were set via outputs from the provider. } func (r *resource) ID() ID { return r.id } func (r *resource) URN() URN { return r.urn } func (r *resource) Type() tokens.Type { return r.t } func (r *resource) Properties() PropertyMap { return r.properties } +func (r *resource) Outputs() PropertySet { return r.outs } + +func (r *resource) ClearOutputs() { + r.outs = nil +} +func (r *resource) MarkOutput(k PropertyKey) { + if r.outs == nil { + r.outs = make(PropertySet) + } + r.outs[k] = true +} func (r *resource) HasID() bool { return (string(r.id) != "") } func (r *resource) SetID(id ID) { contract.Requiref(!r.HasID(), "id", "empty") + glog.V(9).Infof("Assigning ID=%v to resource w/ URN=%v", id, r.urn) r.id = id } -func (r *resource) SetPropertiesFrom(m PropertyMap) { - for k, v := range m { - r.properties[k] = v - } -} - func (r *resource) HasURN() bool { return (string(r.urn) != "") } func (r *resource) SetURN(m URN) { contract.Requiref(!r.HasURN(), "urn", "empty") r.urn = m } +// ShallowClone clones a resource object so that any modifications to it are not reflected in the original. Note that +// the property map is only shallowly cloned so any mutations deep within it may get reflected in the original. +func (r *resource) ShallowClone() Resource { + return &resource{ + id: r.id, + urn: r.urn, + t: r.t, + properties: r.properties.ShallowClone(), + outs: r.outs.ShallowClone(), + } +} + +// PropagateOutputs copies any required output properties onto the target object. +func (r *resource) PropagateOutputs(other Resource) { + rs := r.Properties() + others := other.Properties() + for k := range r.Outputs() { + if others.NeedsValue(k) { + others[k] = rs[k] + other.MarkOutput(k) + } + } +} + // NewResource creates a new resource from the information provided. func NewResource(id ID, urn URN, t tokens.Type, properties PropertyMap) Resource { return &resource{ @@ -112,6 +158,7 @@ func NewResource(id ID, urn URN, t tokens.Type, properties PropertyMap) Resource urn: urn, t: t, properties: properties, + outs: nil, // lazily allocated when provider operations occur. } } @@ -119,7 +166,7 @@ func NewResource(id ID, urn URN, t tokens.Type, properties PropertyMap) Resource // dependencies between resources and must contain all references that could be encountered. func NewObjectResource(ctx *Context, obj *rt.Object) Resource { t := obj.Type() - contract.Assert(IsResourceType(t)) + contract.Assert(predef.IsResourceType(t)) // Extract the urn. This must already exist. urn, hasm := ctx.ObjURN[obj] @@ -135,66 +182,134 @@ func NewObjectResource(ctx *Context, obj *rt.Object) Resource { } } +const resourceOutPropertyToken = tokens.Token("lumi:resource:out") + // cloneObject creates a property map out of a runtime object. The result is fully serializable in the sense that it // can be stored in a JSON or YAML file, serialized over an RPC interface, etc. In particular, any references to other // resources are replaced with their urn equivalents, which the runtime understands. func cloneObject(ctx *Context, obj *rt.Object) PropertyMap { contract.Assert(obj != nil) + + // First accumulate a list of properties that are known to be outputs. + var outs map[PropertyKey]bool + t := obj.Type() + for t != nil { + for name, member := range t.TypeMembers() { + if prop, isprop := member.(*symbols.ClassProperty); isprop { + if attrs := prop.Node.Attributes; attrs != nil { + for _, attr := range *attrs { + if attr.Decorator.Tok == resourceOutPropertyToken { + if outs == nil { + outs = make(map[PropertyKey]bool) + } + outs[PropertyKey(name)] = true + break + } + } + } + } + } + t = t.Base() + } + + // Next walk the object's properties and serialize them in a stable order. src := obj.PropertyValues() dest := make(PropertyMap) for _, k := range src.Stable() { // TODO: detect cycles. obj := src.Get(k) - if v, ok := cloneObjectValue(ctx, obj); ok { - dest[PropertyKey(k)] = v + pky := PropertyKey(k) + var out bool + if outs != nil { + out = outs[pky] + } + if v, ok := cloneObjectProperty(ctx, obj, out); ok { + dest[pky] = v } } return dest } -// cloneObjectValue creates a single property value out of a runtime object. It returns false if the property could not -// be stored in a property (e.g., it is a function or other unrecognized or unserializable runtime object). -func cloneObjectValue(ctx *Context, obj *rt.Object) (PropertyValue, bool) { +// cloneObjectProperty creates a single property value out of a runtime object. It returns false if the property could +// not be stored in a property (e.g., it is a function or other unrecognized or unserializable runtime object). +func cloneObjectProperty(ctx *Context, obj *rt.Object, out bool) (PropertyValue, bool) { t := obj.Type() - if IsResourceType(t) { + + // Serialize resource references as URNs. + if predef.IsResourceType(t) { // For resources, simply look up the urn from the resource map. urn, hasm := ctx.ObjURN[obj] contract.Assertf(hasm, "Missing object reference; possible out of order dependency walk") - return NewPropertyResource(urn), true + return NewResourceProperty(urn), true } + // Serialize simple primitive types with their primitive equivalents. switch t { case types.Null: - return NewPropertyNull(), true + return NewNullProperty(), true case types.Bool: - return NewPropertyBool(obj.BoolValue()), true + return NewBoolProperty(obj.BoolValue()), true case types.Number: - return NewPropertyNumber(obj.NumberValue()), true + return NewNumberProperty(obj.NumberValue()), true case types.String: - return NewPropertyString(obj.StringValue()), true + return NewStringProperty(obj.StringValue()), true case types.Object, types.Dynamic: obj := cloneObject(ctx, obj) // an object literal, clone it - return NewPropertyObject(obj), true + return NewObjectProperty(obj), true } + // Serialize arrays, maps, and object instances in the obvious way. + // TODO: handle symbols.MapType. switch t.(type) { case *symbols.ArrayType: var result []PropertyValue for _, e := range *obj.ArrayValue() { - if v, ok := cloneObjectValue(ctx, e.Obj()); ok { + if v, ok := cloneObjectProperty(ctx, e.Obj(), false); ok { result = append(result, v) } } - return NewPropertyArray(result), true + return NewArrayProperty(result), true case *symbols.Class: obj := cloneObject(ctx, obj) // a class, just deep clone it - return NewPropertyObject(obj), true + return NewObjectProperty(obj), true } - // TODO: handle symbols.MapType. - // TODO: it's unclear if we should do something more drastic here. There will always be unrecognized property - // kinds because objects contain things like constructors, methods, etc. But we may want to ratchet this a bit. - glog.V(5).Infof("Ignoring object value of type '%v': unrecognized kind %v", t, reflect.TypeOf(t)) + // If a latent value, we can propagate an unknown value, but only for certain cases. + if t.Latent() { + // If this is an output property, then this property will turn into an output. Otherwise, it will be marked + // completed. An output property is permitted in more places by virtue of the fact that it is expected not to + // exist during resource create operations, whereas all computed properties should have been resolved by then. + var makeProperty func(PropertyValue) PropertyValue + if out { + makeProperty = MakeOutput + } else { + makeProperty = MakeComputed + } + + future := t.(*symbols.LatentType).Element + switch future { + case types.Null: + return makeProperty(NewNullProperty()), true + case types.Bool: + return makeProperty(NewBoolProperty(false)), true + case types.Number: + return makeProperty(NewNumberProperty(0)), true + case types.String: + return makeProperty(NewStringProperty("")), true + case types.Object, types.Dynamic: + return makeProperty(NewObjectProperty(make(PropertyMap))), true + } + switch future.(type) { + case *symbols.ArrayType: + return makeProperty(NewArrayProperty(nil)), true + case *symbols.Class: + return makeProperty(NewObjectProperty(make(PropertyMap))), true + } + } + + // We can safely skip serializing functions, however, anything else is unexpected at this point. + _, isfunc := t.(*symbols.FunctionType) + contract.Assertf(isfunc, "Unrecognized resource property object type '%v' (%v)", t, reflect.TypeOf(t)) return PropertyValue{}, false } diff --git a/pkg/resource/rpc.go b/pkg/resource/rpc.go index 5ad4c5da5..ed8a52746 100644 --- a/pkg/resource/rpc.go +++ b/pkg/resource/rpc.go @@ -27,25 +27,45 @@ import ( // MarshalOptions controls the marshaling of RPC structures. type MarshalOptions struct { - PermitOlds bool // true to permit old URNs in the properties (e.g., for pre-update). - RawURNs bool // true to marshal URNs "as-is"; often used when ID mappings aren't known yet. + OldURNs bool // true to permit old URNs in the properties (e.g., for pre-update). + RawResources bool // true to marshal resources "as-is"; often used when ID mappings aren't known yet. } -// MarshalProperties marshals a resource's property map as a "JSON-like" protobuf structure. Any URNs are replaced -// with their resource IDs during marshaling; it is an error to marshal a URN for a resource without an ID. -func MarshalProperties(ctx *Context, props PropertyMap, opts MarshalOptions) *structpb.Struct { +// MarshalPropertiesWithUnknowns marshals a resource's property map as a "JSON-like" protobuf structure. Any URNs are +// replaced with their resource IDs during marshaling; it is an error to marshal a URN for a resource without an ID. A +// map of any unknown properties encountered during marshaling (latent values) is returned on the side; these values are +// marshaled using the default value in the returned structure and so this map is essential for interpreting results. +func MarshalPropertiesWithUnknowns( + ctx *Context, props PropertyMap, opts MarshalOptions) (*structpb.Struct, map[string]bool) { + var unk map[string]bool result := &structpb.Struct{ Fields: make(map[string]*structpb.Value), } for _, key := range StablePropertyKeys(props) { - if v, use := MarshalPropertyValue(ctx, props[key], opts); use { - result.Fields[string(key)] = v + if v := props[key]; !v.IsOutput() { // always skip output properties. + mv, known := MarshalPropertyValue(ctx, props[key], opts) + result.Fields[string(key)] = mv + if !known { + if unk == nil { + unk = make(map[string]bool) + } + unk[string(key)] = true // remember that this property was unknown, tainting this whole object. + } } } - return result + return result, unk } -// MarshalPropertyValue marshals a single resource property value into its "JSON-like" value representation. +// MarshalProperties performs ordinary marshaling of a resource's properties but then validates afterwards that all +// fields were known (in other words, no latent properties were encountered). +func MarshalProperties(ctx *Context, props PropertyMap, opts MarshalOptions) *structpb.Struct { + pstr, unks := MarshalPropertiesWithUnknowns(ctx, props, opts) + contract.Assertf(unks == nil, "Unexpected unknown properties during final marshaling") + return pstr +} + +// MarshalPropertyValue marshals a single resource property value into its "JSON-like" value representation. The +// boolean return value indicates whether the value was known (true) or unknown (false). func MarshalPropertyValue(ctx *Context, v PropertyValue, opts MarshalOptions) (*structpb.Value, bool) { if v.IsNull() { return &structpb.Value{ @@ -72,34 +92,39 @@ func MarshalPropertyValue(ctx *Context, v PropertyValue, opts MarshalOptions) (* }, }, true } else if v.IsArray() { + outcome := true var elems []*structpb.Value for _, elem := range v.ArrayValue() { - if elemv, use := MarshalPropertyValue(ctx, elem, opts); use { - elems = append(elems, elemv) - } + elemv, known := MarshalPropertyValue(ctx, elem, opts) + outcome = outcome && known + elems = append(elems, elemv) } return &structpb.Value{ Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: elems}, }, - }, true + }, outcome } else if v.IsObject() { + obj, unks := MarshalPropertiesWithUnknowns(ctx, v.ObjectValue(), opts) return &structpb.Value{ Kind: &structpb.Value_StructValue{ - StructValue: MarshalProperties(ctx, v.ObjectValue(), opts), + StructValue: obj, }, - }, true + }, unks == nil } else if v.IsResource() { + // During marshaling, the wire format of a resource URN is the provider ID; in some circumstances, this is + // not desirable -- such as when the provider is marshaling data for its own use, or when the IDs are not + // yet ready -- in which case RawResources == true and we simply marshal the URN as-is. var wire string m := v.ResourceValue() - if opts.RawURNs { + if opts.RawResources { wire = string(m) } else { contract.Assertf(ctx != nil, "Resource encountered with a nil context; URN not recoverable") var id ID if res, has := ctx.URNRes[m]; has { id = res.ID() // found a new resource with this ID, use it. - } else if oldid, has := ctx.URNOldIDs[m]; opts.PermitOlds && has { + } else if oldid, has := ctx.URNOldIDs[m]; opts.OldURNs && has { id = oldid // found an old resource, maybe deleted, so use that. } else { contract.Failf("Expected resource URN '%v' to exist at marshal time", m) @@ -107,24 +132,44 @@ func MarshalPropertyValue(ctx *Context, v PropertyValue, opts MarshalOptions) (* contract.Assertf(id != "", "Expected resource URN '%v' to have an ID at marshal time", m) wire = string(id) } - glog.V(7).Infof("Serializing resource URN '%v' as '%v' (raw=%v)", m, wire, opts.RawURNs) + glog.V(7).Infof("Serializing resource URN '%v' as '%v' (raw=%v)", m, wire, opts.RawResources) return &structpb.Value{ Kind: &structpb.Value_StringValue{ StringValue: wire, }, }, true + } else if v.IsComputed() { + e := v.ComputedValue().Eventual() + contract.Assert(!e.IsComputed() && !e.IsOutput()) + w, known := MarshalPropertyValue(ctx, e, opts) + contract.Assert(known) + return w, false + } else if v.IsOutput() { + e := v.OutputValue().Eventual() + contract.Assert(!e.IsComputed() && !e.IsOutput()) + w, known := MarshalPropertyValue(ctx, e, opts) + contract.Assert(known) + return w, false } contract.Failf("Unrecognized property value: %v (type=%v)", v.V, reflect.TypeOf(v.V)) return nil, true } -// UnmarshalProperties unmarshals a "JSON-like" protobuf structure into a resource property map. -func UnmarshalProperties(props *structpb.Struct) PropertyMap { +// UnmarshalProperties unmarshals a "JSON-like" protobuf structure into a new resource property map. +func UnmarshalProperties(ctx *Context, props *structpb.Struct, opts MarshalOptions) PropertyMap { result := make(PropertyMap) - if props == nil { - return result + if props != nil { + UnmarshalPropertiesInto(ctx, props, result, opts) } + return result +} + +// UnmarshalPropertiesInto unmarshals a "JSON-like" protobuf structure into an existing resource property map. It +// returns a set of properties that were present in the source but not the destination (or nil if none). +func UnmarshalPropertiesInto(ctx *Context, props *structpb.Struct, t PropertyMap, + opts MarshalOptions) map[PropertyKey]bool { + contract.Assert(props != nil) // First sort the keys so we enumerate them in order (in case errors happen, we want determinism). var keys []string @@ -134,39 +179,96 @@ func UnmarshalProperties(props *structpb.Struct) PropertyMap { sort.Strings(keys) // And now unmarshal every field it into the map. + var added map[PropertyKey]bool for _, k := range keys { - result[PropertyKey(k)] = UnmarshalPropertyValue(props.Fields[k]) + pk := PropertyKey(k) + v := t[pk] + add := t.NeedsValue(pk) + UnmarshalPropertyValueInto(ctx, props.Fields[k], &v, opts) + contract.Assert(!v.IsComputed() && !v.IsOutput()) + t[pk] = v + if add && !v.IsNull() { + if added == nil { + added = make(map[PropertyKey]bool) + } + added[pk] = true + } } + return added +} +// UnmarshalPropertyValue unmarshals a single "JSON-like" value into a new property value. +func UnmarshalPropertyValue(ctx *Context, v *structpb.Value, opts MarshalOptions) PropertyValue { + var result PropertyValue + UnmarshalPropertyValueInto(ctx, v, &result, opts) return result } -// UnmarshalPropertyValue unmarshals a single "JSON-like" value into its property form. -func UnmarshalPropertyValue(v *structpb.Value) PropertyValue { - if v != nil { - switch v.Kind.(type) { - case *structpb.Value_NullValue: - return NewPropertyNull() - case *structpb.Value_BoolValue: - return NewPropertyBool(v.GetBoolValue()) - case *structpb.Value_NumberValue: - return NewPropertyNumber(v.GetNumberValue()) - case *structpb.Value_StringValue: - // TODO: we have no way of determining that this is a resource ID; consider tagging. - return NewPropertyString(v.GetStringValue()) - case *structpb.Value_ListValue: - var elems []PropertyValue - lst := v.GetListValue() - for _, elem := range lst.GetValues() { - elems = append(elems, UnmarshalPropertyValue(elem)) - } - return NewPropertyArray(elems) - case *structpb.Value_StructValue: - props := UnmarshalProperties(v.GetStructValue()) - return NewPropertyObject(props) - default: - contract.Failf("Unrecognized structpb value kind: %v", reflect.TypeOf(v.Kind)) +// UnmarshalPropertyValueInto unmarshals a single "JSON-like" value into an existing property value slot. The existing +// slot may be used to drive transformations, if necessary, such as recovering resource URNs on the receiver side. +func UnmarshalPropertyValueInto(ctx *Context, v *structpb.Value, t *PropertyValue, opts MarshalOptions) { + contract.Assert(v != nil) + + switch v.Kind.(type) { + case *structpb.Value_NullValue: + contract.Assert(t.CanNull()) + *t = NewNullProperty() + case *structpb.Value_BoolValue: + contract.Assert(t.CanBool()) + *t = NewBoolProperty(v.GetBoolValue()) + case *structpb.Value_NumberValue: + contract.Assert(t.CanNumber()) + *t = NewNumberProperty(v.GetNumberValue()) + case *structpb.Value_StringValue: + // In the case of a string, the target may be either a resource URN or a real string. + s := v.GetStringValue() + if t.CanString() || opts.RawResources { + *t = NewStringProperty(s) + } else { + // During unmarshaling, we must translate from the expected resource wire format of a provider ID, back + // into its Lumi side URN. There are cases when this is not desirable, most notably, when a resource + // provider is unmarshaling data for its own consumption (as is the case for receiving payloads from Lumi, + // for instance). In such cases, RawResources == true, and those will end up as simple strings (above). + contract.Assert(t.CanResource()) + contract.Assertf(ctx != nil, "Resource encountered with a nil context; URN not recoverable") + id := ID(s) + urn, has := ctx.IDURN[id] + contract.Assertf(has, "Expected resource ID '%v' to have a URN; none found", id) + glog.V(7).Infof("Deserializing resource ID '%v' as URN '%v' (raw=%v)", id, urn, opts.RawResources) + *t = NewResourceProperty(urn) } + case *structpb.Value_ListValue: + contract.Assert(t.CanArray()) + + // If there's already an array, prefer to swap elements within it. + var elems []PropertyValue + if t.IsArray() { + elems = t.ArrayValue() + } + + lst := v.GetListValue() + for i, elem := range lst.GetValues() { + if i == len(elems) { + elems = append(elems, PropertyValue{}) + } + contract.Assert(len(elems) > i) + UnmarshalPropertyValueInto(ctx, elem, &elems[i], opts) + } + *t = NewArrayProperty(elems) + case *structpb.Value_StructValue: + contract.Assert(t.CanObject()) + + // If there's already an object, prefer to swap existing properties. + var obj PropertyMap + if t.IsObject() { + obj = t.ObjectValue() + } else { + obj = make(PropertyMap) + *t = NewObjectProperty(obj) + } + + UnmarshalPropertiesInto(ctx, v.GetStructValue(), obj, opts) + default: + contract.Failf("Unrecognized structpb value kind: %v", reflect.TypeOf(v.Kind)) } - return NewPropertyNull() } diff --git a/pkg/resource/snapshot.go b/pkg/resource/snapshot.go index 68a1a81b3..bf6c1007d 100644 --- a/pkg/resource/snapshot.go +++ b/pkg/resource/snapshot.go @@ -112,7 +112,7 @@ func createResources(ctx *Context, ns tokens.QName, heap *heapstate.Heap, resobj if err != nil { return nil, err } - name, err := prov.Name(t, res.Properties()) + name, err := prov.Name(res) if err != nil { return nil, err } diff --git a/pkg/tools/lumidl/check.go b/pkg/tools/lumidl/check.go index ef5282ab7..c8dac0f59 100644 --- a/pkg/tools/lumidl/check.go +++ b/pkg/tools/lumidl/check.go @@ -358,7 +358,7 @@ func (chk *Checker) CheckIDLType(t types.Type, opts PropertyOptions) error { } 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 { + if !opts.Optional && !opts.In && !opts.Out { elem := ft.Elem() var ok bool if named, isnamed := elem.(*types.Named); isnamed { diff --git a/pkg/tools/lumidl/gen_pack.go b/pkg/tools/lumidl/gen_pack.go index 96ac5a132..5f016b65b 100644 --- a/pkg/tools/lumidl/gen_pack.go +++ b/pkg/tools/lumidl/gen_pack.go @@ -276,7 +276,11 @@ func (g *PackGenerator) emitResourceClass(w *bufio.Writer, res *Resource) { hasName := false hasRequiredArgs := false fn := forEachField(res, func(fld *types.Var, opt PropertyOptions) { - g.emitField(w, fld, opt, " public ") + var decors string + if opt.Out { + decors = "@lumi.out " + } + g.emitField(w, fld, opt, fmt.Sprintf(" %vpublic ", decors)) if !opt.Out { if isResourceNameProperty(res, opt) { hasName = true diff --git a/pkg/tools/lumidl/gen_rpc.go b/pkg/tools/lumidl/gen_rpc.go index 84917fe1c..a6aa9695f 100644 --- a/pkg/tools/lumidl/gen_rpc.go +++ b/pkg/tools/lumidl/gen_rpc.go @@ -229,14 +229,7 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg * g.FileHadNamedRes = true } - hasouts := false propopts := res.PropertyOptions() - for _, opts := range propopts { - if opts.Out { - hasouts = true - break - } - } // Emit a type token. token := fmt.Sprintf("%v:%v:%v", pkg.Name, module, name) @@ -251,11 +244,7 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg * if !res.Named { writefmtln(w, " Name(ctx context.Context, obj *%v) (string, error)", name) } - writefmt(w, " Create(ctx context.Context, obj *%v) (resource.ID, ", name) - if hasouts { - writefmt(w, "*%vOuts, ", name) - } - writefmtln(w, "error)") + writefmtln(w, " Create(ctx context.Context, obj *%v) (resource.ID, error)", name) writefmtln(w, " Get(ctx context.Context, id resource.ID) (*%v, error)", name) writefmtln(w, " InspectChange(ctx context.Context,") writefmtln(w, " id resource.ID, old *%[1]v, new *%[1]v, diff *resource.ObjectDiff) ([]string, error)", name) @@ -302,10 +291,13 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg * writefmtln(w, " }") if res.Named { // For named resources, we have a canonical way of fetching the name. - writefmtln(w, ` if obj.Name == "" {`) + writefmtln(w, ` if obj.Name == nil || *obj.Name == "" {`) + writefmtln(w, ` if req.Unknowns[%v_Name] {`, name) + writefmtln(w, ` return nil, errors.New("Name property cannot be computed from unknown outputs")`) + writefmtln(w, " }") writefmtln(w, ` return nil, errors.New("Name property cannot be empty")`) writefmtln(w, " }") - writefmtln(w, " return &lumirpc.NameResponse{Name: obj.Name}, nil") + writefmtln(w, " return &lumirpc.NameResponse{Name: *obj.Name}, nil") } else { // For all other resources, delegate to the underlying provider to perform the naming operation. writefmtln(w, " name, err := p.ops.Name(ctx, obj)") @@ -320,23 +312,11 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg * writefmtln(w, " if decerr != nil {") writefmtln(w, " return nil, decerr") writefmtln(w, " }") - writefmt(w, " id, ") - if hasouts { - writefmt(w, "outs, ") - } - writefmtln(w, "err := p.ops.Create(ctx, obj)") + writefmtln(w, " id, err := p.ops.Create(ctx, obj)") writefmtln(w, " if err != nil {") writefmtln(w, " return nil, err") writefmtln(w, " }") - // TODO: validate the output (e.g., required ones are non-nil, etc). - writefmtln(w, " return &lumirpc.CreateResponse{") - writefmtln(w, " Id: string(id),") - if hasouts { - writefmtln(w, " Outputs: resource.MarshalProperties(") - writefmtln(w, " nil, resource.NewPropertyMap(outs), resource.MarshalOptions{},") - writefmtln(w, " ),") - } - writefmtln(w, " }, nil") + writefmtln(w, " return &lumirpc.CreateResponse{Id: string(id)}, nil") writefmtln(w, "}") writefmtln(w, "") writefmtln(w, "func (p *%vProvider) Get(", name) @@ -354,7 +334,7 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg * writefmtln(w, "}") writefmtln(w, "") writefmtln(w, "func (p *%vProvider) InspectChange(", name) - writefmtln(w, " ctx context.Context, req *lumirpc.ChangeRequest) (*lumirpc.InspectChangeResponse, error) {") + writefmtln(w, " ctx context.Context, req *lumirpc.InspectChangeRequest) (*lumirpc.InspectChangeResponse, error) {") writefmtln(w, " contract.Assert(req.GetType() == string(%vToken))", name) writefmtln(w, " id := resource.ID(req.GetId())") writefmtln(w, " old, oldprops, decerr := p.Unmarshal(req.GetOlds())") @@ -386,7 +366,7 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg * writefmtln(w, "}") writefmtln(w, "") writefmtln(w, "func (p *%vProvider) Update(", name) - writefmtln(w, " ctx context.Context, req *lumirpc.ChangeRequest) (*pbempty.Empty, error) {") + writefmtln(w, " ctx context.Context, req *lumirpc.UpdateRequest) (*pbempty.Empty, error) {") writefmtln(w, " contract.Assert(req.GetType() == string(%vToken))", name) writefmtln(w, " id := resource.ID(req.GetId())") writefmtln(w, " old, oldprops, err := p.Unmarshal(req.GetOlds())") @@ -417,7 +397,7 @@ func (g *RPCGenerator) EmitResource(w *bufio.Writer, module tokens.Module, pkg * writefmtln(w, "func (p *%vProvider) Unmarshal(", name) writefmtln(w, " v *pbstruct.Struct) (*%v, resource.PropertyMap, mapper.DecodeError) {", name) writefmtln(w, " var obj %v", name) - writefmtln(w, " props := resource.UnmarshalProperties(v)") + writefmtln(w, " props := resource.UnmarshalProperties(nil, v, resource.MarshalOptions{RawResources: true})") writefmtln(w, " result := mapper.MapIU(props.Mappable(), &obj)") writefmtln(w, " return &obj, props, result") writefmtln(w, "}") @@ -429,36 +409,20 @@ func (g *RPCGenerator) EmitStructType(w *bufio.Writer, module tokens.Module, pkg writefmtln(w, "/* Marshalable %v structure(s) */", name) writefmtln(w, "") - 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] - if opts.Out { - outs = append(outs, i) // remember this so we can generate an out struct. - } // Make a JSON tag for this so we can serialize; note that outputs are always optional in this position. - jsontag := makeJSONTag(opts, opts.Out) - writefmtln(w, " %v %v %v", prop.Name(), g.GenTypeName(prop.Type(), opts.Optional), jsontag) + jsontag := makeJSONTag(opts) + writefmtln(w, " %v %v %v", + prop.Name(), g.GenTypeName(prop.Type(), opts.Optional || opts.In || opts.Out), jsontag) } writefmtln(w, "}") 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] - opts := propopts[out] - jsontag := makeJSONTag(opts, false) - writefmtln(w, " %v %v %v", prop.Name(), g.GenTypeName(prop.Type(), opts.Optional), jsontag) - } - 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 (") @@ -472,9 +436,9 @@ func (g *RPCGenerator) EmitStructType(w *bufio.Writer, module tokens.Module, pkg } // makeJSONTag turns a set of property options into a serializable JSON tag. -func makeJSONTag(opts PropertyOptions, forceopt bool) string { +func makeJSONTag(opts PropertyOptions) string { var flags string - if forceopt || opts.Optional { + if opts.Optional || opts.In || opts.Out { flags = ",omitempty" } return fmt.Sprintf("`json:\"%v%v\"`", opts.Name, flags) diff --git a/pkg/tools/lumidl/options.go b/pkg/tools/lumidl/options.go index 91301fae5..1f3a715e4 100644 --- a/pkg/tools/lumidl/options.go +++ b/pkg/tools/lumidl/options.go @@ -31,6 +31,7 @@ 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. } @@ -47,6 +48,8 @@ func ParsePropertyOptions(tag string) PropertyOptions { opts.Optional = true case "replaces": opts.Replaces = true + case "in": + opts.In = true case "out": opts.Out = true default: diff --git a/pkg/util/cmdutil/log.go b/pkg/util/cmdutil/log.go index 3d1b25fb4..e1703e3c8 100644 --- a/pkg/util/cmdutil/log.go +++ b/pkg/util/cmdutil/log.go @@ -22,12 +22,14 @@ import ( var LogToStderr = false // true if logging is being redirected to stderr. var Verbose = 0 // >0 if verbose logging is enabled at a particular level. +var LogFlow = false // true to flow logging settings to child processes. // InitLogging ensures the glog library has been initialized with the given settings. -func InitLogging(logToStderr bool, verbose int) { +func InitLogging(logToStderr bool, verbose int, logFlow bool) { // Remember the settings in case someone inquires. LogToStderr = logToStderr Verbose = verbose + LogFlow = logFlow // Ensure the glog library has been initialized, including calling flag.Parse beforehand. Unfortunately, // this is the only way to control the way glog runs. That includes poking around at flags below. diff --git a/pkg/util/convutil/conv.go b/pkg/util/convutil/conv.go new file mode 100644 index 000000000..3446d4449 --- /dev/null +++ b/pkg/util/convutil/conv.go @@ -0,0 +1,32 @@ +// Licensed to Pulumi Corporation ("Pulumi") under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// Pulumi licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convutil + +func Int64PToFloat64P(v *int64) *float64 { + if v == nil { + return nil + } + cv := float64(*v) + return &cv +} + +func Float64PToInt64P(v *float64) *int64 { + if v == nil { + return nil + } + cv := int64(*v) + return &cv +} diff --git a/pkg/util/mapper/mapper.go b/pkg/util/mapper/mapper.go index 2cce9eeb8..929ef515b 100644 --- a/pkg/util/mapper/mapper.go +++ b/pkg/util/mapper/mapper.go @@ -17,6 +17,7 @@ package mapper import ( "encoding" + "encoding/json" "fmt" "reflect" "strings" @@ -27,8 +28,12 @@ import ( ) type Mapper interface { + // Decode decodes a JSON-like object into the target pointer to a structure. Decode(tree Object, target interface{}) DecodeError + // DecodeField decodes a single JSON-like value (with a given type and name) into a target pointer to a structure. DecodeField(tree Object, ty reflect.Type, key string, target interface{}, optional bool) FieldError + // Encode encodes an object into a JSON-like in-memory object. + Encode(obj interface{}) Object } func New(opts *Opts) Mapper { @@ -306,7 +311,7 @@ func (md *mapper) adjustValue(val reflect.Value, } else { return val, NewFieldErr(ty, key, errors.Errorf( - "Cannot decode Object to type %v; it isn't a struct, and no custom decoder exists", to)) + "Cannot decode Object{} to type %v; it isn't a struct, and no custom decoder exists", to)) } } else if val.Type().Kind() == reflect.String { // If the source is a string, see if the target implements encoding.TextUnmarshaler. @@ -327,11 +332,25 @@ func (md *mapper) adjustValue(val reflect.Value, return val, nil } +func (md *mapper) Encode(obj interface{}) Object { + // TODO[pulumi/lumi#183]: implement this more efficiently. + data, err := json.Marshal(obj) + contract.Assert(err == nil) + var result Object + err = json.Unmarshal(data, &result) + return result +} + // Map decodes an entire map into a target object, using an anonymous decoder and tag-directed mappings. func Map(tree Object, target interface{}) DecodeError { return New(nil).Decode(tree, target) } +// Unmap translates an already mapped target object into a raw, unmapped form. +func Unmap(obj interface{}) Object { + return New(nil).Encode(obj) +} + // MapI decodes an entire map into a target object, using an anonymous decoder and tag-directed mappings. This variant // ignores any missing required fields in the payload in addition to any unrecognized fields. func MapI(tree Object, target interface{}) DecodeError { diff --git a/pkg/util/mapper/mapper_test.go b/pkg/util/mapper/mapper_test.go index 39e0d9536..1a2c8b43e 100644 --- a/pkg/util/mapper/mapper_test.go +++ b/pkg/util/mapper/mapper_test.go @@ -394,3 +394,143 @@ func decodeCustomStruct(m Mapper, tree Object) (interface{}, error) { } return s, nil } + +type outer struct { + Inners *[]inner `json:"inners,omitempty"` +} + +type inner struct { + A string `json:"a"` + B *string `json:"b,omitempty"` + C *string `json:"c,omitempty"` + D float64 `json:"d"` + E *float64 `json:"e,omitempty"` + F *float64 `json:"f,omitempty"` + G *inner `json:"g,omitempty"` + H *[]inner `json:"h,omitempty"` +} + +func TestBasicUnmap(t *testing.T) { + v2 := "v2" + v5 := float64(5) + i1v2 := "i1v2" + i1v5 := float64(15) + i2v2 := "i2v2" + i2v5 := float64(25) + i3v2 := "i3v2" + i3v5 := float64(35) + o := outer{ + Inners: &[]inner{ + { + A: "v1", + B: &v2, + C: nil, + D: float64(4), + E: &v5, + F: nil, + G: &inner{ + A: "i1v1", + B: &i1v2, + C: nil, + D: float64(14), + E: &i1v5, + F: nil, + G: nil, + H: nil, + }, + H: &[]inner{ + { + A: "i2v1", + B: &i2v2, + C: nil, + D: float64(24), + E: &i2v5, + F: nil, + G: nil, + H: nil, + }, + { + A: "i3v1", + B: &i3v2, + C: nil, + D: float64(34), + E: &i3v5, + F: nil, + G: nil, + H: nil, + }, + }, + }, + }, + } + + // Unmap returns a JSON-like dictionary object representing the above structure. + for _, e := range []interface{}{o, &o} { + um := Unmap(e) + assert.NotNil(t, um) + + // check outer: + assert.NotNil(t, um["inners"]) + arr := um["inners"].([]interface{}) + assert.Equal(t, len(arr), 1) + + // check outer.inner: + inn := arr[0].(map[string]interface{}) + assert.Equal(t, inn["a"], "v1") + assert.Equal(t, inn["b"], "v2") + _, hasc := inn["c"] + assert.False(t, hasc) + assert.Equal(t, inn["d"], float64(4)) + assert.Equal(t, inn["e"], float64(5)) + _, hasf := inn["f"] + assert.False(t, hasf) + assert.NotNil(t, inn["g"]) + + // check outer.inner.inner: + inng := inn["g"].(map[string]interface{}) + assert.Equal(t, inng["a"], "i1v1") + assert.Equal(t, inng["b"], "i1v2") + _, hasgc := inng["c"] + assert.False(t, hasgc) + assert.Equal(t, inng["d"], float64(14)) + assert.Equal(t, inng["e"], float64(15)) + _, hasgf := inng["f"] + assert.False(t, hasgf) + _, hasgg := inng["g"] + assert.False(t, hasgg) + _, hasgh := inng["h"] + assert.False(t, hasgh) + + // check outer.inner.inners[0]: + innh := inn["h"].([]interface{}) + assert.Equal(t, len(innh), 2) + innh0 := innh[0].(map[string]interface{}) + assert.Equal(t, innh0["a"], "i2v1") + assert.Equal(t, innh0["b"], "i2v2") + _, hash0c := inng["c"] + assert.False(t, hash0c) + assert.Equal(t, innh0["d"], float64(24)) + assert.Equal(t, innh0["e"], float64(25)) + _, hash0f := inng["f"] + assert.False(t, hash0f) + _, hash0g := inng["g"] + assert.False(t, hash0g) + _, hash0h := inng["h"] + assert.False(t, hash0h) + + // check outer.inner.inners[1]: + innh1 := innh[1].(map[string]interface{}) + assert.Equal(t, innh1["a"], "i3v1") + assert.Equal(t, innh1["b"], "i3v2") + _, hash1c := inng["c"] + assert.False(t, hash1c) + assert.Equal(t, innh1["d"], float64(34)) + assert.Equal(t, innh1["e"], float64(35)) + _, hash1f := inng["f"] + assert.False(t, hash1f) + _, hash1g := inng["g"] + assert.False(t, hash1g) + _, hash1h := inng["h"] + assert.False(t, hash1h) + } +} diff --git a/sdk/go/pkg/lumirpc/analyzer.pb.go b/sdk/go/pkg/lumirpc/analyzer.pb.go index dcbcbfd8f..2d1d66632 100644 --- a/sdk/go/pkg/lumirpc/analyzer.pb.go +++ b/sdk/go/pkg/lumirpc/analyzer.pb.go @@ -27,8 +27,9 @@ It has these top-level messages: CreateResponse GetRequest GetResponse - ChangeRequest + InspectChangeRequest InspectChangeResponse + UpdateRequest DeleteRequest */ package lumirpc @@ -105,6 +106,7 @@ func (m *AnalyzeFailure) GetReason() string { type AnalyzeResourceRequest struct { Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"` Properties *google_protobuf.Struct `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"` + Unknowns map[string]bool `protobuf:"bytes,3,rep,name=unknowns" json:"unknowns,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` } func (m *AnalyzeResourceRequest) Reset() { *m = AnalyzeResourceRequest{} } @@ -126,6 +128,13 @@ func (m *AnalyzeResourceRequest) GetProperties() *google_protobuf.Struct { return nil } +func (m *AnalyzeResourceRequest) GetUnknowns() map[string]bool { + if m != nil { + return m.Unknowns + } + return nil +} + type AnalyzeResourceResponse struct { Failures []*AnalyzeResourceFailure `protobuf:"bytes,1,rep,name=failures" json:"failures,omitempty"` } @@ -287,24 +296,28 @@ var _Analyzer_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("analyzer.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 301 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x91, 0x4f, 0x4f, 0x83, 0x40, - 0x10, 0xc5, 0xa5, 0x35, 0x2d, 0x8e, 0x49, 0x35, 0x7b, 0x28, 0x84, 0x98, 0x48, 0xf6, 0xc4, 0x89, - 0x26, 0xed, 0xc1, 0x83, 0x89, 0x89, 0x97, 0x9e, 0x3c, 0x61, 0xd2, 0x3b, 0x25, 0x53, 0x42, 0x44, - 0x76, 0xdd, 0x3f, 0x07, 0xfc, 0x38, 0x7e, 0x52, 0xe3, 0x32, 0x20, 0x11, 0x7b, 0x9b, 0x65, 0xdf, - 0xce, 0x7b, 0xbf, 0x07, 0xac, 0xf2, 0x26, 0xaf, 0xdb, 0x4f, 0x54, 0xa9, 0x54, 0xc2, 0x08, 0xb6, - 0xac, 0xed, 0x7b, 0xa5, 0x64, 0x11, 0xdd, 0x95, 0x42, 0x94, 0x35, 0x6e, 0xdc, 0xe7, 0xa3, 0x3d, - 0x6d, 0xb4, 0x51, 0xb6, 0x30, 0x9d, 0x8c, 0x73, 0x58, 0x3d, 0x77, 0x0f, 0x33, 0xfc, 0xb0, 0xa8, - 0x0d, 0xbb, 0x85, 0xb9, 0x7c, 0x2b, 0x43, 0x2f, 0xf6, 0x92, 0xab, 0xec, 0x67, 0xe4, 0x7b, 0xb8, - 0x19, 0x34, 0x5a, 0x8a, 0x46, 0x23, 0xdb, 0x81, 0x7f, 0xca, 0xab, 0xda, 0x2a, 0xd4, 0xa1, 0x17, - 0xcf, 0x93, 0xeb, 0x6d, 0x90, 0x92, 0x61, 0x4a, 0xda, 0x7d, 0x77, 0x9f, 0x0d, 0x42, 0x9e, 0x0c, - 0x5e, 0x74, 0xc7, 0xd6, 0xb0, 0x50, 0x98, 0x6b, 0xd1, 0x90, 0x1d, 0x9d, 0x38, 0xc2, 0xfa, 0xd7, - 0x51, 0x58, 0x55, 0x0c, 0xe9, 0x18, 0x5c, 0x9a, 0x56, 0x22, 0xe9, 0xdd, 0xcc, 0x1e, 0x00, 0xa4, - 0x12, 0x12, 0x95, 0xa9, 0x50, 0x87, 0xb3, 0xd8, 0x73, 0x71, 0x3a, 0xec, 0xb4, 0xc7, 0x4e, 0x5f, - 0x1d, 0x76, 0x36, 0x92, 0xf2, 0x03, 0x04, 0x13, 0x1b, 0x02, 0x7c, 0x9c, 0x00, 0xde, 0xff, 0x05, - 0xec, 0xdf, 0x4c, 0x41, 0x5f, 0x26, 0xf1, 0x7b, 0xe0, 0x08, 0x7c, 0xf2, 0x6f, 0x09, 0x61, 0x38, - 0x8f, 0xca, 0x98, 0x8d, 0xcb, 0xd8, 0x7e, 0x79, 0xe0, 0xd3, 0x3a, 0xc5, 0x9e, 0x60, 0x49, 0x33, - 0x0b, 0xa6, 0x81, 0x5c, 0x47, 0x51, 0xf8, 0x4f, 0x52, 0x47, 0xc5, 0x2f, 0xd8, 0x61, 0xfc, 0x2f, - 0x5d, 0x34, 0x76, 0x16, 0xac, 0xdf, 0x17, 0x9f, 0x17, 0xf4, 0x7b, 0x8f, 0x0b, 0xd7, 0xf3, 0xee, - 0x3b, 0x00, 0x00, 0xff, 0xff, 0x75, 0xfe, 0xfb, 0x84, 0x87, 0x02, 0x00, 0x00, + // 366 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x52, 0xcd, 0x4a, 0xeb, 0x40, + 0x14, 0xbe, 0xd3, 0xde, 0xdb, 0xe6, 0x9e, 0xcb, 0xad, 0x32, 0x48, 0x1b, 0x82, 0xd0, 0x30, 0xab, + 0x6e, 0x4c, 0xa1, 0x5d, 0x28, 0x16, 0x04, 0x17, 0x16, 0x04, 0x57, 0x11, 0xbb, 0x4f, 0xcb, 0xb4, + 0x94, 0xc4, 0x99, 0x71, 0x7e, 0x94, 0xf8, 0x38, 0xbe, 0x9f, 0xef, 0x20, 0x9d, 0x4c, 0x62, 0x6c, + 0x2c, 0xee, 0xce, 0xc9, 0xf9, 0xce, 0x7c, 0x3f, 0x27, 0xd0, 0x4b, 0x58, 0x92, 0xe5, 0xaf, 0x54, + 0x46, 0x42, 0x72, 0xcd, 0x71, 0x37, 0x33, 0x8f, 0x5b, 0x29, 0x56, 0xc1, 0xe9, 0x86, 0xf3, 0x4d, + 0x46, 0xc7, 0xf6, 0xf3, 0xd2, 0xac, 0xc7, 0x4a, 0x4b, 0xb3, 0xd2, 0x05, 0x8c, 0x10, 0xe8, 0x5d, + 0x17, 0x8b, 0x31, 0x7d, 0x32, 0x54, 0x69, 0x7c, 0x0c, 0x6d, 0x91, 0x6e, 0x7c, 0x14, 0xa2, 0xd1, + 0xdf, 0x78, 0x57, 0x92, 0x39, 0x1c, 0x55, 0x18, 0x25, 0x38, 0x53, 0x14, 0x4f, 0xc1, 0x5b, 0x27, + 0xdb, 0xcc, 0x48, 0xaa, 0x7c, 0x14, 0xb6, 0x47, 0xff, 0x26, 0x83, 0xc8, 0x11, 0x46, 0x0e, 0x3b, + 0x2f, 0xe6, 0x71, 0x05, 0x24, 0xa3, 0x8a, 0xcb, 0xcd, 0x70, 0x1f, 0x3a, 0x92, 0x26, 0x8a, 0x33, + 0x47, 0xe7, 0x3a, 0xf2, 0x8e, 0xa0, 0xff, 0x49, 0xc9, 0x8d, 0x5c, 0x55, 0xf2, 0x30, 0xfc, 0xd6, + 0xb9, 0xa0, 0x6e, 0xc1, 0xd6, 0xf8, 0x1c, 0x40, 0x48, 0x2e, 0xa8, 0xd4, 0x5b, 0xaa, 0xfc, 0x56, + 0x88, 0xac, 0x9e, 0xc2, 0x77, 0x54, 0xfa, 0x8e, 0xee, 0xad, 0xef, 0xb8, 0x06, 0xc5, 0xb7, 0xe0, + 0x19, 0x96, 0x32, 0xfe, 0xc2, 0x94, 0xdf, 0xb6, 0x36, 0xce, 0xf6, 0x6d, 0xec, 0xf1, 0x47, 0x0f, + 0x0e, 0x7f, 0xc3, 0xb4, 0xcc, 0xe3, 0x6a, 0x3d, 0x98, 0xc1, 0xff, 0x2f, 0xa3, 0x5d, 0x8e, 0x29, + 0xcd, 0xcb, 0x1c, 0x53, 0x9a, 0xe3, 0x13, 0xf8, 0xf3, 0x9c, 0x64, 0x86, 0x5a, 0x85, 0x5e, 0x5c, + 0x34, 0x97, 0xad, 0x0b, 0x44, 0x16, 0x30, 0x68, 0xd0, 0xb9, 0xa4, 0x67, 0x8d, 0xa4, 0x87, 0x87, + 0x24, 0x36, 0x13, 0xbf, 0x6b, 0xc4, 0x58, 0x26, 0x1f, 0x80, 0xe7, 0x72, 0x28, 0x25, 0x56, 0x7d, + 0xed, 0x2a, 0xad, 0xfa, 0x55, 0x26, 0x6f, 0x08, 0x3c, 0xf7, 0x9c, 0xc4, 0x57, 0xd0, 0x75, 0x35, + 0x1e, 0x34, 0x05, 0xd9, 0xac, 0x02, 0xff, 0x1b, 0xa5, 0xd6, 0x15, 0xf9, 0x85, 0x17, 0xf5, 0x9f, + 0xca, 0x4a, 0xc3, 0xc3, 0x1f, 0xb2, 0x0f, 0xc2, 0xc3, 0x80, 0xf2, 0xdd, 0x65, 0xc7, 0xde, 0x7b, + 0xfa, 0x11, 0x00, 0x00, 0xff, 0xff, 0x9f, 0x75, 0x5c, 0x82, 0x10, 0x03, 0x00, 0x00, } diff --git a/sdk/go/pkg/lumirpc/provider.pb.go b/sdk/go/pkg/lumirpc/provider.pb.go index be511c4e1..91b3aba97 100644 --- a/sdk/go/pkg/lumirpc/provider.pb.go +++ b/sdk/go/pkg/lumirpc/provider.pb.go @@ -23,6 +23,7 @@ var _ = math.Inf type CheckRequest struct { Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"` Properties *google_protobuf.Struct `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"` + Unknowns map[string]bool `protobuf:"bytes,3,rep,name=unknowns" json:"unknowns,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` } func (m *CheckRequest) Reset() { *m = CheckRequest{} } @@ -44,6 +45,13 @@ func (m *CheckRequest) GetProperties() *google_protobuf.Struct { return nil } +func (m *CheckRequest) GetUnknowns() map[string]bool { + if m != nil { + return m.Unknowns + } + return nil +} + type CheckResponse struct { Failures []*CheckFailure `protobuf:"bytes,1,rep,name=failures" json:"failures,omitempty"` } @@ -87,6 +95,7 @@ func (m *CheckFailure) GetReason() string { type NameRequest struct { Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"` Properties *google_protobuf.Struct `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"` + Unknowns map[string]bool `protobuf:"bytes,3,rep,name=unknowns" json:"unknowns,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` } func (m *NameRequest) Reset() { *m = NameRequest{} } @@ -108,6 +117,13 @@ func (m *NameRequest) GetProperties() *google_protobuf.Struct { return nil } +func (m *NameRequest) GetUnknowns() map[string]bool { + if m != nil { + return m.Unknowns + } + return nil +} + type NameResponse struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` } @@ -212,46 +228,54 @@ func (m *GetResponse) GetProperties() *google_protobuf.Struct { return nil } -type ChangeRequest struct { - Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` - Type string `protobuf:"bytes,2,opt,name=type" json:"type,omitempty"` - Olds *google_protobuf.Struct `protobuf:"bytes,3,opt,name=olds" json:"olds,omitempty"` - News *google_protobuf.Struct `protobuf:"bytes,4,opt,name=news" json:"news,omitempty"` +type InspectChangeRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type" json:"type,omitempty"` + Olds *google_protobuf.Struct `protobuf:"bytes,3,opt,name=olds" json:"olds,omitempty"` + News *google_protobuf.Struct `protobuf:"bytes,4,opt,name=news" json:"news,omitempty"` + Unknowns map[string]bool `protobuf:"bytes,5,rep,name=unknowns" json:"unknowns,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` } -func (m *ChangeRequest) Reset() { *m = ChangeRequest{} } -func (m *ChangeRequest) String() string { return proto.CompactTextString(m) } -func (*ChangeRequest) ProtoMessage() {} -func (*ChangeRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{9} } +func (m *InspectChangeRequest) Reset() { *m = InspectChangeRequest{} } +func (m *InspectChangeRequest) String() string { return proto.CompactTextString(m) } +func (*InspectChangeRequest) ProtoMessage() {} +func (*InspectChangeRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{9} } -func (m *ChangeRequest) GetId() string { +func (m *InspectChangeRequest) GetId() string { if m != nil { return m.Id } return "" } -func (m *ChangeRequest) GetType() string { +func (m *InspectChangeRequest) GetType() string { if m != nil { return m.Type } return "" } -func (m *ChangeRequest) GetOlds() *google_protobuf.Struct { +func (m *InspectChangeRequest) GetOlds() *google_protobuf.Struct { if m != nil { return m.Olds } return nil } -func (m *ChangeRequest) GetNews() *google_protobuf.Struct { +func (m *InspectChangeRequest) GetNews() *google_protobuf.Struct { if m != nil { return m.News } return nil } +func (m *InspectChangeRequest) GetUnknowns() map[string]bool { + if m != nil { + return m.Unknowns + } + return nil +} + type InspectChangeResponse struct { Replaces []string `protobuf:"bytes,1,rep,name=replaces" json:"replaces,omitempty"` Changes *google_protobuf.Struct `protobuf:"bytes,2,opt,name=changes" json:"changes,omitempty"` @@ -276,6 +300,46 @@ func (m *InspectChangeResponse) GetChanges() *google_protobuf.Struct { return nil } +type UpdateRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type" json:"type,omitempty"` + Olds *google_protobuf.Struct `protobuf:"bytes,3,opt,name=olds" json:"olds,omitempty"` + News *google_protobuf.Struct `protobuf:"bytes,4,opt,name=news" json:"news,omitempty"` +} + +func (m *UpdateRequest) Reset() { *m = UpdateRequest{} } +func (m *UpdateRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateRequest) ProtoMessage() {} +func (*UpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{11} } + +func (m *UpdateRequest) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *UpdateRequest) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *UpdateRequest) GetOlds() *google_protobuf.Struct { + if m != nil { + return m.Olds + } + return nil +} + +func (m *UpdateRequest) GetNews() *google_protobuf.Struct { + if m != nil { + return m.News + } + return nil +} + type DeleteRequest struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` Type string `protobuf:"bytes,2,opt,name=type" json:"type,omitempty"` @@ -284,7 +348,7 @@ type DeleteRequest struct { func (m *DeleteRequest) Reset() { *m = DeleteRequest{} } func (m *DeleteRequest) String() string { return proto.CompactTextString(m) } func (*DeleteRequest) ProtoMessage() {} -func (*DeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{11} } +func (*DeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{12} } func (m *DeleteRequest) GetId() string { if m != nil { @@ -310,8 +374,9 @@ func init() { proto.RegisterType((*CreateResponse)(nil), "lumirpc.CreateResponse") proto.RegisterType((*GetRequest)(nil), "lumirpc.GetRequest") proto.RegisterType((*GetResponse)(nil), "lumirpc.GetResponse") - proto.RegisterType((*ChangeRequest)(nil), "lumirpc.ChangeRequest") + proto.RegisterType((*InspectChangeRequest)(nil), "lumirpc.InspectChangeRequest") proto.RegisterType((*InspectChangeResponse)(nil), "lumirpc.InspectChangeResponse") + proto.RegisterType((*UpdateRequest)(nil), "lumirpc.UpdateRequest") proto.RegisterType((*DeleteRequest)(nil), "lumirpc.DeleteRequest") } @@ -338,9 +403,9 @@ type ResourceProviderClient interface { // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) // InspectChange checks what impacts a hypothetical update will have on the resource's properties. - InspectChange(ctx context.Context, in *ChangeRequest, opts ...grpc.CallOption) (*InspectChangeResponse, error) + InspectChange(ctx context.Context, in *InspectChangeRequest, opts ...grpc.CallOption) (*InspectChangeResponse, error) // Update updates an existing resource with new values. - Update(ctx context.Context, in *ChangeRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) } @@ -389,7 +454,7 @@ func (c *resourceProviderClient) Get(ctx context.Context, in *GetRequest, opts . return out, nil } -func (c *resourceProviderClient) InspectChange(ctx context.Context, in *ChangeRequest, opts ...grpc.CallOption) (*InspectChangeResponse, error) { +func (c *resourceProviderClient) InspectChange(ctx context.Context, in *InspectChangeRequest, opts ...grpc.CallOption) (*InspectChangeResponse, error) { out := new(InspectChangeResponse) err := grpc.Invoke(ctx, "/lumirpc.ResourceProvider/InspectChange", in, out, c.cc, opts...) if err != nil { @@ -398,7 +463,7 @@ func (c *resourceProviderClient) InspectChange(ctx context.Context, in *ChangeRe return out, nil } -func (c *resourceProviderClient) Update(ctx context.Context, in *ChangeRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { +func (c *resourceProviderClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { out := new(google_protobuf1.Empty) err := grpc.Invoke(ctx, "/lumirpc.ResourceProvider/Update", in, out, c.cc, opts...) if err != nil { @@ -431,9 +496,9 @@ type ResourceProviderServer interface { // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. Get(context.Context, *GetRequest) (*GetResponse, error) // InspectChange checks what impacts a hypothetical update will have on the resource's properties. - InspectChange(context.Context, *ChangeRequest) (*InspectChangeResponse, error) + InspectChange(context.Context, *InspectChangeRequest) (*InspectChangeResponse, error) // Update updates an existing resource with new values. - Update(context.Context, *ChangeRequest) (*google_protobuf1.Empty, error) + Update(context.Context, *UpdateRequest) (*google_protobuf1.Empty, error) // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. Delete(context.Context, *DeleteRequest) (*google_protobuf1.Empty, error) } @@ -515,7 +580,7 @@ func _ResourceProvider_Get_Handler(srv interface{}, ctx context.Context, dec fun } func _ResourceProvider_InspectChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ChangeRequest) + in := new(InspectChangeRequest) if err := dec(in); err != nil { return nil, err } @@ -527,13 +592,13 @@ func _ResourceProvider_InspectChange_Handler(srv interface{}, ctx context.Contex FullMethod: "/lumirpc.ResourceProvider/InspectChange", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ResourceProviderServer).InspectChange(ctx, req.(*ChangeRequest)) + return srv.(ResourceProviderServer).InspectChange(ctx, req.(*InspectChangeRequest)) } return interceptor(ctx, in, info, handler) } func _ResourceProvider_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ChangeRequest) + in := new(UpdateRequest) if err := dec(in); err != nil { return nil, err } @@ -545,7 +610,7 @@ func _ResourceProvider_Update_Handler(srv interface{}, ctx context.Context, dec FullMethod: "/lumirpc.ResourceProvider/Update", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ResourceProviderServer).Update(ctx, req.(*ChangeRequest)) + return srv.(ResourceProviderServer).Update(ctx, req.(*UpdateRequest)) } return interceptor(ctx, in, info, handler) } @@ -608,38 +673,45 @@ var _ResourceProvider_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("provider.proto", fileDescriptor2) } var fileDescriptor2 = []byte{ - // 525 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x54, 0xc1, 0x6e, 0xd3, 0x40, - 0x10, 0xad, 0x93, 0x90, 0x26, 0x93, 0x26, 0x42, 0x4b, 0x93, 0x46, 0x06, 0xa1, 0x68, 0x4f, 0x91, - 0x90, 0x5c, 0x9a, 0x0a, 0x81, 0xe0, 0xd6, 0x42, 0xab, 0x5e, 0x10, 0x72, 0xc5, 0x05, 0xb8, 0xb8, - 0xf6, 0x24, 0x58, 0x38, 0xde, 0x65, 0x77, 0x0d, 0xca, 0x47, 0xf0, 0x3d, 0xfc, 0x1e, 0xf2, 0x7a, - 0xbd, 0xb5, 0x5d, 0x20, 0xed, 0xa1, 0x37, 0xef, 0xcc, 0x9b, 0x37, 0xef, 0x79, 0x66, 0x17, 0x46, - 0x5c, 0xb0, 0x1f, 0x71, 0x84, 0xc2, 0xe3, 0x82, 0x29, 0x46, 0x76, 0x93, 0x6c, 0x1d, 0x0b, 0x1e, - 0xba, 0x8f, 0x57, 0x8c, 0xad, 0x12, 0x3c, 0xd4, 0xe1, 0xab, 0x6c, 0x79, 0x88, 0x6b, 0xae, 0x36, - 0x05, 0xca, 0x7d, 0xd2, 0x4c, 0x4a, 0x25, 0xb2, 0x50, 0x15, 0x59, 0xfa, 0x19, 0xf6, 0x4e, 0xbf, - 0x62, 0xf8, 0xcd, 0xc7, 0xef, 0x19, 0x4a, 0x45, 0x08, 0x74, 0xd4, 0x86, 0xe3, 0xd4, 0x99, 0x39, - 0xf3, 0xbe, 0xaf, 0xbf, 0xc9, 0x4b, 0x00, 0x2e, 0x18, 0x47, 0xa1, 0x62, 0x94, 0xd3, 0xd6, 0xcc, - 0x99, 0x0f, 0x16, 0x07, 0x5e, 0x41, 0xeb, 0x95, 0xb4, 0xde, 0xa5, 0xa6, 0xf5, 0x2b, 0x50, 0x7a, - 0x02, 0x43, 0x43, 0x2e, 0x39, 0x4b, 0x25, 0x92, 0x23, 0xe8, 0x2d, 0x83, 0x38, 0xc9, 0x04, 0xca, - 0xa9, 0x33, 0x6b, 0xcf, 0x07, 0x8b, 0xb1, 0x67, 0x4c, 0x78, 0x1a, 0x79, 0x56, 0x64, 0x7d, 0x0b, - 0xa3, 0x27, 0x46, 0xa0, 0xc9, 0x10, 0x17, 0x7a, 0xa6, 0xc3, 0xc6, 0x88, 0xb4, 0x67, 0x32, 0x81, - 0xae, 0xc0, 0x40, 0xb2, 0x54, 0x8b, 0xec, 0xfb, 0xe6, 0x44, 0x3f, 0xc1, 0xe0, 0x7d, 0xb0, 0xc6, - 0x7b, 0xf1, 0x48, 0x61, 0xaf, 0xe0, 0x36, 0x16, 0x09, 0x74, 0xd2, 0x60, 0x6d, 0xc9, 0xf3, 0x6f, - 0xfa, 0x05, 0x86, 0xa7, 0x02, 0x03, 0x75, 0x3f, 0x0a, 0x2e, 0x61, 0x54, 0xb2, 0x1b, 0x0d, 0x23, - 0x68, 0xc5, 0x91, 0x21, 0x6f, 0xc5, 0x11, 0x39, 0x82, 0x5d, 0x96, 0x29, 0x9e, 0xa9, 0xad, 0xbc, - 0x25, 0x8e, 0x3e, 0x07, 0x38, 0x47, 0x55, 0xea, 0x6d, 0x12, 0x96, 0xfa, 0x5b, 0xd7, 0xfa, 0xe9, - 0x19, 0x0c, 0x74, 0x85, 0xd1, 0x50, 0xb7, 0xe3, 0xdc, 0xde, 0xce, 0x2f, 0x27, 0xdf, 0x9a, 0x20, - 0x5d, 0xe1, 0x1d, 0xba, 0x93, 0x67, 0xd0, 0x61, 0x49, 0x24, 0xa7, 0xed, 0xff, 0x37, 0xd2, 0xa0, - 0x1c, 0x9c, 0xe2, 0x4f, 0x39, 0xed, 0x6c, 0x01, 0xe7, 0x20, 0xba, 0x84, 0xf1, 0x45, 0x2a, 0x39, - 0x86, 0xaa, 0x54, 0x65, 0x1c, 0xba, 0xd0, 0x13, 0xc8, 0x93, 0x20, 0x34, 0xcb, 0xdc, 0xf7, 0xed, - 0x39, 0xff, 0xe3, 0xa1, 0x46, 0x6f, 0xff, 0xe3, 0x06, 0x47, 0x8f, 0x61, 0xf8, 0x16, 0x13, 0x54, - 0x77, 0xb1, 0xbd, 0xf8, 0xdd, 0x86, 0x87, 0x3e, 0x4a, 0x96, 0x89, 0x10, 0x3f, 0x98, 0xd7, 0x81, - 0xbc, 0x82, 0x07, 0xfa, 0xca, 0x90, 0xc6, 0xe5, 0x32, 0xc4, 0xee, 0xa4, 0x19, 0x2e, 0x0c, 0xd1, - 0x1d, 0xf2, 0x02, 0x3a, 0xf9, 0x32, 0x93, 0x7d, 0x8b, 0xa8, 0xdc, 0x1b, 0x77, 0xdc, 0x88, 0xda, - 0xb2, 0x37, 0xd0, 0x2d, 0x36, 0x90, 0x54, 0xa8, 0xab, 0x0b, 0xef, 0x1e, 0xdc, 0x88, 0xdb, 0xe2, - 0x05, 0xb4, 0xcf, 0x51, 0x91, 0x47, 0x16, 0x71, 0xbd, 0x77, 0xee, 0x7e, 0x3d, 0x68, 0x6b, 0x2e, - 0x60, 0x58, 0x9b, 0x49, 0xb5, 0x6f, 0x75, 0x75, 0xdc, 0xa7, 0x36, 0xfe, 0xd7, 0x19, 0xd2, 0x1d, - 0xf2, 0x1a, 0xba, 0x1f, 0x79, 0xd4, 0xd0, 0x5e, 0xe3, 0x98, 0xdc, 0x18, 0xdd, 0xbb, 0xfc, 0x79, - 0x2d, 0x6a, 0x8b, 0x91, 0x55, 0x6a, 0x6b, 0x33, 0xfc, 0x77, 0xed, 0x55, 0x57, 0x47, 0x8e, 0xff, - 0x04, 0x00, 0x00, 0xff, 0xff, 0xbb, 0xab, 0x5b, 0x9a, 0xd5, 0x05, 0x00, 0x00, + // 629 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xc4, 0x55, 0xdd, 0x4e, 0xdb, 0x4c, + 0x10, 0xfd, 0xec, 0x84, 0x10, 0x26, 0x18, 0xa1, 0xfd, 0x12, 0x88, 0xdc, 0x1f, 0xa1, 0xed, 0x0d, + 0x12, 0x92, 0x29, 0x41, 0x55, 0x11, 0x48, 0xad, 0x04, 0x05, 0xd4, 0x9b, 0x0a, 0x19, 0x71, 0xd7, + 0x1b, 0xe3, 0x0c, 0x60, 0xe1, 0x78, 0xb7, 0xeb, 0x35, 0x28, 0x0f, 0xd1, 0x17, 0xe8, 0xc3, 0xf4, + 0x35, 0xaa, 0xbe, 0x4d, 0xe5, 0xf5, 0x7a, 0xb1, 0xc3, 0x4f, 0xca, 0x45, 0xd4, 0x3b, 0xef, 0xcc, + 0x99, 0x99, 0x33, 0x33, 0x67, 0xd7, 0xb0, 0xc4, 0x05, 0xbb, 0x89, 0x86, 0x28, 0x3c, 0x2e, 0x98, + 0x64, 0x64, 0x3e, 0xce, 0x46, 0x91, 0xe0, 0xa1, 0xfb, 0xe2, 0x92, 0xb1, 0xcb, 0x18, 0x37, 0x95, + 0xf9, 0x3c, 0xbb, 0xd8, 0xc4, 0x11, 0x97, 0xe3, 0x02, 0xe5, 0xbe, 0x9c, 0x74, 0xa6, 0x52, 0x64, + 0xa1, 0x2c, 0xbc, 0xf4, 0xb7, 0x05, 0x8b, 0x07, 0x57, 0x18, 0x5e, 0xfb, 0xf8, 0x2d, 0xc3, 0x54, + 0x12, 0x02, 0x4d, 0x39, 0xe6, 0xd8, 0xb7, 0xd6, 0xac, 0xf5, 0x05, 0x5f, 0x7d, 0x93, 0xf7, 0x00, + 0x5c, 0x30, 0x8e, 0x42, 0x46, 0x98, 0xf6, 0xed, 0x35, 0x6b, 0xbd, 0x33, 0x58, 0xf5, 0x8a, 0xbc, + 0x5e, 0x99, 0xd7, 0x3b, 0x55, 0x79, 0xfd, 0x0a, 0x94, 0x7c, 0x84, 0x76, 0x96, 0x5c, 0x27, 0xec, + 0x36, 0x49, 0xfb, 0x8d, 0xb5, 0xc6, 0x7a, 0x67, 0xf0, 0xc6, 0xd3, 0xa4, 0xbd, 0x6a, 0x55, 0xef, + 0x4c, 0xa3, 0x0e, 0x13, 0x29, 0xc6, 0xbe, 0x09, 0x72, 0xf7, 0xc0, 0xa9, 0xb9, 0xc8, 0x32, 0x34, + 0xae, 0x71, 0xac, 0xd9, 0xe5, 0x9f, 0xa4, 0x0b, 0x73, 0x37, 0x41, 0x9c, 0xa1, 0xe2, 0xd5, 0xf6, + 0x8b, 0xc3, 0xae, 0xbd, 0x63, 0xd1, 0x7d, 0x70, 0x74, 0x91, 0x94, 0xb3, 0x24, 0x45, 0xb2, 0x05, + 0xed, 0x8b, 0x20, 0x8a, 0x33, 0x81, 0x69, 0xdf, 0x52, 0x74, 0x7a, 0x75, 0x3a, 0x47, 0x85, 0xd7, + 0x37, 0x30, 0xba, 0xaf, 0xc7, 0xa3, 0x3d, 0xc4, 0x85, 0xb6, 0xee, 0xaf, 0x24, 0x61, 0xce, 0x64, + 0x05, 0x5a, 0x02, 0x83, 0x94, 0x25, 0x8a, 0xca, 0x82, 0xaf, 0x4f, 0xf4, 0x97, 0x05, 0x9d, 0x2f, + 0xc1, 0x08, 0x67, 0x32, 0xe2, 0x0f, 0xf7, 0x46, 0x4c, 0x4d, 0x4f, 0x95, 0xa2, 0xb3, 0x99, 0x30, + 0x85, 0xc5, 0xa2, 0x86, 0x1e, 0x30, 0x81, 0x66, 0x12, 0x8c, 0x4c, 0x67, 0xf9, 0x37, 0xfd, 0x0a, + 0xce, 0x81, 0xc0, 0x40, 0xce, 0xa4, 0x7d, 0x7a, 0x0a, 0x4b, 0x65, 0x76, 0xcd, 0x61, 0x09, 0xec, + 0x68, 0xa8, 0x93, 0xdb, 0xd1, 0x90, 0x6c, 0xc1, 0x3c, 0xcb, 0x24, 0xcf, 0xe4, 0xd4, 0xbc, 0x25, + 0x8e, 0xbe, 0x05, 0x38, 0x46, 0x59, 0xf2, 0x9d, 0x4c, 0x58, 0xf2, 0xb7, 0xef, 0xf8, 0xd3, 0x23, + 0xe8, 0xa8, 0x08, 0xcd, 0xa1, 0xde, 0x8e, 0xf5, 0xf7, 0xed, 0xfc, 0xb0, 0xa1, 0xfb, 0x39, 0x49, + 0x39, 0x86, 0xf2, 0xe0, 0x2a, 0x48, 0x2e, 0xf1, 0x19, 0x24, 0xc8, 0x06, 0x34, 0x59, 0x3c, 0xcc, + 0x65, 0xf0, 0x64, 0x3d, 0x05, 0xca, 0xc1, 0x09, 0xde, 0xa6, 0xfd, 0xe6, 0x14, 0x70, 0x0e, 0x22, + 0xc7, 0x15, 0x91, 0xcd, 0x29, 0x91, 0x6d, 0x18, 0x91, 0x3d, 0x44, 0x77, 0x36, 0x6a, 0xbb, 0x80, + 0xde, 0x44, 0x31, 0x3d, 0x6e, 0x17, 0xda, 0x02, 0x79, 0x1c, 0x84, 0xfa, 0x5e, 0x2f, 0xf8, 0xe6, + 0x9c, 0xaf, 0x3f, 0x54, 0xe8, 0xe9, 0xeb, 0xd7, 0x38, 0xfa, 0xdd, 0x02, 0xe7, 0x8c, 0x0f, 0x2b, + 0x92, 0xfd, 0xa7, 0xd3, 0xa7, 0xdb, 0xe0, 0x7c, 0xc2, 0x18, 0x9f, 0x45, 0x67, 0xf0, 0xb3, 0x01, + 0xcb, 0x3e, 0xa6, 0x2c, 0x13, 0x21, 0x9e, 0xe8, 0xff, 0x06, 0xd9, 0x81, 0x39, 0xf5, 0x9a, 0x91, + 0xde, 0x83, 0xcf, 0xb0, 0xbb, 0x32, 0x69, 0x2e, 0x06, 0x4c, 0xff, 0x23, 0xef, 0xa0, 0x99, 0xdf, + 0x74, 0xd2, 0x7d, 0xe8, 0x71, 0x71, 0x7b, 0x13, 0x56, 0x13, 0xb6, 0x07, 0xad, 0xe2, 0x7a, 0x92, + 0x4a, 0xea, 0xea, 0x6b, 0xe0, 0xae, 0xde, 0xb3, 0x9b, 0xe0, 0x01, 0x34, 0x8e, 0x51, 0x92, 0xff, + 0x0d, 0xe2, 0xee, 0x52, 0xba, 0xdd, 0xba, 0xd1, 0xc4, 0x9c, 0x80, 0x53, 0xd3, 0x08, 0x79, 0xf5, + 0xa4, 0x50, 0xdd, 0xd7, 0x8f, 0xb9, 0x4d, 0xc6, 0x5d, 0x68, 0x15, 0x62, 0xa8, 0xb4, 0x50, 0x53, + 0x87, 0xbb, 0x72, 0x6f, 0x7d, 0x87, 0xf9, 0xff, 0xb7, 0x88, 0x2d, 0x36, 0x57, 0x89, 0xad, 0xad, + 0xf2, 0xf1, 0xd8, 0xf3, 0x96, 0xb2, 0x6c, 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0xac, 0x08, 0xfd, + 0x9c, 0xf6, 0x07, 0x00, 0x00, } diff --git a/sdk/js/src/lumirpc/analyzer_pb.js b/sdk/js/src/lumirpc/analyzer_pb.js index 70bb010bb..add8fd5fb 100644 --- a/sdk/js/src/lumirpc/analyzer_pb.js +++ b/sdk/js/src/lumirpc/analyzer_pb.js @@ -511,7 +511,8 @@ proto.lumirpc.AnalyzeResourceRequest.prototype.toObject = function(opt_includeIn proto.lumirpc.AnalyzeResourceRequest.toObject = function(includeInstance, msg) { var f, obj = { type: jspb.Message.getFieldWithDefault(msg, 1, ""), - properties: (f = msg.getProperties()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f) + properties: (f = msg.getProperties()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f), + unknownsMap: (f = msg.getUnknownsMap()) ? f.toObject(includeInstance, undefined) : [] }; if (includeInstance) { @@ -557,6 +558,12 @@ proto.lumirpc.AnalyzeResourceRequest.deserializeBinaryFromReader = function(msg, reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader); msg.setProperties(value); break; + case 3: + var value = msg.getUnknownsMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readBool); + }); + break; default: reader.skipField(); break; @@ -600,6 +607,10 @@ proto.lumirpc.AnalyzeResourceRequest.serializeBinaryToWriter = function(message, google_protobuf_struct_pb.Struct.serializeBinaryToWriter ); } + f = message.getUnknownsMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(3, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeBool); + } }; @@ -648,6 +659,24 @@ proto.lumirpc.AnalyzeResourceRequest.prototype.hasProperties = function() { }; +/** + * map unknowns = 3; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.lumirpc.AnalyzeResourceRequest.prototype.getUnknownsMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 3, opt_noLazyCreate, + null)); +}; + + +proto.lumirpc.AnalyzeResourceRequest.prototype.clearUnknownsMap = function() { + this.getUnknownsMap().clear(); +}; + + /** * Generated by JsPbCodeGenerator. diff --git a/sdk/js/src/lumirpc/provider_pb.js b/sdk/js/src/lumirpc/provider_pb.js index f302bd75a..024f111ab 100644 --- a/sdk/js/src/lumirpc/provider_pb.js +++ b/sdk/js/src/lumirpc/provider_pb.js @@ -11,7 +11,6 @@ var global = Function('return this')(); var google_protobuf_empty_pb = require('google-protobuf/google/protobuf/empty_pb.js'); var google_protobuf_struct_pb = require('google-protobuf/google/protobuf/struct_pb.js'); -goog.exportSymbol('proto.lumirpc.ChangeRequest', null, global); goog.exportSymbol('proto.lumirpc.CheckFailure', null, global); goog.exportSymbol('proto.lumirpc.CheckRequest', null, global); goog.exportSymbol('proto.lumirpc.CheckResponse', null, global); @@ -20,9 +19,11 @@ goog.exportSymbol('proto.lumirpc.CreateResponse', null, global); goog.exportSymbol('proto.lumirpc.DeleteRequest', null, global); goog.exportSymbol('proto.lumirpc.GetRequest', null, global); goog.exportSymbol('proto.lumirpc.GetResponse', null, global); +goog.exportSymbol('proto.lumirpc.InspectChangeRequest', null, global); goog.exportSymbol('proto.lumirpc.InspectChangeResponse', null, global); goog.exportSymbol('proto.lumirpc.NameRequest', null, global); goog.exportSymbol('proto.lumirpc.NameResponse', null, global); +goog.exportSymbol('proto.lumirpc.UpdateRequest', null, global); /** * Generated by JsPbCodeGenerator. @@ -70,7 +71,8 @@ proto.lumirpc.CheckRequest.prototype.toObject = function(opt_includeInstance) { proto.lumirpc.CheckRequest.toObject = function(includeInstance, msg) { var f, obj = { type: jspb.Message.getFieldWithDefault(msg, 1, ""), - properties: (f = msg.getProperties()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f) + properties: (f = msg.getProperties()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f), + unknownsMap: (f = msg.getUnknownsMap()) ? f.toObject(includeInstance, undefined) : [] }; if (includeInstance) { @@ -116,6 +118,12 @@ proto.lumirpc.CheckRequest.deserializeBinaryFromReader = function(msg, reader) { reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader); msg.setProperties(value); break; + case 3: + var value = msg.getUnknownsMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readBool); + }); + break; default: reader.skipField(); break; @@ -159,6 +167,10 @@ proto.lumirpc.CheckRequest.serializeBinaryToWriter = function(message, writer) { google_protobuf_struct_pb.Struct.serializeBinaryToWriter ); } + f = message.getUnknownsMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(3, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeBool); + } }; @@ -207,6 +219,24 @@ proto.lumirpc.CheckRequest.prototype.hasProperties = function() { }; +/** + * map unknowns = 3; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.lumirpc.CheckRequest.prototype.getUnknownsMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 3, opt_noLazyCreate, + null)); +}; + + +proto.lumirpc.CheckRequest.prototype.clearUnknownsMap = function() { + this.getUnknownsMap().clear(); +}; + + /** * Generated by JsPbCodeGenerator. @@ -589,7 +619,8 @@ proto.lumirpc.NameRequest.prototype.toObject = function(opt_includeInstance) { proto.lumirpc.NameRequest.toObject = function(includeInstance, msg) { var f, obj = { type: jspb.Message.getFieldWithDefault(msg, 1, ""), - properties: (f = msg.getProperties()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f) + properties: (f = msg.getProperties()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f), + unknownsMap: (f = msg.getUnknownsMap()) ? f.toObject(includeInstance, undefined) : [] }; if (includeInstance) { @@ -635,6 +666,12 @@ proto.lumirpc.NameRequest.deserializeBinaryFromReader = function(msg, reader) { reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader); msg.setProperties(value); break; + case 3: + var value = msg.getUnknownsMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readBool); + }); + break; default: reader.skipField(); break; @@ -678,6 +715,10 @@ proto.lumirpc.NameRequest.serializeBinaryToWriter = function(message, writer) { google_protobuf_struct_pb.Struct.serializeBinaryToWriter ); } + f = message.getUnknownsMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(3, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeBool); + } }; @@ -726,6 +767,24 @@ proto.lumirpc.NameRequest.prototype.hasProperties = function() { }; +/** + * map unknowns = 3; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.lumirpc.NameRequest.prototype.getUnknownsMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 3, opt_noLazyCreate, + null)); +}; + + +proto.lumirpc.NameRequest.prototype.clearUnknownsMap = function() { + this.getUnknownsMap().clear(); +}; + + /** * Generated by JsPbCodeGenerator. @@ -1096,8 +1155,7 @@ proto.lumirpc.CreateResponse.prototype.toObject = function(opt_includeInstance) */ proto.lumirpc.CreateResponse.toObject = function(includeInstance, msg) { var f, obj = { - id: jspb.Message.getFieldWithDefault(msg, 1, ""), - outputs: (f = msg.getOutputs()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f) + id: jspb.Message.getFieldWithDefault(msg, 1, "") }; if (includeInstance) { @@ -1138,11 +1196,6 @@ proto.lumirpc.CreateResponse.deserializeBinaryFromReader = function(msg, reader) var value = /** @type {string} */ (reader.readString()); msg.setId(value); break; - case 2: - var value = new google_protobuf_struct_pb.Struct; - reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader); - msg.setOutputs(value); - break; default: reader.skipField(); break; @@ -1178,14 +1231,6 @@ proto.lumirpc.CreateResponse.serializeBinaryToWriter = function(message, writer) f ); } - f = message.getOutputs(); - if (f != null) { - writer.writeMessage( - 2, - f, - google_protobuf_struct_pb.Struct.serializeBinaryToWriter - ); - } }; @@ -1204,36 +1249,6 @@ proto.lumirpc.CreateResponse.prototype.setId = function(value) { }; -/** - * optional google.protobuf.Struct outputs = 2; - * @return {?proto.google.protobuf.Struct} - */ -proto.lumirpc.CreateResponse.prototype.getOutputs = function() { - return /** @type{?proto.google.protobuf.Struct} */ ( - jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 2)); -}; - - -/** @param {?proto.google.protobuf.Struct|undefined} value */ -proto.lumirpc.CreateResponse.prototype.setOutputs = function(value) { - jspb.Message.setWrapperField(this, 2, value); -}; - - -proto.lumirpc.CreateResponse.prototype.clearOutputs = function() { - this.setOutputs(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {!boolean} - */ -proto.lumirpc.CreateResponse.prototype.hasOutputs = function() { - return jspb.Message.getField(this, 2) != null; -}; - - /** * Generated by JsPbCodeGenerator. @@ -1569,12 +1584,12 @@ proto.lumirpc.GetResponse.prototype.hasProperties = function() { * @extends {jspb.Message} * @constructor */ -proto.lumirpc.ChangeRequest = function(opt_data) { +proto.lumirpc.InspectChangeRequest = function(opt_data) { jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.lumirpc.ChangeRequest, jspb.Message); +goog.inherits(proto.lumirpc.InspectChangeRequest, jspb.Message); if (goog.DEBUG && !COMPILED) { - proto.lumirpc.ChangeRequest.displayName = 'proto.lumirpc.ChangeRequest'; + proto.lumirpc.InspectChangeRequest.displayName = 'proto.lumirpc.InspectChangeRequest'; } @@ -1589,8 +1604,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * for transitional soy proto support: http://goto/soy-param-migration * @return {!Object} */ -proto.lumirpc.ChangeRequest.prototype.toObject = function(opt_includeInstance) { - return proto.lumirpc.ChangeRequest.toObject(opt_includeInstance, this); +proto.lumirpc.InspectChangeRequest.prototype.toObject = function(opt_includeInstance) { + return proto.lumirpc.InspectChangeRequest.toObject(opt_includeInstance, this); }; @@ -1599,15 +1614,16 @@ proto.lumirpc.ChangeRequest.prototype.toObject = function(opt_includeInstance) { * @param {boolean|undefined} includeInstance Whether to include the JSPB * instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.lumirpc.ChangeRequest} msg The msg instance to transform. + * @param {!proto.lumirpc.InspectChangeRequest} msg The msg instance to transform. * @return {!Object} */ -proto.lumirpc.ChangeRequest.toObject = function(includeInstance, msg) { +proto.lumirpc.InspectChangeRequest.toObject = function(includeInstance, msg) { var f, obj = { id: jspb.Message.getFieldWithDefault(msg, 1, ""), type: jspb.Message.getFieldWithDefault(msg, 2, ""), olds: (f = msg.getOlds()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f), - news: (f = msg.getNews()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f) + news: (f = msg.getNews()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f), + unknownsMap: (f = msg.getUnknownsMap()) ? f.toObject(includeInstance, undefined) : [] }; if (includeInstance) { @@ -1621,23 +1637,23 @@ proto.lumirpc.ChangeRequest.toObject = function(includeInstance, msg) { /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.lumirpc.ChangeRequest} + * @return {!proto.lumirpc.InspectChangeRequest} */ -proto.lumirpc.ChangeRequest.deserializeBinary = function(bytes) { +proto.lumirpc.InspectChangeRequest.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.lumirpc.ChangeRequest; - return proto.lumirpc.ChangeRequest.deserializeBinaryFromReader(msg, reader); + var msg = new proto.lumirpc.InspectChangeRequest; + return proto.lumirpc.InspectChangeRequest.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.lumirpc.ChangeRequest} msg The message object to deserialize into. + * @param {!proto.lumirpc.InspectChangeRequest} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.lumirpc.ChangeRequest} + * @return {!proto.lumirpc.InspectChangeRequest} */ -proto.lumirpc.ChangeRequest.deserializeBinaryFromReader = function(msg, reader) { +proto.lumirpc.InspectChangeRequest.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -1662,6 +1678,12 @@ proto.lumirpc.ChangeRequest.deserializeBinaryFromReader = function(msg, reader) reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader); msg.setNews(value); break; + case 5: + var value = msg.getUnknownsMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readBool); + }); + break; default: reader.skipField(); break; @@ -1675,9 +1697,9 @@ proto.lumirpc.ChangeRequest.deserializeBinaryFromReader = function(msg, reader) * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.lumirpc.ChangeRequest.prototype.serializeBinary = function() { +proto.lumirpc.InspectChangeRequest.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.lumirpc.ChangeRequest.serializeBinaryToWriter(this, writer); + proto.lumirpc.InspectChangeRequest.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -1685,10 +1707,10 @@ proto.lumirpc.ChangeRequest.prototype.serializeBinary = function() { /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.lumirpc.ChangeRequest} message + * @param {!proto.lumirpc.InspectChangeRequest} message * @param {!jspb.BinaryWriter} writer */ -proto.lumirpc.ChangeRequest.serializeBinaryToWriter = function(message, writer) { +proto.lumirpc.InspectChangeRequest.serializeBinaryToWriter = function(message, writer) { var f = undefined; f = message.getId(); if (f.length > 0) { @@ -1720,6 +1742,10 @@ proto.lumirpc.ChangeRequest.serializeBinaryToWriter = function(message, writer) google_protobuf_struct_pb.Struct.serializeBinaryToWriter ); } + f = message.getUnknownsMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(5, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeBool); + } }; @@ -1727,13 +1753,13 @@ proto.lumirpc.ChangeRequest.serializeBinaryToWriter = function(message, writer) * optional string id = 1; * @return {string} */ -proto.lumirpc.ChangeRequest.prototype.getId = function() { +proto.lumirpc.InspectChangeRequest.prototype.getId = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); }; /** @param {string} value */ -proto.lumirpc.ChangeRequest.prototype.setId = function(value) { +proto.lumirpc.InspectChangeRequest.prototype.setId = function(value) { jspb.Message.setField(this, 1, value); }; @@ -1742,13 +1768,13 @@ proto.lumirpc.ChangeRequest.prototype.setId = function(value) { * optional string type = 2; * @return {string} */ -proto.lumirpc.ChangeRequest.prototype.getType = function() { +proto.lumirpc.InspectChangeRequest.prototype.getType = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); }; /** @param {string} value */ -proto.lumirpc.ChangeRequest.prototype.setType = function(value) { +proto.lumirpc.InspectChangeRequest.prototype.setType = function(value) { jspb.Message.setField(this, 2, value); }; @@ -1757,19 +1783,19 @@ proto.lumirpc.ChangeRequest.prototype.setType = function(value) { * optional google.protobuf.Struct olds = 3; * @return {?proto.google.protobuf.Struct} */ -proto.lumirpc.ChangeRequest.prototype.getOlds = function() { +proto.lumirpc.InspectChangeRequest.prototype.getOlds = function() { return /** @type{?proto.google.protobuf.Struct} */ ( jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 3)); }; /** @param {?proto.google.protobuf.Struct|undefined} value */ -proto.lumirpc.ChangeRequest.prototype.setOlds = function(value) { +proto.lumirpc.InspectChangeRequest.prototype.setOlds = function(value) { jspb.Message.setWrapperField(this, 3, value); }; -proto.lumirpc.ChangeRequest.prototype.clearOlds = function() { +proto.lumirpc.InspectChangeRequest.prototype.clearOlds = function() { this.setOlds(undefined); }; @@ -1778,7 +1804,7 @@ proto.lumirpc.ChangeRequest.prototype.clearOlds = function() { * Returns whether this field is set. * @return {!boolean} */ -proto.lumirpc.ChangeRequest.prototype.hasOlds = function() { +proto.lumirpc.InspectChangeRequest.prototype.hasOlds = function() { return jspb.Message.getField(this, 3) != null; }; @@ -1787,19 +1813,19 @@ proto.lumirpc.ChangeRequest.prototype.hasOlds = function() { * optional google.protobuf.Struct news = 4; * @return {?proto.google.protobuf.Struct} */ -proto.lumirpc.ChangeRequest.prototype.getNews = function() { +proto.lumirpc.InspectChangeRequest.prototype.getNews = function() { return /** @type{?proto.google.protobuf.Struct} */ ( jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 4)); }; /** @param {?proto.google.protobuf.Struct|undefined} value */ -proto.lumirpc.ChangeRequest.prototype.setNews = function(value) { +proto.lumirpc.InspectChangeRequest.prototype.setNews = function(value) { jspb.Message.setWrapperField(this, 4, value); }; -proto.lumirpc.ChangeRequest.prototype.clearNews = function() { +proto.lumirpc.InspectChangeRequest.prototype.clearNews = function() { this.setNews(undefined); }; @@ -1808,11 +1834,29 @@ proto.lumirpc.ChangeRequest.prototype.clearNews = function() { * Returns whether this field is set. * @return {!boolean} */ -proto.lumirpc.ChangeRequest.prototype.hasNews = function() { +proto.lumirpc.InspectChangeRequest.prototype.hasNews = function() { return jspb.Message.getField(this, 4) != null; }; +/** + * map unknowns = 5; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.lumirpc.InspectChangeRequest.prototype.getUnknownsMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 5, opt_noLazyCreate, + null)); +}; + + +proto.lumirpc.InspectChangeRequest.prototype.clearUnknownsMap = function() { + this.getUnknownsMap().clear(); +}; + + /** * Generated by JsPbCodeGenerator. @@ -2021,6 +2065,261 @@ proto.lumirpc.InspectChangeResponse.prototype.hasChanges = function() { +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.lumirpc.UpdateRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.lumirpc.UpdateRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.lumirpc.UpdateRequest.displayName = 'proto.lumirpc.UpdateRequest'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.lumirpc.UpdateRequest.prototype.toObject = function(opt_includeInstance) { + return proto.lumirpc.UpdateRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.lumirpc.UpdateRequest} msg The msg instance to transform. + * @return {!Object} + */ +proto.lumirpc.UpdateRequest.toObject = function(includeInstance, msg) { + var f, obj = { + id: jspb.Message.getFieldWithDefault(msg, 1, ""), + type: jspb.Message.getFieldWithDefault(msg, 2, ""), + olds: (f = msg.getOlds()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f), + news: (f = msg.getNews()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.lumirpc.UpdateRequest} + */ +proto.lumirpc.UpdateRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.lumirpc.UpdateRequest; + return proto.lumirpc.UpdateRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.lumirpc.UpdateRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.lumirpc.UpdateRequest} + */ +proto.lumirpc.UpdateRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setId(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setType(value); + break; + case 3: + var value = new google_protobuf_struct_pb.Struct; + reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader); + msg.setOlds(value); + break; + case 4: + var value = new google_protobuf_struct_pb.Struct; + reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader); + msg.setNews(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.lumirpc.UpdateRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.lumirpc.UpdateRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.lumirpc.UpdateRequest} message + * @param {!jspb.BinaryWriter} writer + */ +proto.lumirpc.UpdateRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getType(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getOlds(); + if (f != null) { + writer.writeMessage( + 3, + f, + google_protobuf_struct_pb.Struct.serializeBinaryToWriter + ); + } + f = message.getNews(); + if (f != null) { + writer.writeMessage( + 4, + f, + google_protobuf_struct_pb.Struct.serializeBinaryToWriter + ); + } +}; + + +/** + * optional string id = 1; + * @return {string} + */ +proto.lumirpc.UpdateRequest.prototype.getId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** @param {string} value */ +proto.lumirpc.UpdateRequest.prototype.setId = function(value) { + jspb.Message.setField(this, 1, value); +}; + + +/** + * optional string type = 2; + * @return {string} + */ +proto.lumirpc.UpdateRequest.prototype.getType = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** @param {string} value */ +proto.lumirpc.UpdateRequest.prototype.setType = function(value) { + jspb.Message.setField(this, 2, value); +}; + + +/** + * optional google.protobuf.Struct olds = 3; + * @return {?proto.google.protobuf.Struct} + */ +proto.lumirpc.UpdateRequest.prototype.getOlds = function() { + return /** @type{?proto.google.protobuf.Struct} */ ( + jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 3)); +}; + + +/** @param {?proto.google.protobuf.Struct|undefined} value */ +proto.lumirpc.UpdateRequest.prototype.setOlds = function(value) { + jspb.Message.setWrapperField(this, 3, value); +}; + + +proto.lumirpc.UpdateRequest.prototype.clearOlds = function() { + this.setOlds(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.lumirpc.UpdateRequest.prototype.hasOlds = function() { + return jspb.Message.getField(this, 3) != null; +}; + + +/** + * optional google.protobuf.Struct news = 4; + * @return {?proto.google.protobuf.Struct} + */ +proto.lumirpc.UpdateRequest.prototype.getNews = function() { + return /** @type{?proto.google.protobuf.Struct} */ ( + jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 4)); +}; + + +/** @param {?proto.google.protobuf.Struct|undefined} value */ +proto.lumirpc.UpdateRequest.prototype.setNews = function(value) { + jspb.Message.setWrapperField(this, 4, value); +}; + + +proto.lumirpc.UpdateRequest.prototype.clearNews = function() { + this.setNews(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.lumirpc.UpdateRequest.prototype.hasNews = function() { + return jspb.Message.getField(this, 4) != null; +}; + + + /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a diff --git a/sdk/proto/analyzer.proto b/sdk/proto/analyzer.proto index 952b14f13..c8343f38d 100644 --- a/sdk/proto/analyzer.proto +++ b/sdk/proto/analyzer.proto @@ -43,6 +43,7 @@ message AnalyzeFailure { message AnalyzeResourceRequest { string type = 1; // the type token of the resource. google.protobuf.Struct properties = 2; // the full properties to use for validation. + map unknowns = 3; // the optional set of properties whose values are unknown. } message AnalyzeResourceResponse { diff --git a/sdk/proto/provider.proto b/sdk/proto/provider.proto index f3be5a627..bf032d0c4 100644 --- a/sdk/proto/provider.proto +++ b/sdk/proto/provider.proto @@ -34,12 +34,12 @@ service ResourceProvider { // Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID // must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational"). rpc Create(CreateRequest) returns (CreateResponse) {} - // Get reads the instance state identified by ID, returning a populated resource object, or an error if not found. + // Get reads the instance state identified by ID, returning a populated resource object, or nil if not found. rpc Get(GetRequest) returns (GetResponse) {} // InspectChange checks what impacts a hypothetical update will have on the resource's properties. - rpc InspectChange(ChangeRequest) returns (InspectChangeResponse) {} + rpc InspectChange(InspectChangeRequest) returns (InspectChangeResponse) {} // Update updates an existing resource with new values. - rpc Update(ChangeRequest) returns (google.protobuf.Empty) {} + rpc Update(UpdateRequest) returns (google.protobuf.Empty) {} // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. rpc Delete(DeleteRequest) returns (google.protobuf.Empty) {} } @@ -47,6 +47,7 @@ service ResourceProvider { message CheckRequest { string type = 1; // the type token of the resource. google.protobuf.Struct properties = 2; // the full properties to use for validation. + map unknowns = 3; // the optional set of properties whose values are unknown. } message CheckResponse { @@ -61,6 +62,7 @@ message CheckFailure { message NameRequest { string type = 1; // the type token of the resource. google.protobuf.Struct properties = 2; // the full properties to use for name creation. + map unknowns = 3; // the optional set of properties whose values are unknown. } message NameResponse { @@ -73,8 +75,7 @@ message CreateRequest { } message CreateResponse { - string id = 1; // the ID of the resource created. - google.protobuf.Struct outputs = 2; // any properties populated at creation time. + string id = 1; // the ID of the resource created. } message GetRequest { @@ -86,11 +87,12 @@ message GetResponse { google.protobuf.Struct properties = 1; // the properties read from the resource. } -message ChangeRequest { - string id = 1; // the ID of the resource to update. - string type = 2; // the type token of the resource to update. - google.protobuf.Struct olds = 3; // the old values of properties to update. - google.protobuf.Struct news = 4; // the new values of properties to update. +message InspectChangeRequest { + string id = 1; // the ID of the resource to inspect. + string type = 2; // the type token of the resource to inspect. + google.protobuf.Struct olds = 3; // the old values of properties to inspect. + google.protobuf.Struct news = 4; // the new values of properties to inspect. + map unknowns = 5; // the optional set of properties whose values are unknown. } message InspectChangeResponse { @@ -98,6 +100,13 @@ message InspectChangeResponse { google.protobuf.Struct changes = 2; // the set of properties that will be changed (but don't require a replacement). } +message UpdateRequest { + string id = 1; // the ID of the resource to update. + string type = 2; // the type token of the resource to update. + google.protobuf.Struct olds = 3; // the old values of properties to update. + google.protobuf.Struct news = 4; // the new values of properties to update. +} + message DeleteRequest { string id = 1; // the ID of the resource to delete. string type = 2; // the type token of the resource to update.