diff --git a/cmd/husk.go b/cmd/husk.go index 45ee93dd9..0b11cae57 100644 --- a/cmd/husk.go +++ b/cmd/husk.go @@ -294,7 +294,7 @@ func apply(cmd *cobra.Command, info *huskCmdInfo, opts applyOptions) { var summary bytes.Buffer if !empty { // Print out the total number of steps performed (and their kinds), the duration, and any summary info. - printSummary(&summary, progress.Ops, false) + printSummary(&summary, progress.Ops, opts.ShowReplaceSteps, false) summary.WriteString(fmt.Sprintf("%vDeployment duration: %v%v\n", colors.SpecUnimportant, time.Since(start), colors.Reset)) } @@ -445,13 +445,14 @@ func saveHusk(husk *resource.Husk, snap resource.Snapshot, file string, existok } type applyOptions struct { - Create bool // true if we are creating resources. - Delete bool // true if we are deleting resources. - DryRun bool // true if we should just print the plan without performing it. - ShowConfig bool // true to show the configuration variables being used. - ShowUnchanged bool // true to show the resources that aren't updated, in addition to those that are. - Summary bool // true if we should only summarize resources and operations. - 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. + ShowConfig bool // true to show the configuration variables being used. + ShowReplaceSteps bool // true to show the replacement steps in the plan. + ShowUnchanged bool // true to show the resources that aren't updated, in addition to those that are. + Summary bool // true if we should only summarize resources and operations. + Output string // the place to store the output, if any. } // applyProgress pretty-prints the plan application process as it goes. @@ -472,9 +473,16 @@ func newProgress(summary bool) *applyProgress { func (prog *applyProgress) Before(step resource.Step) { // Print the step. - var b bytes.Buffer + stepop := step.Op() stepnum := prog.Steps + 1 - b.WriteString(fmt.Sprintf("Applying step #%v [%v]\n", stepnum, step.Op())) + + var extra string + if stepop == resource.OpReplaceCreate || stepop == resource.OpReplaceDelete { + extra = " (part of a replacement step)" + } + + var b bytes.Buffer + b.WriteString(fmt.Sprintf("Applying step #%v [%v]%v\n", stepnum, stepop, extra)) printStep(&b, step, prog.Summary, " ") fmt.Printf(colors.Colorize(&b)) } @@ -521,15 +529,18 @@ func printPlan(d diag.Sink, result *planResult, opts applyOptions) { step := result.Plan.Steps() counts := make(map[resource.StepOp]int) for step != nil { + op := step.Op() // Print this step information (resource and all its properties). // TODO: it would be nice if, in the output, we showed the dependencies a la `git log --graph`. - printStep(&summary, step, opts.Summary, "") + if opts.ShowReplaceSteps || (op != resource.OpReplaceCreate && op != resource.OpReplaceDelete) { + printStep(&summary, step, opts.Summary, "") + } counts[step.Op()]++ step = step.Next() } // Print a summary of operation counts. - printSummary(&summary, counts, true) + printSummary(&summary, counts, opts.ShowReplaceSteps, true) fmt.Printf(colors.Colorize(&summary)) } } @@ -560,7 +571,7 @@ func printConfig(b *bytes.Buffer, result *compileResult) { } } -func printSummary(b *bytes.Buffer, counts map[resource.StepOp]int, plan bool) { +func printSummary(b *bytes.Buffer, counts map[resource.StepOp]int, showReplaceSteps bool, plan bool) { total := 0 for _, c := range counts { total += c @@ -585,6 +596,10 @@ func printSummary(b *bytes.Buffer, counts map[resource.StepOp]int, plan bool) { } for _, op := range resource.StepOps() { + if !showReplaceSteps && (op == resource.OpReplaceCreate || op == resource.OpReplaceDelete) { + // Unless the user requested it, don't show the fine-grained replacement steps; just the logical ones. + continue + } if c := counts[op]; c > 0 { b.WriteString(fmt.Sprintf(" %v%v %v %v%v%v%v\n", op.Prefix(), c, plural("resource", c), planTo, op, pastTense, colors.Reset)) @@ -617,7 +632,7 @@ func printStep(b *bytes.Buffer, step resource.Step, summary bool, indent string) // Next print the resource moniker, properties, etc. printResourceHeader(b, step.Old(), step.New(), indent) b.WriteString(step.Op().Suffix()) - printResourceProperties(b, step.Old(), step.New(), step.Computed(), summary, indent) + printResourceProperties(b, step.Old(), step.New(), step.NewProps(), summary, indent) // Finally make sure to reset the color. b.WriteString(colors.Reset) @@ -790,12 +805,12 @@ func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.V _, newIndent := getArrayElemHeader(b, i, indent) title := func(id string) { printArrayElemHeader(b, i, id) } if add, isadd := a.Adds[i]; isadd { - b.WriteString(colors.SpecAdded) + b.WriteString(resource.OpCreate.Color()) title(addIndent(indent)) printPropertyValue(b, add, addIndent(newIndent)) b.WriteString(colors.Reset) } else if delete, isdelete := a.Deletes[i]; isdelete { - b.WriteString(colors.SpecDeleted) + b.WriteString(resource.OpDelete.Color()) title(deleteIndent(indent)) printPropertyValue(b, delete, deleteIndent(newIndent)) b.WriteString(colors.Reset) @@ -812,23 +827,30 @@ func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.V b.WriteString("{\n") printObjectDiff(b, *diff.Object, indent+" ") b.WriteString(fmt.Sprintf("%s}\n", indent)) + } else if diff.Old.IsResource() && diff.New.IsResource() && diff.New.ResourceValue().Replacement() { + // If the old and new are both resources, and the new is a replacement, show this in a special way (+-). + b.WriteString(resource.OpReplace.Color()) + title(updateIndent(indent)) + printPropertyValue(b, diff.Old, updateIndent(indent)) + b.WriteString(colors.Reset) } else { // If we ended up here, the two values either differ by type, or they have different primitive values. We will // simply emit a deletion line followed by an addition line. if shouldPrintPropertyValue(diff.Old) { - b.WriteString(colors.SpecChanged) + b.WriteString(resource.OpUpdate.Color()) title(deleteIndent(indent)) printPropertyValue(b, diff.Old, deleteIndent(indent)) - b.WriteString(fmt.Sprintf("%s", colors.Reset)) + b.WriteString(colors.Reset) } if shouldPrintPropertyValue(diff.New) { - b.WriteString(colors.SpecChanged) + b.WriteString(resource.OpUpdate.Color()) title(addIndent(indent)) printPropertyValue(b, diff.New, addIndent(indent)) - b.WriteString(fmt.Sprintf("%s", colors.Reset)) + b.WriteString(colors.Reset) } } } func addIndent(indent string) string { return indent[:len(indent)-2] + "+ " } func deleteIndent(indent string) string { return indent[:len(indent)-2] + "- " } +func updateIndent(indent string) string { return indent[:len(indent)-2] + "+-" } diff --git a/cmd/husk_deploy.go b/cmd/husk_deploy.go index d3bcd8e1e..9cddf9c51 100644 --- a/cmd/husk_deploy.go +++ b/cmd/husk_deploy.go @@ -9,6 +9,7 @@ import ( func newHuskDeployCmd() *cobra.Command { var dryRun bool var showConfig bool + var showReplaceSteps bool var showUnchanged bool var summary bool var output string @@ -29,12 +30,13 @@ func newHuskDeployCmd() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { info := initHuskCmd(cmd, args) apply(cmd, info, applyOptions{ - Delete: false, - DryRun: dryRun, - ShowConfig: showConfig, - ShowUnchanged: showUnchanged, - Summary: summary, - Output: output, + Delete: false, + DryRun: dryRun, + ShowConfig: showConfig, + ShowReplaceSteps: showReplaceSteps, + ShowUnchanged: showUnchanged, + Summary: summary, + Output: output, }) }, } @@ -45,6 +47,9 @@ func newHuskDeployCmd() *cobra.Command { cmd.PersistentFlags().BoolVar( &showConfig, "show-config", false, "Show configuration keys and variables") + cmd.PersistentFlags().BoolVar( + &showReplaceSteps, "show-replace-steps", false, + "Show detailed resource replacement creates and deletes; normally shows as a single step") cmd.PersistentFlags().BoolVar( &showUnchanged, "show-unchanged", false, "Show resources that needn't be updated because they haven't changed, alongside those that do") diff --git a/lib/aws/provider/ec2/instance.go b/lib/aws/provider/ec2/instance.go index 9c0a26e5e..1c413132c 100644 --- a/lib/aws/provider/ec2/instance.go +++ b/lib/aws/provider/ec2/instance.go @@ -117,6 +117,7 @@ func (p *instanceProvider) UpdateImpact( } // Now check the diff for updates to any fields (none of them are updateable). + // TODO: we should permit changes to security groups for non-EC2-classic VMs that are in VPCs. diff := olds.Diff(news) replace := diff.Diff(instanceImageID) || diff.Diff(instanceType) || diff.Diff(instanceSecurityGroups) || diff.Diff(instanceKeyName) diff --git a/pkg/resource/moniker.go b/pkg/resource/moniker.go index b074792e1..4c0717a44 100644 --- a/pkg/resource/moniker.go +++ b/pkg/resource/moniker.go @@ -3,7 +3,10 @@ package resource import ( + "strings" + "github.com/pulumi/coconut/pkg/tokens" + "github.com/pulumi/coconut/pkg/util/contract" ) // Moniker is a friendly, but unique, name for a resource, most often auto-assigned by Coconut. These monikers @@ -31,10 +34,32 @@ const MonikerDelimiter = "::" // the delimiter between elements of the moniker. // NewMoniker creates a unique moniker for the given object. func NewMoniker(ns tokens.QName, alloc tokens.Module, t tokens.Type, name tokens.QName) Moniker { - return Moniker( + m := Moniker( string(ns) + MonikerDelimiter + string(alloc) + MonikerDelimiter + string(t) + MonikerDelimiter + string(name), ) + contract.Assert(!m.Replacement()) + return m +} + +// replaceMonikerSuffix is the suffix for monikers referring to resources that are being replaced. +const replaceMonikerSuffix = Moniker("#") + +// Replace returns a new, modified replacement moniker (used to tag resources that are meant to be replaced). +func (m Moniker) Replace() Moniker { + contract.Assert(!m.Replacement()) + return m + replaceMonikerSuffix +} + +// Unreplace returns the underlying replacement's moniker. +func (m Moniker) Unreplace() Moniker { + contract.Assert(m.Replacement()) + return m[:len(m)-len(replaceMonikerSuffix)] +} + +// Replacement returns true if this moniker refers to a resource that is meant to be replaced. +func (m Moniker) Replacement() bool { + return strings.HasSuffix(string(m), string(replaceMonikerSuffix)) } diff --git a/pkg/resource/plan.go b/pkg/resource/plan.go index 15512a87c..e39a7dca5 100644 --- a/pkg/resource/plan.go +++ b/pkg/resource/plan.go @@ -3,8 +3,6 @@ package resource import ( - "fmt" - "github.com/golang/glog" "github.com/pulumi/coconut/pkg/compiler/core" @@ -39,7 +37,7 @@ type Step interface { Op() StepOp // the operation that will be performed. Old() Resource // the old resource state, if any, before performing this step. New() Resource // the new resource state, if any, after performing this step. - Computed() PropertyMap // the projected new resource state, factoring in dependency updates. + NewProps() PropertyMap // the projected new resource state, factoring in dependency updates. Next() Step // the next step to perform, or nil if none. Apply() (error, ResourceState) // performs the operation specified by this step. } @@ -48,19 +46,21 @@ type Step interface { type StepOp string const ( - OpCreate StepOp = "create" - OpRead = "read" - OpUpdate = "update" - OpReplace = "replace" - OpDelete = "delete" + OpCreate StepOp = "create" // creating a new resource. + OpRead StepOp = "read" // reading from an existing 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 (logically). + OpReplaceCreate StepOp = "replace-create" // the fine-grained replacement step to create the new resource. + OpReplaceDelete StepOp = "replace-delete" // the fine-grained replacement step to delete the old resource. ) // Color returns a suggested color for lines of this op type. func (op StepOp) Color() string { switch op { - case OpCreate: + case OpCreate, OpReplaceCreate: return colors.SpecAdded - case OpDelete: + case OpDelete, OpReplaceDelete: return colors.SpecDeleted case OpUpdate: return colors.SpecChanged @@ -83,6 +83,10 @@ func (op StepOp) Prefix() string { return op.Color() + " " case OpReplace: return op.Color() + "-+" + case OpReplaceCreate: + return op.Color() + "~+" + case OpReplaceDelete: + return op.Color() + "~-" default: contract.Failf("Unrecognized resource step op: %v", op) return "" @@ -102,8 +106,10 @@ func StepOps() []StepOp { return []StepOp{ OpCreate, OpUpdate, - OpReplace, OpDelete, + OpReplace, + OpReplaceCreate, + OpReplaceDelete, } } @@ -229,6 +235,156 @@ func (p *plan) checkpoint(resources []Resource) Snapshot { // newPlan handles all three cases: (1) a creation plan from a new snapshot when old doesn't exist (nil), (2) an update // plan when both old and new exist, and (3) a deletion plan when old exists, but not new. func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { + // Create a new plan builder and then proceed to do some building. + pb := newPlanBuilder(ctx, old, new) + if glog.V(7) { + glog.V(7).Infof("Creating plan with #old=%v #new=%v\n", len(pb.OldRes), len(pb.NewRes)) + } + + // First diff the snapshots; in a nutshell: + // + // - Anything in old but not new is a delete + // - Anything in new but not old is a create + // - For those things in both new and old, any changed properties imply an update + // + // Any property changes that require replacement are applied, recursively, in a cascading manner. + for _, old := range pb.OldRes { + m := old.Moniker() + pb.Olds[m] = old + contract.Assert(old.HasID()) + // Keep track of which dependents exist for all resources. + for dep := range old.Properties().AllResources() { + pb.Depends[dep] = append(pb.Depends[dep], m) + } + } + for _, new := range pb.NewRes { + pb.News[new.Moniker()] = new + } + + // Find those things in old but not new, and add them to the delete queue. + for _, old := range pb.OldRes { + m := old.Moniker() + if _, hasnew := pb.News[m]; !hasnew { + step := newDeleteStep(pb.P, old) + pb.Deletes[m] = newPlanVertex(step) + glog.V(7).Infof("Update plan decided to delete '%v'", m) + } + } + + // Find creates and updates: creates are those in new but not old, and updates are those in both. + for _, new := range pb.NewRes { + m := new.Moniker() + if old, hasold := pb.Olds[m]; hasold { + // The resource exists in both new and old; it could be an update. This resource is an update if one of + // these two conditions exist: 1) either the old and new properties don't match or 2) the update impact + // is assessed as having to replace the resource, in which case the ID will change. This might have a + // cascading impact on subsequent updates too, since those IDs must trigger recreations, etc. + contract.Assert(old.Type() == new.Type()) + computed := new.Properties().ReplaceResources(func(r Moniker) Moniker { + if pb.Replaces[r] { + // If the resource is being replaced, simply mangle the moniker so that it's different; this value + // won't actually be used for anything other than the diffing algorithms below. + r = r.Replace() + glog.V(7).Infof("Patched resource '%v's moniker property: %v", m, r) + } + return r + }) + if !old.Properties().DeepEquals(computed) { + // See if this update has the effect of deleting and recreating the resource. If so, we need to make + // sure to insert the right replacement steps into the graph (a create, replace, and delete). + // TODO[pulumi/coconut#90]: this should generalize to any property changes. + prov, err := pb.P.Provider(old) + if err != nil { + return nil, err + } + replace, _, err := prov.UpdateImpact(old.ID(), old.Type(), old.Properties(), computed) + if err != nil { + return nil, err + } + + // Now create a step and vertex of the right kind. + if replace { + // To perform a replacement, create a creation, deletion, and add the appropriate edges. Namely: + // + // - Replacement depends on creation + // - Deletion depends on replacement + // - Existing dependencies depend on replacement (ensured through usual update logic) + // - Deletion depends on updating all existing dependencies (ensured through usual update logic) + // + // This ensures the right sequencing, with the replacement node acting as a juncture in the graph. + pb.Replaces[m] = true + create := newReplaceCreateStep(pb.P, new) + pb.Creates[m] = newPlanVertex(create) + replace := newReplaceStep(pb.P, old, new, computed) + pb.Updates[m] = newPlanVertex(replace) + pb.Updates[m].connectTo(pb.Creates[m]) // replacement depends on creation + delete := newReplaceDeleteStep(pb.P, old) + pb.Deletes[m] = newPlanVertex(delete) + pb.Deletes[m].connectTo(pb.Updates[m]) // deletion depends on replacement + glog.V(7).Infof("Update plan decided to update '%v'; necessitates a replacement", m) + } else { + // An update is simple: just create a single update step and associated node in the graph. + step := newUpdateStep(pb.P, old, new, computed) + pb.Updates[m] = newPlanVertex(step) + glog.V(7).Infof("Update plan decided to update '%v'", m) + } + } else { + pb.Unchanged[old] = new + glog.V(7).Infof("Update plan decided not to update '%v'", m) + } + } else { + // The resource isn't in the old map, so it must be a resource creation. + step := newCreateStep(pb.P, new) + pb.Creates[m] = newPlanVertex(step) + glog.V(7).Infof("Update plan decided to create '%v'", m) + } + } + + // Finally, we need to sequence the overall set of changes to create the final plan. To do this, we create a DAG + // of the above operations, so that inherent dependencies between operations are respected; specifically: + // + // - Deleting a resource depends on deletes of dependents and updates whose olds refer to it + // - Creating a resource depends on creates of dependencies + // - Updating a resource depends on creates or updates of news + // + // Clearly we must prohibit cycles in this overall graph of resource operations (hence the DAG part). To ensure + // this ordering, we will produce a plan graph whose vertices are operations and whose edges encode dependencies. + for _, old := range pb.OldRes { + m := old.Moniker() + if delete, isdelete := pb.Deletes[m]; isdelete { + pb.ConnectDelete(m, delete) // connect this delete so it happens before dependencies. + } else if update, isupdate := pb.Updates[m]; isupdate { + pb.ConnectUpdate(m, update) // connect this delete so it happens after dependencies are created/updated. + } + } + for _, new := range pb.NewRes { + m := new.Moniker() + if create, iscreate := pb.Creates[m]; iscreate { + pb.ConnectCreate(m, create) // connect this create so it happens after dependencies are created/updated. + } + } + + // Finally, finish the creation of the plan, and return it. + return pb.Plan() +} + +// planBuilder records a lot of the necessary information during the creation of a plan. +type planBuilder struct { + P *plan // the plan under construction. + Olds map[Moniker]Resource // a map of moniker to old resource. + OldRes []Resource // a flat list of old resources (in topological order). + News map[Moniker]Resource // a map of moniker to new resource. + NewRes []Resource // a flat list of new resources (in topological order). + Depends map[Moniker][]Moniker // a map of moniker to all existing (old) dependencies. + Creates map[Moniker]*planVertex // a map of pending creates to their associated vertex. + Updates map[Moniker]*planVertex // a map of pending updates to their associated vertex. + Deletes map[Moniker]*planVertex // a map of pending deletes to their associated vertex. + Replaces map[Moniker]bool // a set of monikers that are scheduled for replacement. + Unchanged map[Resource]Resource // a map of unchanged resources to their ID-stamped state. +} + +// newPlanBuilder initializes a fresh plan state instance, ready to use for planning. +func newPlanBuilder(ctx *Context, old Snapshot, new Snapshot) *planBuilder { // These variables are read from either snapshot (preferred new, since it may have updated args). var ns tokens.QName var pkg tokens.Package @@ -252,193 +408,38 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { args = new.Args() } - if glog.V(7) { - glog.V(7).Infof("Creating plan with #old=%v #new=%v\n", len(oldres), len(newres)) - } - - // First diff the snapshots; in a nutshell: - // - // - Anything in old but not new is a delete - // - Anything in new but not old is a create - // - For those things in both new and old, any changed properties imply an update - // - // Any property changes that require replacement are applied, recursively, in a cascading manner. - olds := make(map[Moniker]Resource) - olddepends := make(map[Moniker][]Moniker) - for _, res := range oldres { - m := res.Moniker() - olds[m] = res - contract.Assert(res.HasID()) - // Keep track of which dependents exist for all resources. - for ref := range res.Properties().AllResources() { - olddepends[ref] = append(olddepends[ref], m) - } - } - news := make(map[Moniker]Resource) - for _, res := range newres { - news[res.Moniker()] = res - } - - // Keep track of vertices for our later graph operations. + // Create a new, unfinished plan; it will be completed later on after the builder is done. p := &plan{ - ctx: ctx, - ns: ns, - pkg: pkg, - args: args, - unchanged: make(map[Resource]Resource), - } - vs := make(map[Moniker]*planVertex) - - // Find those things in old but not new, and add them to the delete queue. - deletes := make(map[Resource]bool) - for _, res := range oldres { - m := res.Moniker() - if _, has := news[m]; !has { - deletes[res] = true - step := newDeleteStep(p, res) - vs[m] = newPlanVertex(step) - glog.V(7).Infof("Update plan decided to delete '%v'", m) - } + ctx: ctx, + ns: ns, + pkg: pkg, + args: args, } - // Find creates and updates: creates are those in new but not old, and updates are those in both. - creates := make(map[Resource]bool) - updates := make(map[Resource]Resource) - replaces := make(map[Moniker]bool) - for _, res := range newres { - m := res.Moniker() - if oldres, has := olds[m]; has { - // The resource exists in both new and old; it could be an update. This resource is an update if one of - // these two conditions exist: 1) either the old and new properties don't match or 2) the update impact - // is assessed as having to replace the resource, in which case the ID will change. This might have a - // cascading impact on subsequent updates too, since those IDs must trigger recreations, etc. - contract.Assert(oldres.Type() == res.Type()) - computed := res.Properties().ReplaceResources(func(r Moniker) Moniker { - if replaces[r] { - // If the resource is being replaced, simply mangle the moniker so that it's different; this value - // won't actually be used for anything other than the diffing algorithms below. - r += Moniker("#") - glog.V(7).Infof("Patched resource '%v's moniker property: %v", m, r) - } - return r - }) - if !oldres.Properties().DeepEquals(computed) { - updates[oldres] = res - glog.V(7).Infof("Update plan decided to update '%v'", m) - - // If this update's impact will replace the resource, make sure to remember this fact. - // TODO[pulumi/coconut#90]: this should generalize to any property changes. - prov, err := p.Provider(oldres) - if err != nil { - return nil, err - } - replace, _, err := prov.UpdateImpact(oldres.ID(), oldres.Type(), oldres.Properties(), computed) - if err != nil { - return nil, err - } - - // Now create a step and vertex of the right kind. - var step *step - if replace { - glog.V(7).Infof("Update to resource '%v' necessitates a replacement", m) - step = newReplaceStep(p, oldres, res, computed) - replaces[m] = true - } else { - step = newUpdateStep(p, oldres, res, computed) - } - vs[m] = newPlanVertex(step) - } else { - p.unchanged[oldres] = res - glog.V(7).Infof("Update plan decided not to update '%v'", m) - } - } else { - // The resource isn't in the old map, so it must be a resource creation. - creates[res] = true - step := newCreateStep(p, res) - vs[m] = newPlanVertex(step) - glog.V(7).Infof("Update plan decided to create '%v'", m) - } - } - - // Finally, we need to sequence the overall set of changes to create the final plan. To do this, we create a DAG - // of the above operations, so that inherent dependencies between operations are respected; specifically: - // - // - Deleting a resource depends on deletes of dependents and updates whose olds refer to it - // - Creating a resource depends on creates of dependencies - // - Updating a resource depends on creates or updates of news - // - // Clearly we must prohibit cycles in this overall graph of resource operations (hence the DAG part). To ensure - // this ordering, we will produce a plan graph whose vertices are operations and whose edges encode dependencies. - for _, res := range oldres { - m := res.Moniker() - if deletes[res] { - // Add edges to: - // - any dependents that used to refer to this - fromv := vs[m] - contract.Assert(fromv != nil) - for _, ref := range olddepends[m] { - if tov, has := vs[ref]; has { - contract.Assert(tov != nil) - fromv.connectTo(tov) - glog.V(7).Infof("Deletion '%v' depends on resource '%v'; edge created", m, ref) - } else { - old := olds[ref] - contract.Assert(old != nil) - contract.Assert(!deletes[old]) - contract.Assert(updates[old] == nil) - glog.V(7).Infof("Deletion '%v' depends on resource '%v'; unchanged, so ignoring", m, ref) - } - } - } else if to := updates[res]; to != nil { - // Add edge to: - // - creates news - // - updates news - // TODO[pulumi/coconut#90]: we need to track "cascading updates". - fromv := vs[m] - contract.Assert(fromv != nil) - for ref := range to.Properties().AllResources() { - if tov, has := vs[ref]; has { - contract.Assert(tov != nil) - fromv.connectTo(tov) - glog.V(7).Infof("Updating '%v' depends on resource '%v'; edge created", m, ref) - } else { - old := olds[ref] - contract.Assert(old != nil) - contract.Assert(!deletes[old]) - contract.Assert(updates[old] == nil) - glog.V(7).Infof("Updating '%v' depends on resource '%v'; unchanged, so ignoring", m, ref) - } - } - } - } - for _, res := range newres { - if creates[res] { - // add edge to: - // - creates news - m := res.Moniker() - fromv := vs[m] - contract.Assert(fromv != nil) - for ref := range res.Properties().AllResources() { - if tov, has := vs[ref]; has { - contract.Assert(tov != nil) - fromv.connectTo(tov) - glog.V(7).Infof("Creating '%v' depends on resource '%v'; edge created", m, ref) - } else { - old := olds[ref] - new := news[ref] - contract.Assert(new == nil || !creates[new]) - contract.Assert(old == nil || updates[old] == nil) - glog.V(7).Infof("Creating '%v' depends on resource '%v'; unchanged, so ignoring", m, ref) - } - } - } + return &planBuilder{ + P: p, + Olds: make(map[Moniker]Resource), + OldRes: oldres, + News: make(map[Moniker]Resource), + NewRes: newres, + Depends: make(map[Moniker][]Moniker), + Creates: make(map[Moniker]*planVertex), + Updates: make(map[Moniker]*planVertex), + Deletes: make(map[Moniker]*planVertex), + Replaces: make(map[Moniker]bool), + Unchanged: make(map[Resource]Resource), } +} +// Plan finishes the plan building and returns the resulting, completed plan (or non-nil error if it fails). +func (pb *planBuilder) Plan() (*plan, error) { // For all plan vertices with no ins, make them root nodes. var roots []*planEdge - for _, v := range vs { - if len(v.Ins()) == 0 { - roots = append(roots, &planEdge{to: v}) + for _, vs := range []map[Moniker]*planVertex{pb.Creates, pb.Updates, pb.Deletes} { + for _, v := range vs { + if len(v.Ins()) == 0 { + roots = append(roots, &planEdge{to: v}) + } } } @@ -452,7 +453,64 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { for _, v := range topdag { insertStep(&prev, v.Data().(*step)) } - return p, nil + + // Remember the unchanged nodes. + pb.P.unchanged = pb.Unchanged + + return pb.P, nil +} + +func (pb *planBuilder) ConnectCreate(m Moniker, v *planVertex) { + pb.connectCreateUpdate(m, v, false) +} + +func (pb *planBuilder) ConnectUpdate(m Moniker, v *planVertex) { + pb.connectCreateUpdate(m, v, true) +} + +func (pb *planBuilder) connectCreateUpdate(m Moniker, v *planVertex, update bool) { + var label string + if update { + label = "Updating" + } else { + label = "Creating" + } + + // Add edges to: + // - new resources this step depends on + // - updated resources that this step depends on + new := v.Step().New() + for dep := range new.Properties().AllResources() { + tov, has := pb.Creates[dep] // see if we're creating the dependency. + if !has { + tov, has = pb.Updates[dep] // see if the dependency is being updated. + } + if has { + contract.Assert(tov != nil) + v.connectTo(tov) + glog.V(7).Infof("%v '%v' depends on resource '%v'; edge created", label, m, dep) + } else { + // A missing entry is ok; it means the resource isn't changing. + contract.Assert(pb.Unchanged[new] != nil) + contract.Assert(update == (pb.Olds[dep] != nil)) + glog.V(7).Infof("%v '%v' depends on resource '%v'; unchanged, so ignoring", label, m, dep) + } + } +} + +func (pb *planBuilder) ConnectDelete(m Moniker, v *planVertex) { + // Add edges to: + // - any dependents that used to refer to this (and are necessarily being deleted or updated) + for _, dep := range pb.Depends[m] { + tov, has := pb.Deletes[dep] // see if dependents are being deleted + if !has { + tov, has = pb.Updates[dep] // else, they should be updated, otherwise there is a problem + } + contract.Assertf(has, "Resource '%v' depends on '%v' (scheduled for deletion)", m, dep) + contract.Assert(tov != nil) + v.connectTo(tov) + glog.V(7).Infof("Deletion '%v' depends on resource '%v'; edge created", m, dep) + } } type step struct { @@ -460,7 +518,7 @@ type step struct { op StepOp // the operation to perform. old Resource // the state of the resource before this step. new Resource // the state of the resource after this step. - computed PropertyMap // the computed impact dependency changes will have on this resource. + newprops PropertyMap // the resource's properties, factoring in dependency updates. next *step // the next step after this one in the plan. } @@ -469,28 +527,44 @@ var _ Step = (*step)(nil) func (s *step) Op() StepOp { return s.op } func (s *step) Old() Resource { return s.old } func (s *step) New() Resource { return s.new } -func (s *step) Computed() PropertyMap { return s.computed } +func (s *step) NewProps() PropertyMap { return s.newprops } func (s *step) Next() Step { if s.next == nil { return nil } return s.next } +func (s *step) Provider() (Provider, error) { + contract.Assert(s.old == nil || s.new == nil || s.old.Type() == s.new.Type()) + if s.old != nil { + return s.p.Provider(s.old) + } + contract.Assert(s.new != nil) + return s.p.Provider(s.new) +} func newCreateStep(p *plan, new Resource) *step { - return &step{p: p, op: OpCreate, new: new, computed: new.Properties()} + return &step{p: p, op: OpCreate, new: new, newprops: new.Properties()} } func newDeleteStep(p *plan, old Resource) *step { - return &step{p: p, op: OpDelete, old: old, computed: nil} + return &step{p: p, op: OpDelete, old: old, newprops: nil} } -func newUpdateStep(p *plan, old Resource, new Resource, computed PropertyMap) *step { - return &step{p: p, op: OpUpdate, old: old, new: new, computed: computed} +func newUpdateStep(p *plan, old Resource, new Resource, newprops PropertyMap) *step { + return &step{p: p, op: OpUpdate, old: old, new: new, newprops: newprops} } -func newReplaceStep(p *plan, old Resource, new Resource, computed PropertyMap) *step { - return &step{p: p, op: OpReplace, old: old, new: new, computed: computed} +func newReplaceStep(p *plan, old Resource, new Resource, newprops PropertyMap) *step { + return &step{p: p, op: OpReplace, old: old, new: new, newprops: newprops} +} + +func newReplaceCreateStep(p *plan, new Resource) *step { + return &step{p: p, op: OpReplaceCreate, new: new, newprops: new.Properties()} +} + +func newReplaceDeleteStep(p *plan, old Resource) *step { + return &step{p: p, op: OpReplaceDelete, old: old, newprops: nil} } func insertStep(prev **step, step *step) { @@ -506,58 +580,57 @@ func insertStep(prev **step, step *step) { } func (s *step) Apply() (error, ResourceState) { + // Fetch the provider. + prov, err := s.Provider() + if err != nil { + return err, StateOK + } + // Now simply perform the operation of the right kind. switch s.op { - case OpCreate: + case OpCreate, OpReplaceCreate: + // Invoke the Create RPC function for this provider: contract.Assert(s.old == nil) contract.Assert(s.new != nil) contract.Assertf(!s.new.HasID(), "Resources being created must not have IDs already") - prov, err := s.p.Provider(s.new) - if err != nil { - return err, StateOK - } id, err, rst := prov.Create(s.new.Type(), s.new.Properties()) if err != nil { return err, rst } s.new.SetID(id) - case OpDelete: + + case OpDelete, OpReplaceDelete: + // Invoke the Delete RPC function for this provider: contract.Assert(s.old != nil) contract.Assert(s.new == nil) contract.Assertf(s.old.HasID(), "Resources being deleted must have IDs") - prov, err := s.p.Provider(s.old) - if err != nil { - return err, StateOK - } if err, rst := prov.Delete(s.old.ID(), s.old.Type()); err != nil { return err, rst } - case OpUpdate, OpReplace: + + case OpUpdate: + // Invoke the Update RPC function for this provider: contract.Assert(s.old != nil) contract.Assert(s.new != nil) contract.Assert(s.old.Type() == s.new.Type()) contract.Assertf(s.old.HasID(), "Resources being updated must have IDs") - prov, err := s.p.Provider(s.old) - if err != nil { - return err, StateOK - } - id, err, rst := prov.Update(s.old.ID(), s.old.Type(), s.old.Properties(), s.new.Properties()) - if err != nil { + id := s.old.ID() + if err, rst := prov.Update(id, s.old.Type(), s.old.Properties(), s.new.Properties()); err != nil { return err, rst - } else if id != ID("") { - // An update might need to recreate the resource, in which case the ID must change. - if s.op != OpReplace { - // If we didn't expect to replace this resource, but the provided did so anyway, we need to halt - // things. Dependent nodes aren't scheduled for updates, and so state has become out of synch. - // TODO: this isn't drastic enough; we need to walk and taint all dependencies (or something similar). - return fmt.Errorf("unexpected resource '%v' ID change (old %v, new %v)", - s.old.Type(), s.old.ID(), id), StateUnknown - } - s.new.SetID(id) - } else { - // Otherwise, propagate the old ID on the new resource, so that the resulting snapshot is correct. - s.new.SetID(s.old.ID()) } + + // Propagate the old ID on the new resource, so that the resulting snapshot is correct. + s.new.SetID(id) + + case OpReplace: + contract.Assert(s.old != nil) + contract.Assert(s.new != nil) + contract.Assert(s.old.Type() == s.new.Type()) + contract.Assertf(s.old.HasID(), "Resources being replaced must have IDs") + + // There is nothing to do for OpReplace nodes; they are here to represent logical steps in the graph, and mostly + // for visualization purposes; there will be true OpReplaceCreate and OpReplaceDelete nodes in the graph. + default: contract.Failf("Unexpected step operation: %v", s.op) } diff --git a/pkg/resource/plugin.go b/pkg/resource/plugin.go index 76ef34c09..728aff1ca 100644 --- a/pkg/resource/plugin.go +++ b/pkg/resource/plugin.go @@ -204,9 +204,8 @@ func (p *Plugin) Read(id ID, t tokens.Type) (PropertyMap, error) { return props, nil } -// Update updates an existing resource with new values. Only those values in the provided property bag are updated -// to new values. The resource ID is returned and may be different if the resource had to be recreated. -func (p *Plugin) Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) (ID, error, ResourceState) { +// Update updates an existing resource with new values. +func (p *Plugin) Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) (error, ResourceState) { contract.Requiref(id != "", "id", "not empty") contract.Requiref(t != "", "t", "not empty") @@ -221,15 +220,14 @@ func (p *Plugin) Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap News: MarshalProperties(p.ctx, news, MarshalOptions{}), } - resp, err := p.client.Update(p.ctx.Request(), req) + _, err := p.client.Update(p.ctx.Request(), req) if err != nil { glog.V(7).Infof("Plugin[%v].Update(id=%v,t=%v,...) failed: %v", p.pkg, id, t, err) - return ID(""), err, StateUnknown + return err, StateUnknown } - nid := resp.GetId() - glog.V(7).Infof("Plugin[%v].Update(id=%v,t=%v,...) success: nid=%v", p.pkg, id, t, nid) - return ID(nid), nil, StateOK + glog.V(7).Infof("Plugin[%v].Update(id=%v,t=%v,...) success", p.pkg, id, t) + return nil, StateOK } // UpdateImpact checks what impacts a hypothetical update will have on the resource's properties. diff --git a/pkg/resource/provider.go b/pkg/resource/provider.go index c0ea2b28c..6614adbe1 100644 --- a/pkg/resource/provider.go +++ b/pkg/resource/provider.go @@ -29,9 +29,8 @@ type Provider interface { Create(t tokens.Type, props PropertyMap) (ID, error, ResourceState) // Read reads the instance state identified by id/t, and returns a bag of properties. Read(id ID, t tokens.Type) (PropertyMap, error) - // Update updates an existing resource with new values. Only those values in the provided property bag are updated - // to new values. The resource ID is returned and may be different if the resource had to be recreated. - Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) (ID, error, ResourceState) + // Update updates an existing resource with new values. + Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) (error, ResourceState) // UpdateImpact checks what impacts a hypothetical update will have on the resource's properties. UpdateImpact(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) (bool, PropertyMap, error) // Delete tears down an existing resource. diff --git a/sdk/go/pkg/cocorpc/engine.pb.go b/sdk/go/pkg/cocorpc/engine.pb.go index 7ad9eda53..da3c07108 100644 --- a/sdk/go/pkg/cocorpc/engine.pb.go +++ b/sdk/go/pkg/cocorpc/engine.pb.go @@ -18,7 +18,6 @@ It has these top-level messages: ReadRequest ReadResponse UpdateRequest - UpdateResponse UpdateImpactResponse DeleteRequest */ diff --git a/sdk/go/pkg/cocorpc/provider.pb.go b/sdk/go/pkg/cocorpc/provider.pb.go index 1383c71b1..a825d4162 100644 --- a/sdk/go/pkg/cocorpc/provider.pb.go +++ b/sdk/go/pkg/cocorpc/provider.pb.go @@ -180,22 +180,6 @@ func (m *UpdateRequest) GetNews() *google_protobuf1.Struct { return nil } -type UpdateResponse struct { - Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` -} - -func (m *UpdateResponse) Reset() { *m = UpdateResponse{} } -func (m *UpdateResponse) String() string { return proto.CompactTextString(m) } -func (*UpdateResponse) ProtoMessage() {} -func (*UpdateResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{7} } - -func (m *UpdateResponse) GetId() string { - if m != nil { - return m.Id - } - return "" -} - type UpdateImpactResponse struct { Replace bool `protobuf:"varint,1,opt,name=replace" json:"replace,omitempty"` Impacts *google_protobuf1.Struct `protobuf:"bytes,2,opt,name=impacts" json:"impacts,omitempty"` @@ -204,7 +188,7 @@ type UpdateImpactResponse struct { func (m *UpdateImpactResponse) Reset() { *m = UpdateImpactResponse{} } func (m *UpdateImpactResponse) String() string { return proto.CompactTextString(m) } func (*UpdateImpactResponse) ProtoMessage() {} -func (*UpdateImpactResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{8} } +func (*UpdateImpactResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{7} } func (m *UpdateImpactResponse) GetReplace() bool { if m != nil { @@ -228,7 +212,7 @@ type DeleteRequest struct { func (m *DeleteRequest) Reset() { *m = DeleteRequest{} } func (m *DeleteRequest) String() string { return proto.CompactTextString(m) } func (*DeleteRequest) ProtoMessage() {} -func (*DeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{9} } +func (*DeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{8} } func (m *DeleteRequest) GetId() string { if m != nil { @@ -252,7 +236,6 @@ func init() { proto.RegisterType((*ReadRequest)(nil), "cocorpc.ReadRequest") proto.RegisterType((*ReadResponse)(nil), "cocorpc.ReadResponse") proto.RegisterType((*UpdateRequest)(nil), "cocorpc.UpdateRequest") - proto.RegisterType((*UpdateResponse)(nil), "cocorpc.UpdateResponse") proto.RegisterType((*UpdateImpactResponse)(nil), "cocorpc.UpdateImpactResponse") proto.RegisterType((*DeleteRequest)(nil), "cocorpc.DeleteRequest") } @@ -277,9 +260,8 @@ type ResourceProviderClient interface { Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) // Read reads the instance state identified by ID, returning a populated resource object, or an error if not found. Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (*ReadResponse, error) - // Update updates an existing resource with new values. Only those values in the provided property bag are updated - // to new values. The resource ID is returned and may be different if the resource had to be recreated. - Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) + // Update updates an existing resource with new values. + Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) // UpdateImpact checks what impacts a hypothetical update will have on the resource's properties. UpdateImpact(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateImpactResponse, error) // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. @@ -321,8 +303,8 @@ func (c *resourceProviderClient) Read(ctx context.Context, in *ReadRequest, opts return out, nil } -func (c *resourceProviderClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) { - out := new(UpdateResponse) +func (c *resourceProviderClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/cocorpc.ResourceProvider/Update", in, out, c.cc, opts...) if err != nil { return nil, err @@ -360,9 +342,8 @@ type ResourceProviderServer interface { Create(context.Context, *CreateRequest) (*CreateResponse, error) // Read reads the instance state identified by ID, returning a populated resource object, or an error if not found. Read(context.Context, *ReadRequest) (*ReadResponse, error) - // Update updates an existing resource with new values. Only those values in the provided property bag are updated - // to new values. The resource ID is returned and may be different if the resource had to be recreated. - Update(context.Context, *UpdateRequest) (*UpdateResponse, error) + // Update updates an existing resource with new values. + Update(context.Context, *UpdateRequest) (*google_protobuf.Empty, error) // UpdateImpact checks what impacts a hypothetical update will have on the resource's properties. UpdateImpact(context.Context, *UpdateRequest) (*UpdateImpactResponse, error) // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. @@ -517,33 +498,32 @@ var _ResourceProvider_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("provider.proto", fileDescriptor1) } var fileDescriptor1 = []byte{ - // 436 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x54, 0x5d, 0x8b, 0xd4, 0x30, - 0x14, 0x6d, 0x6b, 0x99, 0xd1, 0x3b, 0x1f, 0x48, 0x58, 0x77, 0x4b, 0x55, 0x18, 0xf2, 0xb4, 0x20, - 0x74, 0xd9, 0x5d, 0x16, 0x41, 0x1f, 0x55, 0x16, 0x5f, 0x44, 0x2a, 0xbe, 0x88, 0x2f, 0xdd, 0xf4, - 0x3a, 0x14, 0xda, 0x49, 0x4c, 0x52, 0x65, 0x7e, 0x84, 0xbf, 0x59, 0x49, 0xd3, 0xd6, 0xb4, 0xc3, - 0x38, 0xe3, 0xc3, 0xbe, 0xa5, 0x37, 0x27, 0xe7, 0xdc, 0xdc, 0x73, 0x52, 0x58, 0x0a, 0xc9, 0x7f, - 0x14, 0x39, 0xca, 0x44, 0x48, 0xae, 0x39, 0x99, 0x32, 0xce, 0xb8, 0x14, 0x2c, 0x7e, 0xba, 0xe6, - 0x7c, 0x5d, 0xe2, 0x45, 0x53, 0xbe, 0xab, 0xbf, 0x5d, 0x60, 0x25, 0xf4, 0xd6, 0xa2, 0xe2, 0x67, - 0xe3, 0x4d, 0xa5, 0x65, 0xcd, 0xb4, 0xdd, 0xa5, 0x5f, 0x60, 0xf6, 0x21, 0xab, 0x30, 0xc5, 0xef, - 0x35, 0x2a, 0x4d, 0x08, 0x84, 0x7a, 0x2b, 0x30, 0xf2, 0x57, 0xfe, 0xf9, 0xa3, 0xb4, 0x59, 0x93, - 0x97, 0x00, 0x42, 0x72, 0x81, 0x52, 0x17, 0xa8, 0xa2, 0x60, 0xe5, 0x9f, 0xcf, 0xae, 0xce, 0x12, - 0xcb, 0x9a, 0x74, 0xac, 0xc9, 0xa7, 0x86, 0x35, 0x75, 0xa0, 0x94, 0xc2, 0xdc, 0x72, 0x2b, 0xc1, - 0x37, 0x0a, 0x0d, 0xf9, 0x26, 0xab, 0x7a, 0x72, 0xb3, 0xa6, 0x5f, 0x61, 0xf1, 0x46, 0x62, 0xa6, - 0xef, 0xa7, 0x83, 0x15, 0x2c, 0x3b, 0xf6, 0xb6, 0x87, 0x25, 0x04, 0x45, 0xde, 0x92, 0x07, 0x45, - 0x4e, 0x2f, 0x61, 0x96, 0x62, 0x96, 0x77, 0xea, 0xa3, 0xed, 0xbe, 0x9b, 0xe0, 0x6f, 0x37, 0xf4, - 0x16, 0xe6, 0xf6, 0x48, 0x4b, 0x39, 0xec, 0xce, 0x3f, 0xbe, 0xbb, 0x5f, 0x3e, 0x2c, 0x3e, 0x8b, - 0xdc, 0xb9, 0xfc, 0x11, 0xf2, 0xe4, 0x05, 0x84, 0xbc, 0xcc, 0x55, 0xf4, 0xe0, 0xdf, 0x42, 0x0d, - 0xc8, 0x80, 0x37, 0xf8, 0x53, 0x45, 0xe1, 0x01, 0xb0, 0x01, 0x99, 0x69, 0x75, 0xed, 0xec, 0x99, - 0x16, 0x83, 0x13, 0x8b, 0x78, 0x5f, 0x89, 0x8c, 0xe9, 0x1e, 0x17, 0xc1, 0x54, 0xa2, 0x28, 0x33, - 0x66, 0x7d, 0x7b, 0x98, 0x76, 0x9f, 0xe4, 0x12, 0xa6, 0x45, 0x83, 0x3d, 0xe8, 0x5b, 0x87, 0xa3, - 0xd7, 0xb0, 0x78, 0x8b, 0x25, 0xfe, 0xd7, 0x54, 0xae, 0x7e, 0x07, 0xf0, 0x38, 0x45, 0xc5, 0x6b, - 0xc9, 0xf0, 0x63, 0xfb, 0x4c, 0xc8, 0x0d, 0x84, 0x26, 0x80, 0xe4, 0x24, 0x69, 0x5f, 0x4a, 0xe2, - 0x64, 0x3d, 0x7e, 0x32, 0xaa, 0xda, 0xbb, 0x50, 0x8f, 0xbc, 0x86, 0x89, 0x4d, 0x0d, 0x39, 0xed, - 0x21, 0x83, 0x90, 0xc6, 0x67, 0x3b, 0xf5, 0xfe, 0xf0, 0x0d, 0x84, 0x26, 0x1d, 0x8e, 0xa6, 0x93, - 0x2f, 0x47, 0xd3, 0x8d, 0x90, 0xd5, 0xb4, 0x93, 0x75, 0x34, 0x07, 0xd9, 0x70, 0x34, 0x87, 0x26, - 0x51, 0x8f, 0xdc, 0xc2, 0xdc, 0xb5, 0x65, 0x2f, 0xc5, 0xf3, 0x51, 0x7d, 0xe8, 0x22, 0xf5, 0xc8, - 0x2b, 0x98, 0xd8, 0xd1, 0x3b, 0x14, 0x03, 0x2f, 0xe2, 0xd3, 0x1d, 0xfb, 0xde, 0x99, 0x7f, 0x0d, - 0xf5, 0xee, 0x26, 0x4d, 0xe5, 0xfa, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5d, 0x6e, 0xc5, 0x48, - 0xa6, 0x04, 0x00, 0x00, + // 426 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x53, 0x4d, 0xab, 0xd3, 0x40, + 0x14, 0x6d, 0x62, 0x68, 0xf5, 0xf6, 0x03, 0x19, 0x9e, 0xef, 0x85, 0xa8, 0x50, 0x66, 0xf5, 0x40, + 0xc8, 0xa3, 0x2d, 0x45, 0xd0, 0xa5, 0x4a, 0x71, 0x23, 0x12, 0x71, 0x23, 0x6e, 0xd2, 0xc9, 0xb5, + 0x04, 0x92, 0xce, 0x38, 0x33, 0x51, 0xfa, 0x23, 0xfc, 0xcb, 0x22, 0xc9, 0x24, 0x71, 0x92, 0x52, + 0x5b, 0x17, 0x6f, 0x97, 0xdc, 0x39, 0x73, 0xee, 0xb9, 0x73, 0xce, 0x85, 0x99, 0x90, 0xfc, 0x47, + 0x9a, 0xa0, 0x0c, 0x85, 0xe4, 0x9a, 0x93, 0x11, 0xe3, 0x8c, 0x4b, 0xc1, 0x82, 0xa7, 0x3b, 0xce, + 0x77, 0x19, 0xde, 0x55, 0xe5, 0x6d, 0xf1, 0xed, 0x0e, 0x73, 0xa1, 0x0f, 0x06, 0x15, 0x3c, 0xeb, + 0x1f, 0x2a, 0x2d, 0x0b, 0xa6, 0xcd, 0x29, 0xfd, 0x02, 0xe3, 0x0f, 0x71, 0x8e, 0x11, 0x7e, 0x2f, + 0x50, 0x69, 0x42, 0xc0, 0xd3, 0x07, 0x81, 0xbe, 0x33, 0x77, 0x6e, 0x1f, 0x45, 0xd5, 0x37, 0x79, + 0x09, 0x20, 0x24, 0x17, 0x28, 0x75, 0x8a, 0xca, 0x77, 0xe7, 0xce, 0xed, 0x78, 0x79, 0x13, 0x1a, + 0xd6, 0xb0, 0x61, 0x0d, 0x3f, 0x55, 0xac, 0x91, 0x05, 0xa5, 0x14, 0x26, 0x86, 0x5b, 0x09, 0xbe, + 0x57, 0x58, 0x92, 0xef, 0xe3, 0xbc, 0x25, 0x2f, 0xbf, 0xe9, 0x57, 0x98, 0xbe, 0x91, 0x18, 0xeb, + 0xfb, 0x51, 0x30, 0x87, 0x59, 0xc3, 0x5e, 0x6b, 0x98, 0x81, 0x9b, 0x26, 0x35, 0xb9, 0x9b, 0x26, + 0x74, 0x01, 0xe3, 0x08, 0xe3, 0xa4, 0xe9, 0xde, 0x3b, 0x6e, 0xd5, 0xb8, 0x7f, 0xd5, 0xd0, 0x0d, + 0x4c, 0xcc, 0x95, 0x9a, 0xb2, 0xab, 0xce, 0xb9, 0x5c, 0xdd, 0x2f, 0x07, 0xa6, 0x9f, 0x45, 0x62, + 0x0d, 0x7f, 0x41, 0x7b, 0xf2, 0x02, 0x3c, 0x9e, 0x25, 0xca, 0x7f, 0xf0, 0xef, 0x46, 0x15, 0xa8, + 0x04, 0xef, 0xf1, 0xa7, 0xf2, 0xbd, 0x33, 0xe0, 0x12, 0x44, 0x19, 0x5c, 0x19, 0x39, 0xef, 0x73, + 0x11, 0x33, 0xdd, 0x0e, 0xe8, 0xc3, 0x48, 0xa2, 0xc8, 0x62, 0x66, 0x5c, 0x79, 0x18, 0x35, 0xbf, + 0x64, 0x01, 0xa3, 0xb4, 0xc2, 0x9e, 0x75, 0xa5, 0xc1, 0xd1, 0x15, 0x4c, 0xdf, 0x62, 0x86, 0xff, + 0x35, 0xf3, 0xf2, 0xb7, 0x0b, 0x8f, 0x23, 0x54, 0xbc, 0x90, 0x0c, 0x3f, 0xd6, 0x4b, 0x40, 0xd6, + 0xe0, 0x95, 0xf1, 0x22, 0x57, 0x61, 0xbd, 0x07, 0xa1, 0x95, 0xe4, 0xe0, 0x49, 0xaf, 0x6a, 0x66, + 0xa1, 0x03, 0xf2, 0x1a, 0x86, 0x26, 0x13, 0xe4, 0xba, 0x85, 0x74, 0x22, 0x18, 0xdc, 0x1c, 0xd5, + 0xdb, 0xcb, 0x6b, 0xf0, 0x4a, 0xef, 0xad, 0x9e, 0x56, 0x7a, 0xac, 0x9e, 0x76, 0x40, 0xe8, 0x80, + 0xbc, 0x82, 0xa1, 0x79, 0x59, 0xab, 0x67, 0xc7, 0xf9, 0xe0, 0xfa, 0xe8, 0xe1, 0xde, 0x95, 0x3b, + 0x4c, 0x07, 0x64, 0x03, 0x13, 0xdb, 0x95, 0x93, 0x0c, 0xcf, 0x7b, 0xf5, 0xae, 0x89, 0x46, 0x84, + 0x79, 0x79, 0x8b, 0xa2, 0x63, 0xc5, 0x69, 0x11, 0xdb, 0x61, 0x55, 0x59, 0xfd, 0x09, 0x00, 0x00, + 0xff, 0xff, 0xf3, 0xeb, 0x1f, 0x7c, 0x83, 0x04, 0x00, 0x00, } diff --git a/sdk/js/src/cocorpc/provider_pb.js b/sdk/js/src/cocorpc/provider_pb.js index 5d087847c..bf51e9e8a 100644 --- a/sdk/js/src/cocorpc/provider_pb.js +++ b/sdk/js/src/cocorpc/provider_pb.js @@ -20,7 +20,6 @@ goog.exportSymbol('proto.cocorpc.ReadRequest', null, global); goog.exportSymbol('proto.cocorpc.ReadResponse', null, global); goog.exportSymbol('proto.cocorpc.UpdateImpactResponse', null, global); goog.exportSymbol('proto.cocorpc.UpdateRequest', null, global); -goog.exportSymbol('proto.cocorpc.UpdateResponse', null, global); /** * Generated by JsPbCodeGenerator. @@ -1249,146 +1248,6 @@ proto.cocorpc.UpdateRequest.prototype.hasNews = function() { -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.cocorpc.UpdateResponse = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.cocorpc.UpdateResponse, jspb.Message); -if (goog.DEBUG && !COMPILED) { - proto.cocorpc.UpdateResponse.displayName = 'proto.cocorpc.UpdateResponse'; -} - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto suitable for use in Soy templates. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. - * @param {boolean=} opt_includeInstance Whether to include the JSPB instance - * for transitional soy proto support: http://goto/soy-param-migration - * @return {!Object} - */ -proto.cocorpc.UpdateResponse.prototype.toObject = function(opt_includeInstance) { - return proto.cocorpc.UpdateResponse.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Whether to include the JSPB - * instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.cocorpc.UpdateResponse} msg The msg instance to transform. - * @return {!Object} - */ -proto.cocorpc.UpdateResponse.toObject = function(includeInstance, msg) { - var f, obj = { - id: jspb.Message.getFieldWithDefault(msg, 1, "") - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.cocorpc.UpdateResponse} - */ -proto.cocorpc.UpdateResponse.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.cocorpc.UpdateResponse; - return proto.cocorpc.UpdateResponse.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.cocorpc.UpdateResponse} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.cocorpc.UpdateResponse} - */ -proto.cocorpc.UpdateResponse.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {string} */ (reader.readString()); - msg.setId(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.cocorpc.UpdateResponse.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.cocorpc.UpdateResponse.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.cocorpc.UpdateResponse} message - * @param {!jspb.BinaryWriter} writer - */ -proto.cocorpc.UpdateResponse.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getId(); - if (f.length > 0) { - writer.writeString( - 1, - f - ); - } -}; - - -/** - * optional string id = 1; - * @return {string} - */ -proto.cocorpc.UpdateResponse.prototype.getId = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); -}; - - -/** @param {string} value */ -proto.cocorpc.UpdateResponse.prototype.setId = function(value) { - jspb.Message.setField(this, 1, value); -}; - - - /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a diff --git a/sdk/proto/provider.proto b/sdk/proto/provider.proto index 92f6367b2..7f1fb5565 100644 --- a/sdk/proto/provider.proto +++ b/sdk/proto/provider.proto @@ -21,9 +21,8 @@ service ResourceProvider { rpc Create(CreateRequest) returns (CreateResponse) {} // Read reads the instance state identified by ID, returning a populated resource object, or an error if not found. rpc Read(ReadRequest) returns (ReadResponse) {} - // Update updates an existing resource with new values. Only those values in the provided property bag are updated - // to new values. The resource ID is returned and may be different if the resource had to be recreated. - rpc Update(UpdateRequest) returns (UpdateResponse) {} + // Update updates an existing resource with new values. + rpc Update(UpdateRequest) returns (google.protobuf.Empty) {} // UpdateImpact checks what impacts a hypothetical update will have on the resource's properties. rpc UpdateImpact(UpdateRequest) returns (UpdateImpactResponse) {} // Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist. @@ -64,10 +63,6 @@ message UpdateRequest { google.protobuf.Struct news = 4; // the new values of properties to update. } -message UpdateResponse { - string id = 1; // the new ID for the resource, if it had to be recreated. -} - message UpdateImpactResponse { bool replace = 1; // true if this update triggers replacement of the resource, false otherwise. google.protobuf.Struct impacts = 2; // the full set of properties that will be altered by this operation.