Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/ecs_task_definition: Fix equivalency comparator #2339

Merged
merged 2 commits into from
Nov 17, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions aws/ecs_task_definition_equivalency.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package aws
import (
"bytes"
"encoding/json"
"log"
"reflect"
"sort"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/private/protocol/json/jsonutil"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/mitchellh/copystructure"
Expand Down Expand Up @@ -34,11 +37,14 @@ func ecsContainerDefinitionsAreEquivalent(def1, def2 string) (bool, error) {
if err != nil {
return false, err
}

canonicalJson2, err := jsonutil.BuildJSON(obj2)
if err != nil {
return false, err
}

log.Printf("[DEBUG] Comparing canonical definitions,\nFirst: %s\nSecond: %s\n",
canonicalJson1, canonicalJson2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not sure if this is debugging that you left in unintentionally, however if intended, maybe we should log instead the result of the compare; logical match or difference etc. Right here we're just logging both and not actually saying if Terraform seems them as equal, that's left up to the user debugger

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, good point - I'm happy to add more context (i.e. whether it's a match).

The reason I left it there is because I had hard times comparing those JSONs in a failing test, where you can only get the original (non-canonical) JSONs which is also kind of intention, because this process should remain hidden as implementation detail.

return bytes.Compare(canonicalJson1, canonicalJson2) == 0, nil
}

Expand All @@ -47,6 +53,12 @@ type containerDefinitions []*ecs.ContainerDefinition
func (cd containerDefinitions) Reduce() error {
for i, def := range cd {
// Deal with special fields which have defaults
if def.Cpu != nil && *def.Cpu == 0 {
def.Cpu = nil
}
if def.Essential == nil {
def.Essential = aws.Bool(true)
}
for j, pm := range def.PortMappings {
if pm.Protocol != nil && *pm.Protocol == "tcp" {
cd[i].PortMappings[j].Protocol = nil
Expand All @@ -56,6 +68,11 @@ func (cd containerDefinitions) Reduce() error {
}
}

// Deal with fields which may be re-ordered in the API
sort.Slice(def.Environment, func(i, j int) bool {
return *def.Environment[i].Name < *def.Environment[j].Name
})

// Create a mutable copy
defCopy, err := copystructure.Copy(def)
if err != nil {
Expand Down
253 changes: 253 additions & 0 deletions aws/ecs_task_definition_equivalency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,259 @@ func TestAwsEcsContainerDefinitionsAreEquivalent_portMappings(t *testing.T) {
}
}

func TestAwsEcsContainerDefinitionsAreEquivalent_arrays(t *testing.T) {
cfgRepresention := `
[
{
"name": "wordpress",
"image": "wordpress",
"essential": true,
"links": ["container1", "container2", "container3"],
"portMappings": [
{"containerPort": 80},
{"containerPort": 81},
{"containerPort": 82}
],
"environment": [
{"name": "VARNAME1", "value": "VARVAL1"},
{"name": "VARNAME2", "value": "VARVAL2"},
{"name": "VARNAME3", "value": "VARVAL3"}
],
"extraHosts": [
{"hostname": "host1", "ipAddress": "127.0.0.1"},
{"hostname": "host2", "ipAddress": "127.0.0.2"},
{"hostname": "host3", "ipAddress": "127.0.0.3"}
],
"mountPoints": [
{"sourceVolume": "vol1", "containerPath": "/vol1"},
{"sourceVolume": "vol2", "containerPath": "/vol2"},
{"sourceVolume": "vol3", "containerPath": "/vol3"}
],
"volumesFrom": [
{"sourceContainer": "container1"},
{"sourceContainer": "container2"},
{"sourceContainer": "container3"}
],
"ulimits": [
{
"name": "core",
"softLimit": 10, "hardLimit": 20
},
{
"name": "cpu",
"softLimit": 10, "hardLimit": 20
},
{
"name": "fsize",
"softLimit": 10, "hardLimit": 20
}
],
"linuxParameters": {
"capabilities": {
"add": ["AUDIT_CONTROL", "AUDIT_WRITE", "BLOCK_SUSPEND"],
"drop": ["CHOWN", "IPC_LOCK", "KILL"]
}
},
"devices": [
{
"hostPath": "/path1",
"permissions": ["read", "write", "mknod"]
},
{
"hostPath": "/path2",
"permissions": ["read", "write"]
},
{
"hostPath": "/path3",
"permissions": ["read", "mknod"]
}
],
"dockerSecurityOptions": ["label:one", "label:two", "label:three"],
"memory": 500,
"cpu": 10
},
{
"name": "container1",
"image": "busybox",
"memory": 100
},
{
"name": "container2",
"image": "busybox",
"memory": 100
},
{
"name": "container3",
"image": "busybox",
"memory": 100
}
]`

apiRepresentation := `
[
{
"cpu": 10,
"dockerSecurityOptions": [
"label:one",
"label:two",
"label:three"
],
"environment": [
{
"name": "VARNAME3",
"value": "VARVAL3"
},
{
"name": "VARNAME2",
"value": "VARVAL2"
},
{
"name": "VARNAME1",
"value": "VARVAL1"
}
],
"essential": true,
"extraHosts": [
{
"hostname": "host1",
"ipAddress": "127.0.0.1"
},
{
"hostname": "host2",
"ipAddress": "127.0.0.2"
},
{
"hostname": "host3",
"ipAddress": "127.0.0.3"
}
],
"image": "wordpress",
"links": [
"container1",
"container2",
"container3"
],
"linuxParameters": {
"capabilities": {
"add": [
"AUDIT_CONTROL",
"AUDIT_WRITE",
"BLOCK_SUSPEND"
],
"drop": [
"CHOWN",
"IPC_LOCK",
"KILL"
]
}
},
"memory": 500,
"mountPoints": [
{
"containerPath": "/vol1",
"sourceVolume": "vol1"
},
{
"containerPath": "/vol2",
"sourceVolume": "vol2"
},
{
"containerPath": "/vol3",
"sourceVolume": "vol3"
}
],
"name": "wordpress",
"portMappings": [
{
"containerPort": 80,
"hostPort": 0,
"protocol": "tcp"
},
{
"containerPort": 81,
"hostPort": 0,
"protocol": "tcp"
},
{
"containerPort": 82,
"hostPort": 0,
"protocol": "tcp"
}
],
"ulimits": [
{
"hardLimit": 20,
"name": "core",
"softLimit": 10
},
{
"hardLimit": 20,
"name": "cpu",
"softLimit": 10
},
{
"hardLimit": 20,
"name": "fsize",
"softLimit": 10
}
],
"volumesFrom": [
{
"sourceContainer": "container1"
},
{
"sourceContainer": "container2"
},
{
"sourceContainer": "container3"
}
]
},
{
"cpu": 0,
"environment": [],
"essential": true,
"image": "busybox",
"memory": 100,
"mountPoints": [],
"name": "container1",
"portMappings": [],
"volumesFrom": []
},
{
"cpu": 0,
"environment": [],
"essential": true,
"image": "busybox",
"memory": 100,
"mountPoints": [],
"name": "container2",
"portMappings": [],
"volumesFrom": []
},
{
"cpu": 0,
"environment": [],
"essential": true,
"image": "busybox",
"memory": 100,
"mountPoints": [],
"name": "container3",
"portMappings": [],
"volumesFrom": []
}
]
`

equal, err := ecsContainerDefinitionsAreEquivalent(cfgRepresention, apiRepresentation)
if err != nil {
t.Fatal(err)
}
if !equal {
t.Fatal("Expected definitions to be equal.")
}
}

func TestAwsEcsContainerDefinitionsAreEquivalent_negative(t *testing.T) {
cfgRepresention := `
[
Expand Down
Loading