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

View file

@ -21,6 +21,7 @@ import (
"sort"
"strconv"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/pulumi/lumi/pkg/diag"
@ -38,7 +39,8 @@ func newPlanCmd() *cobra.Command {
var dotOutput bool
var env string
var showConfig bool
var showReplaceSteps bool
var showReads bool
var showReplaceDeletes bool
var showSames bool
var summary bool
var cmd = &cobra.Command{
@ -62,14 +64,15 @@ func newPlanCmd() *cobra.Command {
}
contract.Assertf(!dotOutput, "TODO[pulumi/lumi#235]: DOT files not yet supported")
opts := deployOptions{
Delete: false,
DryRun: true,
Analyzers: analyzers,
ShowConfig: showConfig,
ShowReplaceSteps: showReplaceSteps,
ShowSames: showSames,
Summary: summary,
DOT: dotOutput,
Delete: false,
DryRun: true,
Analyzers: analyzers,
ShowConfig: showConfig,
ShowReads: showReads,
ShowReplaceDeletes: showReplaceDeletes,
ShowSames: showSames,
Summary: summary,
DOT: dotOutput,
}
if result := plan(cmd, info, opts); result != nil {
if err := printPlan(result, opts); err != nil {
@ -93,7 +96,10 @@ func newPlanCmd() *cobra.Command {
&showConfig, "show-config", false,
"Show configuration keys and variables")
cmd.PersistentFlags().BoolVar(
&showReplaceSteps, "show-replace-steps", false,
&showReads, "show-reads", false,
"Show resources that will be read, in addition to those that will be modified")
cmd.PersistentFlags().BoolVar(
&showReplaceDeletes, "show-replace-deletes", false,
"Show detailed resource replacement creates and deletes; normally shows as a single step")
cmd.PersistentFlags().BoolVar(
&showSames, "show-sames", false,
@ -165,41 +171,44 @@ func printPlan(result *planResult, opts deployOptions) error {
iter, err := result.Plan.Iterate()
if err != nil {
cmdutil.Diag().Errorf(diag.Message("An error occurred while preparing the plan: %v"), err)
return err
return errors.Errorf("An error occurred while preparing the plan: %v", err)
}
step, err := iter.Next()
if err != nil {
cmdutil.Diag().Errorf(diag.Message("An error occurred while enumerating the plan: %v"), err)
return err
return errors.Errorf("An error occurred while enumerating the plan: %v", err)
}
var summary bytes.Buffer
empty := true
counts := make(map[deploy.StepOp]int)
for step != nil {
op := step.Op()
if empty && op != deploy.OpSame {
empty = false
var err error
// Perform the pre-step.
if err = step.Pre(); err != nil {
return errors.Errorf("An error occurred preparing the plan: %v", err)
}
// Print this step information (resource and all its properties).
// IDEA: it would be nice if, in the output, we showed the dependencies a la `git log --graph`.
if (opts.ShowReplaceSteps || op != deploy.OpReplaceDelete) &&
(opts.ShowSames || op != deploy.OpSame) {
track := shouldTrack(step, opts)
if track {
printStep(&summary, step, opts.Summary, true, "")
empty = false
}
// Be sure to skip the step so that in-memory state updates are performed.
step.Skip()
if err = step.Skip(); err != nil {
return errors.Errorf("An error occurred while advancing the plan: %v", err)
}
counts[step.Op()]++
if track {
counts[step.Op()]++
}
var err error
if step, err = iter.Next(); err != nil {
cmdutil.Diag().Errorf(diag.Message("An error occurred while viewing the plan: %v"), err)
return err
return errors.Errorf("An error occurred while viewing the plan: %v", err)
}
}
@ -208,12 +217,27 @@ func printPlan(result *planResult, opts deployOptions) error {
cmdutil.Diag().Infof(diag.Message("no resources need to be updated"))
} else {
// Print a summary of operation counts.
printSummary(&summary, counts, opts.ShowReplaceSteps, true)
printSummary(&summary, counts, true)
fmt.Print(colors.Colorize(&summary))
}
return nil
}
// shouldTrack returns true if the step should be "tracked"; this affects two things: 1) whether the resource is shown
// in the planning phase and 2) whether the resource operation is tallied up and displayed in the final summary.
func shouldTrack(step deploy.Step, opts deployOptions) bool {
// For certain operations, whether they are tracked is controlled by flags (to cut down on superfluous output).
if _, isrd := step.(deploy.ReadStep); isrd {
return opts.ShowReads
} else if step.Op() == deploy.OpSame {
return opts.ShowSames
} else if step.Op() == deploy.OpDelete && step.(*deploy.DeleteStep).Replaced() {
return opts.ShowReplaceDeletes
}
// By default, however, steps are tracked.
return true
}
func printPrelude(b *bytes.Buffer, result *planResult, opts deployOptions, planning bool) {
// If there are configuration variables, show them.
if opts.ShowConfig {
@ -235,12 +259,9 @@ func printConfig(b *bytes.Buffer, config resource.ConfigMap) {
}
}
func printSummary(b *bytes.Buffer, counts map[deploy.StepOp]int, showReplaceSteps bool, plan bool) {
func printSummary(b *bytes.Buffer, counts map[deploy.StepOp]int, plan bool) {
total := 0
for op, c := range counts {
if op == deploy.OpSame || (!showReplaceSteps && op == deploy.OpReplaceDelete) {
continue // skip counting replacement steps unless explicitly requested.
}
for _, c := range counts {
total += c
}
@ -263,10 +284,6 @@ func printSummary(b *bytes.Buffer, counts map[deploy.StepOp]int, showReplaceStep
}
for _, op := range deploy.StepOps {
if op == deploy.OpSame || (!showReplaceSteps && op == deploy.OpReplaceDelete) {
// Unless the user requested it, don't show the fine-grained replacement steps; just the logical ones.
continue
}
if c := counts[op]; c > 0 {
b.WriteString(fmt.Sprintf(" %v%v %v %v%v%v%v\n",
op.Prefix(), c, plural("resource", c), planTo, op, pastTense, colors.Reset))
@ -288,40 +305,51 @@ func printUnchanged(b *bytes.Buffer, stats deploy.PlanSummary, summary bool, pla
for _, res := range stats.Resources() {
if stats.Sames()[res.URN()] {
b.WriteString(" ") // simulate the 2 spaces for +, -, etc.
printResourceHeader(b, res, nil, "")
printResourceHeader(b, res)
printResourceProperties(b, res.URN(), res, nil, nil, nil, summary, planning, "")
}
}
}
func printStep(b *bytes.Buffer, step *deploy.Step, summary bool, planning bool, indent string) {
func printStep(b *bytes.Buffer, step deploy.Step, summary bool, planning bool, indent string) {
// First print out the operation's prefix.
b.WriteString(step.Op().Prefix())
// Next print the resource URN, properties, etc.
printResourceHeader(b, step.Old(), step.New(), indent)
// Next, print the resource type (since it is easy on the eyes and can be quickly identified).
printStepHeader(b, step)
b.WriteString(step.Op().Suffix())
printResourceProperties(b, step.URN(), step.Old(), step.New(), step.Inputs(), step.Reasons(),
summary, planning, indent)
// Next print the resource URN, properties, etc.
if mut, ismut := step.(deploy.MutatingStep); ismut {
var replaces []resource.PropertyKey
if step.Op() == deploy.OpReplace {
replaces = step.(*deploy.ReplaceStep).Reasons()
}
printResourceProperties(b,
mut.URN(), mut.Old(), mut.New(), mut.Inputs(), replaces, summary, planning, indent)
} else if rd, isrd := step.(deploy.ReadStep); isrd {
for _, res := range rd.Resources() {
printResourceProperties(b,
"", nil, res, res.CopyProperties(), nil, summary, planning, indent)
}
} else {
contract.Failf("Expected each step to either be mutating or read-only")
}
// Finally make sure to reset the color.
b.WriteString(colors.Reset)
}
func printResourceHeader(b *bytes.Buffer, old *resource.State, new *resource.Object, indent string) {
var t tokens.Type
if old != nil {
t = old.Type()
} else {
t = new.Type()
}
func printStepHeader(b *bytes.Buffer, step deploy.Step) {
b.WriteString(fmt.Sprintf("%s:\n", string(step.Type())))
}
// The primary header is the resource type (since it is easy on the eyes).
b.WriteString(fmt.Sprintf("%s:\n", string(t)))
func printResourceHeader(b *bytes.Buffer, res resource.Resource) {
b.WriteString(fmt.Sprintf("%s:\n", string(res.Type())))
}
func printResourceProperties(b *bytes.Buffer, urn resource.URN, old *resource.State, new *resource.Object,
inputs resource.PropertyMap, replaces []resource.PropertyKey, summary bool, planning bool, indent string) {
props resource.PropertyMap, replaces []resource.PropertyKey, summary bool, planning bool, indent string) {
indent += detailsIndent
// Print out the URN and, if present, the ID, as "pseudo-properties".
@ -332,17 +360,19 @@ func printResourceProperties(b *bytes.Buffer, urn resource.URN, old *resource.St
if id != "" {
b.WriteString(fmt.Sprintf("%s[id=%s]\n", indent, string(id)))
}
b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, urn))
if urn != "" {
b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, urn))
}
if !summary {
// Print all of the properties associated with this resource.
if old == nil && new != nil {
printObject(b, inputs, planning, indent)
printObject(b, props, planning, indent)
} else if new == nil && old != nil {
printObject(b, old.Inputs(), planning, indent)
} else {
contract.Assert(inputs != nil) // use computed properties for diffs.
printOldNewDiffs(b, old.Inputs(), inputs, replaces, planning, indent)
contract.Assert(props != nil) // use computed properties for diffs.
printOldNewDiffs(b, old.Inputs(), props, replaces, planning, indent)
}
}
}
@ -373,16 +403,22 @@ func printObject(b *bytes.Buffer, props resource.PropertyMap, planning bool, ind
// printResourceOutputProperties prints only those properties that either differ from the input properties or, if
// there is an old snapshot of the resource, differ from the prior old snapshot's output properties.
func printResourceOutputProperties(b *bytes.Buffer, step *deploy.Step, indent string) {
func printResourceOutputProperties(b *bytes.Buffer, step deploy.Step, indent string) {
mut, ismut := step.(deploy.MutatingStep)
if !ismut {
// Only mutating steps have output properties associated with them.
return
}
indent += detailsIndent
b.WriteString(step.Op().Color())
b.WriteString(step.Op().Suffix())
// First fetch all the relevant property maps that we may consult.
newins := step.Inputs()
newouts := step.Outputs()
newins := mut.Inputs()
newouts := mut.Outputs()
var oldouts resource.PropertyMap
if old := step.Old(); old != nil {
if old := mut.Old(); old != nil {
oldouts = old.Outputs()
}
@ -543,12 +579,12 @@ func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.V
titleFunc := func(id string) { printArrayElemHeader(b, i, id) }
if add, isadd := a.Adds[i]; isadd {
b.WriteString(deploy.OpCreate.Color())
title(addIndent(indent))
titleFunc(addIndent(indent))
printPropertyValue(b, add, planning, addIndent(newIndent))
b.WriteString(colors.Reset)
} else if delete, isdelete := a.Deletes[i]; isdelete {
b.WriteString(deploy.OpDelete.Color())
title(deleteIndent(indent))
titleFunc(deleteIndent(indent))
printPropertyValue(b, delete, planning, deleteIndent(newIndent))
b.WriteString(colors.Reset)
} else if update, isupdate := a.Updates[i]; isupdate {

View file

@ -15,6 +15,14 @@ export class Account extends lumi.NamedResource implements AccountArgs {
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 {

View file

@ -22,6 +22,14 @@ export class APIKey extends lumi.NamedResource implements APIKeyArgs {
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 {

View file

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

View file

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

View file

@ -13,6 +13,14 @@ export class ClientCertificate extends lumi.NamedResource implements ClientCerti
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 {

View file

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

View file

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

View file

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

View file

@ -26,6 +26,14 @@ export class Resource extends lumi.NamedResource implements ResourceArgs {
}
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 {

View file

@ -32,6 +32,14 @@ export class RestAPI extends lumi.NamedResource implements RestAPIArgs {
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 {

View file

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

View file

@ -49,6 +49,14 @@ export class UsagePlan extends lumi.NamedResource implements UsagePlanArgs {
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 {

View file

@ -22,6 +22,14 @@ export class UsagePlanKey extends lumi.NamedResource implements UsagePlanKeyArgs
}
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 {

View file

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

View file

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

View file

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

View file

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

View file

@ -33,6 +33,14 @@ export class Route extends lumi.NamedResource implements RouteArgs {
}
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 {

View file

@ -16,6 +16,14 @@ export class RouteTable extends lumi.NamedResource implements RouteTableArgs {
}
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 {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,6 +22,14 @@ export class VPCGatewayAttachment extends lumi.NamedResource implements VPCGatew
}
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 {

View file

@ -21,6 +21,14 @@ export class VPCPeeringConnection extends lumi.NamedResource implements VPCPeeri
}
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 {

View file

@ -15,6 +15,14 @@ export class Application extends lumi.NamedResource implements ApplicationArgs {
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 {

View file

@ -26,6 +26,14 @@ export class ApplicationVersion extends lumi.NamedResource implements Applicatio
}
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 {

View file

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

View file

@ -21,6 +21,14 @@ export class Group extends lumi.NamedResource implements GroupArgs {
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 {

View file

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

View file

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

View file

@ -31,6 +31,14 @@ export class User extends lumi.NamedResource implements UserArgs {
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 {

View file

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

View file

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

View file

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

View file

@ -17,6 +17,14 @@ export class Bucket extends lumi.NamedResource implements BucketArgs {
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 {

View file

@ -26,6 +26,14 @@ export class Object extends lumi.Resource implements ObjectArgs {
}
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 {

View file

@ -26,6 +26,14 @@ export class Topic extends lumi.NamedResource implements TopicArgs {
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 {

View file

@ -29,6 +29,14 @@ export class Queue extends lumi.NamedResource implements QueueArgs {
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 {

View file

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

View file

@ -81,6 +81,7 @@ var (
SpecAdded = Green // for adds (in the diff sense).
SpecChanged = BrightYellow // for changes (in the diff sense).
SpecRead = BrightWhite // for reads (relatively unimportant).
SpecReplaced = Yellow // for replacements (in the diff sense).
SpecDeleted = Red // for deletes (in the diff sense).
)

View file

@ -701,13 +701,6 @@ func (e *evaluator) evalCall(node diag.Diagable,
e.pushScope(frame, !shareActivation)
defer e.popScope(frame)
// Invoke the hooks if available.
if e.hooks != nil {
if leave := e.hooks.OnEnterFunction(sym); leave != nil {
defer leave()
}
}
// If the target is an instance method, the "this" and "super" variables must be bound to values.
if thisVariable != nil {
contract.Assert(this != nil)
@ -740,14 +733,26 @@ func (e *evaluator) evalCall(node diag.Diagable,
}
}
// Now perform the invocation; for intrinsics, just run the code; for all others, interpret the body.
var uw *rt.Unwind
if intrinsic {
isym := sym.(*rt.Intrinsic)
invoker := GetIntrinsicInvoker(isym)
uw = invoker(isym, e, this, args)
} else {
uw = e.evalStatement(fnc.GetBody())
// Invoke the hooks if available.
if e.hooks != nil {
var leave func()
if uw, leave = e.hooks.OnEnterFunction(sym, args); leave != nil {
defer leave()
}
}
// Assuming the hook didn't perform its own logic, we can now perform the real invocation; for intrinsics, just run
// the code; for all others, interpret the body.
if uw == nil {
if intrinsic {
isym := sym.(*rt.Intrinsic)
invoker := GetIntrinsicInvoker(isym)
uw = invoker(isym, e, this, args)
} else {
uw = e.evalStatement(fnc.GetBody())
}
}
// Check that the unwind is as expected. In particular:

View file

@ -30,7 +30,7 @@ type Hooks interface {
// OnEnterModule is invoked whenever we enter a module.
OnEnterModule(sym *symbols.Module) func()
// OnEnterFunction is invoked whenever we enter a function.
OnEnterFunction(fnc symbols.Function) func()
OnEnterFunction(fnc symbols.Function, args []*rt.Object) (*rt.Unwind, func())
// OnObjectInit is invoked after an object has been allocated and initialized. This means that its constructor, if
// any, has been run to completion. The diagnostics tree is the AST node responsible for the allocation.
OnObjectInit(tree diag.Diagable, o *rt.Object)

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
// could not be found, or an error occurred while creating it, a non-nil error is returned.
func (p *Plan) Provider(res resource.Resource) (plugin.Provider, error) {
t := res.Type()
return p.ProviderT(res.Type())
}
// ProviderT fetches the provider for a given resource type, possibly lazily allocating the plugins for it. If a
// provider could not be found, or an error occurred while creating it, a non-nil error is returned.
func (p *Plan) ProviderT(t tokens.Type) (plugin.Provider, error) {
pkg := t.Package()
return p.ctx.Host.Provider(pkg)
}

View file

@ -22,7 +22,6 @@ import (
"github.com/pulumi/lumi/pkg/compiler/errors"
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/resource/plugin"
"github.com/pulumi/lumi/pkg/tokens"
"github.com/pulumi/lumi/pkg/util/contract"
)
@ -31,7 +30,7 @@ import (
// Apply performs all steps in the plan, calling out to the progress reporting functions as desired. It returns four
// things: the resulting Snapshot, no matter whether an error occurs or not; an error, if something went wrong; the step
// that failed, if the error is non-nil; and finally the state of the resource modified in the failing step.
func (p *Plan) Apply(prog Progress) (PlanSummary, *Step, resource.Status, error) {
func (p *Plan) Apply(prog Progress) (PlanSummary, Step, resource.Status, error) {
// Fetch a plan iterator and keep walking it until we are done.
iter, err := p.Iterate()
if err != nil {
@ -43,12 +42,18 @@ func (p *Plan) Apply(prog Progress) (PlanSummary, *Step, resource.Status, error)
return nil, nil, resource.StatusOK, err
}
for step != nil {
// Do the pre-step.
rst := resource.StatusOK
err := step.Pre()
// Perform pre-application progress reporting.
if prog != nil {
prog.Before(step)
}
rst, err := step.Apply()
if err == nil {
rst, err = step.Apply()
}
// Perform post-application progress reporting.
if prog != nil {
@ -135,33 +140,43 @@ func (iter *PlanIterator) Resources() []*resource.State { return iter.resourc
func (iter *PlanIterator) Dones() map[*resource.State]bool { return iter.dones }
func (iter *PlanIterator) Done() bool { return iter.done }
// Produce is used to indicate that a new resource state has been read from a live environment.
func (iter *PlanIterator) Produce(res *resource.Object) {
iter.src.Produce(res)
}
// Next advances the plan by a single step, and returns the next step to be performed. In doing so, it will perform
// evaluation of the program as much as necessary to determine the next step. If there is no further action to be
// taken, Next will return a nil step pointer.
func (iter *PlanIterator) Next() (*Step, error) {
func (iter *PlanIterator) Next() (Step, error) {
for !iter.done {
if !iter.srcdone {
obj, ctx, err := iter.src.Next()
res, q, err := iter.src.Next()
if err != nil {
return nil, err
} else if obj == nil {
// If the source is done, note it, and don't go back for more.
iter.srcdone = true
iter.delqueue = iter.calculateDeletes()
} else {
step, err := iter.nextResource(obj, ctx)
} else if res != nil {
step, err := iter.nextResourceStep(res)
if err != nil {
return nil, err
} else if step != nil {
return step, nil
}
// If the step returned was nil, this resource is fine, so we'll keep on going.
continue
contract.Assert(step != nil)
return step, nil
} else if q != nil {
step, err := iter.nextQueryStep(q)
if err != nil {
return nil, err
}
contract.Assert(step != nil)
return step, nil
}
// If all returns are nil, the source is done, note it, and don't go back for more. Add any deletions to be
// performed, and then keep going 'round the next iteration of the loop so we can wrap up the planning.
iter.srcdone = true
iter.delqueue = iter.calculateDeletes()
} else {
// The interpreter has finished, so we need to now drain any deletions that piled up.
if step := iter.nextDelete(); step != nil {
if step := iter.nextDeleteStep(); step != nil {
return step, nil
}
@ -173,9 +188,10 @@ func (iter *PlanIterator) Next() (*Step, error) {
return nil, nil
}
// nextResource produces a new step for a given resource or nil if there isn't one to perform.
func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module) (*Step, error) {
// nextResourceStep produces a new step for a given resource or nil if there isn't one to perform.
func (iter *PlanIterator) nextResourceStep(res *SourceAllocation) (Step, error) {
// Take a moment in time snapshot of the live object's properties.
new := res.Obj
t := new.Type()
inputs := new.CopyProperties()
@ -190,7 +206,7 @@ func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module)
if err != nil {
return nil, err
}
urn := resource.NewURN(iter.p.Target().Name, ctx, t, name)
urn := resource.NewURN(iter.p.Target().Name, res.Ctx, t, name)
// First ensure the provider is okay with this resource.
var invalid bool
@ -259,7 +275,7 @@ func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module)
} else if len(replacements) > 0 {
iter.replaces[urn] = true
glog.V(7).Infof("Planner decided to replace '%v' (oldprops=%v inputs=%v)", urn, oldprops, inputs)
return NewReplaceCreateStep(iter, old, new, inputs, replacements), nil
return NewReplaceStep(iter, old, new, inputs, replacements), nil
}
iter.updates[urn] = true
@ -273,8 +289,18 @@ func (iter *PlanIterator) nextResource(new *resource.Object, ctx tokens.Module)
return NewCreateStep(iter, urn, new, inputs), nil
}
// nextDelete produces a new step that deletes a resource if necessary.
func (iter *PlanIterator) nextDelete() *Step {
// nextQueryStep produces a new query step that looks up a resource in some manner.
func (iter *PlanIterator) nextQueryStep(q *SourceQuery) (Step, error) {
if id := q.GetID; id != "" {
return NewGetStep(iter, q.Type, id, nil), nil
}
contract.Assert(q.QueryFilter != nil)
contract.Failf("TODO[pulumi/lumi#83]: querying not yet supported")
return nil, nil
}
// nextDeleteStep produces a new step that deletes a resource if necessary.
func (iter *PlanIterator) nextDeleteStep() Step {
if len(iter.delqueue) > 0 {
del := iter.delqueue[0]
iter.delqueue = iter.delqueue[1:]
@ -282,10 +308,10 @@ func (iter *PlanIterator) nextDelete() *Step {
iter.deletes[urn] = true
if iter.replaces[urn] {
glog.V(7).Infof("Planner decided to delete '%v' due to replacement", urn)
return NewReplaceDeleteStep(iter, del)
} else {
glog.V(7).Infof("Planner decided to delete '%v'", urn)
}
glog.V(7).Infof("Planner decided to delete '%v'", urn)
return NewDeleteStep(iter, del)
return NewDeleteStep(iter, del, iter.replaces[urn])
}
return nil
}

View file

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

View file

@ -22,7 +22,7 @@ import (
// Progress can be used for progress reporting.
type Progress interface {
// Before is invoked prior to a step executing.
Before(step *Step)
Before(step Step)
// After is invoked after a step executes, and is given access to the error, if any, that occurred.
After(step *Step, state resource.Status, err error)
After(step Step, state resource.Status, err error)
}

View file

@ -18,6 +18,7 @@ package deploy
import (
"io"
"github.com/pulumi/lumi/pkg/compiler/symbols"
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/tokens"
)
@ -34,8 +35,23 @@ type Source interface {
// A SourceIterator enumerates the list of resources that a source has to offer.
type SourceIterator interface {
io.Closer
// Next returns the next resource object plus a token context (usually where it was allocated); the token is used in
// the production of the ensuing resource's URN. If something went wrong, error is non-nil. If both error and the
// resource are nil, then the iterator has completed its job and no subsequent calls to next should be made.
Next() (*resource.Object, tokens.Module, error)
// Produce registers a resource that was produced during the iteration, to publish next time.
Produce(res *resource.Object)
// Next returns the next step from the source. If the source allocation is non-nil, it represents the creation of
// a resource object; if query is non-nil, it represents querying the resources; if both error and the other
// objects are nil, then the iterator has completed its job and no subsequent calls to next should be made.
Next() (*SourceAllocation, *SourceQuery, error)
}
// SourceAllocation is used when a resource object is allocated.
type SourceAllocation struct {
Obj *resource.Object // the resource object.
Ctx tokens.Module // the context in which the resource was allocated, used in the production of URNs.
}
// SourceQuery is used when a query function is to be performed.
type SourceQuery struct {
Type symbols.Type // the type of resource being queried.
GetID resource.ID // the resource ID to get (for gets only).
QueryFilter resource.PropertyMap // the query's filter (for queries only).
}

View file

@ -23,6 +23,7 @@ import (
"github.com/pulumi/lumi/pkg/compiler/core"
"github.com/pulumi/lumi/pkg/compiler/errors"
"github.com/pulumi/lumi/pkg/compiler/symbols"
"github.com/pulumi/lumi/pkg/compiler/types/predef"
"github.com/pulumi/lumi/pkg/diag"
"github.com/pulumi/lumi/pkg/eval"
"github.com/pulumi/lumi/pkg/eval/rt"
@ -100,6 +101,7 @@ func (src *evalSource) Iterate() (SourceIterator, error) {
type evalSourceIterator struct {
src *evalSource // the owning eval source object.
e eval.Interpreter // the interpreter used to compute the new state.
res *resource.Object // a resource to publish during the next rendezvous.
rz *rendezvous.Rendezvous // the rendezvous where planning and evaluator coroutines meet.
}
@ -109,21 +111,57 @@ func (iter *evalSourceIterator) Close() error {
return nil
}
func (iter *evalSourceIterator) Next() (*resource.Object, tokens.Module, error) {
func (iter *evalSourceIterator) Produce(res *resource.Object) {
iter.res = res
}
func (iter *evalSourceIterator) Next() (*SourceAllocation, *SourceQuery, error) {
// Kick the interpreter to compute some more and then inspect what it has to say.
obj, done, err := iter.rz.Meet(planParty, nil)
var data interface{}
if res := iter.res; res != nil {
data = rt.NewReturnUnwind(res.Obj())
iter.res = nil // reset the state so we don't return things more than once.
}
obj, done, err := iter.rz.Meet(planParty, data)
if err != nil {
return nil, "", err
return nil, nil, err
} else if done {
glog.V(5).Infof("EvalSourceIterator is done")
return nil, "", nil
return nil, nil, nil
}
contract.Assert(obj != nil)
// See what the interpreter came up with. It's either an allocation or a query operation.
if alloc, isalloc := obj.(*AllocRendezvous); isalloc {
glog.V(5).Infof("EvalSourceIterator produced a new object: obj=%v, ctx=%v", alloc.Obj, alloc.Mod.Tok)
return &SourceAllocation{
Obj: resource.NewObject(alloc.Obj),
Ctx: alloc.Mod.Tok,
}, nil, nil
} else if query, isquery := obj.(*QueryRendezvous); isquery {
glog.V(5).Infof("EvalSourceIterator produced a new query: fnc=%v, #args=%v", query.Meth, len(query.Args))
meth := query.Meth
args := query.Args
t := meth.Parent
switch meth.Name() {
case specialResourceGetFunction:
if len(args) == 0 {
return nil, nil,
goerr.Errorf("Missing required argument 'id' for method %v", meth)
} else if !args[0].IsString() {
return nil, nil,
goerr.Errorf("Expected method %v argument 'id' to be a string; got", meth, args[0])
}
return nil, &SourceQuery{Type: t, GetID: resource.ID(args[0].StringValue())}, nil
case specialResourceQueryFunction:
contract.Failf("TODO[pulumi/lumi#83]: query not yet implemented")
default:
contract.Failf("Unrecognized query rendezvous function name: %v", meth.Name())
}
}
// Otherwise, transform the object returned into a resource object that the planner can deal with.
contract.Assert(obj != nil)
info := obj.(*AllocInfo)
glog.V(5).Infof("EvalSourceIterator produced a new object: obj=%v, ctx=%v", info.Obj, info.Mod.Tok)
return resource.NewObject(info.Obj), info.Mod.Tok, nil
contract.Failf("Unexpected rendezvous object: %v (expected alloc or query)", obj)
return nil, nil, nil
}
// InitEvalConfig applies the configuration map to an existing interpreter context. The map is simply a map of tokens --
@ -198,8 +236,8 @@ func forkEval(src *evalSource, rz *rendezvous.Rendezvous, e eval.Interpreter) er
return nil
}
// AllocInfo is the context in which an object got allocated.
type AllocInfo struct {
// AllocRendezvous is used when an object is allocated, and tracks the context in which it was allocated.
type AllocRendezvous struct {
Obj *rt.Object // the object itself.
Loc diag.Diagable // the location information for the allocation.
Pkg *symbols.Package // the package being evaluated when the allocation happened.
@ -207,6 +245,12 @@ type AllocInfo struct {
Fnc symbols.Function // the function being evaluated when the allocation happened.
}
// QueryRendezvous is used when the interpreter hits a query routine that needs to be evaluated by the planner.
type QueryRendezvous struct {
Meth *symbols.ClassMethod // the resource method that triggered the need to rendezvous.
Args []*rt.Object // the arguments supplied, if any.
}
// evalHooks are the interpreter hooks that synchronize between planner and evaluator in the appropriate ways.
type evalHooks struct {
rz *rendezvous.Rendezvous // the rendezvous object.
@ -240,7 +284,7 @@ func (h *evalHooks) OnObjectInit(tree diag.Diagable, obj *rt.Object) {
glog.V(9).Infof("EvalSource OnObjectInit %v (IsResource=%v)", obj, resource.IsResourceObject(obj))
if resource.IsResourceObject(obj) {
// Communicate the full allocation context: AST node, package, module, and function.
alloc := &AllocInfo{
alloc := &AllocRendezvous{
Obj: obj,
Loc: tree,
Pkg: h.currpkg,
@ -276,12 +320,39 @@ func (h *evalHooks) OnEnterModule(mod *symbols.Module) func() {
}
}
// OnEnterFunction is invoked whenever we enter a new function.
func (h *evalHooks) OnEnterFunction(fnc symbols.Function) func() {
const (
specialResourceGetFunction = "get" // gets a single resource by ID.
specialResourceQueryFunction = "query" // queries 0-to-many resources using arbitrary filters.
)
// OnEnterFunction is invoked whenever we enter a new function. If it returns a non-nil unwind object, it will be used
// in place of the actual function call, effectively monkey patching it on the fly.
func (h *evalHooks) OnEnterFunction(fnc symbols.Function, args []*rt.Object) (*rt.Unwind, func()) {
glog.V(9).Infof("EvalSource OnEnterFunction %v", fnc)
prevfnc := h.currfnc
h.currfnc = fnc
return func() {
// If this is one of the "special" resource functions, we need to essentially monkey patch it on the fly.
var uw *rt.Unwind
if meth, ismeth := fnc.(*symbols.ClassMethod); ismeth {
if predef.IsResourceType(meth.Parent) {
switch meth.Name() {
case specialResourceGetFunction, specialResourceQueryFunction:
// For any of these functions, we must defer to the planning side to do its thing. After awaiting our
// turn, we will be given an opportunity to resume with the object and/or unwind in hand.
ret, done, err := h.rz.Meet(evalParty, &QueryRendezvous{
Meth: meth,
Args: args,
})
contract.Assertf(ret != nil, "Expecting unwind instructions from the planning goroutine")
uw = ret.(*rt.Unwind)
contract.Assert(!done)
contract.Assert(err == nil)
}
}
}
return uw, func() {
glog.V(9).Infof("EvalSource OnLeaveFunction %v", fnc)
h.currfnc = prevfnc
}

View file

@ -56,10 +56,17 @@ func (iter *fixedSourceIterator) Close() error {
return nil // nothing to do.
}
func (iter *fixedSourceIterator) Next() (*resource.Object, tokens.Module, error) {
func (iter *fixedSourceIterator) Produce(res *resource.Object) {
// ignore
}
func (iter *fixedSourceIterator) Next() (*SourceAllocation, *SourceQuery, error) {
iter.current++
if iter.current >= len(iter.src.resources) {
return nil, "", nil
return nil, nil, nil
}
return iter.src.resources[iter.current], iter.src.ctx, nil
return &SourceAllocation{
Obj: iter.src.resources[iter.current],
Ctx: iter.src.ctx,
}, nil, nil
}

View file

@ -17,7 +17,6 @@ package deploy
import (
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/tokens"
)
// NullSource is a singleton source that never returns any resources. This may be used in scenarios where the "new"
@ -48,6 +47,10 @@ func (iter *nullSourceIterator) Close() error {
return nil // nothing to do.
}
func (iter *nullSourceIterator) Next() (*resource.Object, tokens.Module, error) {
return nil, "", nil // means "done"
func (iter *nullSourceIterator) Produce(res *resource.Object) {
// ignore
}
func (iter *nullSourceIterator) Next() (*SourceAllocation, *SourceQuery, error) {
return nil, nil, nil // means "done"
}

View file

@ -16,191 +16,437 @@
package deploy
import (
"github.com/pulumi/lumi/pkg/compiler/symbols"
"github.com/pulumi/lumi/pkg/diag/colors"
"github.com/pulumi/lumi/pkg/resource"
"github.com/pulumi/lumi/pkg/resource/plugin"
"github.com/pulumi/lumi/pkg/tokens"
"github.com/pulumi/lumi/pkg/util/contract"
)
// Step is a specification for a deployment operation.
type Step struct {
iter *PlanIterator // the current plan iteration.
op StepOp // the operation that will be performed.
urn resource.URN // the resource URN (for before and after).
old *resource.State // the state of the resource before this step.
new *resource.Object // the state of the resource after this step.
inputs resource.PropertyMap // the input properties to use during the operation.
outputs resource.PropertyMap // the output properties calculated after the operation.
reasons []resource.PropertyKey // the reasons for replacement, if applicable.
type Step interface {
Op() StepOp // the operation performed by this step.
Plan() *Plan // the owning plan.
Iterator() *PlanIterator // the current plan iterator.
Type() tokens.Type // the type affected by this step.
Pre() error // run any pre-execution steps.
Apply() (resource.Status, error) // applies the action that this step represents.
Skip() error // skips past this step (required when iterating a plan).
}
func NewSameStep(iter *PlanIterator, old *resource.State, new *resource.Object, inputs resource.PropertyMap) *Step {
// ReadStep is a step that doesn't actually modify the target environment. It only reads/queries from it.
type ReadStep interface {
Step
Resources() []*resource.Object // all resource objects returned by this step.
}
// MutatingStep is a step that, when performed, will actually modify/mutate the target environment and its resources.
type MutatingStep interface {
Step
URN() resource.URN // the resource URN (for before and after).
New() *resource.Object // the state of the resource before performing this step.
Old() *resource.State // the state of the resource after performing this step.
Inputs() resource.PropertyMap // the input properties to use during the operation.
Outputs() resource.PropertyMap // the output properties calculated during the operation.
}
// SameStep is a mutating step that does nothing.
type SameStep struct {
iter *PlanIterator // the current plan iteration.
old *resource.State // the state of the resource before this step.
new *resource.Object // the state of the resource after this step.
inputs resource.PropertyMap // the computed inputs supplied at creation time.
}
var _ MutatingStep = (*SameStep)(nil)
func NewSameStep(iter *PlanIterator, old *resource.State, new *resource.Object, inputs resource.PropertyMap) Step {
contract.Assert(resource.HasURN(old))
contract.Assert(!resource.HasURN(new))
return &Step{iter: iter, op: OpSame, urn: old.URN(), old: old, new: new, inputs: inputs}
}
func NewCreateStep(iter *PlanIterator, urn resource.URN, new *resource.Object, inputs resource.PropertyMap) *Step {
contract.Assert(!resource.HasURN(new))
return &Step{iter: iter, op: OpCreate, urn: urn, new: new, inputs: inputs}
}
func NewDeleteStep(iter *PlanIterator, old *resource.State) *Step {
contract.Assert(resource.HasURN(old))
return &Step{iter: iter, op: OpDelete, urn: old.URN(), old: old}
}
func NewUpdateStep(iter *PlanIterator, old *resource.State,
new *resource.Object, inputs resource.PropertyMap) *Step {
contract.Assert(resource.HasURN(old))
contract.Assert(!resource.HasURN(new))
return &Step{iter: iter, op: OpUpdate, urn: old.URN(), old: old, new: new, inputs: inputs}
}
func NewReplaceCreateStep(iter *PlanIterator, old *resource.State,
new *resource.Object, inputs resource.PropertyMap, reasons []resource.PropertyKey) *Step {
contract.Assert(resource.HasURN(old))
contract.Assert(!resource.HasURN(new))
return &Step{iter: iter, op: OpReplaceCreate, urn: old.URN(), old: old, new: new, inputs: inputs, reasons: reasons}
}
func NewReplaceDeleteStep(iter *PlanIterator, old *resource.State) *Step {
contract.Assert(resource.HasURN(old))
return &Step{iter: iter, op: OpReplaceDelete, urn: old.URN(), old: old}
}
func (s *Step) Plan() *Plan { return s.iter.p }
func (s *Step) Iterator() *PlanIterator { return s.iter }
func (s *Step) Op() StepOp { return s.op }
func (s *Step) URN() resource.URN { return s.urn }
func (s *Step) Old() *resource.State { return s.old }
func (s *Step) New() *resource.Object { return s.new }
func (s *Step) Inputs() resource.PropertyMap { return s.inputs }
func (s *Step) Outputs() resource.PropertyMap { return s.outputs }
func (s *Step) Reasons() []resource.PropertyKey { return s.reasons }
func (s *Step) Provider() (plugin.Provider, error) {
contract.Assert(s.old == nil || s.new == nil || s.old.Type() == s.new.Type())
if s.old != nil {
return s.Plan().Provider(s.old)
return &SameStep{
iter: iter,
old: old,
new: new,
inputs: inputs,
}
}
func (s *SameStep) Op() StepOp { return OpSame }
func (s *SameStep) Plan() *Plan { return s.iter.p }
func (s *SameStep) Iterator() *PlanIterator { return s.iter }
func (s *SameStep) Type() tokens.Type { return s.old.Type() }
func (s *SameStep) URN() resource.URN { return s.old.URN() }
func (s *SameStep) Old() *resource.State { return s.old }
func (s *SameStep) New() *resource.Object { return s.new }
func (s *SameStep) Inputs() resource.PropertyMap { return s.inputs }
func (s *SameStep) Outputs() resource.PropertyMap { return s.old.Outputs() }
func (s *SameStep) Pre() error {
contract.Assert(s.old != nil)
contract.Assert(s.new != nil)
return s.Plan().Provider(s.new)
return nil
}
func (s *Step) Apply() (resource.Status, error) {
// Fetch the provider.
prov, err := s.Provider()
if err != nil {
return resource.StatusOK, err
}
// Now simply perform the operation of the right kind.
switch s.op {
case OpSame:
// Just propagate the ID and output state to the live object and append to the snapshot.
contract.Assert(s.old != nil)
contract.Assert(s.new != nil)
s.new.Update(s.urn, s.old.ID(), s.old.Outputs())
s.iter.MarkStateSnapshot(s.old)
s.iter.AppendStateSnapshot(s.old)
case OpCreate, OpReplaceCreate:
// Invoke the Create RPC function for this provider:
contract.Assert(s.old == nil || s.op == OpReplaceCreate)
contract.Assert(s.new != nil)
t := s.new.Type()
id, rst, err := prov.Create(t, s.inputs)
if err != nil {
return rst, err
}
contract.Assert(id != "")
// Read the resource state back (to fetch outputs) and store everything on the live object.
outs, err := prov.Get(t, id)
if err != nil {
return resource.StatusUnknown, err
}
s.outputs = outs
state := s.new.Update(s.urn, id, outs)
if s.old != nil {
s.iter.MarkStateSnapshot(s.old)
}
s.iter.AppendStateSnapshot(state)
case OpDelete, OpReplaceDelete:
// Invoke the Delete RPC function for this provider:
contract.Assert(s.old != nil)
contract.Assert(s.new == nil)
if rst, err := prov.Delete(s.old.Type(), s.old.ID()); err != nil {
return rst, err
}
s.iter.MarkStateSnapshot(s.old)
case OpUpdate:
// Invoke the Update RPC function for this provider:
contract.Assert(s.old != nil)
contract.Assert(s.new != nil)
t := s.old.Type()
contract.Assert(t == s.new.Type())
id := s.old.ID()
contract.Assert(id != "")
if rst, err := prov.Update(t, id, s.old.Inputs(), s.inputs); err != nil {
return rst, err
}
// Now read the resource state back in case the update triggered cascading updates to other properties.
outs, err := prov.Get(t, id)
if err != nil {
return resource.StatusUnknown, err
}
s.outputs = outs
state := s.new.Update(s.urn, id, outs)
s.iter.MarkStateSnapshot(s.old)
s.iter.AppendStateSnapshot(state)
default:
contract.Failf("Unexpected step operation: %v", s.op)
}
func (s *SameStep) Apply() (resource.Status, error) {
// Just propagate the ID and output state to the live object and append to the snapshot.
s.new.Update(s.old.URN(), s.old.ID(), s.old.Outputs())
s.iter.MarkStateSnapshot(s.old)
s.iter.AppendStateSnapshot(s.old)
return resource.StatusOK, nil
}
// Skip skips a step. This is required even when just viewing a plan to ensure in-memory object states are correct.
// This factors in the correct differences in behavior depending on the kind of action being taken.
func (s *Step) Skip() {
switch s.op {
case OpSame:
// In the case of a same, both ID and outputs are identical.
s.new.Update(s.urn, s.old.ID(), s.old.Outputs())
case OpCreate:
// In the case of a create, we cannot possibly know the ID or output properties. But we do know the URN.
s.new.SetURN(s.urn)
case OpUpdate:
// In the case of an update, the ID is the same, however, the outputs remain unknown.
s.new.SetURN(s.urn)
s.new.SetID(s.old.ID())
case OpReplaceCreate:
// In the case of a replacement, we neither propagate the ID nor output properties. This may be surprising,
// however, it must be done this way since the entire resource will be deleted and recreated. As a result, we
// actually want the ID to be seen as having been updated (triggering cascading updates as appropriate).
case OpDelete, OpReplaceDelete:
// In the case of a deletion, there is no state to propagate: the new object doesn't even exist.
default:
contract.Failf("Unexpected step operation: %v", s.op)
func (s *SameStep) Skip() error {
// In the case of a same, both ID and outputs are identical.
s.new.Update(s.old.URN(), s.old.ID(), s.old.Outputs())
return nil
}
// CreateStep is a mutating step that creates an entirely new resource.
type CreateStep struct {
iter *PlanIterator // the current plan iteration.
urn resource.URN // the resource URN being created.
new *resource.Object // the state of the resource after this step.
inputs resource.PropertyMap // the input properties for the creation.
outputs resource.PropertyMap // the output properties after creation.
}
var _ MutatingStep = (*CreateStep)(nil)
func NewCreateStep(iter *PlanIterator, urn resource.URN, new *resource.Object, inputs resource.PropertyMap) Step {
contract.Assert(!resource.HasURN(new))
return &CreateStep{
iter: iter,
urn: urn,
new: new,
inputs: inputs,
}
}
// StepOp represents the kind of operation performed by this step.
func (s *CreateStep) Op() StepOp { return OpCreate }
func (s *CreateStep) Plan() *Plan { return s.iter.p }
func (s *CreateStep) Iterator() *PlanIterator { return s.iter }
func (s *CreateStep) Type() tokens.Type { return s.new.Type() }
func (s *CreateStep) URN() resource.URN { return s.urn }
func (s *CreateStep) Old() *resource.State { return nil }
func (s *CreateStep) New() *resource.Object { return s.new }
func (s *CreateStep) Inputs() resource.PropertyMap { return s.inputs }
func (s *CreateStep) Outputs() resource.PropertyMap { return s.outputs }
func (s *CreateStep) Pre() error {
contract.Assert(s.new != nil)
return nil
}
func (s *CreateStep) Apply() (resource.Status, error) {
t := s.new.Type()
// Invoke the Create RPC function for this provider:
prov, err := getProvider(s)
if err != nil {
return resource.StatusOK, err
}
id, rst, err := prov.Create(t, s.inputs)
if err != nil {
return rst, err
}
contract.Assert(id != "")
// Read the resource state back (to fetch outputs) and store everything on the live object.
outs, err := prov.Get(t, id)
if err != nil {
return resource.StatusUnknown, err
}
s.outputs = outs
state := s.new.Update(s.urn, id, outs)
s.iter.AppendStateSnapshot(state)
return resource.StatusOK, nil
}
func (s *CreateStep) Skip() error {
// In the case of a create, we cannot possibly know the ID or output properties. But we do know the URN.
s.new.SetURN(s.urn)
return nil
}
// DeleteStep is a mutating step that deletes an existing resource.
type DeleteStep struct {
iter *PlanIterator // the current plan iteration.
old *resource.State // the state of the existing resource.
replaced bool // true if part of a replacement.
}
var _ MutatingStep = (*DeleteStep)(nil)
func NewDeleteStep(iter *PlanIterator, old *resource.State, replaced bool) Step {
contract.Assert(resource.HasURN(old))
return &DeleteStep{
iter: iter,
old: old,
replaced: replaced,
}
}
func (s *DeleteStep) Op() StepOp { return OpDelete }
func (s *DeleteStep) Plan() *Plan { return s.iter.p }
func (s *DeleteStep) Iterator() *PlanIterator { return s.iter }
func (s *DeleteStep) Type() tokens.Type { return s.old.Type() }
func (s *DeleteStep) URN() resource.URN { return s.old.URN() }
func (s *DeleteStep) Old() *resource.State { return s.old }
func (s *DeleteStep) New() *resource.Object { return nil }
func (s *DeleteStep) Inputs() resource.PropertyMap { return s.old.Inputs() }
func (s *DeleteStep) Outputs() resource.PropertyMap { return s.old.Outputs() }
func (s *DeleteStep) Replaced() bool { return s.replaced }
func (s *DeleteStep) Pre() error {
contract.Assert(s.old != nil)
return nil
}
func (s *DeleteStep) Apply() (resource.Status, error) {
// Invoke the Delete RPC function for this provider:
prov, err := getProvider(s)
if err != nil {
return resource.StatusOK, err
}
if rst, err := prov.Delete(s.old.Type(), s.old.ID()); err != nil {
return rst, err
}
s.iter.MarkStateSnapshot(s.old)
return resource.StatusOK, nil
}
func (s *DeleteStep) Skip() error {
// In the case of a deletion, there is no state to propagate: the new object doesn't even exist.
return nil
}
// UpdateStep is a mutating step that updates an existing resource's state.
type UpdateStep struct {
iter *PlanIterator // the current plan iteration.
old *resource.State // the state of the existing resource.
new *resource.Object // the live resource object.
inputs resource.PropertyMap // the input properties for the update.
outputs resource.PropertyMap // the output properties populated after updating.
}
var _ MutatingStep = (*UpdateStep)(nil)
func NewUpdateStep(iter *PlanIterator, old *resource.State,
new *resource.Object, inputs resource.PropertyMap) Step {
contract.Assert(resource.HasURN(old))
contract.Assert(!resource.HasURN(new))
return &UpdateStep{
iter: iter,
old: old,
new: new,
inputs: inputs,
}
}
func (s *UpdateStep) Op() StepOp { return OpUpdate }
func (s *UpdateStep) Plan() *Plan { return s.iter.p }
func (s *UpdateStep) Iterator() *PlanIterator { return s.iter }
func (s *UpdateStep) Type() tokens.Type { return s.old.Type() }
func (s *UpdateStep) URN() resource.URN { return s.old.URN() }
func (s *UpdateStep) Old() *resource.State { return s.old }
func (s *UpdateStep) New() *resource.Object { return s.new }
func (s *UpdateStep) Inputs() resource.PropertyMap { return s.inputs }
func (s *UpdateStep) Outputs() resource.PropertyMap { return s.outputs }
func (s *UpdateStep) Pre() error {
contract.Assert(s.old != nil)
contract.Assert(s.new != nil)
contract.Assert(s.old.Type() == s.new.Type())
contract.Assert(s.old.ID() != "")
return nil
}
func (s *UpdateStep) Apply() (resource.Status, error) {
t := s.old.Type()
id := s.old.ID()
// Invoke the Update RPC function for this provider:
prov, err := getProvider(s)
if err != nil {
return resource.StatusOK, err
}
if rst, err := prov.Update(t, id, s.old.Inputs(), s.inputs); err != nil {
return rst, err
}
// Now read the resource state back in case the update triggered cascading updates to other properties.
outs, err := prov.Get(t, id)
if err != nil {
return resource.StatusUnknown, err
}
s.outputs = outs
state := s.new.Update(s.old.URN(), id, outs)
s.iter.MarkStateSnapshot(s.old)
s.iter.AppendStateSnapshot(state)
return resource.StatusOK, nil
}
func (s *UpdateStep) Skip() error {
// In the case of an update, the ID is the same, however, the outputs remain unknown.
s.new.SetURN(s.old.URN())
s.new.SetID(s.old.ID())
return nil
}
// ReplaceStep is a mutating step that updates an existing resource's state.
type ReplaceStep struct {
iter *PlanIterator // the current plan iteration.
old *resource.State // the state of the existing resource.
new *resource.Object // the live resource object.
inputs resource.PropertyMap // the input properties for the replacement.
outputs resource.PropertyMap // the output properties populated after replacing.
reasons []resource.PropertyKey // the reasons for the replacement.
}
func NewReplaceStep(iter *PlanIterator, old *resource.State,
new *resource.Object, inputs resource.PropertyMap, reasons []resource.PropertyKey) Step {
contract.Assert(resource.HasURN(old))
contract.Assert(!resource.HasURN(new))
return &ReplaceStep{
iter: iter,
old: old,
new: new,
inputs: inputs,
reasons: reasons,
}
}
func (s *ReplaceStep) Op() StepOp { return OpReplace }
func (s *ReplaceStep) Plan() *Plan { return s.iter.p }
func (s *ReplaceStep) Iterator() *PlanIterator { return s.iter }
func (s *ReplaceStep) Type() tokens.Type { return s.old.Type() }
func (s *ReplaceStep) URN() resource.URN { return s.old.URN() }
func (s *ReplaceStep) Old() *resource.State { return s.old }
func (s *ReplaceStep) New() *resource.Object { return s.new }
func (s *ReplaceStep) Inputs() resource.PropertyMap { return s.inputs }
func (s *ReplaceStep) Outputs() resource.PropertyMap { return s.outputs }
func (s *ReplaceStep) Reasons() []resource.PropertyKey { return s.reasons }
func (s *ReplaceStep) Pre() error {
contract.Assert(s.old != nil)
contract.Assert(s.new != nil)
return nil
}
func (s *ReplaceStep) Apply() (resource.Status, error) {
t := s.new.Type()
// Invoke the Create RPC function for this provider:
prov, err := getProvider(s)
if err != nil {
return resource.StatusOK, err
}
id, rst, err := prov.Create(t, s.inputs)
if err != nil {
return rst, err
}
contract.Assert(id != "")
// Read the resource state back (to fetch outputs) and store everything on the live object.
outs, err := prov.Get(t, id)
if err != nil {
return resource.StatusUnknown, err
}
s.outputs = outs
state := s.new.Update(s.old.URN(), id, outs)
s.iter.MarkStateSnapshot(s.old)
s.iter.AppendStateSnapshot(state)
return resource.StatusOK, nil
}
func (s *ReplaceStep) Skip() error {
// In the case of a replacement, we neither propagate the ID nor output properties. This may be surprising,
// however, it must be done this way since the entire resource will be deleted and recreated. As a result, we
// actually want the ID to be seen as having been updated (triggering cascading updates as appropriate).
s.new.SetURN(s.old.URN())
return nil
}
// GetStep is a read-only step that queries for a single resource.
type GetStep struct {
iter *PlanIterator // the current plan iteration.
t symbols.Type // the type of resource to query.
id resource.ID // the ID of the resource being sought.
obj *resource.Object // the resource object read back from this operation.
outputs resource.PropertyMap // the output properties populated after updating.
}
var _ ReadStep = (*GetStep)(nil)
func NewGetStep(iter *PlanIterator, t symbols.Type, id resource.ID, obj *resource.Object) Step {
return &GetStep{
iter: iter,
t: t,
id: id,
obj: obj,
}
}
func (s *GetStep) Op() StepOp { return OpGet }
func (s *GetStep) Plan() *Plan { return s.iter.p }
func (s *GetStep) Iterator() *PlanIterator { return s.iter }
func (s *GetStep) Type() tokens.Type { return s.t.TypeToken() }
func (s *GetStep) Resources() []*resource.Object { return []*resource.Object{s.obj} }
func (s *GetStep) Pre() error {
// Simply call through to the provider's Get API.
id := s.id
prov, err := getProvider(s)
if err != nil {
return err
}
outs, err := prov.Get(s.Type(), id)
if err != nil {
return err
}
s.outputs = outs
// If no pre-existing object was supplied, create a new one.
if s.obj == nil {
s.obj = resource.NewEmptyObject(s.t)
}
// Populate the object's ID, properties, and URN with the state we read back.
// TODO: it's not clear yet how to correctly populate the URN, given that the allocation context is unknown.
s.obj.SetID(id)
s.obj.SetProperties(outs)
// Finally, the iterate must communicate the result back to the interpreter, by way of an unwind.
s.iter.Produce(s.obj)
return nil
}
func (s *GetStep) Apply() (resource.Status, error) {
return resource.StatusOK, nil
}
func (s *GetStep) Skip() error {
return nil
}
// getProvider fetches the provider for the given step.
func getProvider(s Step) (plugin.Provider, error) {
return s.Plan().ProviderT(s.Type())
}
// StepOp represents the kind of operation performed by a step. It evaluates to its string label.
type StepOp string
const (
OpSame StepOp = "same" // nothing to do.
OpCreate StepOp = "create" // creating a new resource.
OpUpdate StepOp = "update" // updating an existing resource.
OpDelete StepOp = "delete" // deleting an existing resource.
OpReplaceCreate StepOp = "replace" // replacing a resource with a new one.
OpReplaceDelete StepOp = "replace-delete" // the fine-grained replacement step to delete the old resource.
OpSame StepOp = "same" // nothing to do.
OpCreate StepOp = "create" // creating a new resource.
OpUpdate StepOp = "update" // updating an existing resource.
OpDelete StepOp = "delete" // deleting an existing resource.
OpReplace StepOp = "replace" // replacing a resource with a new one.
OpGet StepOp = "get" // fetching a resource by ID or URN.
OpQuery StepOp = "query" // querying a resource list by type and filter.
)
// StepOps contains the full set of step operation types.
@ -209,8 +455,9 @@ var StepOps = []StepOp{
OpCreate,
OpUpdate,
OpDelete,
OpReplaceCreate,
OpReplaceDelete,
OpReplace,
OpGet,
OpQuery,
}
// Color returns a suggested color for lines of this op type.
@ -224,10 +471,10 @@ func (op StepOp) Color() string {
return colors.SpecDeleted
case OpUpdate:
return colors.SpecChanged
case OpReplaceCreate:
case OpReplace:
return colors.SpecReplaced
case OpReplaceDelete:
return colors.SpecDeleted
case OpGet, OpQuery:
return colors.SpecRead
default:
contract.Failf("Unrecognized resource step op: %v", op)
return ""
@ -237,18 +484,16 @@ func (op StepOp) Color() string {
// Prefix returns a suggested prefix for lines of this op type.
func (op StepOp) Prefix() string {
switch op {
case OpSame:
case OpSame, OpGet, OpQuery:
return op.Color() + " "
case OpCreate:
return op.Color() + "+ "
case OpDelete:
return op.Color() + "- "
case OpUpdate:
return op.Color() + " "
case OpReplaceCreate:
return op.Color() + "~+"
case OpReplaceDelete:
return op.Color() + "~-"
return op.Color() + "~ "
case OpReplace:
return op.Color() + "+-"
default:
contract.Failf("Unrecognized resource step op: %v", op)
return ""
@ -257,8 +502,8 @@ func (op StepOp) Prefix() string {
// Suffix returns a suggested suffix for lines of this op type.
func (op StepOp) Suffix() string {
if op == OpUpdate || op == OpReplaceCreate {
return colors.Reset // updates and replacements colorize individual lines
if op == OpUpdate || op == OpReplace || op == OpGet {
return colors.Reset // updates and replacements colorize individual lines; get has none
}
return ""
}

View file

@ -47,6 +47,14 @@ func NewObject(obj *rt.Object) *Object {
return &Object{obj: obj}
}
// NewEmptyObject allocates an empty resource object of a given type.
func NewEmptyObject(t symbols.Type) *Object {
contract.Assert(predef.IsResourceType(t))
return &Object{
obj: rt.NewObject(t, nil, nil, nil),
}
}
func (r *Object) Obj() *rt.Object { return r.obj }
func (r *Object) Type() tokens.Type { return r.obj.Type().TypeToken() }

View file

@ -316,6 +316,7 @@ func (g *PackGenerator) emitResourceClass(w *bufio.Writer, res *Resource) {
} else {
writefmtln(w, " super();")
}
// Next, validate that required parameters exist, and store all arguments on the object.
argLinePrefix := " "
needsArgsCheck := hasArgs && !hasRequiredArgs
@ -338,6 +339,17 @@ func (g *PackGenerator) emitResourceClass(w *bufio.Writer, res *Resource) {
}
writefmtln(w, " }")
writefmtln(w, "")
// Finally, add the standard "factory" functions: get and query.
writefmtln(w, " public static get(id: lumi.ID): %v {", name)
writefmtln(w, " return <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, "}")
}