github.com/hartzell/terraform@v0.8.6-0.20180503104400-0cc9e050ecd4/backend/local/backend.go (about) 1 package local 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13 "sync" 14 15 "github.com/hashicorp/terraform/backend" 16 "github.com/hashicorp/terraform/command/clistate" 17 "github.com/hashicorp/terraform/helper/schema" 18 "github.com/hashicorp/terraform/state" 19 "github.com/hashicorp/terraform/terraform" 20 "github.com/mitchellh/cli" 21 "github.com/mitchellh/colorstring" 22 ) 23 24 const ( 25 DefaultWorkspaceDir = "terraform.tfstate.d" 26 DefaultWorkspaceFile = "environment" 27 DefaultStateFilename = "terraform.tfstate" 28 DefaultBackupExtension = ".backup" 29 ) 30 31 // Local is an implementation of EnhancedBackend that performs all operations 32 // locally. This is the "default" backend and implements normal Terraform 33 // behavior as it is well known. 34 type Local struct { 35 // CLI and Colorize control the CLI output. If CLI is nil then no CLI 36 // output will be done. If CLIColor is nil then no coloring will be done. 37 CLI cli.Ui 38 CLIColor *colorstring.Colorize 39 40 // The State* paths are set from the backend config, and may be left blank 41 // to use the defaults. If the actual paths for the local backend state are 42 // needed, use the StatePaths method. 43 // 44 // StatePath is the local path where state is read from. 45 // 46 // StateOutPath is the local path where the state will be written. 47 // If this is empty, it will default to StatePath. 48 // 49 // StateBackupPath is the local path where a backup file will be written. 50 // Set this to "-" to disable state backup. 51 // 52 // StateWorkspaceDir is the path to the folder containing data for 53 // non-default workspaces. This defaults to DefaultWorkspaceDir if not set. 54 StatePath string 55 StateOutPath string 56 StateBackupPath string 57 StateWorkspaceDir string 58 59 // We only want to create a single instance of a local state, so store them 60 // here as they're loaded. 61 states map[string]state.State 62 63 // Terraform context. Many of these will be overridden or merged by 64 // Operation. See Operation for more details. 65 ContextOpts *terraform.ContextOpts 66 67 // OpInput will ask for necessary input prior to performing any operations. 68 // 69 // OpValidation will perform validation prior to running an operation. The 70 // variable naming doesn't match the style of others since we have a func 71 // Validate. 72 OpInput bool 73 OpValidation bool 74 75 // Backend, if non-nil, will use this backend for non-enhanced behavior. 76 // This allows local behavior with remote state storage. It is a way to 77 // "upgrade" a non-enhanced backend to an enhanced backend with typical 78 // behavior. 79 // 80 // If this is nil, local performs normal state loading and storage. 81 Backend backend.Backend 82 83 // RunningInAutomation indicates that commands are being run by an 84 // automated system rather than directly at a command prompt. 85 // 86 // This is a hint not to produce messages that expect that a user can 87 // run a follow-up command, perhaps because Terraform is running in 88 // some sort of workflow automation tool that abstracts away the 89 // exact commands that are being run. 90 RunningInAutomation bool 91 92 schema *schema.Backend 93 opLock sync.Mutex 94 once sync.Once 95 } 96 97 func (b *Local) Input( 98 ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 99 b.once.Do(b.init) 100 101 f := b.schema.Input 102 if b.Backend != nil { 103 f = b.Backend.Input 104 } 105 106 return f(ui, c) 107 } 108 109 func (b *Local) Validate(c *terraform.ResourceConfig) ([]string, []error) { 110 b.once.Do(b.init) 111 112 f := b.schema.Validate 113 if b.Backend != nil { 114 f = b.Backend.Validate 115 } 116 117 return f(c) 118 } 119 120 func (b *Local) Configure(c *terraform.ResourceConfig) error { 121 b.once.Do(b.init) 122 123 f := b.schema.Configure 124 if b.Backend != nil { 125 f = b.Backend.Configure 126 } 127 128 return f(c) 129 } 130 131 func (b *Local) States() ([]string, error) { 132 // If we have a backend handling state, defer to that. 133 if b.Backend != nil { 134 return b.Backend.States() 135 } 136 137 // the listing always start with "default" 138 envs := []string{backend.DefaultStateName} 139 140 entries, err := ioutil.ReadDir(b.stateWorkspaceDir()) 141 // no error if there's no envs configured 142 if os.IsNotExist(err) { 143 return envs, nil 144 } 145 if err != nil { 146 return nil, err 147 } 148 149 var listed []string 150 for _, entry := range entries { 151 if entry.IsDir() { 152 listed = append(listed, filepath.Base(entry.Name())) 153 } 154 } 155 156 sort.Strings(listed) 157 envs = append(envs, listed...) 158 159 return envs, nil 160 } 161 162 // DeleteState removes a named state. 163 // The "default" state cannot be removed. 164 func (b *Local) DeleteState(name string) error { 165 // If we have a backend handling state, defer to that. 166 if b.Backend != nil { 167 return b.Backend.DeleteState(name) 168 } 169 170 if name == "" { 171 return errors.New("empty state name") 172 } 173 174 if name == backend.DefaultStateName { 175 return errors.New("cannot delete default state") 176 } 177 178 delete(b.states, name) 179 return os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name)) 180 } 181 182 func (b *Local) State(name string) (state.State, error) { 183 statePath, stateOutPath, backupPath := b.StatePaths(name) 184 185 // If we have a backend handling state, delegate to that. 186 if b.Backend != nil { 187 return b.Backend.State(name) 188 } 189 190 if s, ok := b.states[name]; ok { 191 return s, nil 192 } 193 194 if err := b.createState(name); err != nil { 195 return nil, err 196 } 197 198 // Otherwise, we need to load the state. 199 var s state.State = &state.LocalState{ 200 Path: statePath, 201 PathOut: stateOutPath, 202 } 203 204 // If we are backing up the state, wrap it 205 if backupPath != "" { 206 s = &state.BackupState{ 207 Real: s, 208 Path: backupPath, 209 } 210 } 211 212 if b.states == nil { 213 b.states = map[string]state.State{} 214 } 215 b.states[name] = s 216 return s, nil 217 } 218 219 // Operation implements backend.Enhanced 220 // 221 // This will initialize an in-memory terraform.Context to perform the 222 // operation within this process. 223 // 224 // The given operation parameter will be merged with the ContextOpts on 225 // the structure with the following rules. If a rule isn't specified and the 226 // name conflicts, assume that the field is overwritten if set. 227 func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) { 228 // Determine the function to call for our operation 229 var f func(context.Context, context.Context, *backend.Operation, *backend.RunningOperation) 230 switch op.Type { 231 case backend.OperationTypeRefresh: 232 f = b.opRefresh 233 case backend.OperationTypePlan: 234 f = b.opPlan 235 case backend.OperationTypeApply: 236 f = b.opApply 237 default: 238 return nil, fmt.Errorf( 239 "Unsupported operation type: %s\n\n"+ 240 "This is a bug in Terraform and should be reported. The local backend\n"+ 241 "is built-in to Terraform and should always support all operations.", 242 op.Type) 243 } 244 245 // Lock 246 b.opLock.Lock() 247 248 // Build our running operation 249 // the runninCtx is only used to block until the operation returns. 250 runningCtx, done := context.WithCancel(context.Background()) 251 runningOp := &backend.RunningOperation{ 252 Context: runningCtx, 253 } 254 255 // stopCtx wraps the context passed in, and is used to signal a graceful Stop. 256 stopCtx, stop := context.WithCancel(ctx) 257 runningOp.Stop = stop 258 259 // cancelCtx is used to cancel the operation immediately, usually 260 // indicating that the process is exiting. 261 cancelCtx, cancel := context.WithCancel(context.Background()) 262 runningOp.Cancel = cancel 263 264 if op.LockState { 265 op.StateLocker = clistate.NewLocker(stopCtx, op.StateLockTimeout, b.CLI, b.Colorize()) 266 } else { 267 op.StateLocker = clistate.NewNoopLocker() 268 } 269 270 // Do it 271 go func() { 272 defer done() 273 defer stop() 274 defer cancel() 275 276 // the state was locked during context creation, unlock the state when 277 // the operation completes 278 defer func() { 279 runningOp.Err = op.StateLocker.Unlock(runningOp.Err) 280 }() 281 282 defer b.opLock.Unlock() 283 f(stopCtx, cancelCtx, op, runningOp) 284 }() 285 286 // Return 287 return runningOp, nil 288 } 289 290 // opWait wats for the operation to complete, and a stop signal or a 291 // cancelation signal. 292 func (b *Local) opWait( 293 doneCh <-chan struct{}, 294 stopCtx context.Context, 295 cancelCtx context.Context, 296 tfCtx *terraform.Context, 297 opState state.State) (canceled bool) { 298 // Wait for the operation to finish or for us to be interrupted so 299 // we can handle it properly. 300 select { 301 case <-stopCtx.Done(): 302 if b.CLI != nil { 303 b.CLI.Output("stopping operation...") 304 } 305 306 // try to force a PersistState just in case the process is terminated 307 // before we can complete. 308 if err := opState.PersistState(); err != nil { 309 // We can't error out from here, but warn the user if there was an error. 310 // If this isn't transient, we will catch it again below, and 311 // attempt to save the state another way. 312 if b.CLI != nil { 313 b.CLI.Error(fmt.Sprintf(earlyStateWriteErrorFmt, err)) 314 } 315 } 316 317 // Stop execution 318 go tfCtx.Stop() 319 320 select { 321 case <-cancelCtx.Done(): 322 log.Println("[WARN] running operation canceled") 323 // if the operation was canceled, we need to return immediately 324 canceled = true 325 case <-doneCh: 326 } 327 case <-cancelCtx.Done(): 328 // this should not be called without first attempting to stop the 329 // operation 330 log.Println("[ERROR] running operation canceled without Stop") 331 canceled = true 332 case <-doneCh: 333 } 334 return 335 } 336 337 // Colorize returns the Colorize structure that can be used for colorizing 338 // output. This is gauranteed to always return a non-nil value and so is useful 339 // as a helper to wrap any potentially colored strings. 340 func (b *Local) Colorize() *colorstring.Colorize { 341 if b.CLIColor != nil { 342 return b.CLIColor 343 } 344 345 return &colorstring.Colorize{ 346 Colors: colorstring.DefaultColors, 347 Disable: true, 348 } 349 } 350 351 func (b *Local) init() { 352 b.schema = &schema.Backend{ 353 Schema: map[string]*schema.Schema{ 354 "path": &schema.Schema{ 355 Type: schema.TypeString, 356 Optional: true, 357 Default: "", 358 }, 359 360 "workspace_dir": &schema.Schema{ 361 Type: schema.TypeString, 362 Optional: true, 363 Default: "", 364 }, 365 366 "environment_dir": &schema.Schema{ 367 Type: schema.TypeString, 368 Optional: true, 369 Default: "", 370 ConflictsWith: []string{"workspace_dir"}, 371 372 Deprecated: "workspace_dir should be used instead, with the same meaning", 373 }, 374 }, 375 376 ConfigureFunc: b.schemaConfigure, 377 } 378 } 379 380 func (b *Local) schemaConfigure(ctx context.Context) error { 381 d := schema.FromContextBackendConfig(ctx) 382 383 // Set the path if it is set 384 pathRaw, ok := d.GetOk("path") 385 if ok { 386 path := pathRaw.(string) 387 if path == "" { 388 return fmt.Errorf("configured path is empty") 389 } 390 391 b.StatePath = path 392 b.StateOutPath = path 393 } 394 395 if raw, ok := d.GetOk("workspace_dir"); ok { 396 path := raw.(string) 397 if path != "" { 398 b.StateWorkspaceDir = path 399 } 400 } 401 402 // Legacy name, which ConflictsWith workspace_dir 403 if raw, ok := d.GetOk("environment_dir"); ok { 404 path := raw.(string) 405 if path != "" { 406 b.StateWorkspaceDir = path 407 } 408 } 409 410 return nil 411 } 412 413 // StatePaths returns the StatePath, StateOutPath, and StateBackupPath as 414 // configured from the CLI. 415 func (b *Local) StatePaths(name string) (string, string, string) { 416 statePath := b.StatePath 417 stateOutPath := b.StateOutPath 418 backupPath := b.StateBackupPath 419 420 if name == "" { 421 name = backend.DefaultStateName 422 } 423 424 if name == backend.DefaultStateName { 425 if statePath == "" { 426 statePath = DefaultStateFilename 427 } 428 } else { 429 statePath = filepath.Join(b.stateWorkspaceDir(), name, DefaultStateFilename) 430 } 431 432 if stateOutPath == "" { 433 stateOutPath = statePath 434 } 435 436 switch backupPath { 437 case "-": 438 backupPath = "" 439 case "": 440 backupPath = stateOutPath + DefaultBackupExtension 441 } 442 443 return statePath, stateOutPath, backupPath 444 } 445 446 // this only ensures that the named directory exists 447 func (b *Local) createState(name string) error { 448 if name == backend.DefaultStateName { 449 return nil 450 } 451 452 stateDir := filepath.Join(b.stateWorkspaceDir(), name) 453 s, err := os.Stat(stateDir) 454 if err == nil && s.IsDir() { 455 // no need to check for os.IsNotExist, since that is covered by os.MkdirAll 456 // which will catch the other possible errors as well. 457 return nil 458 } 459 460 err = os.MkdirAll(stateDir, 0755) 461 if err != nil { 462 return err 463 } 464 465 return nil 466 } 467 468 // stateWorkspaceDir returns the directory where state environments are stored. 469 func (b *Local) stateWorkspaceDir() string { 470 if b.StateWorkspaceDir != "" { 471 return b.StateWorkspaceDir 472 } 473 474 return DefaultWorkspaceDir 475 } 476 477 func (b *Local) pluginInitRequired(providerErr *terraform.ResourceProviderError) { 478 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 479 strings.TrimSpace(errPluginInit)+"\n", 480 providerErr))) 481 } 482 483 // this relies on multierror to format the plugin errors below the copy 484 const errPluginInit = ` 485 [reset][bold][yellow]Plugin reinitialization required. Please run "terraform init".[reset] 486 [yellow]Reason: Could not satisfy plugin requirements. 487 488 Plugins are external binaries that Terraform uses to access and manipulate 489 resources. The configuration provided requires plugins which can't be located, 490 don't satisfy the version constraints, or are otherwise incompatible. 491 492 [reset][red]%s 493 494 [reset][yellow]Terraform automatically discovers provider requirements from your 495 configuration, including providers used in child modules. To see the 496 requirements and constraints from each module, run "terraform providers". 497 `