Add proper Plan type

This commit is contained in:
Fraser Waters 2021-11-23 16:09:18 +00:00
parent 37b22b8d3c
commit 94bf0840df
20 changed files with 74 additions and 61 deletions

View file

@ -45,7 +45,7 @@ type ApplierOptions struct {
// Applier applies the changes specified by this update operation against the target stack.
type Applier func(ctx context.Context, kind apitype.UpdateKind, stack Stack, op UpdateOperation,
opts ApplierOptions, events chan<- engine.Event) (deploy.Plan, engine.ResourceChanges, result.Result)
opts ApplierOptions, events chan<- engine.Event) (*deploy.Plan, engine.ResourceChanges, result.Result)
func ActionLabel(kind apitype.UpdateKind, dryRun bool) string {
v := updateTextMap[kind]
@ -80,7 +80,7 @@ const (
)
func PreviewThenPrompt(ctx context.Context, kind apitype.UpdateKind, stack Stack,
op UpdateOperation, apply Applier) (deploy.Plan, engine.ResourceChanges, result.Result) {
op UpdateOperation, apply Applier) (*deploy.Plan, engine.ResourceChanges, result.Result) {
// create a channel to hear about the update events from the engine. this will be used so that
// we can build up the diff display in case the user asks to see the details of the diff

View file

@ -161,7 +161,7 @@ type Backend interface {
RenameStack(ctx context.Context, stack Stack, newName tokens.QName) (StackReference, error)
// Preview shows what would be updated given the current workspace's contents.
Preview(ctx context.Context, stack Stack, op UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result)
Preview(ctx context.Context, stack Stack, op UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result)
// Update updates the target stack with the current workspace's contents (config and code).
Update(ctx context.Context, stack Stack, op UpdateOperation) (engine.ResourceChanges, result.Result)
// Import imports resources into a stack.

View file

@ -461,7 +461,7 @@ func (b *localBackend) PackPolicies(
}
func (b *localBackend) Preview(ctx context.Context, stack backend.Stack,
op backend.UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result) {
op backend.UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result) {
if cmdutil.IsTruthy(os.Getenv(PulumiFilestateLockingEnvVar)) {
err := b.Lock(ctx, stack.Ref())
@ -548,7 +548,7 @@ func (b *localBackend) Watch(ctx context.Context, stack backend.Stack,
func (b *localBackend) apply(
ctx context.Context, kind apitype.UpdateKind, stack backend.Stack,
op backend.UpdateOperation, opts backend.ApplierOptions,
events chan<- engine.Event) (deploy.Plan, engine.ResourceChanges, result.Result) {
events chan<- engine.Event) (*deploy.Plan, engine.ResourceChanges, result.Result) {
stackRef := stack.Ref()
stackName := stackRef.Name()
@ -604,7 +604,7 @@ func (b *localBackend) apply(
// Perform the update
start := time.Now().Unix()
var plan deploy.Plan
var plan *deploy.Plan
var changes engine.ResourceChanges
var updateRes result.Result
switch kind {

View file

@ -66,7 +66,7 @@ func (s *localStack) Rename(ctx context.Context, newName tokens.QName) (backend.
func (s *localStack) Preview(
ctx context.Context,
op backend.UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result) {
op backend.UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result) {
return backend.PreviewStack(ctx, s, op)
}

View file

@ -828,7 +828,7 @@ func (b *cloudBackend) RenameStack(ctx context.Context, stack backend.Stack,
}
func (b *cloudBackend) Preview(ctx context.Context, stack backend.Stack,
op backend.UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result) {
op backend.UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result) {
// We can skip PreviewtThenPromptThenExecute, and just go straight to Execute.
opts := backend.ApplierOptions{
DryRun: true,
@ -931,7 +931,7 @@ func (b *cloudBackend) createAndStartUpdate(
func (b *cloudBackend) apply(
ctx context.Context, kind apitype.UpdateKind, stack backend.Stack,
op backend.UpdateOperation, opts backend.ApplierOptions,
events chan<- engine.Event) (deploy.Plan, engine.ResourceChanges, result.Result) {
events chan<- engine.Event) (*deploy.Plan, engine.ResourceChanges, result.Result) {
actionLabel := backend.ActionLabel(kind, opts.DryRun)
@ -985,7 +985,7 @@ func (b *cloudBackend) query(ctx context.Context, op backend.QueryOperation,
func (b *cloudBackend) runEngineAction(
ctx context.Context, kind apitype.UpdateKind, stackRef backend.StackReference,
op backend.UpdateOperation, update client.UpdateIdentifier, token string,
callerEventsOpt chan<- engine.Event, dryRun bool) (deploy.Plan, engine.ResourceChanges, result.Result) {
callerEventsOpt chan<- engine.Event, dryRun bool) (*deploy.Plan, engine.ResourceChanges, result.Result) {
contract.Assertf(token != "", "persisted actions require a token")
u, err := b.newUpdate(ctx, stackRef, op, update, token)
@ -1039,7 +1039,7 @@ func (b *cloudBackend) runEngineAction(
engineCtx.ParentSpan = parentSpan.Context()
}
var plan deploy.Plan
var plan *deploy.Plan
var changes engine.ResourceChanges
var res result.Result
switch kind {

View file

@ -142,7 +142,7 @@ func (s *cloudStack) Rename(ctx context.Context, newName tokens.QName) (backend.
func (s *cloudStack) Preview(
ctx context.Context,
op backend.UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result) {
op backend.UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result) {
return backend.PreviewStack(ctx, s, op)
}

View file

@ -57,7 +57,7 @@ type MockBackend struct {
LogoutAllF func() error
CurrentUserF func() (string, error)
PreviewF func(context.Context, Stack,
UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result)
UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result)
UpdateF func(context.Context, Stack,
UpdateOperation) (engine.ResourceChanges, result.Result)
ImportF func(context.Context, Stack,
@ -180,7 +180,7 @@ func (be *MockBackend) GetStackCrypter(stackRef StackReference) (config.Crypter,
}
func (be *MockBackend) Preview(ctx context.Context, stack Stack,
op UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result) {
op UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result) {
if be.PreviewF != nil {
return be.PreviewF(ctx, stack, op)
@ -335,7 +335,7 @@ type MockStack struct {
ConfigF func() config.Map
SnapshotF func(ctx context.Context) (*deploy.Snapshot, error)
BackendF func() Backend
PreviewF func(ctx context.Context, op UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result)
PreviewF func(ctx context.Context, op UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result)
UpdateF func(ctx context.Context, op UpdateOperation) (engine.ResourceChanges, result.Result)
ImportF func(ctx context.Context, op UpdateOperation,
imports []deploy.Import) (engine.ResourceChanges, result.Result)
@ -383,7 +383,7 @@ func (ms *MockStack) Backend() Backend {
func (ms *MockStack) Preview(
ctx context.Context,
op UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result) {
op UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result) {
if ms.PreviewF != nil {
return ms.PreviewF(ctx, op)

View file

@ -38,7 +38,7 @@ type Stack interface {
Backend() Backend // the backend this stack belongs to.
// Preview changes to this stack.
Preview(ctx context.Context, op UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result)
Preview(ctx context.Context, op UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result)
// Update this stack.
Update(ctx context.Context, op UpdateOperation) (engine.ResourceChanges, result.Result)
// Import resources into this stack.
@ -76,7 +76,7 @@ func RenameStack(ctx context.Context, s Stack, newName tokens.QName) (StackRefer
func PreviewStack(
ctx context.Context,
s Stack,
op UpdateOperation) (deploy.Plan, engine.ResourceChanges, result.Result) {
op UpdateOperation) (*deploy.Plan, engine.ResourceChanges, result.Result) {
return s.Backend().Preview(ctx, s, op)
}

View file

@ -869,7 +869,7 @@ func getRefreshOption(proj *workspace.Project, refresh string) (bool, error) {
return false, nil
}
func writePlan(path string, plan deploy.Plan, enc config.Encrypter, showSecrets bool) error {
func writePlan(path string, plan *deploy.Plan, enc config.Encrypter, showSecrets bool) error {
f, err := os.Create(path)
if err != nil {
return err
@ -883,7 +883,7 @@ func writePlan(path string, plan deploy.Plan, enc config.Encrypter, showSecrets
return json.NewEncoder(f).Encode(deploymentPlan)
}
func readPlan(path string, dec config.Decrypter, enc config.Encrypter) (deploy.Plan, error) {
func readPlan(path string, dec config.Decrypter, enc config.Encrypter) (*deploy.Plan, error) {
f, err := os.Open(path)
if err != nil {
return nil, err

View file

@ -218,7 +218,7 @@ type runActions interface {
// run executes the deployment. It is primarily responsible for handling cancellation.
func (deployment *deployment) run(cancelCtx *Context, actions runActions, policyPacks map[string]string,
preview bool) (deploy.Plan, ResourceChanges, result.Result) {
preview bool) (*deploy.Plan, ResourceChanges, result.Result) {
// Change into the plugin context's working directory.
chdir, err := fsutil.Chdir(deployment.Plugctx.Pwd)
@ -242,7 +242,7 @@ func (deployment *deployment) run(cancelCtx *Context, actions runActions, policy
start := time.Now()
done := make(chan bool)
var newPlan deploy.Plan
var newPlan *deploy.Plan
var walkResult result.Result
go func() {
opts := deploy.Options{

View file

@ -27,7 +27,7 @@ func Destroy(
u UpdateInfo,
ctx *Context,
opts UpdateOptions,
dryRun bool) (deploy.Plan, ResourceChanges, result.Result) {
dryRun bool) (*deploy.Plan, ResourceChanges, result.Result) {
contract.Require(u != nil, "u")
contract.Require(ctx != nil, "ctx")

View file

@ -21,7 +21,7 @@ import (
)
func Import(u UpdateInfo, ctx *Context, opts UpdateOptions, imports []deploy.Import,
dryRun bool) (deploy.Plan, ResourceChanges, result.Result) {
dryRun bool) (*deploy.Plan, ResourceChanges, result.Result) {
contract.Require(u != nil, "u")
contract.Require(ctx != nil, "ctx")

View file

@ -27,7 +27,7 @@ func Refresh(
u UpdateInfo,
ctx *Context,
opts UpdateOptions,
dryRun bool) (deploy.Plan, ResourceChanges, result.Result) {
dryRun bool) (*deploy.Plan, ResourceChanges, result.Result) {
contract.Require(u != nil, "u")
contract.Require(ctx != nil, "ctx")

View file

@ -148,7 +148,7 @@ type UpdateOptions struct {
Host plugin.Host
// The plan to use for the update, if any.
Plan deploy.Plan
Plan *deploy.Plan
}
// ResourceChanges contains the aggregate resource changes by operation type.
@ -168,7 +168,7 @@ func (changes ResourceChanges) HasChanges() bool {
return c > 0
}
func Update(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (deploy.Plan, ResourceChanges, result.Result) {
func Update(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (*deploy.Plan, ResourceChanges, result.Result) {
contract.Require(u != nil, "update")
contract.Require(ctx != nil, "ctx")
@ -423,7 +423,7 @@ func newUpdateSource(
}
func update(ctx *Context, info *deploymentContext, opts deploymentOptions,
preview bool) (deploy.Plan, ResourceChanges, result.Result) {
preview bool) (*deploy.Plan, ResourceChanges, result.Result) {
// Refresh and Import do not execute Policy Packs.
policies := map[string]string{}

View file

@ -152,7 +152,7 @@ type resourcePlans struct {
func newResourcePlan() *resourcePlans {
return &resourcePlans{
plans: make(Plan),
plans: NewPlan(),
}
}
@ -160,19 +160,19 @@ func (m *resourcePlans) set(urn resource.URN, plan *ResourcePlan) {
m.m.Lock()
defer m.m.Unlock()
m.plans[urn] = plan
m.plans.ResourcePlans[urn] = plan
}
func (m *resourcePlans) get(urn resource.URN) (*ResourcePlan, bool) {
m.m.RLock()
defer m.m.RUnlock()
p, ok := m.plans[urn]
p, ok := m.plans.ResourcePlans[urn]
return p, ok
}
func (m *resourcePlans) plan() Plan {
return m.plans
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.
@ -183,7 +183,7 @@ type Deployment struct {
target *Target // the deployment target.
prev *Snapshot // the old resource snapshot for comparison.
olds map[resource.URN]*resource.State // a map of all old resources.
plan Plan // a map of all planned resource changes, if any.
plan *Plan // a map of all planned resource changes, if any.
imports []Import // resources to import, if this is an import deployment.
isImport bool // true if this is an import deployment.
schemaLoader schema.Loader // the schema cache for this deployment, if any.
@ -331,7 +331,7 @@ func buildResourceMap(prev *Snapshot, preview bool) ([]*resource.State, map[reso
//
// Note that a deployment uses internal concurrency and parallelism in various ways, so it must be closed if for some
// reason it isn't carried out to its final conclusion. This will result in cancellation and reclamation of resources.
func NewDeployment(ctx *plugin.Context, target *Target, prev *Snapshot, plan Plan, source Source,
func NewDeployment(ctx *plugin.Context, target *Target, prev *Snapshot, plan *Plan, source Source,
localPolicyPackPaths []string, preview bool, backendClient BackendClient) (*Deployment, error) {
contract.Assert(ctx != nil)
@ -439,7 +439,7 @@ func (d *Deployment) generateEventURN(event SourceEvent) resource.URN {
}
// Execute executes a deployment to completion, using the given cancellation context and running a preview or update.
func (d *Deployment) Execute(ctx context.Context, opts Options, preview bool) (Plan, result.Result) {
func (d *Deployment) Execute(ctx context.Context, opts Options, preview bool) (*Plan, result.Result) {
deploymentExec := &deploymentExecutor{deployment: d}
return deploymentExec.Execute(ctx, opts, preview)
}

View file

@ -114,7 +114,7 @@ func (ex *deploymentExecutor) reportError(urn resource.URN, err error) {
// Execute executes a deployment to completion, using the given cancellation context and running a preview
// or update.
func (ex *deploymentExecutor) Execute(callerCtx context.Context, opts Options, preview bool) (Plan, result.Result) {
func (ex *deploymentExecutor) Execute(callerCtx context.Context, opts Options, preview bool) (*Plan, result.Result) {
// Set up a goroutine that will signal cancellation to the deployment's plugins if the caller context is cancelled.
// We do not hang this off of the context we create below because we do not want the failure of a single step to
// cause other steps to fail.
@ -270,7 +270,7 @@ func (ex *deploymentExecutor) Execute(callerCtx context.Context, opts Options, p
// Check that we did operations for everything expected in the plan. We mutate ResourcePlan.Ops as we run
// so by the time we get here everything in the map should have an empty ops list
if ex.deployment.plan != nil {
for urn, resourcePlan := range ex.deployment.plan {
for urn, resourcePlan := range ex.deployment.plan.ResourcePlans {
if len(resourcePlan.Ops) != 0 {
return nil, result.Errorf("Expected resource operations for %v but none were seen.", urn)
}
@ -445,7 +445,7 @@ func (ex *deploymentExecutor) retirePendingDeletes(callerCtx context.Context, op
func (ex *deploymentExecutor) importResources(
callerCtx context.Context,
opts Options,
preview bool) (Plan, result.Result) {
preview bool) (*Plan, result.Result) {
if len(ex.deployment.imports) == 0 {
return nil, nil

View file

@ -4,9 +4,12 @@ import (
"fmt"
"sort"
"strings"
"time"
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/v3/version"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
@ -17,7 +20,28 @@ import (
// accept any value (including no value) as valid. For operations, a same step is allowed in place of an update or
// a replace step, and an update is allowed in place of a replace step. All resource options are required to match
// exactly.
type Plan map[resource.URN]*ResourcePlan
type Plan struct {
ResourcePlans map[resource.URN]*ResourcePlan
Manifest Manifest
// Any environment variables that were set when the plan was created. Values are encrypted.
EnvironmentVariables map[string][]byte
// The configuration in use during the plan.
Config map[string]config.Value
}
func NewPlan() Plan {
manifest := Manifest{
Time: time.Now(),
Version: version.Version,
// Plugins: sm.plugins, - Explicitly dropped, since we don't use the plugin list in the manifest anymore.
}
manifest.Magic = manifest.NewMagic()
return Plan{
ResourcePlans: make(map[resource.URN]*ResourcePlan),
Manifest: manifest,
}
}
// Goal is a desired state for a resource object. Normally it represents a subset of the resource's state expressed by
// a program, however if Output is true, it represents a more complete, post-deployment view of the state.

View file

@ -165,7 +165,7 @@ func (se *stepExecutor) ExecuteRegisterResourceOutputs(e RegisterResourceOutputs
// If a plan is present check that these outputs match what we recorded before
if se.deployment.plan != nil {
resourcePlan, ok := se.deployment.plan[urn]
resourcePlan, ok := se.deployment.plan.ResourcePlans[urn]
if !ok {
return result.FromError(fmt.Errorf("no plan for resource %v", urn))
} else {

View file

@ -183,7 +183,7 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, res
// 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 {
if resourcePlan, ok := sg.deployment.plan.ResourcePlans[s.URN()]; ok {
if len(resourcePlan.Ops) == 0 {
return nil, result.Errorf("%v is not allowed by the plan: no more steps were expected for this resource", s.Op())
}
@ -314,7 +314,7 @@ func (sg *stepGenerator) generateSteps(event RegisterResourceEvent) ([]Step, res
// If there is a plan for this resource, validate that the program goal conforms to the plan.
// If theres no plan for this resource check that nothing has been changed.
if sg.deployment.plan != nil {
resourcePlan, ok := sg.deployment.plan[urn]
resourcePlan, ok := sg.deployment.plan.ResourcePlans[urn]
if !ok {
if old == nil {
// We could error here, but we'll trigger an error later on anyway that Create isn't valid here
@ -817,7 +817,7 @@ func (sg *stepGenerator) GenerateDeletes(targetsOpt map[resource.URN]bool) ([]St
// Check each proposed delete against the relevant resource plan
for _, s := range dels {
if sg.deployment.plan != nil {
if resourcePlan, ok := sg.deployment.plan[s.URN()]; ok {
if resourcePlan, ok := sg.deployment.plan.ResourcePlans[s.URN()]; ok {
if len(resourcePlan.Ops) == 0 {
return nil, result.Errorf("%v is not allowed by the plan: no more steps were expected for this resource", s.Op())
}

View file

@ -1,10 +1,7 @@
package stack
import (
"time"
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
"github.com/pulumi/pulumi/pkg/v3/version"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
@ -71,9 +68,9 @@ func SerializeResourcePlan(
}, nil
}
func SerializePlan(plan deploy.Plan, enc config.Encrypter, showSecrets bool) (apitype.DeploymentPlanV1, error) {
func SerializePlan(plan *deploy.Plan, enc config.Encrypter, showSecrets bool) (apitype.DeploymentPlanV1, error) {
resourcePlans := map[resource.URN]apitype.ResourcePlanV1{}
for urn, plan := range plan {
for urn, plan := range plan.ResourcePlans {
serializedPlan, err := SerializeResourcePlan(plan, enc, showSecrets)
if err != nil {
return apitype.DeploymentPlanV1{}, err
@ -81,16 +78,8 @@ func SerializePlan(plan deploy.Plan, enc config.Encrypter, showSecrets bool) (ap
resourcePlans[urn] = serializedPlan
}
// Bit odd this isn't part of deploy.Plan but that's just a map right now. We need to change that to track config and things so we'll move this then.
manifest := deploy.Manifest{
Time: time.Now(),
Version: version.Version,
// Plugins: sm.plugins, - Explicitly dropped, since we don't use the plugin list in the manifest anymore.
}
manifest.Magic = manifest.NewMagic()
return apitype.DeploymentPlanV1{
Manifest: manifest.Serialize(),
Manifest: plan.Manifest.Serialize(),
ResourcePlans: resourcePlans,
}, nil
}
@ -151,14 +140,14 @@ func DeserializeResourcePlan(
}, nil
}
func DeserializePlan(plan apitype.DeploymentPlanV1, dec config.Decrypter, enc config.Encrypter) (deploy.Plan, error) {
deserializedPlan := deploy.Plan{}
func DeserializePlan(plan apitype.DeploymentPlanV1, dec config.Decrypter, enc config.Encrypter) (*deploy.Plan, error) {
deserializedPlan := &deploy.Plan{}
for urn, resourcePlan := range plan.ResourcePlans {
deserializedResourcePlan, err := DeserializeResourcePlan(resourcePlan, dec, enc)
if err != nil {
return nil, err
}
deserializedPlan[urn] = deserializedResourcePlan
deserializedPlan.ResourcePlans[urn] = deserializedResourcePlan
}
return deserializedPlan, nil
}