github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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 helpfulErr := fmt.Errorf("%s: %s", n.Info.Id, err.Error()) 98 *n.Error = multierror.Append(*n.Error, helpfulErr) 99 } else { 100 return nil, err 101 } 102 } 103 104 return nil, nil 105 } 106 107 // EvalApplyPost is an EvalNode implementation that does the post-Apply work 108 type EvalApplyPost struct { 109 Info *InstanceInfo 110 State **InstanceState 111 Error *error 112 } 113 114 // TODO: test 115 func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) { 116 state := *n.State 117 118 { 119 // Call post-apply hook 120 err := ctx.Hook(func(h Hook) (HookAction, error) { 121 return h.PostApply(n.Info, state, *n.Error) 122 }) 123 if err != nil { 124 return nil, err 125 } 126 } 127 128 return nil, *n.Error 129 } 130 131 // EvalApplyProvisioners is an EvalNode implementation that executes 132 // the provisioners for a resource. 133 // 134 // TODO(mitchellh): This should probably be split up into a more fine-grained 135 // ApplyProvisioner (single) that is looped over. 136 type EvalApplyProvisioners struct { 137 Info *InstanceInfo 138 State **InstanceState 139 Resource *config.Resource 140 InterpResource *Resource 141 CreateNew *bool 142 Tainted *bool 143 Error *error 144 } 145 146 // TODO: test 147 func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) { 148 state := *n.State 149 150 if !*n.CreateNew { 151 // If we're not creating a new resource, then don't run provisioners 152 return nil, nil 153 } 154 155 if len(n.Resource.Provisioners) == 0 { 156 // We have no provisioners, so don't do anything 157 return nil, nil 158 } 159 160 if n.Error != nil && *n.Error != nil { 161 // We're already errored creating, so mark as tainted and continue 162 if n.Tainted != nil { 163 *n.Tainted = true 164 } 165 166 // We're already tainted, so just return out 167 return nil, nil 168 } 169 170 { 171 // Call pre hook 172 err := ctx.Hook(func(h Hook) (HookAction, error) { 173 return h.PreProvisionResource(n.Info, state) 174 }) 175 if err != nil { 176 return nil, err 177 } 178 } 179 180 // If there are no errors, then we append it to our output error 181 // if we have one, otherwise we just output it. 182 err := n.apply(ctx) 183 if n.Tainted != nil { 184 *n.Tainted = err != nil 185 } 186 if err != nil { 187 if n.Error != nil { 188 *n.Error = multierror.Append(*n.Error, err) 189 } else { 190 return nil, err 191 } 192 } 193 194 { 195 // Call post hook 196 err := ctx.Hook(func(h Hook) (HookAction, error) { 197 return h.PostProvisionResource(n.Info, state) 198 }) 199 if err != nil { 200 return nil, err 201 } 202 } 203 204 return nil, nil 205 } 206 207 func (n *EvalApplyProvisioners) apply(ctx EvalContext) error { 208 state := *n.State 209 210 // Store the original connection info, restore later 211 origConnInfo := state.Ephemeral.ConnInfo 212 defer func() { 213 state.Ephemeral.ConnInfo = origConnInfo 214 }() 215 216 for _, prov := range n.Resource.Provisioners { 217 // Get the provisioner 218 provisioner := ctx.Provisioner(prov.Type) 219 220 // Interpolate the provisioner config 221 provConfig, err := ctx.Interpolate(prov.RawConfig, n.InterpResource) 222 if err != nil { 223 return err 224 } 225 226 // Interpolate the conn info, since it may contain variables 227 connInfo, err := ctx.Interpolate(prov.ConnInfo, n.InterpResource) 228 if err != nil { 229 return err 230 } 231 232 // Merge the connection information 233 overlay := make(map[string]string) 234 if origConnInfo != nil { 235 for k, v := range origConnInfo { 236 overlay[k] = v 237 } 238 } 239 for k, v := range connInfo.Config { 240 switch vt := v.(type) { 241 case string: 242 overlay[k] = vt 243 case int64: 244 overlay[k] = strconv.FormatInt(vt, 10) 245 case int32: 246 overlay[k] = strconv.FormatInt(int64(vt), 10) 247 case int: 248 overlay[k] = strconv.FormatInt(int64(vt), 10) 249 case float32: 250 overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32) 251 case float64: 252 overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64) 253 case bool: 254 overlay[k] = strconv.FormatBool(vt) 255 default: 256 overlay[k] = fmt.Sprintf("%v", vt) 257 } 258 } 259 state.Ephemeral.ConnInfo = overlay 260 261 { 262 // Call pre hook 263 err := ctx.Hook(func(h Hook) (HookAction, error) { 264 return h.PreProvision(n.Info, prov.Type) 265 }) 266 if err != nil { 267 return err 268 } 269 } 270 271 // The output function 272 outputFn := func(msg string) { 273 ctx.Hook(func(h Hook) (HookAction, error) { 274 h.ProvisionOutput(n.Info, prov.Type, msg) 275 return HookActionContinue, nil 276 }) 277 } 278 279 // Invoke the Provisioner 280 output := CallbackUIOutput{OutputFn: outputFn} 281 if err := provisioner.Apply(&output, state, provConfig); err != nil { 282 return err 283 } 284 285 { 286 // Call post hook 287 err := ctx.Hook(func(h Hook) (HookAction, error) { 288 return h.PostProvision(n.Info, prov.Type) 289 }) 290 if err != nil { 291 return err 292 } 293 } 294 } 295 296 return nil 297 298 }