From 3b9cf5b64827b0db3f8ca3b9e192816c604866f8 Mon Sep 17 00:00:00 2001 From: Fraser Waters Date: Tue, 16 Nov 2021 16:26:51 +0000 Subject: [PATCH] Adds/Deletes/Updates --- pkg/resource/deploy/plan.go | 32 ++++++++++++++++++++++++++- pkg/resource/deploy/step_generator.go | 13 ++++++----- pkg/resource/stack/plan.go | 29 +++++++++++++++++++----- sdk/go/common/apitype/plan.go | 8 +++++-- 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/pkg/resource/deploy/plan.go b/pkg/resource/deploy/plan.go index 29d9614d7..76c9dd254 100644 --- a/pkg/resource/deploy/plan.go +++ b/pkg/resource/deploy/plan.go @@ -41,6 +41,32 @@ type GoalPlan struct { CustomTimeouts resource.CustomTimeouts // an optional config object for resource options } +func NewGoalPlan(oldOutputs resource.PropertyMap, goal *resource.Goal) *GoalPlan { + if goal == nil { + return nil + } + + return &GoalPlan{ + Type: goal.Type, + Name: goal.Name, + Custom: goal.Custom, + Adds: nil, + Deletes: nil, + Updates: nil, + Parent: goal.Parent, + Protect: goal.Protect, + Dependencies: goal.Dependencies, + Provider: goal.Provider, + PropertyDependencies: goal.PropertyDependencies, + DeleteBeforeReplace: goal.DeleteBeforeReplace, + IgnoreChanges: goal.IgnoreChanges, + AdditionalSecretOutputs: goal.AdditionalSecretOutputs, + Aliases: goal.Aliases, + ID: goal.ID, + CustomTimeouts: goal.CustomTimeouts, + } +} + // A ResourcePlan represents the planned goal state and resource operations for a single resource. The operations are // ordered. type ResourcePlan struct { @@ -119,7 +145,11 @@ func (rp *ResourcePlan) diffPropertyDependencies(a, b map[resource.PropertyKey][ return nil } -func (rp *ResourcePlan) checkGoal(oldOutputs resource.PropertyMap, newInputs resource.PropertyMap, programGoal *resource.Goal) error { +func (rp *ResourcePlan) checkGoal( + oldOutputs resource.PropertyMap, + newInputs resource.PropertyMap, + programGoal *resource.Goal) error { + contract.Assert(programGoal != nil) contract.Assert(newInputs != nil) // rp.Goal may be nil, but if it isn't Type and Name should match diff --git a/pkg/resource/deploy/step_generator.go b/pkg/resource/deploy/step_generator.go index 4d1208cae..e704b46d5 100644 --- a/pkg/resource/deploy/step_generator.go +++ b/pkg/resource/deploy/step_generator.go @@ -155,7 +155,7 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, res return nil, res } - // Check each proposed step against the relevant resource plan, if any, and generate any output resource plans. + // Check each proposed step against the relevant resource plan, if any for _, s := range steps { if sg.deployment.plan != nil { if resourcePlan, ok := sg.deployment.plan[s.URN()]; ok { @@ -175,10 +175,7 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, res resourcePlan, ok := sg.deployment.newPlans.get(s.URN()) if !ok { - // TODO(pdg-plan): using the program inputs means that non-determinism could sneak in as part of default - // application. However, it is necessary in the face of computed inputs. - resourcePlan = &ResourcePlan{Goal: event.Goal()} - sg.deployment.newPlans.set(s.URN(), resourcePlan) + return nil, result.Errorf("Expected a new resource plan for %v", s.URN()) } resourcePlan.Ops = append(resourcePlan.Ops, s.Op()) } @@ -276,6 +273,12 @@ func (sg *stepGenerator) generateSteps(event RegisterResourceEvent) ([]Step, res inputs = processedInputs } + // Generate the output goal plan + // TODO(pdg-plan): using the program inputs means that non-determinism could sneak in as part of default + // application. However, it is necessary in the face of computed inputs. + newResourcePlan := &ResourcePlan{Goal: NewGoalPlan(oldOutputs, goal)} + sg.deployment.newPlans.set(urn, newResourcePlan) + // If there is a plan for this resource, validate that the program goal conforms to the plan. if sg.deployment.plan != nil { resourcePlan, ok := sg.deployment.plan[urn] diff --git a/pkg/resource/stack/plan.go b/pkg/resource/stack/plan.go index 4627791a7..62b9821bb 100644 --- a/pkg/resource/stack/plan.go +++ b/pkg/resource/stack/plan.go @@ -12,11 +12,21 @@ func SerializeResourcePlan( enc config.Encrypter, showSecrets bool) (apitype.ResourcePlanV1, error) { - properties, err := SerializeProperties(plan.Goal.Properties, enc, showSecrets) + adds, err := SerializeProperties(plan.Goal.Adds, enc, showSecrets) if err != nil { return apitype.ResourcePlanV1{}, err } + updates, err := SerializeProperties(plan.Goal.Adds, enc, showSecrets) + if err != nil { + return apitype.ResourcePlanV1{}, err + } + + deletes := make([]string, len(plan.Goal.Deletes)) + for i := range deletes { + deletes[i] = string(plan.Goal.Deletes[i]) + } + var outputs map[string]interface{} if plan.Outputs != nil { outs, err := SerializeProperties(plan.Outputs, enc, showSecrets) @@ -30,7 +40,9 @@ func SerializeResourcePlan( Type: plan.Goal.Type, Name: plan.Goal.Name, Custom: plan.Goal.Custom, - Properties: properties, + Adds: adds, + Deletes: deletes, + Updates: updates, Parent: plan.Goal.Parent, Protect: plan.Goal.Protect, Dependencies: plan.Goal.Dependencies, @@ -73,7 +85,12 @@ func DeserializeResourcePlan( dec config.Decrypter, enc config.Encrypter) (*deploy.ResourcePlan, error) { - properties, err := DeserializeProperties(plan.Goal.Properties, dec, enc) + adds, err := DeserializeProperties(plan.Goal.Adds, dec, enc) + if err != nil { + return nil, err + } + + updates, err := DeserializeProperties(plan.Goal.Updates, dec, enc) if err != nil { return nil, err } @@ -87,11 +104,13 @@ func DeserializeResourcePlan( outputs = outs } - goal := &resource.Goal{ + goal := &deploy.GoalPlan{ Type: plan.Goal.Type, Name: plan.Goal.Name, Custom: plan.Goal.Custom, - Properties: properties, + Adds: adds, + Deletes: nil, + Updates: updates, Parent: plan.Goal.Parent, Protect: plan.Goal.Protect, Dependencies: plan.Goal.Dependencies, diff --git a/sdk/go/common/apitype/plan.go b/sdk/go/common/apitype/plan.go index 439336f1a..ad7d3dadb 100644 --- a/sdk/go/common/apitype/plan.go +++ b/sdk/go/common/apitype/plan.go @@ -15,8 +15,12 @@ type GoalV1 struct { Name tokens.QName `json:"name"` // true if this resource is custom, managed by a plugin. Custom bool `json:"custom"` - // the resource's input properties. - Properties map[string]interface{} `json:"properties,omitempty"` + // the resource properties that will be added. + Adds map[string]interface{} `json:"adds,omitempty"` + // the resource properties that will be deleted. + Deletes []string `json:"deletes,omitempty"` + // the resource properties that will be updated. + Updates map[string]interface{} `json:"updates,omitempty"` // an optional parent URN for this resource. Parent resource.URN `json:"parent,omitempty"` // true to protect this resource from deletion.