package structs import ( "reflect" "testing" "time" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper" "github.com/stretchr/testify/require" ) func TestJobDiff(t *testing.T) { ci.Parallel(t) cases := []struct { Old, New *Job Expected *JobDiff Error bool Contextual bool }{ { Old: nil, New: nil, Expected: &JobDiff{ Type: DiffTypeNone, }, }, { // Different IDs Old: &Job{ ID: "foo", }, New: &Job{ ID: "bar", }, Error: true, }, { // Primitive only that is the same Old: &Job{ Region: "foo", ID: "foo", Name: "foo", Type: "batch", Priority: 10, AllAtOnce: true, Meta: map[string]string{ "foo": "bar", }, }, New: &Job{ Region: "foo", ID: "foo", Name: "foo", Type: "batch", Priority: 10, AllAtOnce: true, Meta: map[string]string{ "foo": "bar", }, }, Expected: &JobDiff{ Type: DiffTypeNone, ID: "foo", }, }, { // Primitive only that is has diffs Old: &Job{ Region: "foo", ID: "foo", Name: "foo", Type: "batch", Priority: 10, AllAtOnce: true, Meta: map[string]string{ "foo": "bar", }, }, New: &Job{ Region: "bar", ID: "foo", Name: "bar", Type: "system", Priority: 100, AllAtOnce: false, Meta: map[string]string{ "foo": "baz", }, }, Expected: &JobDiff{ Type: DiffTypeEdited, ID: "foo", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "AllAtOnce", Old: "true", New: "false", }, { Type: DiffTypeEdited, Name: "Meta[foo]", Old: "bar", New: "baz", }, { Type: DiffTypeEdited, Name: "Name", Old: "foo", New: "bar", }, { Type: DiffTypeEdited, Name: "Priority", Old: "10", New: "100", }, { Type: DiffTypeEdited, Name: "Region", Old: "foo", New: "bar", }, { Type: DiffTypeEdited, Name: "Type", Old: "batch", New: "system", }, }, }, }, { // Primitive only deleted job Old: &Job{ Region: "foo", ID: "foo", Name: "foo", Type: "batch", Priority: 10, AllAtOnce: true, Meta: map[string]string{ "foo": "bar", }, }, New: nil, Expected: &JobDiff{ Type: DiffTypeDeleted, ID: "foo", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "AllAtOnce", Old: "true", New: "", }, { Type: DiffTypeDeleted, Name: "Dispatched", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "Meta[foo]", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Name", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "Priority", Old: "10", New: "", }, { Type: DiffTypeDeleted, Name: "Region", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "Stop", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "Type", Old: "batch", New: "", }, }, }, }, { // Primitive only added job Old: nil, New: &Job{ Region: "foo", ID: "foo", Name: "foo", Type: "batch", Priority: 10, AllAtOnce: true, Meta: map[string]string{ "foo": "bar", }, }, Expected: &JobDiff{ Type: DiffTypeAdded, ID: "foo", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "AllAtOnce", Old: "", New: "true", }, { Type: DiffTypeAdded, Name: "Dispatched", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "Meta[foo]", Old: "", New: "bar", }, { Type: DiffTypeAdded, Name: "Name", Old: "", New: "foo", }, { Type: DiffTypeAdded, Name: "Priority", Old: "", New: "10", }, { Type: DiffTypeAdded, Name: "Region", Old: "", New: "foo", }, { Type: DiffTypeAdded, Name: "Stop", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "Type", Old: "", New: "batch", }, }, }, }, { // Map diff Old: &Job{ Meta: map[string]string{ "foo": "foo", "bar": "bar", }, }, New: &Job{ Meta: map[string]string{ "bar": "bar", "baz": "baz", }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Meta[baz]", Old: "", New: "baz", }, { Type: DiffTypeDeleted, Name: "Meta[foo]", Old: "foo", New: "", }, }, }, }, { // Datacenter diff both added and removed Old: &Job{ Datacenters: []string{"foo", "bar"}, }, New: &Job{ Datacenters: []string{"baz", "bar"}, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Datacenters", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Datacenters", Old: "", New: "baz", }, { Type: DiffTypeDeleted, Name: "Datacenters", Old: "foo", New: "", }, }, }, }, }, }, { // Datacenter diff just added Old: &Job{ Datacenters: []string{"foo", "bar"}, }, New: &Job{ Datacenters: []string{"foo", "bar", "baz"}, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Datacenters", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Datacenters", Old: "", New: "baz", }, }, }, }, }, }, { // Datacenter diff just deleted Old: &Job{ Datacenters: []string{"foo", "bar"}, }, New: &Job{ Datacenters: []string{"foo"}, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Datacenters", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Datacenters", Old: "bar", New: "", }, }, }, }, }, }, { // Datacenter contextual no change Contextual: true, Old: &Job{ Datacenters: []string{"foo", "bar"}, }, New: &Job{ Datacenters: []string{"foo", "bar"}, }, Expected: &JobDiff{ Type: DiffTypeNone, }, }, { // Datacenter contextual Contextual: true, Old: &Job{ Datacenters: []string{"foo", "bar"}, }, New: &Job{ Datacenters: []string{"foo", "bar", "baz"}, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Datacenters", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Datacenters", Old: "", New: "baz", }, { Type: DiffTypeNone, Name: "Datacenters", Old: "bar", New: "bar", }, { Type: DiffTypeNone, Name: "Datacenters", Old: "foo", New: "foo", }, }, }, }, }, }, { // Periodic added Old: &Job{}, New: &Job{ Periodic: &PeriodicConfig{ Enabled: false, Spec: "*/15 * * * * *", SpecType: "foo", ProhibitOverlap: false, TimeZone: "Europe/Minsk", }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Periodic", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Enabled", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "ProhibitOverlap", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "Spec", Old: "", New: "*/15 * * * * *", }, { Type: DiffTypeAdded, Name: "SpecType", Old: "", New: "foo", }, { Type: DiffTypeAdded, Name: "TimeZone", Old: "", New: "Europe/Minsk", }, }, }, }, }, }, { // Periodic deleted Old: &Job{ Periodic: &PeriodicConfig{ Enabled: false, Spec: "*/15 * * * * *", SpecType: "foo", ProhibitOverlap: false, TimeZone: "Europe/Minsk", }, }, New: &Job{}, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Periodic", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Enabled", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "ProhibitOverlap", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "Spec", Old: "*/15 * * * * *", New: "", }, { Type: DiffTypeDeleted, Name: "SpecType", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "TimeZone", Old: "Europe/Minsk", New: "", }, }, }, }, }, }, { // Periodic edited Old: &Job{ Periodic: &PeriodicConfig{ Enabled: false, Spec: "*/15 * * * * *", SpecType: "foo", ProhibitOverlap: false, TimeZone: "Europe/Minsk", }, }, New: &Job{ Periodic: &PeriodicConfig{ Enabled: true, Spec: "* * * * * *", SpecType: "cron", ProhibitOverlap: true, TimeZone: "America/Los_Angeles", }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Periodic", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Enabled", Old: "false", New: "true", }, { Type: DiffTypeEdited, Name: "ProhibitOverlap", Old: "false", New: "true", }, { Type: DiffTypeEdited, Name: "Spec", Old: "*/15 * * * * *", New: "* * * * * *", }, { Type: DiffTypeEdited, Name: "SpecType", Old: "foo", New: "cron", }, { Type: DiffTypeEdited, Name: "TimeZone", Old: "Europe/Minsk", New: "America/Los_Angeles", }, }, }, }, }, }, { // Periodic edited with context Contextual: true, Old: &Job{ Periodic: &PeriodicConfig{ Enabled: false, Spec: "*/15 * * * * *", SpecType: "foo", ProhibitOverlap: false, TimeZone: "Europe/Minsk", }, }, New: &Job{ Periodic: &PeriodicConfig{ Enabled: true, Spec: "* * * * * *", SpecType: "foo", ProhibitOverlap: false, TimeZone: "Europe/Minsk", }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Periodic", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Enabled", Old: "false", New: "true", }, { Type: DiffTypeNone, Name: "ProhibitOverlap", Old: "false", New: "false", }, { Type: DiffTypeEdited, Name: "Spec", Old: "*/15 * * * * *", New: "* * * * * *", }, { Type: DiffTypeNone, Name: "SpecType", Old: "foo", New: "foo", }, { Type: DiffTypeNone, Name: "TimeZone", Old: "Europe/Minsk", New: "Europe/Minsk", }, }, }, }, }, }, { // Constraints edited Old: &Job{ Constraints: []*Constraint{ { LTarget: "foo", RTarget: "foo", Operand: "foo", }, { LTarget: "bar", RTarget: "bar", Operand: "bar", }, }, }, New: &Job{ Constraints: []*Constraint{ { LTarget: "foo", RTarget: "foo", Operand: "foo", }, { LTarget: "baz", RTarget: "baz", Operand: "baz", }, }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Constraint", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "LTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Operand", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "RTarget", Old: "", New: "baz", }, }, }, { Type: DiffTypeDeleted, Name: "Constraint", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "LTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Operand", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "RTarget", Old: "bar", New: "", }, }, }, }, }, }, { // Affinities edited Old: &Job{ Affinities: []*Affinity{ { LTarget: "foo", RTarget: "foo", Operand: "foo", Weight: 20, }, { LTarget: "bar", RTarget: "bar", Operand: "bar", Weight: 20, }, }, }, New: &Job{ Affinities: []*Affinity{ { LTarget: "foo", RTarget: "foo", Operand: "foo", Weight: 20, }, { LTarget: "baz", RTarget: "baz", Operand: "baz", Weight: 20, }, }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Affinity", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "LTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Operand", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "RTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Weight", Old: "", New: "20", }, }, }, { Type: DiffTypeDeleted, Name: "Affinity", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "LTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Operand", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "RTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Weight", Old: "20", New: "", }, }, }, }, }, }, { // Task groups edited Old: &Job{ TaskGroups: []*TaskGroup{ { Name: "foo", Count: 1, }, { Name: "bar", Count: 1, }, { Name: "baz", Count: 1, }, }, }, New: &Job{ TaskGroups: []*TaskGroup{ { Name: "bar", Count: 1, }, { Name: "baz", Count: 2, }, { Name: "bam", Count: 1, }, }, }, Expected: &JobDiff{ Type: DiffTypeEdited, TaskGroups: []*TaskGroupDiff{ { Type: DiffTypeAdded, Name: "bam", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Count", Old: "", New: "1", }, }, }, { Type: DiffTypeNone, Name: "bar", }, { Type: DiffTypeEdited, Name: "baz", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Count", Old: "1", New: "2", }, }, }, { Type: DiffTypeDeleted, Name: "foo", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Count", Old: "1", New: "", }, }, }, }, }, }, { // Parameterized Job added Old: &Job{}, New: &Job{ ParameterizedJob: &ParameterizedJobConfig{ Payload: DispatchPayloadRequired, MetaOptional: []string{"foo"}, MetaRequired: []string{"bar"}, }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "ParameterizedJob", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Payload", Old: "", New: DispatchPayloadRequired, }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "MetaOptional", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "MetaOptional", Old: "", New: "foo", }, }, }, { Type: DiffTypeAdded, Name: "MetaRequired", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "MetaRequired", Old: "", New: "bar", }, }, }, }, }, }, }, }, { // Parameterized Job deleted Old: &Job{ ParameterizedJob: &ParameterizedJobConfig{ Payload: DispatchPayloadRequired, MetaOptional: []string{"foo"}, MetaRequired: []string{"bar"}, }, }, New: &Job{}, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "ParameterizedJob", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Payload", Old: DispatchPayloadRequired, New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "MetaOptional", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "MetaOptional", Old: "foo", New: "", }, }, }, { Type: DiffTypeDeleted, Name: "MetaRequired", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "MetaRequired", Old: "bar", New: "", }, }, }, }, }, }, }, }, { // Parameterized Job edited Old: &Job{ ParameterizedJob: &ParameterizedJobConfig{ Payload: DispatchPayloadRequired, MetaOptional: []string{"foo"}, MetaRequired: []string{"bar"}, }, }, New: &Job{ ParameterizedJob: &ParameterizedJobConfig{ Payload: DispatchPayloadOptional, MetaOptional: []string{"bam"}, MetaRequired: []string{"bang"}, }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "ParameterizedJob", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Payload", Old: DispatchPayloadRequired, New: DispatchPayloadOptional, }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "MetaOptional", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "MetaOptional", Old: "", New: "bam", }, { Type: DiffTypeDeleted, Name: "MetaOptional", Old: "foo", New: "", }, }, }, { Type: DiffTypeEdited, Name: "MetaRequired", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "MetaRequired", Old: "", New: "bang", }, { Type: DiffTypeDeleted, Name: "MetaRequired", Old: "bar", New: "", }, }, }, }, }, }, }, }, { // Parameterized Job edited with context Contextual: true, Old: &Job{ ParameterizedJob: &ParameterizedJobConfig{ Payload: DispatchPayloadRequired, MetaOptional: []string{"foo"}, MetaRequired: []string{"bar"}, }, }, New: &Job{ ParameterizedJob: &ParameterizedJobConfig{ Payload: DispatchPayloadOptional, MetaOptional: []string{"foo"}, MetaRequired: []string{"bar"}, }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "ParameterizedJob", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Payload", Old: DispatchPayloadRequired, New: DispatchPayloadOptional, }, }, Objects: []*ObjectDiff{ { Type: DiffTypeNone, Name: "MetaOptional", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "MetaOptional", Old: "foo", New: "foo", }, }, }, { Type: DiffTypeNone, Name: "MetaRequired", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "MetaRequired", Old: "bar", New: "bar", }, }, }, }, }, }, }, }, { // Multiregion: region added Old: &Job{ NomadTokenID: "abcdef", Multiregion: &Multiregion{ Strategy: &MultiregionStrategy{ MaxParallel: 1, OnFailure: "fail_all", }, Regions: []*MultiregionRegion{ { Name: "west", Count: 1, Datacenters: []string{"west-1"}, Meta: map[string]string{"region_code": "W"}, }, }, }, }, New: &Job{ NomadTokenID: "12345", Multiregion: &Multiregion{ Strategy: &MultiregionStrategy{ MaxParallel: 2, OnFailure: "fail_all", }, Regions: []*MultiregionRegion{ { Name: "west", Count: 3, Datacenters: []string{"west-2"}, Meta: map[string]string{"region_code": "W"}, }, { Name: "east", Count: 2, Datacenters: []string{"east-1", "east-2"}, Meta: map[string]string{"region_code": "E"}, }, }, }, }, Expected: &JobDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Multiregion", Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Region", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Count", Old: "1", New: "3", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Datacenters", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Datacenters", Old: "", New: "west-2", }, { Type: DiffTypeDeleted, Name: "Datacenters", Old: "west-1", New: "", }, }, }, }, }, { Type: DiffTypeAdded, Name: "Region", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Count", Old: "", New: "2", }, { Type: DiffTypeAdded, Name: "Meta[region_code]", Old: "", New: "E", }, { Type: DiffTypeAdded, Name: "Name", Old: "", New: "east", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Datacenters", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Datacenters", Old: "", New: "east-1", }, { Type: DiffTypeAdded, Name: "Datacenters", Old: "", New: "east-2", }, }, }, }, }, { Type: DiffTypeEdited, Name: "Strategy", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "MaxParallel", Old: "1", New: "2", }, }, }, }, }, }, }, }, } for i, c := range cases { actual, err := c.Old.Diff(c.New, c.Contextual) if c.Error && err == nil { t.Fatalf("case %d: expected errored", i+1) } else if err != nil { if !c.Error { t.Fatalf("case %d: errored %#v", i+1, err) } else { continue } } if !reflect.DeepEqual(actual, c.Expected) { t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n", i+1, actual, c.Expected) } } } func TestTaskGroupDiff(t *testing.T) { ci.Parallel(t) cases := []struct { TestCase string Old, New *TaskGroup Expected *TaskGroupDiff ExpErr bool Contextual bool }{ { TestCase: "Empty", Old: nil, New: nil, Expected: &TaskGroupDiff{ Type: DiffTypeNone, }, }, { TestCase: "Primitive only that has different names", Old: &TaskGroup{ Name: "foo", Count: 10, Meta: map[string]string{ "foo": "bar", }, }, New: &TaskGroup{ Name: "bar", Count: 10, Meta: map[string]string{ "foo": "bar", }, }, ExpErr: true, }, { TestCase: "Primitive only that is the same", Old: &TaskGroup{ Name: "foo", Count: 10, Meta: map[string]string{ "foo": "bar", }, }, New: &TaskGroup{ Name: "foo", Count: 10, Meta: map[string]string{ "foo": "bar", }, }, Expected: &TaskGroupDiff{ Type: DiffTypeNone, Name: "foo", }, }, { TestCase: "Primitive only that has diffs", Old: &TaskGroup{ Name: "foo", Count: 10, Meta: map[string]string{ "foo": "bar", }, }, New: &TaskGroup{ Name: "foo", Count: 100, Meta: map[string]string{ "foo": "baz", }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Name: "foo", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Count", Old: "10", New: "100", }, { Type: DiffTypeEdited, Name: "Meta[foo]", Old: "bar", New: "baz", }, }, }, }, { TestCase: "Map diff", Old: &TaskGroup{ Meta: map[string]string{ "foo": "foo", "bar": "bar", }, }, New: &TaskGroup{ Meta: map[string]string{ "bar": "bar", "baz": "baz", }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Meta[baz]", Old: "", New: "baz", }, { Type: DiffTypeDeleted, Name: "Meta[foo]", Old: "foo", New: "", }, }, }, }, { TestCase: "Constraints edited", Old: &TaskGroup{ Constraints: []*Constraint{ { LTarget: "foo", RTarget: "foo", Operand: "foo", }, { LTarget: "bar", RTarget: "bar", Operand: "bar", }, }, }, New: &TaskGroup{ Constraints: []*Constraint{ { LTarget: "foo", RTarget: "foo", Operand: "foo", }, { LTarget: "baz", RTarget: "baz", Operand: "baz", }, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Constraint", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "LTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Operand", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "RTarget", Old: "", New: "baz", }, }, }, { Type: DiffTypeDeleted, Name: "Constraint", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "LTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Operand", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "RTarget", Old: "bar", New: "", }, }, }, }, }, }, { TestCase: "Affinities edited", Old: &TaskGroup{ Affinities: []*Affinity{ { LTarget: "foo", RTarget: "foo", Operand: "foo", Weight: 20, }, { LTarget: "bar", RTarget: "bar", Operand: "bar", Weight: 20, }, }, }, New: &TaskGroup{ Affinities: []*Affinity{ { LTarget: "foo", RTarget: "foo", Operand: "foo", Weight: 20, }, { LTarget: "baz", RTarget: "baz", Operand: "baz", Weight: 20, }, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Affinity", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "LTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Operand", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "RTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Weight", Old: "", New: "20", }, }, }, { Type: DiffTypeDeleted, Name: "Affinity", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "LTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Operand", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "RTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Weight", Old: "20", New: "", }, }, }, }, }, }, { TestCase: "Consul added", Old: &TaskGroup{}, New: &TaskGroup{ Consul: &Consul{ Namespace: "team1", }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Consul", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Namespace", Old: "", New: "team1", }, }, }, }, }, }, { TestCase: "Consul deleted", Old: &TaskGroup{ Consul: &Consul{ Namespace: "team1", }, }, New: &TaskGroup{}, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Consul", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Namespace", Old: "team1", New: "", }, }, }, }, }, }, { TestCase: "Consul updated", Old: &TaskGroup{ Consul: &Consul{ Namespace: "team1", }, }, New: &TaskGroup{ Consul: &Consul{ Namespace: "team2", }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Consul", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Namespace", Old: "team1", New: "team2", }, }, }, }, }, }, { TestCase: "RestartPolicy added", Old: &TaskGroup{}, New: &TaskGroup{ RestartPolicy: &RestartPolicy{ Attempts: 1, Interval: 1 * time.Second, Delay: 1 * time.Second, Mode: "fail", }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "RestartPolicy", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Attempts", Old: "", New: "1", }, { Type: DiffTypeAdded, Name: "Delay", Old: "", New: "1000000000", }, { Type: DiffTypeAdded, Name: "Interval", Old: "", New: "1000000000", }, { Type: DiffTypeAdded, Name: "Mode", Old: "", New: "fail", }, }, }, }, }, }, { TestCase: "RestartPolicy deleted", Old: &TaskGroup{ RestartPolicy: &RestartPolicy{ Attempts: 1, Interval: 1 * time.Second, Delay: 1 * time.Second, Mode: "fail", }, }, New: &TaskGroup{}, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "RestartPolicy", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Attempts", Old: "1", New: "", }, { Type: DiffTypeDeleted, Name: "Delay", Old: "1000000000", New: "", }, { Type: DiffTypeDeleted, Name: "Interval", Old: "1000000000", New: "", }, { Type: DiffTypeDeleted, Name: "Mode", Old: "fail", New: "", }, }, }, }, }, }, { TestCase: "RestartPolicy edited", Old: &TaskGroup{ RestartPolicy: &RestartPolicy{ Attempts: 1, Interval: 1 * time.Second, Delay: 1 * time.Second, Mode: "fail", }, }, New: &TaskGroup{ RestartPolicy: &RestartPolicy{ Attempts: 2, Interval: 2 * time.Second, Delay: 2 * time.Second, Mode: "delay", }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "RestartPolicy", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Attempts", Old: "1", New: "2", }, { Type: DiffTypeEdited, Name: "Delay", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeEdited, Name: "Interval", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeEdited, Name: "Mode", Old: "fail", New: "delay", }, }, }, }, }, }, { TestCase: "RestartPolicy edited with context", Contextual: true, Old: &TaskGroup{ RestartPolicy: &RestartPolicy{ Attempts: 1, Interval: 1 * time.Second, Delay: 1 * time.Second, Mode: "fail", }, }, New: &TaskGroup{ RestartPolicy: &RestartPolicy{ Attempts: 2, Interval: 2 * time.Second, Delay: 1 * time.Second, Mode: "fail", }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "RestartPolicy", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Attempts", Old: "1", New: "2", }, { Type: DiffTypeNone, Name: "Delay", Old: "1000000000", New: "1000000000", }, { Type: DiffTypeEdited, Name: "Interval", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeNone, Name: "Mode", Old: "fail", New: "fail", }, }, }, }, }, }, { TestCase: "ReschedulePolicy added", Old: &TaskGroup{}, New: &TaskGroup{ ReschedulePolicy: &ReschedulePolicy{ Attempts: 1, Interval: 15 * time.Second, Delay: 5 * time.Second, MaxDelay: 20 * time.Second, DelayFunction: "exponential", Unlimited: false, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "ReschedulePolicy", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Attempts", Old: "", New: "1", }, { Type: DiffTypeAdded, Name: "Delay", Old: "", New: "5000000000", }, { Type: DiffTypeAdded, Name: "DelayFunction", Old: "", New: "exponential", }, { Type: DiffTypeAdded, Name: "Interval", Old: "", New: "15000000000", }, { Type: DiffTypeAdded, Name: "MaxDelay", Old: "", New: "20000000000", }, { Type: DiffTypeAdded, Name: "Unlimited", Old: "", New: "false", }, }, }, }, }, }, { TestCase: "ReschedulePolicy deleted", Old: &TaskGroup{ ReschedulePolicy: &ReschedulePolicy{ Attempts: 1, Interval: 15 * time.Second, Delay: 5 * time.Second, MaxDelay: 20 * time.Second, DelayFunction: "exponential", Unlimited: false, }, }, New: &TaskGroup{}, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "ReschedulePolicy", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Attempts", Old: "1", New: "", }, { Type: DiffTypeDeleted, Name: "Delay", Old: "5000000000", New: "", }, { Type: DiffTypeDeleted, Name: "DelayFunction", Old: "exponential", New: "", }, { Type: DiffTypeDeleted, Name: "Interval", Old: "15000000000", New: "", }, { Type: DiffTypeDeleted, Name: "MaxDelay", Old: "20000000000", New: "", }, { Type: DiffTypeDeleted, Name: "Unlimited", Old: "false", New: "", }, }, }, }, }, }, { TestCase: "ReschedulePolicy edited", Old: &TaskGroup{ ReschedulePolicy: &ReschedulePolicy{ Attempts: 1, Interval: 1 * time.Second, DelayFunction: "exponential", Delay: 20 * time.Second, MaxDelay: 1 * time.Minute, Unlimited: false, }, }, New: &TaskGroup{ ReschedulePolicy: &ReschedulePolicy{ Attempts: 2, Interval: 2 * time.Second, DelayFunction: "constant", Delay: 30 * time.Second, MaxDelay: 1 * time.Minute, Unlimited: true, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "ReschedulePolicy", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Attempts", Old: "1", New: "2", }, { Type: DiffTypeEdited, Name: "Delay", Old: "20000000000", New: "30000000000", }, { Type: DiffTypeEdited, Name: "DelayFunction", Old: "exponential", New: "constant", }, { Type: DiffTypeEdited, Name: "Interval", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeEdited, Name: "Unlimited", Old: "false", New: "true", }, }, }, }, }, }, { TestCase: "ReschedulePolicy edited with context", Contextual: true, Old: &TaskGroup{ ReschedulePolicy: &ReschedulePolicy{ Attempts: 1, Interval: 1 * time.Second, }, }, New: &TaskGroup{ ReschedulePolicy: &ReschedulePolicy{ Attempts: 1, Interval: 2 * time.Second, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "ReschedulePolicy", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Attempts", Old: "1", New: "1", }, { Type: DiffTypeNone, Name: "Delay", Old: "0", New: "0", }, { Type: DiffTypeNone, Name: "DelayFunction", Old: "", New: "", }, { Type: DiffTypeEdited, Name: "Interval", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeNone, Name: "MaxDelay", Old: "0", New: "0", }, { Type: DiffTypeNone, Name: "Unlimited", Old: "false", New: "false", }, }, }, }, }, }, { TestCase: "Update strategy deleted", Old: &TaskGroup{ Update: &UpdateStrategy{ AutoRevert: true, }, }, New: &TaskGroup{}, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Update", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "AutoPromote", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "AutoRevert", Old: "true", New: "", }, { Type: DiffTypeDeleted, Name: "Canary", Old: "0", New: "", }, { Type: DiffTypeDeleted, Name: "HealthyDeadline", Old: "0", New: "", }, { Type: DiffTypeDeleted, Name: "MaxParallel", Old: "0", New: "", }, { Type: DiffTypeDeleted, Name: "MinHealthyTime", Old: "0", New: "", }, { Type: DiffTypeDeleted, Name: "ProgressDeadline", Old: "0", New: "", }, }, }, }, }, }, { TestCase: "Update strategy added", Old: &TaskGroup{}, New: &TaskGroup{ Update: &UpdateStrategy{ AutoRevert: true, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Update", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "AutoPromote", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "AutoRevert", Old: "", New: "true", }, { Type: DiffTypeAdded, Name: "Canary", Old: "", New: "0", }, { Type: DiffTypeAdded, Name: "HealthyDeadline", Old: "", New: "0", }, { Type: DiffTypeAdded, Name: "MaxParallel", Old: "", New: "0", }, { Type: DiffTypeAdded, Name: "MinHealthyTime", Old: "", New: "0", }, { Type: DiffTypeAdded, Name: "ProgressDeadline", Old: "", New: "0", }, }, }, }, }, }, { TestCase: "Update strategy edited", Old: &TaskGroup{ Update: &UpdateStrategy{ MaxParallel: 5, HealthCheck: "foo", MinHealthyTime: 1 * time.Second, HealthyDeadline: 30 * time.Second, ProgressDeadline: 29 * time.Second, AutoRevert: true, AutoPromote: true, Canary: 2, }, }, New: &TaskGroup{ Update: &UpdateStrategy{ MaxParallel: 7, HealthCheck: "bar", MinHealthyTime: 2 * time.Second, HealthyDeadline: 31 * time.Second, ProgressDeadline: 32 * time.Second, AutoRevert: false, AutoPromote: false, Canary: 1, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Update", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "AutoPromote", Old: "true", New: "false", }, { Type: DiffTypeEdited, Name: "AutoRevert", Old: "true", New: "false", }, { Type: DiffTypeEdited, Name: "Canary", Old: "2", New: "1", }, { Type: DiffTypeEdited, Name: "HealthCheck", Old: "foo", New: "bar", }, { Type: DiffTypeEdited, Name: "HealthyDeadline", Old: "30000000000", New: "31000000000", }, { Type: DiffTypeEdited, Name: "MaxParallel", Old: "5", New: "7", }, { Type: DiffTypeEdited, Name: "MinHealthyTime", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeEdited, Name: "ProgressDeadline", Old: "29000000000", New: "32000000000", }, }, }, }, }, }, { TestCase: "Update strategy edited with context", Contextual: true, Old: &TaskGroup{ Update: &UpdateStrategy{ MaxParallel: 5, HealthCheck: "foo", MinHealthyTime: 1 * time.Second, HealthyDeadline: 30 * time.Second, ProgressDeadline: 30 * time.Second, AutoRevert: true, AutoPromote: true, Canary: 2, }, }, New: &TaskGroup{ Update: &UpdateStrategy{ MaxParallel: 7, HealthCheck: "foo", MinHealthyTime: 1 * time.Second, HealthyDeadline: 30 * time.Second, ProgressDeadline: 30 * time.Second, AutoRevert: true, AutoPromote: true, Canary: 2, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Update", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "AutoPromote", Old: "true", New: "true", }, { Type: DiffTypeNone, Name: "AutoRevert", Old: "true", New: "true", }, { Type: DiffTypeNone, Name: "Canary", Old: "2", New: "2", }, { Type: DiffTypeNone, Name: "HealthCheck", Old: "foo", New: "foo", }, { Type: DiffTypeNone, Name: "HealthyDeadline", Old: "30000000000", New: "30000000000", }, { Type: DiffTypeEdited, Name: "MaxParallel", Old: "5", New: "7", }, { Type: DiffTypeNone, Name: "MinHealthyTime", Old: "1000000000", New: "1000000000", }, { Type: DiffTypeNone, Name: "ProgressDeadline", Old: "30000000000", New: "30000000000", }, }, }, }, }, }, { TestCase: "EphemeralDisk added", Old: &TaskGroup{}, New: &TaskGroup{ EphemeralDisk: &EphemeralDisk{ Migrate: true, Sticky: true, SizeMB: 100, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "EphemeralDisk", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Migrate", Old: "", New: "true", }, { Type: DiffTypeAdded, Name: "SizeMB", Old: "", New: "100", }, { Type: DiffTypeAdded, Name: "Sticky", Old: "", New: "true", }, }, }, }, }, }, { TestCase: "EphemeralDisk deleted", Old: &TaskGroup{ EphemeralDisk: &EphemeralDisk{ Migrate: true, Sticky: true, SizeMB: 100, }, }, New: &TaskGroup{}, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "EphemeralDisk", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Migrate", Old: "true", New: "", }, { Type: DiffTypeDeleted, Name: "SizeMB", Old: "100", New: "", }, { Type: DiffTypeDeleted, Name: "Sticky", Old: "true", New: "", }, }, }, }, }, }, { TestCase: "EphemeralDisk edited", Old: &TaskGroup{ EphemeralDisk: &EphemeralDisk{ Migrate: true, Sticky: true, SizeMB: 150, }, }, New: &TaskGroup{ EphemeralDisk: &EphemeralDisk{ Migrate: false, Sticky: false, SizeMB: 90, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "EphemeralDisk", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Migrate", Old: "true", New: "false", }, { Type: DiffTypeEdited, Name: "SizeMB", Old: "150", New: "90", }, { Type: DiffTypeEdited, Name: "Sticky", Old: "true", New: "false", }, }, }, }, }, }, { TestCase: "EphemeralDisk edited with context", Contextual: true, Old: &TaskGroup{ EphemeralDisk: &EphemeralDisk{ Migrate: false, Sticky: false, SizeMB: 100, }, }, New: &TaskGroup{ EphemeralDisk: &EphemeralDisk{ Migrate: true, Sticky: true, SizeMB: 90, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "EphemeralDisk", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Migrate", Old: "false", New: "true", }, { Type: DiffTypeEdited, Name: "SizeMB", Old: "100", New: "90", }, { Type: DiffTypeEdited, Name: "Sticky", Old: "false", New: "true", }, }, }, }, }, }, { TestCase: "TaskGroup Services edited", Contextual: true, Old: &TaskGroup{ Services: []*Service{ { Name: "foo", Namespace: "team1", TaskName: "task1", EnableTagOverride: false, Checks: []*ServiceCheck{ { Name: "foo", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Method: "POST", Body: "{\"key\": \"value\"}", Expose: true, Interval: 1 * time.Second, Timeout: 1 * time.Second, SuccessBeforePassing: 3, FailuresBeforeCritical: 4, }, }, Connect: &ConsulConnect{ Native: false, SidecarTask: &SidecarTask{ Name: "sidecar", Driver: "docker", Env: map[string]string{ "FOO": "BAR", }, Config: map[string]interface{}{ "foo": "baz", }, }, Gateway: &ConsulGateway{ Proxy: &ConsulGatewayProxy{ ConnectTimeout: helper.TimeToPtr(1 * time.Second), EnvoyGatewayBindTaggedAddresses: false, EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{ "service1": { Address: "10.0.0.1", Port: 2001, }, }, EnvoyDNSDiscoveryType: "STRICT_DNS", EnvoyGatewayNoDefaultBind: false, Config: map[string]interface{}{ "foo": 1, }, }, Ingress: &ConsulIngressConfigEntry{ TLS: &ConsulGatewayTLSConfig{ Enabled: false, }, Listeners: []*ConsulIngressListener{{ Port: 3001, Protocol: "tcp", Services: []*ConsulIngressService{{ Name: "listener1", }}, }}, }, Terminating: &ConsulTerminatingConfigEntry{ Services: []*ConsulLinkedService{{ Name: "linked1", CAFile: "ca1.pem", CertFile: "cert1.pem", KeyFile: "key1.pem", SNI: "linked1.consul", }}, }, Mesh: &ConsulMeshConfigEntry{ // nothing }, }, }, }, }, }, New: &TaskGroup{ Services: []*Service{ { Name: "foo", Namespace: "team1", TaskName: "task2", EnableTagOverride: true, Checks: []*ServiceCheck{ { Name: "foo", Type: "tcp", Command: "bar", Path: "bar", Protocol: "tcp", Expose: false, Interval: 2 * time.Second, Timeout: 2 * time.Second, Header: map[string][]string{ "Foo": {"baz"}, }, SuccessBeforePassing: 5, FailuresBeforeCritical: 6, }, }, Connect: &ConsulConnect{ Native: true, SidecarService: &ConsulSidecarService{ Port: "http", Proxy: &ConsulProxy{ LocalServiceAddress: "127.0.0.1", LocalServicePort: 8080, Upstreams: []ConsulUpstream{ { DestinationName: "foo", LocalBindPort: 8000, Datacenter: "dc2", LocalBindAddress: "127.0.0.2", MeshGateway: &ConsulMeshGateway{ Mode: "remote", }, }, }, Config: map[string]interface{}{ "foo": "qux", }, }, }, Gateway: &ConsulGateway{ Proxy: &ConsulGatewayProxy{ ConnectTimeout: helper.TimeToPtr(2 * time.Second), EnvoyGatewayBindTaggedAddresses: true, EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{ "service1": { Address: "10.0.0.2", Port: 2002, }, }, EnvoyDNSDiscoveryType: "LOGICAL_DNS", EnvoyGatewayNoDefaultBind: true, Config: map[string]interface{}{ "foo": 2, }, }, Ingress: &ConsulIngressConfigEntry{ TLS: &ConsulGatewayTLSConfig{ Enabled: true, }, Listeners: []*ConsulIngressListener{{ Port: 3002, Protocol: "http", Services: []*ConsulIngressService{{ Name: "listener2", Hosts: []string{"127.0.0.1", "127.0.0.1:3002"}, }}, }}, }, Terminating: &ConsulTerminatingConfigEntry{ Services: []*ConsulLinkedService{{ Name: "linked2", CAFile: "ca2.pem", CertFile: "cert2.pem", KeyFile: "key2.pem", SNI: "linked2.consul", }}, }, Mesh: &ConsulMeshConfigEntry{ // nothing }, }, }, }, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Address", Old: "", New: "", }, { Type: DiffTypeNone, Name: "AddressMode", Old: "", New: "", }, { Type: DiffTypeEdited, Name: "EnableTagOverride", Old: "false", New: "true", }, { Type: DiffTypeNone, Name: "Name", Old: "foo", New: "foo", }, { Type: DiffTypeNone, Name: "Namespace", Old: "team1", New: "team1", }, { Type: DiffTypeNone, Name: "OnUpdate", Old: "", New: "", }, { Type: DiffTypeNone, Name: "PortLabel", Old: "", New: "", }, { Type: DiffTypeNone, Name: "Provider", Old: "", New: "", }, { Type: DiffTypeEdited, Name: "TaskName", Old: "task1", New: "task2", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Check", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "AddressMode", Old: "", New: "", }, { Type: DiffTypeDeleted, Name: "Body", Old: "{\"key\": \"value\"}", New: "", }, { Type: DiffTypeEdited, Name: "Command", Old: "foo", New: "bar", }, { Type: DiffTypeEdited, Name: "Expose", Old: "true", New: "false", }, { Type: DiffTypeEdited, Name: "FailuresBeforeCritical", Old: "4", New: "6", }, { Type: DiffTypeNone, Name: "GRPCService", Old: "", New: "", }, { Type: DiffTypeNone, Name: "GRPCUseTLS", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "InitialStatus", Old: "", New: "", }, { Type: DiffTypeEdited, Name: "Interval", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeDeleted, Name: "Method", Old: "POST", New: "", }, { Type: DiffTypeNone, Name: "Name", Old: "foo", New: "foo", }, { Type: DiffTypeNone, Name: "OnUpdate", Old: "", New: "", }, { Type: DiffTypeEdited, Name: "Path", Old: "foo", New: "bar", }, { Type: DiffTypeNone, Name: "PortLabel", Old: "", New: "", }, { Type: DiffTypeEdited, Name: "Protocol", Old: "http", New: "tcp", }, { Type: DiffTypeEdited, Name: "SuccessBeforePassing", Old: "3", New: "5", }, { Type: DiffTypeNone, Name: "TLSSkipVerify", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "TaskName", Old: "", New: "", }, { Type: DiffTypeEdited, Name: "Timeout", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeEdited, Name: "Type", Old: "http", New: "tcp", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Header", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Foo[0]", Old: "", New: "baz", }, }, }, }, }, { Type: DiffTypeEdited, Name: "ConsulConnect", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Native", Old: "false", New: "true", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "SidecarService", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "DisableDefaultTCPCheck", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "Port", Old: "", New: "http", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "ConsulProxy", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "LocalServiceAddress", Old: "", New: "127.0.0.1", }, { Type: DiffTypeAdded, Name: "LocalServicePort", Old: "", New: "8080", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "ConsulUpstreams", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Datacenter", Old: "", New: "dc2", }, { Type: DiffTypeAdded, Name: "DestinationName", Old: "", New: "foo", }, { Type: DiffTypeAdded, Name: "LocalBindAddress", Old: "", New: "127.0.0.2", }, { Type: DiffTypeAdded, Name: "LocalBindPort", Old: "", New: "8000", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "MeshGateway", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Mode", Old: "", New: "remote", }, }, }, }, }, { Type: DiffTypeAdded, Name: "Config", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "foo", Old: "", New: "qux", }, }, }, }, }, }, }, { Type: DiffTypeDeleted, Name: "SidecarTask", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Driver", Old: "docker", New: "", }, { Type: DiffTypeDeleted, Name: "Env[FOO]", Old: "BAR", New: "", }, { Type: DiffTypeDeleted, Name: "Name", Old: "sidecar", New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Config", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "foo", Old: "baz", New: "", }, }, }, }, }, { Type: DiffTypeEdited, Name: "Gateway", Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Proxy", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "ConnectTimeout", Old: "1s", New: "2s", }, { Type: DiffTypeEdited, Name: "EnvoyDNSDiscoveryType", Old: "STRICT_DNS", New: "LOGICAL_DNS", }, { Type: DiffTypeEdited, Name: "EnvoyGatewayBindTaggedAddresses", Old: "false", New: "true", }, { Type: DiffTypeEdited, Name: "EnvoyGatewayNoDefaultBind", Old: "false", New: "true", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "EnvoyGatewayBindAddresses", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "service1", Old: "10.0.0.1:2001", New: "10.0.0.2:2002", }, }, }, { Type: DiffTypeEdited, Name: "Config", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "foo", Old: "1", New: "2", }, }, }, }, }, { Type: DiffTypeEdited, Name: "Ingress", Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "TLS", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Enabled", Old: "false", New: "true", }, }, }, { Type: DiffTypeAdded, Name: "Listener", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Port", Old: "", New: "3002", }, { Type: DiffTypeAdded, Name: "Protocol", Old: "", New: "http", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "ConsulIngressService", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Name", Old: "", New: "listener2", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Hosts", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Hosts", Old: "", New: "127.0.0.1", }, { Type: DiffTypeAdded, Name: "Hosts", Old: "", New: "127.0.0.1:3002", }, }, }, }, }, }, }, { Type: DiffTypeDeleted, Name: "Listener", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Port", Old: "3001", New: "", }, { Type: DiffTypeDeleted, Name: "Protocol", Old: "tcp", New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "ConsulIngressService", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Name", Old: "listener1", New: "", }, }, }, }, }, }, }, { Type: DiffTypeEdited, Name: "Terminating", Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "CAFile", Old: "", New: "ca2.pem", }, { Type: DiffTypeAdded, Name: "CertFile", Old: "", New: "cert2.pem", }, { Type: DiffTypeAdded, Name: "KeyFile", Old: "", New: "key2.pem", }, { Type: DiffTypeAdded, Name: "Name", Old: "", New: "linked2", }, { Type: DiffTypeAdded, Name: "SNI", Old: "", New: "linked2.consul", }, }, }, { Type: DiffTypeDeleted, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "CAFile", Old: "ca1.pem", New: "", }, { Type: DiffTypeDeleted, Name: "CertFile", Old: "cert1.pem", New: "", }, { Type: DiffTypeDeleted, Name: "KeyFile", Old: "key1.pem", New: "", }, { Type: DiffTypeDeleted, Name: "Name", Old: "linked1", New: "", }, { Type: DiffTypeDeleted, Name: "SNI", Old: "linked1.consul", New: "", }, }, }, }, }, }, }, }, }, }, }, }, }, }, { TestCase: "TaskGroup Networks edited", Contextual: true, Old: &TaskGroup{ Networks: Networks{ { Device: "foo", CIDR: "foo", IP: "foo", MBits: 100, Hostname: "foo", ReservedPorts: []Port{ { Label: "foo", Value: 80, }, }, }, }, }, New: &TaskGroup{ Networks: Networks{ { Device: "bar", CIDR: "bar", IP: "bar", MBits: 200, Hostname: "bar", DynamicPorts: []Port{ { Label: "bar", To: 8081, HostNetwork: "public", }, }, DNS: &DNSConfig{ Servers: []string{"1.1.1.1"}, }, }, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Network", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Hostname", Old: "", New: "bar", }, { Type: DiffTypeAdded, Name: "MBits", Old: "", New: "200", }, { Type: DiffTypeNone, Name: "Mode", Old: "", New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Dynamic Port", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "HostNetwork", Old: "", New: "public", }, { Type: DiffTypeAdded, Name: "Label", Old: "", New: "bar", }, { Type: DiffTypeAdded, Name: "To", Old: "", New: "8081", }, }, }, { Type: DiffTypeAdded, Name: "DNS", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Servers", Old: "", New: "1.1.1.1", }, }, }, }, }, { Type: DiffTypeDeleted, Name: "Network", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Hostname", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "MBits", Old: "100", New: "", }, { Type: DiffTypeNone, Name: "Mode", Old: "", New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Static Port", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "HostNetwork", Old: "", New: "", }, { Type: DiffTypeDeleted, Name: "Label", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "To", Old: "0", New: "", }, { Type: DiffTypeDeleted, Name: "Value", Old: "80", New: "", }, }, }, }, }, }, }, }, { TestCase: "Tasks edited", Old: &TaskGroup{ Tasks: []*Task{ { Name: "foo", Driver: "docker", }, { Name: "bar", Driver: "docker", }, { Name: "baz", ShutdownDelay: 1 * time.Second, }, }, }, New: &TaskGroup{ Tasks: []*Task{ { Name: "bar", Driver: "docker", }, { Name: "bam", Driver: "docker", }, { Name: "baz", ShutdownDelay: 2 * time.Second, }, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Tasks: []*TaskDiff{ { Type: DiffTypeAdded, Name: "bam", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Driver", Old: "", New: "docker", }, { Type: DiffTypeAdded, Name: "KillTimeout", Old: "", New: "0", }, { Type: DiffTypeAdded, Name: "Leader", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "ShutdownDelay", Old: "", New: "0", }, }, }, { Type: DiffTypeNone, Name: "bar", }, { Type: DiffTypeEdited, Name: "baz", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "ShutdownDelay", Old: "1000000000", New: "2000000000", }, }, }, { Type: DiffTypeDeleted, Name: "foo", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Driver", Old: "docker", New: "", }, { Type: DiffTypeDeleted, Name: "KillTimeout", Old: "0", New: "", }, { Type: DiffTypeDeleted, Name: "Leader", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "ShutdownDelay", Old: "0", New: "", }, }, }, }, }, }, { TestCase: "TaskGroup shutdown_delay edited", Old: &TaskGroup{ ShutdownDelay: helper.TimeToPtr(30 * time.Second), }, New: &TaskGroup{ ShutdownDelay: helper.TimeToPtr(5 * time.Second), }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "ShutdownDelay", Old: "30000000000", New: "5000000000", }, }, }, }, { TestCase: "TaskGroup shutdown_delay removed", Old: &TaskGroup{ ShutdownDelay: helper.TimeToPtr(30 * time.Second), }, New: &TaskGroup{}, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "ShutdownDelay", Old: "30000000000", New: "", }, }, }, }, { TestCase: "TaskGroup shutdown_delay added", Old: &TaskGroup{}, New: &TaskGroup{ ShutdownDelay: helper.TimeToPtr(30 * time.Second), }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "ShutdownDelay", Old: "", New: "30000000000", }, }, }, }, { TestCase: "TaskGroup volumes added", Old: &TaskGroup{}, New: &TaskGroup{ Volumes: map[string]*VolumeRequest{ "foo": { Name: "foo", Type: "host", Source: "foo-src", ReadOnly: true, PerAlloc: true, }, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Volume", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Name", Old: "", New: "foo", }, { Type: DiffTypeAdded, Name: "PerAlloc", Old: "", New: "true", }, { Type: DiffTypeAdded, Name: "ReadOnly", Old: "", New: "true", }, { Type: DiffTypeAdded, Name: "Source", Old: "", New: "foo-src", }, { Type: DiffTypeAdded, Name: "Type", Old: "", New: "host", }, }, }, }, }, }, { TestCase: "TaskGroup volumes edited", Old: &TaskGroup{ Volumes: map[string]*VolumeRequest{ "foo": { Name: "foo", Type: "csi", Source: "foo-src1", ReadOnly: false, MountOptions: &CSIMountOptions{ FSType: "ext4", MountFlags: []string{"relatime", "rw"}, }, }, "bar": { Name: "bar", Type: "host", Source: "bar-src", ReadOnly: true, }, }, }, New: &TaskGroup{ Volumes: map[string]*VolumeRequest{ "foo": { Name: "foo", Type: "csi", Source: "foo-src2", ReadOnly: true, MountOptions: &CSIMountOptions{ FSType: "ext4", MountFlags: []string{"relatime", "rw", "nosuid"}, }, }, "bar": { // untouched Name: "bar", Type: "host", Source: "bar-src", ReadOnly: true, }, }, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Volume", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "ReadOnly", Old: "false", New: "true", }, { Type: DiffTypeEdited, Name: "Source", Old: "foo-src1", New: "foo-src2", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "MountOptions", Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "MountFlags", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "MountFlags", Old: "", New: "nosuid", }, }, }, }, }, }, }, }, }, }, { TestCase: "MaxClientDisconnect added", Old: &TaskGroup{ Name: "foo", MaxClientDisconnect: nil, }, New: &TaskGroup{ Name: "foo", MaxClientDisconnect: helper.TimeToPtr(20 * time.Second), }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Name: "foo", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "MaxClientDisconnect", Old: "", New: "20000000000", }, }, }, }, { TestCase: "MaxClientDisconnect updated", Old: &TaskGroup{ Name: "foo", MaxClientDisconnect: helper.TimeToPtr(10 * time.Second), }, New: &TaskGroup{ Name: "foo", MaxClientDisconnect: helper.TimeToPtr(20 * time.Second), }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Name: "foo", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "MaxClientDisconnect", Old: "10000000000", New: "20000000000", }, }, }, }, { TestCase: "MaxClientDisconnect deleted", Old: &TaskGroup{ Name: "foo", MaxClientDisconnect: helper.TimeToPtr(10 * time.Second), }, New: &TaskGroup{ Name: "foo", MaxClientDisconnect: nil, }, Expected: &TaskGroupDiff{ Type: DiffTypeEdited, Name: "foo", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "MaxClientDisconnect", Old: "10000000000", New: "", }, }, }, }, } for i, c := range cases { require.NotEmpty(t, c.TestCase, "case #%d needs a name", i+1) t.Run(c.TestCase, func(t *testing.T) { result, err := c.Old.Diff(c.New, c.Contextual) switch c.ExpErr { case true: require.Error(t, err, "case %q expected error", c.TestCase) case false: require.NoError(t, err, "case %q expected no error", c.TestCase) require.Equal(t, c.Expected, result) } }) } } func TestTaskDiff(t *testing.T) { ci.Parallel(t) cases := []struct { Name string Old, New *Task Expected *TaskDiff Error bool Contextual bool }{ { Name: "Empty", Old: nil, New: nil, Expected: &TaskDiff{ Type: DiffTypeNone, }, }, { Name: "Primitive only that has different names", Old: &Task{ Name: "foo", Meta: map[string]string{ "foo": "bar", }, }, New: &Task{ Name: "bar", Meta: map[string]string{ "foo": "bar", }, }, Error: true, }, { Name: "Primitive only that is the same", Old: &Task{ Name: "foo", Driver: "exec", User: "foo", Env: map[string]string{ "FOO": "bar", }, Meta: map[string]string{ "foo": "bar", }, KillTimeout: 1 * time.Second, Leader: true, }, New: &Task{ Name: "foo", Driver: "exec", User: "foo", Env: map[string]string{ "FOO": "bar", }, Meta: map[string]string{ "foo": "bar", }, KillTimeout: 1 * time.Second, Leader: true, }, Expected: &TaskDiff{ Type: DiffTypeNone, Name: "foo", }, }, { Name: "Primitive only that has diffs", Old: &Task{ Name: "foo", Driver: "exec", User: "foo", Env: map[string]string{ "FOO": "bar", }, Meta: map[string]string{ "foo": "bar", }, KillTimeout: 1 * time.Second, Leader: true, }, New: &Task{ Name: "foo", Driver: "docker", User: "bar", Env: map[string]string{ "FOO": "baz", }, Meta: map[string]string{ "foo": "baz", }, KillTimeout: 2 * time.Second, Leader: false, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Name: "foo", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Driver", Old: "exec", New: "docker", }, { Type: DiffTypeEdited, Name: "Env[FOO]", Old: "bar", New: "baz", }, { Type: DiffTypeEdited, Name: "KillTimeout", Old: "1000000000", New: "2000000000", }, { Type: DiffTypeEdited, Name: "Leader", Old: "true", New: "false", }, { Type: DiffTypeEdited, Name: "Meta[foo]", Old: "bar", New: "baz", }, { Type: DiffTypeEdited, Name: "User", Old: "foo", New: "bar", }, }, }, }, { Name: "Map diff", Old: &Task{ Meta: map[string]string{ "foo": "foo", "bar": "bar", }, Env: map[string]string{ "foo": "foo", "bar": "bar", }, }, New: &Task{ Meta: map[string]string{ "bar": "bar", "baz": "baz", }, Env: map[string]string{ "bar": "bar", "baz": "baz", }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Env[baz]", Old: "", New: "baz", }, { Type: DiffTypeDeleted, Name: "Env[foo]", Old: "foo", New: "", }, { Type: DiffTypeAdded, Name: "Meta[baz]", Old: "", New: "baz", }, { Type: DiffTypeDeleted, Name: "Meta[foo]", Old: "foo", New: "", }, }, }, }, { Name: "Constraints edited", Old: &Task{ Constraints: []*Constraint{ { LTarget: "foo", RTarget: "foo", Operand: "foo", }, { LTarget: "bar", RTarget: "bar", Operand: "bar", }, }, }, New: &Task{ Constraints: []*Constraint{ { LTarget: "foo", RTarget: "foo", Operand: "foo", }, { LTarget: "baz", RTarget: "baz", Operand: "baz", }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Constraint", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "LTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Operand", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "RTarget", Old: "", New: "baz", }, }, }, { Type: DiffTypeDeleted, Name: "Constraint", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "LTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Operand", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "RTarget", Old: "bar", New: "", }, }, }, }, }, }, { Name: "Affinities edited", Old: &Task{ Affinities: []*Affinity{ { LTarget: "foo", RTarget: "foo", Operand: "foo", Weight: 20, }, { LTarget: "bar", RTarget: "bar", Operand: "bar", Weight: 20, }, }, }, New: &Task{ Affinities: []*Affinity{ { LTarget: "foo", RTarget: "foo", Operand: "foo", Weight: 20, }, { LTarget: "baz", RTarget: "baz", Operand: "baz", Weight: 20, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Affinity", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "LTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Operand", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "RTarget", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "Weight", Old: "", New: "20", }, }, }, { Type: DiffTypeDeleted, Name: "Affinity", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "LTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Operand", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "RTarget", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Weight", Old: "20", New: "", }, }, }, }, }, }, { Name: "LogConfig added", Old: &Task{}, New: &Task{ LogConfig: &LogConfig{ MaxFiles: 1, MaxFileSizeMB: 10, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "LogConfig", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "MaxFileSizeMB", Old: "", New: "10", }, { Type: DiffTypeAdded, Name: "MaxFiles", Old: "", New: "1", }, }, }, }, }, }, { Name: "LogConfig deleted", Old: &Task{ LogConfig: &LogConfig{ MaxFiles: 1, MaxFileSizeMB: 10, }, }, New: &Task{}, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "LogConfig", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "MaxFileSizeMB", Old: "10", New: "", }, { Type: DiffTypeDeleted, Name: "MaxFiles", Old: "1", New: "", }, }, }, }, }, }, { Name: "LogConfig edited", Old: &Task{ LogConfig: &LogConfig{ MaxFiles: 1, MaxFileSizeMB: 10, }, }, New: &Task{ LogConfig: &LogConfig{ MaxFiles: 2, MaxFileSizeMB: 20, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "LogConfig", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "MaxFileSizeMB", Old: "10", New: "20", }, { Type: DiffTypeEdited, Name: "MaxFiles", Old: "1", New: "2", }, }, }, }, }, }, { Name: "LogConfig edited with context", Contextual: true, Old: &Task{ LogConfig: &LogConfig{ MaxFiles: 1, MaxFileSizeMB: 10, }, }, New: &Task{ LogConfig: &LogConfig{ MaxFiles: 1, MaxFileSizeMB: 20, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "LogConfig", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "MaxFileSizeMB", Old: "10", New: "20", }, { Type: DiffTypeNone, Name: "MaxFiles", Old: "1", New: "1", }, }, }, }, }, }, { Name: "Artifacts edited", Old: &Task{ Artifacts: []*TaskArtifact{ { GetterSource: "foo", GetterOptions: map[string]string{ "foo": "bar", }, RelativeDest: "foo", }, { GetterSource: "bar", GetterOptions: map[string]string{ "bar": "baz", }, GetterHeaders: map[string]string{ "User": "user1", }, GetterMode: "dir", RelativeDest: "bar", }, }, }, New: &Task{ Artifacts: []*TaskArtifact{ { GetterSource: "foo/bar", GetterOptions: map[string]string{ "foo": "bar", }, RelativeDest: "foo", }, { GetterSource: "bam", GetterOptions: map[string]string{ "bam": "baz", }, GetterHeaders: map[string]string{ "User": "user2", "User-Agent": "nomad", }, GetterMode: "file", RelativeDest: "bam", }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Artifact", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "GetterSource", Old: "foo", New: "foo/bar", }, }, }, { Type: DiffTypeAdded, Name: "Artifact", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "GetterHeaders[User-Agent]", Old: "", New: "nomad", }, { Type: DiffTypeAdded, Name: "GetterHeaders[User]", Old: "", New: "user2", }, { Type: DiffTypeAdded, Name: "GetterMode", Old: "", New: "file", }, { Type: DiffTypeAdded, Name: "GetterOptions[bam]", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "GetterSource", Old: "", New: "bam", }, { Type: DiffTypeAdded, Name: "RelativeDest", Old: "", New: "bam", }, }, }, { Type: DiffTypeDeleted, Name: "Artifact", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "GetterHeaders[User]", Old: "user1", New: "", }, { Type: DiffTypeDeleted, Name: "GetterMode", Old: "dir", New: "", }, { Type: DiffTypeDeleted, Name: "GetterOptions[bar]", Old: "baz", New: "", }, { Type: DiffTypeDeleted, Name: "GetterSource", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "RelativeDest", Old: "bar", New: "", }, }, }, }, }, }, { Name: "Resources edited (no networks)", Old: &Task{ Resources: &Resources{ CPU: 100, MemoryMB: 100, DiskMB: 100, }, }, New: &Task{ Resources: &Resources{ CPU: 200, MemoryMB: 200, DiskMB: 200, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Resources", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "CPU", Old: "100", New: "200", }, { Type: DiffTypeEdited, Name: "DiskMB", Old: "100", New: "200", }, { Type: DiffTypeEdited, Name: "MemoryMB", Old: "100", New: "200", }, }, }, }, }, }, { Name: "Resources edited (no networks) with context", Contextual: true, Old: &Task{ Resources: &Resources{ CPU: 100, MemoryMB: 100, DiskMB: 100, }, }, New: &Task{ Resources: &Resources{ CPU: 200, MemoryMB: 100, DiskMB: 200, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Resources", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "CPU", Old: "100", New: "200", }, { Type: DiffTypeNone, Name: "Cores", Old: "0", New: "0", }, { Type: DiffTypeEdited, Name: "DiskMB", Old: "100", New: "200", }, { Type: DiffTypeNone, Name: "IOPS", Old: "0", New: "0", }, { Type: DiffTypeNone, Name: "MemoryMB", Old: "100", New: "100", }, { Type: DiffTypeNone, Name: "MemoryMaxMB", Old: "0", New: "0", }, }, }, }, }, }, { Name: "Resources edited memory_max", Old: &Task{ Resources: &Resources{ CPU: 100, MemoryMB: 100, MemoryMaxMB: 200, DiskMB: 100, }, }, New: &Task{ Resources: &Resources{ CPU: 100, MemoryMB: 100, MemoryMaxMB: 300, DiskMB: 100, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Resources", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "MemoryMaxMB", Old: "200", New: "300", }, }, }, }, }, }, { Name: "Resources edited memory_max with context", Contextual: true, Old: &Task{ Resources: &Resources{ CPU: 100, MemoryMB: 100, MemoryMaxMB: 200, DiskMB: 100, }, }, New: &Task{ Resources: &Resources{ CPU: 100, MemoryMB: 100, MemoryMaxMB: 300, DiskMB: 100, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Resources", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "CPU", Old: "100", New: "100", }, { Type: DiffTypeNone, Name: "Cores", Old: "0", New: "0", }, { Type: DiffTypeNone, Name: "DiskMB", Old: "100", New: "100", }, { Type: DiffTypeNone, Name: "IOPS", Old: "0", New: "0", }, { Type: DiffTypeNone, Name: "MemoryMB", Old: "100", New: "100", }, { Type: DiffTypeEdited, Name: "MemoryMaxMB", Old: "200", New: "300", }, }, }, }, }, }, { Name: "Network Resources edited", Old: &Task{ Resources: &Resources{ Networks: []*NetworkResource{ { Device: "foo", CIDR: "foo", IP: "foo", MBits: 100, ReservedPorts: []Port{ { Label: "foo", Value: 80, }, }, DynamicPorts: []Port{ { Label: "bar", To: 8080, }, }, }, }, }, }, New: &Task{ Resources: &Resources{ Networks: []*NetworkResource{ { Device: "bar", CIDR: "bar", IP: "bar", MBits: 200, ReservedPorts: []Port{ { Label: "foo", Value: 81, }, }, DynamicPorts: []Port{ { Label: "baz", To: 8081, }, }, }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Resources", Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Network", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "MBits", Old: "", New: "200", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Static Port", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Label", Old: "", New: "foo", }, { Type: DiffTypeAdded, Name: "To", Old: "", New: "0", }, { Type: DiffTypeAdded, Name: "Value", Old: "", New: "81", }, }, }, { Type: DiffTypeAdded, Name: "Dynamic Port", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Label", Old: "", New: "baz", }, { Type: DiffTypeAdded, Name: "To", Old: "", New: "8081", }, }, }, }, }, { Type: DiffTypeDeleted, Name: "Network", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "MBits", Old: "100", New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Static Port", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Label", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "To", Old: "0", New: "", }, { Type: DiffTypeDeleted, Name: "Value", Old: "80", New: "", }, }, }, { Type: DiffTypeDeleted, Name: "Dynamic Port", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Label", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "To", Old: "8080", New: "", }, }, }, }, }, }, }, }, }, }, { Name: "Device Resources edited", Old: &Task{ Resources: &Resources{ Devices: []*RequestedDevice{ { Name: "foo", Count: 2, }, { Name: "bar", Count: 2, }, { Name: "baz", Count: 2, }, }, }, }, New: &Task{ Resources: &Resources{ Devices: []*RequestedDevice{ { Name: "foo", Count: 2, }, { Name: "bar", Count: 3, }, { Name: "bam", Count: 2, }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Resources", Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Device", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Count", Old: "2", New: "3", }, }, }, { Type: DiffTypeAdded, Name: "Device", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Count", Old: "", New: "2", }, { Type: DiffTypeAdded, Name: "Name", Old: "", New: "bam", }, }, }, { Type: DiffTypeDeleted, Name: "Device", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Count", Old: "2", New: "", }, { Type: DiffTypeDeleted, Name: "Name", Old: "baz", New: "", }, }, }, }, }, }, }, }, { Name: "Device Resources edited with context", Contextual: true, Old: &Task{ Resources: &Resources{ CPU: 100, MemoryMB: 100, DiskMB: 100, Devices: []*RequestedDevice{ { Name: "foo", Count: 2, }, { Name: "bar", Count: 2, }, { Name: "baz", Count: 2, }, }, }, }, New: &Task{ Resources: &Resources{ CPU: 100, MemoryMB: 100, DiskMB: 100, Devices: []*RequestedDevice{ { Name: "foo", Count: 2, }, { Name: "bar", Count: 3, }, { Name: "bam", Count: 2, }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Resources", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "CPU", Old: "100", New: "100", }, { Type: DiffTypeNone, Name: "Cores", Old: "0", New: "0", }, { Type: DiffTypeNone, Name: "DiskMB", Old: "100", New: "100", }, { Type: DiffTypeNone, Name: "IOPS", Old: "0", New: "0", }, { Type: DiffTypeNone, Name: "MemoryMB", Old: "100", New: "100", }, { Type: DiffTypeNone, Name: "MemoryMaxMB", Old: "0", New: "0", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Device", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Count", Old: "2", New: "3", }, { Type: DiffTypeNone, Name: "Name", Old: "bar", New: "bar", }, }, }, { Type: DiffTypeAdded, Name: "Device", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Count", Old: "", New: "2", }, { Type: DiffTypeAdded, Name: "Name", Old: "", New: "bam", }, }, }, { Type: DiffTypeDeleted, Name: "Device", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Count", Old: "2", New: "", }, { Type: DiffTypeDeleted, Name: "Name", Old: "baz", New: "", }, }, }, }, }, }, }, }, { Name: "Config same", Old: &Task{ Config: map[string]interface{}{ "foo": 1, "bar": "bar", "bam": []string{"a", "b"}, "baz": map[string]int{ "a": 1, "b": 2, }, "boom": &Port{ Label: "boom_port", }, }, }, New: &Task{ Config: map[string]interface{}{ "foo": 1, "bar": "bar", "bam": []string{"a", "b"}, "baz": map[string]int{ "a": 1, "b": 2, }, "boom": &Port{ Label: "boom_port", }, }, }, Expected: &TaskDiff{ Type: DiffTypeNone, }, }, { Name: "Config edited", Old: &Task{ Config: map[string]interface{}{ "foo": 1, "bar": "baz", "bam": []string{"a", "b"}, "baz": map[string]int{ "a": 1, "b": 2, }, "boom": &Port{ Label: "boom_port", }, }, }, New: &Task{ Config: map[string]interface{}{ "foo": 2, "bar": "baz", "bam": []string{"a", "c", "d"}, "baz": map[string]int{ "b": 3, "c": 4, }, "boom": &Port{ Label: "boom_port2", }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Config", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "bam[1]", Old: "b", New: "c", }, { Type: DiffTypeAdded, Name: "bam[2]", Old: "", New: "d", }, { Type: DiffTypeDeleted, Name: "baz[a]", Old: "1", New: "", }, { Type: DiffTypeEdited, Name: "baz[b]", Old: "2", New: "3", }, { Type: DiffTypeAdded, Name: "baz[c]", Old: "", New: "4", }, { Type: DiffTypeEdited, Name: "boom.Label", Old: "boom_port", New: "boom_port2", }, { Type: DiffTypeEdited, Name: "foo", Old: "1", New: "2", }, }, }, }, }, }, { Name: "Config edited with context", Contextual: true, Old: &Task{ Config: map[string]interface{}{ "foo": 1, "bar": "baz", "bam": []string{"a", "b"}, "baz": map[string]int{ "a": 1, "b": 2, }, "boom": &Port{ Label: "boom_port", }, }, }, New: &Task{ Config: map[string]interface{}{ "foo": 2, "bar": "baz", "bam": []string{"a", "c", "d"}, "baz": map[string]int{ "a": 1, "b": 2, }, "boom": &Port{ Label: "boom_port", }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Config", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "bam[0]", Old: "a", New: "a", }, { Type: DiffTypeEdited, Name: "bam[1]", Old: "b", New: "c", }, { Type: DiffTypeAdded, Name: "bam[2]", Old: "", New: "d", }, { Type: DiffTypeNone, Name: "bar", Old: "baz", New: "baz", }, { Type: DiffTypeNone, Name: "baz[a]", Old: "1", New: "1", }, { Type: DiffTypeNone, Name: "baz[b]", Old: "2", New: "2", }, { Type: DiffTypeNone, Name: "boom.HostNetwork", Old: "", New: "", }, { Type: DiffTypeNone, Name: "boom.Label", Old: "boom_port", New: "boom_port", }, { Type: DiffTypeNone, Name: "boom.To", Old: "0", New: "0", }, { Type: DiffTypeNone, Name: "boom.Value", Old: "0", New: "0", }, { Type: DiffTypeEdited, Name: "foo", Old: "1", New: "2", }, }, }, }, }, }, { Name: "Services edited (no checks)", Old: &Task{ Services: []*Service{ { Name: "foo", PortLabel: "foo", }, { Name: "bar", PortLabel: "bar", }, { Name: "baz", PortLabel: "baz", }, }, }, New: &Task{ Services: []*Service{ { Name: "bar", PortLabel: "bar", }, { Name: "baz", PortLabel: "baz2", }, { Name: "bam", PortLabel: "bam", }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "PortLabel", Old: "baz", New: "baz2", }, }, }, { Type: DiffTypeAdded, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "EnableTagOverride", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "Name", Old: "", New: "bam", }, { Type: DiffTypeAdded, Name: "PortLabel", Old: "", New: "bam", }, }, }, { Type: DiffTypeDeleted, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "EnableTagOverride", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "Name", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "PortLabel", Old: "foo", New: "", }, }, }, }, }, }, { Name: "Services edited (no checks) with context", Contextual: true, Old: &Task{ Services: []*Service{ { Name: "foo", PortLabel: "foo", }, }, }, New: &Task{ Services: []*Service{ { Name: "foo", PortLabel: "bar", AddressMode: "driver", Address: "a.example.com", TaskName: "task1", }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Address", Old: "", New: "a.example.com", }, { Type: DiffTypeAdded, Name: "AddressMode", Old: "", New: "driver", }, { Type: DiffTypeNone, Name: "EnableTagOverride", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "Name", Old: "foo", New: "foo", }, { Type: DiffTypeNone, Name: "Namespace", Old: "", New: "", }, { Type: DiffTypeNone, Name: "OnUpdate", Old: "", New: "", }, { Type: DiffTypeEdited, Name: "PortLabel", Old: "foo", New: "bar", }, { Type: DiffTypeNone, Name: "Provider", }, { Type: DiffTypeAdded, Name: "TaskName", Old: "", New: "task1", }, }, }, }, }, }, { Name: "Service EnableTagOverride edited no context", Contextual: false, Old: &Task{ Services: []*Service{{ EnableTagOverride: false, }}, }, New: &Task{ Services: []*Service{{ EnableTagOverride: true, }}, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "EnableTagOverride", Old: "false", New: "true", }, }, }, }, }, }, { Name: "Services tags edited (no checks) with context", Contextual: true, Old: &Task{ Services: []*Service{ { Tags: []string{"foo", "bar"}, CanaryTags: []string{"foo", "bar"}, }, }, }, New: &Task{ Services: []*Service{ { Tags: []string{"bar", "bam"}, CanaryTags: []string{"bar", "bam"}, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "CanaryTags", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "CanaryTags", Old: "", New: "bam", }, { Type: DiffTypeNone, Name: "CanaryTags", Old: "bar", New: "bar", }, { Type: DiffTypeDeleted, Name: "CanaryTags", Old: "foo", New: "", }, }, }, { Type: DiffTypeEdited, Name: "Tags", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Tags", Old: "", New: "bam", }, { Type: DiffTypeNone, Name: "Tags", Old: "bar", New: "bar", }, { Type: DiffTypeDeleted, Name: "Tags", Old: "foo", New: "", }, }, }, }, Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Address", }, { Type: DiffTypeNone, Name: "AddressMode", }, { Type: DiffTypeNone, Name: "EnableTagOverride", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "Name", }, { Type: DiffTypeNone, Name: "Namespace", }, { Type: DiffTypeNone, Name: "OnUpdate", }, { Type: DiffTypeNone, Name: "PortLabel", }, { Type: DiffTypeNone, Name: "Provider", }, { Type: DiffTypeNone, Name: "TaskName", }, }, }, }, }, }, { Name: "Service with Connect", Old: &Task{ Services: []*Service{ { Name: "foo", }, }, }, New: &Task{ Services: []*Service{ { Name: "foo", Connect: &ConsulConnect{ SidecarService: &ConsulSidecarService{}, }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "ConsulConnect", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Native", Old: "", New: "false", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "SidecarService", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "DisableDefaultTCPCheck", Old: "", New: "false", }, }, }, }, }, }, }, }, }, }, { Name: "Service with Connect Native", Old: &Task{ Services: []*Service{ { Name: "foo", }, }, }, New: &Task{ Services: []*Service{ { Name: "foo", TaskName: "task1", Connect: &ConsulConnect{ Native: true, }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "TaskName", Old: "", New: "task1", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "ConsulConnect", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Native", Old: "", New: "true", }, }, }, }, }, }, }, }, { Name: "Service Checks edited", Old: &Task{ Services: []*Service{ { Name: "foo", Checks: []*ServiceCheck{ { Name: "foo", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, Header: map[string][]string{ "Foo": {"bar"}, }, SuccessBeforePassing: 1, FailuresBeforeCritical: 1, }, { Name: "bar", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, SuccessBeforePassing: 7, FailuresBeforeCritical: 7, }, { Name: "baz", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, }, }, }, }, }, New: &Task{ Services: []*Service{ { Name: "foo", Checks: []*ServiceCheck{ { Name: "bar", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, SuccessBeforePassing: 7, FailuresBeforeCritical: 7, }, { Name: "baz", Type: "tcp", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, Header: map[string][]string{ "Eggs": {"spam"}, }, }, { Name: "bam", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, SuccessBeforePassing: 2, FailuresBeforeCritical: 2, }, }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Check", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Type", Old: "http", New: "tcp", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Header", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Eggs[0]", Old: "", New: "spam", }, }, }, }, }, { Type: DiffTypeAdded, Name: "Check", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Command", Old: "", New: "foo", }, { Type: DiffTypeAdded, Name: "Expose", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "FailuresBeforeCritical", Old: "", New: "2", }, { Type: DiffTypeAdded, Name: "GRPCUseTLS", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "Interval", Old: "", New: "1000000000", }, { Type: DiffTypeAdded, Name: "Name", Old: "", New: "bam", }, { Type: DiffTypeAdded, Name: "Path", Old: "", New: "foo", }, { Type: DiffTypeAdded, Name: "Protocol", Old: "", New: "http", }, { Type: DiffTypeAdded, Name: "SuccessBeforePassing", Old: "", New: "2", }, { Type: DiffTypeAdded, Name: "TLSSkipVerify", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "Timeout", Old: "", New: "1000000000", }, { Type: DiffTypeAdded, Name: "Type", Old: "", New: "http", }, }, }, { Type: DiffTypeDeleted, Name: "Check", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Command", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "Expose", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "FailuresBeforeCritical", Old: "1", New: "", }, { Type: DiffTypeDeleted, Name: "GRPCUseTLS", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "Interval", Old: "1000000000", New: "", }, { Type: DiffTypeDeleted, Name: "Name", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "Path", Old: "foo", New: "", }, { Type: DiffTypeDeleted, Name: "Protocol", Old: "http", New: "", }, { Type: DiffTypeDeleted, Name: "SuccessBeforePassing", Old: "1", New: "", }, { Type: DiffTypeDeleted, Name: "TLSSkipVerify", Old: "false", New: "", }, { Type: DiffTypeDeleted, Name: "Timeout", Old: "1000000000", New: "", }, { Type: DiffTypeDeleted, Name: "Type", Old: "http", New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Header", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Foo[0]", Old: "bar", }, }, }, }, }, }, }, }, }, }, { Name: "Service Checks edited with context", Contextual: true, Old: &Task{ Services: []*Service{ { Name: "foo", Checks: []*ServiceCheck{ { Name: "foo", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, InitialStatus: "critical", Header: map[string][]string{ "Foo": {"bar"}, }, SuccessBeforePassing: 4, FailuresBeforeCritical: 5, OnUpdate: "require_healthy", }, }, }, }, }, New: &Task{ Services: []*Service{ { Name: "foo", Checks: []*ServiceCheck{ { Name: "foo", Type: "tcp", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, InitialStatus: "passing", Method: "POST", Header: map[string][]string{ "Foo": {"bar", "baz"}, "Eggs": {"spam"}, }, SuccessBeforePassing: 4, OnUpdate: "ignore_warnings", }, }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Address", Old: "", New: "", }, { Type: DiffTypeNone, Name: "AddressMode", Old: "", New: "", }, { Type: DiffTypeNone, Name: "EnableTagOverride", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "Name", Old: "foo", New: "foo", }, { Type: DiffTypeNone, Name: "Namespace", Old: "", New: "", }, { Type: DiffTypeNone, Name: "OnUpdate", }, { Type: DiffTypeNone, Name: "PortLabel", Old: "", New: "", }, { Type: DiffTypeNone, Name: "Provider", }, { Type: DiffTypeNone, Name: "TaskName", Old: "", New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Check", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "AddressMode", Old: "", New: "", }, { Type: DiffTypeNone, Name: "Body", Old: "", New: "", }, { Type: DiffTypeNone, Name: "Command", Old: "foo", New: "foo", }, { Type: DiffTypeNone, Name: "Expose", Old: "false", New: "false", }, { Type: DiffTypeEdited, Name: "FailuresBeforeCritical", Old: "5", New: "0", }, { Type: DiffTypeNone, Name: "GRPCService", Old: "", New: "", }, { Type: DiffTypeNone, Name: "GRPCUseTLS", Old: "false", New: "false", }, { Type: DiffTypeEdited, Name: "InitialStatus", Old: "critical", New: "passing", }, { Type: DiffTypeNone, Name: "Interval", Old: "1000000000", New: "1000000000", }, { Type: DiffTypeAdded, Name: "Method", Old: "", New: "POST", }, { Type: DiffTypeNone, Name: "Name", Old: "foo", New: "foo", }, { Type: DiffTypeEdited, Name: "OnUpdate", Old: "require_healthy", New: "ignore_warnings", }, { Type: DiffTypeNone, Name: "Path", Old: "foo", New: "foo", }, { Type: DiffTypeNone, Name: "PortLabel", Old: "", New: "", }, { Type: DiffTypeNone, Name: "Protocol", Old: "http", New: "http", }, { Type: DiffTypeNone, Name: "SuccessBeforePassing", Old: "4", New: "4", }, { Type: DiffTypeNone, Name: "TLSSkipVerify", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "TaskName", Old: "", New: "", }, { Type: DiffTypeNone, Name: "Timeout", Old: "1000000000", New: "1000000000", }, { Type: DiffTypeEdited, Name: "Type", Old: "http", New: "tcp", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Header", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Eggs[0]", Old: "", New: "spam", }, { Type: DiffTypeNone, Name: "Foo[0]", Old: "bar", New: "bar", }, { Type: DiffTypeAdded, Name: "Foo[1]", Old: "", New: "baz", }, }, }, }, }, }, }, }, }, }, { Name: "CheckRestart edited", Old: &Task{ Services: []*Service{ { Name: "foo", Checks: []*ServiceCheck{ { Name: "foo", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, }, { Name: "bar", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, CheckRestart: &CheckRestart{ Limit: 2, Grace: 2 * time.Second, }, }, { Name: "baz", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, CheckRestart: &CheckRestart{ Limit: 3, Grace: 3 * time.Second, }, }, }, }, }, }, New: &Task{ Services: []*Service{ { Name: "foo", Checks: []*ServiceCheck{ { Name: "foo", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, CheckRestart: &CheckRestart{ Limit: 1, Grace: 1 * time.Second, }, }, { Name: "bar", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, }, { Name: "baz", Type: "http", Command: "foo", Args: []string{"foo"}, Path: "foo", Protocol: "http", Interval: 1 * time.Second, Timeout: 1 * time.Second, CheckRestart: &CheckRestart{ Limit: 4, Grace: 4 * time.Second, }, }, }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Check", Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "CheckRestart", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Grace", Old: "3000000000", New: "4000000000", }, { Type: DiffTypeEdited, Name: "Limit", Old: "3", New: "4", }, }, }, }, }, { Type: DiffTypeEdited, Name: "Check", Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "CheckRestart", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Grace", New: "1000000000", }, { Type: DiffTypeAdded, Name: "IgnoreWarnings", New: "false", }, { Type: DiffTypeAdded, Name: "Limit", New: "1", }, }, }, }, }, { Type: DiffTypeEdited, Name: "Check", Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "CheckRestart", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Grace", Old: "2000000000", }, { Type: DiffTypeDeleted, Name: "IgnoreWarnings", Old: "false", }, { Type: DiffTypeDeleted, Name: "Limit", Old: "2", }, }, }, }, }, }, }, }, }, }, { Name: "Vault added", Old: &Task{}, New: &Task{ Vault: &Vault{ Policies: []string{"foo", "bar"}, Env: true, ChangeMode: "signal", ChangeSignal: "SIGUSR1", }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Vault", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "ChangeMode", Old: "", New: "signal", }, { Type: DiffTypeAdded, Name: "ChangeSignal", Old: "", New: "SIGUSR1", }, { Type: DiffTypeAdded, Name: "Env", Old: "", New: "true", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Policies", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Policies", Old: "", New: "bar", }, { Type: DiffTypeAdded, Name: "Policies", Old: "", New: "foo", }, }, }, }, }, }, }, }, { Name: "Vault deleted", Old: &Task{ Vault: &Vault{ Policies: []string{"foo", "bar"}, Env: true, ChangeMode: "signal", ChangeSignal: "SIGUSR1", }, }, New: &Task{}, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Vault", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "ChangeMode", Old: "signal", New: "", }, { Type: DiffTypeDeleted, Name: "ChangeSignal", Old: "SIGUSR1", New: "", }, { Type: DiffTypeDeleted, Name: "Env", Old: "true", New: "", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "Policies", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "Policies", Old: "bar", New: "", }, { Type: DiffTypeDeleted, Name: "Policies", Old: "foo", New: "", }, }, }, }, }, }, }, }, { Name: "Vault edited", Old: &Task{ Vault: &Vault{ Namespace: "ns1", Policies: []string{"foo", "bar"}, Env: true, ChangeMode: "signal", ChangeSignal: "SIGUSR1", }, }, New: &Task{ Vault: &Vault{ Namespace: "ns2", Policies: []string{"bar", "baz"}, Env: false, ChangeMode: "restart", ChangeSignal: "foo", }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Vault", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "ChangeMode", Old: "signal", New: "restart", }, { Type: DiffTypeEdited, Name: "ChangeSignal", Old: "SIGUSR1", New: "foo", }, { Type: DiffTypeEdited, Name: "Env", Old: "true", New: "false", }, { Type: DiffTypeEdited, Name: "Namespace", Old: "ns1", New: "ns2", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Policies", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Policies", Old: "", New: "baz", }, { Type: DiffTypeDeleted, Name: "Policies", Old: "foo", New: "", }, }, }, }, }, }, }, }, { Name: "Vault edited with context", Contextual: true, Old: &Task{ Vault: &Vault{ Namespace: "ns1", Policies: []string{"foo", "bar"}, Env: true, ChangeMode: "signal", ChangeSignal: "SIGUSR1", }, }, New: &Task{ Vault: &Vault{ Namespace: "ns1", Policies: []string{"bar", "baz"}, Env: true, ChangeMode: "signal", ChangeSignal: "SIGUSR1", }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Vault", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "ChangeMode", Old: "signal", New: "signal", }, { Type: DiffTypeNone, Name: "ChangeSignal", Old: "SIGUSR1", New: "SIGUSR1", }, { Type: DiffTypeNone, Name: "Env", Old: "true", New: "true", }, { Type: DiffTypeNone, Name: "Namespace", Old: "ns1", New: "ns1", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Policies", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Policies", Old: "", New: "baz", }, { Type: DiffTypeNone, Name: "Policies", Old: "bar", New: "bar", }, { Type: DiffTypeDeleted, Name: "Policies", Old: "foo", New: "", }, }, }, }, }, }, }, }, { Name: "Template edited", Old: &Task{ Templates: []*Template{ { SourcePath: "foo", DestPath: "bar", EmbeddedTmpl: "baz", ChangeMode: "bam", ChangeSignal: "SIGHUP", Splay: 1, Perms: "0644", Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(5 * time.Second), }, }, { SourcePath: "foo2", DestPath: "bar2", EmbeddedTmpl: "baz2", ChangeMode: "bam2", ChangeSignal: "SIGHUP2", Splay: 2, Perms: "0666", Envvars: true, }, }, }, New: &Task{ Templates: []*Template{ { SourcePath: "foo", DestPath: "bar", EmbeddedTmpl: "baz new", ChangeMode: "bam", ChangeSignal: "SIGHUP", Splay: 1, Perms: "0644", Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(10 * time.Second), }, }, { SourcePath: "foo3", DestPath: "bar3", EmbeddedTmpl: "baz3", ChangeMode: "bam3", ChangeSignal: "SIGHUP3", Splay: 3, Perms: "0776", Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(10 * time.Second), }, }, }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Template", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "EmbeddedTmpl", Old: "baz", New: "baz new", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Template", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Max", Old: "5000000000", New: "10000000000", }, }, }, }, }, { Type: DiffTypeAdded, Name: "Template", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "ChangeMode", Old: "", New: "bam3", }, { Type: DiffTypeAdded, Name: "ChangeSignal", Old: "", New: "SIGHUP3", }, { Type: DiffTypeAdded, Name: "DestPath", Old: "", New: "bar3", }, { Type: DiffTypeAdded, Name: "EmbeddedTmpl", Old: "", New: "baz3", }, { Type: DiffTypeAdded, Name: "Envvars", Old: "", New: "false", }, { Type: DiffTypeAdded, Name: "Perms", Old: "", New: "0776", }, { Type: DiffTypeAdded, Name: "SourcePath", Old: "", New: "foo3", }, { Type: DiffTypeAdded, Name: "Splay", Old: "", New: "3", }, { Type: DiffTypeAdded, Name: "VaultGrace", Old: "", New: "0", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Template", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Max", Old: "", New: "10000000000", }, { Type: DiffTypeAdded, Name: "Min", Old: "", New: "5000000000", }, }, }, }, }, { Type: DiffTypeDeleted, Name: "Template", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "ChangeMode", Old: "bam2", New: "", }, { Type: DiffTypeDeleted, Name: "ChangeSignal", Old: "SIGHUP2", New: "", }, { Type: DiffTypeDeleted, Name: "DestPath", Old: "bar2", New: "", }, { Type: DiffTypeDeleted, Name: "EmbeddedTmpl", Old: "baz2", New: "", }, { Type: DiffTypeDeleted, Name: "Envvars", Old: "true", New: "", }, { Type: DiffTypeDeleted, Name: "Perms", Old: "0666", New: "", }, { Type: DiffTypeDeleted, Name: "SourcePath", Old: "foo2", New: "", }, { Type: DiffTypeDeleted, Name: "Splay", Old: "2", New: "", }, { Type: DiffTypeDeleted, Name: "VaultGrace", Old: "0", New: "", }, }, }, }, }, }, { Name: "DispatchPayload added", Old: &Task{}, New: &Task{ DispatchPayload: &DispatchPayloadConfig{ File: "foo", }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "DispatchPayload", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "File", Old: "", New: "foo", }, }, }, }, }, }, { Name: "DispatchPayload deleted", Old: &Task{ DispatchPayload: &DispatchPayloadConfig{ File: "foo", }, }, New: &Task{}, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeDeleted, Name: "DispatchPayload", Fields: []*FieldDiff{ { Type: DiffTypeDeleted, Name: "File", Old: "foo", New: "", }, }, }, }, }, }, { Name: "Dispatch payload edited", Old: &Task{ DispatchPayload: &DispatchPayloadConfig{ File: "foo", }, }, New: &Task{ DispatchPayload: &DispatchPayloadConfig{ File: "bar", }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "DispatchPayload", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "File", Old: "foo", New: "bar", }, }, }, }, }, }, { // Place holder for if more fields are added Name: "DispatchPayload edited with context", Contextual: true, Old: &Task{ DispatchPayload: &DispatchPayloadConfig{ File: "foo", }, }, New: &Task{ DispatchPayload: &DispatchPayloadConfig{ File: "bar", }, }, Expected: &TaskDiff{ Type: DiffTypeEdited, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "DispatchPayload", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "File", Old: "foo", New: "bar", }, }, }, }, }, }, } for _, c := range cases { t.Run(c.Name, func(t *testing.T) { actual, err := c.Old.Diff(c.New, c.Contextual) if c.Error { require.Error(t, err) } else { require.NoError(t, err) require.Equal(t, c.Expected, actual) } }) } } func TestServicesDiff(t *testing.T) { ci.Parallel(t) cases := []struct { Name string Old, New []*Service Expected []*ObjectDiff Contextual bool }{ { Name: "No changes - empty", Contextual: true, Old: []*Service{}, New: []*Service{}, Expected: nil, }, { Name: "No changes", Contextual: true, Old: []*Service{ { Name: "webapp", PortLabel: "http", }, }, New: []*Service{ { Name: "webapp", PortLabel: "http", }, }, Expected: nil, }, { Name: "Detect changes", Contextual: true, Old: []*Service{ { Name: "webapp", PortLabel: "http", AddressMode: "host", Address: "a.example.com", EnableTagOverride: true, Tags: []string{"prod"}, CanaryTags: []string{"canary"}, }, }, New: []*Service{ { Name: "webapp-2", PortLabel: "https", AddressMode: "alloc", Address: "b.example.com", EnableTagOverride: false, Tags: []string{"prod", "dev"}, CanaryTags: []string{"qa"}, }, }, Expected: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeEdited, Name: "Address", Old: "a.example.com", New: "b.example.com", }, { Type: DiffTypeEdited, Name: "AddressMode", Old: "host", New: "alloc", }, { Type: DiffTypeEdited, Name: "EnableTagOverride", Old: "true", New: "false", }, { Type: DiffTypeEdited, Name: "Name", Old: "webapp", New: "webapp-2", }, { Type: DiffTypeNone, Name: "Namespace", }, { Type: DiffTypeNone, Name: "OnUpdate", }, { Type: DiffTypeEdited, Name: "PortLabel", Old: "http", New: "https", }, { Type: DiffTypeNone, Name: "Provider", }, { Type: DiffTypeNone, Name: "TaskName", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "CanaryTags", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "CanaryTags", New: "qa", }, { Type: DiffTypeDeleted, Name: "CanaryTags", Old: "canary", }, }, }, { Type: DiffTypeAdded, Name: "Tags", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Tags", New: "dev", }, { Type: DiffTypeNone, Name: "Tags", Old: "prod", New: "prod", }, }, }, }, }, }, }, { Name: "Service added", Contextual: true, Old: []*Service{}, New: []*Service{ { Name: "webapp", PortLabel: "http", }, }, Expected: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Address", }, { Type: DiffTypeNone, Name: "AddressMode", }, { Type: DiffTypeAdded, Name: "EnableTagOverride", New: "false", }, { Type: DiffTypeAdded, Name: "Name", New: "webapp", }, { Type: DiffTypeNone, Name: "Namespace", }, { Type: DiffTypeNone, Name: "OnUpdate", }, { Type: DiffTypeAdded, Name: "PortLabel", New: "http", }, { Type: DiffTypeNone, Name: "Provider", }, { Type: DiffTypeNone, Name: "TaskName", }, }, }, }, }, { Name: "Service added with same name", Contextual: true, Old: []*Service{ { Name: "webapp", PortLabel: "http", }, }, New: []*Service{ { Name: "webapp", PortLabel: "https", }, { Name: "webapp", PortLabel: "http", }, }, Expected: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Address", }, { Type: DiffTypeNone, Name: "AddressMode", }, { Type: DiffTypeAdded, Name: "EnableTagOverride", New: "false", }, { Type: DiffTypeAdded, Name: "Name", New: "webapp", }, { Type: DiffTypeNone, Name: "Namespace", }, { Type: DiffTypeNone, Name: "OnUpdate", }, { Type: DiffTypeAdded, Name: "PortLabel", New: "https", }, { Type: DiffTypeNone, Name: "Provider", }, { Type: DiffTypeNone, Name: "TaskName", }, }, }, }, }, { Name: "Modify port label of service with same name", Contextual: true, Old: []*Service{ { Name: "webapp", PortLabel: "http", }, { Name: "webapp", PortLabel: "https", }, }, New: []*Service{ { Name: "webapp", PortLabel: "https-redirect", }, { Name: "webapp", PortLabel: "https", }, }, Expected: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Address", }, { Type: DiffTypeNone, Name: "AddressMode", }, { Type: DiffTypeNone, Name: "EnableTagOverride", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "Name", Old: "webapp", New: "webapp", }, { Type: DiffTypeNone, Name: "Namespace", }, { Type: DiffTypeNone, Name: "OnUpdate", }, { Type: DiffTypeEdited, Name: "PortLabel", Old: "http", New: "https-redirect", }, { Type: DiffTypeNone, Name: "Provider", }, { Type: DiffTypeNone, Name: "TaskName", }, }, }, }, }, { Name: "Modify similar services", Contextual: true, Old: []*Service{ { Name: "webapp", PortLabel: "http", Tags: []string{"prod"}, }, { Name: "webapp", PortLabel: "http", Tags: []string{"dev"}, }, }, New: []*Service{ { Name: "webapp", PortLabel: "http", Tags: []string{"prod", "qa"}, }, { Name: "webapp", PortLabel: "http", Tags: []string{"dev"}, }, }, Expected: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Address", }, { Type: DiffTypeNone, Name: "AddressMode", }, { Type: DiffTypeNone, Name: "EnableTagOverride", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "Name", Old: "webapp", New: "webapp", }, { Type: DiffTypeNone, Name: "Namespace", }, { Type: DiffTypeNone, Name: "OnUpdate", }, { Type: DiffTypeNone, Name: "PortLabel", Old: "http", New: "http", }, { Type: DiffTypeNone, Name: "Provider", }, { Type: DiffTypeNone, Name: "TaskName", }, }, Objects: []*ObjectDiff{ { Type: DiffTypeAdded, Name: "Tags", Fields: []*FieldDiff{ { Type: DiffTypeAdded, Name: "Tags", New: "qa", }, { Type: DiffTypeNone, Name: "Tags", Old: "prod", New: "prod", }, }, }, }, }, }, }, { Name: "Service with different provider", Contextual: true, Old: []*Service{ { Name: "webapp", Provider: "nomad", PortLabel: "http", }, }, New: []*Service{ { Name: "webapp", Provider: "consul", PortLabel: "http", }, }, Expected: []*ObjectDiff{ { Type: DiffTypeEdited, Name: "Service", Fields: []*FieldDiff{ { Type: DiffTypeNone, Name: "Address", }, { Type: DiffTypeNone, Name: "AddressMode", }, { Type: DiffTypeNone, Name: "EnableTagOverride", Old: "false", New: "false", }, { Type: DiffTypeNone, Name: "Name", Old: "webapp", New: "webapp", }, { Type: DiffTypeNone, Name: "Namespace", }, { Type: DiffTypeNone, Name: "OnUpdate", }, { Type: DiffTypeNone, Name: "PortLabel", Old: "http", New: "http", }, { Type: DiffTypeEdited, Name: "Provider", Old: "nomad", New: "consul", }, { Type: DiffTypeNone, Name: "TaskName", }, }, }, }, }, } for _, c := range cases { t.Run(c.Name, func(t *testing.T) { actual := serviceDiffs(c.Old, c.New, c.Contextual) require.Equal(t, c.Expected, actual) }) } }