github.com/ctrox/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 `