|
|
|
|
@ -14,6 +14,14 @@ import (
|
|
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
errApplyDiscarded = errors.New("Apply discarded.")
|
|
|
|
|
errDestroyDiscarded = errors.New("Destroy discarded.")
|
|
|
|
|
errRunApproved = errors.New("approved using the UI or API")
|
|
|
|
|
errRunDiscarded = errors.New("discarded using the UI or API")
|
|
|
|
|
errRunOverridden = errors.New("overridden using the UI or API")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// backoff will perform exponential backoff based on the iteration and
|
|
|
|
|
// limited by the provided min and max (in milliseconds) durations.
|
|
|
|
|
func backoff(min, max float64, iter int) time.Duration {
|
|
|
|
|
@ -267,12 +275,15 @@ func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Ope
|
|
|
|
|
Description: "Only 'override' will be accepted to override.",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err = b.confirm(stopCtx, op, opts, r, "override"); err != nil {
|
|
|
|
|
err = b.confirm(stopCtx, op, opts, r, "override")
|
|
|
|
|
if err != nil && err != errRunOverridden {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err = b.client.PolicyChecks.Override(stopCtx, pc.ID); err != nil {
|
|
|
|
|
return generalError("error overriding policy check", err)
|
|
|
|
|
if err != errRunOverridden {
|
|
|
|
|
if _, err = b.client.PolicyChecks.Override(stopCtx, pc.ID); err != nil {
|
|
|
|
|
return generalError("error overriding policy check", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if b.CLI != nil {
|
|
|
|
|
@ -284,35 +295,115 @@ func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Ope
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *terraform.InputOpts, r *tfe.Run, keyword string) error {
|
|
|
|
|
v, err := op.UIIn.Input(stopCtx, opts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Error asking %s: %v", opts.Id, err)
|
|
|
|
|
}
|
|
|
|
|
if v != keyword {
|
|
|
|
|
// Retrieve the run again to get its current status.
|
|
|
|
|
r, err = b.client.Runs.Read(stopCtx, r.ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return generalError("error retrieving run", err)
|
|
|
|
|
doneCtx, cancel := context.WithCancel(stopCtx)
|
|
|
|
|
result := make(chan error, 2)
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
// Make sure we cancel doneCtx before we return
|
|
|
|
|
// so the input command is also canceled.
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-doneCtx.Done():
|
|
|
|
|
return
|
|
|
|
|
case <-stopCtx.Done():
|
|
|
|
|
return
|
|
|
|
|
case <-time.After(3 * time.Second):
|
|
|
|
|
// Retrieve the run again to get its current status.
|
|
|
|
|
r, err := b.client.Runs.Read(stopCtx, r.ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
result <- generalError("error retrieving run", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch keyword {
|
|
|
|
|
case "override":
|
|
|
|
|
if r.Status != tfe.RunPolicyOverride {
|
|
|
|
|
if r.Status == tfe.RunDiscarded {
|
|
|
|
|
err = errRunDiscarded
|
|
|
|
|
} else {
|
|
|
|
|
err = errRunOverridden
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case "yes":
|
|
|
|
|
if !r.Actions.IsConfirmable {
|
|
|
|
|
if r.Status == tfe.RunDiscarded {
|
|
|
|
|
err = errRunDiscarded
|
|
|
|
|
} else {
|
|
|
|
|
err = errRunApproved
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
if b.CLI != nil {
|
|
|
|
|
b.CLI.Output(b.Colorize().Color(
|
|
|
|
|
fmt.Sprintf("[reset][yellow]%s[reset]", err.Error())))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err == errRunDiscarded {
|
|
|
|
|
if op.Destroy {
|
|
|
|
|
err = errDestroyDiscarded
|
|
|
|
|
}
|
|
|
|
|
err = errApplyDiscarded
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result <- err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
result <- func() error {
|
|
|
|
|
v, err := op.UIIn.Input(doneCtx, opts)
|
|
|
|
|
if err != nil && err != context.Canceled && stopCtx.Err() != context.Canceled {
|
|
|
|
|
return fmt.Errorf("Error asking %s: %v", opts.Id, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We return the error of our parent channel as we don't
|
|
|
|
|
// care about the error of the doneCtx which is only used
|
|
|
|
|
// within this function. So if the doneCtx was canceled
|
|
|
|
|
// because stopCtx was canceled, this will properly return
|
|
|
|
|
// a context.Canceled error and otherwise it returns nil.
|
|
|
|
|
if doneCtx.Err() == context.Canceled || stopCtx.Err() == context.Canceled {
|
|
|
|
|
return stopCtx.Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure we cancel the context here so the loop that
|
|
|
|
|
// checks for external changes to the run is ended before
|
|
|
|
|
// we start to make changes ourselves.
|
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
|
|
// Make sure we discard the run if possible.
|
|
|
|
|
if r.Actions.IsDiscardable {
|
|
|
|
|
err = b.client.Runs.Discard(stopCtx, r.ID, tfe.RunDiscardOptions{})
|
|
|
|
|
if v != keyword {
|
|
|
|
|
// Retrieve the run again to get its current status.
|
|
|
|
|
r, err = b.client.Runs.Read(stopCtx, r.ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if op.Destroy {
|
|
|
|
|
return generalError("error disarding destroy", err)
|
|
|
|
|
return generalError("error retrieving run", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure we discard the run if possible.
|
|
|
|
|
if r.Actions.IsDiscardable {
|
|
|
|
|
err = b.client.Runs.Discard(stopCtx, r.ID, tfe.RunDiscardOptions{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
if op.Destroy {
|
|
|
|
|
return generalError("error disarding destroy", err)
|
|
|
|
|
}
|
|
|
|
|
return generalError("error disarding apply", err)
|
|
|
|
|
}
|
|
|
|
|
return generalError("error disarding apply", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Even if the run was disarding successfully, we still
|
|
|
|
|
// return an error as the apply command was cancelled.
|
|
|
|
|
if op.Destroy {
|
|
|
|
|
return errors.New("Destroy discarded.")
|
|
|
|
|
// Even if the run was discarded successfully, we still
|
|
|
|
|
// return an error as the apply command was canceled.
|
|
|
|
|
if op.Destroy {
|
|
|
|
|
return errDestroyDiscarded
|
|
|
|
|
}
|
|
|
|
|
return errApplyDiscarded
|
|
|
|
|
}
|
|
|
|
|
return errors.New("Apply discarded.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
return nil
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
return <-result
|
|
|
|
|
}
|
|
|
|
|
|