github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/backend/backend.go (about) 1 // Package backend provides interfaces that the CLI uses to interact with 2 // Terraform. A backend provides the abstraction that allows the same CLI 3 // to simultaneously support both local and remote operations for seamlessly 4 // using Terraform in a team environment. 5 package backend 6 7 import ( 8 "context" 9 "errors" 10 "io/ioutil" 11 "log" 12 "os" 13 14 "github.com/hashicorp/terraform/internal/addrs" 15 "github.com/hashicorp/terraform/internal/command/clistate" 16 "github.com/hashicorp/terraform/internal/command/views" 17 "github.com/hashicorp/terraform/internal/configs" 18 "github.com/hashicorp/terraform/internal/configs/configload" 19 "github.com/hashicorp/terraform/internal/configs/configschema" 20 "github.com/hashicorp/terraform/internal/plans" 21 "github.com/hashicorp/terraform/internal/plans/planfile" 22 "github.com/hashicorp/terraform/internal/states" 23 "github.com/hashicorp/terraform/internal/states/statemgr" 24 "github.com/hashicorp/terraform/internal/terraform" 25 "github.com/hashicorp/terraform/internal/tfdiags" 26 "github.com/mitchellh/go-homedir" 27 "github.com/zclconf/go-cty/cty" 28 ) 29 30 // DefaultStateName is the name of the default, initial state that every 31 // backend must have. This state cannot be deleted. 32 const DefaultStateName = "default" 33 34 var ( 35 // ErrDefaultWorkspaceNotSupported is returned when an operation does not 36 // support using the default workspace, but requires a named workspace to 37 // be selected. 38 ErrDefaultWorkspaceNotSupported = errors.New("default workspace not supported\n" + 39 "You can create a new workspace with the \"workspace new\" command.") 40 41 // ErrWorkspacesNotSupported is an error returned when a caller attempts 42 // to perform an operation on a workspace other than "default" for a 43 // backend that doesn't support multiple workspaces. 44 // 45 // The caller can detect this to do special fallback behavior or produce 46 // a specific, helpful error message. 47 ErrWorkspacesNotSupported = errors.New("workspaces not supported") 48 ) 49 50 // InitFn is used to initialize a new backend. 51 type InitFn func() Backend 52 53 // Backend is the minimal interface that must be implemented to enable Terraform. 54 type Backend interface { 55 // ConfigSchema returns a description of the expected configuration 56 // structure for the receiving backend. 57 // 58 // This method does not have any side-effects for the backend and can 59 // be safely used before configuring. 60 ConfigSchema() *configschema.Block 61 62 // PrepareConfig checks the validity of the values in the given 63 // configuration, and inserts any missing defaults, assuming that its 64 // structure has already been validated per the schema returned by 65 // ConfigSchema. 66 // 67 // This method does not have any side-effects for the backend and can 68 // be safely used before configuring. It also does not consult any 69 // external data such as environment variables, disk files, etc. Validation 70 // that requires such external data should be deferred until the 71 // Configure call. 72 // 73 // If error diagnostics are returned then the configuration is not valid 74 // and must not subsequently be passed to the Configure method. 75 // 76 // This method may return configuration-contextual diagnostics such 77 // as tfdiags.AttributeValue, and so the caller should provide the 78 // necessary context via the diags.InConfigBody method before returning 79 // diagnostics to the user. 80 PrepareConfig(cty.Value) (cty.Value, tfdiags.Diagnostics) 81 82 // Configure uses the provided configuration to set configuration fields 83 // within the backend. 84 // 85 // The given configuration is assumed to have already been validated 86 // against the schema returned by ConfigSchema and passed validation 87 // via PrepareConfig. 88 // 89 // This method may be called only once per backend instance, and must be 90 // called before all other methods except where otherwise stated. 91 // 92 // If error diagnostics are returned, the internal state of the instance 93 // is undefined and no other methods may be called. 94 Configure(cty.Value) tfdiags.Diagnostics 95 96 // StateMgr returns the state manager for the given workspace name. 97 // 98 // If the returned state manager also implements statemgr.Locker then 99 // it's the caller's responsibility to call Lock and Unlock as appropriate. 100 // 101 // If the named workspace doesn't exist, or if it has no state, it will 102 // be created either immediately on this call or the first time 103 // PersistState is called, depending on the state manager implementation. 104 StateMgr(workspace string) (statemgr.Full, error) 105 106 // DeleteWorkspace removes the workspace with the given name if it exists. 107 // 108 // DeleteWorkspace cannot prevent deleting a state that is in use. It is 109 // the responsibility of the caller to hold a Lock for the state manager 110 // belonging to this workspace before calling this method. 111 DeleteWorkspace(name string) error 112 113 // States returns a list of the names of all of the workspaces that exist 114 // in this backend. 115 Workspaces() ([]string, error) 116 } 117 118 // Enhanced implements additional behavior on top of a normal backend. 119 // 120 // Enhanced backends allow customizing the behavior of Terraform operations. 121 // This allows Terraform to potentially run operations remotely, load 122 // configurations from external sources, etc. 123 type Enhanced interface { 124 Backend 125 126 // Operation performs a Terraform operation such as refresh, plan, apply. 127 // It is up to the implementation to determine what "performing" means. 128 // This DOES NOT BLOCK. The context returned as part of RunningOperation 129 // should be used to block for completion. 130 // If the state used in the operation can be locked, it is the 131 // responsibility of the Backend to lock the state for the duration of the 132 // running operation. 133 Operation(context.Context, *Operation) (*RunningOperation, error) 134 } 135 136 // Local implements additional behavior on a Backend that allows local 137 // operations in addition to remote operations. 138 // 139 // This enables more behaviors of Terraform that require more data such 140 // as `console`, `import`, `graph`. These require direct access to 141 // configurations, variables, and more. Not all backends may support this 142 // so we separate it out into its own optional interface. 143 type Local interface { 144 // LocalRun uses information in the Operation to prepare a set of objects 145 // needed to start running that operation. 146 // 147 // The operation doesn't need a Type set, but it needs various other 148 // options set. This is a rather odd API that tries to treat all 149 // operations as the same when they really aren't; see the local and remote 150 // backend's implementations of this to understand what this actually 151 // does, because this operation has no well-defined contract aside from 152 // "whatever it already does". 153 LocalRun(*Operation) (*LocalRun, statemgr.Full, tfdiags.Diagnostics) 154 } 155 156 // LocalRun represents the assortment of objects that we can collect or 157 // calculate from an Operation object, which we can then use for local 158 // operations. 159 // 160 // The operation methods on terraform.Context (Plan, Apply, Import, etc) each 161 // generate new artifacts which supersede parts of the LocalRun object that 162 // started the operation, so callers should be careful to use those subsequent 163 // artifacts instead of the fields of LocalRun where appropriate. The LocalRun 164 // data intentionally doesn't update as a result of calling methods on Context, 165 // in order to make data flow explicit. 166 // 167 // This type is a weird architectural wart resulting from the overly-general 168 // way our backend API models operations, whereby we behave as if all 169 // Terraform operations have the same inputs and outputs even though they 170 // are actually all rather different. The exact meaning of the fields in 171 // this type therefore vary depending on which OperationType was passed to 172 // Local.Context in order to create an object of this type. 173 type LocalRun struct { 174 // Core is an already-initialized Terraform Core context, ready to be 175 // used to run operations such as Plan and Apply. 176 Core *terraform.Context 177 178 // Config is the configuration we're working with, which typically comes 179 // from either config files directly on local disk (when we're creating 180 // a plan, or similar) or from a snapshot embedded in a plan file 181 // (when we're applying a saved plan). 182 Config *configs.Config 183 184 // InputState is the state that should be used for whatever is the first 185 // method call to a context created with CoreOpts. When creating a plan 186 // this will be the previous run state, but when applying a saved plan 187 // this will be the prior state recorded in that plan. 188 InputState *states.State 189 190 // PlanOpts are options to pass to a Plan or Plan-like operation. 191 // 192 // This is nil when we're applying a saved plan, because the plan itself 193 // contains enough information about its options to apply it. 194 PlanOpts *terraform.PlanOpts 195 196 // Plan is a plan loaded from a saved plan file, if our operation is to 197 // apply that saved plan. 198 // 199 // This is nil when we're not applying a saved plan. 200 Plan *plans.Plan 201 } 202 203 // An operation represents an operation for Terraform to execute. 204 // 205 // Note that not all fields are supported by all backends and can result 206 // in an error if set. All backend implementations should show user-friendly 207 // errors explaining any incorrectly set values. For example, the local 208 // backend doesn't support a PlanId being set. 209 // 210 // The operation options are purposely designed to have maximal compatibility 211 // between Terraform and Terraform Servers (a commercial product offered by 212 // HashiCorp). Therefore, it isn't expected that other implementation support 213 // every possible option. The struct here is generalized in order to allow 214 // even partial implementations to exist in the open, without walling off 215 // remote functionality 100% behind a commercial wall. Anyone can implement 216 // against this interface and have Terraform interact with it just as it 217 // would with HashiCorp-provided Terraform Servers. 218 type Operation struct { 219 // Type is the operation to perform. 220 Type OperationType 221 222 // PlanId is an opaque value that backends can use to execute a specific 223 // plan for an apply operation. 224 // 225 // PlanOutBackend is the backend to store with the plan. This is the 226 // backend that will be used when applying the plan. 227 PlanId string 228 PlanRefresh bool // PlanRefresh will do a refresh before a plan 229 PlanOutPath string // PlanOutPath is the path to save the plan 230 PlanOutBackend *plans.Backend 231 232 // ConfigDir is the path to the directory containing the configuration's 233 // root module. 234 ConfigDir string 235 236 // ConfigLoader is a configuration loader that can be used to load 237 // configuration from ConfigDir. 238 ConfigLoader *configload.Loader 239 240 // Hooks can be used to perform actions triggered by various events during 241 // the operation's lifecycle. 242 Hooks []terraform.Hook 243 244 // Plan is a plan that was passed as an argument. This is valid for 245 // plan and apply arguments but may not work for all backends. 246 PlanFile *planfile.Reader 247 248 // The options below are more self-explanatory and affect the runtime 249 // behavior of the operation. 250 PlanMode plans.Mode 251 AutoApprove bool 252 Parallelism int 253 Targets []addrs.Targetable 254 ForceReplace []addrs.AbsResourceInstance 255 Variables map[string]UnparsedVariableValue 256 257 // Some operations use root module variables only opportunistically or 258 // don't need them at all. If this flag is set, the backend must treat 259 // all variables as optional and provide an unknown value for any required 260 // variables that aren't set in order to allow partial evaluation against 261 // the resulting incomplete context. 262 // 263 // This flag is honored only if PlanFile isn't set. If PlanFile is set then 264 // the variables set in the plan are used instead, and they must be valid. 265 AllowUnsetVariables bool 266 267 // View implements the logic for all UI interactions. 268 View views.Operation 269 270 // Input/output/control options. 271 UIIn terraform.UIInput 272 UIOut terraform.UIOutput 273 274 // StateLocker is used to lock the state while providing UI feedback to the 275 // user. This will be replaced by the Backend to update the context. 276 // 277 // If state locking is not necessary, this should be set to a no-op 278 // implementation of clistate.Locker. 279 StateLocker clistate.Locker 280 281 // Workspace is the name of the workspace that this operation should run 282 // in, which controls which named state is used. 283 Workspace string 284 } 285 286 // HasConfig returns true if and only if the operation has a ConfigDir value 287 // that refers to a directory containing at least one Terraform configuration 288 // file. 289 func (o *Operation) HasConfig() bool { 290 return o.ConfigLoader.IsConfigDir(o.ConfigDir) 291 } 292 293 // Config loads the configuration that the operation applies to, using the 294 // ConfigDir and ConfigLoader fields within the receiving operation. 295 func (o *Operation) Config() (*configs.Config, tfdiags.Diagnostics) { 296 var diags tfdiags.Diagnostics 297 config, hclDiags := o.ConfigLoader.LoadConfig(o.ConfigDir) 298 diags = diags.Append(hclDiags) 299 return config, diags 300 } 301 302 // ReportResult is a helper for the common chore of setting the status of 303 // a running operation and showing any diagnostics produced during that 304 // operation. 305 // 306 // If the given diagnostics contains errors then the operation's result 307 // will be set to backend.OperationFailure. It will be set to 308 // backend.OperationSuccess otherwise. It will then use o.View.Diagnostics 309 // to show the given diagnostics before returning. 310 // 311 // Callers should feel free to do each of these operations separately in 312 // more complex cases where e.g. diagnostics are interleaved with other 313 // output, but terminating immediately after reporting error diagnostics is 314 // common and can be expressed concisely via this method. 315 func (o *Operation) ReportResult(op *RunningOperation, diags tfdiags.Diagnostics) { 316 if diags.HasErrors() { 317 op.Result = OperationFailure 318 } else { 319 op.Result = OperationSuccess 320 } 321 if o.View != nil { 322 o.View.Diagnostics(diags) 323 } else { 324 // Shouldn't generally happen, but if it does then we'll at least 325 // make some noise in the logs to help us spot it. 326 if len(diags) != 0 { 327 log.Printf( 328 "[ERROR] Backend needs to report diagnostics but View is not set:\n%s", 329 diags.ErrWithWarnings(), 330 ) 331 } 332 } 333 } 334 335 // RunningOperation is the result of starting an operation. 336 type RunningOperation struct { 337 // For implementers of a backend, this context should not wrap the 338 // passed in context. Otherwise, cancelling the parent context will 339 // immediately mark this context as "done" but those aren't the semantics 340 // we want: we want this context to be done only when the operation itself 341 // is fully done. 342 context.Context 343 344 // Stop requests the operation to complete early, by calling Stop on all 345 // the plugins. If the process needs to terminate immediately, call Cancel. 346 Stop context.CancelFunc 347 348 // Cancel is the context.CancelFunc associated with the embedded context, 349 // and can be called to terminate the operation early. 350 // Once Cancel is called, the operation should return as soon as possible 351 // to avoid running operations during process exit. 352 Cancel context.CancelFunc 353 354 // Result is the exit status of the operation, populated only after the 355 // operation has completed. 356 Result OperationResult 357 358 // PlanEmpty is populated after a Plan operation completes without error 359 // to note whether a plan is empty or has changes. 360 PlanEmpty bool 361 362 // State is the final state after the operation completed. Persisting 363 // this state is managed by the backend. This should only be read 364 // after the operation completes to avoid read/write races. 365 State *states.State 366 } 367 368 // OperationResult describes the result status of an operation. 369 type OperationResult int 370 371 const ( 372 // OperationSuccess indicates that the operation completed as expected. 373 OperationSuccess OperationResult = 0 374 375 // OperationFailure indicates that the operation encountered some sort 376 // of error, and thus may have been only partially performed or not 377 // performed at all. 378 OperationFailure OperationResult = 1 379 ) 380 381 func (r OperationResult) ExitStatus() int { 382 return int(r) 383 } 384 385 // If the argument is a path, Read loads it and returns the contents, 386 // otherwise the argument is assumed to be the desired contents and is simply 387 // returned. 388 func ReadPathOrContents(poc string) (string, error) { 389 if len(poc) == 0 { 390 return poc, nil 391 } 392 393 path := poc 394 if path[0] == '~' { 395 var err error 396 path, err = homedir.Expand(path) 397 if err != nil { 398 return path, err 399 } 400 } 401 402 if _, err := os.Stat(path); err == nil { 403 contents, err := ioutil.ReadFile(path) 404 if err != nil { 405 return string(contents), err 406 } 407 return string(contents), nil 408 } 409 410 return poc, nil 411 }