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