func (m schemaMap) diff( k string, schema *Schema, diff *terraform.InstanceDiff, d *ResourceData, all bool) error { unsupressedDiff := new(terraform.InstanceDiff) unsupressedDiff.Attributes = make(map[string]*terraform.ResourceAttrDiff) var err error switch schema.Type { case TypeBool, TypeInt, TypeFloat, TypeString: err = m.diffString(k, schema, unsupressedDiff, d, all) case TypeList: err = m.diffList(k, schema, unsupressedDiff, d, all) case TypeMap: err = m.diffMap(k, schema, unsupressedDiff, d, all) case TypeSet: err = m.diffSet(k, schema, unsupressedDiff, d, all) default: err = fmt.Errorf("%s: unknown type %#v", k, schema.Type) } for attrK, attrV := range unsupressedDiff.Attributes { if schema.DiffSuppressFunc != nil && schema.DiffSuppressFunc(attrK, attrV.Old, attrV.New, d) { continue } diff.Attributes[attrK] = attrV } return err }
func (m schemaMap) diffSet( k string, schema *Schema, diff *terraform.InstanceDiff, d *ResourceData, all bool) error { if !all { // This is a bit strange, but we expect the entire set to be in the diff, // so we first diff the set normally but with a new diff. Then, if // there IS any change, we just set the change to the entire list. tempD := new(terraform.InstanceDiff) tempD.Attributes = make(map[string]*terraform.ResourceAttrDiff) if err := m.diffList(k, schema, tempD, d, false); err != nil { return err } // If we had no changes, then we're done if tempD.Empty() { return nil } } // We have changes, so re-run the diff, but set a flag to force // getting all diffs, even if there is no change. return m.diffList(k, schema, diff, d, true) }
func testResourceDiffStr(rd *terraform.InstanceDiff) string { var buf bytes.Buffer crud := "UPDATE" if rd.RequiresNew() { crud = "CREATE" } buf.WriteString(fmt.Sprintf( "%s\n", crud)) keyLen := 0 keys := make([]string, 0, len(rd.Attributes)) for key, _ := range rd.Attributes { keys = append(keys, key) if len(key) > keyLen { keyLen = len(key) } } sort.Strings(keys) for _, attrK := range keys { attrDiff := rd.Attributes[attrK] v := attrDiff.New if attrDiff.NewComputed { v = "<computed>" } if attrDiff.NewRemoved { v = "<removed>" } newResource := "" if attrDiff.RequiresNew { newResource = " (forces new resource)" } inOut := "IN " if attrDiff.Type == terraform.DiffAttrOutput { inOut = "OUT" } buf.WriteString(fmt.Sprintf( " %s %s:%s %#v => %#v%s\n", inOut, attrK, strings.Repeat(" ", keyLen-len(attrK)), attrDiff.Old, v, newResource)) } return buf.String() }
// Apply creates, updates, and/or deletes a resource. func (r *Resource) Apply( s *terraform.InstanceState, d *terraform.InstanceDiff, meta interface{}) (*terraform.InstanceState, error) { data, err := schemaMap(r.Schema).Data(s, d) if err != nil { return s, err } if s == nil { // The Terraform API dictates that this should never happen, but // it doesn't hurt to be safe in this case. s = new(terraform.InstanceState) } if d.Destroy || d.RequiresNew() { if s.ID != "" { // Destroy the resource since it is created if err := r.Delete(data, meta); err != nil { return r.recordCurrentSchemaVersion(data.State()), err } // Make sure the ID is gone. data.SetId("") } // If we're only destroying, and not creating, then return // now since we're done! if !d.RequiresNew() { return nil, nil } // Reset the data to be stateless since we just destroyed data, err = schemaMap(r.Schema).Data(nil, d) if err != nil { return nil, err } } err = nil if data.Id() == "" { // We're creating, it is a new resource. data.MarkNewResource() err = r.Create(data, meta) } else { if r.Update == nil { return s, fmt.Errorf("doesn't support update") } err = r.Update(data, meta) } return r.recordCurrentSchemaVersion(data.State()), err }
// Apply performs a create or update depending on the diff, and calls // the proper function on the matching Resource. func (m *Map) Apply( info *terraform.InstanceInfo, s *terraform.InstanceState, d *terraform.InstanceDiff, meta interface{}) (*terraform.InstanceState, error) { r, ok := m.Mapping[info.Type] if !ok { return nil, fmt.Errorf("Unknown resource type: %s", info.Type) } if d.Destroy || d.RequiresNew() { if s.ID != "" { // Destroy the resource if it is created err := r.Destroy(s, meta) if err != nil { return s, err } s.ID = "" } // If we're only destroying, and not creating, then return now. // Otherwise, we continue so that we can create a new resource. if !d.RequiresNew() { return nil, nil } } var result *terraform.InstanceState var err error if s.ID == "" { result, err = r.Create(s, d, meta) } else { if r.Update == nil { return s, fmt.Errorf( "Resource type '%s' doesn't support update", info.Type) } result, err = r.Update(s, d, meta) } if result != nil { if result.Attributes == nil { result.Attributes = make(map[string]string) } result.Attributes["id"] = result.ID } return result, err }
func (h *CountHook) PostDiff( n *terraform.InstanceInfo, d *terraform.InstanceDiff) ( terraform.HookAction, error) { h.Lock() defer h.Unlock() switch d.ChangeType() { case terraform.DiffDestroyCreate: h.ToRemoveAndAdd += 1 case terraform.DiffCreate: h.ToAdd += 1 case terraform.DiffDestroy: h.ToRemove += 1 case terraform.DiffUpdate: h.ToChange += 1 } return terraform.HookActionContinue, nil }
func (h *CountHook) PreApply( n *terraform.InstanceInfo, s *terraform.InstanceState, d *terraform.InstanceDiff) (terraform.HookAction, error) { h.Lock() defer h.Unlock() if h.pending == nil { h.pending = make(map[string]countHookAction) } action := countHookActionChange if d.GetDestroy() { action = countHookActionRemove } else if s.ID == "" { action = countHookActionAdd } h.pending[n.HumanId()] = action return terraform.HookActionContinue, nil }
func (h *CountHook) PostDiff( n *terraform.InstanceInfo, d *terraform.InstanceDiff) ( terraform.HookAction, error) { h.Lock() defer h.Unlock() // We don't count anything for data sources if strings.HasPrefix(n.Id, "data.") { return terraform.HookActionContinue, nil } switch d.ChangeType() { case terraform.DiffDestroyCreate: h.ToRemoveAndAdd += 1 case terraform.DiffCreate: h.ToAdd += 1 case terraform.DiffDestroy: h.ToRemove += 1 case terraform.DiffUpdate: h.ToChange += 1 } return terraform.HookActionContinue, nil }
// Diff returns the diff for a resource given the schema map, // state, and configuration. func (m schemaMap) Diff( s *terraform.InstanceState, c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { result := new(terraform.InstanceDiff) result.Attributes = make(map[string]*terraform.ResourceAttrDiff) d := &ResourceData{ schema: m, state: s, config: c, } for k, schema := range m { err := m.diff(k, schema, result, d, false) if err != nil { return nil, err } } // If the diff requires a new resource, then we recompute the diff // so we have the complete new resource diff, and preserve the // RequiresNew fields where necessary so the user knows exactly what // caused that. if result.RequiresNew() { // Create the new diff result2 := new(terraform.InstanceDiff) result2.Attributes = make(map[string]*terraform.ResourceAttrDiff) // Reset the data to not contain state. We have to call init() // again in order to reset the FieldReaders. d.state = nil d.init() // Perform the diff again for k, schema := range m { err := m.diff(k, schema, result2, d, false) if err != nil { return nil, err } } // Force all the fields to not force a new since we know what we // want to force new. for k, attr := range result2.Attributes { if attr == nil { continue } if attr.RequiresNew { attr.RequiresNew = false } if s != nil { attr.Old = s.Attributes[k] } } // Now copy in all the requires new diffs... for k, attr := range result.Attributes { if attr == nil { continue } newAttr, ok := result2.Attributes[k] if !ok { newAttr = attr } if attr.RequiresNew { newAttr.RequiresNew = true } result2.Attributes[k] = newAttr } // And set the diff! result = result2 } // Remove any nil diffs just to keep things clean for k, v := range result.Attributes { if v == nil { delete(result.Attributes, k) } } // Go through and detect all of the ComputedWhens now that we've // finished the diff. // TODO if result.Empty() { // If we don't have any diff elements, just return nil return nil, nil } return result, nil }
func (h *UiHook) PreApply( n *terraform.InstanceInfo, s *terraform.InstanceState, d *terraform.InstanceDiff) (terraform.HookAction, error) { h.once.Do(h.init) id := n.HumanId() op := uiResourceModify if d.Destroy { op = uiResourceDestroy } else if s.ID == "" { op = uiResourceCreate } h.l.Lock() h.resources[id] = uiResourceState{ Op: op, Start: time.Now().Round(time.Second), } h.l.Unlock() var operation string switch op { case uiResourceModify: operation = "Modifying..." case uiResourceDestroy: operation = "Destroying..." case uiResourceCreate: operation = "Creating..." case uiResourceUnknown: return terraform.HookActionContinue, nil } attrBuf := new(bytes.Buffer) // Get all the attributes that are changing, and sort them. Also // determine the longest key so that we can align them all. keyLen := 0 dAttrs := d.CopyAttributes() keys := make([]string, 0, len(dAttrs)) for key, _ := range dAttrs { // Skip the ID since we do that specially if key == "id" { continue } keys = append(keys, key) if len(key) > keyLen { keyLen = len(key) } } sort.Strings(keys) // Go through and output each attribute for _, attrK := range keys { attrDiff, _ := d.GetAttribute(attrK) v := attrDiff.New u := attrDiff.Old if attrDiff.NewComputed { v = "<computed>" } if attrDiff.Sensitive { u = "<sensitive>" v = "<sensitive>" } attrBuf.WriteString(fmt.Sprintf( " %s:%s %#v => %#v\n", attrK, strings.Repeat(" ", keyLen-len(attrK)), u, v)) } attrString := strings.TrimSpace(attrBuf.String()) if attrString != "" { attrString = "\n " + attrString } h.ui.Output(h.Colorize.Color(fmt.Sprintf( "[reset][bold]%s: %s[reset_bold]%s", id, operation, attrString))) // Set a timer to show an operation is still happening time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id) }) return terraform.HookActionContinue, nil }