From 26cf93f75961687a2cba6a523f5364eda1f43585 Mon Sep 17 00:00:00 2001 From: joeduffy Date: Mon, 19 Jun 2017 17:24:00 -0700 Subject: [PATCH] Implement `get` functions on all resources This change implements the `get` function for resources. Per pulumi/lumi#83, this allows Lumi scripts to actually read from the target environment. For example, we can now look up a SecurityGroup from its ARN: let group = aws.ec2.SecurityGroup.get( "arn:aws:ec2:us-west-2:153052954103:security-group:sg-02150d79"); The returned object is a fully functional resource object. So, we can then link it up with an EC2 instance, for example, in the usual ways: let instance = new aws.ec2.Instance(..., { securityGroups: [ group ], }); This didn't require any changes to the RPC or provider model, since we already implement the Get function. There are a few loose ends; two are short term: 1) URNs are not rehydrated. 2) Query is not yet implemented. One is mid-term: 3) We probably want a URN-based lookup function. But we will likely wait until we tackle pulumi/lumi#109 before adding this. And one is long term (and subtle): 4) These amount to I/O and are not repeatable! A change in the target environment may cause a script to generate a different plan intermittently. Most likely we want to apply a different kind of deployment "policy" for such scripts. These are inching towards the scripting model of pulumi/lumi#121, which is an entirely different beast than the repeatable immutable infrastructure deployments. Finally, it is worth noting that with this, we have some of the fundamental underpinnings required to finally tackle "inference" (pulumi/lumi#142). --- cmd/lumi/deploy.go | 71 ++- cmd/lumi/plan.go | 156 +++-- lib/aws/pack/apigateway/account.ts | 8 + lib/aws/pack/apigateway/apiKey.ts | 8 + lib/aws/pack/apigateway/authorizer.ts | 8 + lib/aws/pack/apigateway/basePathMapping.ts | 8 + lib/aws/pack/apigateway/clientCertificate.ts | 8 + lib/aws/pack/apigateway/deployment.ts | 8 + lib/aws/pack/apigateway/method.ts | 8 + lib/aws/pack/apigateway/model.ts | 8 + lib/aws/pack/apigateway/resource.ts | 8 + lib/aws/pack/apigateway/restAPI.ts | 8 + lib/aws/pack/apigateway/stage.ts | 8 + lib/aws/pack/apigateway/usagePlan.ts | 8 + lib/aws/pack/apigateway/usagePlanKey.ts | 8 + lib/aws/pack/cloudwatch/alarm.ts | 16 + lib/aws/pack/dynamodb/table.ts | 8 + lib/aws/pack/ec2/instance.ts | 8 + lib/aws/pack/ec2/internetGateway.ts | 8 + lib/aws/pack/ec2/route.ts | 8 + lib/aws/pack/ec2/routeTable.ts | 8 + lib/aws/pack/ec2/securityGroup.ts | 8 + lib/aws/pack/ec2/securityGroupEgress.ts | 8 + lib/aws/pack/ec2/securityGroupIngress.ts | 8 + lib/aws/pack/ec2/subnet.ts | 8 + lib/aws/pack/ec2/vpc.ts | 8 + lib/aws/pack/ec2/vpcGatewayAttachment.ts | 8 + lib/aws/pack/ec2/vpcPeeringConnection.ts | 8 + lib/aws/pack/elasticbeanstalk/application.ts | 8 + .../elasticbeanstalk/applicationVersion.ts | 8 + lib/aws/pack/elasticbeanstalk/environment.ts | 8 + lib/aws/pack/iam/group.ts | 8 + lib/aws/pack/iam/policy.ts | 8 + lib/aws/pack/iam/role.ts | 8 + lib/aws/pack/iam/user.ts | 8 + lib/aws/pack/kms/key.ts | 8 + lib/aws/pack/lambda/function.ts | 8 + lib/aws/pack/lambda/permission.ts | 8 + lib/aws/pack/s3/bucket.ts | 8 + lib/aws/pack/s3/object.ts | 8 + lib/aws/pack/sns/topic.ts | 8 + lib/aws/pack/sqs/queue.ts | 8 + lib/lumi/resource.ts | 7 +- pkg/diag/colors/colors.go | 1 + pkg/eval/eval.go | 33 +- pkg/eval/hooks.go | 2 +- pkg/resource/deploy/plan.go | 7 +- pkg/resource/deploy/plan_apply.go | 78 ++- pkg/resource/deploy/plan_test.go | 55 +- pkg/resource/deploy/progress.go | 4 +- pkg/resource/deploy/source.go | 24 +- pkg/resource/deploy/source_eval.go | 101 ++- pkg/resource/deploy/source_fixed.go | 13 +- pkg/resource/deploy/source_null.go | 9 +- pkg/resource/deploy/step.go | 593 +++++++++++++----- pkg/resource/resource_object.go | 8 + pkg/tools/lumidl/gen_pack.go | 12 + 57 files changed, 1143 insertions(+), 359 deletions(-) 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, "}") }