backend/remote: add support for the apply operation
This commit is contained in:
parent
27b720113e
commit
621d589189
|
@ -166,7 +166,7 @@ type Operation struct {
|
||||||
// RunningOperation is the result of starting an operation.
|
// RunningOperation is the result of starting an operation.
|
||||||
type RunningOperation struct {
|
type RunningOperation struct {
|
||||||
// For implementers of a backend, this context should not wrap the
|
// For implementers of a backend, this context should not wrap the
|
||||||
// passed in context. Otherwise, canceling the parent context will
|
// passed in context. Otherwise, cancelling the parent context will
|
||||||
// immediately mark this context as "done" but those aren't the semantics
|
// immediately mark this context as "done" but those aren't the semantics
|
||||||
// we want: we want this context to be done only when the operation itself
|
// we want: we want this context to be done only when the operation itself
|
||||||
// is fully done.
|
// is fully done.
|
||||||
|
|
|
@ -241,8 +241,8 @@ No configuration files found!
|
||||||
|
|
||||||
Apply requires configuration to be present. Applying without a configuration
|
Apply requires configuration to be present. Applying without a configuration
|
||||||
would mark everything for destruction, which is normally not what is desired.
|
would mark everything for destruction, which is normally not what is desired.
|
||||||
If you would like to destroy everything, please run 'terraform destroy' instead
|
If you would like to destroy everything, please run 'terraform destroy' which
|
||||||
which does not require any configuration files.
|
does not require any configuration files.
|
||||||
`
|
`
|
||||||
|
|
||||||
const stateWriteBackedUpError = `Failed to persist state to backend.
|
const stateWriteBackedUpError = `Failed to persist state to backend.
|
||||||
|
@ -285,7 +285,7 @@ This is a serious bug in Terraform and should be reported.
|
||||||
|
|
||||||
const earlyStateWriteErrorFmt = `Error saving current state: %s
|
const earlyStateWriteErrorFmt = `Error saving current state: %s
|
||||||
|
|
||||||
Terraform encountered an error attempting to save the state before canceling
|
Terraform encountered an error attempting to save the state before cancelling
|
||||||
the current operation. Once the operation is complete another attempt will be
|
the current operation. Once the operation is complete another attempt will be
|
||||||
made to save the final state.
|
made to save the final state.
|
||||||
`
|
`
|
||||||
|
|
|
@ -394,15 +394,17 @@ func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the function to call for our operation
|
// Determine the function to call for our operation
|
||||||
var f func(context.Context, context.Context, *backend.Operation, *backend.RunningOperation)
|
var f func(context.Context, context.Context, *backend.Operation) error
|
||||||
switch op.Type {
|
switch op.Type {
|
||||||
case backend.OperationTypePlan:
|
case backend.OperationTypePlan:
|
||||||
f = b.opPlan
|
f = b.opPlan
|
||||||
|
case backend.OperationTypeApply:
|
||||||
|
f = b.opApply
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"\n\nThe \"remote\" backend currently only supports the \"plan\" operation.\n"+
|
"\n\nThe \"remote\" backend does not support the %q operation.\n"+
|
||||||
"Please use the remote backend web UI for all other operations:\n"+
|
"Please use the remote backend web UI for running this operation:\n"+
|
||||||
"https://%s/app/%s/%s", b.hostname, b.organization, op.Workspace)
|
"https://%s/app/%s/%s", op.Type, b.hostname, b.organization, op.Workspace)
|
||||||
// return nil, backend.ErrOperationNotSupported
|
// return nil, backend.ErrOperationNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +434,11 @@ func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
defer b.opLock.Unlock()
|
defer b.opLock.Unlock()
|
||||||
f(stopCtx, cancelCtx, op, runningOp)
|
|
||||||
|
err := f(stopCtx, cancelCtx, op)
|
||||||
|
if err != nil && err != context.Canceled {
|
||||||
|
runningOp.Err = err
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Return
|
// Return
|
||||||
|
@ -453,6 +459,13 @@ func (b *Remote) Colorize() *colorstring.Colorize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generalError(msg string, err error) error {
|
||||||
|
if err != context.Canceled {
|
||||||
|
err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(generalErr, msg, err)))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const generalErr = `
|
const generalErr = `
|
||||||
%s: %v
|
%s: %v
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operation) error {
|
||||||
|
log.Printf("[INFO] backend/remote: starting Apply operation")
|
||||||
|
|
||||||
|
// Retrieve the workspace used to run this operation in.
|
||||||
|
w, err := b.client.Workspaces.Read(stopCtx, b.organization, op.Workspace)
|
||||||
|
if err != nil {
|
||||||
|
return generalError("error retrieving workspace", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.VCSRepo != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(applyErrVCSNotSupported))
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.Plan != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(applyErrPlanNotSupported))
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.Targets != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(applyErrTargetsNotSupported))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.Module == nil || op.Module.Config().Dir == "") && !op.Destroy {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(planErrNoConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := b.plan(stopCtx, cancelCtx, op, w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.PolicyChecks) > 0 {
|
||||||
|
err = b.checkPolicy(stopCtx, cancelCtx, op, r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasUI := op.UIOut != nil && op.UIIn != nil
|
||||||
|
mustConfirm := hasUI && (op.Destroy && (!op.DestroyForce && !op.AutoApprove))
|
||||||
|
if mustConfirm {
|
||||||
|
opts := &terraform.InputOpts{Id: "approve"}
|
||||||
|
|
||||||
|
if op.Destroy {
|
||||||
|
opts.Query = "Do you really want to destroy all resources in workspace \"" + op.Workspace + "\"?"
|
||||||
|
opts.Description = "Terraform will destroy all your managed infrastructure, as shown above.\n" +
|
||||||
|
"There is no undo. Only 'yes' will be accepted to confirm."
|
||||||
|
} else {
|
||||||
|
opts.Query = "Do you want to perform these actions in workspace \"" + op.Workspace + "\"?"
|
||||||
|
opts.Description = "Terraform will perform the actions described above.\n" +
|
||||||
|
"Only 'yes' will be accepted to approve."
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = b.confirm(stopCtx, op, opts, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.client.Runs.Apply(stopCtx, r.ID, tfe.RunApplyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return generalError("error approving the apply command", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := b.client.Applies.Logs(stopCtx, r.Apply.ID)
|
||||||
|
if err != nil {
|
||||||
|
return generalError("error retrieving logs", err)
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(logs)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
if b.CLI != nil {
|
||||||
|
b.CLI.Output(b.Colorize().Color(scanner.Text()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return generalError("error reading logs", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error {
|
||||||
|
for _, pc := range r.PolicyChecks {
|
||||||
|
logs, err := b.client.PolicyChecks.Logs(stopCtx, pc.ID)
|
||||||
|
if err != nil {
|
||||||
|
return generalError("error retrieving policy check logs", err)
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(logs)
|
||||||
|
|
||||||
|
var msgPrefix string
|
||||||
|
switch pc.Scope {
|
||||||
|
case tfe.PolicyScopeOrganization:
|
||||||
|
msgPrefix = "Organization policy check"
|
||||||
|
case tfe.PolicyScopeWorkspace:
|
||||||
|
msgPrefix = "Workspace policy check"
|
||||||
|
default:
|
||||||
|
msgPrefix = fmt.Sprintf("Unknown policy check (%s)", pc.Scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.CLI != nil {
|
||||||
|
b.CLI.Output(b.Colorize().Color("\n" + msgPrefix + ":\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
if b.CLI != nil {
|
||||||
|
b.CLI.Output(b.Colorize().Color(scanner.Text()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return generalError("error reading logs", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc, err := b.client.PolicyChecks.Read(stopCtx, pc.ID)
|
||||||
|
if err != nil {
|
||||||
|
return generalError("error retrieving policy check", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pc.Status {
|
||||||
|
case tfe.PolicyPasses:
|
||||||
|
continue
|
||||||
|
case tfe.PolicyErrored:
|
||||||
|
return fmt.Errorf(msgPrefix + " errored.")
|
||||||
|
case tfe.PolicyHardFailed:
|
||||||
|
return fmt.Errorf(msgPrefix + " hard failed.")
|
||||||
|
case tfe.PolicySoftFailed:
|
||||||
|
if op.UIOut == nil || op.UIIn == nil ||
|
||||||
|
!pc.Actions.IsOverridable || !pc.Permissions.CanOverride {
|
||||||
|
return fmt.Errorf(msgPrefix + " soft failed.")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknown or unexpected policy state: %s", pc.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &terraform.InputOpts{
|
||||||
|
Id: "override",
|
||||||
|
Query: "Do you want to override the failed policy check?",
|
||||||
|
Description: "Only 'yes' will be accepted to override.",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = b.confirm(stopCtx, op, opts, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *terraform.InputOpts, r *tfe.Run) error {
|
||||||
|
v, err := op.UIIn.Input(opts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error asking %s: %v", opts.Id, err)
|
||||||
|
}
|
||||||
|
if v != "yes" {
|
||||||
|
// Make sure we discard the run.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 cancelled.")
|
||||||
|
}
|
||||||
|
return errors.New("Apply cancelled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyErrVCSNotSupported = `
|
||||||
|
Apply not allowed for workspaces with a VCS connection!
|
||||||
|
|
||||||
|
A workspace that is connected to a VCS requires the VCS based workflow
|
||||||
|
to ensure that the VCS remains the single source of truth.
|
||||||
|
`
|
||||||
|
|
||||||
|
const applyErrPlanNotSupported = `
|
||||||
|
Applying a saved plan is currently not supported!
|
||||||
|
|
||||||
|
The "remote" backend currently requires configuration to be present
|
||||||
|
and does not accept an existing saved plan as an argument at this time.
|
||||||
|
`
|
||||||
|
|
||||||
|
const applyErrTargetsNotSupported = `
|
||||||
|
Resource targeting is currently not supported!
|
||||||
|
|
||||||
|
The "remote" backend does not support resource targeting at this time.
|
||||||
|
`
|
||||||
|
|
||||||
|
const applyErrNoConfig = `
|
||||||
|
No configuration files found!
|
||||||
|
|
||||||
|
Apply requires configuration to be present. Applying without a configuration
|
||||||
|
would mark everything for destruction, which is normally not what is desired.
|
||||||
|
If you would like to destroy everything, please run 'terraform destroy' which
|
||||||
|
does not require any configuration files.
|
||||||
|
`
|
||||||
|
|
||||||
|
const applyDefaultHeader = `
|
||||||
|
[reset][yellow]Running apply in the remote backend. Output will stream here. Pressing Ctrl-C
|
||||||
|
will stop streaming the logs, but will not stop the apply running remotely.
|
||||||
|
To view this run in a browser, visit:
|
||||||
|
https://%s/app/%s/%s/runs/%s[reset]
|
||||||
|
|
||||||
|
Waiting for the apply to start...
|
||||||
|
`
|
|
@ -3,8 +3,8 @@ package remote
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -16,51 +16,45 @@ import (
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operation, runningOp *backend.RunningOperation) {
|
func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operation) error {
|
||||||
log.Printf("[INFO] backend/remote: starting Plan operation")
|
log.Printf("[INFO] backend/remote: starting Plan operation")
|
||||||
|
|
||||||
if op.Plan != nil {
|
if op.Plan != nil {
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrPlanNotSupported))
|
return fmt.Errorf(strings.TrimSpace(planErrPlanNotSupported))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if op.PlanOutPath != "" {
|
if op.PlanOutPath != "" {
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrOutPathNotSupported))
|
return fmt.Errorf(strings.TrimSpace(planErrOutPathNotSupported))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if op.Targets != nil {
|
if op.Targets != nil {
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrTargetsNotSupported))
|
return fmt.Errorf(strings.TrimSpace(planErrTargetsNotSupported))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op.Module == nil || op.Module.Config().Dir == "") && !op.Destroy {
|
if (op.Module == nil || op.Module.Config().Dir == "") && !op.Destroy {
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrNoConfig))
|
return fmt.Errorf(strings.TrimSpace(planErrNoConfig))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the workspace used to run this operation in.
|
// Retrieve the workspace used to run this operation in.
|
||||||
w, err := b.client.Workspaces.Read(stopCtx, b.organization, op.Workspace)
|
w, err := b.client.Workspaces.Read(stopCtx, b.organization, op.Workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != context.Canceled {
|
return generalError("error retrieving workspace", err)
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
|
||||||
generalErr, "error retrieving workspace", err)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = b.plan(stopCtx, cancelCtx, op, w)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Remote) plan(stopCtx, cancelCtx context.Context, op *backend.Operation, w *tfe.Workspace) (*tfe.Run, error) {
|
||||||
configOptions := tfe.ConfigurationVersionCreateOptions{
|
configOptions := tfe.ConfigurationVersionCreateOptions{
|
||||||
AutoQueueRuns: tfe.Bool(false),
|
AutoQueueRuns: tfe.Bool(false),
|
||||||
Speculative: tfe.Bool(true),
|
Speculative: tfe.Bool(op.Type == backend.OperationTypePlan),
|
||||||
}
|
}
|
||||||
|
|
||||||
cv, err := b.client.ConfigurationVersions.Create(stopCtx, w.ID, configOptions)
|
cv, err := b.client.ConfigurationVersions.Create(stopCtx, w.ID, configOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != context.Canceled {
|
return nil, generalError("error creating configuration version", err)
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
|
||||||
generalErr, "error creating configuration version", err)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var configDir string
|
var configDir string
|
||||||
|
@ -78,45 +72,34 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
|
||||||
// be executed when we are destroying and doesn't need the config.
|
// be executed when we are destroying and doesn't need the config.
|
||||||
configDir, err = ioutil.TempDir("", "tf")
|
configDir, err = ioutil.TempDir("", "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
return nil, generalError("error creating temporary directory", err)
|
||||||
generalErr, "error creating temporary directory", err)))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(configDir)
|
defer os.RemoveAll(configDir)
|
||||||
|
|
||||||
// Make sure the configured working directory exists.
|
// Make sure the configured working directory exists.
|
||||||
err = os.MkdirAll(filepath.Join(configDir, w.WorkingDirectory), 0700)
|
err = os.MkdirAll(filepath.Join(configDir, w.WorkingDirectory), 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
return nil, generalError(
|
||||||
generalErr, "error creating temporary working directory", err)))
|
"error creating temporary working directory", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.client.ConfigurationVersions.Upload(stopCtx, cv.UploadURL, configDir)
|
err = b.client.ConfigurationVersions.Upload(stopCtx, cv.UploadURL, configDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != context.Canceled {
|
return nil, generalError("error uploading configuration files", err)
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
|
||||||
generalErr, "error uploading configuration files", err)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uploaded := false
|
uploaded := false
|
||||||
for i := 0; i < 60 && !uploaded; i++ {
|
for i := 0; i < 60 && !uploaded; i++ {
|
||||||
select {
|
select {
|
||||||
case <-stopCtx.Done():
|
case <-stopCtx.Done():
|
||||||
return
|
return nil, context.Canceled
|
||||||
case <-cancelCtx.Done():
|
case <-cancelCtx.Done():
|
||||||
return
|
return nil, context.Canceled
|
||||||
case <-time.After(500 * time.Millisecond):
|
case <-time.After(500 * time.Millisecond):
|
||||||
cv, err = b.client.ConfigurationVersions.Read(stopCtx, cv.ID)
|
cv, err = b.client.ConfigurationVersions.Read(stopCtx, cv.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != context.Canceled {
|
return nil, generalError("error retrieving configuration version", err)
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
|
||||||
generalErr, "error retrieving configuration version", err)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cv.Status == tfe.ConfigurationUploaded {
|
if cv.Status == tfe.ConfigurationUploaded {
|
||||||
|
@ -126,9 +109,8 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
|
||||||
}
|
}
|
||||||
|
|
||||||
if !uploaded {
|
if !uploaded {
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
return nil, generalError(
|
||||||
generalErr, "error uploading configuration files", "operation timed out")))
|
"error uploading configuration files", errors.New("operation timed out"))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runOptions := tfe.RunCreateOptions{
|
runOptions := tfe.RunCreateOptions{
|
||||||
|
@ -140,20 +122,12 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
|
||||||
|
|
||||||
r, err := b.client.Runs.Create(stopCtx, runOptions)
|
r, err := b.client.Runs.Create(stopCtx, runOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != context.Canceled {
|
return nil, generalError("error creating run", err)
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
|
||||||
generalErr, "error creating run", err)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err = b.client.Runs.Read(stopCtx, r.ID)
|
r, err = b.client.Runs.Read(stopCtx, r.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != context.Canceled {
|
return nil, generalError("error retrieving run", err)
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
|
||||||
generalErr, "error retrieving run", err)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.CLI != nil {
|
if b.CLI != nil {
|
||||||
|
@ -163,11 +137,7 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
|
||||||
|
|
||||||
logs, err := b.client.Plans.Logs(stopCtx, r.Plan.ID)
|
logs, err := b.client.Plans.Logs(stopCtx, r.Plan.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != context.Canceled {
|
return nil, generalError("error retrieving logs", err)
|
||||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(fmt.Sprintf(
|
|
||||||
generalErr, "error retrieving logs", err)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
scanner := bufio.NewScanner(logs)
|
scanner := bufio.NewScanner(logs)
|
||||||
|
|
||||||
|
@ -177,11 +147,10 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
if err != context.Canceled && err != io.EOF {
|
return nil, generalError("error reading logs", err)
|
||||||
runningOp.Err = fmt.Errorf("Error reading logs: %v", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const planErrPlanNotSupported = `
|
const planErrPlanNotSupported = `
|
||||||
|
@ -217,7 +186,7 @@ a Terraform configuration file in the path being executed and try again.
|
||||||
const planDefaultHeader = `
|
const planDefaultHeader = `
|
||||||
[reset][yellow]Running plan in the remote backend. Output will stream here. Pressing Ctrl-C
|
[reset][yellow]Running plan in the remote backend. Output will stream here. Pressing Ctrl-C
|
||||||
will stop streaming the logs, but will not stop the plan running remotely.
|
will stop streaming the logs, but will not stop the plan running remotely.
|
||||||
To view this plan in a browser, visit:
|
To view this run in a browser, visit:
|
||||||
https://%s/app/%s/%s/runs/%s[reset]
|
https://%s/app/%s/%s/runs/%s[reset]
|
||||||
|
|
||||||
Waiting for the plan to start...
|
Waiting for the plan to start...
|
||||||
|
|
Loading…
Reference in New Issue