diff --git a/bundle/deploy/files/delete.go b/bundle/deploy/files/delete.go index 9f7ad4d4..440e9aaf 100644 --- a/bundle/deploy/files/delete.go +++ b/bundle/deploy/files/delete.go @@ -22,12 +22,11 @@ func (m *delete) Apply(ctx context.Context, b *bundle.Bundle) error { return nil } - cmdio.LogString(ctx, "Starting deletion of remote bundle files") - cmdio.LogString(ctx, fmt.Sprintf("Bundle remote directory is %s", b.Config.Workspace.RootPath)) + cmdio.LogString(ctx, fmt.Sprintf("Deleting remote bundle directory %s", b.Config.Workspace.RootPath)) red := color.New(color.FgRed).SprintFunc() if !b.AutoApprove { - proceed, err := cmdio.AskYesOrNo(ctx, fmt.Sprintf("\n%s and all files in it will be %s Proceed?", b.Config.Workspace.RootPath, red("deleted permanently!"))) + proceed, err := cmdio.AskYesOrNo(ctx, fmt.Sprintf("\n%s and any files in it will be permanently %s. Proceed?", b.Config.Workspace.RootPath, red("deleted"))) if err != nil { return err } @@ -44,6 +43,8 @@ func (m *delete) Apply(ctx context.Context, b *bundle.Bundle) error { return err } + cmdio.LogString(ctx, "Deleted remote bundle directory") + // Clean up sync snapshot file sync, err := getSync(ctx, b) if err != nil { @@ -54,8 +55,7 @@ func (m *delete) Apply(ctx context.Context, b *bundle.Bundle) error { return err } - cmdio.LogString(ctx, fmt.Sprintf("Deleted snapshot file at %s", sync.SnapshotPath())) - cmdio.LogString(ctx, "Successfully deleted files!") + cmdio.LogString(ctx, fmt.Sprintf("Deleted local snapshot file at %s", sync.SnapshotPath())) return nil } diff --git a/bundle/deploy/files/upload.go b/bundle/deploy/files/upload.go index 9b7a85a4..4714e2f4 100644 --- a/bundle/deploy/files/upload.go +++ b/bundle/deploy/files/upload.go @@ -15,7 +15,7 @@ func (m *upload) Name() string { } func (m *upload) Apply(ctx context.Context, b *bundle.Bundle) error { - cmdio.LogString(ctx, "Starting upload of bundle files") + cmdio.LogString(ctx, "Uploading source files") sync, err := getSync(ctx, b) if err != nil { return err @@ -26,7 +26,7 @@ func (m *upload) Apply(ctx context.Context, b *bundle.Bundle) error { return err } - cmdio.LogString(ctx, fmt.Sprintf("Uploaded bundle files at %s!\n", b.Config.Workspace.FilesPath)) + cmdio.LogString(ctx, fmt.Sprintf("Upload complete. Source files are available at %s", b.Config.Workspace.FilesPath)) return nil } diff --git a/bundle/deploy/terraform/apply.go b/bundle/deploy/terraform/apply.go index c682ea37..7f096ecf 100644 --- a/bundle/deploy/terraform/apply.go +++ b/bundle/deploy/terraform/apply.go @@ -16,12 +16,6 @@ func (w *apply) Name() string { } func (w *apply) Apply(ctx context.Context, b *bundle.Bundle) error { - // Return early if plan is empty - if b.Plan.IsEmpty { - cmdio.LogString(ctx, "Terraform plan is empty. Skipping apply.") - return nil - } - // Error if terraform is not initialized tf := b.Terraform if tf == nil { @@ -30,29 +24,41 @@ func (w *apply) Apply(ctx context.Context, b *bundle.Bundle) error { // Error if plan is missing if b.Plan.Path == "" { - return fmt.Errorf("no plan found") + return fmt.Errorf("no path specified for computed plan") } - // Read and log plan file - plan, err := tf.ShowPlanFile(ctx, b.Plan.Path) - if err != nil { - return err - } - err = logPlan(ctx, plan) - if err != nil { - return err + // Print the plan if it's not empty + if !b.Plan.IsEmpty { + // parse the computed terraform plan + plan, err := tf.ShowPlanFile(ctx, b.Plan.Path) + if err != nil { + return err + } + + // print a high level summary of the terraform plan + err = printPlanSummary(ctx, plan) + if err != nil { + return err + } } - // We do not block for confirmation checks at deploy - cmdio.LogString(ctx, "Starting deployment") + if !b.Plan.IsEmpty { + // We do not block for confirmation checks at deploy + cmdio.LogString(ctx, "Deploying resources") + } - // Apply terraform according to the plan - err = tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path)) + // Apply terraform plan. Note: we apply even if a plan is empty to generate + // an empty state file that downstream state file upload mutator relies on. + err := tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path)) if err != nil { return fmt.Errorf("terraform apply: %w", err) } - cmdio.LogString(ctx, "Resource deployment completed!") + if b.Plan.IsEmpty { + cmdio.LogString(ctx, "No changes to deploy") + } else { + cmdio.LogString(ctx, "Deployment complete") + } return nil } diff --git a/bundle/deploy/terraform/destroy.go b/bundle/deploy/terraform/destroy.go index 936d8d45..2df75a20 100644 --- a/bundle/deploy/terraform/destroy.go +++ b/bundle/deploy/terraform/destroy.go @@ -35,7 +35,7 @@ func (w *destroy) Apply(ctx context.Context, b *bundle.Bundle) error { } // print the resources that will be destroyed - err = logPlan(ctx, plan) + err = printPlanSummary(ctx, plan) if err != nil { return err } @@ -43,7 +43,7 @@ func (w *destroy) Apply(ctx context.Context, b *bundle.Bundle) error { // Ask for confirmation, if needed if !b.Plan.ConfirmApply { red := color.New(color.FgRed).SprintFunc() - b.Plan.ConfirmApply, err = cmdio.AskYesOrNo(ctx, fmt.Sprintf("\nThis will permanently %s resources! Proceed?", red("destroy"))) + b.Plan.ConfirmApply, err = cmdio.AskYesOrNo(ctx, fmt.Sprintf("This will permanently %s resources! Proceed?", red("destroy"))) if err != nil { return err } @@ -58,7 +58,7 @@ func (w *destroy) Apply(ctx context.Context, b *bundle.Bundle) error { return fmt.Errorf("no plan found") } - cmdio.LogString(ctx, "Starting to destroy resources") + cmdio.LogString(ctx, "Destroying resources") // Apply terraform according to the computed destroy plan err = tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path)) @@ -66,7 +66,7 @@ func (w *destroy) Apply(ctx context.Context, b *bundle.Bundle) error { return fmt.Errorf("terraform destroy: %w", err) } - cmdio.LogString(ctx, "Successfully destroyed resources!") + cmdio.LogString(ctx, "Destruction complete") return nil } diff --git a/bundle/deploy/terraform/plan.go b/bundle/deploy/terraform/plan.go index 18dd7b22..99c5a468 100644 --- a/bundle/deploy/terraform/plan.go +++ b/bundle/deploy/terraform/plan.go @@ -12,8 +12,10 @@ import ( tfjson "github.com/hashicorp/terraform-json" ) -func logPlan(ctx context.Context, plan *tfjson.Plan) error { - cmdio.LogString(ctx, "Plan:") +// printPlanSummary prints a high level summary of the terraform plan that will +// be applied during bundle deploy / destroy. +func printPlanSummary(ctx context.Context, plan *tfjson.Plan) error { + cmdio.LogString(ctx, "\nPlan:") for _, change := range plan.ResourceChanges { tfActions := change.Change.Actions if tfActions.Read() || tfActions.NoOp() { @@ -76,7 +78,12 @@ func (p *plan) Apply(ctx context.Context, b *bundle.Bundle) error { return fmt.Errorf("terraform not initialized") } - cmdio.LogString(ctx, "Starting plan computation") + if p.goal == PlanDeploy { + cmdio.LogString(ctx, "Planning deployment") + } + if p.goal == PlanDestroy { + cmdio.LogString(ctx, "Planning destruction") + } err := tf.Init(ctx, tfexec.Upgrade(true)) if err != nil { @@ -103,7 +110,7 @@ func (p *plan) Apply(ctx context.Context, b *bundle.Bundle) error { IsEmpty: !notEmpty, } - cmdio.LogString(ctx, fmt.Sprintf("Planning complete and persisted at %s\n", planPath)) + cmdio.LogString(ctx, "Planning complete") return nil }