github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/backend/local/backend.go (about) 1 package local 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "sort" 11 "strings" 12 "sync" 13 14 "github.com/hashicorp/terraform/backend" 15 "github.com/hashicorp/terraform/helper/schema" 16 "github.com/hashicorp/terraform/state" 17 "github.com/hashicorp/terraform/terraform" 18 "github.com/mitchellh/cli" 19 "github.com/mitchellh/colorstring" 20 ) 21 22 const ( 23 DefaultEnvDir = "terraform.tfstate.d" 24 DefaultEnvFile = "environment" 25 DefaultStateFilename = "terraform.tfstate" 26 DefaultDataDir = ".terraform" 27 DefaultBackupExtension = ".backup" 28 ) 29 30 // Local is an implementation of EnhancedBackend that performs all operations 31 // locally. This is the "default" backend and implements normal Terraform 32 // behavior as it is well known. 33 type Local struct { 34 // CLI and Colorize control the CLI output. If CLI is nil then no CLI 35 // output will be done. If CLIColor is nil then no coloring will be done. 36 CLI cli.Ui 37 CLIColor *colorstring.Colorize 38 39 // The State* paths are set from the CLI options, and may be left blank to 40 // use the defaults. If the actual paths for the local backend state are 41 // needed, use the StatePaths method. 42 // 43 // StatePath is the local path where state is read from. 44 // 45 // StateOutPath is the local path where the state will be written. 46 // If this is empty, it will default to StatePath. 47 // 48 // StateBackupPath is the local path where a backup file will be written. 49 // Set this to "-" to disable state backup. 50 // 51 // StateEnvPath is the path to the folder containing environments. This 52 // defaults to DefaultEnvDir if not set. 53 StatePath string 54 StateOutPath string 55 StateBackupPath string 56 StateEnvDir string 57 58 // We only want to create a single instance of a local state, so store them 59 // here as they're loaded. 60 states map[string]state.State 61 62 // Terraform context. Many of these will be overridden or merged by 63 // Operation. See Operation for more details. 64 ContextOpts *terraform.ContextOpts 65 66 // OpInput will ask for necessary input prior to performing any operations. 67 // 68 // OpValidation will perform validation prior to running an operation. The 69 // variable naming doesn't match the style of others since we have a func 70 // Validate. 71 OpInput bool 72 OpValidation bool 73 74 // Backend, if non-nil, will use this backend for non-enhanced behavior. 75 // This allows local behavior with remote state storage. It is a way to 76 // "upgrade" a non-enhanced backend to an enhanced backend with typical 77 // behavior. 78 // 79 // If this is nil, local performs normal state loading and storage. 80 Backend backend.Backend 81 82 schema *schema.Backend 83 opLock sync.Mutex 84 once sync.Once 85 } 86 87 func (b *Local) Input( 88 ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 89 b.once.Do(b.init) 90 91 f := b.schema.Input 92 if b.Backend != nil { 93 f = b.Backend.Input 94 } 95 96 return f(ui, c) 97 } 98 99 func (b *Local) Validate(c *terraform.ResourceConfig) ([]string, []error) { 100 b.once.Do(b.init) 101 102 f := b.schema.Validate 103 if b.Backend != nil { 104 f = b.Backend.Validate 105 } 106 107 return f(c) 108 } 109 110 func (b *Local) Configure(c *terraform.ResourceConfig) error { 111 b.once.Do(b.init) 112 113 f := b.schema.Configure 114 if b.Backend != nil { 115 f = b.Backend.Configure 116 } 117 118 return f(c) 119 } 120 121 func (b *Local) States() ([]string, error) { 122 // If we have a backend handling state, defer to that. 123 if b.Backend != nil { 124 return b.Backend.States() 125 } 126 127 // the listing always start with "default" 128 envs := []string{backend.DefaultStateName} 129 130 entries, err := ioutil.ReadDir(b.stateEnvDir()) 131 // no error if there's no envs configured 132 if os.IsNotExist(err) { 133 return envs, nil 134 } 135 if err != nil { 136 return nil, err 137 } 138 139 var listed []string 140 for _, entry := range entries { 141 if entry.IsDir() { 142 listed = append(listed, filepath.Base(entry.Name())) 143 } 144 } 145 146 sort.Strings(listed) 147 envs = append(envs, listed...) 148 149 return envs, nil 150 } 151 152 // DeleteState removes a named state. 153 // The "default" state cannot be removed. 154 func (b *Local) DeleteState(name string) error { 155 // If we have a backend handling state, defer to that. 156 if b.Backend != nil { 157 return b.Backend.DeleteState(name) 158 } 159 160 if name == "" { 161 return errors.New("empty state name") 162 } 163 164 if name == backend.DefaultStateName { 165 return errors.New("cannot delete default state") 166 } 167 168 delete(b.states, name) 169 return os.RemoveAll(filepath.Join(b.stateEnvDir(), name)) 170 } 171 172 func (b *Local) State(name string) (state.State, error) { 173 statePath, stateOutPath, backupPath := b.StatePaths(name) 174 175 // If we have a backend handling state, defer to that. 176 if b.Backend != nil { 177 s, err := b.Backend.State(name) 178 if err != nil { 179 return nil, err 180 } 181 182 // make sure we always have a backup state, unless it disabled 183 if backupPath == "" { 184 return s, nil 185 } 186 187 // see if the delegated backend returned a BackupState of its own 188 if s, ok := s.(*state.BackupState); ok { 189 return s, nil 190 } 191 192 s = &state.BackupState{ 193 Real: s, 194 Path: backupPath, 195 } 196 return s, nil 197 } 198 199 if s, ok := b.states[name]; ok { 200 return s, nil 201 } 202 203 if err := b.createState(name); err != nil { 204 return nil, err 205 } 206 207 // Otherwise, we need to load the state. 208 var s state.State = &state.LocalState{ 209 Path: statePath, 210 PathOut: stateOutPath, 211 } 212 213 // If we are backing up the state, wrap it 214 if backupPath != "" { 215 s = &state.BackupState{ 216 Real: s, 217 Path: backupPath, 218 } 219 } 220 221 if b.states == nil { 222 b.states = map[string]state.State{} 223 } 224 b.states[name] = s 225 return s, nil 226 } 227 228 // Operation implements backend.Enhanced 229 // 230 // This will initialize an in-memory terraform.Context to perform the 231 // operation within this process. 232 // 233 // The given operation parameter will be merged with the ContextOpts on 234 // the structure with the following rules. If a rule isn't specified and the 235 // name conflicts, assume that the field is overwritten if set. 236 func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) { 237 // Determine the function to call for our operation 238 var f func(context.Context, *backend.Operation, *backend.RunningOperation) 239 switch op.Type { 240 case backend.OperationTypeRefresh: 241 f = b.opRefresh 242 case backend.OperationTypePlan: 243 f = b.opPlan 244 case backend.OperationTypeApply: 245 f = b.opApply 246 default: 247 return nil, fmt.Errorf( 248 "Unsupported operation type: %s\n\n"+ 249 "This is a bug in Terraform and should be reported. The local backend\n"+ 250 "is built-in to Terraform and should always support all operations.", 251 op.Type) 252 } 253 254 // Lock 255 b.opLock.Lock() 256 257 // Build our running operation 258 runningCtx, runningCtxCancel := context.WithCancel(context.Background()) 259 runningOp := &backend.RunningOperation{Context: runningCtx} 260 261 // Do it 262 go func() { 263 defer b.opLock.Unlock() 264 defer runningCtxCancel() 265 f(ctx, op, runningOp) 266 }() 267 268 // Return 269 return runningOp, nil 270 } 271 272 // Colorize returns the Colorize structure that can be used for colorizing 273 // output. This is gauranteed to always return a non-nil value and so is useful 274 // as a helper to wrap any potentially colored strings. 275 func (b *Local) Colorize() *colorstring.Colorize { 276 if b.CLIColor != nil { 277 return b.CLIColor 278 } 279 280 return &colorstring.Colorize{ 281 Colors: colorstring.DefaultColors, 282 Disable: true, 283 } 284 } 285 286 func (b *Local) init() { 287 b.schema = &schema.Backend{ 288 Schema: map[string]*schema.Schema{ 289 "path": &schema.Schema{ 290 Type: schema.TypeString, 291 Optional: true, 292 Default: "", 293 }, 294 295 "environment_dir": &schema.Schema{ 296 Type: schema.TypeString, 297 Optional: true, 298 Default: "", 299 }, 300 }, 301 302 ConfigureFunc: b.schemaConfigure, 303 } 304 } 305 306 func (b *Local) schemaConfigure(ctx context.Context) error { 307 d := schema.FromContextBackendConfig(ctx) 308 309 // Set the path if it is set 310 pathRaw, ok := d.GetOk("path") 311 if ok { 312 path := pathRaw.(string) 313 if path == "" { 314 return fmt.Errorf("configured path is empty") 315 } 316 317 b.StatePath = path 318 b.StateOutPath = path 319 } 320 321 if raw, ok := d.GetOk("environment_dir"); ok { 322 path := raw.(string) 323 if path != "" { 324 b.StateEnvDir = path 325 } 326 } 327 328 return nil 329 } 330 331 // StatePaths returns the StatePath, StateOutPath, and StateBackupPath as 332 // configured from the CLI. 333 func (b *Local) StatePaths(name string) (string, string, string) { 334 statePath := b.StatePath 335 stateOutPath := b.StateOutPath 336 backupPath := b.StateBackupPath 337 338 if name == "" { 339 name = backend.DefaultStateName 340 } 341 342 if name == backend.DefaultStateName { 343 if statePath == "" { 344 statePath = DefaultStateFilename 345 } 346 } else { 347 statePath = filepath.Join(b.stateEnvDir(), name, DefaultStateFilename) 348 } 349 350 if stateOutPath == "" { 351 stateOutPath = statePath 352 } 353 354 switch backupPath { 355 case "-": 356 backupPath = "" 357 case "": 358 backupPath = stateOutPath + DefaultBackupExtension 359 } 360 361 return statePath, stateOutPath, backupPath 362 } 363 364 // this only ensures that the named directory exists 365 func (b *Local) createState(name string) error { 366 if name == backend.DefaultStateName { 367 return nil 368 } 369 370 stateDir := filepath.Join(b.stateEnvDir(), name) 371 s, err := os.Stat(stateDir) 372 if err == nil && s.IsDir() { 373 // no need to check for os.IsNotExist, since that is covered by os.MkdirAll 374 // which will catch the other possible errors as well. 375 return nil 376 } 377 378 err = os.MkdirAll(stateDir, 0755) 379 if err != nil { 380 return err 381 } 382 383 return nil 384 } 385 386 // stateEnvDir returns the directory where state environments are stored. 387 func (b *Local) stateEnvDir() string { 388 if b.StateEnvDir != "" { 389 return b.StateEnvDir 390 } 391 392 return DefaultEnvDir 393 } 394 395 // currentStateName returns the name of the current named state as set in the 396 // configuration files. 397 // If there are no configured environments, currentStateName returns "default" 398 func (b *Local) currentStateName() (string, error) { 399 contents, err := ioutil.ReadFile(filepath.Join(DefaultDataDir, DefaultEnvFile)) 400 if os.IsNotExist(err) { 401 return backend.DefaultStateName, nil 402 } 403 if err != nil { 404 return "", err 405 } 406 407 if fromFile := strings.TrimSpace(string(contents)); fromFile != "" { 408 return fromFile, nil 409 } 410 411 return backend.DefaultStateName, nil 412 }