github.com/hs0210/hashicorp-terraform@v0.11.12-beta1/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 } 95 96 // New returns a new initialized local backend. 97 func New() *Local { 98 return NewWithBackend(nil) 99 } 100 101 // NewWithBackend returns a new local backend initialized with a 102 // dedicated backend for non-enhanced behavior. 103 func NewWithBackend(backend backend.Backend) *Local { 104 b := &Local{ 105 Backend: backend, 106 } 107 108 b.schema = &schema.Backend{ 109 Schema: map[string]*schema.Schema{ 110 "path": &schema.Schema{ 111 Type: schema.TypeString, 112 Optional: true, 113 Default: "", 114 }, 115 116 "workspace_dir": &schema.Schema{ 117 Type: schema.TypeString, 118 Optional: true, 119 Default: "", 120 }, 121 122 "environment_dir": &schema.Schema{ 123 Type: schema.TypeString, 124 Optional: true, 125 Default: "", 126 ConflictsWith: []string{"workspace_dir"}, 127 Deprecated: "workspace_dir should be used instead, with the same meaning", 128 }, 129 }, 130 131 ConfigureFunc: b.configure, 132 } 133 134 return b 135 } 136 137 func (b *Local) configure(ctx context.Context) error { 138 d := schema.FromContextBackendConfig(ctx) 139 140 // Set the path if it is set 141 pathRaw, ok := d.GetOk("path") 142 if ok { 143 path := pathRaw.(string) 144 if path == "" { 145 return fmt.Errorf("configured path is empty") 146 } 147 148 b.StatePath = path 149 b.StateOutPath = path 150 } 151 152 if raw, ok := d.GetOk("workspace_dir"); ok { 153 path := raw.(string) 154 if path != "" { 155 b.StateWorkspaceDir = path 156 } 157 } 158 159 // Legacy name, which ConflictsWith workspace_dir 160 if raw, ok := d.GetOk("environment_dir"); ok { 161 path := raw.(string) 162 if path != "" { 163 b.StateWorkspaceDir = path 164 } 165 } 166 167 return nil 168 } 169 170 func (b *Local) Input(ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 171 f := b.schema.Input 172 if b.Backend != nil { 173 f = b.Backend.Input 174 } 175 return f(ui, c) 176 } 177 178 func (b *Local) Validate(c *terraform.ResourceConfig) ([]string, []error) { 179 f := b.schema.Validate 180 if b.Backend != nil { 181 f = b.Backend.Validate 182 } 183 return f(c) 184 } 185 186 func (b *Local) Configure(c *terraform.ResourceConfig) error { 187 f := b.schema.Configure 188 if b.Backend != nil { 189 f = b.Backend.Configure 190 } 191 return f(c) 192 } 193 194 func (b *Local) State(name string) (state.State, error) { 195 statePath, stateOutPath, backupPath := b.StatePaths(name) 196 197 // If we have a backend handling state, delegate to that. 198 if b.Backend != nil { 199 return b.Backend.State(name) 200 } 201 202 if s, ok := b.states[name]; ok { 203 return s, nil 204 } 205 206 if err := b.createState(name); err != nil { 207 return nil, err 208 } 209 210 // Otherwise, we need to load the state. 211 var s state.State = &state.LocalState{ 212 Path: statePath, 213 PathOut: stateOutPath, 214 } 215 216 // If we are backing up the state, wrap it 217 if backupPath != "" { 218 s = &state.BackupState{ 219 Real: s, 220 Path: backupPath, 221 } 222 } 223 224 if b.states == nil { 225 b.states = map[string]state.State{} 226 } 227 b.states[name] = s 228 return s, nil 229 } 230 231 // DeleteState removes a named state. 232 // The "default" state cannot be removed. 233 func (b *Local) DeleteState(name string) error { 234 // If we have a backend handling state, defer to that. 235 if b.Backend != nil { 236 return b.Backend.DeleteState(name) 237 } 238 239 if name == "" { 240 return errors.New("empty state name") 241 } 242 243 if name == backend.DefaultStateName { 244 return errors.New("cannot delete default state") 245 } 246 247 delete(b.states, name) 248 return os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name)) 249 } 250 251 func (b *Local) States() ([]string, error) { 252 // If we have a backend handling state, defer to that. 253 if b.Backend != nil { 254 return b.Backend.States() 255 } 256 257 // the listing always start with "default" 258 envs := []string{backend.DefaultStateName} 259 260 entries, err := ioutil.ReadDir(b.stateWorkspaceDir()) 261 // no error if there's no envs configured 262 if os.IsNotExist(err) { 263 return envs, nil 264 } 265 if err != nil { 266 return nil, err 267 } 268 269 var listed []string 270 for _, entry := range entries { 271 if entry.IsDir() { 272 listed = append(listed, filepath.Base(entry.Name())) 273 } 274 } 275 276 sort.Strings(listed) 277 envs = append(envs, listed...) 278 279 return envs, nil 280 } 281 282 // Operation implements backend.Enhanced 283 // 284 // This will initialize an in-memory terraform.Context to perform the 285 // operation within this process. 286 // 287 // The given operation parameter will be merged with the ContextOpts on 288 // the structure with the following rules. If a rule isn't specified and the 289 // name conflicts, assume that the field is overwritten if set. 290 func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) { 291 // Determine the function to call for our operation 292 var f func(context.Context, context.Context, *backend.Operation, *backend.RunningOperation) 293 switch op.Type { 294 case backend.OperationTypeRefresh: 295 f = b.opRefresh 296 case backend.OperationTypePlan: 297 f = b.opPlan 298 case backend.OperationTypeApply: 299 f = b.opApply 300 default: 301 return nil, fmt.Errorf( 302 "Unsupported operation type: %s\n\n"+ 303 "This is a bug in Terraform and should be reported. The local backend\n"+ 304 "is built-in to Terraform and should always support all operations.", 305 op.Type) 306 } 307 308 // Lock 309 b.opLock.Lock() 310 311 // Build our running operation 312 // the runninCtx is only used to block until the operation returns. 313 runningCtx, done := context.WithCancel(context.Background()) 314 runningOp := &backend.RunningOperation{ 315 Context: runningCtx, 316 } 317 318 // stopCtx wraps the context passed in, and is used to signal a graceful Stop. 319 stopCtx, stop := context.WithCancel(ctx) 320 runningOp.Stop = stop 321 322 // cancelCtx is used to cancel the operation immediately, usually 323 // indicating that the process is exiting. 324 cancelCtx, cancel := context.WithCancel(context.Background()) 325 runningOp.Cancel = cancel 326 327 if op.LockState { 328 op.StateLocker = clistate.NewLocker(stopCtx, op.StateLockTimeout, b.CLI, b.Colorize()) 329 } else { 330 op.StateLocker = clistate.NewNoopLocker() 331 } 332 333 // Do it 334 go func() { 335 defer done() 336 defer stop() 337 defer cancel() 338 339 // the state was locked during context creation, unlock the state when 340 // the operation completes 341 defer func() { 342 runningOp.Err = op.StateLocker.Unlock(runningOp.Err) 343 }() 344 345 defer b.opLock.Unlock() 346 f(stopCtx, cancelCtx, op, runningOp) 347 }() 348 349 // Return 350 return runningOp, nil 351 } 352 353 // opWait waits for the operation to complete, and a stop signal or a 354 // cancelation signal. 355 func (b *Local) opWait( 356 doneCh <-chan struct{}, 357 stopCtx context.Context, 358 cancelCtx context.Context, 359 tfCtx *terraform.Context, 360 opState state.State) (canceled bool) { 361 // Wait for the operation to finish or for us to be interrupted so 362 // we can handle it properly. 363 select { 364 case <-stopCtx.Done(): 365 if b.CLI != nil { 366 b.CLI.Output("stopping operation...") 367 } 368 369 // try to force a PersistState just in case the process is terminated 370 // before we can complete. 371 if err := opState.PersistState(); err != nil { 372 // We can't error out from here, but warn the user if there was an error. 373 // If this isn't transient, we will catch it again below, and 374 // attempt to save the state another way. 375 if b.CLI != nil { 376 b.CLI.Error(fmt.Sprintf(earlyStateWriteErrorFmt, err)) 377 } 378 } 379 380 // Stop execution 381 go tfCtx.Stop() 382 383 select { 384 case <-cancelCtx.Done(): 385 log.Println("[WARN] running operation canceled") 386 // if the operation was canceled, we need to return immediately 387 canceled = true 388 case <-doneCh: 389 } 390 case <-cancelCtx.Done(): 391 // this should not be called without first attempting to stop the 392 // operation 393 log.Println("[ERROR] running operation canceled without Stop") 394 canceled = true 395 case <-doneCh: 396 } 397 return 398 } 399 400 // Colorize returns the Colorize structure that can be used for colorizing 401 // output. This is gauranteed to always return a non-nil value and so is useful 402 // as a helper to wrap any potentially colored strings. 403 func (b *Local) Colorize() *colorstring.Colorize { 404 if b.CLIColor != nil { 405 return b.CLIColor 406 } 407 408 return &colorstring.Colorize{ 409 Colors: colorstring.DefaultColors, 410 Disable: true, 411 } 412 } 413 414 // StatePaths returns the StatePath, StateOutPath, and StateBackupPath as 415 // configured from the CLI. 416 func (b *Local) StatePaths(name string) (string, string, string) { 417 statePath := b.StatePath 418 stateOutPath := b.StateOutPath 419 backupPath := b.StateBackupPath 420 421 if name == "" { 422 name = backend.DefaultStateName 423 } 424 425 if name == backend.DefaultStateName { 426 if statePath == "" { 427 statePath = DefaultStateFilename 428 } 429 } else { 430 statePath = filepath.Join(b.stateWorkspaceDir(), name, DefaultStateFilename) 431 } 432 433 if stateOutPath == "" { 434 stateOutPath = statePath 435 } 436 437 switch backupPath { 438 case "-": 439 backupPath = "" 440 case "": 441 backupPath = stateOutPath + DefaultBackupExtension 442 } 443 444 return statePath, stateOutPath, backupPath 445 } 446 447 // this only ensures that the named directory exists 448 func (b *Local) createState(name string) error { 449 if name == backend.DefaultStateName { 450 return nil 451 } 452 453 stateDir := filepath.Join(b.stateWorkspaceDir(), name) 454 s, err := os.Stat(stateDir) 455 if err == nil && s.IsDir() { 456 // no need to check for os.IsNotExist, since that is covered by os.MkdirAll 457 // which will catch the other possible errors as well. 458 return nil 459 } 460 461 err = os.MkdirAll(stateDir, 0755) 462 if err != nil { 463 return err 464 } 465 466 return nil 467 } 468 469 // stateWorkspaceDir returns the directory where state environments are stored. 470 func (b *Local) stateWorkspaceDir() string { 471 if b.StateWorkspaceDir != "" { 472 return b.StateWorkspaceDir 473 } 474 475 return DefaultWorkspaceDir 476 } 477 478 func (b *Local) pluginInitRequired(providerErr *terraform.ResourceProviderError) { 479 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 480 strings.TrimSpace(errPluginInit)+"\n", 481 providerErr))) 482 } 483 484 // this relies on multierror to format the plugin errors below the copy 485 const errPluginInit = ` 486 [reset][bold][yellow]Plugin reinitialization required. Please run "terraform init".[reset] 487 [yellow]Reason: Could not satisfy plugin requirements. 488 489 Plugins are external binaries that Terraform uses to access and manipulate 490 resources. The configuration provided requires plugins which can't be located, 491 don't satisfy the version constraints, or are otherwise incompatible. 492 493 [reset][red]%s 494 495 [reset][yellow]Terraform automatically discovers provider requirements from your 496 configuration, including providers used in child modules. To see the 497 requirements and constraints from each module, run "terraform providers". 498 `