Repository: allegro/marathon-consul (go)

Issue:
Health check invalid port number
Health check are always created in consul with port number from $PORT0 marathon variable. This is only valid for health checks with `Port Type: Port Index`.
For health checks with `Port Type: Port Number` port number should be taken from key "port" from marathon health check definition.

Eg.: marathon health check definition with `Port Type: Port Number` with static port `8123`:

```
"healthChecks": [
    {
      "path": "/v1/version",
      "protocol": "HTTP",
      "gracePeriodSeconds": 60,
      "intervalSeconds": 10,
      "timeoutSeconds": 5,
      "maxConsecutiveFailures": 3,
      "ignoreHttp1xx": false,
      "port": 8123
    }
  ]
```

So there is `"port": 8123` instead of `"portIndex": 0,`.

This check in consul looks like:
```
  {
    "ModifyIndex": 5488518,
    "CreateIndex": 5488430,
    "Node": "dev-mesos-p2",
    "CheckID": "service:ops_mesos-dns.cd314424-6b12-11e6-8d24-0242f3fd597e",
    "Name": "Service 'mesos-dns' check",
    "Status": "critical",
    "Notes": "",
    "Output": "Get http://10.14.30.74:31313/v1/version: dial tcp 10.14.30.74:31313: getsockopt: connection refused",
    "ServiceID": "ops_mesos-dns.cd314424-6b12-11e6-8d24-0242f3fd597e",
    "ServiceName": "mesos-dns"
  }
```

Port number should be `8123` instead of `31313`.
Port for check was taken from $PORT0 instead of static port key from check definition.

Pull request description:
Fixes #108 Use static port number in health check
If `port` is present in health check configuration then it's used instead of `portIndex`. This allows performing health check on static ports.
Change will apply only to newly started tasks, old won't be touched.

Patch:
diff --git a/apps/app.go b/apps/app.go
index 9095165..26b0423 100644
--- a/apps/app.go
+++ b/apps/app.go
@@ -10,6 +10,7 @@ const MARATHON_CONSUL_LABEL = "consul"
 type HealthCheck struct {
 	Path                   string `json:"path"`
 	PortIndex              int    `json:"portIndex"`
+	Port                   int    `json:"port"`
 	Protocol               string `json:"protocol"`
 	GracePeriodSeconds     int    `json:"gracePeriodSeconds"`
 	IntervalSeconds        int    `json:"intervalSeconds"`
diff --git a/apps/app.json b/apps/app.json
index 5468478..05d34f2 100644
--- a/apps/app.json
+++ b/apps/app.json
@@ -34,6 +34,16 @@
         "timeoutSeconds": 10,
         "maxConsecutiveFailures": 3,
         "ignoreHttp1xx": false
+      },
+      {
+        "path": "/custom",
+        "protocol": "HTTP",
+        "port": 8123,
+        "gracePeriodSeconds": 10,
+        "intervalSeconds": 5,
+        "timeoutSeconds": 10,
+        "maxConsecutiveFailures": 3,
+        "ignoreHttp1xx": false
       }
     ],
     "dependencies": [],
diff --git a/consul/consul.go b/consul/consul.go
index 7648795..3507f82 100644
--- a/consul/consul.go
+++ b/consul/consul.go
@@ -220,19 +220,25 @@ func (c *Consul) marathonTaskToConsulService(task *apps.Task, app *apps.App) (*c
 		Port:    task.Ports[0],
 		Address: serviceAddress,
 		Tags:    c.marathonLabelsToConsulTags(app.Labels),
-		Checks:  c.marathonToConsulChecks(task, app.HealthChecks, serviceAddress),
+		Checks:  marathonToConsulChecks(task, app.HealthChecks, serviceAddress),
 	}, nil
 }
 
-func (c *Consul) marathonToConsulChecks(task *apps.Task, healthChecks []apps.HealthCheck, serviceAddress string) consulapi.AgentServiceChecks {
+func marathonToConsulChecks(task *apps.Task, healthChecks []apps.HealthCheck, serviceAddress string) consulapi.AgentServiceChecks {
 	var checks consulapi.AgentServiceChecks = make(consulapi.AgentServiceChecks, 0, len(healthChecks))
 
 	for _, check := range healthChecks {
+		var port int
+		if check.Port != 0 {
+			port = check.Port
+		} else {
+			port = task.Ports[check.PortIndex]
+		}
 		switch check.Protocol {
 		case "HTTP", "HTTPS":
 			if parsedUrl, err := url.ParseRequestURI(check.Path); err == nil {
 				parsedUrl.Scheme = strings.ToLower(check.Protocol)
-				parsedUrl.Host = fmt.Sprintf("%s:%d", serviceAddress, task.Ports[check.PortIndex])
+				parsedUrl.Host = fmt.Sprintf("%s:%d", serviceAddress, port)
 
 				checks = append(checks, &consulapi.AgentServiceCheck{
 					HTTP:     parsedUrl.String(),
@@ -247,7 +253,7 @@ func (c *Consul) marathonToConsulChecks(task *apps.Task, healthChecks []apps.Hea
 			}
 		case "TCP":
 			checks = append(checks, &consulapi.AgentServiceCheck{
-				TCP:      fmt.Sprintf("%s:%d", serviceAddress, task.Ports[check.PortIndex]),
+				TCP:      fmt.Sprintf("%s:%d", serviceAddress, port),
 				Interval: fmt.Sprintf("%ds", check.IntervalSeconds),
 				Timeout:  fmt.Sprintf("%ds", check.TimeoutSeconds),
 			})


Test patch:
diff --git a/apps/app_test.go b/apps/app_test.go
index aafb856..4a8104d 100644
--- a/apps/app_test.go
+++ b/apps/app_test.go
@@ -55,13 +55,26 @@ func TestParseApp(t *testing.T) {
 	appBlob, _ := ioutil.ReadFile("app.json")
 
 	expected := &App{Labels: map[string]string{"consul": "true", "public": "tag"},
-		HealthChecks: []HealthCheck{{Path: "/",
-			PortIndex:              0,
-			Protocol:               "HTTP",
-			GracePeriodSeconds:     10,
-			IntervalSeconds:        5,
-			TimeoutSeconds:         10,
-			MaxConsecutiveFailures: 3}},
+		HealthChecks: []HealthCheck{
+			{
+				Path:                   "/",
+				PortIndex:              0,
+				Protocol:               "HTTP",
+				GracePeriodSeconds:     10,
+				IntervalSeconds:        5,
+				TimeoutSeconds:         10,
+				MaxConsecutiveFailures: 3,
+			},
+			{
+				Path:                   "/custom",
+				Port:                   8123,
+				Protocol:               "HTTP",
+				GracePeriodSeconds:     10,
+				IntervalSeconds:        5,
+				TimeoutSeconds:         10,
+				MaxConsecutiveFailures: 3,
+			},
+		},
 		ID: "/myapp",
 		Tasks: []Task{{
 			ID:    "myapp.cc49ccc1-9812-11e5-a06e-56847afe9799",
diff --git a/consul/consul_test.go b/consul/consul_test.go
index daf9678..4013ce3 100644
--- a/consul/consul_test.go
+++ b/consul/consul_test.go
@@ -495,7 +495,7 @@ func TestMarathonTaskToConsulServiceMapping(t *testing.T) {
 			{
 				Path:                   "/api/health?with=query",
 				Protocol:               "HTTP",
-				PortIndex:              0,
+				Port:                   8123,
 				IntervalSeconds:        60,
 				TimeoutSeconds:         20,
 				MaxConsecutiveFailures: 3,
@@ -531,6 +531,13 @@ func TestMarathonTaskToConsulServiceMapping(t *testing.T) {
 				TimeoutSeconds:         20,
 				MaxConsecutiveFailures: 3,
 			},
+			{
+				Protocol:               "TCP",
+				Port:                   8234,
+				IntervalSeconds:        40,
+				TimeoutSeconds:         20,
+				MaxConsecutiveFailures: 3,
+			},
 			{
 				Protocol: "COMMAND",
 				Command: struct {
@@ -562,11 +569,11 @@ func TestMarathonTaskToConsulServiceMapping(t *testing.T) {
 	assert.Equal(t, []string{"marathon", "public"}, service.Tags)
 	assert.Equal(t, 8090, service.Port)
 	assert.Nil(t, service.Check)
-	assert.Equal(t, 4, len(service.Checks))
+	assert.Equal(t, 5, len(service.Checks))
 
 	assert.Equal(t, consulapi.AgentServiceChecks{
 		{
-			HTTP:     "http://127.0.0.6:8090/api/health?with=query",
+			HTTP:     "http://127.0.0.6:8123/api/health?with=query",
 			Interval: "60s",
 			Timeout:  "20s",
 		},
@@ -580,6 +587,11 @@ func TestMarathonTaskToConsulServiceMapping(t *testing.T) {
 			Interval: "40s",
 			Timeout:  "20s",
 		},
+		{
+			TCP:      "127.0.0.6:8234",
+			Interval: "40s",
+			Timeout:  "20s",
+		},
 		{
 			Script:   "echo 1",
 			Interval: "30s",