github.com/johandry/terraform@v0.11.12-beta1/backend/remote/backend_apply.go (about)

     1  package remote
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"log"
     8  	"strings"
     9  
    10  	tfe "github.com/hashicorp/go-tfe"
    11  	"github.com/hashicorp/terraform/backend"
    12  	"github.com/hashicorp/terraform/terraform"
    13  )
    14  
    15  func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operation, w *tfe.Workspace) (*tfe.Run, error) {
    16  	log.Printf("[INFO] backend/remote: starting Apply operation")
    17  
    18  	if !w.Permissions.CanUpdate {
    19  		return nil, fmt.Errorf(strings.TrimSpace(applyErrNoUpdateRights))
    20  	}
    21  
    22  	if w.VCSRepo != nil {
    23  		return nil, fmt.Errorf(strings.TrimSpace(applyErrVCSNotSupported))
    24  	}
    25  
    26  	if op.Parallelism != defaultParallelism {
    27  		return nil, fmt.Errorf(strings.TrimSpace(applyErrParallelismNotSupported))
    28  	}
    29  
    30  	if op.Plan != nil {
    31  		return nil, fmt.Errorf(strings.TrimSpace(applyErrPlanNotSupported))
    32  	}
    33  
    34  	if !op.PlanRefresh {
    35  		return nil, fmt.Errorf(strings.TrimSpace(applyErrNoRefreshNotSupported))
    36  	}
    37  
    38  	if op.Targets != nil {
    39  		return nil, fmt.Errorf(strings.TrimSpace(applyErrTargetsNotSupported))
    40  	}
    41  
    42  	if op.Variables != nil {
    43  		return nil, fmt.Errorf(strings.TrimSpace(
    44  			fmt.Sprintf(applyErrVariablesNotSupported, b.hostname, b.organization, op.Workspace)))
    45  	}
    46  
    47  	if (op.Module == nil || op.Module.Config().Dir == "") && !op.Destroy {
    48  		return nil, fmt.Errorf(strings.TrimSpace(applyErrNoConfig))
    49  	}
    50  
    51  	// Run the plan phase.
    52  	r, err := b.plan(stopCtx, cancelCtx, op, w)
    53  	if err != nil {
    54  		return r, err
    55  	}
    56  
    57  	// This check is also performed in the plan method to determine if
    58  	// the policies should be checked, but we need to check the values
    59  	// here again to determine if we are done and should return.
    60  	if !r.HasChanges || r.Status == tfe.RunErrored {
    61  		return r, nil
    62  	}
    63  
    64  	// Retrieve the run to get its current status.
    65  	r, err = b.client.Runs.Read(stopCtx, r.ID)
    66  	if err != nil {
    67  		return r, generalError("error retrieving run", err)
    68  	}
    69  
    70  	// Return if the run cannot be confirmed.
    71  	if !w.AutoApply && !r.Actions.IsConfirmable {
    72  		return r, nil
    73  	}
    74  
    75  	// Since we already checked the permissions before creating the run
    76  	// this should never happen. But it doesn't hurt to keep this in as
    77  	// a safeguard for any unexpected situations.
    78  	if !w.AutoApply && !r.Permissions.CanApply {
    79  		// Make sure we discard the run if possible.
    80  		if r.Actions.IsDiscardable {
    81  			err = b.client.Runs.Discard(stopCtx, r.ID, tfe.RunDiscardOptions{})
    82  			if err != nil {
    83  				if op.Destroy {
    84  					return r, generalError("error disarding destroy", err)
    85  				}
    86  				return r, generalError("error disarding apply", err)
    87  			}
    88  		}
    89  		return r, fmt.Errorf(strings.TrimSpace(
    90  			fmt.Sprintf(applyErrNoApplyRights, b.hostname, b.organization, op.Workspace)))
    91  	}
    92  
    93  	mustConfirm := (op.UIIn != nil && op.UIOut != nil) &&
    94  		((op.Destroy && (!op.DestroyForce && !op.AutoApprove)) || (!op.Destroy && !op.AutoApprove))
    95  
    96  	if !w.AutoApply {
    97  		if mustConfirm {
    98  			opts := &terraform.InputOpts{Id: "approve"}
    99  
   100  			if op.Destroy {
   101  				opts.Query = "\nDo you really want to destroy all resources in workspace \"" + op.Workspace + "\"?"
   102  				opts.Description = "Terraform will destroy all your managed infrastructure, as shown above.\n" +
   103  					"There is no undo. Only 'yes' will be accepted to confirm."
   104  			} else {
   105  				opts.Query = "\nDo you want to perform these actions in workspace \"" + op.Workspace + "\"?"
   106  				opts.Description = "Terraform will perform the actions described above.\n" +
   107  					"Only 'yes' will be accepted to approve."
   108  			}
   109  
   110  			if err = b.confirm(stopCtx, op, opts, r, "yes"); err != nil {
   111  				return r, err
   112  			}
   113  		}
   114  
   115  		err = b.client.Runs.Apply(stopCtx, r.ID, tfe.RunApplyOptions{})
   116  		if err != nil {
   117  			return r, generalError("error approving the apply command", err)
   118  		}
   119  	}
   120  
   121  	// If we don't need to ask for confirmation, insert a blank
   122  	// line to separate the ouputs.
   123  	if w.AutoApply || !mustConfirm {
   124  		if b.CLI != nil {
   125  			b.CLI.Output("")
   126  		}
   127  	}
   128  
   129  	r, err = b.waitForRun(stopCtx, cancelCtx, op, "apply", r, w)
   130  	if err != nil {
   131  		return r, err
   132  	}
   133  
   134  	logs, err := b.client.Applies.Logs(stopCtx, r.Apply.ID)
   135  	if err != nil {
   136  		return r, generalError("error retrieving logs", err)
   137  	}
   138  	scanner := bufio.NewScanner(logs)
   139  
   140  	skip := 0
   141  	for scanner.Scan() {
   142  		// Skip the first 3 lines to prevent duplicate output.
   143  		if skip < 3 {
   144  			skip++
   145  			continue
   146  		}
   147  		if b.CLI != nil {
   148  			b.CLI.Output(b.Colorize().Color(scanner.Text()))
   149  		}
   150  	}
   151  	if err := scanner.Err(); err != nil {
   152  		return r, generalError("error reading logs", err)
   153  	}
   154  
   155  	return r, nil
   156  }
   157  
   158  const applyErrNoUpdateRights = `
   159  Insufficient rights to apply changes!
   160  
   161  [reset][yellow]The provided credentials have insufficient rights to apply changes. In order
   162  to apply changes at least write permissions on the workspace are required.[reset]
   163  `
   164  
   165  const applyErrVCSNotSupported = `
   166  Apply not allowed for workspaces with a VCS connection.
   167  
   168  A workspace that is connected to a VCS requires the VCS-driven workflow
   169  to ensure that the VCS remains the single source of truth.
   170  `
   171  
   172  const applyErrParallelismNotSupported = `
   173  Custom parallelism values are currently not supported!
   174  
   175  The "remote" backend does not support setting a custom parallelism
   176  value at this time.
   177  `
   178  
   179  const applyErrPlanNotSupported = `
   180  Applying a saved plan is currently not supported!
   181  
   182  The "remote" backend currently requires configuration to be present and
   183  does not accept an existing saved plan as an argument at this time.
   184  `
   185  
   186  const applyErrNoRefreshNotSupported = `
   187  Applying without refresh is currently not supported!
   188  
   189  Currently the "remote" backend will always do an in-memory refresh of
   190  the Terraform state prior to generating the plan.
   191  `
   192  
   193  const applyErrTargetsNotSupported = `
   194  Resource targeting is currently not supported!
   195  
   196  The "remote" backend does not support resource targeting at this time.
   197  `
   198  
   199  const applyErrVariablesNotSupported = `
   200  Run variables are currently not supported!
   201  
   202  The "remote" backend does not support setting run variables at this time.
   203  Currently the only to way to pass variables to the remote backend is by
   204  creating a '*.auto.tfvars' variables file. This file will automatically
   205  be loaded by the "remote" backend when the workspace is configured to use
   206  Terraform v0.10.0 or later.
   207  
   208  Additionally you can also set variables on the workspace in the web UI:
   209  https://%s/app/%s/%s/variables
   210  `
   211  
   212  const applyErrNoConfig = `
   213  No configuration files found!
   214  
   215  Apply requires configuration to be present. Applying without a configuration
   216  would mark everything for destruction, which is normally not what is desired.
   217  If you would like to destroy everything, please run 'terraform destroy' which
   218  does not require any configuration files.
   219  `
   220  
   221  const applyErrNoApplyRights = `
   222  Insufficient rights to approve the pending changes!
   223  
   224  [reset][yellow]There are pending changes, but the provided credentials have insufficient rights
   225  to approve them. The run will be discarded to prevent it from blocking the queue
   226  waiting for external approval. To queue a run that can be approved by someone
   227  else, please use the 'Queue Plan' button in the web UI:
   228  https://%s/app/%s/%s/runs[reset]
   229  `
   230  
   231  const applyDefaultHeader = `
   232  [reset][yellow]Running apply in the remote backend. Output will stream here. Pressing Ctrl-C
   233  will cancel the remote apply if its still pending. If the apply started it
   234  will stop streaming the logs, but will not stop the apply running remotely.[reset]
   235  
   236  Preparing the remote apply...
   237  `