github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/backend/local/backend.go (about) 1 package local 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/hashicorp/errwrap" 9 "github.com/hashicorp/terraform/backend" 10 "github.com/hashicorp/terraform/helper/schema" 11 "github.com/hashicorp/terraform/state" 12 "github.com/hashicorp/terraform/terraform" 13 "github.com/mitchellh/cli" 14 "github.com/mitchellh/colorstring" 15 ) 16 17 // Local is an implementation of EnhancedBackend that performs all operations 18 // locally. This is the "default" backend and implements normal Terraform 19 // behavior as it is well known. 20 type Local struct { 21 // CLI and Colorize control the CLI output. If CLI is nil then no CLI 22 // output will be done. If CLIColor is nil then no coloring will be done. 23 CLI cli.Ui 24 CLIColor *colorstring.Colorize 25 26 // StatePath is the local path where state is read from. 27 // 28 // StateOutPath is the local path where the state will be written. 29 // If this is empty, it will default to StatePath. 30 // 31 // StateBackupPath is the local path where a backup file will be written. 32 // If this is empty, no backup will be taken. 33 StatePath string 34 StateOutPath string 35 StateBackupPath string 36 37 // we only want to create a single instance of the local state 38 state state.State 39 40 // ContextOpts are the base context options to set when initializing a 41 // Terraform context. Many of these will be overridden or merged by 42 // Operation. See Operation for more details. 43 ContextOpts *terraform.ContextOpts 44 45 // OpInput will ask for necessary input prior to performing any operations. 46 // 47 // OpValidation will perform validation prior to running an operation. The 48 // variable naming doesn't match the style of others since we have a func 49 // Validate. 50 OpInput bool 51 OpValidation bool 52 53 // Backend, if non-nil, will use this backend for non-enhanced behavior. 54 // This allows local behavior with remote state storage. It is a way to 55 // "upgrade" a non-enhanced backend to an enhanced backend with typical 56 // behavior. 57 // 58 // If this is nil, local performs normal state loading and storage. 59 Backend backend.Backend 60 61 schema *schema.Backend 62 opLock sync.Mutex 63 once sync.Once 64 } 65 66 func (b *Local) Input( 67 ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 68 b.once.Do(b.init) 69 70 f := b.schema.Input 71 if b.Backend != nil { 72 f = b.Backend.Input 73 } 74 75 return f(ui, c) 76 } 77 78 func (b *Local) Validate(c *terraform.ResourceConfig) ([]string, []error) { 79 b.once.Do(b.init) 80 81 f := b.schema.Validate 82 if b.Backend != nil { 83 f = b.Backend.Validate 84 } 85 86 return f(c) 87 } 88 89 func (b *Local) Configure(c *terraform.ResourceConfig) error { 90 b.once.Do(b.init) 91 92 f := b.schema.Configure 93 if b.Backend != nil { 94 f = b.Backend.Configure 95 } 96 97 return f(c) 98 } 99 100 func (b *Local) State() (state.State, error) { 101 // If we have a backend handling state, defer to that. 102 if b.Backend != nil { 103 return b.Backend.State() 104 } 105 106 if b.state != nil { 107 return b.state, nil 108 } 109 110 // Otherwise, we need to load the state. 111 var s state.State = &state.LocalState{ 112 Path: b.StatePath, 113 PathOut: b.StateOutPath, 114 } 115 116 // Load the state as a sanity check 117 if err := s.RefreshState(); err != nil { 118 return nil, errwrap.Wrapf("Error reading local state: {{err}}", err) 119 } 120 121 // If we are backing up the state, wrap it 122 if path := b.StateBackupPath; path != "" { 123 s = &state.BackupState{ 124 Real: s, 125 Path: path, 126 } 127 } 128 129 b.state = s 130 return s, nil 131 } 132 133 // Operation implements backend.Enhanced 134 // 135 // This will initialize an in-memory terraform.Context to perform the 136 // operation within this process. 137 // 138 // The given operation parameter will be merged with the ContextOpts on 139 // the structure with the following rules. If a rule isn't specified and the 140 // name conflicts, assume that the field is overwritten if set. 141 func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) { 142 // Determine the function to call for our operation 143 var f func(context.Context, *backend.Operation, *backend.RunningOperation) 144 switch op.Type { 145 case backend.OperationTypeRefresh: 146 f = b.opRefresh 147 case backend.OperationTypePlan: 148 f = b.opPlan 149 case backend.OperationTypeApply: 150 f = b.opApply 151 default: 152 return nil, fmt.Errorf( 153 "Unsupported operation type: %s\n\n"+ 154 "This is a bug in Terraform and should be reported. The local backend\n"+ 155 "is built-in to Terraform and should always support all operations.", 156 op.Type) 157 } 158 159 // Lock 160 b.opLock.Lock() 161 162 // Build our running operation 163 runningCtx, runningCtxCancel := context.WithCancel(context.Background()) 164 runningOp := &backend.RunningOperation{Context: runningCtx} 165 166 // Do it 167 go func() { 168 defer b.opLock.Unlock() 169 defer runningCtxCancel() 170 f(ctx, op, runningOp) 171 }() 172 173 // Return 174 return runningOp, nil 175 } 176 177 // Colorize returns the Colorize structure that can be used for colorizing 178 // output. This is gauranteed to always return a non-nil value and so is useful 179 // as a helper to wrap any potentially colored strings. 180 func (b *Local) Colorize() *colorstring.Colorize { 181 if b.CLIColor != nil { 182 return b.CLIColor 183 } 184 185 return &colorstring.Colorize{ 186 Colors: colorstring.DefaultColors, 187 Disable: true, 188 } 189 } 190 191 func (b *Local) init() { 192 b.schema = &schema.Backend{ 193 Schema: map[string]*schema.Schema{ 194 "path": &schema.Schema{ 195 Type: schema.TypeString, 196 Optional: true, 197 }, 198 }, 199 200 ConfigureFunc: b.schemaConfigure, 201 } 202 } 203 204 func (b *Local) schemaConfigure(ctx context.Context) error { 205 d := schema.FromContextBackendConfig(ctx) 206 207 // Set the path if it is set 208 pathRaw, ok := d.GetOk("path") 209 if ok { 210 path := pathRaw.(string) 211 if path == "" { 212 return fmt.Errorf("configured path is empty") 213 } 214 215 b.StatePath = path 216 } 217 218 return nil 219 }