github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/cloud/backend_apply.go (about)

     1  package cloud
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"io"
     7  	"log"
     8  
     9  	tfe "github.com/hashicorp/go-tfe"
    10  	"github.com/hashicorp/terraform/internal/backend"
    11  	"github.com/hashicorp/terraform/internal/plans"
    12  	"github.com/hashicorp/terraform/internal/terraform"
    13  	"github.com/hashicorp/terraform/internal/tfdiags"
    14  )
    15  
    16  func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backend.Operation, w *tfe.Workspace) (*tfe.Run, error) {
    17  	log.Printf("[INFO] cloud: starting Apply operation")
    18  
    19  	var diags tfdiags.Diagnostics
    20  
    21  	// We should remove the `CanUpdate` part of this test, but for now
    22  	// (to remain compatible with tfe.v2.1) we'll leave it in here.
    23  	if !w.Permissions.CanUpdate && !w.Permissions.CanQueueApply {
    24  		diags = diags.Append(tfdiags.Sourceless(
    25  			tfdiags.Error,
    26  			"Insufficient rights to apply changes",
    27  			"The provided credentials have insufficient rights to apply changes. In order "+
    28  				"to apply changes at least write permissions on the workspace are required.",
    29  		))
    30  		return nil, diags.Err()
    31  	}
    32  
    33  	if w.VCSRepo != nil {
    34  		diags = diags.Append(tfdiags.Sourceless(
    35  			tfdiags.Error,
    36  			"Apply not allowed for workspaces with a VCS connection",
    37  			"A workspace that is connected to a VCS requires the VCS-driven workflow "+
    38  				"to ensure that the VCS remains the single source of truth.",
    39  		))
    40  		return nil, diags.Err()
    41  	}
    42  
    43  	if b.ContextOpts != nil && b.ContextOpts.Parallelism != defaultParallelism {
    44  		diags = diags.Append(tfdiags.Sourceless(
    45  			tfdiags.Error,
    46  			"Custom parallelism values are currently not supported",
    47  			`Terraform Cloud does not support setting a custom parallelism `+
    48  				`value at this time.`,
    49  		))
    50  	}
    51  
    52  	if op.PlanFile != nil {
    53  		diags = diags.Append(tfdiags.Sourceless(
    54  			tfdiags.Error,
    55  			"Applying a saved plan is currently not supported",
    56  			`Terraform Cloud currently requires configuration to be present and `+
    57  				`does not accept an existing saved plan as an argument at this time.`,
    58  		))
    59  	}
    60  
    61  	if !op.HasConfig() && op.PlanMode != plans.DestroyMode {
    62  		diags = diags.Append(tfdiags.Sourceless(
    63  			tfdiags.Error,
    64  			"No configuration files found",
    65  			`Apply requires configuration to be present. Applying without a configuration `+
    66  				`would mark everything for destruction, which is normally not what is desired. `+
    67  				`If you would like to destroy everything, please run 'terraform destroy' which `+
    68  				`does not require any configuration files.`,
    69  		))
    70  	}
    71  
    72  	// Return if there are any errors.
    73  	if diags.HasErrors() {
    74  		return nil, diags.Err()
    75  	}
    76  
    77  	// Run the plan phase.
    78  	r, err := b.plan(stopCtx, cancelCtx, op, w)
    79  	if err != nil {
    80  		return r, err
    81  	}
    82  
    83  	// This check is also performed in the plan method to determine if
    84  	// the policies should be checked, but we need to check the values
    85  	// here again to determine if we are done and should return.
    86  	if !r.HasChanges || r.Status == tfe.RunCanceled || r.Status == tfe.RunErrored {
    87  		return r, nil
    88  	}
    89  
    90  	// Retrieve the run to get its current status.
    91  	r, err = b.client.Runs.Read(stopCtx, r.ID)
    92  	if err != nil {
    93  		return r, generalError("Failed to retrieve run", err)
    94  	}
    95  
    96  	// Return if the run cannot be confirmed.
    97  	if !op.AutoApprove && !r.Actions.IsConfirmable {
    98  		return r, nil
    99  	}
   100  
   101  	mustConfirm := (op.UIIn != nil && op.UIOut != nil) && !op.AutoApprove
   102  
   103  	if mustConfirm && b.input {
   104  		opts := &terraform.InputOpts{Id: "approve"}
   105  
   106  		if op.PlanMode == plans.DestroyMode {
   107  			opts.Query = "\nDo you really want to destroy all resources in workspace \"" + op.Workspace + "\"?"
   108  			opts.Description = "Terraform will destroy all your managed infrastructure, as shown above.\n" +
   109  				"There is no undo. Only 'yes' will be accepted to confirm."
   110  		} else {
   111  			opts.Query = "\nDo you want to perform these actions in workspace \"" + op.Workspace + "\"?"
   112  			opts.Description = "Terraform will perform the actions described above.\n" +
   113  				"Only 'yes' will be accepted to approve."
   114  		}
   115  
   116  		err = b.confirm(stopCtx, op, opts, r, "yes")
   117  		if err != nil && err != errRunApproved {
   118  			return r, err
   119  		}
   120  	} else if mustConfirm && !b.input {
   121  		return r, errApplyNeedsUIConfirmation
   122  	} else {
   123  		// If we don't need to ask for confirmation, insert a blank
   124  		// line to separate the ouputs.
   125  		if b.CLI != nil {
   126  			b.CLI.Output("")
   127  		}
   128  	}
   129  
   130  	if !op.AutoApprove && err != errRunApproved {
   131  		if err = b.client.Runs.Apply(stopCtx, r.ID, tfe.RunApplyOptions{}); err != nil {
   132  			return r, generalError("Failed to approve the apply command", err)
   133  		}
   134  	}
   135  
   136  	r, err = b.waitForRun(stopCtx, cancelCtx, op, "apply", r, w)
   137  	if err != nil {
   138  		return r, err
   139  	}
   140  
   141  	logs, err := b.client.Applies.Logs(stopCtx, r.Apply.ID)
   142  	if err != nil {
   143  		return r, generalError("Failed to retrieve logs", err)
   144  	}
   145  	reader := bufio.NewReaderSize(logs, 64*1024)
   146  
   147  	if b.CLI != nil {
   148  		skip := 0
   149  		for next := true; next; {
   150  			var l, line []byte
   151  
   152  			for isPrefix := true; isPrefix; {
   153  				l, isPrefix, err = reader.ReadLine()
   154  				if err != nil {
   155  					if err != io.EOF {
   156  						return r, generalError("Failed to read logs", err)
   157  					}
   158  					next = false
   159  				}
   160  				line = append(line, l...)
   161  			}
   162  
   163  			// Skip the first 3 lines to prevent duplicate output.
   164  			if skip < 3 {
   165  				skip++
   166  				continue
   167  			}
   168  
   169  			if next || len(line) > 0 {
   170  				b.CLI.Output(b.Colorize().Color(string(line)))
   171  			}
   172  		}
   173  	}
   174  
   175  	return r, nil
   176  }
   177  
   178  const applyDefaultHeader = `
   179  [reset][yellow]Running apply in Terraform Cloud. Output will stream here. Pressing Ctrl-C
   180  will cancel the remote apply if it's still pending. If the apply started it
   181  will stop streaming the logs, but will not stop the apply running remotely.[reset]
   182  
   183  Preparing the remote apply...
   184  `