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).
This commit is contained in:
joeduffy 2017-06-19 17:24:00 -07:00
parent c265620f28
commit 26cf93f759
57 changed files with 1143 additions and 359 deletions

View file

@ -37,7 +37,8 @@ func newDeployCmd() *cobra.Command {
var dryRun bool var dryRun bool
var env string var env string
var showConfig bool var showConfig bool
var showReplaceSteps bool var showReads bool
var showReplaceDeletes bool
var showSames bool var showSames bool
var summary bool var summary bool
var output string var output string
@ -66,7 +67,8 @@ func newDeployCmd() *cobra.Command {
DryRun: dryRun, DryRun: dryRun,
Analyzers: analyzers, Analyzers: analyzers,
ShowConfig: showConfig, ShowConfig: showConfig,
ShowReplaceSteps: showReplaceSteps, ShowReads: showReads,
ShowReplaceDeletes: showReplaceDeletes,
ShowSames: showSames, ShowSames: showSames,
Summary: summary, Summary: summary,
Output: output, Output: output,
@ -88,7 +90,10 @@ func newDeployCmd() *cobra.Command {
&showConfig, "show-config", false, &showConfig, "show-config", false,
"Show configuration keys and variables") "Show configuration keys and variables")
cmd.PersistentFlags().BoolVar( 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") "Show detailed resource replacement creates and deletes; normally shows as a single step")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&showSames, "show-sames", false, &showSames, "show-sames", false,
@ -109,7 +114,8 @@ type deployOptions struct {
DryRun bool // true if we should just print the plan without performing it. 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. Analyzers []string // an optional set of analyzers to run as part of this deployment.
ShowConfig bool // true to show the configuration variables being used. ShowConfig bool // true to show the configuration variables being used.
ShowReplaceSteps bool // true to show the replacement steps in the plan. 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. 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. Summary bool // true if we should only summarize resources and operations.
DOT bool // true if we should print the DOT file for this plan. DOT bool // true if we should print the DOT file for this plan.
@ -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. // Create an object to track progress and perform the actual operations.
start := time.Now() start := time.Now()
progress := newProgress(opts.Summary) progress := newProgress(opts)
summary, _, _, err := result.Plan.Apply(progress) summary, _, _, err := result.Plan.Apply(progress)
contract.Assert(summary != nil) contract.Assert(summary != nil)
empty := (summary.Steps() == 0) // if no step is returned, it was empty. 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")) cmdutil.Diag().Infof(diag.Message("no resources need to be updated"))
} else { } else {
// Print out the total number of steps performed (and their kinds), the duration, and any summary info. // 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", footer.WriteString(fmt.Sprintf("%vDeployment duration: %v%v\n",
colors.SpecUnimportant, time.Since(start), colors.Reset)) colors.SpecUnimportant, time.Since(start), colors.Reset))
} }
@ -171,18 +177,18 @@ type deployProgress struct {
Steps int Steps int
Ops map[deploy.StepOp]int Ops map[deploy.StepOp]int
MaybeCorrupt bool MaybeCorrupt bool
Summary bool Opts deployOptions
} }
func newProgress(summary bool) *deployProgress { func newProgress(opts deployOptions) *deployProgress {
return &deployProgress{ return &deployProgress{
Steps: 0, Steps: 0,
Ops: make(map[deploy.StepOp]int), Ops: make(map[deploy.StepOp]int),
Summary: summary, Opts: opts,
} }
} }
func (prog *deployProgress) Before(step *deploy.Step) { func (prog *deployProgress) Before(step deploy.Step) {
stepop := step.Op() stepop := step.Op()
if stepop == deploy.OpSame { if stepop == deploy.OpSame {
return return
@ -192,17 +198,18 @@ func (prog *deployProgress) Before(step *deploy.Step) {
stepnum := prog.Steps + 1 stepnum := prog.Steps + 1
var extra string 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)" extra = " (part of a replacement change)"
} }
var b bytes.Buffer var b bytes.Buffer
b.WriteString(fmt.Sprintf("Applying step #%v [%v]%v\n", stepnum, stepop, extra)) 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)) 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() stepop := step.Op()
if err != nil { if err != nil {
// Issue a true, bonafide error. // 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(colors.Reset)
b.WriteString("\n") b.WriteString("\n")
fmt.Printf(colors.Colorize(&b)) fmt.Printf(colors.Colorize(&b))
} else if stepop != deploy.OpSame { } else if shouldTrack(step, prog.Opts) {
// Increment the counters. // Increment the counters.
prog.Steps++ prog.Steps++
prog.Ops[stepop]++ prog.Ops[stepop]++

View file

@ -21,6 +21,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/pulumi/lumi/pkg/diag" "github.com/pulumi/lumi/pkg/diag"
@ -38,7 +39,8 @@ func newPlanCmd() *cobra.Command {
var dotOutput bool var dotOutput bool
var env string var env string
var showConfig bool var showConfig bool
var showReplaceSteps bool var showReads bool
var showReplaceDeletes bool
var showSames bool var showSames bool
var summary bool var summary bool
var cmd = &cobra.Command{ var cmd = &cobra.Command{
@ -66,7 +68,8 @@ func newPlanCmd() *cobra.Command {
DryRun: true, DryRun: true,
Analyzers: analyzers, Analyzers: analyzers,
ShowConfig: showConfig, ShowConfig: showConfig,
ShowReplaceSteps: showReplaceSteps, ShowReads: showReads,
ShowReplaceDeletes: showReplaceDeletes,
ShowSames: showSames, ShowSames: showSames,
Summary: summary, Summary: summary,
DOT: dotOutput, DOT: dotOutput,
@ -93,7 +96,10 @@ func newPlanCmd() *cobra.Command {
&showConfig, "show-config", false, &showConfig, "show-config", false,
"Show configuration keys and variables") "Show configuration keys and variables")
cmd.PersistentFlags().BoolVar( 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") "Show detailed resource replacement creates and deletes; normally shows as a single step")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&showSames, "show-sames", false, &showSames, "show-sames", false,
@ -165,41 +171,44 @@ func printPlan(result *planResult, opts deployOptions) error {
iter, err := result.Plan.Iterate() iter, err := result.Plan.Iterate()
if err != nil { if err != nil {
cmdutil.Diag().Errorf(diag.Message("An error occurred while preparing the plan: %v"), err) return errors.Errorf("An error occurred while preparing the plan: %v", err)
return err
} }
step, err := iter.Next() step, err := iter.Next()
if err != nil { if err != nil {
cmdutil.Diag().Errorf(diag.Message("An error occurred while enumerating the plan: %v"), err) return errors.Errorf("An error occurred while enumerating the plan: %v", err)
return err
} }
var summary bytes.Buffer var summary bytes.Buffer
empty := true empty := true
counts := make(map[deploy.StepOp]int) counts := make(map[deploy.StepOp]int)
for step != nil { for step != nil {
op := step.Op() var err error
if empty && op != deploy.OpSame {
empty = false // 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). // 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`. // IDEA: it would be nice if, in the output, we showed the dependencies a la `git log --graph`.
if (opts.ShowReplaceSteps || op != deploy.OpReplaceDelete) && track := shouldTrack(step, opts)
(opts.ShowSames || op != deploy.OpSame) { if track {
printStep(&summary, step, opts.Summary, true, "") printStep(&summary, step, opts.Summary, true, "")
empty = false
} }
// Be sure to skip the step so that in-memory state updates are performed. // 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)
}
if track {
counts[step.Op()]++ counts[step.Op()]++
}
var err error
if step, err = iter.Next(); err != nil { if step, err = iter.Next(); err != nil {
cmdutil.Diag().Errorf(diag.Message("An error occurred while viewing the plan: %v"), err) return errors.Errorf("An error occurred while viewing the plan: %v", err)
return err
} }
} }
@ -208,12 +217,27 @@ func printPlan(result *planResult, opts deployOptions) error {
cmdutil.Diag().Infof(diag.Message("no resources need to be updated")) cmdutil.Diag().Infof(diag.Message("no resources need to be updated"))
} else { } else {
// Print a summary of operation counts. // Print a summary of operation counts.
printSummary(&summary, counts, opts.ShowReplaceSteps, true) printSummary(&summary, counts, true)
fmt.Print(colors.Colorize(&summary)) fmt.Print(colors.Colorize(&summary))
} }
return nil 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) { func printPrelude(b *bytes.Buffer, result *planResult, opts deployOptions, planning bool) {
// If there are configuration variables, show them. // If there are configuration variables, show them.
if opts.ShowConfig { 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 total := 0
for op, c := range counts { for _, c := range counts {
if op == deploy.OpSame || (!showReplaceSteps && op == deploy.OpReplaceDelete) {
continue // skip counting replacement steps unless explicitly requested.
}
total += c total += c
} }
@ -263,10 +284,6 @@ func printSummary(b *bytes.Buffer, counts map[deploy.StepOp]int, showReplaceStep
} }
for _, op := range deploy.StepOps { 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 { if c := counts[op]; c > 0 {
b.WriteString(fmt.Sprintf(" %v%v %v %v%v%v%v\n", b.WriteString(fmt.Sprintf(" %v%v %v %v%v%v%v\n",
op.Prefix(), c, plural("resource", c), planTo, op, pastTense, colors.Reset)) 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() { for _, res := range stats.Resources() {
if stats.Sames()[res.URN()] { if stats.Sames()[res.URN()] {
b.WriteString(" ") // simulate the 2 spaces for +, -, etc. 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, "") 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. // First print out the operation's prefix.
b.WriteString(step.Op().Prefix()) b.WriteString(step.Op().Prefix())
// Next print the resource URN, properties, etc. // Next, print the resource type (since it is easy on the eyes and can be quickly identified).
printResourceHeader(b, step.Old(), step.New(), indent) printStepHeader(b, step)
b.WriteString(step.Op().Suffix()) 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. // Finally make sure to reset the color.
b.WriteString(colors.Reset) b.WriteString(colors.Reset)
} }
func printResourceHeader(b *bytes.Buffer, old *resource.State, new *resource.Object, indent string) { func printStepHeader(b *bytes.Buffer, step deploy.Step) {
var t tokens.Type b.WriteString(fmt.Sprintf("%s:\n", string(step.Type())))
if old != nil { }
t = old.Type()
} else {
t = new.Type()
}
// The primary header is the resource type (since it is easy on the eyes). func printResourceHeader(b *bytes.Buffer, res resource.Resource) {
b.WriteString(fmt.Sprintf("%s:\n", string(t))) b.WriteString(fmt.Sprintf("%s:\n", string(res.Type())))
} }
func printResourceProperties(b *bytes.Buffer, urn resource.URN, old *resource.State, new *resource.Object, 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 indent += detailsIndent
// Print out the URN and, if present, the ID, as "pseudo-properties". // 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 != "" { if id != "" {
b.WriteString(fmt.Sprintf("%s[id=%s]\n", indent, string(id))) b.WriteString(fmt.Sprintf("%s[id=%s]\n", indent, string(id)))
} }
if urn != "" {
b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, urn)) b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, urn))
}
if !summary { if !summary {
// Print all of the properties associated with this resource. // Print all of the properties associated with this resource.
if old == nil && new != nil { if old == nil && new != nil {
printObject(b, inputs, planning, indent) printObject(b, props, planning, indent)
} else if new == nil && old != nil { } else if new == nil && old != nil {
printObject(b, old.Inputs(), planning, indent) printObject(b, old.Inputs(), planning, indent)
} else { } else {
contract.Assert(inputs != nil) // use computed properties for diffs. contract.Assert(props != nil) // use computed properties for diffs.
printOldNewDiffs(b, old.Inputs(), inputs, replaces, planning, indent) 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 // 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. // 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 indent += detailsIndent
b.WriteString(step.Op().Color()) b.WriteString(step.Op().Color())
b.WriteString(step.Op().Suffix()) b.WriteString(step.Op().Suffix())
// First fetch all the relevant property maps that we may consult. // First fetch all the relevant property maps that we may consult.
newins := step.Inputs() newins := mut.Inputs()
newouts := step.Outputs() newouts := mut.Outputs()
var oldouts resource.PropertyMap var oldouts resource.PropertyMap
if old := step.Old(); old != nil { if old := mut.Old(); old != nil {
oldouts = old.Outputs() 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) } titleFunc := func(id string) { printArrayElemHeader(b, i, id) }
if add, isadd := a.Adds[i]; isadd { if add, isadd := a.Adds[i]; isadd {
b.WriteString(deploy.OpCreate.Color()) b.WriteString(deploy.OpCreate.Color())
title(addIndent(indent)) titleFunc(addIndent(indent))
printPropertyValue(b, add, planning, addIndent(newIndent)) printPropertyValue(b, add, planning, addIndent(newIndent))
b.WriteString(colors.Reset) b.WriteString(colors.Reset)
} else if delete, isdelete := a.Deletes[i]; isdelete { } else if delete, isdelete := a.Deletes[i]; isdelete {
b.WriteString(deploy.OpDelete.Color()) b.WriteString(deploy.OpDelete.Color())
title(deleteIndent(indent)) titleFunc(deleteIndent(indent))
printPropertyValue(b, delete, planning, deleteIndent(newIndent)) printPropertyValue(b, delete, planning, deleteIndent(newIndent))
b.WriteString(colors.Reset) b.WriteString(colors.Reset)
} else if update, isupdate := a.Updates[i]; isupdate { } else if update, isupdate := a.Updates[i]; isupdate {

View file

@ -15,6 +15,14 @@ export class Account extends lumi.NamedResource implements AccountArgs {
this.cloudWatchRole = args.cloudWatchRole; this.cloudWatchRole = args.cloudWatchRole;
} }
} }
public static get(id: lumi.ID): Account {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Account[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface AccountArgs { export interface AccountArgs {

View file

@ -22,6 +22,14 @@ export class APIKey extends lumi.NamedResource implements APIKeyArgs {
this.stageKeys = args.stageKeys; this.stageKeys = args.stageKeys;
} }
} }
public static get(id: lumi.ID): APIKey {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): APIKey[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface APIKeyArgs { export interface APIKeyArgs {

View file

@ -34,6 +34,14 @@ export class Authorizer extends lumi.NamedResource implements AuthorizerArgs {
this.providers = args.providers; this.providers = args.providers;
this.restAPI = args.restAPI; this.restAPI = args.restAPI;
} }
public static get(id: lumi.ID): Authorizer {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Authorizer[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface AuthorizerArgs { export interface AuthorizerArgs {

View file

@ -26,6 +26,14 @@ export class BasePathMapping extends lumi.NamedResource implements BasePathMappi
this.basePath = args.basePath; this.basePath = args.basePath;
this.stage = args.stage; this.stage = args.stage;
} }
public static get(id: lumi.ID): BasePathMapping {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): BasePathMapping[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface BasePathMappingArgs { export interface BasePathMappingArgs {

View file

@ -13,6 +13,14 @@ export class ClientCertificate extends lumi.NamedResource implements ClientCerti
this.description = args.description; this.description = args.description;
} }
} }
public static get(id: lumi.ID): ClientCertificate {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): ClientCertificate[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface ClientCertificateArgs { export interface ClientCertificateArgs {

View file

@ -20,6 +20,14 @@ export class Deployment extends lumi.NamedResource implements DeploymentArgs {
this.restAPI = args.restAPI; this.restAPI = args.restAPI;
this.description = args.description; this.description = args.description;
} }
public static get(id: lumi.ID): Deployment {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Deployment[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface DeploymentArgs { export interface DeploymentArgs {

View file

@ -97,6 +97,14 @@ export class Method extends lumi.NamedResource implements MethodArgs {
this.requestModels = args.requestModels; this.requestModels = args.requestModels;
this.requestParameters = args.requestParameters; this.requestParameters = args.requestParameters;
} }
public static get(id: lumi.ID): Method {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Method[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface MethodArgs { export interface MethodArgs {

View file

@ -30,6 +30,14 @@ export class Model extends lumi.NamedResource implements ModelArgs {
this.modelName = args.modelName; this.modelName = args.modelName;
this.description = args.description; this.description = args.description;
} }
public static get(id: lumi.ID): Model {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Model[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface ModelArgs { export interface ModelArgs {

View file

@ -26,6 +26,14 @@ export class Resource extends lumi.NamedResource implements ResourceArgs {
} }
this.restAPI = args.restAPI; this.restAPI = args.restAPI;
} }
public static get(id: lumi.ID): Resource {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Resource[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface ResourceArgs { export interface ResourceArgs {

View file

@ -32,6 +32,14 @@ export class RestAPI extends lumi.NamedResource implements RestAPIArgs {
this.parameters = args.parameters; this.parameters = args.parameters;
} }
} }
public static get(id: lumi.ID): RestAPI {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): RestAPI[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface RestAPIArgs { export interface RestAPIArgs {

View file

@ -45,6 +45,14 @@ export class Stage extends lumi.NamedResource implements StageArgs {
this.methodSettings = args.methodSettings; this.methodSettings = args.methodSettings;
this.variables = args.variables; this.variables = args.variables;
} }
public static get(id: lumi.ID): Stage {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Stage[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface StageArgs { export interface StageArgs {

View file

@ -49,6 +49,14 @@ export class UsagePlan extends lumi.NamedResource implements UsagePlanArgs {
this.usagePlanName = args.usagePlanName; this.usagePlanName = args.usagePlanName;
} }
} }
public static get(id: lumi.ID): UsagePlan {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): UsagePlan[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface UsagePlanArgs { export interface UsagePlanArgs {

View file

@ -22,6 +22,14 @@ export class UsagePlanKey extends lumi.NamedResource implements UsagePlanKeyArgs
} }
this.usagePlan = args.usagePlan; this.usagePlan = args.usagePlan;
} }
public static get(id: lumi.ID): UsagePlanKey {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): UsagePlanKey[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface UsagePlanKeyArgs { export interface UsagePlanKeyArgs {

View file

@ -56,6 +56,14 @@ export class ActionTarget extends lumi.NamedResource implements ActionTargetArgs
this.subscription = args.subscription; this.subscription = args.subscription;
} }
} }
public static get(id: lumi.ID): ActionTarget {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): ActionTarget[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface ActionTargetArgs { export interface ActionTargetArgs {
@ -120,6 +128,14 @@ export class Alarm extends lumi.NamedResource implements AlarmArgs {
this.okActions = args.okActions; this.okActions = args.okActions;
this.unit = args.unit; this.unit = args.unit;
} }
public static get(id: lumi.ID): Alarm {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Alarm[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface AlarmArgs { export interface AlarmArgs {

View file

@ -67,6 +67,14 @@ export class Table extends lumi.NamedResource implements TableArgs {
this.tableName = args.tableName; this.tableName = args.tableName;
this.globalSecondaryIndexes = args.globalSecondaryIndexes; this.globalSecondaryIndexes = args.globalSecondaryIndexes;
} }
public static get(id: lumi.ID): Table {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Table[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface TableArgs { export interface TableArgs {

View file

@ -87,6 +87,14 @@ export class Instance extends lumi.NamedResource implements InstanceArgs {
this.keyName = args.keyName; this.keyName = args.keyName;
this.tags = args.tags; this.tags = args.tags;
} }
public static get(id: lumi.ID): Instance {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Instance[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface InstanceArgs { export interface InstanceArgs {

View file

@ -9,6 +9,14 @@ export class InternetGateway extends lumi.NamedResource implements InternetGatew
constructor(name: string, args?: InternetGatewayArgs) { constructor(name: string, args?: InternetGatewayArgs) {
super(name); super(name);
} }
public static get(id: lumi.ID): InternetGateway {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): InternetGateway[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface InternetGatewayArgs { export interface InternetGatewayArgs {

View file

@ -33,6 +33,14 @@ export class Route extends lumi.NamedResource implements RouteArgs {
} }
this.vpcGatewayAttachment = args.vpcGatewayAttachment; this.vpcGatewayAttachment = args.vpcGatewayAttachment;
} }
public static get(id: lumi.ID): Route {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Route[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface RouteArgs { export interface RouteArgs {

View file

@ -16,6 +16,14 @@ export class RouteTable extends lumi.NamedResource implements RouteTableArgs {
} }
this.vpc = args.vpc; this.vpc = args.vpc;
} }
public static get(id: lumi.ID): RouteTable {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): RouteTable[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface RouteTableArgs { export interface RouteTableArgs {

View file

@ -25,6 +25,14 @@ export class SecurityGroup extends lumi.NamedResource implements SecurityGroupAr
this.securityGroupEgress = args.securityGroupEgress; this.securityGroupEgress = args.securityGroupEgress;
this.securityGroupIngress = args.securityGroupIngress; this.securityGroupIngress = args.securityGroupIngress;
} }
public static get(id: lumi.ID): SecurityGroup {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): SecurityGroup[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface SecurityGroupArgs { export interface SecurityGroupArgs {

View file

@ -39,6 +39,14 @@ export class SecurityGroupEgress extends lumi.NamedResource implements SecurityG
this.destinationPrefixListId = args.destinationPrefixListId; this.destinationPrefixListId = args.destinationPrefixListId;
this.destinationSecurityGroup = args.destinationSecurityGroup; this.destinationSecurityGroup = args.destinationSecurityGroup;
} }
public static get(id: lumi.ID): SecurityGroupEgress {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): SecurityGroupEgress[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface SecurityGroupEgressArgs { export interface SecurityGroupEgressArgs {

View file

@ -34,6 +34,14 @@ export class SecurityGroupIngress extends lumi.NamedResource implements Security
this.sourceSecurityGroupOwnerId = args.sourceSecurityGroupOwnerId; this.sourceSecurityGroupOwnerId = args.sourceSecurityGroupOwnerId;
this.toPort = args.toPort; this.toPort = args.toPort;
} }
public static get(id: lumi.ID): SecurityGroupIngress {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): SecurityGroupIngress[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface SecurityGroupIngressArgs { export interface SecurityGroupIngressArgs {

View file

@ -25,6 +25,14 @@ export class Subnet extends lumi.NamedResource implements SubnetArgs {
this.availabilityZone = args.availabilityZone; this.availabilityZone = args.availabilityZone;
this.mapPublicIpOnLaunch = args.mapPublicIpOnLaunch; this.mapPublicIpOnLaunch = args.mapPublicIpOnLaunch;
} }
public static get(id: lumi.ID): Subnet {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Subnet[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface SubnetArgs { export interface SubnetArgs {

View file

@ -29,6 +29,14 @@ export class VPC extends lumi.NamedResource implements VPCArgs {
this.enableDnsSupport = args.enableDnsSupport; this.enableDnsSupport = args.enableDnsSupport;
this.enableDnsHostnames = args.enableDnsHostnames; this.enableDnsHostnames = args.enableDnsHostnames;
} }
public static get(id: lumi.ID): VPC {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): VPC[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface VPCArgs { export interface VPCArgs {

View file

@ -22,6 +22,14 @@ export class VPCGatewayAttachment extends lumi.NamedResource implements VPCGatew
} }
this.internetGateway = args.internetGateway; this.internetGateway = args.internetGateway;
} }
public static get(id: lumi.ID): VPCGatewayAttachment {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): VPCGatewayAttachment[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface VPCGatewayAttachmentArgs { export interface VPCGatewayAttachmentArgs {

View file

@ -21,6 +21,14 @@ export class VPCPeeringConnection extends lumi.NamedResource implements VPCPeeri
} }
this.vpc = args.vpc; this.vpc = args.vpc;
} }
public static get(id: lumi.ID): VPCPeeringConnection {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): VPCPeeringConnection[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface VPCPeeringConnectionArgs { export interface VPCPeeringConnectionArgs {

View file

@ -15,6 +15,14 @@ export class Application extends lumi.NamedResource implements ApplicationArgs {
this.description = args.description; this.description = args.description;
} }
} }
public static get(id: lumi.ID): Application {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Application[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface ApplicationArgs { export interface ApplicationArgs {

View file

@ -26,6 +26,14 @@ export class ApplicationVersion extends lumi.NamedResource implements Applicatio
} }
this.sourceBundle = args.sourceBundle; this.sourceBundle = args.sourceBundle;
} }
public static get(id: lumi.ID): ApplicationVersion {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): ApplicationVersion[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface ApplicationVersionArgs { export interface ApplicationVersionArgs {

View file

@ -40,6 +40,14 @@ export class Environment extends lumi.NamedResource implements EnvironmentArgs {
this.tier = args.tier; this.tier = args.tier;
this.version = args.version; this.version = args.version;
} }
public static get(id: lumi.ID): Environment {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Environment[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface EnvironmentArgs { export interface EnvironmentArgs {

View file

@ -21,6 +21,14 @@ export class Group extends lumi.NamedResource implements GroupArgs {
this.policies = args.policies; this.policies = args.policies;
} }
} }
public static get(id: lumi.ID): Group {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Group[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface GroupArgs { export interface GroupArgs {

View file

@ -34,6 +34,14 @@ export class Policy extends lumi.NamedResource implements PolicyArgs {
this.roles = args.roles; this.roles = args.roles;
this.users = args.users; this.users = args.users;
} }
public static get(id: lumi.ID): Policy {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Policy[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface PolicyArgs { export interface PolicyArgs {

View file

@ -26,6 +26,14 @@ export class Role extends lumi.NamedResource implements RoleArgs {
this.managedPolicyARNs = args.managedPolicyARNs; this.managedPolicyARNs = args.managedPolicyARNs;
this.policies = args.policies; this.policies = args.policies;
} }
public static get(id: lumi.ID): Role {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Role[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface RoleArgs { export interface RoleArgs {

View file

@ -31,6 +31,14 @@ export class User extends lumi.NamedResource implements UserArgs {
this.policies = args.policies; this.policies = args.policies;
} }
} }
public static get(id: lumi.ID): User {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): User[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface UserArgs { export interface UserArgs {

View file

@ -20,6 +20,14 @@ export class Key extends lumi.NamedResource implements KeyArgs {
this.enabled = args.enabled; this.enabled = args.enabled;
this.enableKeyRotation = args.enableKeyRotation; this.enableKeyRotation = args.enableKeyRotation;
} }
public static get(id: lumi.ID): Key {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Key[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface KeyArgs { export interface KeyArgs {

View file

@ -69,6 +69,14 @@ export class Function extends lumi.NamedResource implements FunctionArgs {
this.timeout = args.timeout; this.timeout = args.timeout;
this.vpcConfig = args.vpcConfig; this.vpcConfig = args.vpcConfig;
} }
public static get(id: lumi.ID): Function {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Function[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface FunctionArgs { export interface FunctionArgs {

View file

@ -31,6 +31,14 @@ export class Permission extends lumi.NamedResource implements PermissionArgs {
this.sourceAccount = args.sourceAccount; this.sourceAccount = args.sourceAccount;
this.sourceARN = args.sourceARN; this.sourceARN = args.sourceARN;
} }
public static get(id: lumi.ID): Permission {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Permission[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface PermissionArgs { export interface PermissionArgs {

View file

@ -17,6 +17,14 @@ export class Bucket extends lumi.NamedResource implements BucketArgs {
this.accessControl = args.accessControl; this.accessControl = args.accessControl;
} }
} }
public static get(id: lumi.ID): Bucket {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Bucket[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface BucketArgs { export interface BucketArgs {

View file

@ -26,6 +26,14 @@ export class Object extends lumi.Resource implements ObjectArgs {
} }
this.source = args.source; this.source = args.source;
} }
public static get(id: lumi.ID): Object {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Object[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface ObjectArgs { export interface ObjectArgs {

View file

@ -26,6 +26,14 @@ export class Topic extends lumi.NamedResource implements TopicArgs {
this.subscription = args.subscription; this.subscription = args.subscription;
} }
} }
public static get(id: lumi.ID): Topic {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Topic[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface TopicArgs { export interface TopicArgs {

View file

@ -29,6 +29,14 @@ export class Queue extends lumi.NamedResource implements QueueArgs {
this.visibilityTimeout = args.visibilityTimeout; this.visibilityTimeout = args.visibilityTimeout;
} }
} }
public static get(id: lumi.ID): Queue {
return <any>undefined; // functionality provided by the runtime
}
public static query(q: any): Queue[] {
return <any>undefined; // functionality provided by the runtime
}
} }
export interface QueueArgs { export interface QueueArgs {

View file

@ -13,10 +13,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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. // Resource represents a class whose CRUD operations are implemented by a provider plugin.
export abstract class Resource { export abstract class Resource {
public readonly id: string; // the provider-assigned unique ID (initialized by the runtime). public readonly id: ID; // the provider-assigned unique ID (initialized by the runtime).
public readonly urn: string; // the Lumi URN (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. // NamedResource is a kind of resource that has a friendly resource name associated with it.

View file

@ -81,6 +81,7 @@ var (
SpecAdded = Green // for adds (in the diff sense). SpecAdded = Green // for adds (in the diff sense).
SpecChanged = BrightYellow // for changes (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). SpecReplaced = Yellow // for replacements (in the diff sense).
SpecDeleted = Red // for deletes (in the diff sense). SpecDeleted = Red // for deletes (in the diff sense).
) )

View file

@ -701,13 +701,6 @@ func (e *evaluator) evalCall(node diag.Diagable,
e.pushScope(frame, !shareActivation) e.pushScope(frame, !shareActivation)
defer e.popScope(frame) 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 the target is an instance method, the "this" and "super" variables must be bound to values.
if thisVariable != nil { if thisVariable != nil {
contract.Assert(this != nil) contract.Assert(this != nil)
@ -740,8 +733,19 @@ 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 var uw *rt.Unwind
// 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 { if intrinsic {
isym := sym.(*rt.Intrinsic) isym := sym.(*rt.Intrinsic)
invoker := GetIntrinsicInvoker(isym) invoker := GetIntrinsicInvoker(isym)
@ -749,6 +753,7 @@ func (e *evaluator) evalCall(node diag.Diagable,
} else { } else {
uw = e.evalStatement(fnc.GetBody()) uw = e.evalStatement(fnc.GetBody())
} }
}
// Check that the unwind is as expected. In particular: // Check that the unwind is as expected. In particular:
// 1) no breaks or continues are expected; // 1) no breaks or continues are expected;

View file

@ -30,7 +30,7 @@ type Hooks interface {
// OnEnterModule is invoked whenever we enter a module. // OnEnterModule is invoked whenever we enter a module.
OnEnterModule(sym *symbols.Module) func() OnEnterModule(sym *symbols.Module) func()
// OnEnterFunction is invoked whenever we enter a function. // 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 // 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. // any, has been run to completion. The diagnostics tree is the AST node responsible for the allocation.
OnObjectInit(tree diag.Diagable, o *rt.Object) OnObjectInit(tree diag.Diagable, o *rt.Object)

View file

@ -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 // 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. // 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) { 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() pkg := t.Package()
return p.ctx.Host.Provider(pkg) return p.ctx.Host.Provider(pkg)
} }

View file

@ -22,7 +22,6 @@ import (
"github.com/pulumi/lumi/pkg/compiler/errors" "github.com/pulumi/lumi/pkg/compiler/errors"
"github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/resource/plugin" "github.com/pulumi/lumi/pkg/resource/plugin"
"github.com/pulumi/lumi/pkg/tokens"
"github.com/pulumi/lumi/pkg/util/contract" "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 // 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 // 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. // 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. // Fetch a plan iterator and keep walking it until we are done.
iter, err := p.Iterate() iter, err := p.Iterate()
if err != nil { if err != nil {
@ -43,12 +42,18 @@ func (p *Plan) Apply(prog Progress) (PlanSummary, *Step, resource.Status, error)
return nil, nil, resource.StatusOK, err return nil, nil, resource.StatusOK, err
} }
for step != nil { for step != nil {
// Do the pre-step.
rst := resource.StatusOK
err := step.Pre()
// Perform pre-application progress reporting. // Perform pre-application progress reporting.
if prog != nil { if prog != nil {
prog.Before(step) prog.Before(step)
} }
rst, err := step.Apply() if err == nil {
rst, err = step.Apply()
}
// Perform post-application progress reporting. // Perform post-application progress reporting.
if prog != nil { 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) Dones() map[*resource.State]bool { return iter.dones }
func (iter *PlanIterator) Done() bool { return iter.done } 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 // 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 // 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. // taken, Next will return a nil step pointer.
func (iter *PlanIterator) Next() (*Step, error) { func (iter *PlanIterator) Next() (Step, error) {
for !iter.done { for !iter.done {
if !iter.srcdone { if !iter.srcdone {
obj, ctx, err := iter.src.Next() res, q, err := iter.src.Next()
if err != nil { if err != nil {
return nil, err return nil, err
} else if obj == nil { } else if res != nil {
// If the source is done, note it, and don't go back for more. step, err := iter.nextResourceStep(res)
iter.srcdone = true
iter.delqueue = iter.calculateDeletes()
} else {
step, err := iter.nextResource(obj, ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} else if step != nil { }
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 return step, nil
} }
// If the step returned was nil, this resource is fine, so we'll keep on going. // If all returns are nil, the source is done, note it, and don't go back for more. Add any deletions to be
continue // 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 { } else {
// The interpreter has finished, so we need to now drain any deletions that piled up. // 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 return step, nil
} }
@ -173,9 +188,10 @@ func (iter *PlanIterator) Next() (*Step, error) {
return nil, nil return nil, nil
} }
// nextResource produces a new step for a given resource or nil if there isn't one to perform. // nextResourceStep 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) { func (iter *PlanIterator) nextResourceStep(res *SourceAllocation) (Step, error) {
// Take a moment in time snapshot of the live object's properties. // Take a moment in time snapshot of the live object's properties.
new := res.Obj
t := new.Type() t := new.Type()
inputs := new.CopyProperties() inputs := new.CopyProperties()
@ -190,7 +206,7 @@ func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module)
if err != nil { if err != nil {
return nil, err 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. // First ensure the provider is okay with this resource.
var invalid bool var invalid bool
@ -259,7 +275,7 @@ func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module)
} else if len(replacements) > 0 { } else if len(replacements) > 0 {
iter.replaces[urn] = true iter.replaces[urn] = true
glog.V(7).Infof("Planner decided to replace '%v' (oldprops=%v inputs=%v)", urn, oldprops, inputs) 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 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 return NewCreateStep(iter, urn, new, inputs), nil
} }
// nextDelete produces a new step that deletes a resource if necessary. // nextQueryStep produces a new query step that looks up a resource in some manner.
func (iter *PlanIterator) nextDelete() *Step { 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 { if len(iter.delqueue) > 0 {
del := iter.delqueue[0] del := iter.delqueue[0]
iter.delqueue = iter.delqueue[1:] iter.delqueue = iter.delqueue[1:]
@ -282,10 +308,10 @@ func (iter *PlanIterator) nextDelete() *Step {
iter.deletes[urn] = true iter.deletes[urn] = true
if iter.replaces[urn] { if iter.replaces[urn] {
glog.V(7).Infof("Planner decided to delete '%v' due to replacement", 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 return nil
} }

View file

@ -116,8 +116,12 @@ func (iter *errorSourceIterator) Close() error {
return nil // nothing to do. return nil // nothing to do.
} }
func (iter *errorSourceIterator) Next() (*resource.Object, tokens.Module, error) { func (iter *errorSourceIterator) Produce(res *resource.Object) {
return nil, "", iter.src.err // 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. // TestBasicCRUDPlan creates a plan with numerous C(R)UD operations.
@ -236,44 +240,48 @@ func TestBasicCRUDPlan(t *testing.T) {
break break
} }
err = step.Pre()
assert.Nil(t, err)
var urn resource.URN var urn resource.URN
var realID bool var realID bool
var expectOuts resource.PropertyMap var expectOuts resource.PropertyMap
var obj *resource.Object var obj *resource.Object
op := step.Op() switch s := step.(type) {
switch op { case *CreateStep: // A is created
case OpCreate: // A is created old := s.Old()
old := step.Old() new := s.New()
new := step.New()
assert.Nil(t, old) assert.Nil(t, old)
assert.NotNil(t, new) assert.NotNil(t, new)
assert.Equal(t, newResAProps, step.Inputs()) assert.Equal(t, newResAProps, s.Inputs())
obj, urn, realID = new, urnA, false obj, urn, realID = new, urnA, false
case OpUpdate: // B is updated case *UpdateStep: // B is updated
old := step.Old() old := s.Old()
new := step.New() new := s.New()
assert.NotNil(t, old) assert.NotNil(t, old)
assert.Equal(t, urnB, old.URN()) assert.Equal(t, urnB, old.URN())
assert.Equal(t, oldResB, old) assert.Equal(t, oldResB, old)
assert.NotNil(t, new) assert.NotNil(t, new)
assert.Equal(t, newResBProps, step.Inputs()) assert.Equal(t, newResBProps, s.Inputs())
obj, urn, realID = new, urnB, true obj, urn, realID = new, urnB, true
case OpSame: // C is the same case *SameStep: // C is the same
old := step.Old() old := s.Old()
new := step.New() new := s.New()
assert.NotNil(t, old) assert.NotNil(t, old)
assert.Equal(t, urnC, old.URN()) assert.Equal(t, urnC, old.URN())
assert.Equal(t, oldResC, old) assert.Equal(t, oldResC, old)
assert.NotNil(t, new) 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() obj, urn, realID, expectOuts = new, urnC, true, oldResC.Outputs()
case OpDelete: // D is deleted case *DeleteStep: // D is deleted
old := step.Old() old := s.Old()
new := step.New() new := s.New()
assert.NotNil(t, old) assert.NotNil(t, old)
assert.Equal(t, urnD, old.URN()) assert.Equal(t, urnD, old.URN())
assert.Equal(t, oldResD, old) assert.Equal(t, oldResD, old)
assert.Nil(t, new) assert.Nil(t, new)
default:
t.FailNow() // unexpected step kind.
} }
if obj != nil { 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 { if obj != nil {
// Ensure the ID and URN are populated correctly. // Ensure the ID and URN are populated correctly.
if realID { if realID {
@ -317,8 +327,9 @@ func TestBasicCRUDPlan(t *testing.T) {
assert.Equal(t, 1, seen[OpCreate]) assert.Equal(t, 1, seen[OpCreate])
assert.Equal(t, 1, seen[OpUpdate]) assert.Equal(t, 1, seen[OpUpdate])
assert.Equal(t, 1, seen[OpDelete]) assert.Equal(t, 1, seen[OpDelete])
assert.Equal(t, 0, seen[OpReplaceCreate]) assert.Equal(t, 0, seen[OpReplace])
assert.Equal(t, 0, seen[OpReplaceDelete]) assert.Equal(t, 0, seen[OpGet])
assert.Equal(t, 0, seen[OpQuery])
assert.Equal(t, 1, len(iter.Creates())) assert.Equal(t, 1, len(iter.Creates()))
assert.True(t, iter.Creates()[urnA]) assert.True(t, iter.Creates()[urnA])

View file

@ -22,7 +22,7 @@ import (
// Progress can be used for progress reporting. // Progress can be used for progress reporting.
type Progress interface { type Progress interface {
// Before is invoked prior to a step executing. // 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 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)
} }

View file

@ -18,6 +18,7 @@ package deploy
import ( import (
"io" "io"
"github.com/pulumi/lumi/pkg/compiler/symbols"
"github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/tokens" "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. // A SourceIterator enumerates the list of resources that a source has to offer.
type SourceIterator interface { type SourceIterator interface {
io.Closer io.Closer
// Next returns the next resource object plus a token context (usually where it was allocated); the token is used in // Produce registers a resource that was produced during the iteration, to publish next time.
// the production of the ensuing resource's URN. If something went wrong, error is non-nil. If both error and the Produce(res *resource.Object)
// resource are nil, then the iterator has completed its job and no subsequent calls to next should be made. // Next returns the next step from the source. If the source allocation is non-nil, it represents the creation of
Next() (*resource.Object, tokens.Module, error) // 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).
} }

View file

@ -23,6 +23,7 @@ import (
"github.com/pulumi/lumi/pkg/compiler/core" "github.com/pulumi/lumi/pkg/compiler/core"
"github.com/pulumi/lumi/pkg/compiler/errors" "github.com/pulumi/lumi/pkg/compiler/errors"
"github.com/pulumi/lumi/pkg/compiler/symbols" "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/diag"
"github.com/pulumi/lumi/pkg/eval" "github.com/pulumi/lumi/pkg/eval"
"github.com/pulumi/lumi/pkg/eval/rt" "github.com/pulumi/lumi/pkg/eval/rt"
@ -100,6 +101,7 @@ func (src *evalSource) Iterate() (SourceIterator, error) {
type evalSourceIterator struct { type evalSourceIterator struct {
src *evalSource // the owning eval source object. src *evalSource // the owning eval source object.
e eval.Interpreter // the interpreter used to compute the new state. 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. rz *rendezvous.Rendezvous // the rendezvous where planning and evaluator coroutines meet.
} }
@ -109,21 +111,57 @@ func (iter *evalSourceIterator) Close() error {
return nil 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. // 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 { if err != nil {
return nil, "", err return nil, nil, err
} else if done { } else if done {
glog.V(5).Infof("EvalSourceIterator is 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.Failf("Unexpected rendezvous object: %v (expected alloc or query)", obj)
contract.Assert(obj != nil) return nil, nil, 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
} }
// InitEvalConfig applies the configuration map to an existing interpreter context. The map is simply a map of tokens -- // 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 return nil
} }
// AllocInfo is the context in which an object got allocated. // AllocRendezvous is used when an object is allocated, and tracks the context in which it was allocated.
type AllocInfo struct { type AllocRendezvous struct {
Obj *rt.Object // the object itself. Obj *rt.Object // the object itself.
Loc diag.Diagable // the location information for the allocation. Loc diag.Diagable // the location information for the allocation.
Pkg *symbols.Package // the package being evaluated when the allocation happened. 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. 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. // evalHooks are the interpreter hooks that synchronize between planner and evaluator in the appropriate ways.
type evalHooks struct { type evalHooks struct {
rz *rendezvous.Rendezvous // the rendezvous object. 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)) glog.V(9).Infof("EvalSource OnObjectInit %v (IsResource=%v)", obj, resource.IsResourceObject(obj))
if resource.IsResourceObject(obj) { if resource.IsResourceObject(obj) {
// Communicate the full allocation context: AST node, package, module, and function. // Communicate the full allocation context: AST node, package, module, and function.
alloc := &AllocInfo{ alloc := &AllocRendezvous{
Obj: obj, Obj: obj,
Loc: tree, Loc: tree,
Pkg: h.currpkg, Pkg: h.currpkg,
@ -276,12 +320,39 @@ func (h *evalHooks) OnEnterModule(mod *symbols.Module) func() {
} }
} }
// OnEnterFunction is invoked whenever we enter a new function. const (
func (h *evalHooks) OnEnterFunction(fnc symbols.Function) func() { 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) glog.V(9).Infof("EvalSource OnEnterFunction %v", fnc)
prevfnc := h.currfnc prevfnc := h.currfnc
h.currfnc = fnc 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) glog.V(9).Infof("EvalSource OnLeaveFunction %v", fnc)
h.currfnc = prevfnc h.currfnc = prevfnc
} }

View file

@ -56,10 +56,17 @@ func (iter *fixedSourceIterator) Close() error {
return nil // nothing to do. 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++ iter.current++
if iter.current >= len(iter.src.resources) { 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
} }

View file

@ -17,7 +17,6 @@ package deploy
import ( import (
"github.com/pulumi/lumi/pkg/resource" "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" // 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. return nil // nothing to do.
} }
func (iter *nullSourceIterator) Next() (*resource.Object, tokens.Module, error) { func (iter *nullSourceIterator) Produce(res *resource.Object) {
return nil, "", nil // means "done" // ignore
}
func (iter *nullSourceIterator) Next() (*SourceAllocation, *SourceQuery, error) {
return nil, nil, nil // means "done"
} }

View file

@ -16,100 +16,136 @@
package deploy package deploy
import ( import (
"github.com/pulumi/lumi/pkg/compiler/symbols"
"github.com/pulumi/lumi/pkg/diag/colors" "github.com/pulumi/lumi/pkg/diag/colors"
"github.com/pulumi/lumi/pkg/resource" "github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/resource/plugin" "github.com/pulumi/lumi/pkg/resource/plugin"
"github.com/pulumi/lumi/pkg/tokens"
"github.com/pulumi/lumi/pkg/util/contract" "github.com/pulumi/lumi/pkg/util/contract"
) )
// Step is a specification for a deployment operation. // Step is a specification for a deployment operation.
type Step struct { 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).
}
// 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. 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. old *resource.State // the state of the resource before this step.
new *resource.Object // the state of the resource after this step. new *resource.Object // the state of the resource after this step.
inputs resource.PropertyMap // the input properties to use during the operation. inputs resource.PropertyMap // the computed inputs supplied at creation time.
outputs resource.PropertyMap // the output properties calculated after the operation.
reasons []resource.PropertyKey // the reasons for replacement, if applicable.
} }
func NewSameStep(iter *PlanIterator, old *resource.State, new *resource.Object, inputs resource.PropertyMap) *Step { 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(old))
contract.Assert(!resource.HasURN(new)) contract.Assert(!resource.HasURN(new))
return &Step{iter: iter, op: OpSame, urn: old.URN(), old: old, new: new, inputs: inputs} return &SameStep{
} iter: iter,
old: old,
func NewCreateStep(iter *PlanIterator, urn resource.URN, new *resource.Object, inputs resource.PropertyMap) *Step { new: new,
contract.Assert(!resource.HasURN(new)) inputs: inputs,
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)
} }
contract.Assert(s.new != nil)
return s.Plan().Provider(s.new)
} }
func (s *Step) Apply() (resource.Status, error) { func (s *SameStep) Op() StepOp { return OpSame }
// Fetch the provider. func (s *SameStep) Plan() *Plan { return s.iter.p }
prov, err := s.Provider() 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 nil
}
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
}
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,
}
}
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 { if err != nil {
return resource.StatusOK, err 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) id, rst, err := prov.Create(t, s.inputs)
if err != nil { if err != nil {
return rst, err return rst, err
@ -123,28 +159,118 @@ func (s *Step) Apply() (resource.Status, error) {
} }
s.outputs = outs s.outputs = outs
state := s.new.Update(s.urn, id, outs) state := s.new.Update(s.urn, id, outs)
if s.old != nil {
s.iter.MarkStateSnapshot(s.old)
}
s.iter.AppendStateSnapshot(state) s.iter.AppendStateSnapshot(state)
return resource.StatusOK, nil
}
case OpDelete, OpReplaceDelete: func (s *CreateStep) Skip() error {
// Invoke the Delete RPC function for this provider: // 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) contract.Assert(s.old != nil)
contract.Assert(s.new == 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 { if rst, err := prov.Delete(s.old.Type(), s.old.ID()); err != nil {
return rst, err return rst, err
} }
s.iter.MarkStateSnapshot(s.old) s.iter.MarkStateSnapshot(s.old)
return resource.StatusOK, nil
}
case OpUpdate: func (s *DeleteStep) Skip() error {
// Invoke the Update RPC function for this provider: // 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.old != nil)
contract.Assert(s.new != 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() t := s.old.Type()
contract.Assert(t == s.new.Type())
id := s.old.ID() id := s.old.ID()
contract.Assert(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 { if rst, err := prov.Update(t, id, s.old.Inputs(), s.inputs); err != nil {
return rst, err return rst, err
} }
@ -155,43 +281,162 @@ func (s *Step) Apply() (resource.Status, error) {
return resource.StatusUnknown, err return resource.StatusUnknown, err
} }
s.outputs = outs s.outputs = outs
state := s.new.Update(s.urn, id, outs) state := s.new.Update(s.old.URN(), id, outs)
s.iter.MarkStateSnapshot(s.old) s.iter.MarkStateSnapshot(s.old)
s.iter.AppendStateSnapshot(state) s.iter.AppendStateSnapshot(state)
default:
contract.Failf("Unexpected step operation: %v", s.op)
}
return resource.StatusOK, nil 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. func (s *UpdateStep) Skip() error {
// 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. // In the case of an update, the ID is the same, however, the outputs remain unknown.
s.new.SetURN(s.urn) s.new.SetURN(s.old.URN())
s.new.SetID(s.old.ID()) s.new.SetID(s.old.ID())
case OpReplaceCreate: return nil
// 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). // ReplaceStep is a mutating step that updates an existing resource's state.
case OpDelete, OpReplaceDelete: type ReplaceStep struct {
// In the case of a deletion, there is no state to propagate: the new object doesn't even exist. iter *PlanIterator // the current plan iteration.
default: old *resource.State // the state of the existing resource.
contract.Failf("Unexpected step operation: %v", s.op) 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,
} }
} }
// StepOp represents the kind of operation performed by this step. 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 type StepOp string
const ( const (
@ -199,8 +444,9 @@ const (
OpCreate StepOp = "create" // creating a new resource. OpCreate StepOp = "create" // creating a new resource.
OpUpdate StepOp = "update" // updating an existing resource. OpUpdate StepOp = "update" // updating an existing resource.
OpDelete StepOp = "delete" // deleting an existing resource. OpDelete StepOp = "delete" // deleting an existing resource.
OpReplaceCreate StepOp = "replace" // replacing a resource with a new one. OpReplace StepOp = "replace" // replacing a resource with a new one.
OpReplaceDelete StepOp = "replace-delete" // the fine-grained replacement step to delete the old resource. 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. // StepOps contains the full set of step operation types.
@ -209,8 +455,9 @@ var StepOps = []StepOp{
OpCreate, OpCreate,
OpUpdate, OpUpdate,
OpDelete, OpDelete,
OpReplaceCreate, OpReplace,
OpReplaceDelete, OpGet,
OpQuery,
} }
// Color returns a suggested color for lines of this op type. // Color returns a suggested color for lines of this op type.
@ -224,10 +471,10 @@ func (op StepOp) Color() string {
return colors.SpecDeleted return colors.SpecDeleted
case OpUpdate: case OpUpdate:
return colors.SpecChanged return colors.SpecChanged
case OpReplaceCreate: case OpReplace:
return colors.SpecReplaced return colors.SpecReplaced
case OpReplaceDelete: case OpGet, OpQuery:
return colors.SpecDeleted return colors.SpecRead
default: default:
contract.Failf("Unrecognized resource step op: %v", op) contract.Failf("Unrecognized resource step op: %v", op)
return "" return ""
@ -237,18 +484,16 @@ func (op StepOp) Color() string {
// Prefix returns a suggested prefix for lines of this op type. // Prefix returns a suggested prefix for lines of this op type.
func (op StepOp) Prefix() string { func (op StepOp) Prefix() string {
switch op { switch op {
case OpSame: case OpSame, OpGet, OpQuery:
return op.Color() + " " return op.Color() + " "
case OpCreate: case OpCreate:
return op.Color() + "+ " return op.Color() + "+ "
case OpDelete: case OpDelete:
return op.Color() + "- " return op.Color() + "- "
case OpUpdate: case OpUpdate:
return op.Color() + " " return op.Color() + "~ "
case OpReplaceCreate: case OpReplace:
return op.Color() + "~+" return op.Color() + "+-"
case OpReplaceDelete:
return op.Color() + "~-"
default: default:
contract.Failf("Unrecognized resource step op: %v", op) contract.Failf("Unrecognized resource step op: %v", op)
return "" return ""
@ -257,8 +502,8 @@ func (op StepOp) Prefix() string {
// Suffix returns a suggested suffix for lines of this op type. // Suffix returns a suggested suffix for lines of this op type.
func (op StepOp) Suffix() string { func (op StepOp) Suffix() string {
if op == OpUpdate || op == OpReplaceCreate { if op == OpUpdate || op == OpReplace || op == OpGet {
return colors.Reset // updates and replacements colorize individual lines return colors.Reset // updates and replacements colorize individual lines; get has none
} }
return "" return ""
} }

View file

@ -47,6 +47,14 @@ func NewObject(obj *rt.Object) *Object {
return &Object{obj: obj} 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) Obj() *rt.Object { return r.obj }
func (r *Object) Type() tokens.Type { return r.obj.Type().TypeToken() } func (r *Object) Type() tokens.Type { return r.obj.Type().TypeToken() }

View file

@ -316,6 +316,7 @@ func (g *PackGenerator) emitResourceClass(w *bufio.Writer, res *Resource) {
} else { } else {
writefmtln(w, " super();") writefmtln(w, " super();")
} }
// Next, validate that required parameters exist, and store all arguments on the object. // Next, validate that required parameters exist, and store all arguments on the object.
argLinePrefix := " " argLinePrefix := " "
needsArgsCheck := hasArgs && !hasRequiredArgs needsArgsCheck := hasArgs && !hasRequiredArgs
@ -338,6 +339,17 @@ func (g *PackGenerator) emitResourceClass(w *bufio.Writer, res *Resource) {
} }
writefmtln(w, " }") 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 <any>undefined; // functionality provided by the runtime")
writefmtln(w, " }")
writefmtln(w, "")
writefmtln(w, " public static query(q: any): %v[] {", name)
writefmtln(w, " return <any>undefined; // functionality provided by the runtime")
writefmtln(w, " }")
writefmtln(w, "}") writefmtln(w, "}")
} }