WIP: outputs in plans

This commit is contained in:
Pat Gavlin 2021-05-04 14:52:11 -07:00
parent 24a0eb0274
commit 954bdc025b
7 changed files with 64 additions and 12 deletions

View file

@ -145,6 +145,30 @@ func (m *resourceMap) mapRange(callback func(urn resource.URN, state *resource.S
})
}
type resourcePlans struct {
m sync.RWMutex
plans map[resource.URN]*ResourcePlan
}
func (m *resourcePlans) set(urn resource.URN, plan *ResourcePlan) {
m.m.Lock()
defer m.m.Unlock()
m.plans[urn] = plan
}
func (m *resourcePlans) get(urn resource.URN) (*ResourcePlan, bool) {
m.m.RLock()
defer m.m.RUnlock()
p, ok := m.plans[urn]
return p, ok
}
func (m *resourcePlans) plan() Plan {
return m.plans
}
// A Deployment manages the iterative computation and execution of a deployment based on a stream of goal states.
// A running deployment emits events that indicate its progress. These events must be used to record the new state
// of the deployment target.
@ -164,7 +188,7 @@ type Deployment struct {
providers *providers.Registry // the provider registry for this deployment.
goals *goalMap // the set of resource goals generated by the deployment.
news *resourceMap // the set of new resources generated by the deployment
newPlan map[resource.URN]*ResourcePlan // the set of new resource plans.
newPlans *resourcePlans // the set of new resource plans.
}
// addDefaultProviders adds any necessary default provider definitions and references to the given snapshot. Version
@ -354,7 +378,7 @@ func NewDeployment(ctx *plugin.Context, target *Target, prev *Snapshot, plan Pla
providers: reg,
goals: newGoals,
news: newResources,
newPlan: Plan{},
newPlans: &resourcePlans{},
}, nil
}

View file

@ -295,7 +295,7 @@ func (ex *deploymentExecutor) Execute(callerCtx context.Context, opts Options, p
return nil, result.Bail()
}
return ex.deployment.newPlan, res
return ex.deployment.newPlans.plan(), res
}
func (ex *deploymentExecutor) performDeletes(
@ -466,7 +466,7 @@ func (ex *deploymentExecutor) importResources(callerCtx context.Context, opts Op
ex.reportExecResult("canceled", preview)
return nil, result.Bail()
}
return ex.deployment.newPlan, nil
return ex.deployment.newPlans.plan(), nil
}
// refresh refreshes the state of the base checkpoint file for the current deployment in memory.

View file

@ -21,8 +21,9 @@ type Plan map[resource.URN]*ResourcePlan
// A ResourcePlan represents the planned goal state and resource operations for a single resource. The operations are
// ordered.
type ResourcePlan struct {
Goal *resource.Goal
Ops []StepOp
Goal *resource.Goal
Ops []StepOp
Outputs resource.PropertyMap
}
func (rp *ResourcePlan) diffURNs(a, b []resource.URN) (message string, changed bool) {

View file

@ -296,6 +296,11 @@ func (se *stepExecutor) executeStep(workerID int, step Step) error {
if _, hasGoal := se.deployment.goals.get(newState.URN); hasGoal {
se.deployment.news.set(newState.URN, newState)
}
// Update the resource's outputs in the generated plan.
if resourcePlan, ok := se.deployment.newPlans.get(newState.URN); ok {
resourcePlan.Outputs = newState.Outputs
}
}
if events != nil {

View file

@ -170,12 +170,12 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, res
resourcePlan.Ops = resourcePlan.Ops[1:]
}
resourcePlan, ok := sg.deployment.newPlan[s.URN()]
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.newPlan[s.URN()] = resourcePlan
sg.deployment.newPlans.set(s.URN(), resourcePlan)
}
resourcePlan.Ops = append(resourcePlan.Ops, s.Op())
}

View file

@ -13,6 +13,15 @@ func SerializeResourcePlan(plan *deploy.ResourcePlan, enc config.Encrypter, show
return apitype.ResourcePlanV1{}, err
}
var outputs map[string]interface{}
if plan.Outputs != nil {
outs, err := SerializeProperties(plan.Outputs, enc, showSecrets)
if err != nil {
return apitype.ResourcePlanV1{}, err
}
outputs = outs
}
goal := apitype.GoalV1{
Type: plan.Goal.Type,
Name: plan.Goal.Name,
@ -37,8 +46,9 @@ func SerializeResourcePlan(plan *deploy.ResourcePlan, enc config.Encrypter, show
}
return apitype.ResourcePlanV1{
Goal: goal,
Steps: steps,
Goal: goal,
Steps: steps,
Outputs: outputs,
}, nil
}
@ -60,6 +70,15 @@ func DeserializeResourcePlan(plan apitype.ResourcePlanV1, dec config.Decrypter,
return nil, err
}
var outputs resource.PropertyMap
if plan.Outputs != nil {
outs, err := DeserializeProperties(plan.Outputs, dec, enc)
if err != nil {
return nil, err
}
outputs = outs
}
goal := &resource.Goal{
Type: plan.Goal.Type,
Name: plan.Goal.Name,
@ -84,8 +103,9 @@ func DeserializeResourcePlan(plan apitype.ResourcePlanV1, dec config.Decrypter,
}
return &deploy.ResourcePlan{
Goal: goal,
Ops: ops,
Goal: goal,
Ops: ops,
Outputs: outputs,
}, nil
}

View file

@ -47,6 +47,8 @@ type ResourcePlanV1 struct {
Goal GoalV1 `json:"goal"`
// The steps to be performed on the resource.
Steps []OpType `json:"steps,omitempty"`
// The proposed outputs for the resource, if any. Purely advisory.
Outputs map[string]interface{} `json:"state"`
}
// VersionedDeploymentPlan is a version number plus a JSON document. The version number describes what