Merge pull request #186 from pulumi/lumi-90-outprops

Implement 1/3rds of output properties
This commit is contained in:
Joe Duffy 2017-06-01 10:28:39 -07:00 committed by GitHub
commit 4ec2745fc1
124 changed files with 8217 additions and 2186 deletions

View file

@ -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))
}
}

View file

@ -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,

View file

@ -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("<null>")
} 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] + "+-" }

View file

@ -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")

View file

@ -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)
}

View file

@ -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("<null>")
} 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] + "+-" }

View file

@ -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 {

View file

@ -1648,7 +1648,6 @@ export class Transformer {
private async transformClassDeclaration(
modtok: tokens.ModuleToken, node: ts.ClassDeclaration): Promise<ast.Class> {
// 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, <ast.Class>{
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<ast.Class> {
// 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, <ast.Class>{
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<VariableDeclaration<ast.LocalVariable>> {
// 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<ts.Decorator>): Promise<ast.Attribute[] | undefined> {
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<VariableDeclaration<ast.LocalVariable>> {
// 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<ast.ClassMethod> {
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, <ast.ClassMethod>{
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<ast.ClassProperty>(
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<ast.ClassMethod> {
let decl: FunctionLikeDeclaration = await this.transformFunctionLikeOrSignatureCommon(node, false);
let attributes: ast.Attribute[] | undefined = await this.transformDecorators(node.decorators);
return this.withLocation(node, <ast.ClassMethod>{
kind: ast.classMethodKind,
name: decl.name,
attributes: attributes,
access: this.getClassAccessibility(node),
parameters: decl.parameters,
returnType: decl.returnType,

View file

@ -0,0 +1,4 @@
{
"name": "basic/decorators"
}

File diff suppressed because it is too large Load diff

View file

@ -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) {}

View file

@ -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 { }
}

View file

@ -0,0 +1,11 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es5"
},
"files": [
"index.ts",
"decors.ts"
]
}

View file

@ -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{

View file

@ -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.

View file

@ -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

View file

@ -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();

View file

@ -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[];

View file

@ -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;
}

View file

@ -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();

View file

@ -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();

View file

@ -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();

202
lib/aws/provider/arn/arn.go Normal file
View file

@ -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()
}

View file

@ -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))
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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,

View file

@ -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)}})
}

View file

@ -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",
)
}

View file

@ -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

View file

@ -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 <bucket>/<key>")
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
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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{

View file

@ -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)

View file

@ -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
}

View file

@ -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 (`<bucket>%v<key>`)", 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.
}

View file

@ -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"`
}

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`
}

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`
}

View file

@ -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"`

View file

@ -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"`

View file

@ -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"

View file

@ -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.

View file

@ -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"`

View file

@ -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"`
}

View file

@ -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"

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`
}

View file

@ -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"`
}

View file

@ -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"`
}

View file

@ -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"
)

View file

@ -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"

View file

@ -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"`

View file

@ -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"`

View file

@ -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"

View file

@ -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"`

View file

@ -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"`

View file

@ -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"

View file

@ -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"`

View file

@ -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"`
}

View file

@ -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
}

View file

@ -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"`

View file

@ -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"`

View file

@ -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.
}

View file

@ -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.

View file

@ -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
}

View file

@ -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,
}
}

View file

@ -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 }

View file

@ -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.

View file

@ -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()) }

View file

@ -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
}

View file

@ -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
}

View file

@ -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("<error>") // a type for internal compiler errors; not for direct use.
Error = symbols.NewPrimitiveType("<error>", false) // a type for internal compiler errors; not for direct use.
)

View file

@ -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
}

View file

@ -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())<<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
@ -2056,7 +2171,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())>>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
}

View file

@ -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())
}

View file

@ -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

View file

@ -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 {

View file

@ -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)

View file

@ -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 }

View file

@ -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
}
}

View file

@ -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),
}

View file

@ -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": "<URN>" }` 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

View file

@ -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)
}
}

Some files were not shown because too many files have changed in this diff Show more