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  }