github.com/bendemaree/terraform@v0.5.4-0.20150613200311-f50d97d6eee6/terraform/eval_apply.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "strconv" 7 8 "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/terraform/config" 10 ) 11 12 // EvalApply is an EvalNode implementation that writes the diff to 13 // the full diff. 14 type EvalApply struct { 15 Info *InstanceInfo 16 State **InstanceState 17 Diff **InstanceDiff 18 Provider *ResourceProvider 19 Output **InstanceState 20 CreateNew *bool 21 Error *error 22 } 23 24 // TODO: test 25 func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) { 26 diff := *n.Diff 27 provider := *n.Provider 28 state := *n.State 29 30 // If we have no diff, we have nothing to do! 31 if diff.Empty() { 32 log.Printf( 33 "[DEBUG] apply: %s: diff is empty, doing nothing.", n.Info.Id) 34 return nil, nil 35 } 36 37 // Remove any output values from the diff 38 for k, ad := range diff.Attributes { 39 if ad.Type == DiffAttrOutput { 40 delete(diff.Attributes, k) 41 } 42 } 43 44 // If the state is nil, make it non-nil 45 if state == nil { 46 state = new(InstanceState) 47 } 48 state.init() 49 50 // Flag if we're creating a new instance 51 if n.CreateNew != nil { 52 *n.CreateNew = (state.ID == "" && !diff.Destroy) || diff.RequiresNew() 53 } 54 55 { 56 // Call pre-apply hook 57 err := ctx.Hook(func(h Hook) (HookAction, error) { 58 return h.PreApply(n.Info, state, diff) 59 }) 60 if err != nil { 61 return nil, err 62 } 63 } 64 65 // With the completed diff, apply! 66 log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id) 67 state, err := provider.Apply(n.Info, state, diff) 68 if state == nil { 69 state = new(InstanceState) 70 } 71 state.init() 72 73 // Force the "id" attribute to be our ID 74 if state.ID != "" { 75 state.Attributes["id"] = state.ID 76 } 77 78 // If the value is the unknown variable value, then it is an error. 79 // In this case we record the error and remove it from the state 80 for ak, av := range state.Attributes { 81 if av == config.UnknownVariableValue { 82 err = multierror.Append(err, fmt.Errorf( 83 "Attribute with unknown value: %s", ak)) 84 delete(state.Attributes, ak) 85 } 86 } 87 88 // Write the final state 89 if n.Output != nil { 90 *n.Output = state 91 } 92 93 // If there are no errors, then we append it to our output error 94 // if we have one, otherwise we just output it. 95 if err != nil { 96 if n.Error != nil { 97 *n.Error = multierror.Append(*n.Error, err) 98 } else { 99 return nil, err 100 } 101 } 102 103 return nil, nil 104 } 105 106 // EvalApplyPost is an EvalNode implementation that does the post-Apply work 107 type EvalApplyPost struct { 108 Info *InstanceInfo 109 State **InstanceState 110 Error *error 111 } 112 113 // TODO: test 114 func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) { 115 state := *n.State 116 117 { 118 // Call post-apply hook 119 err := ctx.Hook(func(h Hook) (HookAction, error) { 120 return h.PostApply(n.Info, state, *n.Error) 121 }) 122 if err != nil { 123 return nil, err 124 } 125 } 126 127 return nil, *n.Error 128 } 129 130 // EvalApplyProvisioners is an EvalNode implementation that executes 131 // the provisioners for a resource. 132 // 133 // TODO(mitchellh): This should probably be split up into a more fine-grained 134 // ApplyProvisioner (single) that is looped over. 135 type EvalApplyProvisioners struct { 136 Info *InstanceInfo 137 State **InstanceState 138 Resource *config.Resource 139 InterpResource *Resource 140 CreateNew *bool 141 Tainted *bool 142 Error *error 143 } 144 145 // TODO: test 146 func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) { 147 state := *n.State 148 149 if !*n.CreateNew { 150 // If we're not creating a new resource, then don't run provisioners 151 return nil, nil 152 } 153 154 if len(n.Resource.Provisioners) == 0 { 155 // We have no provisioners, so don't do anything 156 return nil, nil 157 } 158 159 if n.Error != nil && *n.Error != nil { 160 // We're already errored creating, so mark as tainted and continue 161 if n.Tainted != nil { 162 *n.Tainted = true 163 } 164 165 // We're already tainted, so just return out 166 return nil, nil 167 } 168 169 { 170 // Call pre hook 171 err := ctx.Hook(func(h Hook) (HookAction, error) { 172 return h.PreProvisionResource(n.Info, state) 173 }) 174 if err != nil { 175 return nil, err 176 } 177 } 178 179 // If there are no errors, then we append it to our output error 180 // if we have one, otherwise we just output it. 181 err := n.apply(ctx) 182 if n.Tainted != nil { 183 *n.Tainted = err != nil 184 } 185 if err != nil { 186 if n.Error != nil { 187 *n.Error = multierror.Append(*n.Error, err) 188 } else { 189 return nil, err 190 } 191 } 192 193 { 194 // Call post hook 195 err := ctx.Hook(func(h Hook) (HookAction, error) { 196 return h.PostProvisionResource(n.Info, state) 197 }) 198 if err != nil { 199 return nil, err 200 } 201 } 202 203 return nil, nil 204 } 205 206 func (n *EvalApplyProvisioners) apply(ctx EvalContext) error { 207 state := *n.State 208 209 // Store the original connection info, restore later 210 origConnInfo := state.Ephemeral.ConnInfo 211 defer func() { 212 state.Ephemeral.ConnInfo = origConnInfo 213 }() 214 215 for _, prov := range n.Resource.Provisioners { 216 // Get the provisioner 217 provisioner := ctx.Provisioner(prov.Type) 218 219 // Interpolate the provisioner config 220 provConfig, err := ctx.Interpolate(prov.RawConfig, n.InterpResource) 221 if err != nil { 222 return err 223 } 224 225 // Interpolate the conn info, since it may contain variables 226 connInfo, err := ctx.Interpolate(prov.ConnInfo, n.InterpResource) 227 if err != nil { 228 return err 229 } 230 231 // Merge the connection information 232 overlay := make(map[string]string) 233 if origConnInfo != nil { 234 for k, v := range origConnInfo { 235 overlay[k] = v 236 } 237 } 238 for k, v := range connInfo.Config { 239 switch vt := v.(type) { 240 case string: 241 overlay[k] = vt 242 case int64: 243 overlay[k] = strconv.FormatInt(vt, 10) 244 case int32: 245 overlay[k] = strconv.FormatInt(int64(vt), 10) 246 case int: 247 overlay[k] = strconv.FormatInt(int64(vt), 10) 248 case float32: 249 overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32) 250 case float64: 251 overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64) 252 case bool: 253 overlay[k] = strconv.FormatBool(vt) 254 default: 255 overlay[k] = fmt.Sprintf("%v", vt) 256 } 257 } 258 state.Ephemeral.ConnInfo = overlay 259 260 { 261 // Call pre hook 262 err := ctx.Hook(func(h Hook) (HookAction, error) { 263 return h.PreProvision(n.Info, prov.Type) 264 }) 265 if err != nil { 266 return err 267 } 268 } 269 270 // The output function 271 outputFn := func(msg string) { 272 ctx.Hook(func(h Hook) (HookAction, error) { 273 h.ProvisionOutput(n.Info, prov.Type, msg) 274 return HookActionContinue, nil 275 }) 276 } 277 278 // Invoke the Provisioner 279 output := CallbackUIOutput{OutputFn: outputFn} 280 if err := provisioner.Apply(&output, state, provConfig); err != nil { 281 return err 282 } 283 284 { 285 // Call post hook 286 err := ctx.Hook(func(h Hook) (HookAction, error) { 287 return h.PostProvision(n.Info, prov.Type) 288 }) 289 if err != nil { 290 return err 291 } 292 } 293 } 294 295 return nil 296 297 }