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