github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/terraform/tfplan/daemon_plan.go (about) 1 package tfplan 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/go-kit/kit/log" 8 "github.com/go-kit/kit/log/level" 9 "github.com/pkg/errors" 10 "github.com/replicatedhq/ship/pkg/api" 11 "github.com/replicatedhq/ship/pkg/lifecycle/daemon/daemontypes" 12 ) 13 14 type PlanConfirmer interface { 15 ConfirmPlan( 16 ctx context.Context, 17 formmatedTerraformPlan string, 18 release api.Release, 19 confirmedChan chan bool, 20 ) (bool, error) 21 WithStatusReceiver(daemontypes.StatusReceiver) PlanConfirmer 22 } 23 24 func NewPlanner( 25 logger log.Logger, 26 daemon daemontypes.Daemon, 27 ) PlanConfirmer { 28 return &DaemonPlanner{ 29 Logger: logger, 30 Daemon: daemon, 31 } 32 } 33 34 // DaemonPlanner interfaces with the Daemon 35 // to perform interactions with the end user 36 type DaemonPlanner struct { 37 Logger log.Logger 38 Daemon daemontypes.Daemon 39 } 40 41 // WithStatusReceiver is a no-op for the daemon version of PlanConfirmer 42 func (d *DaemonPlanner) WithStatusReceiver(daemontypes.StatusReceiver) PlanConfirmer { 43 return d 44 } 45 46 // ConfirmPlan presents the plan to the user. 47 // returns true if the plan should be applied 48 func (d *DaemonPlanner) ConfirmPlan( 49 ctx context.Context, 50 formmatedTerraformPlan string, 51 release api.Release, 52 confirmedChan chan bool, 53 ) (bool, error) { 54 debug := level.Debug(log.With(d.Logger, "struct", "daemonplanner", "method", "plan")) 55 56 debug.Log("event", "step.push") 57 daemonExitedChan := d.Daemon.EnsureStarted(ctx, &release) 58 d.Daemon.PushMessageStep( 59 ctx, 60 daemontypes.Message{Contents: formmatedTerraformPlan, TrustedHTML: true}, 61 planActions(), 62 ) 63 64 shouldApply, err := d.awaitPlanResult(ctx, daemonExitedChan) 65 return shouldApply, errors.Wrap(err, "await plan confirm") 66 67 } 68 69 func (d *DaemonPlanner) awaitPlanResult(ctx context.Context, daemonExitedChan chan error) (bool, error) { 70 debug := level.Debug(log.With(d.Logger, "struct", "daemonplanner", "method", "plan.confirm.await")) 71 for { 72 select { 73 case <-ctx.Done(): 74 debug.Log("event", "ctx.done") 75 return false, ctx.Err() 76 case err := <-daemonExitedChan: 77 debug.Log("event", "daemon.exit") 78 if err != nil { 79 return false, err 80 } 81 return false, errors.New("daemon exited") 82 case applyRequested := <-d.Daemon.TerraformConfirmedChan(): 83 debug.Log("event", "plan.confirmed", "applyRequested", applyRequested) 84 return applyRequested, nil 85 case <-time.After(10 * time.Second): 86 debug.Log("waitingFor", "plan.confirmed") 87 } 88 } 89 } 90 91 func planActions() []daemontypes.Action { 92 return []daemontypes.Action{ 93 { 94 ButtonType: "primary", 95 Text: "Apply", 96 LoadingText: "Applying", 97 OnClick: daemontypes.ActionRequest{ 98 URI: "/terraform/apply", 99 Method: "POST", 100 }, 101 }, 102 { 103 ButtonType: "secondary-gray", 104 Text: "Skip", 105 LoadingText: "Skipping", 106 OnClick: daemontypes.ActionRequest{ 107 URI: "/terraform/skip", 108 Method: "POST", 109 }, 110 }, 111 } 112 }