Track which updates triggered a replacement

This change tracks which updates triggered a replacement.  This enables
better output and diagnostics.  For example, we now colorize those
properties differently in the output.  This makes it easier to diagnose
why an unexpected resource might be getting deleted and recreated.
This commit is contained in:
joeduffy 2017-03-02 15:24:39 -08:00
parent f0d9b12a3c
commit 523c669a03
9 changed files with 239 additions and 152 deletions

View file

@ -621,7 +621,7 @@ func printUnchanged(b *bytes.Buffer, plan resource.Plan, summary bool) {
for _, res := range plan.Unchanged() {
b.WriteString(" ") // simulate the 2 spaces for +, -, etc.
printResourceHeader(b, res, nil, "")
printResourceProperties(b, res, nil, nil, summary, "")
printResourceProperties(b, res, nil, nil, nil, summary, "")
}
}
@ -632,7 +632,13 @@ 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.NewProps(), summary, indent)
var replaces []resource.PropertyKey
if step.Old() != nil {
m := step.Old().Moniker()
replaceMap := step.Plan().Replaces()
replaces = replaceMap[m]
}
printResourceProperties(b, step.Old(), step.New(), step.NewProps(), replaces, summary, indent)
// Finally make sure to reset the color.
b.WriteString(colors.Reset)
@ -651,7 +657,7 @@ func printResourceHeader(b *bytes.Buffer, old resource.Resource, new resource.Re
}
func printResourceProperties(b *bytes.Buffer, old resource.Resource, new resource.Resource,
computed resource.PropertyMap, summary bool, indent string) {
computed resource.PropertyMap, replaces []resource.PropertyKey, summary bool, indent string) {
indent += detailsIndent
// Print out the moniker and, if present, the ID, as "pseudo-properties".
@ -677,7 +683,7 @@ func printResourceProperties(b *bytes.Buffer, old resource.Resource, new resourc
printObject(b, old.Properties(), indent)
} else {
contract.Assert(computed != nil) // use computed properties for diffs.
printOldNewDiffs(b, old.Properties(), computed, indent)
printOldNewDiffs(b, old.Properties(), computed, replaces, indent)
}
}
}
@ -747,16 +753,18 @@ func printArrayElemHeader(b *bytes.Buffer, i int, indent string) string {
return newIndent
}
func printOldNewDiffs(b *bytes.Buffer, olds resource.PropertyMap, news resource.PropertyMap, indent string) {
func printOldNewDiffs(b *bytes.Buffer, olds resource.PropertyMap, news resource.PropertyMap,
replaces []resource.PropertyKey, indent string) {
// Get the full diff structure between the two, and print it (recursively).
if diff := olds.Diff(news); diff != nil {
printObjectDiff(b, *diff, indent)
printObjectDiff(b, *diff, replaces, false, indent)
} else {
printObject(b, news, indent)
}
}
func printObjectDiff(b *bytes.Buffer, diff resource.ObjectDiff, indent string) {
func printObjectDiff(b *bytes.Buffer, diff resource.ObjectDiff,
replaces []resource.PropertyKey, causedReplace bool, indent string) {
contract.Assert(len(indent) > 2)
// Compute the maximum with of property keys so we can justify everything.
@ -768,6 +776,15 @@ func printObjectDiff(b *bytes.Buffer, diff resource.ObjectDiff, indent string) {
}
}
// If a list of what causes a resource to get replaced exist, create a handy map.
var replaceMap map[resource.PropertyKey]bool
if len(replaces) > 0 {
replaceMap = make(map[resource.PropertyKey]bool)
for _, k := range replaces {
replaceMap[k] = true
}
}
// To print an object diff, enumerate the keys in stable order, and print each property independently.
for _, k := range keys {
title := func(id string) { printPropertyTitle(b, k, maxkey, id) }
@ -786,7 +803,10 @@ func printObjectDiff(b *bytes.Buffer, diff resource.ObjectDiff, indent string) {
b.WriteString(colors.Reset)
}
} else if update, isupdate := diff.Updates[k]; isupdate {
printPropertyValueDiff(b, title, update, indent)
if !causedReplace && replaceMap != nil {
causedReplace = replaceMap[k]
}
printPropertyValueDiff(b, title, update, causedReplace, indent)
} else if same := diff.Sames[k]; shouldPrintPropertyValue(same) {
title(indent)
printPropertyValue(b, diff.Sames[k], indent)
@ -794,13 +814,15 @@ func printObjectDiff(b *bytes.Buffer, diff resource.ObjectDiff, indent string) {
}
}
func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.ValueDiff, indent string) {
func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.ValueDiff,
causedReplace bool, indent string) {
contract.Assert(len(indent) > 2)
if diff.Array != nil {
title(indent)
a := diff.Array
b.WriteString("[\n")
a := diff.Array
for i := 0; i < a.Len(); i++ {
_, newIndent := getArrayElemHeader(b, i, indent)
title := func(id string) { printArrayElemHeader(b, i, id) }
@ -815,7 +837,7 @@ func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.V
printPropertyValue(b, delete, deleteIndent(newIndent))
b.WriteString(colors.Reset)
} else if update, isupdate := a.Updates[i]; isupdate {
printPropertyValueDiff(b, title, update, indent)
printPropertyValueDiff(b, title, update, causedReplace, indent)
} else {
title(indent)
printPropertyValue(b, a.Sames[i], newIndent)
@ -825,7 +847,7 @@ func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.V
} else if diff.Object != nil {
title(indent)
b.WriteString("{\n")
printObjectDiff(b, *diff.Object, indent+" ")
printObjectDiff(b, *diff.Object, nil, causedReplace, 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 (+-).
@ -837,13 +859,25 @@ func printPropertyValueDiff(b *bytes.Buffer, title func(string), diff resource.V
// 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(resource.OpUpdate.Color())
var color string
if causedReplace {
color = resource.OpDelete.Color() // this property triggered replacement; color as a delete
} else {
color = resource.OpUpdate.Color()
}
b.WriteString(color)
title(deleteIndent(indent))
printPropertyValue(b, diff.Old, deleteIndent(indent))
b.WriteString(colors.Reset)
}
if shouldPrintPropertyValue(diff.New) {
b.WriteString(resource.OpUpdate.Color())
var color string
if causedReplace {
color = resource.OpCreate.Color() // this property triggered replacement; color as a create
} else {
color = resource.OpUpdate.Color()
}
b.WriteString(color)
title(addIndent(indent))
printPropertyValue(b, diff.New, addIndent(indent))
b.WriteString(colors.Reset)

View file

@ -118,10 +118,18 @@ 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.
var replaces []string
diff := olds.Diff(news)
replace := diff.Diff(instanceImageID) || diff.Diff(instanceType) ||
diff.Diff(instanceSecurityGroups) || diff.Diff(instanceKeyName)
return &cocorpc.UpdateImpactResponse{Replace: replace}, nil
if diff.Diff(instanceImageID) {
replaces = append(replaces, instanceImageID)
}
if diff.Diff(instanceType) {
replaces = append(replaces, instanceType)
}
if diff.Diff(instanceKeyName) {
replaces = append(replaces, instanceKeyName)
}
return &cocorpc.UpdateImpactResponse{Replaces: replaces}, nil
}
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist.

View file

@ -114,13 +114,13 @@ func (p *sgProvider) Update(ctx context.Context, req *cocorpc.UpdateRequest) (*p
// Provided it's okay, unmarshal, validate, and diff the properties.
id := req.GetId()
oldgrp, newgrp, diff, replace, err := unmarshalSecurityGroupProperties(req.GetOlds(), req.GetNews())
oldgrp, newgrp, diff, replaces, err := unmarshalSecurityGroupProperties(req.GetOlds(), req.GetNews())
if err != nil {
return nil, err
}
// If this was a replacement, the UpdateImpact routine should have rejected it.
if replace {
if len(replaces) > 0 {
return nil, errors.New("this update requires a resource replacement")
}
@ -220,12 +220,12 @@ func (p *sgProvider) UpdateImpact(
ctx context.Context, req *cocorpc.UpdateRequest) (*cocorpc.UpdateImpactResponse, error) {
contract.Assert(req.GetType() == string(SecurityGroup))
// First unmarshal and validate the properties.
_, _, _, replace, err := unmarshalSecurityGroupProperties(req.GetOlds(), req.GetNews())
_, _, _, replaces, err := unmarshalSecurityGroupProperties(req.GetOlds(), req.GetNews())
if err != nil {
return nil, err
}
return &cocorpc.UpdateImpactResponse{
Replace: replace,
Replaces: replaces,
// TODO: serialize the otherproperties that will be updated.
}, nil
}
@ -233,23 +233,32 @@ func (p *sgProvider) UpdateImpact(
// unmarshalSecurityGroupProperties unmarshals old and new properties, diffs them and checks whether resource
// replacement is necessary. If an error occurs, the returned error is non-nil.
func unmarshalSecurityGroupProperties(olds *pbstruct.Struct,
news *pbstruct.Struct) (*securityGroup, *securityGroup, *resource.ObjectDiff, bool, error) {
news *pbstruct.Struct) (*securityGroup, *securityGroup, *resource.ObjectDiff, []string, error) {
// Deserialize the old/new properties and validate them before bothering to diff them.
oldprops := resource.UnmarshalProperties(olds)
oldgrp, err := newSecurityGroup(oldprops, true)
if err != nil {
return nil, nil, nil, false, err
return nil, nil, nil, nil, err
}
newprops := resource.UnmarshalProperties(news)
newgrp, err := newSecurityGroup(newprops, true)
if err != nil {
return nil, nil, nil, false, err
return nil, nil, nil, nil, err
}
// Now diff the properties to determine whether this must be recreated.
var replaces []string
diff := oldprops.Diff(newprops)
replace := diff.Diff(securityGroupName) || diff.Diff(securityGroupDescription) || diff.Diff(securityGroupVPCID)
return oldgrp, newgrp, diff, replace, nil
if diff.Diff(securityGroupName) {
replaces = append(replaces, securityGroupName)
}
if diff.Diff(securityGroupDescription) {
replaces = append(replaces, securityGroupDescription)
}
if diff.Diff(securityGroupVPCID) {
replaces = append(replaces, securityGroupVPCID)
}
return oldgrp, newgrp, diff, replaces, nil
}
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist.
@ -284,11 +293,11 @@ type securityGroup struct {
}
const (
securityGroupName resource.PropertyKey = "name"
securityGroupDescription = "groupDescription"
securityGroupVPCID = "vpc"
securityGroupEgress = "securityGroupEgress"
securityGroupIngress = "securityGroupIngress"
securityGroupName = "name"
securityGroupDescription = "groupDescription"
securityGroupVPCID = "vpc"
securityGroupEgress = "securityGroupEgress"
securityGroupIngress = "securityGroupIngress"
)
// newSecurityGroup creates a new instance bag of state, validating required properties if asked to do so.

View file

@ -22,6 +22,7 @@ import (
type Plan interface {
Empty() bool // true if the plan is empty.
Steps() Step // the first step to perform, linked to the rest.
Replaces() map[Moniker][]PropertyKey // resources being replaced and their properties.
Unchanged() map[Resource]Resource // the resources untouched by this plan.
Apply(prog Progress) (Snapshot, error, Step, ResourceState) // performs the operations specified in this plan.
}
@ -34,6 +35,7 @@ type Progress interface {
// Step is a specification for a deployment operation.
type Step interface {
Plan() Plan // the plan this step belongs to.
Op() StepOp // the operation that will be performed.
Logical() bool // true if this is a logical step, rather than a physical one.
Old() Resource // the old resource state, if any, before performing this step.
@ -124,18 +126,20 @@ func NewPlan(ctx *Context, old Snapshot, new Snapshot) (Plan, error) {
}
type plan struct {
ctx *Context // this plan's context.
ns tokens.QName // the husk/namespace target being deployed into.
pkg tokens.Package // the package from which this snapshot came.
args core.Args // the arguments used to compile this package.
first *step // the first step to take.
unchanged map[Resource]Resource // the resources that are remaining the same without modification.
ctx *Context // this plan's context.
ns tokens.QName // the husk/namespace target being deployed into.
pkg tokens.Package // the package from which this snapshot came.
args core.Args // the arguments used to compile this package.
first *step // the first step to take.
replaces map[Moniker][]PropertyKey // resources being replaced and their properties.
unchanged map[Resource]Resource // the resources that are remaining the same without modification.
}
var _ Plan = (*plan)(nil)
func (p *plan) Unchanged() map[Resource]Resource { return p.unchanged }
func (p *plan) Empty() bool { return p.Steps() == nil }
func (p *plan) Replaces() map[Moniker][]PropertyKey { return p.replaces }
func (p *plan) Unchanged() map[Resource]Resource { return p.unchanged }
func (p *plan) Empty() bool { return p.Steps() == nil }
func (p *plan) Steps() Step {
if p.first == nil {
@ -282,7 +286,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) {
// 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 pb.Replace(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()
@ -298,13 +302,13 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) {
if err != nil {
return nil, err
}
replace, _, err := prov.UpdateImpact(old.ID(), old.Type(), old.Properties(), computed)
replaces, _, 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 {
if len(replaces) > 0 {
// To perform a replacement, create a creation, deletion, and add the appropriate edges. Namely:
//
// - Replacement depends on creation
@ -313,7 +317,11 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) {
// - 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
replkeys := make([]PropertyKey, len(replaces))
for i, repl := range replaces {
replkeys[i] = PropertyKey(repl)
}
pb.Replaces[m] = replkeys
create := newReplaceCreateStep(pb.P, new)
pb.Creates[m] = newPlanVertex(create)
replace := newReplaceStep(pb.P, old, new, computed)
@ -371,17 +379,17 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) {
// 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.
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][]PropertyKey // a map of monikers scheduled for replacement to properties being replaced.
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.
@ -427,38 +435,13 @@ func newPlanBuilder(ctx *Context, old Snapshot, new Snapshot) *planBuilder {
Creates: make(map[Moniker]*planVertex),
Updates: make(map[Moniker]*planVertex),
Deletes: make(map[Moniker]*planVertex),
Replaces: make(map[Moniker]bool),
Replaces: make(map[Moniker][]PropertyKey),
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 _, 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})
}
}
}
// Now topologically sort the steps in the order they must execute, thread the plan together, and return it.
g := newPlanGraph(roots)
topdag, err := graph.Topsort(g)
if err != nil {
return nil, err
}
var prev *step
for _, v := range topdag {
insertStep(&prev, v.Data().(*step))
}
// Remember the unchanged nodes.
pb.P.unchanged = pb.Unchanged
return pb.P, nil
func (pb *planBuilder) Replace(m Moniker) bool {
return len(pb.Replaces[m]) > 0
}
func (pb *planBuilder) ConnectCreate(m Moniker, v *planVertex) {
@ -514,6 +497,36 @@ func (pb *planBuilder) ConnectDelete(m Moniker, v *planVertex) {
}
}
// 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 _, 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})
}
}
}
// Now topologically sort the steps in the order they must execute, thread the plan together, and return it.
g := newPlanGraph(roots)
topdag, err := graph.Topsort(g)
if err != nil {
return nil, err
}
var prev *step
for _, v := range topdag {
insertStep(&prev, v.Data().(*step))
}
// Remember extra information useful for plan consumers.
pb.P.replaces = pb.Replaces
pb.P.unchanged = pb.Unchanged
return pb.P, nil
}
type step struct {
p *plan // this step's plan.
op StepOp // the operation to perform.
@ -525,6 +538,7 @@ type step struct {
var _ Step = (*step)(nil)
func (s *step) Plan() Plan { return s.p }
func (s *step) Op() StepOp { return s.op }
func (s *step) Logical() bool { return s.op == OpReplace }
func (s *step) Old() Resource { return s.old }

View file

@ -231,7 +231,8 @@ func (p *Plugin) Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap
}
// UpdateImpact checks what impacts a hypothetical update will have on the resource's properties.
func (p *Plugin) UpdateImpact(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) (bool, PropertyMap, error) {
func (p *Plugin) UpdateImpact(id ID, t tokens.Type,
olds PropertyMap, news PropertyMap) ([]string, PropertyMap, error) {
contract.Requiref(id != "", "id", "not empty")
contract.Requiref(t != "", "t", "not empty")
@ -251,14 +252,14 @@ func (p *Plugin) UpdateImpact(id ID, t tokens.Type, olds PropertyMap, news Prope
resp, err := p.client.UpdateImpact(p.ctx.Request(), req)
if err != nil {
glog.V(7).Infof("Plugin[%v].UpdateImpact(id=%v,t=%v,...) failed: %v", p.pkg, id, t, err)
return false, nil, err
return nil, nil, err
}
replace := resp.GetReplace()
impacts := UnmarshalProperties(resp.GetImpacts())
glog.V(7).Infof("Plugin[%v].Update(id=%v,t=%v,...) success: replace=%v #impacts=%v",
p.pkg, id, t, replace, len(impacts))
return replace, impacts, nil
replaces := resp.GetReplaces()
changes := UnmarshalProperties(resp.GetChanges())
glog.V(7).Infof("Plugin[%v].Update(id=%v,t=%v,...) success: #replaces=%v #changes=%v",
p.pkg, id, t, len(replaces), len(changes))
return replaces, changes, nil
}
// Delete tears down an existing resource.

View file

@ -32,7 +32,7 @@ type Provider interface {
// 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)
UpdateImpact(id ID, t tokens.Type, olds PropertyMap, news PropertyMap) ([]string, PropertyMap, error)
// Delete tears down an existing resource.
Delete(id ID, t tokens.Type) (error, ResourceState)
}

View file

@ -181,8 +181,8 @@ func (m *UpdateRequest) GetNews() *google_protobuf1.Struct {
}
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"`
Replaces []string `protobuf:"bytes,1,rep,name=replaces" json:"replaces,omitempty"`
Changes *google_protobuf1.Struct `protobuf:"bytes,2,opt,name=changes" json:"changes,omitempty"`
}
func (m *UpdateImpactResponse) Reset() { *m = UpdateImpactResponse{} }
@ -190,16 +190,16 @@ func (m *UpdateImpactResponse) String() string { return proto.Compact
func (*UpdateImpactResponse) ProtoMessage() {}
func (*UpdateImpactResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{7} }
func (m *UpdateImpactResponse) GetReplace() bool {
func (m *UpdateImpactResponse) GetReplaces() []string {
if m != nil {
return m.Replace
return m.Replaces
}
return false
return nil
}
func (m *UpdateImpactResponse) GetImpacts() *google_protobuf1.Struct {
func (m *UpdateImpactResponse) GetChanges() *google_protobuf1.Struct {
if m != nil {
return m.Impacts
return m.Changes
}
return nil
}
@ -498,32 +498,32 @@ var _ResourceProvider_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("provider.proto", fileDescriptor1) }
var fileDescriptor1 = []byte{
// 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,
// 431 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x53, 0x5d, 0x8b, 0xd3, 0x40,
0x14, 0x6d, 0xb2, 0xa1, 0xeb, 0xde, 0x7e, 0x20, 0xc3, 0xba, 0x1b, 0xa2, 0x42, 0x99, 0xa7, 0x82,
0x90, 0x65, 0x77, 0x29, 0x82, 0x3e, 0xaa, 0x14, 0x5f, 0x44, 0x22, 0xbe, 0x88, 0x2f, 0xe9, 0xe4,
0x5a, 0x03, 0x49, 0x66, 0x9c, 0x99, 0x28, 0xfd, 0x11, 0xfe, 0x65, 0x91, 0x64, 0x92, 0x38, 0x49,
0xa9, 0xad, 0x0f, 0xbe, 0x25, 0x77, 0xce, 0x9c, 0x7b, 0xee, 0x9c, 0x73, 0x61, 0x2e, 0x24, 0xff,
0x9e, 0x26, 0x28, 0x43, 0x21, 0xb9, 0xe6, 0xe4, 0x9c, 0x71, 0xc6, 0xa5, 0x60, 0xc1, 0xe3, 0x2d,
0xe7, 0xdb, 0x0c, 0x6f, 0xea, 0xf2, 0xa6, 0xfc, 0x72, 0x83, 0xb9, 0xd0, 0x3b, 0x83, 0x0a, 0x9e,
0x0c, 0x0f, 0x95, 0x96, 0x25, 0xd3, 0xe6, 0x94, 0x7e, 0x82, 0xc9, 0xbb, 0x38, 0xc7, 0x08, 0xbf,
0x95, 0xa8, 0x34, 0x21, 0xe0, 0xe9, 0x9d, 0x40, 0xdf, 0x59, 0x38, 0xcb, 0x8b, 0xa8, 0xfe, 0x26,
0xcf, 0x01, 0x84, 0xe4, 0x02, 0xa5, 0x4e, 0x51, 0xf9, 0xee, 0xc2, 0x59, 0x4e, 0xee, 0xae, 0x43,
0xc3, 0x1a, 0xb6, 0xac, 0xe1, 0x87, 0x9a, 0x35, 0xb2, 0xa0, 0x94, 0xc2, 0xd4, 0x70, 0x2b, 0xc1,
0x0b, 0x85, 0x15, 0x79, 0x11, 0xe7, 0x1d, 0x79, 0xf5, 0x4d, 0x3f, 0xc3, 0xec, 0x95, 0xc4, 0x58,
0xff, 0x1f, 0x05, 0x0b, 0x98, 0xb7, 0xec, 0x8d, 0x86, 0x39, 0xb8, 0x69, 0xd2, 0x90, 0xbb, 0x69,
0x42, 0x6f, 0x61, 0x12, 0x61, 0x9c, 0xb4, 0xdd, 0x07, 0xc7, 0x9d, 0x1a, 0xf7, 0x8f, 0x1a, 0xba,
0x86, 0xa9, 0xb9, 0xd2, 0x50, 0xf6, 0xd5, 0x39, 0xa7, 0xab, 0xfb, 0xe9, 0xc0, 0xec, 0xa3, 0x48,
0xac, 0xe1, 0x4f, 0x68, 0x4f, 0x9e, 0x81, 0xc7, 0xb3, 0x44, 0xf9, 0x67, 0x7f, 0x6f, 0x54, 0x83,
0x2a, 0x70, 0x81, 0x3f, 0x94, 0xef, 0x1d, 0x01, 0x57, 0x20, 0x8a, 0x70, 0x69, 0xe4, 0xbc, 0xcd,
0x45, 0xcc, 0x74, 0x37, 0x60, 0x00, 0x0f, 0x24, 0x8a, 0x2c, 0x66, 0xf5, 0x78, 0x67, 0xcb, 0x8b,
0xa8, 0xfb, 0x27, 0xb7, 0x70, 0xce, 0xbe, 0xc6, 0xc5, 0xf6, 0xb8, 0x2f, 0x2d, 0x8e, 0xde, 0xc3,
0xec, 0x35, 0x66, 0xf8, 0x4f, 0x53, 0xdf, 0xfd, 0x72, 0xe1, 0x61, 0x84, 0x8a, 0x97, 0x92, 0xe1,
0xfb, 0x66, 0x0d, 0xc8, 0x0a, 0xbc, 0x2a, 0x60, 0xe4, 0x32, 0x6c, 0x36, 0x21, 0xb4, 0xb2, 0x1c,
0x3c, 0x1a, 0x54, 0xcd, 0x34, 0x74, 0x44, 0x5e, 0xc2, 0xd8, 0xa4, 0x82, 0x5c, 0x75, 0x90, 0x5e,
0x08, 0x83, 0xeb, 0xbd, 0x7a, 0x77, 0x79, 0x05, 0x5e, 0xe5, 0xbe, 0xd5, 0xd3, 0xca, 0x8f, 0xd5,
0xd3, 0x8e, 0x08, 0x1d, 0x91, 0x17, 0x30, 0x36, 0x6f, 0x6b, 0xf5, 0xec, 0x79, 0x1f, 0x5c, 0xed,
0x3d, 0xdc, 0x9b, 0x6a, 0x8b, 0xe9, 0x88, 0xac, 0x61, 0x6a, 0xfb, 0x72, 0x90, 0xe1, 0xe9, 0xa0,
0xde, 0xb7, 0xd1, 0x88, 0x30, 0x2f, 0x6f, 0x51, 0xf4, 0xac, 0x38, 0x2c, 0x62, 0x33, 0xae, 0x2b,
0xf7, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x9f, 0x5e, 0xb9, 0x34, 0x85, 0x04, 0x00, 0x00,
}

View file

@ -1259,12 +1259,19 @@ proto.cocorpc.UpdateRequest.prototype.hasNews = function() {
* @constructor
*/
proto.cocorpc.UpdateImpactResponse = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
jspb.Message.initialize(this, opt_data, 0, -1, proto.cocorpc.UpdateImpactResponse.repeatedFields_, null);
};
goog.inherits(proto.cocorpc.UpdateImpactResponse, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.cocorpc.UpdateImpactResponse.displayName = 'proto.cocorpc.UpdateImpactResponse';
}
/**
* List of repeated fields within this message type.
* @private {!Array<number>}
* @const
*/
proto.cocorpc.UpdateImpactResponse.repeatedFields_ = [1];
if (jspb.Message.GENERATE_TO_OBJECT) {
@ -1293,8 +1300,8 @@ proto.cocorpc.UpdateImpactResponse.prototype.toObject = function(opt_includeInst
*/
proto.cocorpc.UpdateImpactResponse.toObject = function(includeInstance, msg) {
var f, obj = {
replace: jspb.Message.getFieldWithDefault(msg, 1, false),
impacts: (f = msg.getImpacts()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f)
replacesList: jspb.Message.getField(msg, 1),
changes: (f = msg.getChanges()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f)
};
if (includeInstance) {
@ -1332,13 +1339,13 @@ proto.cocorpc.UpdateImpactResponse.deserializeBinaryFromReader = function(msg, r
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {boolean} */ (reader.readBool());
msg.setReplace(value);
var value = /** @type {string} */ (reader.readString());
msg.addReplaces(value);
break;
case 2:
var value = new google_protobuf_struct_pb.Struct;
reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader);
msg.setImpacts(value);
msg.setChanges(value);
break;
default:
reader.skipField();
@ -1368,14 +1375,14 @@ proto.cocorpc.UpdateImpactResponse.prototype.serializeBinary = function() {
*/
proto.cocorpc.UpdateImpactResponse.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getReplace();
if (f) {
writer.writeBool(
f = message.getReplacesList();
if (f.length > 0) {
writer.writeRepeatedString(
1,
f
);
}
f = message.getImpacts();
f = message.getChanges();
if (f != null) {
writer.writeMessage(
2,
@ -1387,40 +1394,54 @@ proto.cocorpc.UpdateImpactResponse.serializeBinaryToWriter = function(message, w
/**
* optional bool replace = 1;
* Note that Boolean fields may be set to 0/1 when serialized from a Java server.
* You should avoid comparisons like {@code val === true/false} in those cases.
* @return {boolean}
* repeated string replaces = 1;
* If you change this array by adding, removing or replacing elements, or if you
* replace the array itself, then you must call the setter to update it.
* @return {!Array.<string>}
*/
proto.cocorpc.UpdateImpactResponse.prototype.getReplace = function() {
return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 1, false));
proto.cocorpc.UpdateImpactResponse.prototype.getReplacesList = function() {
return /** @type {!Array.<string>} */ (jspb.Message.getField(this, 1));
};
/** @param {boolean} value */
proto.cocorpc.UpdateImpactResponse.prototype.setReplace = function(value) {
jspb.Message.setField(this, 1, value);
/** @param {!Array.<string>} value */
proto.cocorpc.UpdateImpactResponse.prototype.setReplacesList = function(value) {
jspb.Message.setField(this, 1, value || []);
};
/**
* optional google.protobuf.Struct impacts = 2;
* @param {!string} value
* @param {number=} opt_index
*/
proto.cocorpc.UpdateImpactResponse.prototype.addReplaces = function(value, opt_index) {
jspb.Message.addToRepeatedField(this, 1, value, opt_index);
};
proto.cocorpc.UpdateImpactResponse.prototype.clearReplacesList = function() {
this.setReplacesList([]);
};
/**
* optional google.protobuf.Struct changes = 2;
* @return {?proto.google.protobuf.Struct}
*/
proto.cocorpc.UpdateImpactResponse.prototype.getImpacts = function() {
proto.cocorpc.UpdateImpactResponse.prototype.getChanges = function() {
return /** @type{?proto.google.protobuf.Struct} */ (
jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 2));
};
/** @param {?proto.google.protobuf.Struct|undefined} value */
proto.cocorpc.UpdateImpactResponse.prototype.setImpacts = function(value) {
proto.cocorpc.UpdateImpactResponse.prototype.setChanges = function(value) {
jspb.Message.setWrapperField(this, 2, value);
};
proto.cocorpc.UpdateImpactResponse.prototype.clearImpacts = function() {
this.setImpacts(undefined);
proto.cocorpc.UpdateImpactResponse.prototype.clearChanges = function() {
this.setChanges(undefined);
};
@ -1428,7 +1449,7 @@ proto.cocorpc.UpdateImpactResponse.prototype.clearImpacts = function() {
* Returns whether this field is set.
* @return {!boolean}
*/
proto.cocorpc.UpdateImpactResponse.prototype.hasImpacts = function() {
proto.cocorpc.UpdateImpactResponse.prototype.hasChanges = function() {
return jspb.Message.getField(this, 2) != null;
};

View file

@ -64,8 +64,8 @@ message UpdateRequest {
}
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.
repeated string replaces = 1; // if this update requires a replacement, the set of properties triggering it.
google.protobuf.Struct changes = 2; // the set of properties that will be changed (but don't require a replacement).
}
message DeleteRequest {