diff --git a/cmd/lumi/deploy.go b/cmd/lumi/deploy.go index 5a5d19d40..a62246fd6 100644 --- a/cmd/lumi/deploy.go +++ b/cmd/lumi/deploy.go @@ -37,7 +37,8 @@ func newDeployCmd() *cobra.Command { var dryRun bool var env string var showConfig bool - var showReplaceSteps bool + var showReads bool + var showReplaceDeletes bool var showSames bool var summary bool var output string @@ -62,14 +63,15 @@ func newDeployCmd() *cobra.Command { return err } deployLatest(cmd, info, deployOptions{ - Delete: false, - DryRun: dryRun, - Analyzers: analyzers, - ShowConfig: showConfig, - ShowReplaceSteps: showReplaceSteps, - ShowSames: showSames, - Summary: summary, - Output: output, + Delete: false, + DryRun: dryRun, + Analyzers: analyzers, + ShowConfig: showConfig, + ShowReads: showReads, + ShowReplaceDeletes: showReplaceDeletes, + ShowSames: showSames, + Summary: summary, + Output: output, }) return nil }), @@ -88,7 +90,10 @@ func newDeployCmd() *cobra.Command { &showConfig, "show-config", false, "Show configuration keys and variables") cmd.PersistentFlags().BoolVar( - &showReplaceSteps, "show-replace-steps", false, + &showReads, "show-reads", false, + "Show resources that will be read, in addition to those that will be modified") + cmd.PersistentFlags().BoolVar( + &showReplaceDeletes, "show-replace-deletes", false, "Show detailed resource replacement creates and deletes; normally shows as a single step") cmd.PersistentFlags().BoolVar( &showSames, "show-sames", false, @@ -104,16 +109,17 @@ func newDeployCmd() *cobra.Command { } 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. - ShowSames 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. + 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. + ShowReads bool // true to show the read-only steps in the plan. + ShowReplaceDeletes bool // true to show the replacement deletion steps in the plan. + ShowSames 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. } func deployLatest(cmd *cobra.Command, info *envCmdInfo, opts deployOptions) error { @@ -132,7 +138,7 @@ func deployLatest(cmd *cobra.Command, info *envCmdInfo, opts deployOptions) erro // Create an object to track progress and perform the actual operations. start := time.Now() - progress := newProgress(opts.Summary) + progress := newProgress(opts) summary, _, _, err := result.Plan.Apply(progress) contract.Assert(summary != nil) empty := (summary.Steps() == 0) // if no step is returned, it was empty. @@ -143,7 +149,7 @@ func deployLatest(cmd *cobra.Command, info *envCmdInfo, opts deployOptions) erro cmdutil.Diag().Infof(diag.Message("no resources need to be updated")) } else { // Print out the total number of steps performed (and their kinds), the duration, and any summary info. - printSummary(&footer, progress.Ops, opts.ShowReplaceSteps, false) + printSummary(&footer, progress.Ops, false) footer.WriteString(fmt.Sprintf("%vDeployment duration: %v%v\n", colors.SpecUnimportant, time.Since(start), colors.Reset)) } @@ -171,18 +177,18 @@ type deployProgress struct { Steps int Ops map[deploy.StepOp]int MaybeCorrupt bool - Summary bool + Opts deployOptions } -func newProgress(summary bool) *deployProgress { +func newProgress(opts deployOptions) *deployProgress { return &deployProgress{ - Steps: 0, - Ops: make(map[deploy.StepOp]int), - Summary: summary, + Steps: 0, + Ops: make(map[deploy.StepOp]int), + Opts: opts, } } -func (prog *deployProgress) Before(step *deploy.Step) { +func (prog *deployProgress) Before(step deploy.Step) { stepop := step.Op() if stepop == deploy.OpSame { return @@ -192,17 +198,18 @@ func (prog *deployProgress) Before(step *deploy.Step) { stepnum := prog.Steps + 1 var extra string - if stepop == deploy.OpReplaceCreate || stepop == deploy.OpReplaceDelete { + if stepop == deploy.OpReplace || + (stepop == deploy.OpDelete && step.(*deploy.DeleteStep).Replaced()) { 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, false, "") + printStep(&b, step, prog.Opts.Summary, false, "") fmt.Print(colors.Colorize(&b)) } -func (prog *deployProgress) After(step *deploy.Step, status resource.Status, err error) { +func (prog *deployProgress) After(step deploy.Step, status resource.Status, err error) { stepop := step.Op() if err != nil { // Issue a true, bonafide error. @@ -226,7 +233,7 @@ func (prog *deployProgress) After(step *deploy.Step, status resource.Status, err b.WriteString(colors.Reset) b.WriteString("\n") fmt.Printf(colors.Colorize(&b)) - } else if stepop != deploy.OpSame { + } else if shouldTrack(step, prog.Opts) { // Increment the counters. prog.Steps++ prog.Ops[stepop]++ diff --git a/cmd/lumi/plan.go b/cmd/lumi/plan.go index cd30ee2ee..71b025f06 100644 --- a/cmd/lumi/plan.go +++ b/cmd/lumi/plan.go @@ -21,6 +21,7 @@ import ( "sort" "strconv" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/lumi/pkg/diag" @@ -38,7 +39,8 @@ func newPlanCmd() *cobra.Command { var dotOutput bool var env string var showConfig bool - var showReplaceSteps bool + var showReads bool + var showReplaceDeletes bool var showSames bool var summary bool var cmd = &cobra.Command{ @@ -62,14 +64,15 @@ func newPlanCmd() *cobra.Command { } contract.Assertf(!dotOutput, "TODO[pulumi/lumi#235]: DOT files not yet supported") opts := deployOptions{ - Delete: false, - DryRun: true, - Analyzers: analyzers, - ShowConfig: showConfig, - ShowReplaceSteps: showReplaceSteps, - ShowSames: showSames, - Summary: summary, - DOT: dotOutput, + Delete: false, + DryRun: true, + Analyzers: analyzers, + ShowConfig: showConfig, + ShowReads: showReads, + ShowReplaceDeletes: showReplaceDeletes, + ShowSames: showSames, + Summary: summary, + DOT: dotOutput, } if result := plan(cmd, info, opts); result != nil { if err := printPlan(result, opts); err != nil { @@ -93,7 +96,10 @@ func newPlanCmd() *cobra.Command { &showConfig, "show-config", false, "Show configuration keys and variables") cmd.PersistentFlags().BoolVar( - &showReplaceSteps, "show-replace-steps", false, + &showReads, "show-reads", false, + "Show resources that will be read, in addition to those that will be modified") + cmd.PersistentFlags().BoolVar( + &showReplaceDeletes, "show-replace-deletes", false, "Show detailed resource replacement creates and deletes; normally shows as a single step") cmd.PersistentFlags().BoolVar( &showSames, "show-sames", false, @@ -165,41 +171,44 @@ func printPlan(result *planResult, opts deployOptions) error { iter, err := result.Plan.Iterate() if err != nil { - cmdutil.Diag().Errorf(diag.Message("An error occurred while preparing the plan: %v"), err) - return err + return errors.Errorf("An error occurred while preparing the plan: %v", err) } step, err := iter.Next() if err != nil { - cmdutil.Diag().Errorf(diag.Message("An error occurred while enumerating the plan: %v"), err) - return err + return errors.Errorf("An error occurred while enumerating the plan: %v", err) } var summary bytes.Buffer empty := true counts := make(map[deploy.StepOp]int) for step != nil { - op := step.Op() - if empty && op != deploy.OpSame { - empty = false + var err error + + // Perform the pre-step. + if err = step.Pre(); err != nil { + return errors.Errorf("An error occurred preparing the plan: %v", err) } // Print this step information (resource and all its properties). // IDEA: it would be nice if, in the output, we showed the dependencies a la `git log --graph`. - if (opts.ShowReplaceSteps || op != deploy.OpReplaceDelete) && - (opts.ShowSames || op != deploy.OpSame) { + track := shouldTrack(step, opts) + if track { printStep(&summary, step, opts.Summary, true, "") + empty = false } // Be sure to skip the step so that in-memory state updates are performed. - step.Skip() + if err = step.Skip(); err != nil { + return errors.Errorf("An error occurred while advancing the plan: %v", err) + } - counts[step.Op()]++ + if track { + counts[step.Op()]++ + } - var err error if step, err = iter.Next(); err != nil { - cmdutil.Diag().Errorf(diag.Message("An error occurred while viewing the plan: %v"), err) - return err + return errors.Errorf("An error occurred while viewing the plan: %v", err) } } @@ -208,12 +217,27 @@ func printPlan(result *planResult, opts deployOptions) error { cmdutil.Diag().Infof(diag.Message("no resources need to be updated")) } else { // Print a summary of operation counts. - printSummary(&summary, counts, opts.ShowReplaceSteps, true) + printSummary(&summary, counts, true) fmt.Print(colors.Colorize(&summary)) } return nil } +// shouldTrack returns true if the step should be "tracked"; this affects two things: 1) whether the resource is shown +// in the planning phase and 2) whether the resource operation is tallied up and displayed in the final summary. +func shouldTrack(step deploy.Step, opts deployOptions) bool { + // For certain operations, whether they are tracked is controlled by flags (to cut down on superfluous output). + if _, isrd := step.(deploy.ReadStep); isrd { + return opts.ShowReads + } else if step.Op() == deploy.OpSame { + return opts.ShowSames + } else if step.Op() == deploy.OpDelete && step.(*deploy.DeleteStep).Replaced() { + return opts.ShowReplaceDeletes + } + // By default, however, steps are tracked. + return true +} + func printPrelude(b *bytes.Buffer, result *planResult, opts deployOptions, planning bool) { // If there are configuration variables, show them. if opts.ShowConfig { @@ -235,12 +259,9 @@ func printConfig(b *bytes.Buffer, config resource.ConfigMap) { } } -func printSummary(b *bytes.Buffer, counts map[deploy.StepOp]int, showReplaceSteps bool, plan bool) { +func printSummary(b *bytes.Buffer, counts map[deploy.StepOp]int, plan bool) { total := 0 - for op, c := range counts { - if op == deploy.OpSame || (!showReplaceSteps && op == deploy.OpReplaceDelete) { - continue // skip counting replacement steps unless explicitly requested. - } + for _, c := range counts { total += c } @@ -263,10 +284,6 @@ func printSummary(b *bytes.Buffer, counts map[deploy.StepOp]int, showReplaceStep } for _, op := range deploy.StepOps { - if op == deploy.OpSame || (!showReplaceSteps && op == deploy.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)) @@ -288,40 +305,51 @@ func printUnchanged(b *bytes.Buffer, stats deploy.PlanSummary, summary bool, pla for _, res := range stats.Resources() { if stats.Sames()[res.URN()] { b.WriteString(" ") // simulate the 2 spaces for +, -, etc. - printResourceHeader(b, res, nil, "") + printResourceHeader(b, res) printResourceProperties(b, res.URN(), res, nil, nil, nil, summary, planning, "") } } } -func printStep(b *bytes.Buffer, step *deploy.Step, summary bool, planning bool, indent string) { +func printStep(b *bytes.Buffer, step deploy.Step, summary bool, planning 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) + // Next, print the resource type (since it is easy on the eyes and can be quickly identified). + printStepHeader(b, step) b.WriteString(step.Op().Suffix()) - printResourceProperties(b, step.URN(), step.Old(), step.New(), step.Inputs(), step.Reasons(), - summary, planning, indent) + + // Next print the resource URN, properties, etc. + if mut, ismut := step.(deploy.MutatingStep); ismut { + var replaces []resource.PropertyKey + if step.Op() == deploy.OpReplace { + replaces = step.(*deploy.ReplaceStep).Reasons() + } + printResourceProperties(b, + mut.URN(), mut.Old(), mut.New(), mut.Inputs(), replaces, summary, planning, indent) + } else if rd, isrd := step.(deploy.ReadStep); isrd { + for _, res := range rd.Resources() { + printResourceProperties(b, + "", nil, res, res.CopyProperties(), nil, summary, planning, indent) + } + } else { + contract.Failf("Expected each step to either be mutating or read-only") + } // Finally make sure to reset the color. b.WriteString(colors.Reset) } -func printResourceHeader(b *bytes.Buffer, old *resource.State, new *resource.Object, indent string) { - var t tokens.Type - if old != nil { - t = old.Type() - } else { - t = new.Type() - } +func printStepHeader(b *bytes.Buffer, step deploy.Step) { + b.WriteString(fmt.Sprintf("%s:\n", string(step.Type()))) +} - // The primary header is the resource type (since it is easy on the eyes). - b.WriteString(fmt.Sprintf("%s:\n", string(t))) +func printResourceHeader(b *bytes.Buffer, res resource.Resource) { + b.WriteString(fmt.Sprintf("%s:\n", string(res.Type()))) } func printResourceProperties(b *bytes.Buffer, urn resource.URN, old *resource.State, new *resource.Object, - inputs resource.PropertyMap, replaces []resource.PropertyKey, summary bool, planning bool, indent string) { + props resource.PropertyMap, replaces []resource.PropertyKey, summary bool, planning bool, indent string) { indent += detailsIndent // Print out the URN and, if present, the ID, as "pseudo-properties". @@ -332,17 +360,19 @@ func printResourceProperties(b *bytes.Buffer, urn resource.URN, old *resource.St if id != "" { b.WriteString(fmt.Sprintf("%s[id=%s]\n", indent, string(id))) } - b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, urn)) + if urn != "" { + b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, urn)) + } if !summary { // Print all of the properties associated with this resource. if old == nil && new != nil { - printObject(b, inputs, planning, indent) + printObject(b, props, planning, indent) } else if new == nil && old != nil { printObject(b, old.Inputs(), planning, indent) } else { - contract.Assert(inputs != nil) // use computed properties for diffs. - printOldNewDiffs(b, old.Inputs(), inputs, replaces, planning, indent) + contract.Assert(props != nil) // use computed properties for diffs. + printOldNewDiffs(b, old.Inputs(), props, replaces, planning, indent) } } } @@ -373,16 +403,22 @@ func printObject(b *bytes.Buffer, props resource.PropertyMap, planning bool, ind // printResourceOutputProperties prints only those properties that either differ from the input properties or, if // there is an old snapshot of the resource, differ from the prior old snapshot's output properties. -func printResourceOutputProperties(b *bytes.Buffer, step *deploy.Step, indent string) { +func printResourceOutputProperties(b *bytes.Buffer, step deploy.Step, indent string) { + mut, ismut := step.(deploy.MutatingStep) + if !ismut { + // Only mutating steps have output properties associated with them. + return + } + indent += detailsIndent b.WriteString(step.Op().Color()) b.WriteString(step.Op().Suffix()) // First fetch all the relevant property maps that we may consult. - newins := step.Inputs() - newouts := step.Outputs() + newins := mut.Inputs() + newouts := mut.Outputs() var oldouts resource.PropertyMap - if old := step.Old(); old != nil { + if old := mut.Old(); old != nil { oldouts = old.Outputs() } @@ -543,12 +579,12 @@ func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.V titleFunc := func(id string) { printArrayElemHeader(b, i, id) } if add, isadd := a.Adds[i]; isadd { b.WriteString(deploy.OpCreate.Color()) - title(addIndent(indent)) + titleFunc(addIndent(indent)) printPropertyValue(b, add, planning, addIndent(newIndent)) b.WriteString(colors.Reset) } else if delete, isdelete := a.Deletes[i]; isdelete { b.WriteString(deploy.OpDelete.Color()) - title(deleteIndent(indent)) + titleFunc(deleteIndent(indent)) printPropertyValue(b, delete, planning, deleteIndent(newIndent)) b.WriteString(colors.Reset) } else if update, isupdate := a.Updates[i]; isupdate { diff --git a/lib/aws/pack/apigateway/account.ts b/lib/aws/pack/apigateway/account.ts index 5d1f4c170..4d2159b56 100644 --- a/lib/aws/pack/apigateway/account.ts +++ b/lib/aws/pack/apigateway/account.ts @@ -15,6 +15,14 @@ export class Account extends lumi.NamedResource implements AccountArgs { this.cloudWatchRole = args.cloudWatchRole; } } + + public static get(id: lumi.ID): Account { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Account[] { + return undefined; // functionality provided by the runtime + } } export interface AccountArgs { diff --git a/lib/aws/pack/apigateway/apiKey.ts b/lib/aws/pack/apigateway/apiKey.ts index bc40eb79b..dd56ad458 100644 --- a/lib/aws/pack/apigateway/apiKey.ts +++ b/lib/aws/pack/apigateway/apiKey.ts @@ -22,6 +22,14 @@ export class APIKey extends lumi.NamedResource implements APIKeyArgs { this.stageKeys = args.stageKeys; } } + + public static get(id: lumi.ID): APIKey { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): APIKey[] { + return undefined; // functionality provided by the runtime + } } export interface APIKeyArgs { diff --git a/lib/aws/pack/apigateway/authorizer.ts b/lib/aws/pack/apigateway/authorizer.ts index f50ecbd94..5979eea1e 100644 --- a/lib/aws/pack/apigateway/authorizer.ts +++ b/lib/aws/pack/apigateway/authorizer.ts @@ -34,6 +34,14 @@ export class Authorizer extends lumi.NamedResource implements AuthorizerArgs { this.providers = args.providers; this.restAPI = args.restAPI; } + + public static get(id: lumi.ID): Authorizer { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Authorizer[] { + return undefined; // functionality provided by the runtime + } } export interface AuthorizerArgs { diff --git a/lib/aws/pack/apigateway/basePathMapping.ts b/lib/aws/pack/apigateway/basePathMapping.ts index 582bb942f..419713a8e 100644 --- a/lib/aws/pack/apigateway/basePathMapping.ts +++ b/lib/aws/pack/apigateway/basePathMapping.ts @@ -26,6 +26,14 @@ export class BasePathMapping extends lumi.NamedResource implements BasePathMappi this.basePath = args.basePath; this.stage = args.stage; } + + public static get(id: lumi.ID): BasePathMapping { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): BasePathMapping[] { + return undefined; // functionality provided by the runtime + } } export interface BasePathMappingArgs { diff --git a/lib/aws/pack/apigateway/clientCertificate.ts b/lib/aws/pack/apigateway/clientCertificate.ts index 14c9f1ef2..e6461c3cd 100644 --- a/lib/aws/pack/apigateway/clientCertificate.ts +++ b/lib/aws/pack/apigateway/clientCertificate.ts @@ -13,6 +13,14 @@ export class ClientCertificate extends lumi.NamedResource implements ClientCerti this.description = args.description; } } + + public static get(id: lumi.ID): ClientCertificate { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): ClientCertificate[] { + return undefined; // functionality provided by the runtime + } } export interface ClientCertificateArgs { diff --git a/lib/aws/pack/apigateway/deployment.ts b/lib/aws/pack/apigateway/deployment.ts index d7d9ffe70..286d6b504 100644 --- a/lib/aws/pack/apigateway/deployment.ts +++ b/lib/aws/pack/apigateway/deployment.ts @@ -20,6 +20,14 @@ export class Deployment extends lumi.NamedResource implements DeploymentArgs { this.restAPI = args.restAPI; this.description = args.description; } + + public static get(id: lumi.ID): Deployment { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Deployment[] { + return undefined; // functionality provided by the runtime + } } export interface DeploymentArgs { diff --git a/lib/aws/pack/apigateway/method.ts b/lib/aws/pack/apigateway/method.ts index a53fb20c4..8b1606998 100644 --- a/lib/aws/pack/apigateway/method.ts +++ b/lib/aws/pack/apigateway/method.ts @@ -97,6 +97,14 @@ export class Method extends lumi.NamedResource implements MethodArgs { this.requestModels = args.requestModels; this.requestParameters = args.requestParameters; } + + public static get(id: lumi.ID): Method { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Method[] { + return undefined; // functionality provided by the runtime + } } export interface MethodArgs { diff --git a/lib/aws/pack/apigateway/model.ts b/lib/aws/pack/apigateway/model.ts index bdd0b6634..f8d54a925 100644 --- a/lib/aws/pack/apigateway/model.ts +++ b/lib/aws/pack/apigateway/model.ts @@ -30,6 +30,14 @@ export class Model extends lumi.NamedResource implements ModelArgs { this.modelName = args.modelName; this.description = args.description; } + + public static get(id: lumi.ID): Model { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Model[] { + return undefined; // functionality provided by the runtime + } } export interface ModelArgs { diff --git a/lib/aws/pack/apigateway/resource.ts b/lib/aws/pack/apigateway/resource.ts index 1ef51fb22..4d04d78b6 100644 --- a/lib/aws/pack/apigateway/resource.ts +++ b/lib/aws/pack/apigateway/resource.ts @@ -26,6 +26,14 @@ export class Resource extends lumi.NamedResource implements ResourceArgs { } this.restAPI = args.restAPI; } + + public static get(id: lumi.ID): Resource { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Resource[] { + return undefined; // functionality provided by the runtime + } } export interface ResourceArgs { diff --git a/lib/aws/pack/apigateway/restAPI.ts b/lib/aws/pack/apigateway/restAPI.ts index a420a5934..2e5b15417 100644 --- a/lib/aws/pack/apigateway/restAPI.ts +++ b/lib/aws/pack/apigateway/restAPI.ts @@ -32,6 +32,14 @@ export class RestAPI extends lumi.NamedResource implements RestAPIArgs { this.parameters = args.parameters; } } + + public static get(id: lumi.ID): RestAPI { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): RestAPI[] { + return undefined; // functionality provided by the runtime + } } export interface RestAPIArgs { diff --git a/lib/aws/pack/apigateway/stage.ts b/lib/aws/pack/apigateway/stage.ts index 54da3fbcd..fb1d8d2ae 100644 --- a/lib/aws/pack/apigateway/stage.ts +++ b/lib/aws/pack/apigateway/stage.ts @@ -45,6 +45,14 @@ export class Stage extends lumi.NamedResource implements StageArgs { this.methodSettings = args.methodSettings; this.variables = args.variables; } + + public static get(id: lumi.ID): Stage { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Stage[] { + return undefined; // functionality provided by the runtime + } } export interface StageArgs { diff --git a/lib/aws/pack/apigateway/usagePlan.ts b/lib/aws/pack/apigateway/usagePlan.ts index 0e3081144..43293e799 100644 --- a/lib/aws/pack/apigateway/usagePlan.ts +++ b/lib/aws/pack/apigateway/usagePlan.ts @@ -49,6 +49,14 @@ export class UsagePlan extends lumi.NamedResource implements UsagePlanArgs { this.usagePlanName = args.usagePlanName; } } + + public static get(id: lumi.ID): UsagePlan { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): UsagePlan[] { + return undefined; // functionality provided by the runtime + } } export interface UsagePlanArgs { diff --git a/lib/aws/pack/apigateway/usagePlanKey.ts b/lib/aws/pack/apigateway/usagePlanKey.ts index ce1b26452..4b060f3d6 100644 --- a/lib/aws/pack/apigateway/usagePlanKey.ts +++ b/lib/aws/pack/apigateway/usagePlanKey.ts @@ -22,6 +22,14 @@ export class UsagePlanKey extends lumi.NamedResource implements UsagePlanKeyArgs } this.usagePlan = args.usagePlan; } + + public static get(id: lumi.ID): UsagePlanKey { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): UsagePlanKey[] { + return undefined; // functionality provided by the runtime + } } export interface UsagePlanKeyArgs { diff --git a/lib/aws/pack/cloudwatch/alarm.ts b/lib/aws/pack/cloudwatch/alarm.ts index 207833d59..b01c0951e 100644 --- a/lib/aws/pack/cloudwatch/alarm.ts +++ b/lib/aws/pack/cloudwatch/alarm.ts @@ -56,6 +56,14 @@ export class ActionTarget extends lumi.NamedResource implements ActionTargetArgs this.subscription = args.subscription; } } + + public static get(id: lumi.ID): ActionTarget { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): ActionTarget[] { + return undefined; // functionality provided by the runtime + } } export interface ActionTargetArgs { @@ -120,6 +128,14 @@ export class Alarm extends lumi.NamedResource implements AlarmArgs { this.okActions = args.okActions; this.unit = args.unit; } + + public static get(id: lumi.ID): Alarm { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Alarm[] { + return undefined; // functionality provided by the runtime + } } export interface AlarmArgs { diff --git a/lib/aws/pack/dynamodb/table.ts b/lib/aws/pack/dynamodb/table.ts index ce56af031..a7428bbd5 100644 --- a/lib/aws/pack/dynamodb/table.ts +++ b/lib/aws/pack/dynamodb/table.ts @@ -67,6 +67,14 @@ export class Table extends lumi.NamedResource implements TableArgs { this.tableName = args.tableName; this.globalSecondaryIndexes = args.globalSecondaryIndexes; } + + public static get(id: lumi.ID): Table { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Table[] { + return undefined; // functionality provided by the runtime + } } export interface TableArgs { diff --git a/lib/aws/pack/ec2/instance.ts b/lib/aws/pack/ec2/instance.ts index 877b2b8de..8dfd2c1ae 100644 --- a/lib/aws/pack/ec2/instance.ts +++ b/lib/aws/pack/ec2/instance.ts @@ -87,6 +87,14 @@ export class Instance extends lumi.NamedResource implements InstanceArgs { this.keyName = args.keyName; this.tags = args.tags; } + + public static get(id: lumi.ID): Instance { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Instance[] { + return undefined; // functionality provided by the runtime + } } export interface InstanceArgs { diff --git a/lib/aws/pack/ec2/internetGateway.ts b/lib/aws/pack/ec2/internetGateway.ts index cdb3b5345..fbc3d742f 100644 --- a/lib/aws/pack/ec2/internetGateway.ts +++ b/lib/aws/pack/ec2/internetGateway.ts @@ -9,6 +9,14 @@ export class InternetGateway extends lumi.NamedResource implements InternetGatew constructor(name: string, args?: InternetGatewayArgs) { super(name); } + + public static get(id: lumi.ID): InternetGateway { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): InternetGateway[] { + return undefined; // functionality provided by the runtime + } } export interface InternetGatewayArgs { diff --git a/lib/aws/pack/ec2/route.ts b/lib/aws/pack/ec2/route.ts index 2435eac31..fd95562ae 100644 --- a/lib/aws/pack/ec2/route.ts +++ b/lib/aws/pack/ec2/route.ts @@ -33,6 +33,14 @@ export class Route extends lumi.NamedResource implements RouteArgs { } this.vpcGatewayAttachment = args.vpcGatewayAttachment; } + + public static get(id: lumi.ID): Route { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Route[] { + return undefined; // functionality provided by the runtime + } } export interface RouteArgs { diff --git a/lib/aws/pack/ec2/routeTable.ts b/lib/aws/pack/ec2/routeTable.ts index a2a64280b..01e37b6c2 100644 --- a/lib/aws/pack/ec2/routeTable.ts +++ b/lib/aws/pack/ec2/routeTable.ts @@ -16,6 +16,14 @@ export class RouteTable extends lumi.NamedResource implements RouteTableArgs { } this.vpc = args.vpc; } + + public static get(id: lumi.ID): RouteTable { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): RouteTable[] { + return undefined; // functionality provided by the runtime + } } export interface RouteTableArgs { diff --git a/lib/aws/pack/ec2/securityGroup.ts b/lib/aws/pack/ec2/securityGroup.ts index f1f73852c..e93d629ad 100644 --- a/lib/aws/pack/ec2/securityGroup.ts +++ b/lib/aws/pack/ec2/securityGroup.ts @@ -25,6 +25,14 @@ export class SecurityGroup extends lumi.NamedResource implements SecurityGroupAr this.securityGroupEgress = args.securityGroupEgress; this.securityGroupIngress = args.securityGroupIngress; } + + public static get(id: lumi.ID): SecurityGroup { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): SecurityGroup[] { + return undefined; // functionality provided by the runtime + } } export interface SecurityGroupArgs { diff --git a/lib/aws/pack/ec2/securityGroupEgress.ts b/lib/aws/pack/ec2/securityGroupEgress.ts index 94a4615d0..a29caa261 100644 --- a/lib/aws/pack/ec2/securityGroupEgress.ts +++ b/lib/aws/pack/ec2/securityGroupEgress.ts @@ -39,6 +39,14 @@ export class SecurityGroupEgress extends lumi.NamedResource implements SecurityG this.destinationPrefixListId = args.destinationPrefixListId; this.destinationSecurityGroup = args.destinationSecurityGroup; } + + public static get(id: lumi.ID): SecurityGroupEgress { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): SecurityGroupEgress[] { + return undefined; // functionality provided by the runtime + } } export interface SecurityGroupEgressArgs { diff --git a/lib/aws/pack/ec2/securityGroupIngress.ts b/lib/aws/pack/ec2/securityGroupIngress.ts index 498c60ffb..747ec323f 100644 --- a/lib/aws/pack/ec2/securityGroupIngress.ts +++ b/lib/aws/pack/ec2/securityGroupIngress.ts @@ -34,6 +34,14 @@ export class SecurityGroupIngress extends lumi.NamedResource implements Security this.sourceSecurityGroupOwnerId = args.sourceSecurityGroupOwnerId; this.toPort = args.toPort; } + + public static get(id: lumi.ID): SecurityGroupIngress { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): SecurityGroupIngress[] { + return undefined; // functionality provided by the runtime + } } export interface SecurityGroupIngressArgs { diff --git a/lib/aws/pack/ec2/subnet.ts b/lib/aws/pack/ec2/subnet.ts index 681840d6f..22003afb4 100644 --- a/lib/aws/pack/ec2/subnet.ts +++ b/lib/aws/pack/ec2/subnet.ts @@ -25,6 +25,14 @@ export class Subnet extends lumi.NamedResource implements SubnetArgs { this.availabilityZone = args.availabilityZone; this.mapPublicIpOnLaunch = args.mapPublicIpOnLaunch; } + + public static get(id: lumi.ID): Subnet { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Subnet[] { + return undefined; // functionality provided by the runtime + } } export interface SubnetArgs { diff --git a/lib/aws/pack/ec2/vpc.ts b/lib/aws/pack/ec2/vpc.ts index a972c58db..1624f4ad5 100644 --- a/lib/aws/pack/ec2/vpc.ts +++ b/lib/aws/pack/ec2/vpc.ts @@ -29,6 +29,14 @@ export class VPC extends lumi.NamedResource implements VPCArgs { this.enableDnsSupport = args.enableDnsSupport; this.enableDnsHostnames = args.enableDnsHostnames; } + + public static get(id: lumi.ID): VPC { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): VPC[] { + return undefined; // functionality provided by the runtime + } } export interface VPCArgs { diff --git a/lib/aws/pack/ec2/vpcGatewayAttachment.ts b/lib/aws/pack/ec2/vpcGatewayAttachment.ts index 1ddc2ec80..07e91ddb7 100644 --- a/lib/aws/pack/ec2/vpcGatewayAttachment.ts +++ b/lib/aws/pack/ec2/vpcGatewayAttachment.ts @@ -22,6 +22,14 @@ export class VPCGatewayAttachment extends lumi.NamedResource implements VPCGatew } this.internetGateway = args.internetGateway; } + + public static get(id: lumi.ID): VPCGatewayAttachment { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): VPCGatewayAttachment[] { + return undefined; // functionality provided by the runtime + } } export interface VPCGatewayAttachmentArgs { diff --git a/lib/aws/pack/ec2/vpcPeeringConnection.ts b/lib/aws/pack/ec2/vpcPeeringConnection.ts index 824bfeb59..fd6c21a0e 100644 --- a/lib/aws/pack/ec2/vpcPeeringConnection.ts +++ b/lib/aws/pack/ec2/vpcPeeringConnection.ts @@ -21,6 +21,14 @@ export class VPCPeeringConnection extends lumi.NamedResource implements VPCPeeri } this.vpc = args.vpc; } + + public static get(id: lumi.ID): VPCPeeringConnection { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): VPCPeeringConnection[] { + return undefined; // functionality provided by the runtime + } } export interface VPCPeeringConnectionArgs { diff --git a/lib/aws/pack/elasticbeanstalk/application.ts b/lib/aws/pack/elasticbeanstalk/application.ts index 57b9d139b..e19ce4bf6 100644 --- a/lib/aws/pack/elasticbeanstalk/application.ts +++ b/lib/aws/pack/elasticbeanstalk/application.ts @@ -15,6 +15,14 @@ export class Application extends lumi.NamedResource implements ApplicationArgs { this.description = args.description; } } + + public static get(id: lumi.ID): Application { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Application[] { + return undefined; // functionality provided by the runtime + } } export interface ApplicationArgs { diff --git a/lib/aws/pack/elasticbeanstalk/applicationVersion.ts b/lib/aws/pack/elasticbeanstalk/applicationVersion.ts index 05a94b47b..aa8364825 100644 --- a/lib/aws/pack/elasticbeanstalk/applicationVersion.ts +++ b/lib/aws/pack/elasticbeanstalk/applicationVersion.ts @@ -26,6 +26,14 @@ export class ApplicationVersion extends lumi.NamedResource implements Applicatio } this.sourceBundle = args.sourceBundle; } + + public static get(id: lumi.ID): ApplicationVersion { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): ApplicationVersion[] { + return undefined; // functionality provided by the runtime + } } export interface ApplicationVersionArgs { diff --git a/lib/aws/pack/elasticbeanstalk/environment.ts b/lib/aws/pack/elasticbeanstalk/environment.ts index 8f4370309..556ded849 100644 --- a/lib/aws/pack/elasticbeanstalk/environment.ts +++ b/lib/aws/pack/elasticbeanstalk/environment.ts @@ -40,6 +40,14 @@ export class Environment extends lumi.NamedResource implements EnvironmentArgs { this.tier = args.tier; this.version = args.version; } + + public static get(id: lumi.ID): Environment { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Environment[] { + return undefined; // functionality provided by the runtime + } } export interface EnvironmentArgs { diff --git a/lib/aws/pack/iam/group.ts b/lib/aws/pack/iam/group.ts index 17ee72a3e..badeb93db 100644 --- a/lib/aws/pack/iam/group.ts +++ b/lib/aws/pack/iam/group.ts @@ -21,6 +21,14 @@ export class Group extends lumi.NamedResource implements GroupArgs { this.policies = args.policies; } } + + public static get(id: lumi.ID): Group { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Group[] { + return undefined; // functionality provided by the runtime + } } export interface GroupArgs { diff --git a/lib/aws/pack/iam/policy.ts b/lib/aws/pack/iam/policy.ts index 83e0346f2..b01a7f17f 100644 --- a/lib/aws/pack/iam/policy.ts +++ b/lib/aws/pack/iam/policy.ts @@ -34,6 +34,14 @@ export class Policy extends lumi.NamedResource implements PolicyArgs { this.roles = args.roles; this.users = args.users; } + + public static get(id: lumi.ID): Policy { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Policy[] { + return undefined; // functionality provided by the runtime + } } export interface PolicyArgs { diff --git a/lib/aws/pack/iam/role.ts b/lib/aws/pack/iam/role.ts index 46f65014a..9de5df22a 100644 --- a/lib/aws/pack/iam/role.ts +++ b/lib/aws/pack/iam/role.ts @@ -26,6 +26,14 @@ export class Role extends lumi.NamedResource implements RoleArgs { this.managedPolicyARNs = args.managedPolicyARNs; this.policies = args.policies; } + + public static get(id: lumi.ID): Role { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Role[] { + return undefined; // functionality provided by the runtime + } } export interface RoleArgs { diff --git a/lib/aws/pack/iam/user.ts b/lib/aws/pack/iam/user.ts index 71db052bb..93e611d00 100644 --- a/lib/aws/pack/iam/user.ts +++ b/lib/aws/pack/iam/user.ts @@ -31,6 +31,14 @@ export class User extends lumi.NamedResource implements UserArgs { this.policies = args.policies; } } + + public static get(id: lumi.ID): User { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): User[] { + return undefined; // functionality provided by the runtime + } } export interface UserArgs { diff --git a/lib/aws/pack/kms/key.ts b/lib/aws/pack/kms/key.ts index 2bb3af409..91342959c 100644 --- a/lib/aws/pack/kms/key.ts +++ b/lib/aws/pack/kms/key.ts @@ -20,6 +20,14 @@ export class Key extends lumi.NamedResource implements KeyArgs { this.enabled = args.enabled; this.enableKeyRotation = args.enableKeyRotation; } + + public static get(id: lumi.ID): Key { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Key[] { + return undefined; // functionality provided by the runtime + } } export interface KeyArgs { diff --git a/lib/aws/pack/lambda/function.ts b/lib/aws/pack/lambda/function.ts index 2536c1991..2e0b7e1b5 100644 --- a/lib/aws/pack/lambda/function.ts +++ b/lib/aws/pack/lambda/function.ts @@ -69,6 +69,14 @@ export class Function extends lumi.NamedResource implements FunctionArgs { this.timeout = args.timeout; this.vpcConfig = args.vpcConfig; } + + public static get(id: lumi.ID): Function { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Function[] { + return undefined; // functionality provided by the runtime + } } export interface FunctionArgs { diff --git a/lib/aws/pack/lambda/permission.ts b/lib/aws/pack/lambda/permission.ts index 42743cad1..b51c549b7 100644 --- a/lib/aws/pack/lambda/permission.ts +++ b/lib/aws/pack/lambda/permission.ts @@ -31,6 +31,14 @@ export class Permission extends lumi.NamedResource implements PermissionArgs { this.sourceAccount = args.sourceAccount; this.sourceARN = args.sourceARN; } + + public static get(id: lumi.ID): Permission { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Permission[] { + return undefined; // functionality provided by the runtime + } } export interface PermissionArgs { diff --git a/lib/aws/pack/s3/bucket.ts b/lib/aws/pack/s3/bucket.ts index 0fc71640b..db43b8954 100644 --- a/lib/aws/pack/s3/bucket.ts +++ b/lib/aws/pack/s3/bucket.ts @@ -17,6 +17,14 @@ export class Bucket extends lumi.NamedResource implements BucketArgs { this.accessControl = args.accessControl; } } + + public static get(id: lumi.ID): Bucket { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Bucket[] { + return undefined; // functionality provided by the runtime + } } export interface BucketArgs { diff --git a/lib/aws/pack/s3/object.ts b/lib/aws/pack/s3/object.ts index 957f358a3..6b31cfbb1 100644 --- a/lib/aws/pack/s3/object.ts +++ b/lib/aws/pack/s3/object.ts @@ -26,6 +26,14 @@ export class Object extends lumi.Resource implements ObjectArgs { } this.source = args.source; } + + public static get(id: lumi.ID): Object { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Object[] { + return undefined; // functionality provided by the runtime + } } export interface ObjectArgs { diff --git a/lib/aws/pack/sns/topic.ts b/lib/aws/pack/sns/topic.ts index a4a3d972b..9a43db77a 100644 --- a/lib/aws/pack/sns/topic.ts +++ b/lib/aws/pack/sns/topic.ts @@ -26,6 +26,14 @@ export class Topic extends lumi.NamedResource implements TopicArgs { this.subscription = args.subscription; } } + + public static get(id: lumi.ID): Topic { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Topic[] { + return undefined; // functionality provided by the runtime + } } export interface TopicArgs { diff --git a/lib/aws/pack/sqs/queue.ts b/lib/aws/pack/sqs/queue.ts index 9470d5f4a..a81ac215a 100644 --- a/lib/aws/pack/sqs/queue.ts +++ b/lib/aws/pack/sqs/queue.ts @@ -29,6 +29,14 @@ export class Queue extends lumi.NamedResource implements QueueArgs { this.visibilityTimeout = args.visibilityTimeout; } } + + public static get(id: lumi.ID): Queue { + return undefined; // functionality provided by the runtime + } + + public static query(q: any): Queue[] { + return undefined; // functionality provided by the runtime + } } export interface QueueArgs { diff --git a/lib/lumi/resource.ts b/lib/lumi/resource.ts index 020d173dd..aee305383 100644 --- a/lib/lumi/resource.ts +++ b/lib/lumi/resource.ts @@ -13,10 +13,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +export type ID = string; +export type URN = string; + // Resource represents a class whose CRUD operations are implemented by a provider plugin. export abstract class Resource { - public readonly id: string; // the provider-assigned unique ID (initialized by the runtime). - public readonly urn: string; // the Lumi URN (initialized by the runtime). + public readonly id: ID; // the provider-assigned unique ID (initialized by the runtime). + public readonly urn: URN; // the Lumi URN (initialized by the runtime). } // NamedResource is a kind of resource that has a friendly resource name associated with it. diff --git a/pkg/diag/colors/colors.go b/pkg/diag/colors/colors.go index fe4bb5561..15017272f 100644 --- a/pkg/diag/colors/colors.go +++ b/pkg/diag/colors/colors.go @@ -81,6 +81,7 @@ var ( SpecAdded = Green // for adds (in the diff sense). SpecChanged = BrightYellow // for changes (in the diff sense). + SpecRead = BrightWhite // for reads (relatively unimportant). SpecReplaced = Yellow // for replacements (in the diff sense). SpecDeleted = Red // for deletes (in the diff sense). ) diff --git a/pkg/eval/eval.go b/pkg/eval/eval.go index 95b51c75f..5e6cef2d8 100644 --- a/pkg/eval/eval.go +++ b/pkg/eval/eval.go @@ -701,13 +701,6 @@ func (e *evaluator) evalCall(node diag.Diagable, e.pushScope(frame, !shareActivation) defer e.popScope(frame) - // Invoke the hooks if available. - if e.hooks != nil { - if leave := e.hooks.OnEnterFunction(sym); leave != nil { - defer leave() - } - } - // If the target is an instance method, the "this" and "super" variables must be bound to values. if thisVariable != nil { contract.Assert(this != nil) @@ -740,14 +733,26 @@ func (e *evaluator) evalCall(node diag.Diagable, } } - // Now perform the invocation; for intrinsics, just run the code; for all others, interpret the body. var uw *rt.Unwind - if intrinsic { - isym := sym.(*rt.Intrinsic) - invoker := GetIntrinsicInvoker(isym) - uw = invoker(isym, e, this, args) - } else { - uw = e.evalStatement(fnc.GetBody()) + + // Invoke the hooks if available. + if e.hooks != nil { + var leave func() + if uw, leave = e.hooks.OnEnterFunction(sym, args); leave != nil { + defer leave() + } + } + + // Assuming the hook didn't perform its own logic, we can now perform the real invocation; for intrinsics, just run + // the code; for all others, interpret the body. + if uw == nil { + if intrinsic { + isym := sym.(*rt.Intrinsic) + invoker := GetIntrinsicInvoker(isym) + uw = invoker(isym, e, this, args) + } else { + uw = e.evalStatement(fnc.GetBody()) + } } // Check that the unwind is as expected. In particular: diff --git a/pkg/eval/hooks.go b/pkg/eval/hooks.go index 16084077d..f7532fd6b 100644 --- a/pkg/eval/hooks.go +++ b/pkg/eval/hooks.go @@ -30,7 +30,7 @@ type Hooks interface { // OnEnterModule is invoked whenever we enter a module. OnEnterModule(sym *symbols.Module) func() // OnEnterFunction is invoked whenever we enter a function. - OnEnterFunction(fnc symbols.Function) func() + OnEnterFunction(fnc symbols.Function, args []*rt.Object) (*rt.Unwind, func()) // OnObjectInit is invoked after an object has been allocated and initialized. This means that its constructor, if // any, has been run to completion. The diagnostics tree is the AST node responsible for the allocation. OnObjectInit(tree diag.Diagable, o *rt.Object) diff --git a/pkg/resource/deploy/plan.go b/pkg/resource/deploy/plan.go index ea3af6385..8ce0e15fe 100644 --- a/pkg/resource/deploy/plan.go +++ b/pkg/resource/deploy/plan.go @@ -82,7 +82,12 @@ func (p *Plan) New() Source { return p.new } // Provider fetches the provider for a given resource, possibly lazily allocating the plugins for it. If a provider // could not be found, or an error occurred while creating it, a non-nil error is returned. func (p *Plan) Provider(res resource.Resource) (plugin.Provider, error) { - t := res.Type() + return p.ProviderT(res.Type()) +} + +// ProviderT fetches the provider for a given resource type, possibly lazily allocating the plugins for it. If a +// provider could not be found, or an error occurred while creating it, a non-nil error is returned. +func (p *Plan) ProviderT(t tokens.Type) (plugin.Provider, error) { pkg := t.Package() return p.ctx.Host.Provider(pkg) } diff --git a/pkg/resource/deploy/plan_apply.go b/pkg/resource/deploy/plan_apply.go index 57c0481c6..de30e9e8a 100644 --- a/pkg/resource/deploy/plan_apply.go +++ b/pkg/resource/deploy/plan_apply.go @@ -22,7 +22,6 @@ import ( "github.com/pulumi/lumi/pkg/compiler/errors" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/resource/plugin" - "github.com/pulumi/lumi/pkg/tokens" "github.com/pulumi/lumi/pkg/util/contract" ) @@ -31,7 +30,7 @@ import ( // Apply performs all steps in the plan, calling out to the progress reporting functions as desired. It returns four // things: the resulting Snapshot, no matter whether an error occurs or not; an error, if something went wrong; the step // that failed, if the error is non-nil; and finally the state of the resource modified in the failing step. -func (p *Plan) Apply(prog Progress) (PlanSummary, *Step, resource.Status, error) { +func (p *Plan) Apply(prog Progress) (PlanSummary, Step, resource.Status, error) { // Fetch a plan iterator and keep walking it until we are done. iter, err := p.Iterate() if err != nil { @@ -43,12 +42,18 @@ func (p *Plan) Apply(prog Progress) (PlanSummary, *Step, resource.Status, error) return nil, nil, resource.StatusOK, err } for step != nil { + // Do the pre-step. + rst := resource.StatusOK + err := step.Pre() + // Perform pre-application progress reporting. if prog != nil { prog.Before(step) } - rst, err := step.Apply() + if err == nil { + rst, err = step.Apply() + } // Perform post-application progress reporting. if prog != nil { @@ -135,33 +140,43 @@ func (iter *PlanIterator) Resources() []*resource.State { return iter.resourc func (iter *PlanIterator) Dones() map[*resource.State]bool { return iter.dones } func (iter *PlanIterator) Done() bool { return iter.done } +// Produce is used to indicate that a new resource state has been read from a live environment. +func (iter *PlanIterator) Produce(res *resource.Object) { + iter.src.Produce(res) +} + // Next advances the plan by a single step, and returns the next step to be performed. In doing so, it will perform // evaluation of the program as much as necessary to determine the next step. If there is no further action to be // taken, Next will return a nil step pointer. -func (iter *PlanIterator) Next() (*Step, error) { +func (iter *PlanIterator) Next() (Step, error) { for !iter.done { if !iter.srcdone { - obj, ctx, err := iter.src.Next() + res, q, err := iter.src.Next() if err != nil { return nil, err - } else if obj == nil { - // If the source is done, note it, and don't go back for more. - iter.srcdone = true - iter.delqueue = iter.calculateDeletes() - } else { - step, err := iter.nextResource(obj, ctx) + } else if res != nil { + step, err := iter.nextResourceStep(res) if err != nil { return nil, err - } else if step != nil { - return step, nil } - - // If the step returned was nil, this resource is fine, so we'll keep on going. - continue + contract.Assert(step != nil) + return step, nil + } else if q != nil { + step, err := iter.nextQueryStep(q) + if err != nil { + return nil, err + } + contract.Assert(step != nil) + return step, nil } + + // If all returns are nil, the source is done, note it, and don't go back for more. Add any deletions to be + // performed, and then keep going 'round the next iteration of the loop so we can wrap up the planning. + iter.srcdone = true + iter.delqueue = iter.calculateDeletes() } else { // The interpreter has finished, so we need to now drain any deletions that piled up. - if step := iter.nextDelete(); step != nil { + if step := iter.nextDeleteStep(); step != nil { return step, nil } @@ -173,9 +188,10 @@ func (iter *PlanIterator) Next() (*Step, error) { return nil, nil } -// nextResource produces a new step for a given resource or nil if there isn't one to perform. -func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module) (*Step, error) { +// nextResourceStep produces a new step for a given resource or nil if there isn't one to perform. +func (iter *PlanIterator) nextResourceStep(res *SourceAllocation) (Step, error) { // Take a moment in time snapshot of the live object's properties. + new := res.Obj t := new.Type() inputs := new.CopyProperties() @@ -190,7 +206,7 @@ func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module) if err != nil { return nil, err } - urn := resource.NewURN(iter.p.Target().Name, ctx, t, name) + urn := resource.NewURN(iter.p.Target().Name, res.Ctx, t, name) // First ensure the provider is okay with this resource. var invalid bool @@ -259,7 +275,7 @@ func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module) } else if len(replacements) > 0 { iter.replaces[urn] = true glog.V(7).Infof("Planner decided to replace '%v' (oldprops=%v inputs=%v)", urn, oldprops, inputs) - return NewReplaceCreateStep(iter, old, new, inputs, replacements), nil + return NewReplaceStep(iter, old, new, inputs, replacements), nil } iter.updates[urn] = true @@ -273,8 +289,18 @@ func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module) return NewCreateStep(iter, urn, new, inputs), nil } -// nextDelete produces a new step that deletes a resource if necessary. -func (iter *PlanIterator) nextDelete() *Step { +// nextQueryStep produces a new query step that looks up a resource in some manner. +func (iter *PlanIterator) nextQueryStep(q *SourceQuery) (Step, error) { + if id := q.GetID; id != "" { + return NewGetStep(iter, q.Type, id, nil), nil + } + contract.Assert(q.QueryFilter != nil) + contract.Failf("TODO[pulumi/lumi#83]: querying not yet supported") + return nil, nil +} + +// nextDeleteStep produces a new step that deletes a resource if necessary. +func (iter *PlanIterator) nextDeleteStep() Step { if len(iter.delqueue) > 0 { del := iter.delqueue[0] iter.delqueue = iter.delqueue[1:] @@ -282,10 +308,10 @@ func (iter *PlanIterator) nextDelete() *Step { iter.deletes[urn] = true if iter.replaces[urn] { glog.V(7).Infof("Planner decided to delete '%v' due to replacement", urn) - return NewReplaceDeleteStep(iter, del) + } else { + glog.V(7).Infof("Planner decided to delete '%v'", urn) } - glog.V(7).Infof("Planner decided to delete '%v'", urn) - return NewDeleteStep(iter, del) + return NewDeleteStep(iter, del, iter.replaces[urn]) } return nil } diff --git a/pkg/resource/deploy/plan_test.go b/pkg/resource/deploy/plan_test.go index 52a919ae8..b879536ed 100644 --- a/pkg/resource/deploy/plan_test.go +++ b/pkg/resource/deploy/plan_test.go @@ -116,8 +116,12 @@ func (iter *errorSourceIterator) Close() error { return nil // nothing to do. } -func (iter *errorSourceIterator) Next() (*resource.Object, tokens.Module, error) { - return nil, "", iter.src.err +func (iter *errorSourceIterator) Produce(res *resource.Object) { + // nothing to do. +} + +func (iter *errorSourceIterator) Next() (*SourceAllocation, *SourceQuery, error) { + return nil, nil, iter.src.err } // TestBasicCRUDPlan creates a plan with numerous C(R)UD operations. @@ -236,44 +240,48 @@ func TestBasicCRUDPlan(t *testing.T) { break } + err = step.Pre() + assert.Nil(t, err) + var urn resource.URN var realID bool var expectOuts resource.PropertyMap var obj *resource.Object - op := step.Op() - switch op { - case OpCreate: // A is created - old := step.Old() - new := step.New() + switch s := step.(type) { + case *CreateStep: // A is created + old := s.Old() + new := s.New() assert.Nil(t, old) assert.NotNil(t, new) - assert.Equal(t, newResAProps, step.Inputs()) + assert.Equal(t, newResAProps, s.Inputs()) obj, urn, realID = new, urnA, false - case OpUpdate: // B is updated - old := step.Old() - new := step.New() + case *UpdateStep: // B is updated + old := s.Old() + new := s.New() assert.NotNil(t, old) assert.Equal(t, urnB, old.URN()) assert.Equal(t, oldResB, old) assert.NotNil(t, new) - assert.Equal(t, newResBProps, step.Inputs()) + assert.Equal(t, newResBProps, s.Inputs()) obj, urn, realID = new, urnB, true - case OpSame: // C is the same - old := step.Old() - new := step.New() + case *SameStep: // C is the same + old := s.Old() + new := s.New() assert.NotNil(t, old) assert.Equal(t, urnC, old.URN()) assert.Equal(t, oldResC, old) assert.NotNil(t, new) - assert.Equal(t, newResCProps, step.Inputs()) + assert.Equal(t, newResCProps, s.Inputs()) obj, urn, realID, expectOuts = new, urnC, true, oldResC.Outputs() - case OpDelete: // D is deleted - old := step.Old() - new := step.New() + case *DeleteStep: // D is deleted + old := s.Old() + new := s.New() assert.NotNil(t, old) assert.Equal(t, urnD, old.URN()) assert.Equal(t, oldResD, old) assert.Nil(t, new) + default: + t.FailNow() // unexpected step kind. } if obj != nil { @@ -292,8 +300,10 @@ func TestBasicCRUDPlan(t *testing.T) { } } - step.Skip() + err = step.Skip() + assert.Nil(t, err) + op := step.Op() if obj != nil { // Ensure the ID and URN are populated correctly. if realID { @@ -317,8 +327,9 @@ func TestBasicCRUDPlan(t *testing.T) { assert.Equal(t, 1, seen[OpCreate]) assert.Equal(t, 1, seen[OpUpdate]) assert.Equal(t, 1, seen[OpDelete]) - assert.Equal(t, 0, seen[OpReplaceCreate]) - assert.Equal(t, 0, seen[OpReplaceDelete]) + assert.Equal(t, 0, seen[OpReplace]) + assert.Equal(t, 0, seen[OpGet]) + assert.Equal(t, 0, seen[OpQuery]) assert.Equal(t, 1, len(iter.Creates())) assert.True(t, iter.Creates()[urnA]) diff --git a/pkg/resource/deploy/progress.go b/pkg/resource/deploy/progress.go index 11b6fc3bf..dbcb4b48e 100644 --- a/pkg/resource/deploy/progress.go +++ b/pkg/resource/deploy/progress.go @@ -22,7 +22,7 @@ import ( // Progress can be used for progress reporting. type Progress interface { // Before is invoked prior to a step executing. - Before(step *Step) + Before(step Step) // After is invoked after a step executes, and is given access to the error, if any, that occurred. - After(step *Step, state resource.Status, err error) + After(step Step, state resource.Status, err error) } diff --git a/pkg/resource/deploy/source.go b/pkg/resource/deploy/source.go index 22d667b34..8427a85d7 100644 --- a/pkg/resource/deploy/source.go +++ b/pkg/resource/deploy/source.go @@ -18,6 +18,7 @@ package deploy import ( "io" + "github.com/pulumi/lumi/pkg/compiler/symbols" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/tokens" ) @@ -34,8 +35,23 @@ type Source interface { // A SourceIterator enumerates the list of resources that a source has to offer. type SourceIterator interface { io.Closer - // Next returns the next resource object plus a token context (usually where it was allocated); the token is used in - // the production of the ensuing resource's URN. If something went wrong, error is non-nil. If both error and the - // resource are nil, then the iterator has completed its job and no subsequent calls to next should be made. - Next() (*resource.Object, tokens.Module, error) + // Produce registers a resource that was produced during the iteration, to publish next time. + Produce(res *resource.Object) + // Next returns the next step from the source. If the source allocation is non-nil, it represents the creation of + // a resource object; if query is non-nil, it represents querying the resources; if both error and the other + // objects are nil, then the iterator has completed its job and no subsequent calls to next should be made. + Next() (*SourceAllocation, *SourceQuery, error) +} + +// SourceAllocation is used when a resource object is allocated. +type SourceAllocation struct { + Obj *resource.Object // the resource object. + Ctx tokens.Module // the context in which the resource was allocated, used in the production of URNs. +} + +// SourceQuery is used when a query function is to be performed. +type SourceQuery struct { + Type symbols.Type // the type of resource being queried. + GetID resource.ID // the resource ID to get (for gets only). + QueryFilter resource.PropertyMap // the query's filter (for queries only). } diff --git a/pkg/resource/deploy/source_eval.go b/pkg/resource/deploy/source_eval.go index a9e1abb0d..263494939 100644 --- a/pkg/resource/deploy/source_eval.go +++ b/pkg/resource/deploy/source_eval.go @@ -23,6 +23,7 @@ import ( "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/compiler/types/predef" "github.com/pulumi/lumi/pkg/diag" "github.com/pulumi/lumi/pkg/eval" "github.com/pulumi/lumi/pkg/eval/rt" @@ -100,6 +101,7 @@ func (src *evalSource) Iterate() (SourceIterator, error) { type evalSourceIterator struct { src *evalSource // the owning eval source object. e eval.Interpreter // the interpreter used to compute the new state. + res *resource.Object // a resource to publish during the next rendezvous. rz *rendezvous.Rendezvous // the rendezvous where planning and evaluator coroutines meet. } @@ -109,21 +111,57 @@ func (iter *evalSourceIterator) Close() error { return nil } -func (iter *evalSourceIterator) Next() (*resource.Object, tokens.Module, error) { +func (iter *evalSourceIterator) Produce(res *resource.Object) { + iter.res = res +} + +func (iter *evalSourceIterator) Next() (*SourceAllocation, *SourceQuery, error) { // Kick the interpreter to compute some more and then inspect what it has to say. - obj, done, err := iter.rz.Meet(planParty, nil) + var data interface{} + if res := iter.res; res != nil { + data = rt.NewReturnUnwind(res.Obj()) + iter.res = nil // reset the state so we don't return things more than once. + } + obj, done, err := iter.rz.Meet(planParty, data) if err != nil { - return nil, "", err + return nil, nil, err } else if done { glog.V(5).Infof("EvalSourceIterator is done") - return nil, "", nil + return nil, nil, nil + } + contract.Assert(obj != nil) + + // See what the interpreter came up with. It's either an allocation or a query operation. + if alloc, isalloc := obj.(*AllocRendezvous); isalloc { + glog.V(5).Infof("EvalSourceIterator produced a new object: obj=%v, ctx=%v", alloc.Obj, alloc.Mod.Tok) + return &SourceAllocation{ + Obj: resource.NewObject(alloc.Obj), + Ctx: alloc.Mod.Tok, + }, nil, nil + } else if query, isquery := obj.(*QueryRendezvous); isquery { + glog.V(5).Infof("EvalSourceIterator produced a new query: fnc=%v, #args=%v", query.Meth, len(query.Args)) + meth := query.Meth + args := query.Args + t := meth.Parent + switch meth.Name() { + case specialResourceGetFunction: + if len(args) == 0 { + return nil, nil, + goerr.Errorf("Missing required argument 'id' for method %v", meth) + } else if !args[0].IsString() { + return nil, nil, + goerr.Errorf("Expected method %v argument 'id' to be a string; got", meth, args[0]) + } + return nil, &SourceQuery{Type: t, GetID: resource.ID(args[0].StringValue())}, nil + case specialResourceQueryFunction: + contract.Failf("TODO[pulumi/lumi#83]: query not yet implemented") + default: + contract.Failf("Unrecognized query rendezvous function name: %v", meth.Name()) + } } - // Otherwise, transform the object returned into a resource object that the planner can deal with. - contract.Assert(obj != nil) - info := obj.(*AllocInfo) - glog.V(5).Infof("EvalSourceIterator produced a new object: obj=%v, ctx=%v", info.Obj, info.Mod.Tok) - return resource.NewObject(info.Obj), info.Mod.Tok, nil + contract.Failf("Unexpected rendezvous object: %v (expected alloc or query)", obj) + return nil, nil, nil } // InitEvalConfig applies the configuration map to an existing interpreter context. The map is simply a map of tokens -- @@ -198,8 +236,8 @@ func forkEval(src *evalSource, rz *rendezvous.Rendezvous, e eval.Interpreter) er return nil } -// AllocInfo is the context in which an object got allocated. -type AllocInfo struct { +// AllocRendezvous is used when an object is allocated, and tracks the context in which it was allocated. +type AllocRendezvous struct { Obj *rt.Object // the object itself. Loc diag.Diagable // the location information for the allocation. Pkg *symbols.Package // the package being evaluated when the allocation happened. @@ -207,6 +245,12 @@ type AllocInfo struct { Fnc symbols.Function // the function being evaluated when the allocation happened. } +// QueryRendezvous is used when the interpreter hits a query routine that needs to be evaluated by the planner. +type QueryRendezvous struct { + Meth *symbols.ClassMethod // the resource method that triggered the need to rendezvous. + Args []*rt.Object // the arguments supplied, if any. +} + // evalHooks are the interpreter hooks that synchronize between planner and evaluator in the appropriate ways. type evalHooks struct { rz *rendezvous.Rendezvous // the rendezvous object. @@ -240,7 +284,7 @@ func (h *evalHooks) OnObjectInit(tree diag.Diagable, obj *rt.Object) { glog.V(9).Infof("EvalSource OnObjectInit %v (IsResource=%v)", obj, resource.IsResourceObject(obj)) if resource.IsResourceObject(obj) { // Communicate the full allocation context: AST node, package, module, and function. - alloc := &AllocInfo{ + alloc := &AllocRendezvous{ Obj: obj, Loc: tree, Pkg: h.currpkg, @@ -276,12 +320,39 @@ func (h *evalHooks) OnEnterModule(mod *symbols.Module) func() { } } -// OnEnterFunction is invoked whenever we enter a new function. -func (h *evalHooks) OnEnterFunction(fnc symbols.Function) func() { +const ( + specialResourceGetFunction = "get" // gets a single resource by ID. + specialResourceQueryFunction = "query" // queries 0-to-many resources using arbitrary filters. +) + +// OnEnterFunction is invoked whenever we enter a new function. If it returns a non-nil unwind object, it will be used +// in place of the actual function call, effectively monkey patching it on the fly. +func (h *evalHooks) OnEnterFunction(fnc symbols.Function, args []*rt.Object) (*rt.Unwind, func()) { glog.V(9).Infof("EvalSource OnEnterFunction %v", fnc) prevfnc := h.currfnc h.currfnc = fnc - return func() { + + // If this is one of the "special" resource functions, we need to essentially monkey patch it on the fly. + var uw *rt.Unwind + if meth, ismeth := fnc.(*symbols.ClassMethod); ismeth { + if predef.IsResourceType(meth.Parent) { + switch meth.Name() { + case specialResourceGetFunction, specialResourceQueryFunction: + // For any of these functions, we must defer to the planning side to do its thing. After awaiting our + // turn, we will be given an opportunity to resume with the object and/or unwind in hand. + ret, done, err := h.rz.Meet(evalParty, &QueryRendezvous{ + Meth: meth, + Args: args, + }) + contract.Assertf(ret != nil, "Expecting unwind instructions from the planning goroutine") + uw = ret.(*rt.Unwind) + contract.Assert(!done) + contract.Assert(err == nil) + } + } + } + + return uw, func() { glog.V(9).Infof("EvalSource OnLeaveFunction %v", fnc) h.currfnc = prevfnc } diff --git a/pkg/resource/deploy/source_fixed.go b/pkg/resource/deploy/source_fixed.go index d53d1c14c..a178905b3 100644 --- a/pkg/resource/deploy/source_fixed.go +++ b/pkg/resource/deploy/source_fixed.go @@ -56,10 +56,17 @@ func (iter *fixedSourceIterator) Close() error { return nil // nothing to do. } -func (iter *fixedSourceIterator) Next() (*resource.Object, tokens.Module, error) { +func (iter *fixedSourceIterator) Produce(res *resource.Object) { + // ignore +} + +func (iter *fixedSourceIterator) Next() (*SourceAllocation, *SourceQuery, error) { iter.current++ if iter.current >= len(iter.src.resources) { - return nil, "", nil + return nil, nil, nil } - return iter.src.resources[iter.current], iter.src.ctx, nil + return &SourceAllocation{ + Obj: iter.src.resources[iter.current], + Ctx: iter.src.ctx, + }, nil, nil } diff --git a/pkg/resource/deploy/source_null.go b/pkg/resource/deploy/source_null.go index 62d789f9a..8e0baf1d7 100644 --- a/pkg/resource/deploy/source_null.go +++ b/pkg/resource/deploy/source_null.go @@ -17,7 +17,6 @@ package deploy import ( "github.com/pulumi/lumi/pkg/resource" - "github.com/pulumi/lumi/pkg/tokens" ) // NullSource is a singleton source that never returns any resources. This may be used in scenarios where the "new" @@ -48,6 +47,10 @@ func (iter *nullSourceIterator) Close() error { return nil // nothing to do. } -func (iter *nullSourceIterator) Next() (*resource.Object, tokens.Module, error) { - return nil, "", nil // means "done" +func (iter *nullSourceIterator) Produce(res *resource.Object) { + // ignore +} + +func (iter *nullSourceIterator) Next() (*SourceAllocation, *SourceQuery, error) { + return nil, nil, nil // means "done" } diff --git a/pkg/resource/deploy/step.go b/pkg/resource/deploy/step.go index 63d977144..bae2680cd 100644 --- a/pkg/resource/deploy/step.go +++ b/pkg/resource/deploy/step.go @@ -16,191 +16,437 @@ package deploy import ( + "github.com/pulumi/lumi/pkg/compiler/symbols" "github.com/pulumi/lumi/pkg/diag/colors" "github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/resource/plugin" + "github.com/pulumi/lumi/pkg/tokens" "github.com/pulumi/lumi/pkg/util/contract" ) // Step is a specification for a deployment operation. -type Step struct { - iter *PlanIterator // the current plan iteration. - op StepOp // the operation that will be performed. - urn resource.URN // the resource URN (for before and after). - old *resource.State // the state of the resource before this step. - new *resource.Object // the state of the resource after this step. - inputs resource.PropertyMap // the input properties to use during the operation. - outputs resource.PropertyMap // the output properties calculated after the operation. - reasons []resource.PropertyKey // the reasons for replacement, if applicable. +type Step interface { + Op() StepOp // the operation performed by this step. + Plan() *Plan // the owning plan. + Iterator() *PlanIterator // the current plan iterator. + Type() tokens.Type // the type affected by this step. + Pre() error // run any pre-execution steps. + Apply() (resource.Status, error) // applies the action that this step represents. + Skip() error // skips past this step (required when iterating a plan). } -func NewSameStep(iter *PlanIterator, old *resource.State, new *resource.Object, inputs resource.PropertyMap) *Step { +// ReadStep is a step that doesn't actually modify the target environment. It only reads/queries from it. +type ReadStep interface { + Step + Resources() []*resource.Object // all resource objects returned by this step. +} + +// MutatingStep is a step that, when performed, will actually modify/mutate the target environment and its resources. +type MutatingStep interface { + Step + URN() resource.URN // the resource URN (for before and after). + New() *resource.Object // the state of the resource before performing this step. + Old() *resource.State // the state of the resource after performing this step. + Inputs() resource.PropertyMap // the input properties to use during the operation. + Outputs() resource.PropertyMap // the output properties calculated during the operation. +} + +// SameStep is a mutating step that does nothing. +type SameStep struct { + iter *PlanIterator // the current plan iteration. + old *resource.State // the state of the resource before this step. + new *resource.Object // the state of the resource after this step. + inputs resource.PropertyMap // the computed inputs supplied at creation time. +} + +var _ MutatingStep = (*SameStep)(nil) + +func NewSameStep(iter *PlanIterator, old *resource.State, new *resource.Object, inputs resource.PropertyMap) Step { contract.Assert(resource.HasURN(old)) contract.Assert(!resource.HasURN(new)) - return &Step{iter: iter, op: OpSame, urn: old.URN(), old: old, new: new, inputs: inputs} -} - -func NewCreateStep(iter *PlanIterator, urn resource.URN, new *resource.Object, inputs resource.PropertyMap) *Step { - contract.Assert(!resource.HasURN(new)) - return &Step{iter: iter, op: OpCreate, urn: urn, new: new, inputs: inputs} -} - -func NewDeleteStep(iter *PlanIterator, old *resource.State) *Step { - contract.Assert(resource.HasURN(old)) - return &Step{iter: iter, op: OpDelete, urn: old.URN(), old: old} -} - -func NewUpdateStep(iter *PlanIterator, old *resource.State, - new *resource.Object, inputs resource.PropertyMap) *Step { - contract.Assert(resource.HasURN(old)) - contract.Assert(!resource.HasURN(new)) - return &Step{iter: iter, op: OpUpdate, urn: old.URN(), old: old, new: new, inputs: inputs} -} - -func NewReplaceCreateStep(iter *PlanIterator, old *resource.State, - new *resource.Object, inputs resource.PropertyMap, reasons []resource.PropertyKey) *Step { - contract.Assert(resource.HasURN(old)) - contract.Assert(!resource.HasURN(new)) - return &Step{iter: iter, op: OpReplaceCreate, urn: old.URN(), old: old, new: new, inputs: inputs, reasons: reasons} -} - -func NewReplaceDeleteStep(iter *PlanIterator, old *resource.State) *Step { - contract.Assert(resource.HasURN(old)) - return &Step{iter: iter, op: OpReplaceDelete, urn: old.URN(), old: old} -} - -func (s *Step) Plan() *Plan { return s.iter.p } -func (s *Step) Iterator() *PlanIterator { return s.iter } -func (s *Step) Op() StepOp { return s.op } -func (s *Step) URN() resource.URN { return s.urn } -func (s *Step) Old() *resource.State { return s.old } -func (s *Step) New() *resource.Object { return s.new } -func (s *Step) Inputs() resource.PropertyMap { return s.inputs } -func (s *Step) Outputs() resource.PropertyMap { return s.outputs } -func (s *Step) Reasons() []resource.PropertyKey { return s.reasons } - -func (s *Step) Provider() (plugin.Provider, error) { - contract.Assert(s.old == nil || s.new == nil || s.old.Type() == s.new.Type()) - if s.old != nil { - return s.Plan().Provider(s.old) + return &SameStep{ + iter: iter, + old: old, + new: new, + inputs: inputs, } +} + +func (s *SameStep) Op() StepOp { return OpSame } +func (s *SameStep) Plan() *Plan { return s.iter.p } +func (s *SameStep) Iterator() *PlanIterator { return s.iter } +func (s *SameStep) Type() tokens.Type { return s.old.Type() } +func (s *SameStep) URN() resource.URN { return s.old.URN() } +func (s *SameStep) Old() *resource.State { return s.old } +func (s *SameStep) New() *resource.Object { return s.new } +func (s *SameStep) Inputs() resource.PropertyMap { return s.inputs } +func (s *SameStep) Outputs() resource.PropertyMap { return s.old.Outputs() } + +func (s *SameStep) Pre() error { + contract.Assert(s.old != nil) contract.Assert(s.new != nil) - return s.Plan().Provider(s.new) + return nil } -func (s *Step) Apply() (resource.Status, error) { - // Fetch the provider. - prov, err := s.Provider() - if err != nil { - return resource.StatusOK, err - } - - // Now simply perform the operation of the right kind. - switch s.op { - case OpSame: - // Just propagate the ID and output state to the live object and append to the snapshot. - contract.Assert(s.old != nil) - contract.Assert(s.new != nil) - s.new.Update(s.urn, s.old.ID(), s.old.Outputs()) - s.iter.MarkStateSnapshot(s.old) - s.iter.AppendStateSnapshot(s.old) - - case OpCreate, OpReplaceCreate: - // Invoke the Create RPC function for this provider: - contract.Assert(s.old == nil || s.op == OpReplaceCreate) - contract.Assert(s.new != nil) - t := s.new.Type() - id, rst, err := prov.Create(t, s.inputs) - if err != nil { - return rst, err - } - contract.Assert(id != "") - - // Read the resource state back (to fetch outputs) and store everything on the live object. - outs, err := prov.Get(t, id) - if err != nil { - return resource.StatusUnknown, err - } - s.outputs = outs - state := s.new.Update(s.urn, id, outs) - if s.old != nil { - s.iter.MarkStateSnapshot(s.old) - } - s.iter.AppendStateSnapshot(state) - - case OpDelete, OpReplaceDelete: - // Invoke the Delete RPC function for this provider: - contract.Assert(s.old != nil) - contract.Assert(s.new == nil) - if rst, err := prov.Delete(s.old.Type(), s.old.ID()); err != nil { - return rst, err - } - s.iter.MarkStateSnapshot(s.old) - - case OpUpdate: - // Invoke the Update RPC function for this provider: - contract.Assert(s.old != nil) - contract.Assert(s.new != nil) - t := s.old.Type() - contract.Assert(t == s.new.Type()) - id := s.old.ID() - contract.Assert(id != "") - if rst, err := prov.Update(t, id, s.old.Inputs(), s.inputs); err != nil { - return rst, err - } - - // Now read the resource state back in case the update triggered cascading updates to other properties. - outs, err := prov.Get(t, id) - if err != nil { - return resource.StatusUnknown, err - } - s.outputs = outs - state := s.new.Update(s.urn, id, outs) - s.iter.MarkStateSnapshot(s.old) - s.iter.AppendStateSnapshot(state) - - default: - contract.Failf("Unexpected step operation: %v", s.op) - } - +func (s *SameStep) Apply() (resource.Status, error) { + // Just propagate the ID and output state to the live object and append to the snapshot. + s.new.Update(s.old.URN(), s.old.ID(), s.old.Outputs()) + s.iter.MarkStateSnapshot(s.old) + s.iter.AppendStateSnapshot(s.old) return resource.StatusOK, nil } -// Skip skips a step. This is required even when just viewing a plan to ensure in-memory object states are correct. -// This factors in the correct differences in behavior depending on the kind of action being taken. -func (s *Step) Skip() { - switch s.op { - case OpSame: - // In the case of a same, both ID and outputs are identical. - s.new.Update(s.urn, s.old.ID(), s.old.Outputs()) - case OpCreate: - // In the case of a create, we cannot possibly know the ID or output properties. But we do know the URN. - s.new.SetURN(s.urn) - case OpUpdate: - // In the case of an update, the ID is the same, however, the outputs remain unknown. - s.new.SetURN(s.urn) - s.new.SetID(s.old.ID()) - case OpReplaceCreate: - // In the case of a replacement, we neither propagate the ID nor output properties. This may be surprising, - // however, it must be done this way since the entire resource will be deleted and recreated. As a result, we - // actually want the ID to be seen as having been updated (triggering cascading updates as appropriate). - case OpDelete, OpReplaceDelete: - // In the case of a deletion, there is no state to propagate: the new object doesn't even exist. - default: - contract.Failf("Unexpected step operation: %v", s.op) +func (s *SameStep) Skip() error { + // In the case of a same, both ID and outputs are identical. + s.new.Update(s.old.URN(), s.old.ID(), s.old.Outputs()) + return nil +} + +// CreateStep is a mutating step that creates an entirely new resource. +type CreateStep struct { + iter *PlanIterator // the current plan iteration. + urn resource.URN // the resource URN being created. + new *resource.Object // the state of the resource after this step. + inputs resource.PropertyMap // the input properties for the creation. + outputs resource.PropertyMap // the output properties after creation. +} + +var _ MutatingStep = (*CreateStep)(nil) + +func NewCreateStep(iter *PlanIterator, urn resource.URN, new *resource.Object, inputs resource.PropertyMap) Step { + contract.Assert(!resource.HasURN(new)) + return &CreateStep{ + iter: iter, + urn: urn, + new: new, + inputs: inputs, } } -// StepOp represents the kind of operation performed by this step. +func (s *CreateStep) Op() StepOp { return OpCreate } +func (s *CreateStep) Plan() *Plan { return s.iter.p } +func (s *CreateStep) Iterator() *PlanIterator { return s.iter } +func (s *CreateStep) Type() tokens.Type { return s.new.Type() } +func (s *CreateStep) URN() resource.URN { return s.urn } +func (s *CreateStep) Old() *resource.State { return nil } +func (s *CreateStep) New() *resource.Object { return s.new } +func (s *CreateStep) Inputs() resource.PropertyMap { return s.inputs } +func (s *CreateStep) Outputs() resource.PropertyMap { return s.outputs } + +func (s *CreateStep) Pre() error { + contract.Assert(s.new != nil) + return nil +} + +func (s *CreateStep) Apply() (resource.Status, error) { + t := s.new.Type() + + // Invoke the Create RPC function for this provider: + prov, err := getProvider(s) + if err != nil { + return resource.StatusOK, err + } + id, rst, err := prov.Create(t, s.inputs) + if err != nil { + return rst, err + } + contract.Assert(id != "") + + // Read the resource state back (to fetch outputs) and store everything on the live object. + outs, err := prov.Get(t, id) + if err != nil { + return resource.StatusUnknown, err + } + s.outputs = outs + state := s.new.Update(s.urn, id, outs) + s.iter.AppendStateSnapshot(state) + return resource.StatusOK, nil +} + +func (s *CreateStep) Skip() error { + // In the case of a create, we cannot possibly know the ID or output properties. But we do know the URN. + s.new.SetURN(s.urn) + return nil +} + +// DeleteStep is a mutating step that deletes an existing resource. +type DeleteStep struct { + iter *PlanIterator // the current plan iteration. + old *resource.State // the state of the existing resource. + replaced bool // true if part of a replacement. +} + +var _ MutatingStep = (*DeleteStep)(nil) + +func NewDeleteStep(iter *PlanIterator, old *resource.State, replaced bool) Step { + contract.Assert(resource.HasURN(old)) + return &DeleteStep{ + iter: iter, + old: old, + replaced: replaced, + } +} + +func (s *DeleteStep) Op() StepOp { return OpDelete } +func (s *DeleteStep) Plan() *Plan { return s.iter.p } +func (s *DeleteStep) Iterator() *PlanIterator { return s.iter } +func (s *DeleteStep) Type() tokens.Type { return s.old.Type() } +func (s *DeleteStep) URN() resource.URN { return s.old.URN() } +func (s *DeleteStep) Old() *resource.State { return s.old } +func (s *DeleteStep) New() *resource.Object { return nil } +func (s *DeleteStep) Inputs() resource.PropertyMap { return s.old.Inputs() } +func (s *DeleteStep) Outputs() resource.PropertyMap { return s.old.Outputs() } +func (s *DeleteStep) Replaced() bool { return s.replaced } + +func (s *DeleteStep) Pre() error { + contract.Assert(s.old != nil) + return nil +} + +func (s *DeleteStep) Apply() (resource.Status, error) { + // Invoke the Delete RPC function for this provider: + prov, err := getProvider(s) + if err != nil { + return resource.StatusOK, err + } + if rst, err := prov.Delete(s.old.Type(), s.old.ID()); err != nil { + return rst, err + } + s.iter.MarkStateSnapshot(s.old) + return resource.StatusOK, nil +} + +func (s *DeleteStep) Skip() error { + // In the case of a deletion, there is no state to propagate: the new object doesn't even exist. + return nil +} + +// UpdateStep is a mutating step that updates an existing resource's state. +type UpdateStep struct { + iter *PlanIterator // the current plan iteration. + old *resource.State // the state of the existing resource. + new *resource.Object // the live resource object. + inputs resource.PropertyMap // the input properties for the update. + outputs resource.PropertyMap // the output properties populated after updating. +} + +var _ MutatingStep = (*UpdateStep)(nil) + +func NewUpdateStep(iter *PlanIterator, old *resource.State, + new *resource.Object, inputs resource.PropertyMap) Step { + contract.Assert(resource.HasURN(old)) + contract.Assert(!resource.HasURN(new)) + return &UpdateStep{ + iter: iter, + old: old, + new: new, + inputs: inputs, + } +} + +func (s *UpdateStep) Op() StepOp { return OpUpdate } +func (s *UpdateStep) Plan() *Plan { return s.iter.p } +func (s *UpdateStep) Iterator() *PlanIterator { return s.iter } +func (s *UpdateStep) Type() tokens.Type { return s.old.Type() } +func (s *UpdateStep) URN() resource.URN { return s.old.URN() } +func (s *UpdateStep) Old() *resource.State { return s.old } +func (s *UpdateStep) New() *resource.Object { return s.new } +func (s *UpdateStep) Inputs() resource.PropertyMap { return s.inputs } +func (s *UpdateStep) Outputs() resource.PropertyMap { return s.outputs } + +func (s *UpdateStep) Pre() error { + contract.Assert(s.old != nil) + contract.Assert(s.new != nil) + contract.Assert(s.old.Type() == s.new.Type()) + contract.Assert(s.old.ID() != "") + return nil +} + +func (s *UpdateStep) Apply() (resource.Status, error) { + t := s.old.Type() + id := s.old.ID() + + // Invoke the Update RPC function for this provider: + prov, err := getProvider(s) + if err != nil { + return resource.StatusOK, err + } + if rst, err := prov.Update(t, id, s.old.Inputs(), s.inputs); err != nil { + return rst, err + } + + // Now read the resource state back in case the update triggered cascading updates to other properties. + outs, err := prov.Get(t, id) + if err != nil { + return resource.StatusUnknown, err + } + s.outputs = outs + state := s.new.Update(s.old.URN(), id, outs) + s.iter.MarkStateSnapshot(s.old) + s.iter.AppendStateSnapshot(state) + return resource.StatusOK, nil +} + +func (s *UpdateStep) Skip() error { + // In the case of an update, the ID is the same, however, the outputs remain unknown. + s.new.SetURN(s.old.URN()) + s.new.SetID(s.old.ID()) + return nil +} + +// ReplaceStep is a mutating step that updates an existing resource's state. +type ReplaceStep struct { + iter *PlanIterator // the current plan iteration. + old *resource.State // the state of the existing resource. + new *resource.Object // the live resource object. + inputs resource.PropertyMap // the input properties for the replacement. + outputs resource.PropertyMap // the output properties populated after replacing. + reasons []resource.PropertyKey // the reasons for the replacement. +} + +func NewReplaceStep(iter *PlanIterator, old *resource.State, + new *resource.Object, inputs resource.PropertyMap, reasons []resource.PropertyKey) Step { + contract.Assert(resource.HasURN(old)) + contract.Assert(!resource.HasURN(new)) + return &ReplaceStep{ + iter: iter, + old: old, + new: new, + inputs: inputs, + reasons: reasons, + } +} + +func (s *ReplaceStep) Op() StepOp { return OpReplace } +func (s *ReplaceStep) Plan() *Plan { return s.iter.p } +func (s *ReplaceStep) Iterator() *PlanIterator { return s.iter } +func (s *ReplaceStep) Type() tokens.Type { return s.old.Type() } +func (s *ReplaceStep) URN() resource.URN { return s.old.URN() } +func (s *ReplaceStep) Old() *resource.State { return s.old } +func (s *ReplaceStep) New() *resource.Object { return s.new } +func (s *ReplaceStep) Inputs() resource.PropertyMap { return s.inputs } +func (s *ReplaceStep) Outputs() resource.PropertyMap { return s.outputs } +func (s *ReplaceStep) Reasons() []resource.PropertyKey { return s.reasons } + +func (s *ReplaceStep) Pre() error { + contract.Assert(s.old != nil) + contract.Assert(s.new != nil) + return nil +} + +func (s *ReplaceStep) Apply() (resource.Status, error) { + t := s.new.Type() + + // Invoke the Create RPC function for this provider: + prov, err := getProvider(s) + if err != nil { + return resource.StatusOK, err + } + id, rst, err := prov.Create(t, s.inputs) + if err != nil { + return rst, err + } + contract.Assert(id != "") + + // Read the resource state back (to fetch outputs) and store everything on the live object. + outs, err := prov.Get(t, id) + if err != nil { + return resource.StatusUnknown, err + } + s.outputs = outs + state := s.new.Update(s.old.URN(), id, outs) + s.iter.MarkStateSnapshot(s.old) + s.iter.AppendStateSnapshot(state) + return resource.StatusOK, nil +} + +func (s *ReplaceStep) Skip() error { + // In the case of a replacement, we neither propagate the ID nor output properties. This may be surprising, + // however, it must be done this way since the entire resource will be deleted and recreated. As a result, we + // actually want the ID to be seen as having been updated (triggering cascading updates as appropriate). + s.new.SetURN(s.old.URN()) + return nil +} + +// GetStep is a read-only step that queries for a single resource. +type GetStep struct { + iter *PlanIterator // the current plan iteration. + t symbols.Type // the type of resource to query. + id resource.ID // the ID of the resource being sought. + obj *resource.Object // the resource object read back from this operation. + outputs resource.PropertyMap // the output properties populated after updating. +} + +var _ ReadStep = (*GetStep)(nil) + +func NewGetStep(iter *PlanIterator, t symbols.Type, id resource.ID, obj *resource.Object) Step { + return &GetStep{ + iter: iter, + t: t, + id: id, + obj: obj, + } +} + +func (s *GetStep) Op() StepOp { return OpGet } +func (s *GetStep) Plan() *Plan { return s.iter.p } +func (s *GetStep) Iterator() *PlanIterator { return s.iter } +func (s *GetStep) Type() tokens.Type { return s.t.TypeToken() } +func (s *GetStep) Resources() []*resource.Object { return []*resource.Object{s.obj} } + +func (s *GetStep) Pre() error { + // Simply call through to the provider's Get API. + id := s.id + prov, err := getProvider(s) + if err != nil { + return err + } + outs, err := prov.Get(s.Type(), id) + if err != nil { + return err + } + s.outputs = outs + + // If no pre-existing object was supplied, create a new one. + if s.obj == nil { + s.obj = resource.NewEmptyObject(s.t) + } + + // Populate the object's ID, properties, and URN with the state we read back. + // TODO: it's not clear yet how to correctly populate the URN, given that the allocation context is unknown. + s.obj.SetID(id) + s.obj.SetProperties(outs) + + // Finally, the iterate must communicate the result back to the interpreter, by way of an unwind. + s.iter.Produce(s.obj) + + return nil +} + +func (s *GetStep) Apply() (resource.Status, error) { + return resource.StatusOK, nil +} + +func (s *GetStep) Skip() error { + return nil +} + +// getProvider fetches the provider for the given step. +func getProvider(s Step) (plugin.Provider, error) { + return s.Plan().ProviderT(s.Type()) +} + +// StepOp represents the kind of operation performed by a step. It evaluates to its string label. type StepOp string const ( - OpSame StepOp = "same" // nothing to do. - OpCreate StepOp = "create" // creating a new resource. - OpUpdate StepOp = "update" // updating an existing resource. - OpDelete StepOp = "delete" // deleting an existing resource. - OpReplaceCreate StepOp = "replace" // replacing a resource with a new one. - OpReplaceDelete StepOp = "replace-delete" // the fine-grained replacement step to delete the old resource. + OpSame StepOp = "same" // nothing to do. + OpCreate StepOp = "create" // creating a new resource. + OpUpdate StepOp = "update" // updating an existing resource. + OpDelete StepOp = "delete" // deleting an existing resource. + OpReplace StepOp = "replace" // replacing a resource with a new one. + OpGet StepOp = "get" // fetching a resource by ID or URN. + OpQuery StepOp = "query" // querying a resource list by type and filter. ) // StepOps contains the full set of step operation types. @@ -209,8 +455,9 @@ var StepOps = []StepOp{ OpCreate, OpUpdate, OpDelete, - OpReplaceCreate, - OpReplaceDelete, + OpReplace, + OpGet, + OpQuery, } // Color returns a suggested color for lines of this op type. @@ -224,10 +471,10 @@ func (op StepOp) Color() string { return colors.SpecDeleted case OpUpdate: return colors.SpecChanged - case OpReplaceCreate: + case OpReplace: return colors.SpecReplaced - case OpReplaceDelete: - return colors.SpecDeleted + case OpGet, OpQuery: + return colors.SpecRead default: contract.Failf("Unrecognized resource step op: %v", op) return "" @@ -237,18 +484,16 @@ func (op StepOp) Color() string { // Prefix returns a suggested prefix for lines of this op type. func (op StepOp) Prefix() string { switch op { - case OpSame: + case OpSame, OpGet, OpQuery: return op.Color() + " " case OpCreate: return op.Color() + "+ " case OpDelete: return op.Color() + "- " case OpUpdate: - return op.Color() + " " - case OpReplaceCreate: - return op.Color() + "~+" - case OpReplaceDelete: - return op.Color() + "~-" + return op.Color() + "~ " + case OpReplace: + return op.Color() + "+-" default: contract.Failf("Unrecognized resource step op: %v", op) return "" @@ -257,8 +502,8 @@ func (op StepOp) Prefix() string { // Suffix returns a suggested suffix for lines of this op type. func (op StepOp) Suffix() string { - if op == OpUpdate || op == OpReplaceCreate { - return colors.Reset // updates and replacements colorize individual lines + if op == OpUpdate || op == OpReplace || op == OpGet { + return colors.Reset // updates and replacements colorize individual lines; get has none } return "" } diff --git a/pkg/resource/resource_object.go b/pkg/resource/resource_object.go index 6a9ebacc0..c44779677 100644 --- a/pkg/resource/resource_object.go +++ b/pkg/resource/resource_object.go @@ -47,6 +47,14 @@ func NewObject(obj *rt.Object) *Object { return &Object{obj: obj} } +// NewEmptyObject allocates an empty resource object of a given type. +func NewEmptyObject(t symbols.Type) *Object { + contract.Assert(predef.IsResourceType(t)) + return &Object{ + obj: rt.NewObject(t, nil, nil, nil), + } +} + func (r *Object) Obj() *rt.Object { return r.obj } func (r *Object) Type() tokens.Type { return r.obj.Type().TypeToken() } diff --git a/pkg/tools/lumidl/gen_pack.go b/pkg/tools/lumidl/gen_pack.go index ce2322911..481444297 100644 --- a/pkg/tools/lumidl/gen_pack.go +++ b/pkg/tools/lumidl/gen_pack.go @@ -316,6 +316,7 @@ func (g *PackGenerator) emitResourceClass(w *bufio.Writer, res *Resource) { } else { writefmtln(w, " super();") } + // Next, validate that required parameters exist, and store all arguments on the object. argLinePrefix := " " needsArgsCheck := hasArgs && !hasRequiredArgs @@ -338,6 +339,17 @@ func (g *PackGenerator) emitResourceClass(w *bufio.Writer, res *Resource) { } writefmtln(w, " }") + writefmtln(w, "") + + // Finally, add the standard "factory" functions: get and query. + writefmtln(w, " public static get(id: lumi.ID): %v {", name) + writefmtln(w, " return undefined; // functionality provided by the runtime") + writefmtln(w, " }") + writefmtln(w, "") + writefmtln(w, " public static query(q: any): %v[] {", name) + writefmtln(w, " return undefined; // functionality provided by the runtime") + writefmtln(w, " }") + writefmtln(w, "}") }