1package structs
2
3import (
4	"fmt"
5	"reflect"
6	"sort"
7	"strings"
8
9	"github.com/hashicorp/nomad/helper/flatmap"
10	"github.com/mitchellh/hashstructure"
11)
12
13// DiffType denotes the type of a diff object.
14type DiffType string
15
16var (
17	DiffTypeNone    DiffType = "None"
18	DiffTypeAdded   DiffType = "Added"
19	DiffTypeDeleted DiffType = "Deleted"
20	DiffTypeEdited  DiffType = "Edited"
21)
22
23func (d DiffType) Less(other DiffType) bool {
24	// Edited > Added > Deleted > None
25	// But we do a reverse sort
26	if d == other {
27		return false
28	}
29
30	if d == DiffTypeEdited {
31		return true
32	} else if other == DiffTypeEdited {
33		return false
34	} else if d == DiffTypeAdded {
35		return true
36	} else if other == DiffTypeAdded {
37		return false
38	} else if d == DiffTypeDeleted {
39		return true
40	} else if other == DiffTypeDeleted {
41		return false
42	}
43
44	return true
45}
46
47// JobDiff contains the diff of two jobs.
48type JobDiff struct {
49	Type       DiffType
50	ID         string
51	Fields     []*FieldDiff
52	Objects    []*ObjectDiff
53	TaskGroups []*TaskGroupDiff
54}
55
56// Diff returns a diff of two jobs and a potential error if the Jobs are not
57// diffable. If contextual diff is enabled, objects within the job will contain
58// field information even if unchanged.
59func (j *Job) Diff(other *Job, contextual bool) (*JobDiff, error) {
60	// See agent.ApiJobToStructJob Update is a default for TaskGroups
61	diff := &JobDiff{Type: DiffTypeNone}
62	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
63	filter := []string{"ID", "Status", "StatusDescription", "Version", "Stable", "CreateIndex",
64		"ModifyIndex", "JobModifyIndex", "Update", "SubmitTime"}
65
66	if j == nil && other == nil {
67		return diff, nil
68	} else if j == nil {
69		j = &Job{}
70		diff.Type = DiffTypeAdded
71		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
72		diff.ID = other.ID
73	} else if other == nil {
74		other = &Job{}
75		diff.Type = DiffTypeDeleted
76		oldPrimitiveFlat = flatmap.Flatten(j, filter, true)
77		diff.ID = j.ID
78	} else {
79		if j.ID != other.ID {
80			return nil, fmt.Errorf("can not diff jobs with different IDs: %q and %q", j.ID, other.ID)
81		}
82
83		oldPrimitiveFlat = flatmap.Flatten(j, filter, true)
84		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
85		diff.ID = other.ID
86	}
87
88	// Diff the primitive fields.
89	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
90
91	// Datacenters diff
92	if setDiff := stringSetDiff(j.Datacenters, other.Datacenters, "Datacenters", contextual); setDiff != nil && setDiff.Type != DiffTypeNone {
93		diff.Objects = append(diff.Objects, setDiff)
94	}
95
96	// Constraints diff
97	conDiff := primitiveObjectSetDiff(
98		interfaceSlice(j.Constraints),
99		interfaceSlice(other.Constraints),
100		[]string{"str"},
101		"Constraint",
102		contextual)
103	if conDiff != nil {
104		diff.Objects = append(diff.Objects, conDiff...)
105	}
106
107	// Affinities diff
108	affinitiesDiff := primitiveObjectSetDiff(
109		interfaceSlice(j.Affinities),
110		interfaceSlice(other.Affinities),
111		[]string{"str"},
112		"Affinity",
113		contextual)
114	if affinitiesDiff != nil {
115		diff.Objects = append(diff.Objects, affinitiesDiff...)
116	}
117
118	// Task groups diff
119	tgs, err := taskGroupDiffs(j.TaskGroups, other.TaskGroups, contextual)
120	if err != nil {
121		return nil, err
122	}
123	diff.TaskGroups = tgs
124
125	// Periodic diff
126	if pDiff := primitiveObjectDiff(j.Periodic, other.Periodic, nil, "Periodic", contextual); pDiff != nil {
127		diff.Objects = append(diff.Objects, pDiff)
128	}
129
130	// ParameterizedJob diff
131	if cDiff := parameterizedJobDiff(j.ParameterizedJob, other.ParameterizedJob, contextual); cDiff != nil {
132		diff.Objects = append(diff.Objects, cDiff)
133	}
134
135	// Check to see if there is a diff. We don't use reflect because we are
136	// filtering quite a few fields that will change on each diff.
137	if diff.Type == DiffTypeNone {
138		for _, fd := range diff.Fields {
139			if fd.Type != DiffTypeNone {
140				diff.Type = DiffTypeEdited
141				break
142			}
143		}
144	}
145
146	if diff.Type == DiffTypeNone {
147		for _, od := range diff.Objects {
148			if od.Type != DiffTypeNone {
149				diff.Type = DiffTypeEdited
150				break
151			}
152		}
153	}
154
155	if diff.Type == DiffTypeNone {
156		for _, tg := range diff.TaskGroups {
157			if tg.Type != DiffTypeNone {
158				diff.Type = DiffTypeEdited
159				break
160			}
161		}
162	}
163
164	return diff, nil
165}
166
167func (j *JobDiff) GoString() string {
168	out := fmt.Sprintf("Job %q (%s):\n", j.ID, j.Type)
169
170	for _, f := range j.Fields {
171		out += fmt.Sprintf("%#v\n", f)
172	}
173
174	for _, o := range j.Objects {
175		out += fmt.Sprintf("%#v\n", o)
176	}
177
178	for _, tg := range j.TaskGroups {
179		out += fmt.Sprintf("%#v\n", tg)
180	}
181
182	return out
183}
184
185// TaskGroupDiff contains the diff of two task groups.
186type TaskGroupDiff struct {
187	Type    DiffType
188	Name    string
189	Fields  []*FieldDiff
190	Objects []*ObjectDiff
191	Tasks   []*TaskDiff
192	Updates map[string]uint64
193}
194
195// Diff returns a diff of two task groups. If contextual diff is enabled,
196// objects' fields will be stored even if no diff occurred as long as one field
197// changed.
198func (tg *TaskGroup) Diff(other *TaskGroup, contextual bool) (*TaskGroupDiff, error) {
199	diff := &TaskGroupDiff{Type: DiffTypeNone}
200	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
201	filter := []string{"Name"}
202
203	if tg == nil && other == nil {
204		return diff, nil
205	} else if tg == nil {
206		tg = &TaskGroup{}
207		diff.Type = DiffTypeAdded
208		diff.Name = other.Name
209		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
210	} else if other == nil {
211		other = &TaskGroup{}
212		diff.Type = DiffTypeDeleted
213		diff.Name = tg.Name
214		oldPrimitiveFlat = flatmap.Flatten(tg, filter, true)
215	} else {
216		if !reflect.DeepEqual(tg, other) {
217			diff.Type = DiffTypeEdited
218		}
219		if tg.Name != other.Name {
220			return nil, fmt.Errorf("can not diff task groups with different names: %q and %q", tg.Name, other.Name)
221		}
222		diff.Name = other.Name
223		oldPrimitiveFlat = flatmap.Flatten(tg, filter, true)
224		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
225	}
226
227	// Diff the primitive fields.
228	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
229
230	// Constraints diff
231	conDiff := primitiveObjectSetDiff(
232		interfaceSlice(tg.Constraints),
233		interfaceSlice(other.Constraints),
234		[]string{"str"},
235		"Constraint",
236		contextual)
237	if conDiff != nil {
238		diff.Objects = append(diff.Objects, conDiff...)
239	}
240
241	// Affinities diff
242	affinitiesDiff := primitiveObjectSetDiff(
243		interfaceSlice(tg.Affinities),
244		interfaceSlice(other.Affinities),
245		[]string{"str"},
246		"Affinity",
247		contextual)
248	if affinitiesDiff != nil {
249		diff.Objects = append(diff.Objects, affinitiesDiff...)
250	}
251
252	// Restart policy diff
253	rDiff := primitiveObjectDiff(tg.RestartPolicy, other.RestartPolicy, nil, "RestartPolicy", contextual)
254	if rDiff != nil {
255		diff.Objects = append(diff.Objects, rDiff)
256	}
257
258	// Reschedule policy diff
259	reschedDiff := primitiveObjectDiff(tg.ReschedulePolicy, other.ReschedulePolicy, nil, "ReschedulePolicy", contextual)
260	if reschedDiff != nil {
261		diff.Objects = append(diff.Objects, reschedDiff)
262	}
263
264	// EphemeralDisk diff
265	diskDiff := primitiveObjectDiff(tg.EphemeralDisk, other.EphemeralDisk, nil, "EphemeralDisk", contextual)
266	if diskDiff != nil {
267		diff.Objects = append(diff.Objects, diskDiff)
268	}
269
270	// Update diff
271	// COMPAT: Remove "Stagger" in 0.7.0.
272	if uDiff := primitiveObjectDiff(tg.Update, other.Update, []string{"Stagger"}, "Update", contextual); uDiff != nil {
273		diff.Objects = append(diff.Objects, uDiff)
274	}
275
276	// Network Resources diff
277	if nDiffs := networkResourceDiffs(tg.Networks, other.Networks, contextual); nDiffs != nil {
278		diff.Objects = append(diff.Objects, nDiffs...)
279	}
280
281	// Services diff
282	if sDiffs := serviceDiffs(tg.Services, other.Services, contextual); sDiffs != nil {
283		diff.Objects = append(diff.Objects, sDiffs...)
284	}
285
286	// Tasks diff
287	tasks, err := taskDiffs(tg.Tasks, other.Tasks, contextual)
288	if err != nil {
289		return nil, err
290	}
291	diff.Tasks = tasks
292
293	return diff, nil
294}
295
296func (tg *TaskGroupDiff) GoString() string {
297	out := fmt.Sprintf("Group %q (%s):\n", tg.Name, tg.Type)
298
299	if len(tg.Updates) != 0 {
300		out += "Updates {\n"
301		for update, count := range tg.Updates {
302			out += fmt.Sprintf("%d %s\n", count, update)
303		}
304		out += "}\n"
305	}
306
307	for _, f := range tg.Fields {
308		out += fmt.Sprintf("%#v\n", f)
309	}
310
311	for _, o := range tg.Objects {
312		out += fmt.Sprintf("%#v\n", o)
313	}
314
315	for _, t := range tg.Tasks {
316		out += fmt.Sprintf("%#v\n", t)
317	}
318
319	return out
320}
321
322// TaskGroupDiffs diffs two sets of task groups. If contextual diff is enabled,
323// objects' fields will be stored even if no diff occurred as long as one field
324// changed.
325func taskGroupDiffs(old, new []*TaskGroup, contextual bool) ([]*TaskGroupDiff, error) {
326	oldMap := make(map[string]*TaskGroup, len(old))
327	newMap := make(map[string]*TaskGroup, len(new))
328	for _, o := range old {
329		oldMap[o.Name] = o
330	}
331	for _, n := range new {
332		newMap[n.Name] = n
333	}
334
335	var diffs []*TaskGroupDiff
336	for name, oldGroup := range oldMap {
337		// Diff the same, deleted and edited
338		diff, err := oldGroup.Diff(newMap[name], contextual)
339		if err != nil {
340			return nil, err
341		}
342		diffs = append(diffs, diff)
343	}
344
345	for name, newGroup := range newMap {
346		// Diff the added
347		if old, ok := oldMap[name]; !ok {
348			diff, err := old.Diff(newGroup, contextual)
349			if err != nil {
350				return nil, err
351			}
352			diffs = append(diffs, diff)
353		}
354	}
355
356	sort.Sort(TaskGroupDiffs(diffs))
357	return diffs, nil
358}
359
360// For sorting TaskGroupDiffs
361type TaskGroupDiffs []*TaskGroupDiff
362
363func (tg TaskGroupDiffs) Len() int           { return len(tg) }
364func (tg TaskGroupDiffs) Swap(i, j int)      { tg[i], tg[j] = tg[j], tg[i] }
365func (tg TaskGroupDiffs) Less(i, j int) bool { return tg[i].Name < tg[j].Name }
366
367// TaskDiff contains the diff of two Tasks
368type TaskDiff struct {
369	Type        DiffType
370	Name        string
371	Fields      []*FieldDiff
372	Objects     []*ObjectDiff
373	Annotations []string
374}
375
376// Diff returns a diff of two tasks. If contextual diff is enabled, objects
377// within the task will contain field information even if unchanged.
378func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) {
379	diff := &TaskDiff{Type: DiffTypeNone}
380	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
381	filter := []string{"Name", "Config"}
382
383	if t == nil && other == nil {
384		return diff, nil
385	} else if t == nil {
386		t = &Task{}
387		diff.Type = DiffTypeAdded
388		diff.Name = other.Name
389		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
390	} else if other == nil {
391		other = &Task{}
392		diff.Type = DiffTypeDeleted
393		diff.Name = t.Name
394		oldPrimitiveFlat = flatmap.Flatten(t, filter, true)
395	} else {
396		if !reflect.DeepEqual(t, other) {
397			diff.Type = DiffTypeEdited
398		}
399		if t.Name != other.Name {
400			return nil, fmt.Errorf("can not diff tasks with different names: %q and %q", t.Name, other.Name)
401		}
402		diff.Name = other.Name
403		oldPrimitiveFlat = flatmap.Flatten(t, filter, true)
404		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
405	}
406
407	// Diff the primitive fields.
408	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
409
410	// Constraints diff
411	conDiff := primitiveObjectSetDiff(
412		interfaceSlice(t.Constraints),
413		interfaceSlice(other.Constraints),
414		[]string{"str"},
415		"Constraint",
416		contextual)
417	if conDiff != nil {
418		diff.Objects = append(diff.Objects, conDiff...)
419	}
420
421	// Affinities diff
422	affinitiesDiff := primitiveObjectSetDiff(
423		interfaceSlice(t.Affinities),
424		interfaceSlice(other.Affinities),
425		[]string{"str"},
426		"Affinity",
427		contextual)
428	if affinitiesDiff != nil {
429		diff.Objects = append(diff.Objects, affinitiesDiff...)
430	}
431
432	// Config diff
433	if cDiff := configDiff(t.Config, other.Config, contextual); cDiff != nil {
434		diff.Objects = append(diff.Objects, cDiff)
435	}
436
437	// Resources diff
438	if rDiff := t.Resources.Diff(other.Resources, contextual); rDiff != nil {
439		diff.Objects = append(diff.Objects, rDiff)
440	}
441
442	// LogConfig diff
443	lDiff := primitiveObjectDiff(t.LogConfig, other.LogConfig, nil, "LogConfig", contextual)
444	if lDiff != nil {
445		diff.Objects = append(diff.Objects, lDiff)
446	}
447
448	// Dispatch payload diff
449	dDiff := primitiveObjectDiff(t.DispatchPayload, other.DispatchPayload, nil, "DispatchPayload", contextual)
450	if dDiff != nil {
451		diff.Objects = append(diff.Objects, dDiff)
452	}
453
454	// Artifacts diff
455	diffs := primitiveObjectSetDiff(
456		interfaceSlice(t.Artifacts),
457		interfaceSlice(other.Artifacts),
458		nil,
459		"Artifact",
460		contextual)
461	if diffs != nil {
462		diff.Objects = append(diff.Objects, diffs...)
463	}
464
465	// Services diff
466	if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil {
467		diff.Objects = append(diff.Objects, sDiffs...)
468	}
469
470	// Vault diff
471	vDiff := vaultDiff(t.Vault, other.Vault, contextual)
472	if vDiff != nil {
473		diff.Objects = append(diff.Objects, vDiff)
474	}
475
476	// Template diff
477	tmplDiffs := primitiveObjectSetDiff(
478		interfaceSlice(t.Templates),
479		interfaceSlice(other.Templates),
480		nil,
481		"Template",
482		contextual)
483	if tmplDiffs != nil {
484		diff.Objects = append(diff.Objects, tmplDiffs...)
485	}
486
487	return diff, nil
488}
489
490func (t *TaskDiff) GoString() string {
491	var out string
492	if len(t.Annotations) == 0 {
493		out = fmt.Sprintf("Task %q (%s):\n", t.Name, t.Type)
494	} else {
495		out = fmt.Sprintf("Task %q (%s) (%s):\n", t.Name, t.Type, strings.Join(t.Annotations, ","))
496	}
497
498	for _, f := range t.Fields {
499		out += fmt.Sprintf("%#v\n", f)
500	}
501
502	for _, o := range t.Objects {
503		out += fmt.Sprintf("%#v\n", o)
504	}
505
506	return out
507}
508
509// taskDiffs diffs a set of tasks. If contextual diff is enabled, unchanged
510// fields within objects nested in the tasks will be returned.
511func taskDiffs(old, new []*Task, contextual bool) ([]*TaskDiff, error) {
512	oldMap := make(map[string]*Task, len(old))
513	newMap := make(map[string]*Task, len(new))
514	for _, o := range old {
515		oldMap[o.Name] = o
516	}
517	for _, n := range new {
518		newMap[n.Name] = n
519	}
520
521	var diffs []*TaskDiff
522	for name, oldGroup := range oldMap {
523		// Diff the same, deleted and edited
524		diff, err := oldGroup.Diff(newMap[name], contextual)
525		if err != nil {
526			return nil, err
527		}
528		diffs = append(diffs, diff)
529	}
530
531	for name, newGroup := range newMap {
532		// Diff the added
533		if old, ok := oldMap[name]; !ok {
534			diff, err := old.Diff(newGroup, contextual)
535			if err != nil {
536				return nil, err
537			}
538			diffs = append(diffs, diff)
539		}
540	}
541
542	sort.Sort(TaskDiffs(diffs))
543	return diffs, nil
544}
545
546// For sorting TaskDiffs
547type TaskDiffs []*TaskDiff
548
549func (t TaskDiffs) Len() int           { return len(t) }
550func (t TaskDiffs) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
551func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name }
552
553// serviceDiff returns the diff of two service objects. If contextual diff is
554// enabled, all fields will be returned, even if no diff occurred.
555func serviceDiff(old, new *Service, contextual bool) *ObjectDiff {
556	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"}
557	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
558
559	if reflect.DeepEqual(old, new) {
560		return nil
561	} else if old == nil {
562		old = &Service{}
563		diff.Type = DiffTypeAdded
564		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
565	} else if new == nil {
566		new = &Service{}
567		diff.Type = DiffTypeDeleted
568		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
569	} else {
570		diff.Type = DiffTypeEdited
571		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
572		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
573	}
574
575	// Diff the primitive fields.
576	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
577
578	if setDiff := stringSetDiff(old.CanaryTags, new.CanaryTags, "CanaryTags", contextual); setDiff != nil {
579		diff.Objects = append(diff.Objects, setDiff)
580	}
581
582	// Tag diffs
583	if setDiff := stringSetDiff(old.Tags, new.Tags, "Tags", contextual); setDiff != nil {
584		diff.Objects = append(diff.Objects, setDiff)
585	}
586
587	// Checks diffs
588	if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil {
589		diff.Objects = append(diff.Objects, cDiffs...)
590	}
591
592	// Consul Connect diffs
593	if conDiffs := connectDiffs(old.Connect, new.Connect, contextual); conDiffs != nil {
594		diff.Objects = append(diff.Objects, conDiffs)
595	}
596
597	return diff
598}
599
600// serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged
601// fields within objects nested in the tasks will be returned.
602func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff {
603	oldMap := make(map[string]*Service, len(old))
604	newMap := make(map[string]*Service, len(new))
605	for _, o := range old {
606		oldMap[o.Name] = o
607	}
608	for _, n := range new {
609		newMap[n.Name] = n
610	}
611
612	var diffs []*ObjectDiff
613	for name, oldService := range oldMap {
614		// Diff the same, deleted and edited
615		if diff := serviceDiff(oldService, newMap[name], contextual); diff != nil {
616			diffs = append(diffs, diff)
617		}
618	}
619
620	for name, newService := range newMap {
621		// Diff the added
622		if old, ok := oldMap[name]; !ok {
623			if diff := serviceDiff(old, newService, contextual); diff != nil {
624				diffs = append(diffs, diff)
625			}
626		}
627	}
628
629	sort.Sort(ObjectDiffs(diffs))
630	return diffs
631}
632
633// serviceCheckDiff returns the diff of two service check objects. If contextual
634// diff is enabled, all fields will be returned, even if no diff occurred.
635func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff {
636	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"}
637	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
638
639	if reflect.DeepEqual(old, new) {
640		return nil
641	} else if old == nil {
642		old = &ServiceCheck{}
643		diff.Type = DiffTypeAdded
644		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
645	} else if new == nil {
646		new = &ServiceCheck{}
647		diff.Type = DiffTypeDeleted
648		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
649	} else {
650		diff.Type = DiffTypeEdited
651		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
652		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
653	}
654
655	// Diff the primitive fields.
656	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
657
658	// Diff Header
659	if headerDiff := checkHeaderDiff(old.Header, new.Header, contextual); headerDiff != nil {
660		diff.Objects = append(diff.Objects, headerDiff)
661	}
662
663	// Diff check_restart
664	if crDiff := checkRestartDiff(old.CheckRestart, new.CheckRestart, contextual); crDiff != nil {
665		diff.Objects = append(diff.Objects, crDiff)
666	}
667
668	return diff
669}
670
671// checkHeaderDiff returns the diff of two service check header objects. If
672// contextual diff is enabled, all fields will be returned, even if no diff
673// occurred.
674func checkHeaderDiff(old, new map[string][]string, contextual bool) *ObjectDiff {
675	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Header"}
676	var oldFlat, newFlat map[string]string
677
678	if reflect.DeepEqual(old, new) {
679		return nil
680	} else if len(old) == 0 {
681		diff.Type = DiffTypeAdded
682		newFlat = flatmap.Flatten(new, nil, false)
683	} else if len(new) == 0 {
684		diff.Type = DiffTypeDeleted
685		oldFlat = flatmap.Flatten(old, nil, false)
686	} else {
687		diff.Type = DiffTypeEdited
688		oldFlat = flatmap.Flatten(old, nil, false)
689		newFlat = flatmap.Flatten(new, nil, false)
690	}
691
692	diff.Fields = fieldDiffs(oldFlat, newFlat, contextual)
693	return diff
694}
695
696// checkRestartDiff returns the diff of two service check check_restart
697// objects. If contextual diff is enabled, all fields will be returned, even if
698// no diff occurred.
699func checkRestartDiff(old, new *CheckRestart, contextual bool) *ObjectDiff {
700	diff := &ObjectDiff{Type: DiffTypeNone, Name: "CheckRestart"}
701	var oldFlat, newFlat map[string]string
702
703	if reflect.DeepEqual(old, new) {
704		return nil
705	} else if old == nil {
706		diff.Type = DiffTypeAdded
707		newFlat = flatmap.Flatten(new, nil, true)
708		diff.Type = DiffTypeAdded
709	} else if new == nil {
710		diff.Type = DiffTypeDeleted
711		oldFlat = flatmap.Flatten(old, nil, true)
712	} else {
713		diff.Type = DiffTypeEdited
714		oldFlat = flatmap.Flatten(old, nil, true)
715		newFlat = flatmap.Flatten(new, nil, true)
716	}
717
718	diff.Fields = fieldDiffs(oldFlat, newFlat, contextual)
719	return diff
720}
721
722// connectDiffs returns the diff of two Consul connect objects. If contextual
723// diff is enabled, all fields will be returned, even if no diff occurred.
724func connectDiffs(old, new *ConsulConnect, contextual bool) *ObjectDiff {
725	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulConnect"}
726	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
727
728	if reflect.DeepEqual(old, new) {
729		return nil
730	} else if old == nil {
731		old = &ConsulConnect{}
732		diff.Type = DiffTypeAdded
733		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
734	} else if new == nil {
735		new = &ConsulConnect{}
736		diff.Type = DiffTypeDeleted
737		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
738	} else {
739		diff.Type = DiffTypeEdited
740		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
741		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
742	}
743
744	// Diff the primitive fields.
745	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
746
747	sidecarSvcDiff := connectSidecarServiceDiff(
748		old.SidecarService, new.SidecarService, contextual)
749	if sidecarSvcDiff != nil {
750		diff.Objects = append(diff.Objects, sidecarSvcDiff)
751	}
752
753	sidecarTaskDiff := sidecarTaskDiff(old.SidecarTask, new.SidecarTask, contextual)
754	if sidecarTaskDiff != nil {
755		diff.Objects = append(diff.Objects, sidecarTaskDiff)
756	}
757
758	return diff
759}
760
761// connectSidecarServiceDiff returns the diff of two ConsulSidecarService objects.
762// If contextual diff is enabled, all fields will be returned, even if no diff occurred.
763func connectSidecarServiceDiff(old, new *ConsulSidecarService, contextual bool) *ObjectDiff {
764	diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarService"}
765	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
766
767	if reflect.DeepEqual(old, new) {
768		return nil
769	} else if old == nil {
770		old = &ConsulSidecarService{}
771		diff.Type = DiffTypeAdded
772		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
773	} else if new == nil {
774		new = &ConsulSidecarService{}
775		diff.Type = DiffTypeDeleted
776		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
777	} else {
778		diff.Type = DiffTypeEdited
779		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
780		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
781	}
782
783	// Diff the primitive fields.
784	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
785
786	consulProxyDiff := consulProxyDiff(old.Proxy, new.Proxy, contextual)
787	if consulProxyDiff != nil {
788		diff.Objects = append(diff.Objects, consulProxyDiff)
789	}
790
791	return diff
792}
793
794// sidecarTaskDiff returns the diff of two Task objects.
795// If contextual diff is enabled, all fields will be returned, even if no diff occurred.
796func sidecarTaskDiff(old, new *SidecarTask, contextual bool) *ObjectDiff {
797	diff := &ObjectDiff{Type: DiffTypeNone, Name: "SidecarTask"}
798	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
799
800	if reflect.DeepEqual(old, new) {
801		return nil
802	} else if old == nil {
803		old = &SidecarTask{}
804		diff.Type = DiffTypeAdded
805		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
806	} else if new == nil {
807		new = &SidecarTask{}
808		diff.Type = DiffTypeDeleted
809		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
810	} else {
811		diff.Type = DiffTypeEdited
812		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
813		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
814	}
815
816	// Diff the primitive fields.
817	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, false)
818
819	// Config diff
820	if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil {
821		diff.Objects = append(diff.Objects, cDiff)
822	}
823
824	// Resources diff
825	if rDiff := old.Resources.Diff(new.Resources, contextual); rDiff != nil {
826		diff.Objects = append(diff.Objects, rDiff)
827	}
828
829	// LogConfig diff
830	lDiff := primitiveObjectDiff(old.LogConfig, new.LogConfig, nil, "LogConfig", contextual)
831	if lDiff != nil {
832		diff.Objects = append(diff.Objects, lDiff)
833	}
834
835	return diff
836}
837
838// consulProxyDiff returns the diff of two ConsulProxy objects.
839// If contextual diff is enabled, all fields will be returned, even if no diff occurred.
840func consulProxyDiff(old, new *ConsulProxy, contextual bool) *ObjectDiff {
841	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ConsulProxy"}
842	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
843
844	if reflect.DeepEqual(old, new) {
845		return nil
846	} else if old == nil {
847		old = &ConsulProxy{}
848		diff.Type = DiffTypeAdded
849		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
850	} else if new == nil {
851		new = &ConsulProxy{}
852		diff.Type = DiffTypeDeleted
853		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
854	} else {
855		diff.Type = DiffTypeEdited
856		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
857		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
858	}
859
860	// Diff the primitive fields.
861	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
862
863	consulUpstreamsDiff := primitiveObjectSetDiff(
864		interfaceSlice(old.Upstreams),
865		interfaceSlice(new.Upstreams),
866		nil, "ConsulUpstreams", contextual)
867	if consulUpstreamsDiff != nil {
868		diff.Objects = append(diff.Objects, consulUpstreamsDiff...)
869	}
870
871	// Config diff
872	if cDiff := configDiff(old.Config, new.Config, contextual); cDiff != nil {
873		diff.Objects = append(diff.Objects, cDiff)
874	}
875
876	return diff
877}
878
879// serviceCheckDiffs diffs a set of service checks. If contextual diff is
880// enabled, unchanged fields within objects nested in the tasks will be
881// returned.
882func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff {
883	oldMap := make(map[string]*ServiceCheck, len(old))
884	newMap := make(map[string]*ServiceCheck, len(new))
885	for _, o := range old {
886		oldMap[o.Name] = o
887	}
888	for _, n := range new {
889		newMap[n.Name] = n
890	}
891
892	var diffs []*ObjectDiff
893	for name, oldCheck := range oldMap {
894		// Diff the same, deleted and edited
895		if diff := serviceCheckDiff(oldCheck, newMap[name], contextual); diff != nil {
896			diffs = append(diffs, diff)
897		}
898	}
899
900	for name, newCheck := range newMap {
901		// Diff the added
902		if old, ok := oldMap[name]; !ok {
903			if diff := serviceCheckDiff(old, newCheck, contextual); diff != nil {
904				diffs = append(diffs, diff)
905			}
906		}
907	}
908
909	sort.Sort(ObjectDiffs(diffs))
910	return diffs
911}
912
913// vaultDiff returns the diff of two vault objects. If contextual diff is
914// enabled, all fields will be returned, even if no diff occurred.
915func vaultDiff(old, new *Vault, contextual bool) *ObjectDiff {
916	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Vault"}
917	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
918
919	if reflect.DeepEqual(old, new) {
920		return nil
921	} else if old == nil {
922		old = &Vault{}
923		diff.Type = DiffTypeAdded
924		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
925	} else if new == nil {
926		new = &Vault{}
927		diff.Type = DiffTypeDeleted
928		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
929	} else {
930		diff.Type = DiffTypeEdited
931		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
932		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
933	}
934
935	// Diff the primitive fields.
936	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
937
938	// Policies diffs
939	if setDiff := stringSetDiff(old.Policies, new.Policies, "Policies", contextual); setDiff != nil {
940		diff.Objects = append(diff.Objects, setDiff)
941	}
942
943	return diff
944}
945
946// parameterizedJobDiff returns the diff of two parameterized job objects. If
947// contextual diff is enabled, all fields will be returned, even if no diff
948// occurred.
949func parameterizedJobDiff(old, new *ParameterizedJobConfig, contextual bool) *ObjectDiff {
950	diff := &ObjectDiff{Type: DiffTypeNone, Name: "ParameterizedJob"}
951	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
952
953	if reflect.DeepEqual(old, new) {
954		return nil
955	} else if old == nil {
956		old = &ParameterizedJobConfig{}
957		diff.Type = DiffTypeAdded
958		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
959	} else if new == nil {
960		new = &ParameterizedJobConfig{}
961		diff.Type = DiffTypeDeleted
962		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
963	} else {
964		diff.Type = DiffTypeEdited
965		oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
966		newPrimitiveFlat = flatmap.Flatten(new, nil, true)
967	}
968
969	// Diff the primitive fields.
970	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
971
972	// Meta diffs
973	if optionalDiff := stringSetDiff(old.MetaOptional, new.MetaOptional, "MetaOptional", contextual); optionalDiff != nil {
974		diff.Objects = append(diff.Objects, optionalDiff)
975	}
976
977	if requiredDiff := stringSetDiff(old.MetaRequired, new.MetaRequired, "MetaRequired", contextual); requiredDiff != nil {
978		diff.Objects = append(diff.Objects, requiredDiff)
979	}
980
981	return diff
982}
983
984// Diff returns a diff of two resource objects. If contextual diff is enabled,
985// non-changed fields will still be returned.
986func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff {
987	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Resources"}
988	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
989
990	if reflect.DeepEqual(r, other) {
991		return nil
992	} else if r == nil {
993		r = &Resources{}
994		diff.Type = DiffTypeAdded
995		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
996	} else if other == nil {
997		other = &Resources{}
998		diff.Type = DiffTypeDeleted
999		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
1000	} else {
1001		diff.Type = DiffTypeEdited
1002		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
1003		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
1004	}
1005
1006	// Diff the primitive fields.
1007	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
1008
1009	// Network Resources diff
1010	if nDiffs := networkResourceDiffs(r.Networks, other.Networks, contextual); nDiffs != nil {
1011		diff.Objects = append(diff.Objects, nDiffs...)
1012	}
1013
1014	// Requested Devices diff
1015	if nDiffs := requestedDevicesDiffs(r.Devices, other.Devices, contextual); nDiffs != nil {
1016		diff.Objects = append(diff.Objects, nDiffs...)
1017	}
1018
1019	return diff
1020}
1021
1022// Diff returns a diff of two network resources. If contextual diff is enabled,
1023// non-changed fields will still be returned.
1024func (r *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectDiff {
1025	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Network"}
1026	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
1027	filter := []string{"Device", "CIDR", "IP"}
1028
1029	if reflect.DeepEqual(r, other) {
1030		return nil
1031	} else if r == nil {
1032		r = &NetworkResource{}
1033		diff.Type = DiffTypeAdded
1034		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
1035	} else if other == nil {
1036		other = &NetworkResource{}
1037		diff.Type = DiffTypeDeleted
1038		oldPrimitiveFlat = flatmap.Flatten(r, filter, true)
1039	} else {
1040		diff.Type = DiffTypeEdited
1041		oldPrimitiveFlat = flatmap.Flatten(r, filter, true)
1042		newPrimitiveFlat = flatmap.Flatten(other, filter, true)
1043	}
1044
1045	// Diff the primitive fields.
1046	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
1047
1048	// Port diffs
1049	resPorts := portDiffs(r.ReservedPorts, other.ReservedPorts, false, contextual)
1050	dynPorts := portDiffs(r.DynamicPorts, other.DynamicPorts, true, contextual)
1051	if resPorts != nil {
1052		diff.Objects = append(diff.Objects, resPorts...)
1053	}
1054	if dynPorts != nil {
1055		diff.Objects = append(diff.Objects, dynPorts...)
1056	}
1057
1058	return diff
1059}
1060
1061// networkResourceDiffs diffs a set of NetworkResources. If contextual diff is enabled,
1062// non-changed fields will still be returned.
1063func networkResourceDiffs(old, new []*NetworkResource, contextual bool) []*ObjectDiff {
1064	makeSet := func(objects []*NetworkResource) map[string]*NetworkResource {
1065		objMap := make(map[string]*NetworkResource, len(objects))
1066		for _, obj := range objects {
1067			hash, err := hashstructure.Hash(obj, nil)
1068			if err != nil {
1069				panic(err)
1070			}
1071			objMap[fmt.Sprintf("%d", hash)] = obj
1072		}
1073
1074		return objMap
1075	}
1076
1077	oldSet := makeSet(old)
1078	newSet := makeSet(new)
1079
1080	var diffs []*ObjectDiff
1081	for k, oldV := range oldSet {
1082		if newV, ok := newSet[k]; !ok {
1083			if diff := oldV.Diff(newV, contextual); diff != nil {
1084				diffs = append(diffs, diff)
1085			}
1086		}
1087	}
1088	for k, newV := range newSet {
1089		if oldV, ok := oldSet[k]; !ok {
1090			if diff := oldV.Diff(newV, contextual); diff != nil {
1091				diffs = append(diffs, diff)
1092			}
1093		}
1094	}
1095
1096	sort.Sort(ObjectDiffs(diffs))
1097	return diffs
1098
1099}
1100
1101// portDiffs returns the diff of two sets of ports. The dynamic flag marks the
1102// set of ports as being Dynamic ports versus Static ports. If contextual diff is enabled,
1103// non-changed fields will still be returned.
1104func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff {
1105	makeSet := func(ports []Port) map[string]Port {
1106		portMap := make(map[string]Port, len(ports))
1107		for _, port := range ports {
1108			portMap[port.Label] = port
1109		}
1110
1111		return portMap
1112	}
1113
1114	oldPorts := makeSet(old)
1115	newPorts := makeSet(new)
1116
1117	var filter []string
1118	name := "Static Port"
1119	if dynamic {
1120		filter = []string{"Value"}
1121		name = "Dynamic Port"
1122	}
1123
1124	var diffs []*ObjectDiff
1125	for portLabel, oldPort := range oldPorts {
1126		// Diff the same, deleted and edited
1127		if newPort, ok := newPorts[portLabel]; ok {
1128			diff := primitiveObjectDiff(oldPort, newPort, filter, name, contextual)
1129			if diff != nil {
1130				diffs = append(diffs, diff)
1131			}
1132		} else {
1133			diff := primitiveObjectDiff(oldPort, nil, filter, name, contextual)
1134			if diff != nil {
1135				diffs = append(diffs, diff)
1136			}
1137		}
1138	}
1139	for label, newPort := range newPorts {
1140		// Diff the added
1141		if _, ok := oldPorts[label]; !ok {
1142			diff := primitiveObjectDiff(nil, newPort, filter, name, contextual)
1143			if diff != nil {
1144				diffs = append(diffs, diff)
1145			}
1146		}
1147	}
1148
1149	sort.Sort(ObjectDiffs(diffs))
1150	return diffs
1151
1152}
1153
1154// Diff returns a diff of two requested devices. If contextual diff is enabled,
1155// non-changed fields will still be returned.
1156func (r *RequestedDevice) Diff(other *RequestedDevice, contextual bool) *ObjectDiff {
1157	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Device"}
1158	var oldPrimitiveFlat, newPrimitiveFlat map[string]string
1159
1160	if reflect.DeepEqual(r, other) {
1161		return nil
1162	} else if r == nil {
1163		diff.Type = DiffTypeAdded
1164		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
1165	} else if other == nil {
1166		diff.Type = DiffTypeDeleted
1167		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
1168	} else {
1169		diff.Type = DiffTypeEdited
1170		oldPrimitiveFlat = flatmap.Flatten(r, nil, true)
1171		newPrimitiveFlat = flatmap.Flatten(other, nil, true)
1172	}
1173
1174	// Diff the primitive fields.
1175	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
1176
1177	return diff
1178}
1179
1180// requestedDevicesDiffs diffs a set of RequestedDevices. If contextual diff is enabled,
1181// non-changed fields will still be returned.
1182func requestedDevicesDiffs(old, new []*RequestedDevice, contextual bool) []*ObjectDiff {
1183	makeSet := func(devices []*RequestedDevice) map[string]*RequestedDevice {
1184		deviceMap := make(map[string]*RequestedDevice, len(devices))
1185		for _, d := range devices {
1186			deviceMap[d.Name] = d
1187		}
1188
1189		return deviceMap
1190	}
1191
1192	oldSet := makeSet(old)
1193	newSet := makeSet(new)
1194
1195	var diffs []*ObjectDiff
1196	for k, oldV := range oldSet {
1197		newV := newSet[k]
1198		if diff := oldV.Diff(newV, contextual); diff != nil {
1199			diffs = append(diffs, diff)
1200		}
1201	}
1202	for k, newV := range newSet {
1203		if oldV, ok := oldSet[k]; !ok {
1204			if diff := oldV.Diff(newV, contextual); diff != nil {
1205				diffs = append(diffs, diff)
1206			}
1207		}
1208	}
1209
1210	sort.Sort(ObjectDiffs(diffs))
1211	return diffs
1212
1213}
1214
1215// configDiff returns the diff of two Task Config objects. If contextual diff is
1216// enabled, all fields will be returned, even if no diff occurred.
1217func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff {
1218	diff := &ObjectDiff{Type: DiffTypeNone, Name: "Config"}
1219	if reflect.DeepEqual(old, new) {
1220		return nil
1221	} else if len(old) == 0 {
1222		diff.Type = DiffTypeAdded
1223	} else if len(new) == 0 {
1224		diff.Type = DiffTypeDeleted
1225	} else {
1226		diff.Type = DiffTypeEdited
1227	}
1228
1229	// Diff the primitive fields.
1230	oldPrimitiveFlat := flatmap.Flatten(old, nil, false)
1231	newPrimitiveFlat := flatmap.Flatten(new, nil, false)
1232	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
1233	return diff
1234}
1235
1236// ObjectDiff contains the diff of two generic objects.
1237type ObjectDiff struct {
1238	Type    DiffType
1239	Name    string
1240	Fields  []*FieldDiff
1241	Objects []*ObjectDiff
1242}
1243
1244func (o *ObjectDiff) GoString() string {
1245	out := fmt.Sprintf("\n%q (%s) {\n", o.Name, o.Type)
1246	for _, f := range o.Fields {
1247		out += fmt.Sprintf("%#v\n", f)
1248	}
1249	for _, o := range o.Objects {
1250		out += fmt.Sprintf("%#v\n", o)
1251	}
1252	out += "}"
1253	return out
1254}
1255
1256func (o *ObjectDiff) Less(other *ObjectDiff) bool {
1257	if reflect.DeepEqual(o, other) {
1258		return false
1259	} else if other == nil {
1260		return false
1261	} else if o == nil {
1262		return true
1263	}
1264
1265	if o.Name != other.Name {
1266		return o.Name < other.Name
1267	}
1268
1269	if o.Type != other.Type {
1270		return o.Type.Less(other.Type)
1271	}
1272
1273	if lO, lOther := len(o.Fields), len(other.Fields); lO != lOther {
1274		return lO < lOther
1275	}
1276
1277	if lO, lOther := len(o.Objects), len(other.Objects); lO != lOther {
1278		return lO < lOther
1279	}
1280
1281	// Check each field
1282	sort.Sort(FieldDiffs(o.Fields))
1283	sort.Sort(FieldDiffs(other.Fields))
1284
1285	for i, oV := range o.Fields {
1286		if oV.Less(other.Fields[i]) {
1287			return true
1288		}
1289	}
1290
1291	// Check each object
1292	sort.Sort(ObjectDiffs(o.Objects))
1293	sort.Sort(ObjectDiffs(other.Objects))
1294	for i, oV := range o.Objects {
1295		if oV.Less(other.Objects[i]) {
1296			return true
1297		}
1298	}
1299
1300	return false
1301}
1302
1303// For sorting ObjectDiffs
1304type ObjectDiffs []*ObjectDiff
1305
1306func (o ObjectDiffs) Len() int           { return len(o) }
1307func (o ObjectDiffs) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
1308func (o ObjectDiffs) Less(i, j int) bool { return o[i].Less(o[j]) }
1309
1310type FieldDiff struct {
1311	Type        DiffType
1312	Name        string
1313	Old, New    string
1314	Annotations []string
1315}
1316
1317// fieldDiff returns a FieldDiff if old and new are different otherwise, it
1318// returns nil. If contextual diff is enabled, even non-changed fields will be
1319// returned.
1320func fieldDiff(old, new, name string, contextual bool) *FieldDiff {
1321	diff := &FieldDiff{Name: name, Type: DiffTypeNone}
1322	if old == new {
1323		if !contextual {
1324			return nil
1325		}
1326		diff.Old, diff.New = old, new
1327		return diff
1328	}
1329
1330	if old == "" {
1331		diff.Type = DiffTypeAdded
1332		diff.New = new
1333	} else if new == "" {
1334		diff.Type = DiffTypeDeleted
1335		diff.Old = old
1336	} else {
1337		diff.Type = DiffTypeEdited
1338		diff.Old = old
1339		diff.New = new
1340	}
1341	return diff
1342}
1343
1344func (f *FieldDiff) GoString() string {
1345	out := fmt.Sprintf("%q (%s): %q => %q", f.Name, f.Type, f.Old, f.New)
1346	if len(f.Annotations) != 0 {
1347		out += fmt.Sprintf(" (%s)", strings.Join(f.Annotations, ", "))
1348	}
1349
1350	return out
1351}
1352
1353func (f *FieldDiff) Less(other *FieldDiff) bool {
1354	if reflect.DeepEqual(f, other) {
1355		return false
1356	} else if other == nil {
1357		return false
1358	} else if f == nil {
1359		return true
1360	}
1361
1362	if f.Name != other.Name {
1363		return f.Name < other.Name
1364	} else if f.Old != other.Old {
1365		return f.Old < other.Old
1366	}
1367
1368	return f.New < other.New
1369}
1370
1371// For sorting FieldDiffs
1372type FieldDiffs []*FieldDiff
1373
1374func (f FieldDiffs) Len() int           { return len(f) }
1375func (f FieldDiffs) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
1376func (f FieldDiffs) Less(i, j int) bool { return f[i].Less(f[j]) }
1377
1378// fieldDiffs takes a map of field names to their values and returns a set of
1379// field diffs. If contextual diff is enabled, even non-changed fields will be
1380// returned.
1381func fieldDiffs(old, new map[string]string, contextual bool) []*FieldDiff {
1382	var diffs []*FieldDiff
1383	visited := make(map[string]struct{})
1384	for k, oldV := range old {
1385		visited[k] = struct{}{}
1386		newV := new[k]
1387		if diff := fieldDiff(oldV, newV, k, contextual); diff != nil {
1388			diffs = append(diffs, diff)
1389		}
1390	}
1391
1392	for k, newV := range new {
1393		if _, ok := visited[k]; !ok {
1394			if diff := fieldDiff("", newV, k, contextual); diff != nil {
1395				diffs = append(diffs, diff)
1396			}
1397		}
1398	}
1399
1400	sort.Sort(FieldDiffs(diffs))
1401	return diffs
1402}
1403
1404// stringSetDiff diffs two sets of strings with the given name.
1405func stringSetDiff(old, new []string, name string, contextual bool) *ObjectDiff {
1406	oldMap := make(map[string]struct{}, len(old))
1407	newMap := make(map[string]struct{}, len(new))
1408	for _, o := range old {
1409		oldMap[o] = struct{}{}
1410	}
1411	for _, n := range new {
1412		newMap[n] = struct{}{}
1413	}
1414	if reflect.DeepEqual(oldMap, newMap) && !contextual {
1415		return nil
1416	}
1417
1418	diff := &ObjectDiff{Name: name}
1419	var added, removed bool
1420	for k := range oldMap {
1421		if _, ok := newMap[k]; !ok {
1422			diff.Fields = append(diff.Fields, fieldDiff(k, "", name, contextual))
1423			removed = true
1424		} else if contextual {
1425			diff.Fields = append(diff.Fields, fieldDiff(k, k, name, contextual))
1426		}
1427	}
1428
1429	for k := range newMap {
1430		if _, ok := oldMap[k]; !ok {
1431			diff.Fields = append(diff.Fields, fieldDiff("", k, name, contextual))
1432			added = true
1433		}
1434	}
1435
1436	sort.Sort(FieldDiffs(diff.Fields))
1437
1438	// Determine the type
1439	if added && removed {
1440		diff.Type = DiffTypeEdited
1441	} else if added {
1442		diff.Type = DiffTypeAdded
1443	} else if removed {
1444		diff.Type = DiffTypeDeleted
1445	} else {
1446		// Diff of an empty set
1447		if len(diff.Fields) == 0 {
1448			return nil
1449		}
1450
1451		diff.Type = DiffTypeNone
1452	}
1453
1454	return diff
1455}
1456
1457// primitiveObjectDiff returns a diff of the passed objects' primitive fields.
1458// The filter field can be used to exclude fields from the diff. The name is the
1459// name of the objects. If contextual is set, non-changed fields will also be
1460// stored in the object diff.
1461func primitiveObjectDiff(old, new interface{}, filter []string, name string, contextual bool) *ObjectDiff {
1462	oldPrimitiveFlat := flatmap.Flatten(old, filter, true)
1463	newPrimitiveFlat := flatmap.Flatten(new, filter, true)
1464	delete(oldPrimitiveFlat, "")
1465	delete(newPrimitiveFlat, "")
1466
1467	diff := &ObjectDiff{Name: name}
1468	diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
1469
1470	var added, deleted, edited bool
1471	for _, f := range diff.Fields {
1472		switch f.Type {
1473		case DiffTypeEdited:
1474			edited = true
1475			break
1476		case DiffTypeDeleted:
1477			deleted = true
1478		case DiffTypeAdded:
1479			added = true
1480		}
1481	}
1482
1483	if edited || added && deleted {
1484		diff.Type = DiffTypeEdited
1485	} else if added {
1486		diff.Type = DiffTypeAdded
1487	} else if deleted {
1488		diff.Type = DiffTypeDeleted
1489	} else {
1490		return nil
1491	}
1492
1493	return diff
1494}
1495
1496// primitiveObjectSetDiff does a set difference of the old and new sets. The
1497// filter parameter can be used to filter a set of primitive fields in the
1498// passed structs. The name corresponds to the name of the passed objects. If
1499// contextual diff is enabled, objects' primitive fields will be returned even if
1500// no diff exists.
1501func primitiveObjectSetDiff(old, new []interface{}, filter []string, name string, contextual bool) []*ObjectDiff {
1502	makeSet := func(objects []interface{}) map[string]interface{} {
1503		objMap := make(map[string]interface{}, len(objects))
1504		for _, obj := range objects {
1505			hash, err := hashstructure.Hash(obj, nil)
1506			if err != nil {
1507				panic(err)
1508			}
1509			objMap[fmt.Sprintf("%d", hash)] = obj
1510		}
1511
1512		return objMap
1513	}
1514
1515	oldSet := makeSet(old)
1516	newSet := makeSet(new)
1517
1518	var diffs []*ObjectDiff
1519	for k, v := range oldSet {
1520		// Deleted
1521		if _, ok := newSet[k]; !ok {
1522			diffs = append(diffs, primitiveObjectDiff(v, nil, filter, name, contextual))
1523		}
1524	}
1525	for k, v := range newSet {
1526		// Added
1527		if _, ok := oldSet[k]; !ok {
1528			diffs = append(diffs, primitiveObjectDiff(nil, v, filter, name, contextual))
1529		}
1530	}
1531
1532	sort.Sort(ObjectDiffs(diffs))
1533	return diffs
1534}
1535
1536// interfaceSlice is a helper method that takes a slice of typed elements and
1537// returns a slice of interface. This method will panic if given a non-slice
1538// input.
1539func interfaceSlice(slice interface{}) []interface{} {
1540	s := reflect.ValueOf(slice)
1541	if s.Kind() != reflect.Slice {
1542		panic("InterfaceSlice() given a non-slice type")
1543	}
1544
1545	ret := make([]interface{}, s.Len())
1546
1547	for i := 0; i < s.Len(); i++ {
1548		ret[i] = s.Index(i).Interface()
1549	}
1550
1551	return ret
1552}
1553