Plumb plans through the CLI.
This commit is contained in:
parent
33e8980cee
commit
e101f80839
|
@ -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) (engine.ResourceChanges, result.Result)
|
||||
opts ApplierOptions, events chan<- engine.Event) (engine.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) (engine.ResourceChanges, result.Result) {
|
||||
op UpdateOperation, apply Applier) (engine.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
|
||||
|
||||
|
@ -112,22 +112,22 @@ func PreviewThenPrompt(ctx context.Context, kind apitype.UpdateKind, stack Stack
|
|||
ShowLink: true,
|
||||
}
|
||||
|
||||
changes, res := apply(ctx, kind, stack, op, opts, eventsChannel)
|
||||
plan, changes, res := apply(ctx, kind, stack, op, opts, eventsChannel)
|
||||
if res != nil {
|
||||
close(eventsChannel)
|
||||
return changes, res
|
||||
return plan, changes, res
|
||||
}
|
||||
|
||||
// If there are no changes, or we're auto-approving or just previewing, we can skip the confirmation prompt.
|
||||
if op.Opts.AutoApprove || kind == apitype.PreviewUpdate {
|
||||
close(eventsChannel)
|
||||
return changes, nil
|
||||
return plan, changes, nil
|
||||
}
|
||||
|
||||
// Otherwise, ensure the user wants to proceed.
|
||||
res = confirmBeforeUpdating(kind, stack, events, op.Opts)
|
||||
close(eventsChannel)
|
||||
return changes, res
|
||||
return plan, changes, res
|
||||
}
|
||||
|
||||
// confirmBeforeUpdating asks the user whether to proceed. A nil error means yes.
|
||||
|
@ -197,10 +197,13 @@ func PreviewThenPromptThenExecute(ctx context.Context, kind apitype.UpdateKind,
|
|||
// Preview the operation to the user and ask them if they want to proceed.
|
||||
|
||||
if !op.Opts.SkipPreview {
|
||||
changes, res := PreviewThenPrompt(ctx, kind, stack, op, apply)
|
||||
plan, changes, res := PreviewThenPrompt(ctx, kind, stack, op, apply)
|
||||
if res != nil || kind == apitype.PreviewUpdate {
|
||||
return changes, res
|
||||
}
|
||||
|
||||
// TODO(pdg-plan): should this check for an existing plan?
|
||||
op.Opts.Engine.Plan = plan
|
||||
}
|
||||
|
||||
// Perform the change (!DryRun) and show the cloud link to the result.
|
||||
|
@ -209,7 +212,8 @@ func PreviewThenPromptThenExecute(ctx context.Context, kind apitype.UpdateKind,
|
|||
DryRun: false,
|
||||
ShowLink: true,
|
||||
}
|
||||
return apply(ctx, kind, stack, op, opts, nil /*events*/)
|
||||
_, changes, res := apply(ctx, kind, stack, op, opts, nil /*events*/)
|
||||
return changes, res
|
||||
}
|
||||
|
||||
func createDiff(updateKind apitype.UpdateKind, events []engine.Event, displayOpts display.Options) string {
|
||||
|
|
|
@ -162,7 +162,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) (engine.ResourceChanges, result.Result)
|
||||
Preview(ctx context.Context, stack Stack, op UpdateOperation) (engine.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.
|
||||
|
|
|
@ -459,7 +459,7 @@ func (b *localBackend) PackPolicies(
|
|||
}
|
||||
|
||||
func (b *localBackend) Preview(ctx context.Context, stack backend.Stack,
|
||||
op backend.UpdateOperation) (engine.ResourceChanges, result.Result) {
|
||||
op backend.UpdateOperation) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
|
||||
if cmdutil.IsTruthy(os.Getenv(PulumiFilestateLockingEnvVar)) {
|
||||
err := b.Lock(ctx, stack.Ref())
|
||||
|
@ -546,7 +546,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) (engine.ResourceChanges, result.Result) {
|
||||
events chan<- engine.Event) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
|
||||
stackRef := stack.Ref()
|
||||
stackName := stackRef.Name()
|
||||
|
@ -561,7 +561,7 @@ func (b *localBackend) apply(
|
|||
// Start the update.
|
||||
update, err := b.newUpdate(stackName, op)
|
||||
if err != nil {
|
||||
return nil, result.FromError(err)
|
||||
return nil, nil, result.FromError(err)
|
||||
}
|
||||
|
||||
// Spawn a display loop to show events on the CLI.
|
||||
|
@ -602,19 +602,20 @@ func (b *localBackend) apply(
|
|||
|
||||
// Perform the update
|
||||
start := time.Now().Unix()
|
||||
var plan engine.Plan
|
||||
var changes engine.ResourceChanges
|
||||
var updateRes result.Result
|
||||
switch kind {
|
||||
case apitype.PreviewUpdate:
|
||||
changes, updateRes = engine.Update(update, engineCtx, op.Opts.Engine, true)
|
||||
plan, changes, updateRes = engine.Update(update, engineCtx, op.Opts.Engine, true)
|
||||
case apitype.UpdateUpdate:
|
||||
changes, updateRes = engine.Update(update, engineCtx, op.Opts.Engine, opts.DryRun)
|
||||
_, changes, updateRes = engine.Update(update, engineCtx, op.Opts.Engine, opts.DryRun)
|
||||
case apitype.ResourceImportUpdate:
|
||||
changes, updateRes = engine.Import(update, engineCtx, op.Opts.Engine, op.Imports, opts.DryRun)
|
||||
_, changes, updateRes = engine.Import(update, engineCtx, op.Opts.Engine, op.Imports, opts.DryRun)
|
||||
case apitype.RefreshUpdate:
|
||||
changes, updateRes = engine.Refresh(update, engineCtx, op.Opts.Engine, opts.DryRun)
|
||||
_, changes, updateRes = engine.Refresh(update, engineCtx, op.Opts.Engine, opts.DryRun)
|
||||
case apitype.DestroyUpdate:
|
||||
changes, updateRes = engine.Destroy(update, engineCtx, op.Opts.Engine, opts.DryRun)
|
||||
_, changes, updateRes = engine.Destroy(update, engineCtx, op.Opts.Engine, opts.DryRun)
|
||||
default:
|
||||
contract.Failf("Unrecognized update kind: %s", kind)
|
||||
}
|
||||
|
@ -658,16 +659,16 @@ func (b *localBackend) apply(
|
|||
|
||||
if updateRes != nil {
|
||||
// We swallow saveErr and backupErr as they are less important than the updateErr.
|
||||
return changes, updateRes
|
||||
return plan, changes, updateRes
|
||||
}
|
||||
|
||||
if saveErr != nil {
|
||||
// We swallow backupErr as it is less important than the saveErr.
|
||||
return changes, result.FromError(errors.Wrap(saveErr, "saving update info"))
|
||||
return plan, changes, result.FromError(errors.Wrap(saveErr, "saving update info"))
|
||||
}
|
||||
|
||||
if backupErr != nil {
|
||||
return changes, result.FromError(errors.Wrap(backupErr, "saving backup"))
|
||||
return plan, changes, result.FromError(errors.Wrap(backupErr, "saving backup"))
|
||||
}
|
||||
|
||||
// Make sure to print a link to the stack's checkpoint before exiting.
|
||||
|
@ -703,7 +704,7 @@ func (b *localBackend) apply(
|
|||
}
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
return plan, changes, nil
|
||||
}
|
||||
|
||||
// query executes a query program against the resource outputs of a locally hosted stack.
|
||||
|
|
|
@ -64,7 +64,7 @@ func (s *localStack) Rename(ctx context.Context, newName tokens.QName) (backend.
|
|||
return backend.RenameStack(ctx, s, newName)
|
||||
}
|
||||
|
||||
func (s *localStack) Preview(ctx context.Context, op backend.UpdateOperation) (engine.ResourceChanges, result.Result) {
|
||||
func (s *localStack) Preview(ctx context.Context, op backend.UpdateOperation) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
return backend.PreviewStack(ctx, s, op)
|
||||
}
|
||||
|
||||
|
|
|
@ -824,7 +824,7 @@ func (b *cloudBackend) RenameStack(ctx context.Context, stack backend.Stack,
|
|||
}
|
||||
|
||||
func (b *cloudBackend) Preview(ctx context.Context, stack backend.Stack,
|
||||
op backend.UpdateOperation) (engine.ResourceChanges, result.Result) {
|
||||
op backend.UpdateOperation) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
// We can skip PreviewtThenPromptThenExecute, and just go straight to Execute.
|
||||
opts := backend.ApplierOptions{
|
||||
DryRun: true,
|
||||
|
@ -927,7 +927,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) (engine.ResourceChanges, result.Result) {
|
||||
events chan<- engine.Event) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
|
||||
actionLabel := backend.ActionLabel(kind, opts.DryRun)
|
||||
|
||||
|
@ -941,7 +941,7 @@ func (b *cloudBackend) apply(
|
|||
update, version, token, err :=
|
||||
b.createAndStartUpdate(ctx, kind, stack, &op, opts.DryRun)
|
||||
if err != nil {
|
||||
return nil, result.FromError(err)
|
||||
return nil, nil, result.FromError(err)
|
||||
}
|
||||
|
||||
if !op.Opts.Display.SuppressPermalink && opts.ShowLink && !op.Opts.Display.JSONDisplay {
|
||||
|
@ -981,12 +981,12 @@ 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) (engine.ResourceChanges, result.Result) {
|
||||
callerEventsOpt chan<- engine.Event, dryRun bool) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
|
||||
contract.Assertf(token != "", "persisted actions require a token")
|
||||
u, err := b.newUpdate(ctx, stackRef, op, update, token)
|
||||
if err != nil {
|
||||
return nil, result.FromError(err)
|
||||
return nil, nil, result.FromError(err)
|
||||
}
|
||||
|
||||
// displayEvents renders the event to the console and Pulumi service. The processor for the
|
||||
|
@ -1035,19 +1035,20 @@ func (b *cloudBackend) runEngineAction(
|
|||
engineCtx.ParentSpan = parentSpan.Context()
|
||||
}
|
||||
|
||||
var plan engine.Plan
|
||||
var changes engine.ResourceChanges
|
||||
var res result.Result
|
||||
switch kind {
|
||||
case apitype.PreviewUpdate:
|
||||
changes, res = engine.Update(u, engineCtx, op.Opts.Engine, true)
|
||||
plan, changes, res = engine.Update(u, engineCtx, op.Opts.Engine, true)
|
||||
case apitype.UpdateUpdate:
|
||||
changes, res = engine.Update(u, engineCtx, op.Opts.Engine, dryRun)
|
||||
_, changes, res = engine.Update(u, engineCtx, op.Opts.Engine, dryRun)
|
||||
case apitype.ResourceImportUpdate:
|
||||
changes, res = engine.Import(u, engineCtx, op.Opts.Engine, op.Imports, dryRun)
|
||||
_, changes, res = engine.Import(u, engineCtx, op.Opts.Engine, op.Imports, dryRun)
|
||||
case apitype.RefreshUpdate:
|
||||
changes, res = engine.Refresh(u, engineCtx, op.Opts.Engine, dryRun)
|
||||
_, changes, res = engine.Refresh(u, engineCtx, op.Opts.Engine, dryRun)
|
||||
case apitype.DestroyUpdate:
|
||||
changes, res = engine.Destroy(u, engineCtx, op.Opts.Engine, dryRun)
|
||||
_, changes, res = engine.Destroy(u, engineCtx, op.Opts.Engine, dryRun)
|
||||
default:
|
||||
contract.Failf("Unrecognized update kind: %s", kind)
|
||||
}
|
||||
|
@ -1073,7 +1074,7 @@ func (b *cloudBackend) runEngineAction(
|
|||
res = result.Merge(res, result.FromError(errors.Wrap(completeErr, "failed to complete update")))
|
||||
}
|
||||
|
||||
return changes, res
|
||||
return plan, changes, res
|
||||
}
|
||||
|
||||
func (b *cloudBackend) CancelCurrentUpdate(ctx context.Context, stackRef backend.StackReference) error {
|
||||
|
|
|
@ -140,7 +140,7 @@ func (s *cloudStack) Rename(ctx context.Context, newName tokens.QName) (backend.
|
|||
return backend.RenameStack(ctx, s, newName)
|
||||
}
|
||||
|
||||
func (s *cloudStack) Preview(ctx context.Context, op backend.UpdateOperation) (engine.ResourceChanges, result.Result) {
|
||||
func (s *cloudStack) Preview(ctx context.Context, op backend.UpdateOperation) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
return backend.PreviewStack(ctx, s, op)
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ type MockBackend struct {
|
|||
LogoutAllF func() error
|
||||
CurrentUserF func() (string, error)
|
||||
PreviewF func(context.Context, Stack,
|
||||
UpdateOperation) (engine.ResourceChanges, result.Result)
|
||||
UpdateOperation) (engine.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) (engine.ResourceChanges, result.Result) {
|
||||
op UpdateOperation) (engine.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) (engine.ResourceChanges, result.Result)
|
||||
PreviewF func(ctx context.Context, op UpdateOperation) (engine.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)
|
||||
|
@ -381,7 +381,7 @@ func (ms *MockStack) Backend() Backend {
|
|||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (ms *MockStack) Preview(ctx context.Context, op UpdateOperation) (engine.ResourceChanges, result.Result) {
|
||||
func (ms *MockStack) Preview(ctx context.Context, op UpdateOperation) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
if ms.PreviewF != nil {
|
||||
return ms.PreviewF(ctx, op)
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ type Stack interface {
|
|||
Backend() Backend // the backend this stack belongs to.
|
||||
|
||||
// Preview changes to this stack.
|
||||
Preview(ctx context.Context, op UpdateOperation) (engine.ResourceChanges, result.Result)
|
||||
Preview(ctx context.Context, op UpdateOperation) (engine.Plan, engine.ResourceChanges, result.Result)
|
||||
// Update this stack.
|
||||
Update(ctx context.Context, op UpdateOperation) (engine.ResourceChanges, result.Result)
|
||||
// Import resources into this stack.
|
||||
|
@ -75,7 +75,7 @@ func RenameStack(ctx context.Context, s Stack, newName tokens.QName) (StackRefer
|
|||
}
|
||||
|
||||
// PreviewStack previews changes to this stack.
|
||||
func PreviewStack(ctx context.Context, s Stack, op UpdateOperation) (engine.ResourceChanges, result.Result) {
|
||||
func PreviewStack(ctx context.Context, s Stack, op UpdateOperation) (engine.Plan, engine.ResourceChanges, result.Result) {
|
||||
return s.Backend().Preview(ctx, s, op)
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ func Watch(ctx context.Context, b Backend, stack Stack, op UpdateOperation,
|
|||
op.Opts.Display.Color.Colorize(colors.SpecImportant+"Updating..."+colors.Reset+"\n"))
|
||||
|
||||
// Perform the update operation
|
||||
_, res := apply(ctx, apitype.UpdateUpdate, stack, op, opts, nil)
|
||||
_, _, res := apply(ctx, apitype.UpdateUpdate, stack, op, opts, nil)
|
||||
if res != nil {
|
||||
logging.V(5).Infof("watch update failed: %v", res.Error())
|
||||
if res.Error() == context.Canceled {
|
||||
|
|
|
@ -36,6 +36,8 @@ func newPreviewCmd() *cobra.Command {
|
|||
var configArray []string
|
||||
var configPath bool
|
||||
var client string
|
||||
var planFilePath string
|
||||
var showSecrets bool
|
||||
|
||||
// Flags for engine.UpdateOptions.
|
||||
var jsonDisplay bool
|
||||
|
@ -182,7 +184,7 @@ func newPreviewCmd() *cobra.Command {
|
|||
Display: displayOpts,
|
||||
}
|
||||
|
||||
changes, res := s.Preview(commandContext(), backend.UpdateOperation{
|
||||
plan, changes, res := s.Preview(commandContext(), backend.UpdateOperation{
|
||||
Proj: proj,
|
||||
Root: root,
|
||||
M: m,
|
||||
|
@ -198,6 +200,16 @@ func newPreviewCmd() *cobra.Command {
|
|||
case expectNop && changes != nil && changes.HasChanges():
|
||||
return result.FromError(errors.New("error: no changes were expected but changes were proposed"))
|
||||
default:
|
||||
if planFilePath != "" {
|
||||
encrypter, err := sm.Encrypter()
|
||||
if err != nil {
|
||||
return result.FromError(err)
|
||||
}
|
||||
if err = writePlan(planFilePath, plan, encrypter, showSecrets); err != nil {
|
||||
return result.FromError(err)
|
||||
}
|
||||
// TODO(pdg-plan): emit a message about how to apply the plan
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
|
@ -221,6 +233,11 @@ func newPreviewCmd() *cobra.Command {
|
|||
cmd.PersistentFlags().BoolVar(
|
||||
&configPath, "config-path", false,
|
||||
"Config keys contain a path to a property in a map or list to set")
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&planFilePath, "plan", "",
|
||||
"Save the operations proposed by the preview to a plan file at the given path")
|
||||
cmd.Flags().BoolVarP(
|
||||
&showSecrets, "show-secrets", "", false, "Emit secrets in plaintext in the plan file. Defaults to `false`")
|
||||
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&client, "client", "", "The address of an existing language runtime host to connect to")
|
||||
|
|
|
@ -76,6 +76,7 @@ func newUpCmd() *cobra.Command {
|
|||
var replaces []string
|
||||
var targetReplaces []string
|
||||
var targetDependents bool
|
||||
var planFilePath string
|
||||
|
||||
// up implementation used when the source of the Pulumi program is in the current working directory.
|
||||
upWorkingDirectory := func(opts backend.UpdateOptions) result.Result {
|
||||
|
@ -143,6 +144,22 @@ func newUpCmd() *cobra.Command {
|
|||
TargetDependents: targetDependents,
|
||||
}
|
||||
|
||||
if planFilePath != "" {
|
||||
dec, err := sm.Decrypter()
|
||||
if err != nil {
|
||||
return result.FromError(err)
|
||||
}
|
||||
enc, err := sm.Encrypter()
|
||||
if err != nil {
|
||||
return result.FromError(err)
|
||||
}
|
||||
plan, err := readPlan(planFilePath, dec, enc)
|
||||
if err != nil {
|
||||
return result.FromError(err)
|
||||
}
|
||||
opts.Engine.Plan = plan
|
||||
}
|
||||
|
||||
changes, res := s.Update(commandContext(), backend.UpdateOperation{
|
||||
Proj: proj,
|
||||
Root: root,
|
||||
|
@ -505,6 +522,12 @@ func newUpCmd() *cobra.Command {
|
|||
&yes, "yes", "y", false,
|
||||
"Automatically approve and perform the update after previewing it")
|
||||
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&planFilePath, "plan", "",
|
||||
"Path to a plan file to use for the update. The update will use property values from the plan, and will not "+
|
||||
"perform operations that exceed its constraints (e.g. replacements instead of updates, or updates instead"+
|
||||
"of sames).")
|
||||
|
||||
if hasDebugCommands() {
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&eventLogPath, "event-log", "",
|
||||
|
|
|
@ -45,6 +45,7 @@ import (
|
|||
"github.com/pulumi/pulumi/pkg/v3/secrets/passphrase"
|
||||
"github.com/pulumi/pulumi/pkg/v3/util/cancel"
|
||||
"github.com/pulumi/pulumi/pkg/v3/util/tracing"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/constant"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/ciutil"
|
||||
|
@ -862,3 +863,31 @@ func getRefreshOption(proj *workspace.Project, refresh string) (bool, error) {
|
|||
// the default functionality right now is to always skip a refresh
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func writePlan(path string, plan engine.Plan, enc config.Encrypter, showSecrets bool) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer contract.IgnoreClose(f)
|
||||
|
||||
deploymentPlan, err := stack.SerializePlan(plan, enc, showSecrets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewEncoder(f).Encode(deploymentPlan)
|
||||
}
|
||||
|
||||
func readPlan(path string, dec config.Decrypter, enc config.Encrypter) (engine.Plan, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer contract.IgnoreClose(f)
|
||||
|
||||
var deploymentPlan apitype.DeploymentPlanV1
|
||||
if err := json.NewDecoder(f).Decode(&deploymentPlan); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stack.DeserializePlan(deploymentPlan, dec, enc)
|
||||
}
|
||||
|
|
|
@ -171,6 +171,8 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, res
|
|||
|
||||
resourcePlan, ok := sg.plan.newResourcePlans[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.plan.newResourcePlans[s.URN()] = resourcePlan
|
||||
}
|
||||
|
@ -272,6 +274,8 @@ func (sg *stepGenerator) generateSteps(event RegisterResourceEvent) ([]Step, res
|
|||
|
||||
// If there is a plan for this resource, finalize its inputs.
|
||||
if resourcePlan, ok := sg.plan.resourcePlans[urn]; ok {
|
||||
// should really overwrite goal info completely here
|
||||
|
||||
inputs = resourcePlan.completeInputs(inputs)
|
||||
}
|
||||
|
||||
|
|
102
pkg/resource/stack/plan.go
Normal file
102
pkg/resource/stack/plan.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package stack
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi/pkg/v2/resource/deploy"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/config"
|
||||
)
|
||||
|
||||
func SerializeResourcePlan(plan *deploy.ResourcePlan, enc config.Encrypter, showSecrets bool) (apitype.ResourcePlanV1, error) {
|
||||
properties, err := SerializeProperties(plan.Goal.Properties, enc, showSecrets)
|
||||
if err != nil {
|
||||
return apitype.ResourcePlanV1{}, err
|
||||
}
|
||||
|
||||
goal := apitype.GoalV1{
|
||||
Type: plan.Goal.Type,
|
||||
Name: plan.Goal.Name,
|
||||
Custom: plan.Goal.Custom,
|
||||
Properties: properties,
|
||||
Parent: plan.Goal.Parent,
|
||||
Protect: plan.Goal.Protect,
|
||||
Dependencies: plan.Goal.Dependencies,
|
||||
Provider: plan.Goal.Provider,
|
||||
PropertyDependencies: plan.Goal.PropertyDependencies,
|
||||
DeleteBeforeReplace: plan.Goal.DeleteBeforeReplace,
|
||||
IgnoreChanges: plan.Goal.IgnoreChanges,
|
||||
AdditionalSecretOutputs: plan.Goal.AdditionalSecretOutputs,
|
||||
Aliases: plan.Goal.Aliases,
|
||||
ID: plan.Goal.ID,
|
||||
CustomTimeouts: plan.Goal.CustomTimeouts,
|
||||
}
|
||||
|
||||
steps := make([]apitype.OpType, len(plan.Ops))
|
||||
for i, op := range plan.Ops {
|
||||
steps[i] = apitype.OpType(op)
|
||||
}
|
||||
|
||||
return apitype.ResourcePlanV1{
|
||||
Goal: goal,
|
||||
Steps: steps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SerializePlan(plan map[resource.URN]*deploy.ResourcePlan, enc config.Encrypter, showSecrets bool) (apitype.DeploymentPlanV1, error) {
|
||||
resourcePlans := map[resource.URN]apitype.ResourcePlanV1{}
|
||||
for urn, plan := range plan {
|
||||
serializedPlan, err := SerializeResourcePlan(plan, enc, showSecrets)
|
||||
if err != nil {
|
||||
return apitype.DeploymentPlanV1{}, err
|
||||
}
|
||||
resourcePlans[urn] = serializedPlan
|
||||
}
|
||||
return apitype.DeploymentPlanV1{ResourcePlans: resourcePlans}, nil
|
||||
}
|
||||
|
||||
func DeserializeResourcePlan(plan apitype.ResourcePlanV1, dec config.Decrypter, enc config.Encrypter) (*deploy.ResourcePlan, error) {
|
||||
properties, err := DeserializeProperties(plan.Goal.Properties, dec, enc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goal := &resource.Goal{
|
||||
Type: plan.Goal.Type,
|
||||
Name: plan.Goal.Name,
|
||||
Custom: plan.Goal.Custom,
|
||||
Properties: properties,
|
||||
Parent: plan.Goal.Parent,
|
||||
Protect: plan.Goal.Protect,
|
||||
Dependencies: plan.Goal.Dependencies,
|
||||
Provider: plan.Goal.Provider,
|
||||
PropertyDependencies: plan.Goal.PropertyDependencies,
|
||||
DeleteBeforeReplace: plan.Goal.DeleteBeforeReplace,
|
||||
IgnoreChanges: plan.Goal.IgnoreChanges,
|
||||
AdditionalSecretOutputs: plan.Goal.AdditionalSecretOutputs,
|
||||
Aliases: plan.Goal.Aliases,
|
||||
ID: plan.Goal.ID,
|
||||
CustomTimeouts: plan.Goal.CustomTimeouts,
|
||||
}
|
||||
|
||||
ops := make([]deploy.StepOp, len(plan.Steps))
|
||||
for i, op := range plan.Steps {
|
||||
ops[i] = deploy.StepOp(op)
|
||||
}
|
||||
|
||||
return &deploy.ResourcePlan{
|
||||
Goal: goal,
|
||||
Ops: ops,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func DeserializePlan(plan apitype.DeploymentPlanV1, dec config.Decrypter, enc config.Encrypter) (map[resource.URN]*deploy.ResourcePlan, error) {
|
||||
resourcePlans := map[resource.URN]*deploy.ResourcePlan{}
|
||||
for urn, plan := range plan.ResourcePlans {
|
||||
deserializedPlan, err := DeserializeResourcePlan(plan, dec, enc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourcePlans[urn] = deserializedPlan
|
||||
}
|
||||
return resourcePlans, nil
|
||||
}
|
56
sdk/go/common/apitype/plan.go
Normal file
56
sdk/go/common/apitype/plan.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package apitype
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
|
||||
)
|
||||
|
||||
// GoalV1 is the serializable version of a resource goal state.
|
||||
type GoalV1 struct {
|
||||
// the type of resource.
|
||||
Type tokens.Type `json:"type"`
|
||||
// the name for the resource's URN.
|
||||
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"`
|
||||
// an optional parent URN for this resource.
|
||||
Parent resource.URN `json:"parent,omitempty"`
|
||||
// true to protect this resource from deletion.
|
||||
Protect bool `json:"protect"`
|
||||
// dependencies of this resource object.
|
||||
Dependencies []resource.URN `json:"dependencies,omitempty"`
|
||||
// the provider to use for this resource.
|
||||
Provider string `json:"provider,omitempty"`
|
||||
// the set of dependencies that affect each property.
|
||||
PropertyDependencies map[resource.PropertyKey][]resource.URN `json:"propertyDependencies,omitempty"`
|
||||
// true if this resource should be deleted prior to replacement.
|
||||
DeleteBeforeReplace *bool `json:"deleteBeforeReplace,omitempty"`
|
||||
// a list of property names to ignore during changes.
|
||||
IgnoreChanges []string `json:"ignoreChanges,omitempty"`
|
||||
// outputs that should always be treated as secrets.
|
||||
AdditionalSecretOutputs []resource.PropertyKey `json:"additionalSecretOutputs,omitempty"`
|
||||
// additional URNs that should be aliased to this resource.
|
||||
Aliases []resource.URN `json:"aliases,omitempty"`
|
||||
// the expected ID of the resource, if any.
|
||||
ID resource.ID `json:"id,omitempty"`
|
||||
// an optional config object for resource options
|
||||
CustomTimeouts resource.CustomTimeouts `json:"customTimeouts,omitempty"`
|
||||
}
|
||||
|
||||
// ResourcePlanV1 is the serializable version of a resource plan.
|
||||
type ResourcePlanV1 struct {
|
||||
// The goal state for the resource.
|
||||
Goal GoalV1 `json:"goal"`
|
||||
// The steps to be performed on the resource.
|
||||
Steps []OpType `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// DeploymentPlanV1 is the serializable version of a deployment plan.
|
||||
type DeploymentPlanV1 struct {
|
||||
// TODO(pdg-plan): should there be a message here?
|
||||
|
||||
// The set of resource plans.
|
||||
ResourcePlans map[resource.URN]ResourcePlanV1 `json:"resourcePlans,omitempty"`
|
||||
}
|
Loading…
Reference in a new issue