New Upstream Snapshot - golang-github-gambol99-go-marathon

Ready changes

Summary

Merged new upstream version: 0.7.1+git20200116.1.94e7bcb+ds (was: 0.7.1).

Resulting package

Built on 2022-09-03T13:01 (took 17m38s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots golang-github-gambol99-go-marathon-dev

Lintian Result

Diff

diff --git a/.gitignore b/.gitignore
index 1ff0a01..c7415d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,8 +12,10 @@ examples/events_sse_transport/events_sse_transport
 examples/glog/glog
 examples/groups/groups
 examples/multiple_endpoints/multiple_endpoints
+examples/pods/pods
 examples/queue/queue
 examples/tasks/tasks
+coverage
 
 # Folders
 _obj
diff --git a/.travis.yml b/.travis.yml
index dd36b0b..76b7e8e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,19 @@
-#
-#   Author: Rohith (gambol99@gmail.com)
-#   Date: 2015-02-17 17:11:18 +0000 (Tue, 17 Feb 2015)
-#
-#  vim:ts=2:sw=2:et
-#
+env:
+  global:
+    secure: YiSCbBUz0VMONSBZ6TfRaSM9bFBuT5xvaknt9WxWczPSiSgiY8+dGYlsOaX2jzI26J4zA8KxIyxOihN1UE28tkkGoXRkRovoQuOl9YUYp+VCtZdaeksZ7tJ/j/b6aYGpGN3GRRfxkuIhXw1ghZLgqdCVtqfmD3GODlmeuFE01ug=
 language: go
 go:
-  - 1.5
-  - 1.6
-  - 1.7
-  - 1.8
+- 1.6
+- 1.7
+- 1.8
+- 1.9
+- "1.10"
+- "1.11"
 install:
-  - make check-format test examples
+- go get github.com/mattn/goveralls
+script:
+- make test examples
+- if ([[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_EVENT_TYPE} == "push" ]]); then
+    make coverage;
+    goveralls -coverprofile=coverage -service=travis-ci;
+  fi
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index 909e68f..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,41 +0,0 @@
-Anton Liparin <a_liparin@wargaming.net>
-atheatos <atheatos.engr@gmail.com>
-Brian Knox <taotetek@gmail.com>
-Conor Mongey <mongeyc@gmail.com>
-Daniel Bornkessel <github@bornkessel.com>
-David Bosschaert <david.bosschaert@gmail.com>
-Denis Parchenko <denis.parchenko@gmail.com>
-Diego de Oliveira <diego@diegooliveira.com>
-Dmitry Fedorov <d_fedorov@wargaming.net>
-eduser25 <eduards@plumgrid.com>
-Elliot Anderson <elliot.a@gmail.com>
-François Samin <francois.samin+github@gmail.com>
-Harpreet Sawhney <harpreet.sawhney@gmail.com>
-Ian Babrou <ibobrik@gmail.com>
-Israel Derdik <derdik@adobe.com>
-Ivan Babrou <ibobrik@gmail.com>
-Jacob Koren <jakekoren113@gmail.com>
-Jie Zhang <zhangjie0220@gmail.com>
-Johan Haals <johan.haals@gmail.com>
-juliendsv <julien.dasilva@gmail.com>
-Kan Wu <optimuswu8685@gmail.com>
-kevinschoon <kevinschoon@gmail.com>
-Luke Amdor <luke.amdor@gmail.com>
-Maarten Dirkse <mdirkse@bol.com>
-Marcelo Salazar <chelosalazar@gmail.com>
-Marvin Hoffmann <contact@marvin-hoffmann.de>
-Matt DeBoer <matt.deboer@gmail.com>
-Matthias Kadenbach <matthias.kadenbach@gmail.com>
-Mike Solomon <mikesol@gmail.com>
-Mikhail Dyakov <wtltl2@gmail.com>
-Nic Grayson <nic.grayson@banno.com>
-ohmystack <jiangjun1990@gmail.com>
-Raffaele Di Fazio <raffaele.di.fazio@zalando.de>
-Robert Jacob <robert.jacob@holidaycheck.com>
-Rohith <gambol99@gmail.com>
-Timo Reimann <ttr314@googlemail.com>
-Tracy Livengood <tracy.livengood@gmail.com>
-Xavi Ramirez <xavi.ramirez@clever.com>
-Yang Bai <hamo.by@gmail.com>
-Yifa Zhang <zyfdegg@gmail.com>
-Zhanpeng Chen <lowstz@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b0088e..79d7e99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
+### Added
+- [#273][PR273] Implement readiness checks.
+- [#267][PR267] Add DCOS path parameter for additional marathon instances.
 
 ## [0.7.1] - 2017-02-20
 ### Fixed
@@ -118,6 +121,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 [0.1.1]: https://github.com/gambol99/go-marathon/compare/v0.1.0...v0.1.1
 [0.1.0]: https://github.com/gambol99/go-marathon/compare/v0.0.1...v0.1.0
 
+[PR273]: https://github.com/gambol99/go-marathon/pull/273
+[PR267]: https://github.com/gambol99/go-marathon/pull/267
 [PR261]: https://github.com/gambol99/go-marathon/pull/261
 [PR259]: https://github.com/gambol99/go-marathon/pull/259
 [PR256]: https://github.com/gambol99/go-marathon/pull/256
diff --git a/Makefile b/Makefile
index 3f3dc8f..8452136 100644
--- a/Makefile
+++ b/Makefile
@@ -10,14 +10,11 @@ DEPS=$(shell go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
 PACKAGES=$(shell go list ./...)
 VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr
 
-.PHONY: test examples authors changelog check-format
+.PHONY: test examples changelog check-format coverage cover
 
 build:
 	go build
 
-authors:
-	git log --format='%aN <%aE>' | sort -u > AUTHORS
-
 deps:
 	@echo "--> Installing build dependencies"
 	@go get -d -v ./... $(DEPS)
@@ -40,6 +37,10 @@ cover:
 	@echo "--> Running go test --cover"
 	@go test --cover
 
+coverage:
+	@echo "--> Running go coverage"
+	@go test -covermode=count -coverprofile=coverage
+
 format:
 	@echo "--> Running go fmt"
 	@go fmt $(PACKAGES)
@@ -53,10 +54,9 @@ check-format:
 		exit 1; \
 	fi
 
-test: deps
+test: deps vet
 	@echo "--> Running go tests"
 	@go test -race -v
-	@$(MAKE) vet
 	@$(MAKE) cover
 
 examples:
diff --git a/README.md b/README.md
index 3ec93f2..bcf7010 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 [![Build Status](https://travis-ci.org/gambol99/go-marathon.svg?branch=master)](https://travis-ci.org/gambol99/go-marathon)
 [![GoDoc](http://godoc.org/github.com/gambol99/go-marathon?status.png)](http://godoc.org/github.com/gambol99/go-marathon)
+[![Go Report Card](https://goreportcard.com/badge/github.com/katallaxie/go-marathon)](https://goreportcard.com/report/github.com/katallaxie/go-marathon)
+[![Coverage Status](https://coveralls.io/repos/github/gambol99/go-marathon/badge.svg?branch=master)](https://coveralls.io/github/gambol99/go-marathon?branch=master)
 
 # Go-Marathon
 
@@ -10,10 +12,11 @@ It currently supports
 - Helper filters for pulling the status, configuration and tasks
 - Multiple Endpoint support for HA deployments
 - Marathon Event Subscriptions and Event Streams
+- Pods
 
 Note: the library is still under active development; users should expect frequent (possibly breaking) API changes for the time being.
 
-It requires Go version 1.5 or higher.
+It requires Go version 1.6 or higher.
 
 ## Code Examples
 
@@ -24,7 +27,7 @@ You can use `examples/docker-compose.yml` in order to start a test cluster.
 
 ### Creating a client
 
-```Go
+```go
 import (
 	marathon "github.com/gambol99/go-marathon"
 )
@@ -37,24 +40,39 @@ if err != nil {
 	log.Fatalf("Failed to create a client for marathon, error: %s", err)
 }
 
-applications, err := client.Applications()
+applications, err := client.Applications(nil)
 ...
 ```
 
 Note, you can also specify multiple endpoint for Marathon (i.e. you have setup Marathon in HA mode and having multiple running)
 
-```Go
+```go
 marathonURL := "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
 ```
 
 The first one specified will be used, if that goes offline the member is marked as *"unavailable"* and a
 background process will continue to ping the member until it's back online.
 
-### Custom HTTP Client
+You can also pass a custom path to the URL, which is especially needed in case of DCOS:
 
-If you wish to override the http client (by default http.DefaultClient) used by the API; use cases bypassing TLS verification, load root CA's or change the timeouts etc, you can pass a custom client in the config.
+```go
+marathonURL := "http://10.241.1.71:8080/cluster,10.241.1.72:8080/cluster,10.241.1.73:8080/cluster"
+```
 
-```Go
+If you specify a `DCOSToken` in the configuration file but do not pass a custom URL path, `/marathon` will be used.
+
+### Customizing the HTTP Clients
+
+HTTP clients with reasonable timeouts are used by default. It is possible to pass custom clients to the configuration though if the behavior should be customized (e.g., to bypass TLS verification, load root CAs, or change timeouts).
+
+Two clients can be given independently of each other:
+
+- `HTTPClient` used only for (non-SSE) HTTP API requests. By default, an http.Client with 10 seconds timeout for the entire request is used.
+- `HTTPSSEClient` used only for SSE-based subscription requests. Note that `HTTPSSEClient` cannot have a response read timeout set as this breaks SSE communication; trying to do so will lead to an error during the SSE connection setup. By default, an http.Client with 5 seconds timeout for dial and TLS handshake, and 10 seconds timeout for response headers received is used.
+
+If no `HTTPSSEClient` is given but an `HTTPClient` is, it will be used for SSE subscriptions as well (thereby overriding the default SSE HTTP client).
+
+```go
 marathonURL := "http://10.241.1.71:8080"
 config := marathon.NewDefaultConfig()
 config.URL = marathonURL
@@ -70,35 +88,48 @@ config.HTTPClient = &http.Client{
         },
     },
 }
+config.HTTPSSEClient = &http.Client{
+    // Invalid to set Timeout as it contains timeout for reading a response body
+    Transport: &http.Transport{
+        Dial: (&net.Dialer{
+            Timeout:   10 * time.Second,
+            KeepAlive: 10 * time.Second,
+        }).Dial,
+        TLSClientConfig: &tls.Config{
+            InsecureSkipVerify: true,
+        },
+    },
+}
 ```
 
 ### Listing the applications
 
-```Go
-applications, err := client.Applications()
+```go
+applications, err := client.Applications(nil)
 if err != nil {
-	log.Fatalf("Failed to list applications")
+	log.Fatalf("Failed to list applications: %s", err)
 }
 
-log.Printf("Found %d applications running", len(applications.Apps))
+log.Printf("Found %d application(s) running", len(applications.Apps))
 for _, application := range applications.Apps {
 	log.Printf("Application: %s", application)
-	details, err := client.Application(application.ID)
-	assert(err)
-	if details.Tasks != nil && len(details.Tasks) > 0 {
+	appID := application.ID
+
+	details, err := client.Application(appID)
+	if err != nil {
+		log.Fatalf("Failed to get application %s: %s", appID, err)
+	}
+	if details.Tasks != nil {
 		for _, task := range details.Tasks {
-			log.Printf("task: %s", task)
+			log.Printf("application %s has task: %s", appID, task)
 		}
-		// check the health of the application
-		health, err := client.ApplicationOK(details.ID)
-		log.Printf("Application: %s, healthy: %t", details.ID, health)
 	}
 }
 ```
 
 ### Creating a new application
 
-```Go
+```go
 log.Printf("Deploying a new application")
 application := marathon.NewDockerApplication().
   Name(applicationName).
@@ -130,7 +161,7 @@ Note: Applications may also be defined by means of initializing a `marathon.Appl
 
 Change the number of application instances to 4
 
-```Go
+```go
 log.Printf("Scale to 4 instances")
 if err := client.ScaleApplicationInstances(application.ID, 4); err != nil {
 	log.Fatalf("Failed to delete the application: %s, error: %s", application, err)
@@ -140,6 +171,38 @@ if err := client.ScaleApplicationInstances(application.ID, 4); err != nil {
 }
 ```
 
+### Pods
+
+Pods allow you to deploy groups of tasks as a unit. All tasks in a single instance of a pod share networking and storage. View the [Marathon documentation](https://mesosphere.github.io/marathon/docs/pods.html) for more details on this feature.
+
+Examples of their usage can be seen in the `examples/pods` directory, and a smaller snippet is below.
+
+```Go
+// Initialize a single-container pod running nginx
+pod := marathon.NewPod()
+
+image := marathon.NewDockerPodContainerImage().SetID("nginx")
+
+container := marathon.NewPodContainer().
+	SetName("container", i).
+	CPUs(0.1).
+	Memory(128).
+	SetImage(image)
+
+pod.Name("mypod").AddContainer(container)
+
+// Create it and wait for it to start up
+pod, err := client.CreatePod(pod)
+err = client.WaitOnPod(pod.ID, time.Minute*1)
+
+// Scale it
+pod.Count(5)
+pod, err = client.UpdatePod(pod, true)
+
+// Delete it
+id, err := client.DeletePod(pod.ID, true)
+```
+
 ### Subscription & Events
 
 Request to listen to events related to applications — namely status updates, health checks
@@ -154,7 +217,7 @@ Event subscriptions can also be individually controlled with the `Subscribe` and
 
 Only available in Marathon >= 0.9.0. Does not require any special configuration or prerequisites.
 
-```Go
+```go
 // Configure client
 config := marathon.NewDefaultConfig()
 config.URL = marathonURL
@@ -201,7 +264,7 @@ additional settings:
 - `EventsPort` — built-in web server port. Default `10001`.
 - `CallbackURL` — custom callback URL. Default `""`.
 
-```Go
+```go
 // Configure client
 config := marathon.NewDefaultConfig()
 config.URL = marathonURL
@@ -245,7 +308,7 @@ See [events.go](events.go) for a full list of event IDs.
 #### Controlling subscriptions
 If you simply want to (de)register event subscribers (i.e. without starting an internal web server) you can use the `Subscribe` and `Unsubscribe` methods.
 
-```Go
+```go
 // Configure client
 config := marathon.NewDefaultConfig()
 config.URL = marathonURL
@@ -364,7 +427,7 @@ and the tests
 func TestFoo(t * testing.T) {
 	endpoint := newFakeMarathonEndpoint(t, nil)  // No custom configs given.
 	defer endpoint.Close()
-	app, err := endpoint.Client.Applications()
+	app, err := endpoint.Client.Applications(nil)
 	// Do something with "foo"
 }
 
@@ -375,7 +438,7 @@ func TestFoo(t * testing.T) {
 		},
 	})
 	defer endpoint.Close()
-	app, err := endpoint.Client.Applications()
+	app, err := endpoint.Client.Applications(nil)
 	// Do something with "bar"
 }
 ```
diff --git a/application.go b/application.go
index 573fc81..00f940b 100644
--- a/application.go
+++ b/application.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -56,43 +56,55 @@ type Port struct {
 
 // Application is the definition for an application in marathon
 type Application struct {
-	ID                         string               `json:"id,omitempty"`
-	Cmd                        *string              `json:"cmd,omitempty"`
-	Args                       *[]string            `json:"args,omitempty"`
-	Constraints                *[][]string          `json:"constraints,omitempty"`
-	Container                  *Container           `json:"container,omitempty"`
-	CPUs                       float64              `json:"cpus,omitempty"`
-	Disk                       *float64             `json:"disk,omitempty"`
-	Env                        *map[string]string   `json:"env,omitempty"`
-	Executor                   *string              `json:"executor,omitempty"`
-	HealthChecks               *[]HealthCheck       `json:"healthChecks,omitempty"`
-	Instances                  *int                 `json:"instances,omitempty"`
-	Mem                        *float64             `json:"mem,omitempty"`
-	Tasks                      []*Task              `json:"tasks,omitempty"`
-	Ports                      []int                `json:"ports"`
-	PortDefinitions            *[]PortDefinition    `json:"portDefinitions,omitempty"`
-	RequirePorts               *bool                `json:"requirePorts,omitempty"`
-	BackoffSeconds             *float64             `json:"backoffSeconds,omitempty"`
-	BackoffFactor              *float64             `json:"backoffFactor,omitempty"`
-	MaxLaunchDelaySeconds      *float64             `json:"maxLaunchDelaySeconds,omitempty"`
-	TaskKillGracePeriodSeconds *float64             `json:"taskKillGracePeriodSeconds,omitempty"`
-	Deployments                []map[string]string  `json:"deployments,omitempty"`
-	Dependencies               []string             `json:"dependencies"`
-	TasksRunning               int                  `json:"tasksRunning,omitempty"`
-	TasksStaged                int                  `json:"tasksStaged,omitempty"`
-	TasksHealthy               int                  `json:"tasksHealthy,omitempty"`
-	TasksUnhealthy             int                  `json:"tasksUnhealthy,omitempty"`
-	TaskStats                  map[string]TaskStats `json:"taskStats,omitempty"`
-	User                       string               `json:"user,omitempty"`
-	UpgradeStrategy            *UpgradeStrategy     `json:"upgradeStrategy,omitempty"`
-	Uris                       *[]string            `json:"uris,omitempty"`
-	Version                    string               `json:"version,omitempty"`
-	VersionInfo                *VersionInfo         `json:"versionInfo,omitempty"`
-	Labels                     *map[string]string   `json:"labels,omitempty"`
-	AcceptedResourceRoles      []string             `json:"acceptedResourceRoles,omitempty"`
-	LastTaskFailure            *LastTaskFailure     `json:"lastTaskFailure,omitempty"`
-	Fetch                      *[]Fetch             `json:"fetch,omitempty"`
-	IPAddressPerTask           *IPAddressPerTask    `json:"ipAddress,omitempty"`
+	ID          string        `json:"id,omitempty"`
+	Cmd         *string       `json:"cmd,omitempty"`
+	Args        *[]string     `json:"args,omitempty"`
+	Constraints *[][]string   `json:"constraints,omitempty"`
+	Container   *Container    `json:"container,omitempty"`
+	CPUs        float64       `json:"cpus,omitempty"`
+	GPUs        *float64      `json:"gpus,omitempty"`
+	Disk        *float64      `json:"disk,omitempty"`
+	Networks    *[]PodNetwork `json:"networks,omitempty"`
+
+	// Contains non-secret environment variables. Secrets environment variables are part of the Secrets map.
+	Env                        *map[string]string  `json:"-"`
+	Executor                   *string             `json:"executor,omitempty"`
+	HealthChecks               *[]HealthCheck      `json:"healthChecks,omitempty"`
+	ReadinessChecks            *[]ReadinessCheck   `json:"readinessChecks,omitempty"`
+	Instances                  *int                `json:"instances,omitempty"`
+	Mem                        *float64            `json:"mem,omitempty"`
+	Tasks                      []*Task             `json:"tasks,omitempty"`
+	Ports                      []int               `json:"ports"`
+	PortDefinitions            *[]PortDefinition   `json:"portDefinitions,omitempty"`
+	RequirePorts               *bool               `json:"requirePorts,omitempty"`
+	BackoffSeconds             *float64            `json:"backoffSeconds,omitempty"`
+	BackoffFactor              *float64            `json:"backoffFactor,omitempty"`
+	MaxLaunchDelaySeconds      *float64            `json:"maxLaunchDelaySeconds,omitempty"`
+	TaskKillGracePeriodSeconds *float64            `json:"taskKillGracePeriodSeconds,omitempty"`
+	Deployments                []map[string]string `json:"deployments,omitempty"`
+	// Available when embedding readiness information through query parameter.
+	ReadinessCheckResults *[]ReadinessCheckResult `json:"readinessCheckResults,omitempty"`
+	Dependencies          []string                `json:"dependencies"`
+	TasksRunning          int                     `json:"tasksRunning,omitempty"`
+	TasksStaged           int                     `json:"tasksStaged,omitempty"`
+	TasksHealthy          int                     `json:"tasksHealthy,omitempty"`
+	TasksUnhealthy        int                     `json:"tasksUnhealthy,omitempty"`
+	TaskStats             map[string]TaskStats    `json:"taskStats,omitempty"`
+	User                  string                  `json:"user,omitempty"`
+	UpgradeStrategy       *UpgradeStrategy        `json:"upgradeStrategy,omitempty"`
+	UnreachableStrategy   *UnreachableStrategy    `json:"unreachableStrategy,omitempty"`
+	KillSelection         string                  `json:"killSelection,omitempty"`
+	Uris                  *[]string               `json:"uris,omitempty"`
+	Version               string                  `json:"version,omitempty"`
+	VersionInfo           *VersionInfo            `json:"versionInfo,omitempty"`
+	Labels                *map[string]string      `json:"labels,omitempty"`
+	AcceptedResourceRoles []string                `json:"acceptedResourceRoles,omitempty"`
+	LastTaskFailure       *LastTaskFailure        `json:"lastTaskFailure,omitempty"`
+	Fetch                 *[]Fetch                `json:"fetch,omitempty"`
+	IPAddressPerTask      *IPAddressPerTask       `json:"ipAddress,omitempty"`
+	Residency             *Residency              `json:"residency,omitempty"`
+	Secrets               *map[string]Secret      `json:"-"`
+	Role                  *string                 `json:"role,omitempty"`
 }
 
 // ApplicationVersions is a collection of application versions for a specific app in marathon
@@ -143,6 +155,14 @@ type Stats struct {
 	LifeTime map[string]float64 `json:"lifeTime"`
 }
 
+// Secret is the environment variable and secret store path associated with a secret.
+// The value for EnvVar is populated from the env field, and Source is populated from
+// the secrets field of the application json.
+type Secret struct {
+	EnvVar string
+	Source string
+}
+
 // SetIPAddressPerTask defines that the application will have a IP address defines by a external agent.
 // This configuration is not allowed to be used with Port or PortDefinitions. Thus, the implementation
 // clears both.
@@ -180,6 +200,22 @@ func (r *Application) CPU(cpu float64) *Application {
 	return r
 }
 
+// SetGPUs set the amount of GPU per instance which is assigned to the application
+//		gpu:	the GPU (check MESOS docs) per instance
+func (r *Application) SetGPUs(gpu float64) *Application {
+	r.GPUs = &gpu
+	return r
+}
+
+// EmptyGPUs explicitly empties GPUs -- use this if you need to empty
+// gpus of an application that already has gpus set (setting port definitions to nil will
+// keep the current value)
+func (r *Application) EmptyGPUs() *Application {
+	g := 0.0
+	r.GPUs = &g
+	return r
+}
+
 // Storage sets the amount of disk space the application is assigned, which for docker
 // application I don't believe is relevant
 //		disk:	the disk space in MB
@@ -333,8 +369,8 @@ func (r *Application) EmptyLabels() *Application {
 }
 
 // AddEnv adds an environment variable to the application
-//		name:	the name of the variable
-//		value:	go figure, the value associated to the above
+// name:	the name of the variable
+// value:	go figure, the value associated to the above
 func (r *Application) AddEnv(name, value string) *Application {
 	if r.Env == nil {
 		r.EmptyEnvs()
@@ -353,6 +389,28 @@ func (r *Application) EmptyEnvs() *Application {
 	return r
 }
 
+// AddSecret adds a secret declaration
+// envVar: the name of the environment variable
+// name:	the name of the secret
+// source:	the source ID of the secret
+func (r *Application) AddSecret(envVar, name, source string) *Application {
+	if r.Secrets == nil {
+		r.EmptySecrets()
+	}
+	(*r.Secrets)[name] = Secret{EnvVar: envVar, Source: source}
+
+	return r
+}
+
+// EmptySecrets explicitly empties the secrets -- use this if you need to empty
+// the secrets of an application that already has secrets set (setting secrets to nil will
+// keep the current value)
+func (r *Application) EmptySecrets() *Application {
+	r.Secrets = &map[string]Secret{}
+
+	return r
+}
+
 // SetExecutor sets the executor
 func (r *Application) SetExecutor(executor string) *Application {
 	r.Executor = &executor
@@ -388,6 +446,26 @@ func (r *Application) HasHealthChecks() bool {
 	return r.HealthChecks != nil && len(*r.HealthChecks) > 0
 }
 
+// AddReadinessCheck adds a readiness check.
+func (r *Application) AddReadinessCheck(readinessCheck ReadinessCheck) *Application {
+	if r.ReadinessChecks == nil {
+		r.EmptyReadinessChecks()
+	}
+
+	readinessChecks := *r.ReadinessChecks
+	readinessChecks = append(readinessChecks, readinessCheck)
+	r.ReadinessChecks = &readinessChecks
+
+	return r
+}
+
+// EmptyReadinessChecks empties the readiness checks.
+func (r *Application) EmptyReadinessChecks() *Application {
+	r.ReadinessChecks = &[]ReadinessCheck{}
+
+	return r
+}
+
 // DeploymentIDs retrieves the application deployments IDs
 func (r *Application) DeploymentIDs() []*DeploymentID {
 	var deployments []*DeploymentID
@@ -413,18 +491,21 @@ func (r *Application) DeploymentIDs() []*DeploymentID {
 // CheckHTTP adds a HTTP check to an application
 //		port: 		the port the check should be checking
 // 		interval:	the interval in seconds the check should be performed
-func (r *Application) CheckHTTP(uri string, port, interval int) (*Application, error) {
+func (r *Application) CheckHTTP(path string, port, interval int) (*Application, error) {
 	if r.Container == nil || r.Container.Docker == nil {
 		return nil, ErrNoApplicationContainer
 	}
 	// step: get the port index
 	portIndex, err := r.Container.Docker.ServicePortIndex(port)
 	if err != nil {
-		return nil, err
+		portIndex, err = r.Container.ServicePortIndex(port)
+		if err != nil {
+			return nil, err
+		}
 	}
 	health := NewDefaultHealthCheck()
 	health.IntervalSeconds = interval
-	*health.Path = uri
+	*health.Path = path
 	*health.PortIndex = portIndex
 	// step: add to the checks
 	r.AddHealthCheck(*health)
@@ -443,7 +524,10 @@ func (r *Application) CheckTCP(port, interval int) (*Application, error) {
 	// step: get the port index
 	portIndex, err := r.Container.Docker.ServicePortIndex(port)
 	if err != nil {
-		return nil, err
+		portIndex, err = r.Container.ServicePortIndex(port)
+		if err != nil {
+			return nil, err
+		}
 	}
 	health := NewDefaultHealthCheck()
 	health.Protocol = "TCP"
@@ -515,6 +599,37 @@ func (r *Application) EmptyUpgradeStrategy() *Application {
 	return r
 }
 
+// SetUnreachableStrategy sets the unreachable strategy.
+func (r *Application) SetUnreachableStrategy(us UnreachableStrategy) *Application {
+	r.UnreachableStrategy = &us
+	return r
+}
+
+// EmptyUnreachableStrategy explicitly empties the unreachable strategy -- use this if
+// you need to empty the unreachable strategy of an application that already has
+// the unreachable strategy set (setting it to nil will keep the current value).
+func (r *Application) EmptyUnreachableStrategy() *Application {
+	r.UnreachableStrategy = &UnreachableStrategy{}
+	return r
+}
+
+// SetResidency sets behavior for resident applications, an application is resident when
+// it has local persistent volumes set
+func (r *Application) SetResidency(whenLost TaskLostBehaviorType) *Application {
+	r.Residency = &Residency{
+		TaskLostBehavior: whenLost,
+	}
+	return r
+}
+
+// EmptyResidency explicitly empties the residency -- use this if
+// you need to empty the residency of an application that already has
+// the residency set (setting it to nil will keep the current value).
+func (r *Application) EmptyResidency() *Application {
+	r.Residency = &Residency{}
+	return r
+}
+
 // String returns the json representation of this application
 func (r *Application) String() string {
 	s, err := json.MarshalIndent(r, "", "  ")
@@ -571,9 +686,9 @@ func (r *marathonClient) HasApplicationVersion(name, version string) (bool, erro
 // ApplicationVersions is a list of versions which has been deployed with marathon for a specific application
 //		name:		the id used to identify the application
 func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions, error) {
-	uri := fmt.Sprintf("%s/versions", buildURI(name))
+	path := fmt.Sprintf("%s/versions", buildPath(name))
 	versions := new(ApplicationVersions)
-	if err := r.apiGet(uri, nil, versions); err != nil {
+	if err := r.apiGet(path, nil, versions); err != nil {
 		return nil, err
 	}
 	return versions, nil
@@ -583,9 +698,9 @@ func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions,
 // 		name: 		the id used to identify the application
 //		version: 	the version (normally a timestamp) you wish to change to
 func (r *marathonClient) SetApplicationVersion(name string, version *ApplicationVersion) (*DeploymentID, error) {
-	uri := fmt.Sprintf(buildURI(name))
+	path := buildPath(name)
 	deploymentID := new(DeploymentID)
-	if err := r.apiPut(uri, version, deploymentID); err != nil {
+	if err := r.apiPut(path, version, deploymentID); err != nil {
 		return nil, err
 	}
 
@@ -599,7 +714,7 @@ func (r *marathonClient) Application(name string) (*Application, error) {
 		Application *Application `json:"app"`
 	}
 
-	if err := r.apiGet(buildURI(name), nil, &wrapper); err != nil {
+	if err := r.apiGet(buildPath(name), nil, &wrapper); err != nil {
 		return nil, err
 	}
 
@@ -610,7 +725,7 @@ func (r *marathonClient) Application(name string) (*Application, error) {
 // 		name: 		the id used to identify the application
 //		opts:		GetAppOpts request payload
 func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Application, error) {
-	u, err := addOptions(buildURI(name), opts)
+	path, err := addOptions(buildPath(name), opts)
 	if err != nil {
 		return nil, err
 	}
@@ -618,7 +733,7 @@ func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Applicat
 		Application *Application `json:"app"`
 	}
 
-	if err := r.apiGet(u, nil, &wrapper); err != nil {
+	if err := r.apiGet(path, nil, &wrapper); err != nil {
 		return nil, err
 	}
 
@@ -631,8 +746,8 @@ func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Applicat
 func (r *marathonClient) ApplicationByVersion(name, version string) (*Application, error) {
 	app := new(Application)
 
-	uri := fmt.Sprintf("%s/versions/%s", buildURI(name), version)
-	if err := r.apiGet(uri, nil, app); err != nil {
+	path := fmt.Sprintf("%s/versions/%s", buildPath(name), version)
+	if err := r.apiGet(path, nil, app); err != nil {
 		return nil, err
 	}
 
@@ -693,7 +808,7 @@ func (r *marathonClient) ApplicationDeployments(name string) ([]*DeploymentID, e
 // 		application:		the structure holding the application configuration
 func (r *marathonClient) CreateApplication(application *Application) (*Application, error) {
 	result := new(Application)
-	if err := r.apiPost(marathonAPIApps, application, result); err != nil {
+	if err := r.ApiPost(marathonAPIApps, application, result); err != nil {
 		return nil, err
 	}
 
@@ -704,24 +819,7 @@ func (r *marathonClient) CreateApplication(application *Application) (*Applicati
 //		name:		the id of the application
 //		timeout:	a duration of time to wait for an application to deploy
 func (r *marathonClient) WaitOnApplication(name string, timeout time.Duration) error {
-	if r.appExistAndRunning(name) {
-		return nil
-	}
-
-	timeoutTimer := time.After(timeout)
-	ticker := time.NewTicker(r.config.PollingWaitTime)
-	defer ticker.Stop()
-
-	for {
-		select {
-		case <-timeoutTimer:
-			return ErrTimeoutError
-		case <-ticker.C:
-			if r.appExistAndRunning(name) {
-				return nil
-			}
-		}
-	}
+	return r.wait(name, timeout, r.appExistAndRunning)
 }
 
 func (r *marathonClient) appExistAndRunning(name string) bool {
@@ -739,10 +837,10 @@ func (r *marathonClient) appExistAndRunning(name string) bool {
 // 		name: 		the id used to identify the application
 //		force:		used to force the delete operation in case of blocked deployment
 func (r *marathonClient) DeleteApplication(name string, force bool) (*DeploymentID, error) {
-	uri := buildURIWithForceParam(name, force)
+	path := buildPathWithForceParam(name, force)
 	// step: check of the application already exists
 	deployID := new(DeploymentID)
-	if err := r.apiDelete(uri, nil, deployID); err != nil {
+	if err := r.apiDelete(path, nil, deployID); err != nil {
 		return nil, err
 	}
 
@@ -754,8 +852,8 @@ func (r *marathonClient) DeleteApplication(name string, force bool) (*Deployment
 func (r *marathonClient) RestartApplication(name string, force bool) (*DeploymentID, error) {
 	deployment := new(DeploymentID)
 	var options struct{}
-	uri := buildURIWithForceParam(fmt.Sprintf("%s/restart", name), force)
-	if err := r.apiPost(uri, &options, deployment); err != nil {
+	path := buildPathWithForceParam(fmt.Sprintf("%s/restart", name), force)
+	if err := r.ApiPost(path, &options, deployment); err != nil {
 		return nil, err
 	}
 
@@ -770,9 +868,9 @@ func (r *marathonClient) ScaleApplicationInstances(name string, instances int, f
 	changes := new(Application)
 	changes.ID = validateID(name)
 	changes.Instances = &instances
-	uri := buildURIWithForceParam(name, force)
+	path := buildPathWithForceParam(name, force)
 	deployID := new(DeploymentID)
-	if err := r.apiPut(uri, changes, deployID); err != nil {
+	if err := r.apiPut(path, changes, deployID); err != nil {
 		return nil, err
 	}
 
@@ -783,22 +881,22 @@ func (r *marathonClient) ScaleApplicationInstances(name string, instances int, f
 // 		application:		the structure holding the application configuration
 func (r *marathonClient) UpdateApplication(application *Application, force bool) (*DeploymentID, error) {
 	result := new(DeploymentID)
-	uri := buildURIWithForceParam(application.ID, force)
-	if err := r.apiPut(uri, application, result); err != nil {
+	path := buildPathWithForceParam(application.ID, force)
+	if err := r.apiPut(path, application, result); err != nil {
 		return nil, err
 	}
 	return result, nil
 }
 
-func buildURIWithForceParam(path string, force bool) string {
-	uri := buildURI(path)
+func buildPathWithForceParam(rootPath string, force bool) string {
+	path := buildPath(rootPath)
 	if force {
-		uri += "?force=true"
+		path += "?force=true"
 	}
-	return uri
+	return path
 }
 
-func buildURI(path string) string {
+func buildPath(path string) string {
 	return fmt.Sprintf("%s/%s", marathonAPIApps, trimRootPath(path))
 }
 
@@ -867,3 +965,22 @@ func (d *Discovery) AddPort(port Port) *Discovery {
 	d.Ports = &ports
 	return d
 }
+
+// EmptyNetworks explicitly empties networks
+func (r *Application) EmptyNetworks() *Application {
+	r.Networks = &[]PodNetwork{}
+	return r
+}
+
+// SetNetwork sets the networking mode
+func (r *Application) SetNetwork(name string, mode PodNetworkMode) *Application {
+	if r.Networks == nil {
+		r.EmptyNetworks()
+	}
+
+	network := PodNetwork{Name: name, Mode: mode}
+	networks := *r.Networks
+	networks = append(networks, network)
+	r.Networks = &networks
+	return r
+}
diff --git a/application_marshalling.go b/application_marshalling.go
new file mode 100644
index 0000000..8bedd9e
--- /dev/null
+++ b/application_marshalling.go
@@ -0,0 +1,106 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// Alias aliases the Application struct so that it will be marshaled/unmarshaled automatically
+type Alias Application
+
+// TmpEnvSecret holds the secret values deserialized from the environment variables field
+type TmpEnvSecret struct {
+	Secret string `json:"secret,omitempty"`
+}
+
+// TmpSecret holds the deserialized secrets field in a Marathon application configuration
+type TmpSecret struct {
+	Source string `json:"source,omitempty"`
+}
+
+// UnmarshalJSON unmarshals the given Application JSON as expected except for environment variables and secrets.
+// Environment varialbes are stored in the Env field. Secrets, including the environment variable part,
+// are stored in the Secrets field.
+func (app *Application) UnmarshalJSON(b []byte) error {
+	aux := &struct {
+		*Alias
+		Env     map[string]interface{} `json:"env"`
+		Secrets map[string]TmpSecret   `json:"secrets"`
+	}{
+		Alias: (*Alias)(app),
+	}
+	if err := json.Unmarshal(b, aux); err != nil {
+		return fmt.Errorf("malformed application definition %v", err)
+	}
+	env := &map[string]string{}
+	secrets := &map[string]Secret{}
+
+	for envName, genericEnvValue := range aux.Env {
+		switch envValOrSecret := genericEnvValue.(type) {
+		case string:
+			(*env)[envName] = envValOrSecret
+		case map[string]interface{}:
+			for secret, secretStore := range envValOrSecret {
+				if secStore, ok := secretStore.(string); ok && secret == "secret" {
+					(*secrets)[secStore] = Secret{EnvVar: envName}
+					break
+				}
+				return fmt.Errorf("unexpected secret field %v of value type %T", secret, envValOrSecret[secret])
+			}
+		default:
+			return fmt.Errorf("unexpected environment variable type %T", envValOrSecret)
+		}
+	}
+	app.Env = env
+	for k, v := range aux.Secrets {
+		tmp := (*secrets)[k]
+		tmp.Source = v.Source
+		(*secrets)[k] = tmp
+	}
+	app.Secrets = secrets
+	return nil
+}
+
+// MarshalJSON marshals the given Application as expected except for environment variables and secrets,
+// which are marshaled from specialized structs.  The environment variable piece of the secrets and other
+// normal environment variables are combined and marshaled to the env field.  The secrets and the related
+// source are marshaled into the secrets field.
+func (app *Application) MarshalJSON() ([]byte, error) {
+	env := make(map[string]interface{})
+	secrets := make(map[string]TmpSecret)
+
+	if app.Env != nil {
+		for k, v := range *app.Env {
+			env[string(k)] = string(v)
+		}
+	}
+	if app.Secrets != nil {
+		for k, v := range *app.Secrets {
+			env[v.EnvVar] = TmpEnvSecret{Secret: k}
+			secrets[k] = TmpSecret{v.Source}
+		}
+	}
+	aux := &struct {
+		*Alias
+		Env     map[string]interface{} `json:"env,omitempty"`
+		Secrets map[string]TmpSecret   `json:"secrets,omitempty"`
+	}{Alias: (*Alias)(app), Env: env, Secrets: secrets}
+
+	return json.Marshal(aux)
+}
diff --git a/application_marshalling_test.go b/application_marshalling_test.go
new file mode 100644
index 0000000..b581794
--- /dev/null
+++ b/application_marshalling_test.go
@@ -0,0 +1,100 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"encoding/json"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestEnvironmentVariableUnmarshal(t *testing.T) {
+	defaultConfig := NewDefaultConfig()
+	configs := &configContainer{
+		client: &defaultConfig,
+		server: &serverConfig{
+			scope: "environment-variables",
+		},
+	}
+
+	endpoint := newFakeMarathonEndpoint(t, configs)
+	defer endpoint.Close()
+
+	application, err := endpoint.Client.Application(fakeAppName)
+	require.NoError(t, err)
+
+	env := application.Env
+	secrets := application.Secrets
+
+	require.NotNil(t, env)
+	assert.Equal(t, "bar", (*env)["FOO"])
+	assert.Equal(t, "TOP", (*secrets)["secret"].EnvVar)
+	assert.Equal(t, "/path/to/secret", (*secrets)["secret"].Source)
+}
+
+func TestMalformedPayloadUnmarshal(t *testing.T) {
+	var tests = []struct {
+		expected    string
+		given       []byte
+		description string
+	}{
+		{
+			expected:    "unexpected secret field",
+			given:       []byte(`{"env": {"FOO": "bar", "SECRET": {"not_secret": "secret1"}}, "secrets": {"secret1": {"source": "/path/to/secret"}}}`),
+			description: "Field in environment secret not equal to secret.",
+		},
+		{
+			expected:    "unexpected secret field",
+			given:       []byte(`{"env": {"FOO": "bar", "SECRET": {"secret": 1}}, "secrets": {"secret1": {"source": "/path/to/secret"}}}`),
+			description: "Invalid value in environment secret.",
+		},
+		{
+			expected:    "unexpected environment variable type",
+			given:       []byte(`{"env": {"FOO": 1, "SECRET": {"secret": "secret1"}}, "secrets": {"secret1": {"source": "/path/to/secret"}}}`),
+			description: "Invalid environment variable type.",
+		},
+		{
+			expected:    "malformed application definition",
+			given:       []byte(`{"env": "value"}`),
+			description: "Bad application definition.",
+		},
+	}
+
+	for _, test := range tests {
+		tmpApp := new(Application)
+
+		err := json.Unmarshal(test.given, &tmpApp)
+		if assert.Error(t, err, test.description) {
+			assert.True(t, strings.HasPrefix(err.Error(), test.expected), test.description)
+		}
+	}
+}
+
+func TestEnvironmentVariableMarshal(t *testing.T) {
+	testApp := new(Application)
+	targetString := []byte(`{"ports":null,"dependencies":null,"env":{"FOO":"bar","TOP":{"secret":"secret1"}},"secrets":{"secret1":{"source":"/path/to/secret"}}}`)
+	testApp.AddEnv("FOO", "bar")
+	testApp.AddSecret("TOP", "secret1", "/path/to/secret")
+
+	app, err := json.Marshal(testApp)
+	if assert.NoError(t, err) {
+		assert.Equal(t, targetString, app)
+	}
+}
diff --git a/application_test.go b/application_test.go
index d4fe3c6..898dd3e 100644
--- a/application_test.go
+++ b/application_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -18,12 +18,15 @@ package marathon
 
 import (
 	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/url"
+	"strings"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestApplicationDependsOn(t *testing.T) {
@@ -39,6 +42,71 @@ func TestApplicationMemory(t *testing.T) {
 	assert.Equal(t, 50.0, *app.Mem)
 }
 
+func TestApplicationString(t *testing.T) {
+	type test struct {
+		name                string
+		app                 *Application
+		expectedAppJSONPath string
+		setup               func(*Application)
+	}
+
+	tests := []test{
+		{
+			name: "marathon < 1.5",
+			app: NewDockerApplication().
+				Name("my-app").
+				CPU(0.1).
+				Memory(64).
+				Storage(0.0).
+				Count(2).
+				AddArgs("/usr/sbin/apache2ctl", "-D", "FOREGROUND").
+				AddEnv("NAME", "frontend_http").
+				AddEnv("SERVICE_80_NAME", "test_http"),
+			expectedAppJSONPath: "tests/app-definitions/TestApplicationString-output.json",
+			setup: func(app *Application) {
+				app.
+					Container.Docker.Container("quay.io/gambol99/apache-php:latest").
+					Bridged().
+					Expose(80).
+					Expose(443)
+			},
+		},
+		{
+			name: "marathon > 1.5",
+			app: NewDockerApplication().
+				Name("my-app").
+				CPU(0.1).
+				Memory(64).
+				Storage(0.0).
+				Count(2).
+				SetNetwork("", "container/bridge").
+				AddArgs("/usr/sbin/apache2ctl", "-D", "FOREGROUND").
+				AddEnv("NAME", "frontend_http").
+				AddEnv("SERVICE_80_NAME", "test_http"),
+			expectedAppJSONPath: "tests/app-definitions/TestApplicationString-1.5-output.json",
+			setup: func(app *Application) {
+				app.
+					Container.Expose(80).Expose(443).
+					Docker.Container("quay.io/gambol99/apache-php:latest")
+			},
+		},
+	}
+
+	for _, test := range tests {
+		label := fmt.Sprintf("test: %s", test.name)
+
+		test.setup(test.app)
+		_, err := test.app.CheckHTTP("/health", 80, 5)
+		assert.Nil(t, err)
+
+		expectedAppJSONBytes, err := ioutil.ReadFile(test.expectedAppJSONPath)
+		if err != nil {
+			panic(err)
+		}
+		expectedAppJSON := strings.TrimSpace(string(expectedAppJSONBytes))
+		assert.Equal(t, expectedAppJSON, test.app.String(), label)
+	}
+}
 func TestApplicationCount(t *testing.T) {
 	app := NewDockerApplication()
 	assert.Nil(t, app.Instances)
@@ -100,6 +168,20 @@ func TestApplicationCPU(t *testing.T) {
 	assert.Equal(t, 0.1, app.CPUs)
 }
 
+func TestApplicationSetGPUs(t *testing.T) {
+	app := NewDockerApplication()
+	assert.Nil(t, app.GPUs)
+	app.SetGPUs(0.1)
+	assert.Equal(t, 0.1, *app.GPUs)
+}
+
+func TestApplicationEmptyGPUs(t *testing.T) {
+	app := NewDockerApplication()
+	assert.Nil(t, app.GPUs)
+	app.EmptyGPUs()
+	assert.Equal(t, 0.0, *app.GPUs)
+}
+
 func TestApplicationArgs(t *testing.T) {
 	app := NewDockerApplication()
 	assert.Nil(t, app.Args)
@@ -156,15 +238,30 @@ func TestApplicationEnvs(t *testing.T) {
 	assert.Nil(t, app.Env)
 
 	app.AddEnv("hello", "world").AddEnv("foo", "bar")
-	assert.Equal(t, 2, len(*app.Env))
-	assert.Equal(t, "world", (*app.Env)["hello"])
-	assert.Equal(t, "bar", (*app.Env)["foo"])
-
+	if assert.Equal(t, 2, len((*app.Env))) {
+		assert.Equal(t, "world", (*app.Env)["hello"])
+		assert.Equal(t, "bar", (*app.Env)["foo"])
+	}
 	app.EmptyEnvs()
 	assert.NotNil(t, app.Env)
 	assert.Equal(t, 0, len(*app.Env))
 }
 
+func TestApplicationSecrets(t *testing.T) {
+	app := NewDockerApplication()
+	assert.Nil(t, app.Env)
+
+	app.AddSecret("MY_FIRST_SECRET", "secret0", "path/to/my/secret")
+	app.AddSecret("MY_SECOND_SECRET", "secret1", "path/to/my/other/secret")
+	if assert.Equal(t, 2, len(*app.Secrets)) {
+		assert.Equal(t, Secret{EnvVar: "MY_FIRST_SECRET", Source: "path/to/my/secret"}, (*app.Secrets)["secret0"])
+		assert.Equal(t, Secret{EnvVar: "MY_SECOND_SECRET", Source: "path/to/my/other/secret"}, (*app.Secrets)["secret1"])
+	}
+	app.EmptySecrets()
+	assert.NotNil(t, app.Secrets)
+	assert.Equal(t, 0, len(*app.Secrets))
+}
+
 func TestApplicationSetExecutor(t *testing.T) {
 	app := NewDockerApplication()
 	assert.Nil(t, app.Executor)
@@ -179,28 +276,47 @@ func TestApplicationSetExecutor(t *testing.T) {
 func TestApplicationHealthChecks(t *testing.T) {
 	app := NewDockerApplication()
 	assert.Nil(t, app.HealthChecks)
-	app.AddHealthCheck(HealthCheck{}.SetPath("/check1")).
-		AddHealthCheck(HealthCheck{}.SetPath("/check2"))
+	hc1 := NewDefaultHealthCheck()
+	hc2 := NewDefaultHealthCheck()
+	app.AddHealthCheck(*hc1).AddHealthCheck(*hc2)
 
 	assert.Equal(t, 2, len(*app.HealthChecks))
-	assert.Equal(t, HealthCheck{}.SetPath("/check1"), (*app.HealthChecks)[0])
-	assert.Equal(t, HealthCheck{}.SetPath("/check2"), (*app.HealthChecks)[1])
+	assert.Equal(t, *hc1, (*app.HealthChecks)[0])
+	assert.Equal(t, *hc2, (*app.HealthChecks)[1])
 
 	app.EmptyHealthChecks()
 	assert.NotNil(t, app.HealthChecks)
 	assert.Equal(t, 0, len(*app.HealthChecks))
 }
 
+func TestApplicationReadinessChecks(t *testing.T) {
+	app := NewDockerApplication()
+	require.Nil(t, app.HealthChecks)
+	rc := ReadinessCheck{}
+	rc.SetName("/readiness")
+	app.AddReadinessCheck(rc)
+
+	require.Equal(t, 1, len(*app.ReadinessChecks))
+	assert.Equal(t, "/readiness", *((*app.ReadinessChecks)[0].Name))
+
+	app.EmptyReadinessChecks()
+	require.NotNil(t, app.ReadinessChecks)
+	assert.Equal(t, 0, len(*app.ReadinessChecks))
+}
+
 func TestApplicationPortDefinitions(t *testing.T) {
 	app := NewDockerApplication()
 	assert.Nil(t, app.PortDefinitions)
-	app.AddPortDefinition(PortDefinition{Protocol: "tcp", Name: "es"}.SetPort(9201).AddLabel("foo", "bar")).
-		AddPortDefinition(PortDefinition{Protocol: "udp,tcp", Name: "syslog"}.SetPort(514))
+	pd1 := new(PortDefinition)
+	pd1.SetProtocol("tcp").SetName("es").SetPort(9092).AddLabel("foo", "bar")
+	pd2 := new(PortDefinition)
+	pd2.SetProtocol("udp,tcp").SetName("syslog").SetPort(514)
+	app.AddPortDefinition(*pd1).AddPortDefinition(*pd2)
 
 	assert.Equal(t, 2, len(*app.PortDefinitions))
-	assert.Equal(t, PortDefinition{Protocol: "tcp", Name: "es"}.SetPort(9201).AddLabel("foo", "bar"), (*app.PortDefinitions)[0])
+	assert.Equal(t, *pd1, (*app.PortDefinitions)[0])
 	assert.Equal(t, 1, len(*(*app.PortDefinitions)[0].Labels))
-	assert.Equal(t, PortDefinition{Protocol: "udp,tcp", Name: "syslog"}.SetPort(514), (*app.PortDefinitions)[1])
+	assert.Equal(t, *pd2, (*app.PortDefinitions)[1])
 	assert.Nil(t, (*app.PortDefinitions)[1].Labels)
 
 	(*app.PortDefinitions)[0].EmptyLabels()
@@ -213,45 +329,87 @@ func TestApplicationPortDefinitions(t *testing.T) {
 }
 
 func TestHasHealthChecks(t *testing.T) {
-	app := NewDockerApplication()
-	assert.False(t, app.HasHealthChecks())
-	app.Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80)
-	_, err := app.CheckTCP(80, 10)
-	assert.NoError(t, err)
-	assert.True(t, app.HasHealthChecks())
+	apps := []*Application{
+		NewDockerApplication(),
+		NewDockerApplication(),
+	}
+
+	for i := range apps {
+		assert.False(t, apps[i].HasHealthChecks())
+	}
+
+	// Marathon < 1.5
+	apps[0].Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80)
+
+	// Marathon >= 1.5
+	apps[1].Container.Expose(80).Docker.Container("quay.io/gambol99/apache-php:latest")
+
+	for i := range apps {
+		_, err := apps[i].CheckTCP(80, 10)
+		assert.NoError(t, err)
+		assert.True(t, apps[i].HasHealthChecks())
+	}
 }
 
 func TestApplicationCheckTCP(t *testing.T) {
-	app := NewDockerApplication()
-	assert.False(t, app.HasHealthChecks())
-	_, err := app.CheckTCP(80, 10)
-	assert.Error(t, err)
-	assert.False(t, app.HasHealthChecks())
-	app.Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80)
-	_, err = app.CheckTCP(80, 10)
-	assert.NoError(t, err)
-	assert.True(t, app.HasHealthChecks())
-	check := (*app.HealthChecks)[0]
-	assert.Equal(t, "TCP", check.Protocol)
-	assert.Equal(t, 10, check.IntervalSeconds)
-	assert.Equal(t, 0, *check.PortIndex)
+	apps := []*Application{
+		NewDockerApplication(),
+		NewDockerApplication(),
+	}
+
+	for i := range apps {
+		assert.False(t, apps[i].HasHealthChecks())
+		_, err := apps[i].CheckTCP(80, 10)
+		assert.Error(t, err)
+		assert.False(t, apps[i].HasHealthChecks())
+	}
+
+	// Marathon < 1.5
+	apps[0].Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80)
+
+	// Marathon >= 1.5
+	apps[1].Container.Expose(80).Docker.Container("quay.io/gambol99/apache-php:latest")
+
+	for i := range apps {
+		_, err := apps[i].CheckTCP(80, 10)
+		assert.NoError(t, err)
+		assert.True(t, apps[i].HasHealthChecks())
+		check := (*apps[i].HealthChecks)[0]
+		assert.Equal(t, "TCP", check.Protocol)
+		assert.Equal(t, 10, check.IntervalSeconds)
+		assert.Equal(t, 0, *check.PortIndex)
+	}
 }
 
 func TestApplicationCheckHTTP(t *testing.T) {
-	app := NewDockerApplication()
-	assert.False(t, app.HasHealthChecks())
-	_, err := app.CheckHTTP("/", 80, 10)
-	assert.Error(t, err)
-	assert.False(t, app.HasHealthChecks())
-	app.Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80)
-	_, err = app.CheckHTTP("/health", 80, 10)
-	assert.NoError(t, err)
-	assert.True(t, app.HasHealthChecks())
-	check := (*app.HealthChecks)[0]
-	assert.Equal(t, "HTTP", check.Protocol)
-	assert.Equal(t, 10, check.IntervalSeconds)
-	assert.Equal(t, "/health", *check.Path)
-	assert.Equal(t, 0, *check.PortIndex)
+	apps := []*Application{
+		NewDockerApplication(),
+		NewDockerApplication(),
+	}
+
+	for i := range apps {
+		assert.False(t, apps[i].HasHealthChecks())
+		_, err := apps[i].CheckHTTP("/", 80, 10)
+		assert.Error(t, err)
+		assert.False(t, apps[i].HasHealthChecks())
+	}
+
+	// Marathon < 1.5
+	apps[0].Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80)
+
+	// Marathon >= 1.5
+	apps[1].Container.Expose(80).Docker.Container("quay.io/gambol99/apache-php:latest")
+
+	for i := range apps {
+		_, err := apps[i].CheckHTTP("/health", 80, 10)
+		assert.NoError(t, err)
+		assert.True(t, apps[i].HasHealthChecks())
+		check := (*apps[i].HealthChecks)[0]
+		assert.Equal(t, "HTTP", check.Protocol)
+		assert.Equal(t, 10, check.IntervalSeconds)
+		assert.Equal(t, "/health", *check.Path)
+		assert.Equal(t, 0, *check.PortIndex)
+	}
 }
 
 func TestCreateApplication(t *testing.T) {
@@ -289,6 +447,8 @@ func TestApplications(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NotNil(t, applications)
 	assert.Equal(t, len(applications.Apps), 2)
+	assert.Equal(t, (*applications.Apps[0].Secrets)["secret0"].EnvVar, "SECRET1")
+	assert.Equal(t, (*applications.Apps[0].Secrets)["secret0"].Source, "secret/definition/id")
 
 	v := url.Values{}
 	v.Set("cmd", "nginx")
@@ -313,6 +473,32 @@ func TestApplicationsEmbedTaskStats(t *testing.T) {
 	assert.Equal(t, applications.Apps[0].TaskStats["startedAfterLastScaling"].Stats.LifeTime["averageSeconds"], 17024.575)
 }
 
+func TestApplicationsEmbedReadiness(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	v := url.Values{}
+	v.Set("embed", "apps.readiness")
+	applications, err := endpoint.Client.Applications(v)
+	require.NoError(t, err)
+	require.NotNil(t, applications)
+	require.Equal(t, len(applications.Apps), 1)
+	require.NotNil(t, applications.Apps[0].ReadinessCheckResults)
+	require.True(t, len(*applications.Apps[0].ReadinessCheckResults) > 0)
+	actualRes := (*applications.Apps[0].ReadinessCheckResults)[0]
+	expectedRes := ReadinessCheckResult{
+		Name:   "myReadyCheck",
+		TaskID: "test_frontend_app1.c9de6033",
+		Ready:  false,
+		LastResponse: ReadinessLastResponse{
+			Body:        "{}",
+			ContentType: "application/json",
+			Status:      500,
+		},
+	}
+	assert.Equal(t, expectedRes, actualRes)
+}
+
 func TestListApplications(t *testing.T) {
 	endpoint := newFakeMarathonEndpoint(t, nil)
 	defer endpoint.Close()
@@ -336,7 +522,7 @@ func TestApplicationVersions(t *testing.T) {
 	assert.Equal(t, len(versions.Versions), 1)
 	assert.Equal(t, versions.Versions[0], "2014-04-04T06:25:31.399Z")
 	/* check we get an error on app not there */
-	versions, err = endpoint.Client.ApplicationVersions("/not/there")
+	_, err = endpoint.Client.ApplicationVersions("/not/there")
 	assert.Error(t, err)
 }
 
@@ -378,9 +564,9 @@ func TestApplicationFetchURIs(t *testing.T) {
 	assert.Equal(t, Fetch{URI: "file://uri2.tar.gz"}, (*app.Fetch)[1])
 	assert.Equal(t, Fetch{URI: "file://uri3.tar.gz"}, (*app.Fetch)[2])
 
-	app.EmptyUris()
-	assert.NotNil(t, app.Uris)
-	assert.Equal(t, 0, len(*app.Uris))
+	app.EmptyFetchURIs()
+	assert.NotNil(t, app.Fetch)
+	assert.Equal(t, 0, len(*app.Fetch))
 }
 
 func TestSetApplicationVersion(t *testing.T) {
@@ -411,15 +597,16 @@ func TestHasApplicationVersion(t *testing.T) {
 }
 
 func TestDeleteApplication(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
 	for _, force := range []bool{false, true} {
-		endpoint := newFakeMarathonEndpoint(t, nil)
-		defer endpoint.Close()
 		id, err := endpoint.Client.DeleteApplication(fakeAppName, force)
 		assert.NoError(t, err)
 		assert.NotNil(t, id)
 		assert.Equal(t, "83b215a6-4e26-4e44-9333-5c385eda6438", id.DeploymentID)
 		assert.Equal(t, "2014-08-26T07:37:50.462Z", id.Version)
-		id, err = endpoint.Client.DeleteApplication("no_such_app", force)
+		_, err = endpoint.Client.DeleteApplication("no_such_app", force)
 		assert.Error(t, err)
 	}
 }
@@ -446,6 +633,10 @@ func verifyApplication(application *Application, t *testing.T) {
 	assert.NotNil(t, application.Tasks)
 	assert.Equal(t, len(*application.HealthChecks), 1)
 	assert.Equal(t, len(application.Tasks), 2)
+	assert.Equal(t, application.Residency, &Residency{
+		TaskLostBehavior:                 TaskLostBehaviorTypeRelaunchAfterTimeout,
+		RelaunchEscalationTimeoutSeconds: 60,
+	})
 }
 
 func TestApplication(t *testing.T) {
@@ -608,19 +799,20 @@ func TestIPAddressPerTask(t *testing.T) {
 	ipPerTask.
 		AddGroup("label").
 		AddLabel("key", "value").
-		SetDiscovery(Discovery{})
+		SetDiscovery(Discovery{
+			Ports: &[]Port{},
+		})
 
 	assert.Equal(t, 1, len(*ipPerTask.Groups))
 	assert.Equal(t, "label", (*ipPerTask.Groups)[0])
 	assert.Equal(t, "value", (*ipPerTask.Labels)["key"])
-	assert.NotEmpty(t, ipPerTask.Discovery)
+	assert.NotEmpty(t, *ipPerTask.Discovery)
 
 	ipPerTask.EmptyGroups()
 	assert.Equal(t, 0, len(*ipPerTask.Groups))
 
 	ipPerTask.EmptyLabels()
 	assert.Equal(t, 0, len(*ipPerTask.Labels))
-
 }
 
 func TestIPAddressPerTaskDiscovery(t *testing.T) {
@@ -640,10 +832,12 @@ func TestIPAddressPerTaskDiscovery(t *testing.T) {
 func TestUpgradeStrategy(t *testing.T) {
 	app := Application{}
 	assert.Nil(t, app.UpgradeStrategy)
-	app.SetUpgradeStrategy(UpgradeStrategy{}.SetMinimumHealthCapacity(1.0).SetMaximumOverCapacity(0.0))
-	us := app.UpgradeStrategy
-	assert.Equal(t, 1.0, *us.MinimumHealthCapacity)
-	assert.Equal(t, 0.0, *us.MaximumOverCapacity)
+	us := new(UpgradeStrategy)
+	us.SetMinimumHealthCapacity(1.0).SetMaximumOverCapacity(0.0)
+	app.SetUpgradeStrategy(*us)
+	testUs := app.UpgradeStrategy
+	assert.Equal(t, 1.0, *testUs.MinimumHealthCapacity)
+	assert.Equal(t, 0.0, *testUs.MaximumOverCapacity)
 
 	app.EmptyUpgradeStrategy()
 	us = app.UpgradeStrategy
@@ -651,3 +845,24 @@ func TestUpgradeStrategy(t *testing.T) {
 	assert.Nil(t, us.MinimumHealthCapacity)
 	assert.Nil(t, us.MaximumOverCapacity)
 }
+
+func TestBridgedNetworking(t *testing.T) {
+	app := NewDockerApplication().SetNetwork("test", "container/bridge")
+	networks := *app.Networks
+
+	assert.Equal(t, networks[0].Mode, BridgeNetworkMode)
+}
+
+func TestContainerNetworking(t *testing.T) {
+	app := NewDockerApplication().SetNetwork("test", "container")
+	networks := *app.Networks
+
+	assert.Equal(t, networks[0].Mode, ContainerNetworkMode)
+}
+
+func TestHostNetworking(t *testing.T) {
+	app := NewDockerApplication().SetNetwork("test", "host")
+	networks := *app.Networks
+
+	assert.Equal(t, networks[0].Mode, HostNetworkMode)
+}
diff --git a/client.go b/client.go
index 6ad34e4..42e5ea5 100644
--- a/client.go
+++ b/client.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -24,15 +24,21 @@ import (
 	"io"
 	"io/ioutil"
 	"log"
+	"net"
 	"net/http"
 	"net/url"
 	"regexp"
+	"strings"
 	"sync"
 	"time"
 )
 
 // Marathon is the interface to the marathon API
 type Marathon interface {
+	// -- GENERIC API ACCESS ---
+
+	ApiPost(path string, post, result interface{}) error
+
 	// -- APPLICATIONS ---
 
 	// get a listing of the application ids
@@ -68,6 +74,40 @@ type Marathon interface {
 	// wait of application
 	WaitOnApplication(name string, timeout time.Duration) error
 
+	// -- PODS ---
+	// whether this version of Marathon supports pods
+	SupportsPods() (bool, error)
+
+	// get pod status
+	PodStatus(name string) (*PodStatus, error)
+	// get all pod statuses
+	PodStatuses() ([]*PodStatus, error)
+
+	// get pod
+	Pod(name string) (*Pod, error)
+	// get all pods
+	Pods() ([]Pod, error)
+	// create pod
+	CreatePod(pod *Pod) (*Pod, error)
+	// update pod
+	UpdatePod(pod *Pod, force bool) (*Pod, error)
+	// delete pod
+	DeletePod(name string, force bool) (*DeploymentID, error)
+	// wait on pod to be deployed
+	WaitOnPod(name string, timeout time.Duration) error
+	// check if a pod is running
+	PodIsRunning(name string) bool
+
+	// get versions of a pod
+	PodVersions(name string) ([]string, error)
+	// get pod by version
+	PodByVersion(name, version string) (*Pod, error)
+
+	// delete instances of a pod
+	DeletePodInstances(name string, instances []string) ([]*PodInstance, error)
+	// delete pod instance
+	DeletePodInstance(name, instance string) (*PodInstance, error)
+
 	// -- TASKS ---
 
 	// get a list of tasks for a specific application
@@ -149,12 +189,28 @@ type Marathon interface {
 }
 
 var (
-	// ErrInvalidResponse is thrown when marathon responds with invalid or error response
-	ErrInvalidResponse = errors.New("invalid response from Marathon")
 	// ErrMarathonDown is thrown when all the marathon endpoints are down
 	ErrMarathonDown = errors.New("all the Marathon hosts are presently down")
 	// ErrTimeoutError is thrown when the operation has timed out
 	ErrTimeoutError = errors.New("the operation has timed out")
+
+	// Default HTTP client used for SSE subscription requests
+	// It is invalid to set client.Timeout because it includes time to read response so
+	// set dial, tls handshake and response header timeouts instead
+	defaultHTTPSSEClient = &http.Client{
+		Transport: &http.Transport{
+			Dial: (&net.Dialer{
+				Timeout: 5 * time.Second,
+			}).Dial,
+			ResponseHeaderTimeout: 10 * time.Second,
+			TLSHandshakeTimeout:   5 * time.Second,
+		},
+	}
+
+	// Default HTTP client used for non SSE requests
+	defaultHTTPClient = &http.Client{
+		Timeout: 10 * time.Second,
+	}
 )
 
 // EventsChannelContext holds contextual data for an EventsChannel.
@@ -174,22 +230,41 @@ type marathonClient struct {
 	ipAddress string
 	// the http server
 	eventsHTTP *http.Server
-	// the http client use for making requests
-	httpClient *http.Client
 	// the marathon hosts
 	hosts *cluster
 	// a map of service you wish to listen to
 	listeners map[EventsChannel]EventsChannelContext
-	// a custom logger for debug log messages
-	debugLog *log.Logger
+	// a custom log function for debug messages
+	debugLog func(format string, v ...interface{})
+	// the marathon HTTP client to ensure consistency in requests
+	client *httpClient
+}
+
+type httpClient struct {
+	// the configuration for the marathon HTTP client
+	config Config
+}
+
+// newRequestError signals that creating a new http.Request failed
+type newRequestError struct {
+	error
 }
 
 // NewClient creates a new marathon client
 //		config:			the configuration to use
 func NewClient(config Config) (Marathon, error) {
-	// step: if no http client, set to default
+	// step: if the SSE HTTP client is missing, prefer a configured regular
+	// client, and otherwise use the default SSE HTTP client.
+	if config.HTTPSSEClient == nil {
+		config.HTTPSSEClient = defaultHTTPSSEClient
+		if config.HTTPClient != nil {
+			config.HTTPSSEClient = config.HTTPClient
+		}
+	}
+
+	// step: if a regular HTTP client is missing, use the default one.
 	if config.HTTPClient == nil {
-		config.HTTPClient = http.DefaultClient
+		config.HTTPClient = defaultHTTPClient
 	}
 
 	// step: if no polling wait time is set, default to 500 milliseconds.
@@ -197,23 +272,29 @@ func NewClient(config Config) (Marathon, error) {
 		config.PollingWaitTime = defaultPollingWaitTime
 	}
 
+	// step: setup shared client
+	client := &httpClient{config: config}
+
 	// step: create a new cluster
-	hosts, err := newCluster(config.HTTPClient, config.URL)
+	hosts, err := newCluster(client, config.URL, config.DCOSToken != "")
 	if err != nil {
 		return nil, err
 	}
 
-	debugLogOutput := config.LogOutput
-	if debugLogOutput == nil {
-		debugLogOutput = ioutil.Discard
+	debugLog := func(string, ...interface{}) {}
+	if config.LogOutput != nil {
+		logger := log.New(config.LogOutput, "", 0)
+		debugLog = func(format string, v ...interface{}) {
+			logger.Printf(format, v...)
+		}
 	}
 
 	return &marathonClient{
-		config:     config,
-		listeners:  make(map[EventsChannel]EventsChannelContext),
-		hosts:      hosts,
-		httpClient: config.HTTPClient,
-		debugLog:   log.New(debugLogOutput, "", 0),
+		config:    config,
+		listeners: make(map[EventsChannel]EventsChannelContext),
+		hosts:     hosts,
+		debugLog:  debugLog,
+		client:    client,
 	}, nil
 }
 
@@ -230,54 +311,51 @@ func (r *marathonClient) Ping() (bool, error) {
 	return true, nil
 }
 
-func (r *marathonClient) apiGet(uri string, post, result interface{}) error {
-	return r.apiCall("GET", uri, post, result)
+func (r *marathonClient) apiHead(path string, result interface{}) error {
+	return r.apiCall("HEAD", path, nil, result)
 }
 
-func (r *marathonClient) apiPut(uri string, post, result interface{}) error {
-	return r.apiCall("PUT", uri, post, result)
+func (r *marathonClient) apiGet(path string, post, result interface{}) error {
+	return r.apiCall("GET", path, post, result)
 }
 
-func (r *marathonClient) apiPost(uri string, post, result interface{}) error {
-	return r.apiCall("POST", uri, post, result)
+func (r *marathonClient) apiPut(path string, post, result interface{}) error {
+	return r.apiCall("PUT", path, post, result)
 }
 
-func (r *marathonClient) apiDelete(uri string, post, result interface{}) error {
-	return r.apiCall("DELETE", uri, post, result)
+func (r *marathonClient) ApiPost(path string, post, result interface{}) error {
+	return r.apiCall("POST", path, post, result)
 }
 
-func (r *marathonClient) apiCall(method, uri string, body, result interface{}) error {
-	for {
-		// step: grab a member from the cluster and attempt to perform the request
-		member, err := r.hosts.getMember()
-		if err != nil {
-			return ErrMarathonDown
-		}
+func (r *marathonClient) apiDelete(path string, post, result interface{}) error {
+	return r.apiCall("DELETE", path, post, result)
+}
 
-		// step: Create the endpoint url
-		url := fmt.Sprintf("%s/%s", member, uri)
-		if r.config.DCOSToken != "" {
-			url = fmt.Sprintf("%s/%s", member+"/marathon", uri)
-		}
+func (r *marathonClient) apiCall(method, path string, body, result interface{}) error {
+	const deploymentHeader = "Marathon-Deployment-Id"
 
+	for {
 		// step: marshall the request to json
 		var requestBody []byte
+		var err error
 		if body != nil {
 			if requestBody, err = json.Marshal(body); err != nil {
 				return err
 			}
 		}
 
-		// step: create the api request
-		request, err := r.buildAPIRequest(method, url, bytes.NewReader(requestBody))
+		// step: create the API request
+		request, member, err := r.buildAPIRequest(method, path, bytes.NewReader(requestBody))
 		if err != nil {
 			return err
 		}
-		response, err := r.httpClient.Do(request)
+
+		// step: perform the API request
+		response, err := r.client.Do(request)
 		if err != nil {
 			r.hosts.markDown(member)
 			// step: attempt the request on another member
-			r.debugLog.Printf("apiCall(): request failed on host: %s, error: %s, trying another\n", member, err)
+			r.debugLog("apiCall(): request failed on host: %s, error: %s, trying another", member, err)
 			continue
 		}
 		defer response.Body.Close()
@@ -289,17 +367,29 @@ func (r *marathonClient) apiCall(method, uri string, body, result interface{}) e
 		}
 
 		if len(requestBody) > 0 {
-			r.debugLog.Printf("apiCall(): %v %v %s returned %v %s\n", request.Method, request.URL.String(), requestBody, response.Status, oneLogLine(respBody))
+			r.debugLog("apiCall(): %v %v %s returned %v %s", request.Method, request.URL.String(), requestBody, response.Status, oneLogLine(respBody))
 		} else {
-			r.debugLog.Printf("apiCall(): %v %v returned %v %s\n", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
+			r.debugLog("apiCall(): %v %v returned %v %s", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
 		}
 
-		// step: check for a successfull response
+		// step: check for a successful response
 		if response.StatusCode >= 200 && response.StatusCode <= 299 {
 			if result != nil {
-				if err := json.Unmarshal(respBody, result); err != nil {
-					r.debugLog.Printf("apiCall(): failed to unmarshall the response from marathon, error: %s\n", err)
-					return ErrInvalidResponse
+				// If we have a deployment ID header and no response body, give them that
+				// This specifically handles the use case of a DELETE on an app/pod
+				// We need a way to retrieve the deployment ID
+				deploymentID := response.Header.Get(deploymentHeader)
+				if len(respBody) == 0 && deploymentID != "" {
+					d := DeploymentID{
+						DeploymentID: deploymentID,
+					}
+					if deployID, ok := result.(*DeploymentID); ok {
+						*deployID = d
+					}
+				} else {
+					if err := json.Unmarshal(respBody, result); err != nil {
+						return fmt.Errorf("failed to unmarshal response from Marathon: %s", err)
+					}
 				}
 			}
 			return nil
@@ -309,7 +399,7 @@ func (r *marathonClient) apiCall(method, uri string, body, result interface{}) e
 		if response.StatusCode >= 500 && response.StatusCode <= 599 {
 			// step: mark the host as down
 			r.hosts.markDown(member)
-			r.debugLog.Printf("apiCall(): request failed, host: %s, status: %d, trying another\n", member, response.StatusCode)
+			r.debugLog("apiCall(): request failed, host: %s, status: %d, trying another", member, response.StatusCode)
 			continue
 		}
 
@@ -317,29 +407,88 @@ func (r *marathonClient) apiCall(method, uri string, body, result interface{}) e
 	}
 }
 
-// buildAPIRequest creates a default API request
-func (r *marathonClient) buildAPIRequest(method, url string, reader io.Reader) (*http.Request, error) {
-	// Make the http request to Marathon
-	request, err := http.NewRequest(method, url, reader)
+// wait waits until the provided function returns true (or times out)
+func (r *marathonClient) wait(name string, timeout time.Duration, fn func(string) bool) error {
+	timer := time.NewTimer(timeout)
+	defer timer.Stop()
+
+	ticker := time.NewTicker(r.config.PollingWaitTime)
+	defer ticker.Stop()
+	for {
+		if fn(name) {
+			return nil
+		}
+
+		select {
+		case <-timer.C:
+			return ErrTimeoutError
+		case <-ticker.C:
+			continue
+		}
+	}
+}
+
+// buildAPIRequest creates a default API request.
+// It fails when there is no available member in the cluster anymore or when the request can not be built.
+func (r *marathonClient) buildAPIRequest(method, path string, reader io.Reader) (request *http.Request, member string, err error) {
+	// Grab a member from the cluster
+	member, err = r.hosts.getMember()
+	if err != nil {
+		return nil, "", ErrMarathonDown
+	}
+
+	// Build the HTTP request to Marathon
+	request, err = r.client.buildMarathonJSONRequest(method, member, path, reader)
+	if err != nil {
+		return nil, member, newRequestError{err}
+	}
+	return request, member, nil
+}
+
+// buildMarathonJSONRequest is like buildMarathonRequest but sets the
+// Content-Type and Accept headers to application/json.
+func (rc *httpClient) buildMarathonJSONRequest(method, member, path string, reader io.Reader) (request *http.Request, err error) {
+	req, err := rc.buildMarathonRequest(method, member, path, reader)
+	if err == nil {
+		req.Header.Add("Content-Type", "application/json")
+		req.Header.Add("Accept", "application/json")
+	}
+
+	return req, err
+}
+
+// buildMarathonRequest creates a new HTTP request and configures it according to the *httpClient configuration.
+// The path must not contain a leading "/", otherwise buildMarathonRequest will panic.
+func (rc *httpClient) buildMarathonRequest(method, member, path string, reader io.Reader) (request *http.Request, err error) {
+	if strings.HasPrefix(path, "/") {
+		panic(fmt.Sprintf("Path '%s' must not start with a leading slash", path))
+	}
+
+	// Create the endpoint URL
+	url := fmt.Sprintf("%s/%s", member, path)
+
+	// Instantiate an HTTP request
+	request, err = http.NewRequest(method, url, reader)
 	if err != nil {
 		return nil, err
 	}
 
 	// Add any basic auth and the content headers
-	if r.config.HTTPBasicAuthUser != "" && r.config.HTTPBasicPassword != "" {
-		request.SetBasicAuth(r.config.HTTPBasicAuthUser, r.config.HTTPBasicPassword)
+	if rc.config.HTTPBasicAuthUser != "" && rc.config.HTTPBasicPassword != "" {
+		request.SetBasicAuth(rc.config.HTTPBasicAuthUser, rc.config.HTTPBasicPassword)
 	}
 
-	if r.config.DCOSToken != "" {
-		request.Header.Add("Authorization", "token="+r.config.DCOSToken)
+	if rc.config.DCOSToken != "" {
+		request.Header.Add("Authorization", "token="+rc.config.DCOSToken)
 	}
 
-	request.Header.Add("Content-Type", "application/json")
-	request.Header.Add("Accept", "application/json")
-
 	return request, nil
 }
 
+func (rc *httpClient) Do(request *http.Request) (response *http.Response, err error) {
+	return rc.config.HTTPClient.Do(request)
+}
+
 var oneLogLineRegex = regexp.MustCompile(`(?m)^\s*`)
 
 // oneLogLine removes indentation at the beginning of each line and
diff --git a/client_test.go b/client_test.go
index 30a223e..64fba60 100644
--- a/client_test.go
+++ b/client_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -17,10 +17,13 @@ limitations under the License.
 package marathon
 
 import (
-	"net/http"
+	"bytes"
 	"testing"
 
+	"net/http"
+
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestNewClient(t *testing.T) {
@@ -35,10 +38,88 @@ func TestNewClient(t *testing.T) {
 
 	conf := cl.(*marathonClient).config
 
-	assert.Equal(t, conf.HTTPClient, http.DefaultClient)
+	assert.Equal(t, conf.HTTPClient, defaultHTTPClient)
+	assert.Equal(t, conf.HTTPSSEClient, defaultHTTPSSEClient)
+	assert.Zero(t, conf.HTTPSSEClient.Timeout)
 	assert.Equal(t, conf.PollingWaitTime, defaultPollingWaitTime)
 }
 
+func TestHTTPClientDefaults(t *testing.T) {
+	customHTTPRegularClient := http.DefaultClient
+
+	tests := []struct {
+		name                  string
+		httpRegularClient     *http.Client
+		httpSSEClient         *http.Client
+		wantHTTPRegularClient *http.Client
+		wantHTTPSSEClient     *http.Client
+	}{
+		{
+			name:                  "regular HTTP client missing",
+			httpRegularClient:     nil,
+			wantHTTPRegularClient: defaultHTTPClient,
+		},
+		{
+			name:              "SSE and regular HTTP clients missing",
+			httpSSEClient:     nil,
+			wantHTTPSSEClient: defaultHTTPSSEClient,
+		},
+		{
+			name:              "SSE HTTP client missing, regular HTTP client available",
+			httpSSEClient:     nil,
+			httpRegularClient: customHTTPRegularClient,
+			wantHTTPSSEClient: customHTTPRegularClient,
+		},
+	}
+
+	for _, test := range tests {
+		config := NewDefaultConfig()
+		config.HTTPClient = test.httpRegularClient
+		config.HTTPSSEClient = test.httpSSEClient
+
+		client, err := NewClient(config)
+		if !assert.NoError(t, err, test.name) {
+			continue
+		}
+
+		maraClient := client.(*marathonClient)
+		if test.wantHTTPRegularClient != nil {
+			if !assert.Equal(t, test.wantHTTPRegularClient, maraClient.config.HTTPClient, test.name) {
+				continue
+			}
+		}
+
+		if test.wantHTTPSSEClient != nil {
+			if !assert.Equal(t, test.wantHTTPSSEClient, maraClient.config.HTTPSSEClient, test.name) {
+				continue
+			}
+		}
+	}
+}
+
+func TestLogOutput(t *testing.T) {
+	buf := bytes.NewBuffer(nil)
+	config := Config{
+		URL:       "http://marathon",
+		LogOutput: buf,
+	}
+
+	cl, err := NewClient(config)
+	require.Nil(t, err)
+
+	cl.(*marathonClient).debugLog("this is a %s", "test")
+
+	assert.Equal(t, "this is a test\n", buf.String())
+}
+
+func TestInvalidConfig(t *testing.T) {
+	config := Config{
+		URL: "",
+	}
+	_, err := NewClient(config)
+	assert.Error(t, err)
+}
+
 func TestPing(t *testing.T) {
 	endpoint := newFakeMarathonEndpoint(t, nil)
 	defer endpoint.Close()
@@ -127,6 +208,56 @@ func TestAPIRequest(t *testing.T) {
 	}
 }
 
+func TestBuildApiRequestFailure(t *testing.T) {
+	tests := []struct {
+		name              string
+		expectedError     error
+		expectedErrorType interface{}
+		path              string
+		clusterDown       bool
+	}{
+		{
+			name:          "cluster down",
+			expectedError: ErrMarathonDown,
+			clusterDown:   true,
+		},
+		{
+			name:              "invalid request parameter",
+			expectedErrorType: newRequestError{},
+			path:              "%zzzzz",
+		},
+	}
+
+	for _, test := range tests {
+		if test.expectedError == nil && test.expectedErrorType == nil {
+			panic("Testcase requires at least one of 'expectedError' or 'expectedErrorType'")
+		}
+
+		clientCfg := NewDefaultConfig()
+		config := configContainer{client: &clientCfg}
+		endpoint := newFakeMarathonEndpoint(t, &config)
+
+		client := endpoint.Client.(*marathonClient)
+
+		if test.clusterDown {
+			for _, member := range client.hosts.members {
+				member.status = memberStatusDown
+			}
+		}
+
+		_, _, err := client.buildAPIRequest("GET", test.path, nil)
+
+		if test.expectedError != nil {
+			assert.Equal(t, test.expectedError, err)
+		}
+		if test.expectedErrorType != nil {
+			assert.IsType(t, test.expectedErrorType, err)
+		}
+
+		endpoint.Close()
+	}
+}
+
 func TestOneLogLine(t *testing.T) {
 	in := `
 	a
diff --git a/cluster.go b/cluster.go
index 670b6da..3ca99b2 100644
--- a/cluster.go
+++ b/cluster.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package marathon
 
 import (
 	"fmt"
-	"net/http"
 	"net/url"
 	"strings"
 	"sync"
@@ -38,8 +37,11 @@ type cluster struct {
 	sync.RWMutex
 	// a collection of nodes
 	members []*member
-	// the http client
-	client *http.Client
+	// the marathon HTTP client to ensure consistency in requests
+	client *httpClient
+	// healthCheckInterval is the interval by which we probe down nodes for
+	// availability again.
+	healthCheckInterval time.Duration
 }
 
 // member represents an individual endpoint
@@ -51,7 +53,7 @@ type member struct {
 }
 
 // newCluster returns a new marathon cluster
-func newCluster(client *http.Client, marathonURL string) (*cluster, error) {
+func newCluster(client *httpClient, marathonURL string, isDCOS bool) (*cluster, error) {
 	// step: extract and basic validate the endpoints
 	var members []*member
 	var defaultProto string
@@ -83,13 +85,21 @@ func newCluster(client *http.Client, marathonURL string) (*cluster, error) {
 			return nil, newInvalidEndpointError("endpoint: %s must have a host", endpoint)
 		}
 
+		// step: if DCOS is set and no path is given, set the default DCOS path.
+		// done in order to maintain compatibility with automatic addition of the
+		// default DCOS path.
+		if isDCOS && strings.TrimLeft(u.Path, "/") == "" {
+			u.Path = defaultDCOSPath
+		}
+
 		// step: create a new node for this endpoint
 		members = append(members, &member{endpoint: u.String()})
 	}
 
 	return &cluster{
-		client:  client,
-		members: members,
+		client:              client,
+		members:             members,
+		healthCheckInterval: 5 * time.Second,
 	}, nil
 }
 
@@ -124,17 +134,21 @@ func (c *cluster) markDown(endpoint string) {
 // healthCheckNode performs a health check on the node and when active updates the status
 func (c *cluster) healthCheckNode(node *member) {
 	// step: wait for the node to become active ... we are assuming a /ping is enough here
-	for {
-		res, err := c.client.Get(fmt.Sprintf("%s/ping", node.endpoint))
-		if err == nil && res.StatusCode == 200 {
-			break
+	ticker := time.NewTicker(c.healthCheckInterval)
+	defer ticker.Stop()
+	for range ticker.C {
+		req, err := c.client.buildMarathonRequest("GET", node.endpoint, "ping", nil)
+		if err == nil {
+			res, err := c.client.Do(req)
+			if err == nil && res.StatusCode == 200 {
+				// step: mark the node as active again
+				c.Lock()
+				node.status = memberStatusUp
+				c.Unlock()
+				break
+			}
 		}
-		<-time.After(time.Duration(5 * time.Second))
 	}
-	// step: mark the node as active again
-	c.Lock()
-	defer c.Unlock()
-	node.status = memberStatusUp
 }
 
 // activeMembers returns a list of active members
diff --git a/cluster_test.go b/cluster_test.go
index 5f71169..51acc5a 100644
--- a/cluster_test.go
+++ b/cluster_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -17,61 +17,97 @@ limitations under the License.
 package marathon
 
 import (
-	"net/http"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestSize(t *testing.T) {
-	cluster, err := newCluster(http.DefaultClient, fakeMarathonURL)
+	cluster, err := newStandardCluster(fakeMarathonURL)
 	assert.NoError(t, err)
 	assert.Equal(t, cluster.size(), 3)
 }
 
 func TestActive(t *testing.T) {
-	cluster, err := newCluster(http.DefaultClient, fakeMarathonURL)
+	cluster, err := newStandardCluster(fakeMarathonURL)
 	assert.NoError(t, err)
 	assert.Equal(t, len(cluster.activeMembers()), 3)
 }
 
 func TestNonActive(t *testing.T) {
-	cluster, err := newCluster(http.DefaultClient, fakeMarathonURL)
+	cluster, err := newStandardCluster(fakeMarathonURL)
 	assert.NoError(t, err)
 	assert.Equal(t, len(cluster.nonActiveMembers()), 0)
 }
 
 func TestGetMember(t *testing.T) {
-	cluster, err := newCluster(http.DefaultClient, fakeMarathonURL)
-	assert.NoError(t, err)
-	member, err := cluster.getMember()
-	assert.NoError(t, err)
-	assert.Equal(t, member, "http://127.0.0.1:3000")
-}
-
-func TestGetMemberWithPath(t *testing.T) {
-	cluster, err := newCluster(http.DefaultClient, fakeMarathonURLWithPath)
-	assert.NoError(t, err)
-	member, err := cluster.getMember()
-	assert.NoError(t, err)
-	assert.Equal(t, member, "http://127.0.0.1:3000/path")
+	cases := []struct {
+		isDCOS      bool
+		MarathonURL string
+		member      string
+	}{
+		{
+			isDCOS:      false,
+			MarathonURL: fakeMarathonURL,
+			member:      "http://127.0.0.1:3000",
+		},
+		{
+			isDCOS:      false,
+			MarathonURL: fakeMarathonURLWithPath,
+			member:      "http://127.0.0.1:3000/path",
+		},
+		{
+			isDCOS:      true,
+			MarathonURL: fakeMarathonURL,
+			member:      "http://127.0.0.1:3000/marathon",
+		},
+		{
+			isDCOS:      true,
+			MarathonURL: fakeMarathonURLWithPath,
+			member:      "http://127.0.0.1:3000/path",
+		},
+	}
+	for _, x := range cases {
+		cluster, err := newCluster(&httpClient{config: Config{HTTPClient: defaultHTTPClient}}, x.MarathonURL, x.isDCOS)
+		assert.NoError(t, err)
+		member, err := cluster.getMember()
+		assert.NoError(t, err)
+		assert.Equal(t, member, x.member)
+	}
 }
 
 func TestMarkDown(t *testing.T) {
 	endpoint := newFakeMarathonEndpoint(t, nil)
 	defer endpoint.Close()
-	cluster, err := newCluster(http.DefaultClient, endpoint.URL)
-	assert.NoError(t, err)
-	assert.Equal(t, len(cluster.activeMembers()), 3)
+	cluster, err := newStandardCluster(endpoint.URL)
+	require.NoError(t, err)
+	require.Equal(t, len(cluster.activeMembers()), 3)
+	cluster.healthCheckInterval = 2500 * time.Millisecond
 
 	members := cluster.activeMembers()
 	cluster.markDown(members[0])
 	cluster.markDown(members[1])
-	assert.Equal(t, 1, len(cluster.activeMembers()))
-
-	time.Sleep(10 * time.Millisecond)
-	assert.Equal(t, len(cluster.activeMembers()), 3)
+	require.Equal(t, len(cluster.activeMembers()), 1)
+
+	ticker := time.NewTicker(250 * time.Millisecond)
+	defer ticker.Stop()
+	timeout := time.NewTimer(5 * time.Second)
+	defer timeout.Stop()
+	var numFoundMembers int
+	for {
+		numFoundMembers = len(cluster.activeMembers())
+		if numFoundMembers == 3 {
+			break
+		}
+		select {
+		case <-ticker.C:
+			continue
+		case <-timeout.C:
+			t.Fatalf("found %d active member(s), want 3", numFoundMembers)
+		}
+	}
 }
 
 func TestValidClusterHosts(t *testing.T) {
@@ -121,7 +157,7 @@ func TestValidClusterHosts(t *testing.T) {
 		},
 	}
 	for _, x := range cs {
-		c, err := newCluster(http.DefaultClient, x.URL)
+		c, err := newStandardCluster(x.URL)
 		if !assert.NoError(t, err, "URL '%s' should not have thrown an error: %s", x.URL, err) {
 			continue
 		}
@@ -141,9 +177,13 @@ func TestInvalidClusterHosts(t *testing.T) {
 		"http://127.0.0.1:3000,127.0.0.1:3000,",
 		"foo://127.0.0.1:3000",
 	} {
-		_, err := newCluster(http.DefaultClient, invalidHost)
+		_, err := newStandardCluster(invalidHost)
 		if !assert.Error(t, err) {
 			t.Errorf("undetected invalid host: %s", invalidHost)
 		}
 	}
 }
+
+func newStandardCluster(url string) (*cluster, error) {
+	return newCluster(&httpClient{config: Config{HTTPClient: defaultHTTPClient}}, url, false)
+}
diff --git a/config.go b/config.go
index a0ede28..2e110cc 100644
--- a/config.go
+++ b/config.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@ import (
 
 const defaultPollingWaitTime = 500 * time.Millisecond
 
+const defaultDCOSPath = "marathon"
+
 // EventsTransport describes which transport should be used to deliver Marathon events
 type EventsTransport int
 
@@ -48,8 +50,10 @@ type Config struct {
 	DCOSToken string
 	// LogOutput the output for debug log messages
 	LogOutput io.Writer
-	// HTTPClient is the http client
+	// HTTPClient is the HTTP client
 	HTTPClient *http.Client
+	// HTTPSSEClient is the HTTP client used for SSE subscriptions, can't have client.Timeout set
+	HTTPSSEClient *http.Client
 	// wait time (in milliseconds) between repetitive requests to the API during polling
 	PollingWaitTime time.Duration
 }
diff --git a/const.go b/const.go
index 43b1d46..ba8d9b8 100644
--- a/const.go
+++ b/const.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ const (
 	marathonAPIEventStream  = marathonAPIVersion + "/events"
 	marathonAPISubscription = marathonAPIVersion + "/eventSubscriptions"
 	marathonAPIApps         = marathonAPIVersion + "/apps"
+	marathonAPIPods         = marathonAPIVersion + "/pods"
 	marathonAPITasks        = marathonAPIVersion + "/tasks"
 	marathonAPIDeployments  = marathonAPIVersion + "/deployments"
 	marathonAPIGroups       = marathonAPIVersion + "/groups"
diff --git a/debian/changelog b/debian/changelog
index a59c6ab..f87873b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-github-gambol99-go-marathon (0.7.1+git20200116.1.94e7bcb+ds-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+  * Drop patch 01-fix-tests-with-testify.patch, present upstream.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 03 Sep 2022 12:46:52 -0000
+
 golang-github-gambol99-go-marathon (0.7.1-1) unstable; urgency=medium
 
   * Initial release (Closes: #993242)
diff --git a/debian/patches/01-fix-tests-with-testify.patch b/debian/patches/01-fix-tests-with-testify.patch
deleted file mode 100644
index e42b1ea..0000000
--- a/debian/patches/01-fix-tests-with-testify.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-Description: Fix test with new version of testify
-Origin: https://github.com/gambol99/go-marathon/pull/300/commits/4ef257c7554f6324b06359106e0f9162e5f52b02
-Forwarded: not-needed
-Author: Timo Reimann
-Reviewed-by: Aloïs Micard <creekorful@debian.org>
-Last-Update: 2021-08-30
-
---- a/application_test.go
-+++ b/application_test.go
-@@ -608,7 +608,9 @@
- 	ipPerTask.
- 		AddGroup("label").
- 		AddLabel("key", "value").
--		SetDiscovery(Discovery{})
-+		SetDiscovery(Discovery{
-+			Ports: &[]Port{},
-+		})
- 
- 	assert.Equal(t, 1, len(*ipPerTask.Groups))
- 	assert.Equal(t, "label", (*ipPerTask.Groups)[0])
diff --git a/debian/patches/series b/debian/patches/series
index 1923743..e69de29 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +0,0 @@
-01-fix-tests-with-testify.patch
diff --git a/deployment.go b/deployment.go
index 7e2bddd..fc8480d 100644
--- a/deployment.go
+++ b/deployment.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ type Deployment struct {
 	CurrentStep    int                 `json:"currentStep"`
 	TotalSteps     int                 `json:"totalSteps"`
 	AffectedApps   []string            `json:"affectedApps"`
+	AffectedPods   []string            `json:"affectedPods"`
 	Steps          [][]*DeploymentStep `json:"-"`
 	XXStepsRaw     json.RawMessage     `json:"steps"` // Holds raw steps JSON to unmarshal later
 	CurrentActions []*DeploymentStep   `json:"currentActions"`
@@ -42,8 +43,9 @@ type DeploymentID struct {
 
 // DeploymentStep is a step in the application deployment plan
 type DeploymentStep struct {
-	Action string `json:"action"`
-	App    string `json:"app"`
+	Action                string                  `json:"action"`
+	App                   string                  `json:"app"`
+	ReadinessCheckResults *[]ReadinessCheckResult `json:"readinessCheckResults,omitempty"`
 }
 
 // StepActions is a series of deployment steps
@@ -106,8 +108,17 @@ func (r *marathonClient) Deployments() ([]*Deployment, error) {
 // 	id:		the deployment id you wish to delete
 // 	force:	whether or not to force the deletion
 func (r *marathonClient) DeleteDeployment(id string, force bool) (*DeploymentID, error) {
+	path := fmt.Sprintf("%s/%s", marathonAPIDeployments, id)
+
+	// if force=true, no body is returned
+	if force {
+		path += "?force=true"
+		return nil, r.apiDelete(path, nil, nil)
+	}
+
 	deployment := new(DeploymentID)
-	err := r.apiDelete(fmt.Sprintf("%s/%s", marathonAPIDeployments, id), nil, deployment)
+	err := r.apiDelete(path, nil, deployment)
+
 	if err != nil {
 		return nil, err
 	}
diff --git a/deployment_test.go b/deployment_test.go
index ab487be..e8842d6 100644
--- a/deployment_test.go
+++ b/deployment_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestDeployments(t *testing.T) {
@@ -27,13 +28,13 @@ func TestDeployments(t *testing.T) {
 	defer endpoint.Close()
 
 	deployments, err := endpoint.Client.Deployments()
-	assert.NoError(t, err)
-	assert.NotNil(t, deployments)
-	assert.Equal(t, len(deployments), 1)
+	require.NoError(t, err)
+	require.NotNil(t, deployments)
+	require.Equal(t, len(deployments), 1)
 	deployment := deployments[0]
-	assert.NotNil(t, deployment)
+	require.NotNil(t, deployment)
 	assert.Equal(t, deployment.ID, "867ed450-f6a8-4d33-9b0e-e11c5513990b")
-	assert.NotNil(t, deployment.Steps)
+	require.NotNil(t, deployment.Steps)
 	assert.Equal(t, len(deployment.Steps), 1)
 }
 
@@ -53,14 +54,39 @@ func TestDeploymentsV1(t *testing.T) {
 	assert.Equal(t, deployment.ID, "2620aa06-1001-4eea-8861-a51957d4fd80")
 	assert.NotNil(t, deployment.Steps)
 	assert.Equal(t, len(deployment.Steps), 2)
+
+	require.Equal(t, len(deployment.CurrentActions), 1)
+	curAction := deployment.CurrentActions[0]
+	require.NotNil(t, curAction)
+	require.NotNil(t, curAction.ReadinessCheckResults)
+	require.True(t, len(*curAction.ReadinessCheckResults) > 0)
+	actualRes := (*curAction.ReadinessCheckResults)[0]
+	expectedRes := ReadinessCheckResult{
+		Name:   "myReadyCheck",
+		TaskID: "test_frontend_app1.c9de6033",
+		Ready:  false,
+		LastResponse: ReadinessLastResponse{
+			Body:        "{}",
+			ContentType: "application/json",
+			Status:      500,
+		},
+	}
+	assert.Equal(t, expectedRes, actualRes)
 }
 
 func TestDeleteDeployment(t *testing.T) {
 	endpoint := newFakeMarathonEndpoint(t, nil)
 	defer endpoint.Close()
 	id, err := endpoint.Client.DeleteDeployment(fakeDeploymentID, false)
-	assert.NoError(t, err)
-	assert.NotNil(t, t)
+	require.NoError(t, err)
 	assert.Equal(t, id.DeploymentID, "0b1467fc-d5cd-4bbc-bac2-2805351cee1e")
 	assert.Equal(t, id.Version, "2014-08-26T08:20:26.171Z")
 }
+
+func TestDeleteDeploymentForce(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+	resp, err := endpoint.Client.DeleteDeployment(fakeDeploymentID, true)
+	require.NoError(t, err)
+	assert.Nil(t, resp)
+}
diff --git a/docker.go b/docker.go
index 550409a..c1f8cfb 100644
--- a/docker.go
+++ b/docker.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -23,9 +23,10 @@ import (
 
 // Container is the definition for a container type in marathon
 type Container struct {
-	Type    string    `json:"type,omitempty"`
-	Docker  *Docker   `json:"docker,omitempty"`
-	Volumes *[]Volume `json:"volumes,omitempty"`
+	Type         string         `json:"type,omitempty"`
+	Docker       *Docker        `json:"docker,omitempty"`
+	Volumes      *[]Volume      `json:"volumes,omitempty"`
+	PortMappings *[]PortMapping `json:"portMappings,omitempty"`
 }
 
 // PortMapping is the portmapping structure between container and mesos
@@ -36,6 +37,7 @@ type PortMapping struct {
 	Name          string             `json:"name,omitempty"`
 	ServicePort   int                `json:"servicePort,omitempty"`
 	Protocol      string             `json:"protocol,omitempty"`
+	NetworkNames  *[]string          `json:"networkNames,omitempty"`
 }
 
 // Parameters is the parameters to pass to the docker client when creating the container
@@ -46,10 +48,76 @@ type Parameters struct {
 
 // Volume is the docker volume details associated to the container
 type Volume struct {
-	ContainerPath string          `json:"containerPath,omitempty"`
-	HostPath      string          `json:"hostPath,omitempty"`
-	External      *ExternalVolume `json:"external,omitempty"`
-	Mode          string          `json:"mode,omitempty"`
+	ContainerPath string            `json:"containerPath,omitempty"`
+	HostPath      string            `json:"hostPath,omitempty"`
+	External      *ExternalVolume   `json:"external,omitempty"`
+	Mode          string            `json:"mode,omitempty"`
+	Persistent    *PersistentVolume `json:"persistent,omitempty"`
+	Secret        string            `json:"secret,omitempty"`
+}
+
+// PersistentVolumeType is the a persistent docker volume to be mounted
+type PersistentVolumeType string
+
+const (
+	// PersistentVolumeTypeRoot is the root path of the persistent volume
+	PersistentVolumeTypeRoot PersistentVolumeType = "root"
+	// PersistentVolumeTypePath is the mount path of the persistent volume
+	PersistentVolumeTypePath PersistentVolumeType = "path"
+	// PersistentVolumeTypeMount is the mount type of the persistent volume
+	PersistentVolumeTypeMount PersistentVolumeType = "mount"
+)
+
+// PersistentVolume declares a Volume to be Persistent, and sets
+// the size (in MiB) and optional type, max size (MiB) and constraints for the Volume.
+type PersistentVolume struct {
+	Type        PersistentVolumeType `json:"type,omitempty"`
+	Size        int                  `json:"size"`
+	MaxSize     int                  `json:"maxSize,omitempty"`
+	Constraints *[][]string          `json:"constraints,omitempty"`
+}
+
+// SetType sets the type of mesos disk resource to use
+//		type:	       PersistentVolumeType enum
+func (p *PersistentVolume) SetType(tp PersistentVolumeType) *PersistentVolume {
+	p.Type = tp
+	return p
+}
+
+// SetSize sets size of the persistent volume
+//		size:	        size in MiB
+func (p *PersistentVolume) SetSize(size int) *PersistentVolume {
+	p.Size = size
+	return p
+}
+
+// SetMaxSize sets maximum size of an exclusive mount-disk resource to consider;
+// does not apply to root or path disk resource types
+//		maxSize:	size in MiB
+func (p *PersistentVolume) SetMaxSize(maxSize int) *PersistentVolume {
+	p.MaxSize = maxSize
+	return p
+}
+
+// AddConstraint adds a new constraint
+//		constraints:	the constraint definition, one constraint per array element
+func (p *PersistentVolume) AddConstraint(constraints ...string) *PersistentVolume {
+	if p.Constraints == nil {
+		p.EmptyConstraints()
+	}
+
+	c := *p.Constraints
+	c = append(c, constraints)
+	p.Constraints = &c
+	return p
+}
+
+// EmptyConstraints explicitly empties constraints -- use this if you need to empty
+// constraints of an application that already has constraints set (setting constraints to nil will
+// keep the current value)
+func (p *PersistentVolume) EmptyConstraints() *PersistentVolume {
+	p.Constraints = &[][]string{}
+	return p
 }
 
 // ExternalVolume is an external volume definition
@@ -59,6 +127,18 @@ type ExternalVolume struct {
 	Options  *map[string]string `json:"options,omitempty"`
 }
 
+// PullConfig specifies a secret for authentication with a private Docker registry
+type PullConfig struct {
+	Secret string `json:"secret,omitempty"`
+}
+
+// NewPullConfig creats a *PullConfig based on a given secret
+func NewPullConfig(secret string) *PullConfig {
+	return &PullConfig{
+		Secret: secret,
+	}
+}
+
 // Docker is the docker definition from a marathon application
 type Docker struct {
 	ForcePullImage *bool          `json:"forcePullImage,omitempty"`
@@ -67,6 +147,7 @@ type Docker struct {
 	Parameters     *[]Parameters  `json:"parameters,omitempty"`
 	PortMappings   *[]PortMapping `json:"portMappings,omitempty"`
 	Privileged     *bool          `json:"privileged,omitempty"`
+	PullConfig     *PullConfig    `json:"pullConfig,omitempty"`
 }
 
 // Volume attachs a volume to the container
@@ -98,6 +179,26 @@ func (container *Container) EmptyVolumes() *Container {
 	return container
 }
 
+// SetPersistentVolume defines persistent properties for volume
+func (v *Volume) SetPersistentVolume() *PersistentVolume {
+	ev := &PersistentVolume{}
+	v.Persistent = ev
+	return ev
+}
+
+// SetSecretVolume defines secret and containerPath for volume
+func (v *Volume) SetSecretVolume(containerPath, secret string) *Volume {
+	v.ContainerPath = containerPath
+	v.Secret = secret
+	return v
+}
+
+// EmptyPersistentVolume empties the persistent volume definition
+func (v *Volume) EmptyPersistentVolume() *Volume {
+	v.Persistent = &PersistentVolume{}
+	return v
+}
+
 // SetExternalVolume define external elements for a volume
 //      name: the name of the volume
 //      provider: the provider of the volume (e.g. dvdi)
@@ -181,6 +282,19 @@ func (docker *Docker) Host() *Docker {
 	return docker
 }
 
+// Expose sets the container to expose the following TCP ports
+//		ports:			the TCP ports the container is exposing
+func (container *Container) Expose(ports ...int) *Container {
+	for _, port := range ports {
+		container.ExposePort(PortMapping{
+			ContainerPort: port,
+			HostPort:      0,
+			ServicePort:   0,
+			Protocol:      "tcp"})
+	}
+	return container
+}
+
 // Expose sets the container to expose the following TCP ports
 //		ports:			the TCP ports the container is exposing
 func (docker *Docker) Expose(ports ...int) *Docker {
@@ -194,6 +308,19 @@ func (docker *Docker) Expose(ports ...int) *Docker {
 	return docker
 }
 
+// ExposeUDP sets the container to expose the following UDP ports
+//		ports:			the UDP ports the container is exposing
+func (container *Container) ExposeUDP(ports ...int) *Container {
+	for _, port := range ports {
+		container.ExposePort(PortMapping{
+			ContainerPort: port,
+			HostPort:      0,
+			ServicePort:   0,
+			Protocol:      "udp"})
+	}
+	return container
+}
+
 // ExposeUDP sets the container to expose the following UDP ports
 //		ports:			the UDP ports the container is exposing
 func (docker *Docker) ExposeUDP(ports ...int) *Docker {
@@ -207,6 +334,19 @@ func (docker *Docker) ExposeUDP(ports ...int) *Docker {
 	return docker
 }
 
+// ExposePort exposes an port in the container
+func (container *Container) ExposePort(portMapping PortMapping) *Container {
+	if container.PortMappings == nil {
+		container.EmptyPortMappings()
+	}
+
+	portMappings := *container.PortMappings
+	portMappings = append(portMappings, portMapping)
+	container.PortMappings = &portMappings
+
+	return container
+}
+
 // ExposePort exposes an port in the container
 func (docker *Docker) ExposePort(portMapping PortMapping) *Docker {
 	if docker.PortMappings == nil {
@@ -220,6 +360,14 @@ func (docker *Docker) ExposePort(portMapping PortMapping) *Docker {
 	return docker
 }
 
+// EmptyPortMappings explicitly empties the port mappings -- use this if you need to empty
+// port mappings of an application that already has port mappings set (setting port mappings to nil will
+// keep the current value)
+func (container *Container) EmptyPortMappings() *Container {
+	container.PortMappings = &[]PortMapping{}
+	return container
+}
+
 // EmptyPortMappings explicitly empties the port mappings -- use this if you need to empty
 // port mappings of an application that already has port mappings set (setting port mappings to nil will
 // keep the current value)
@@ -275,6 +423,24 @@ func (docker *Docker) EmptyParameters() *Docker {
 	return docker
 }
 
+// ServicePortIndex finds the service port index of the exposed port
+//		port:			the port you are looking for
+func (container *Container) ServicePortIndex(port int) (int, error) {
+	if container.PortMappings == nil || len(*container.PortMappings) == 0 {
+		return 0, errors.New("The container does not contain any port mappings to search")
+	}
+
+	// step: iterate and find the port
+	for index, containerPort := range *container.PortMappings {
+		if containerPort.ContainerPort == port {
+			return index, nil
+		}
+	}
+
+	// step: we didn't find the port in the mappings
+	return 0, fmt.Errorf("The container port %d was not found in the container port mappings", port)
+}
+
 // ServicePortIndex finds the service port index of the exposed port
 //		port:			the port you are looking for
 func (docker *Docker) ServicePortIndex(port int) (int, error) {
@@ -290,5 +456,32 @@ func (docker *Docker) ServicePortIndex(port int) (int, error) {
 	}
 
 	// step: we didn't find the port in the mappings
-	return 0, fmt.Errorf("The container port required was not found in the container port mappings")
+	return 0, fmt.Errorf("The docker port %d was not found in the container port mappings", port)
+}
+
+// SetPullConfig adds *PullConfig to Docker
+func (docker *Docker) SetPullConfig(pullConfig *PullConfig) *Docker {
+	docker.PullConfig = pullConfig
+
+	return docker
+}
+
+// AddNetwork adds a network name to a PortMapping
+//		name:	the name of the network
+func (p *PortMapping) AddNetwork(name string) *PortMapping {
+	if p.NetworkNames == nil {
+		p.EmptyNetworkNames()
+	}
+	networks := *p.NetworkNames
+	networks = append(networks, name)
+	p.NetworkNames = &networks
+	return p
+}
+
+// EmptyNetworkNames explicitly empties the network names -- use this if you need to empty
+// the network names of a port mapping that already has network names set
+func (p *PortMapping) EmptyNetworkNames() *PortMapping {
+	p.NetworkNames = &[]string{}
+
+	return p
 }
diff --git a/docker_test.go b/docker_test.go
index 98af321..6fe3def 100644
--- a/docker_test.go
+++ b/docker_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2015 Rohith All rights reserved.
+Copyright 2015 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func createPortMapping(containerPort int, protocol string) *PortMapping {
@@ -45,28 +46,55 @@ func TestDockerAddParameter(t *testing.T) {
 	assert.NotNil(t, docker.Parameters)
 	assert.Equal(t, 0, len(*docker.Parameters))
 }
-
 func TestDockerExpose(t *testing.T) {
-	app := NewDockerApplication()
-	app.Container.Docker.Expose(8080).Expose(80, 443)
+	apps := []*Application{
+		NewDockerApplication(),
+		NewDockerApplication(),
+	}
+
+	// Marathon < 1.5
+	apps[0].Container.Docker.Expose(8080).Expose(80, 443)
+
+	// Marathon >= 1.5
+	apps[1].Container.Expose(8080).Expose(80, 443)
 
-	portMappings := app.Container.Docker.PortMappings
-	assert.Equal(t, 3, len(*portMappings))
+	portMappings := []*[]PortMapping{
+		apps[0].Container.Docker.PortMappings,
+		apps[1].Container.PortMappings,
+	}
+
+	for _, portMapping := range portMappings {
+		assert.Equal(t, 3, len(*portMapping))
 
-	assert.Equal(t, *createPortMapping(8080, "tcp"), (*portMappings)[0])
-	assert.Equal(t, *createPortMapping(80, "tcp"), (*portMappings)[1])
-	assert.Equal(t, *createPortMapping(443, "tcp"), (*portMappings)[2])
+		assert.Equal(t, *createPortMapping(8080, "tcp"), (*portMapping)[0])
+		assert.Equal(t, *createPortMapping(80, "tcp"), (*portMapping)[1])
+		assert.Equal(t, *createPortMapping(443, "tcp"), (*portMapping)[2])
+	}
 }
 
 func TestDockerExposeUDP(t *testing.T) {
-	app := NewDockerApplication()
-	app.Container.Docker.ExposeUDP(53).ExposeUDP(5060, 6881)
+	apps := []*Application{
+		NewDockerApplication(),
+		NewDockerApplication(),
+	}
 
-	portMappings := app.Container.Docker.PortMappings
-	assert.Equal(t, 3, len(*portMappings))
-	assert.Equal(t, *createPortMapping(53, "udp"), (*portMappings)[0])
-	assert.Equal(t, *createPortMapping(5060, "udp"), (*portMappings)[1])
-	assert.Equal(t, *createPortMapping(6881, "udp"), (*portMappings)[2])
+	// Marathon < 1.5
+	apps[0].Container.Docker.ExposeUDP(53).ExposeUDP(5060, 6881)
+
+	// Marathon >= 1.5
+	apps[1].Container.ExposeUDP(53).ExposeUDP(5060, 6881)
+
+	portMappings := []*[]PortMapping{
+		apps[0].Container.Docker.PortMappings,
+		apps[1].Container.PortMappings,
+	}
+
+	for _, portMapping := range portMappings {
+		assert.Equal(t, 3, len(*portMapping))
+		assert.Equal(t, *createPortMapping(53, "udp"), (*portMapping)[0])
+		assert.Equal(t, *createPortMapping(5060, "udp"), (*portMapping)[1])
+		assert.Equal(t, *createPortMapping(6881, "udp"), (*portMapping)[2])
+	}
 }
 
 func TestPortMappingLabels(t *testing.T) {
@@ -84,6 +112,20 @@ func TestPortMappingLabels(t *testing.T) {
 	assert.Equal(t, 0, len(*pm.Labels))
 }
 
+func TestPortMappingNetworkNames(t *testing.T) {
+	pm := createPortMapping(80, "tcp")
+
+	pm.AddNetwork("test")
+
+	assert.Equal(t, 1, len(*pm.NetworkNames))
+	assert.Equal(t, "test", (*pm.NetworkNames)[0])
+
+	pm.EmptyNetworkNames()
+
+	assert.NotNil(t, pm.NetworkNames)
+	assert.Equal(t, 0, len(*pm.NetworkNames))
+}
+
 func TestVolume(t *testing.T) {
 	container := NewDockerApplication().Container
 
@@ -99,6 +141,19 @@ func TestVolume(t *testing.T) {
 	assert.Equal(t, (*container.Volumes)[1].Mode, "R")
 }
 
+func TestSecretVolume(t *testing.T) {
+	container := NewDockerApplication().Container
+
+	container.Volume("", "oldPath", "")
+
+	sv1 := (*container.Volumes)[0]
+	assert.Equal(t, sv1.ContainerPath, "oldPath")
+
+	sv1.SetSecretVolume("newPath", "some-secret")
+	assert.Equal(t, sv1.ContainerPath, "newPath")
+	assert.Equal(t, sv1.Secret, "some-secret")
+}
+
 func TestExternalVolume(t *testing.T) {
 	container := NewDockerApplication().Container
 
@@ -122,3 +177,43 @@ func TestExternalVolume(t *testing.T) {
 	assert.Equal(t, ev2.Name, "")
 	assert.Equal(t, ev2.Provider, "")
 }
+
+func TestDockerPersistentVolume(t *testing.T) {
+	docker := NewDockerApplication()
+	container := docker.Container.Volume("/host", "/container", "RW")
+	require.Equal(t, 1, len(*docker.Container.Volumes))
+
+	pVol := (*container.Volumes)[0].SetPersistentVolume()
+	pVol.SetType(PersistentVolumeTypeMount)
+	pVol.SetSize(256)
+	pVol.SetMaxSize(128)
+	pVol.AddConstraint("cons1", "EQUAL", "tag1")
+	pVol.AddConstraint("cons2", "UNIQUE")
+
+	assert.Equal(t, 256, pVol.Size)
+	assert.Equal(t, PersistentVolumeTypeMount, pVol.Type)
+	assert.Equal(t, 128, pVol.MaxSize)
+
+	if assert.NotNil(t, pVol.Constraints) {
+		constraints := *pVol.Constraints
+		require.Equal(t, 2, len(constraints))
+		assert.Equal(t, []string{"cons1", "EQUAL", "tag1"}, constraints[0])
+		assert.Equal(t, []string{"cons2", "UNIQUE"}, constraints[1])
+	}
+
+	pVol.EmptyConstraints()
+	if assert.NotNil(t, pVol.Constraints) {
+		assert.Empty(t, len(*pVol.Constraints))
+	}
+}
+
+func TestDockerPullConfig(t *testing.T) {
+	secretName := "mysecret1"
+	app := NewDockerApplication()
+	pullConfig := NewPullConfig(secretName)
+	app.Container.Docker.SetPullConfig(pullConfig)
+
+	if assert.NotNil(t, app.Container.Docker.PullConfig) {
+		assert.Equal(t, secretName, app.Container.Docker.PullConfig.Secret)
+	}
+}
diff --git a/error.go b/error.go
index 21e7311..fd8a7d4 100644
--- a/error.go
+++ b/error.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2015 Rohith All rights reserved.
+Copyright 2015 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -42,6 +42,8 @@ const (
 	ErrCodeServer
 	// ErrCodeUnknown specifies an unknown error.
 	ErrCodeUnknown
+	// ErrCodeMethodNotAllowed specifies a 405 Method Not Allowed.
+	ErrCodeMethodNotAllowed
 )
 
 // InvalidEndpointError indicates a endpoint error in the marathon urls
@@ -56,7 +58,7 @@ func (e *InvalidEndpointError) Error() string {
 
 // newInvalidEndpointError creates a new error
 func newInvalidEndpointError(message string, args ...interface{}) error {
-	return &InvalidEndpointError{message: fmt.Sprintf(message, args)}
+	return &InvalidEndpointError{message: fmt.Sprintf(message, args...)}
 }
 
 // APIError represents a generic API error.
@@ -82,6 +84,8 @@ func NewAPIError(code int, content []byte) error {
 		errDef = &simpleErrDef{code: ErrCodeForbidden}
 	case code == http.StatusNotFound:
 		errDef = &simpleErrDef{code: ErrCodeNotFound}
+	case code == http.StatusMethodNotAllowed:
+		errDef = &simpleErrDef{code: ErrCodeMethodNotAllowed}
 	case code == http.StatusConflict:
 		errDef = &conflictDef{}
 	case code == 422:
diff --git a/error_test.go b/error_test.go
index 5e1822e..ae014dc 100644
--- a/error_test.go
+++ b/error_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2015 Rohith All rights reserved.
+Copyright 2015 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -61,6 +61,13 @@ func TestErrors(t *testing.T) {
 			errText:  "App '/not_existent' does not exist",
 			content:  `{"message": "App '/not_existent' does not exist"}`,
 		},
+		// 405
+		{
+			httpCode: http.StatusMethodNotAllowed,
+			errCode:  ErrCodeMethodNotAllowed,
+			errText:  "",
+			content:  `{"message": null}`,
+		},
 		// 409 POST
 		{
 			httpCode:   http.StatusConflict,
diff --git a/events.go b/events.go
index 5a68985..5814cad 100644
--- a/events.go
+++ b/events.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -273,9 +273,10 @@ type EventGroupChangeFailed struct {
 
 // EventDeploymentSuccess describes a 'deployment_success' event.
 type EventDeploymentSuccess struct {
-	ID        string `json:"id"`
-	EventType string `json:"eventType"`
-	Timestamp string `json:"timestamp"`
+	ID        string          `json:"id"`
+	EventType string          `json:"eventType"`
+	Timestamp string          `json:"timestamp"`
+	Plan      *DeploymentPlan `json:"plan"`
 }
 
 // EventDeploymentFailed describes a 'deployment_failed' event.
diff --git a/examples/applications/main.go b/examples/applications/main.go
index c28c821..4222eb8 100644
--- a/examples/applications/main.go
+++ b/examples/applications/main.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/examples/events_callback_transport/main.go b/examples/events_callback_transport/main.go
index 415bec9..7e77bdc 100644
--- a/examples/events_callback_transport/main.go
+++ b/examples/events_callback_transport/main.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -71,9 +71,9 @@ func main() {
 			log.Printf("Exiting the loop")
 			done = true
 		case event := <-events:
-			log.Printf("Recieved application event: %s", event)
+			log.Printf("Received application event: %s", event)
 		case event := <-deployments:
-			log.Printf("Recieved deployment event: %v", event)
+			log.Printf("Received deployment event: %v", event)
 			var deployment *marathon.EventDeploymentStepSuccess
 			deployment = event.Event.(*marathon.EventDeploymentStepSuccess)
 			log.Printf("deployment step: %v", deployment.CurrentStep)
diff --git a/examples/events_sse_transport/main.go b/examples/events_sse_transport/main.go
index a035d91..ff72436 100644
--- a/examples/events_sse_transport/main.go
+++ b/examples/events_sse_transport/main.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2015 Denis Parchenko All rights reserved.
+Copyright 2015 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -66,9 +66,9 @@ func main() {
 			log.Printf("Exiting the loop")
 			done = true
 		case event := <-events:
-			log.Printf("Recieved application event: %s", event)
+			log.Printf("Received application event: %s", event)
 		case event := <-deployments:
-			log.Printf("Recieved deployment event: %v", event)
+			log.Printf("Received deployment event: %v", event)
 			var deployment *marathon.EventDeploymentStepSuccess
 			deployment = event.Event.(*marathon.EventDeploymentStepSuccess)
 			log.Printf("deployment step: %v", deployment.CurrentStep)
diff --git a/examples/glog/main.go b/examples/glog/main.go
index a814a87..d4eeb96 100644
--- a/examples/glog/main.go
+++ b/examples/glog/main.go
@@ -1,4 +1,6 @@
 /*
+Copyright 2015 The go-marathon Authors All rights reserved.
+
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
diff --git a/examples/groups/main.go b/examples/groups/main.go
index baa364e..fb494d8 100644
--- a/examples/groups/main.go
+++ b/examples/groups/main.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -107,7 +107,7 @@ func main() {
 	assert(client.CreateGroup(group))
 	log.Printf("Successfully created the group: %s", group.ID)
 
-	log.Printf("Updating the group paramaters")
+	log.Printf("Updating the group parameters")
 	frontend.Count(4)
 
 	id, err := client.UpdateGroup(groupName, group, true)
diff --git a/examples/multiple_endpoints/main.go b/examples/multiple_endpoints/main.go
index 4b284b9..97e1572 100644
--- a/examples/multiple_endpoints/main.go
+++ b/examples/multiple_endpoints/main.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/examples/pods/main.go b/examples/pods/main.go
new file mode 100644
index 0000000..12f075f
--- /dev/null
+++ b/examples/pods/main.go
@@ -0,0 +1,173 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"time"
+
+	marathon "github.com/gambol99/go-marathon"
+)
+
+var marathonURL string
+var dcosToken string
+
+func init() {
+	flag.StringVar(&marathonURL, "url", "http://127.0.0.1:8080", "the url for the marathon endpoint")
+	flag.StringVar(&dcosToken, "token", "", "DCOS token for auth")
+}
+
+func assert(err error) {
+	if err != nil {
+		log.Fatalf("Failed, error: %s", err)
+	}
+}
+
+func waitOnDeployment(client marathon.Marathon, id *marathon.DeploymentID) {
+	assert(client.WaitOnDeployment(id.DeploymentID, 0))
+}
+
+func createRawPod() *marathon.Pod {
+	var containers []*marathon.PodContainer
+	for i := 0; i < 2; i++ {
+		container := &marathon.PodContainer{
+			Name: fmt.Sprintf("container%d", i),
+			Exec: &marathon.PodExec{
+				Command: marathon.PodCommand{
+					Shell: "echo Hello World && sleep 600",
+				},
+			},
+			Image: &marathon.PodContainerImage{
+				Kind:      "DOCKER",
+				ID:        "nginx",
+				ForcePull: marathon.Bool(true),
+			},
+			VolumeMounts: []*marathon.PodVolumeMount{
+				&marathon.PodVolumeMount{
+					Name:      "sharedvolume",
+					MountPath: "/peers",
+				},
+			},
+			Resources: &marathon.Resources{
+				Cpus: 0.1,
+				Mem:  128,
+			},
+			Env: map[string]string{
+				"key": "value",
+			},
+		}
+
+		containers = append(containers, container)
+	}
+
+	pod := &marathon.Pod{
+		ID:         "/mypod",
+		Containers: containers,
+		Scaling: &marathon.PodScalingPolicy{
+			Kind:      "fixed",
+			Instances: 2,
+		},
+		Volumes: []*marathon.PodVolume{
+			&marathon.PodVolume{
+				Name: "sharedvolume",
+				Host: "/tmp",
+			},
+		},
+	}
+
+	return pod
+}
+
+func createConveniencePod() *marathon.Pod {
+	pod := marathon.NewPod()
+
+	pod.Name("mypod").
+		Count(2).
+		AddVolume(marathon.NewPodVolume("sharedvolume", "/tmp"))
+
+	for i := 0; i < 2; i++ {
+		image := marathon.NewDockerPodContainerImage().
+			SetID("nginx")
+
+		container := marathon.NewPodContainer().
+			SetName(fmt.Sprintf("container%d", i)).
+			CPUs(0.1).
+			Memory(128).
+			SetImage(image).
+			AddEnv("key", "value").
+			AddVolumeMount(marathon.NewPodVolumeMount("sharedvolume", "/peers")).
+			SetCommand("echo Hello World && sleep 600")
+
+		pod.AddContainer(container)
+	}
+
+	return pod
+}
+
+func doPlayground(client marathon.Marathon, pod *marathon.Pod) {
+	// Create a pod
+	fmt.Println("Creating pod...")
+	pod, err := client.CreatePod(pod)
+	assert(err)
+
+	// Check its status
+	fmt.Println("Waiting on pod...")
+	err = client.WaitOnPod(pod.ID, time.Minute*1)
+	assert(err)
+
+	// Scale it
+	fmt.Println("Scaling pod...")
+	pod.Count(5)
+	pod, err = client.UpdatePod(pod, true)
+	assert(err)
+
+	// Get instances
+	status, err := client.PodStatus(pod.ID)
+	fmt.Printf("Pod status: %s\n", status.Status)
+	assert(err)
+
+	// Kill an instance
+	fmt.Println("Deleting an instance...")
+	_, err = client.DeletePodInstance(pod.ID, status.Instances[0].ID)
+	assert(err)
+
+	// Delete it
+	fmt.Println("Deleting the pod")
+	id, err := client.DeletePod(pod.ID, true)
+	assert(err)
+
+	waitOnDeployment(client, id)
+}
+
+func main() {
+	flag.Parse()
+	config := marathon.NewDefaultConfig()
+	config.URL = marathonURL
+	config.DCOSToken = dcosToken
+	client, err := marathon.NewClient(config)
+	assert(err)
+
+	fmt.Println("Convenience Pods:")
+	podC := createConveniencePod()
+	doPlayground(client, podC)
+
+	fmt.Println("Raw Pods:")
+	podR := createRawPod()
+	doPlayground(client, podR)
+}
diff --git a/examples/queue/main.go b/examples/queue/main.go
index c64f5e5..354a5a0 100644
--- a/examples/queue/main.go
+++ b/examples/queue/main.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2016 Rohith All rights reserved.
+Copyright 2016 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/examples/tasks/main.go b/examples/tasks/main.go
index 6fcb4e1..62e24e0 100644
--- a/examples/tasks/main.go
+++ b/examples/tasks/main.go
@@ -1,3 +1,19 @@
+/*
+Copyright 2015 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package main
 
 import (
diff --git a/group.go b/group.go
index 2cdf2ac..4dfc7e4 100644
--- a/group.go
+++ b/group.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -106,12 +106,12 @@ func (r *marathonClient) Group(name string) (*Group, error) {
 // GroupsBy retrieves a list of all the groups from marathon by embed options
 //		opts:		GetGroupOpts request payload
 func (r *marathonClient) GroupsBy(opts *GetGroupOpts) (*Groups, error) {
-	u, err := addOptions(marathonAPIGroups, opts)
+	path, err := addOptions(marathonAPIGroups, opts)
 	if err != nil {
 		return nil, err
 	}
 	groups := new(Groups)
-	if err := r.apiGet(u, "", groups); err != nil {
+	if err := r.apiGet(path, "", groups); err != nil {
 		return nil, err
 	}
 	return groups, nil
@@ -121,12 +121,12 @@ func (r *marathonClient) GroupsBy(opts *GetGroupOpts) (*Groups, error) {
 //		name:			the identifier for the group
 //		opts:			GetGroupOpts request payload
 func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error) {
-	u, err := addOptions(fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)), opts)
+	path, err := addOptions(fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)), opts)
 	if err != nil {
 		return nil, err
 	}
 	group := new(Group)
-	if err := r.apiGet(u, nil, group); err != nil {
+	if err := r.apiGet(path, nil, group); err != nil {
 		return nil, err
 	}
 	return group, nil
@@ -135,8 +135,8 @@ func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error
 // HasGroup checks if the group exists in marathon
 // 		name:			the identifier for the group
 func (r *marathonClient) HasGroup(name string) (bool, error) {
-	uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
-	err := r.apiCall("GET", uri, "", nil)
+	path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
+	err := r.apiGet(path, "", nil)
 	if err != nil {
 		if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
 			return false, nil
@@ -149,7 +149,7 @@ func (r *marathonClient) HasGroup(name string) (bool, error) {
 // CreateGroup creates a new group in marathon
 //		group:			a pointer the Group structure defining the group
 func (r *marathonClient) CreateGroup(group *Group) error {
-	return r.apiPost(marathonAPIGroups, group, nil)
+	return r.ApiPost(marathonAPIGroups, group, nil)
 }
 
 // WaitOnGroup waits for all the applications in a group to be deployed
@@ -208,11 +208,11 @@ func (r *marathonClient) WaitOnGroup(name string, timeout time.Duration) error {
 //		force:			used to force the delete operation in case of blocked deployment
 func (r *marathonClient) DeleteGroup(name string, force bool) (*DeploymentID, error) {
 	version := new(DeploymentID)
-	uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
+	path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
 	if force {
-		uri = uri + "?force=true"
+		path += "?force=true"
 	}
-	if err := r.apiDelete(uri, nil, version); err != nil {
+	if err := r.apiDelete(path, nil, version); err != nil {
 		return nil, err
 	}
 
@@ -225,11 +225,11 @@ func (r *marathonClient) DeleteGroup(name string, force bool) (*DeploymentID, er
 //		force:			used to force the update operation in case of blocked deployment
 func (r *marathonClient) UpdateGroup(name string, group *Group, force bool) (*DeploymentID, error) {
 	deploymentID := new(DeploymentID)
-	uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
+	path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
 	if force {
-		uri = uri + "?force=true"
+		path += "?force=true"
 	}
-	if err := r.apiPut(uri, group, deploymentID); err != nil {
+	if err := r.apiPut(path, group, deploymentID); err != nil {
 		return nil, err
 	}
 
diff --git a/group_test.go b/group_test.go
index c1f00f9..30e995a 100644
--- a/group_test.go
+++ b/group_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -29,38 +29,72 @@ func TestGroups(t *testing.T) {
 	groups, err := endpoint.Client.Groups()
 	assert.NoError(t, err)
 	assert.NotNil(t, groups)
-	assert.Equal(t, len(groups.Groups), 1)
+	assert.Equal(t, 1, len(groups.Groups))
 	group := groups.Groups[0]
-	assert.Equal(t, group.ID, fakeGroupName)
+	assert.Equal(t, fakeGroupName, group.ID)
 }
 
-func TestGroup(t *testing.T) {
+func TestNewGroup(t *testing.T) {
 	endpoint := newFakeMarathonEndpoint(t, nil)
 	defer endpoint.Close()
 
 	group, err := endpoint.Client.Group(fakeGroupName)
 	assert.NoError(t, err)
 	assert.NotNil(t, group)
-	assert.Equal(t, len(group.Apps), 1)
-	assert.Equal(t, group.ID, fakeGroupName)
+	assert.Equal(t, 1, len(group.Apps))
+	assert.Equal(t, fakeGroupName, group.ID)
 
 	group, err = endpoint.Client.Group(fakeGroupName1)
 
 	assert.NoError(t, err)
 	assert.NotNil(t, group)
-	assert.Equal(t, group.ID, fakeGroupName1)
+	assert.Equal(t, fakeGroupName1, group.ID)
 	assert.NotNil(t, group.Groups)
-	assert.Equal(t, len(group.Groups), 1)
+	assert.Equal(t, 1, len(group.Groups))
 
 	frontend := group.Groups[0]
-	assert.Equal(t, frontend.ID, "frontend")
-	assert.Equal(t, len(frontend.Apps), 3)
+	assert.Equal(t, "frontend", frontend.ID)
+	assert.Equal(t, 3, len(frontend.Apps))
 	for _, app := range frontend.Apps {
 		assert.NotNil(t, app.Container)
 		assert.NotNil(t, app.Container.Docker)
-		assert.Equal(t, app.Container.Docker.Network, "BRIDGE")
-		if len(*app.Container.Docker.PortMappings) == 0 {
+		for _, network := range *app.Networks {
+			assert.Equal(t, BridgeNetworkMode, network.Mode)
+		}
+		if len(*app.Container.PortMappings) == 0 {
 			t.Fail()
 		}
 	}
 }
+
+// TODO @kamsz: How to work with old and new endpoints from methods.yml?
+// func TestGroup(t *testing.T) {
+// 	endpoint := newFakeMarathonEndpoint(t, nil)
+// 	defer endpoint.Close()
+
+// 	group, err := endpoint.Client.Group(fakeGroupName)
+// 	assert.NoError(t, err)
+// 	assert.NotNil(t, group)
+// 	assert.Equal(t, 1, len(group.Apps))
+// 	assert.Equal(t, fakeGroupName, group.ID)
+
+// 	group, err = endpoint.Client.Group(fakeGroupName1)
+
+// 	assert.NoError(t, err)
+// 	assert.NotNil(t, group)
+// 	assert.Equal(t, fakeGroupName1, group.ID)
+// 	assert.NotNil(t, group.Groups)
+// 	assert.Equal(t, 1, len(group.Groups))
+
+// 	frontend := group.Groups[0]
+// 	assert.Equal(t, "frontend", frontend.ID)
+// 	assert.Equal(t, 3, len(frontend.Apps))
+// 	for _, app := range frontend.Apps {
+// 		assert.NotNil(t, app.Container)
+// 		assert.NotNil(t, app.Container.Docker)
+// 		assert.Equal(t, "BRIDGE", app.Container.Docker.Network)
+// 		if len(*app.Container.Docker.PortMappings) == 0 {
+// 			t.Fail()
+// 		}
+// 	}
+// }
diff --git a/health.go b/health.go
index 8810213..8e9432e 100644
--- a/health.go
+++ b/health.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -27,38 +27,94 @@ type HealthCheck struct {
 	GracePeriodSeconds     int      `json:"gracePeriodSeconds,omitempty"`
 	IntervalSeconds        int      `json:"intervalSeconds,omitempty"`
 	TimeoutSeconds         int      `json:"timeoutSeconds,omitempty"`
+	IgnoreHTTP1xx          *bool    `json:"ignoreHttp1xx,omitempty"`
+}
+
+// HTTPHealthCheck describes an HTTP based health check
+type HTTPHealthCheck struct {
+	Endpoint string `json:"endpoint,omitempty"`
+	Path     string `json:"path,omitempty"`
+	Scheme   string `json:"scheme,omitempty"`
+}
+
+// TCPHealthCheck describes a TCP based health check
+type TCPHealthCheck struct {
+	Endpoint string `json:"endpoint,omitempty"`
+}
+
+// CommandHealthCheck describes a shell-based health check
+type CommandHealthCheck struct {
+	Command PodCommand `json:"command,omitempty"`
+}
+
+// PodHealthCheck describes how to determine a pod's health
+type PodHealthCheck struct {
+	HTTP                   *HTTPHealthCheck    `json:"http,omitempty"`
+	TCP                    *TCPHealthCheck     `json:"tcp,omitempty"`
+	Exec                   *CommandHealthCheck `json:"exec,omitempty"`
+	GracePeriodSeconds     *int                `json:"gracePeriodSeconds,omitempty"`
+	IntervalSeconds        *int                `json:"intervalSeconds,omitempty"`
+	MaxConsecutiveFailures *int                `json:"maxConsecutiveFailures,omitempty"`
+	TimeoutSeconds         *int                `json:"timeoutSeconds,omitempty"`
+	DelaySeconds           *int                `json:"delaySeconds,omitempty"`
+}
+
+// NewPodHealthCheck creates an empty PodHealthCheck
+func NewPodHealthCheck() *PodHealthCheck {
+	return &PodHealthCheck{}
+}
+
+// NewHTTPHealthCheck creates an empty HTTPHealthCheck
+func NewHTTPHealthCheck() *HTTPHealthCheck {
+	return &HTTPHealthCheck{}
+}
+
+// NewTCPHealthCheck creates an empty TCPHealthCheck
+func NewTCPHealthCheck() *TCPHealthCheck {
+	return &TCPHealthCheck{}
+}
+
+// NewCommandHealthCheck creates an empty CommandHealthCheck
+func NewCommandHealthCheck() *CommandHealthCheck {
+	return &CommandHealthCheck{}
 }
 
 // SetCommand sets the given command on the health check.
-func (h HealthCheck) SetCommand(c Command) HealthCheck {
+func (h *HealthCheck) SetCommand(c Command) *HealthCheck {
 	h.Command = &c
 	return h
 }
 
 // SetPortIndex sets the given port index on the health check.
-func (h HealthCheck) SetPortIndex(i int) HealthCheck {
+func (h *HealthCheck) SetPortIndex(i int) *HealthCheck {
 	h.PortIndex = &i
 	return h
 }
 
 // SetPort sets the given port on the health check.
-func (h HealthCheck) SetPort(i int) HealthCheck {
+func (h *HealthCheck) SetPort(i int) *HealthCheck {
 	h.Port = &i
 	return h
 }
 
 // SetPath sets the given path on the health check.
-func (h HealthCheck) SetPath(p string) HealthCheck {
+func (h *HealthCheck) SetPath(p string) *HealthCheck {
 	h.Path = &p
 	return h
 }
 
 // SetMaxConsecutiveFailures sets the maximum consecutive failures on the health check.
-func (h HealthCheck) SetMaxConsecutiveFailures(i int) HealthCheck {
+func (h *HealthCheck) SetMaxConsecutiveFailures(i int) *HealthCheck {
 	h.MaxConsecutiveFailures = &i
 	return h
 }
 
+// SetIgnoreHTTP1xx sets ignore http 1xx on the health check.
+func (h *HealthCheck) SetIgnoreHTTP1xx(ignore bool) *HealthCheck {
+	h.IgnoreHTTP1xx = &ignore
+	return h
+}
+
 // NewDefaultHealthCheck creates a default application health check
 func NewDefaultHealthCheck() *HealthCheck {
 	portIndex := 0
@@ -91,3 +147,90 @@ type HealthCheckResult struct {
 type Command struct {
 	Value string `json:"value"`
 }
+
+// SetHTTPHealthCheck configures the pod's health check for an HTTP endpoint.
+// Note this will erase any configured TCP/Exec health checks.
+func (p *PodHealthCheck) SetHTTPHealthCheck(h *HTTPHealthCheck) *PodHealthCheck {
+	p.HTTP = h
+	p.TCP = nil
+	p.Exec = nil
+	return p
+}
+
+// SetTCPHealthCheck configures the pod's health check for a TCP endpoint.
+// Note this will erase any configured HTTP/Exec health checks.
+func (p *PodHealthCheck) SetTCPHealthCheck(t *TCPHealthCheck) *PodHealthCheck {
+	p.TCP = t
+	p.HTTP = nil
+	p.Exec = nil
+	return p
+}
+
+// SetExecHealthCheck configures the pod's health check for a command.
+// Note this will erase any configured HTTP/TCP health checks.
+func (p *PodHealthCheck) SetExecHealthCheck(e *CommandHealthCheck) *PodHealthCheck {
+	p.Exec = e
+	p.HTTP = nil
+	p.TCP = nil
+	return p
+}
+
+// SetGracePeriod sets the health check initial grace period, in seconds
+func (p *PodHealthCheck) SetGracePeriod(gracePeriodSeconds int) *PodHealthCheck {
+	p.GracePeriodSeconds = &gracePeriodSeconds
+	return p
+}
+
+// SetInterval sets the health check polling interval, in seconds
+func (p *PodHealthCheck) SetInterval(intervalSeconds int) *PodHealthCheck {
+	p.IntervalSeconds = &intervalSeconds
+	return p
+}
+
+// SetMaxConsecutiveFailures sets the maximum consecutive failures on the health check
+func (p *PodHealthCheck) SetMaxConsecutiveFailures(maxFailures int) *PodHealthCheck {
+	p.MaxConsecutiveFailures = &maxFailures
+	return p
+}
+
+// SetTimeout sets the length of time the health check will await a result, in seconds
+func (p *PodHealthCheck) SetTimeout(timeoutSeconds int) *PodHealthCheck {
+	p.TimeoutSeconds = &timeoutSeconds
+	return p
+}
+
+// SetDelay sets the length of time a pod will delay running health checks on initial launch, in seconds
+func (p *PodHealthCheck) SetDelay(delaySeconds int) *PodHealthCheck {
+	p.DelaySeconds = &delaySeconds
+	return p
+}
+
+// SetEndpoint sets the name of the pod health check endpoint
+func (h *HTTPHealthCheck) SetEndpoint(endpoint string) *HTTPHealthCheck {
+	h.Endpoint = endpoint
+	return h
+}
+
+// SetPath sets the HTTP path of the pod health check endpoint
+func (h *HTTPHealthCheck) SetPath(path string) *HTTPHealthCheck {
+	h.Path = path
+	return h
+}
+
+// SetScheme sets the HTTP scheme of the pod health check endpoint
+func (h *HTTPHealthCheck) SetScheme(scheme string) *HTTPHealthCheck {
+	h.Scheme = scheme
+	return h
+}
+
+// SetEndpoint sets the name of the pod health check endpoint
+func (t *TCPHealthCheck) SetEndpoint(endpoint string) *TCPHealthCheck {
+	t.Endpoint = endpoint
+	return t
+}
+
+// SetCommand sets a CommandHealthCheck's underlying PodCommand
+func (c *CommandHealthCheck) SetCommand(p PodCommand) *CommandHealthCheck {
+	c.Command = p
+	return c
+}
diff --git a/health_test.go b/health_test.go
new file mode 100644
index 0000000..825e097
--- /dev/null
+++ b/health_test.go
@@ -0,0 +1,60 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCommand(t *testing.T) {
+	hc := new(HealthCheck)
+	command := Command{"curl localhost:8080"}
+	hc.SetCommand(command)
+	assert.Equal(t, command, (*hc.Command))
+}
+
+func TestPortIndex(t *testing.T) {
+	hc := new(HealthCheck)
+	hc.SetPortIndex(0)
+	assert.Equal(t, 0, (*hc.PortIndex))
+}
+
+func TestPort(t *testing.T) {
+	hc := new(HealthCheck)
+	hc.SetPort(8000)
+	assert.Equal(t, 8000, (*hc.Port))
+}
+
+func TestPath(t *testing.T) {
+	hc := new(HealthCheck)
+	hc.SetPath("/path")
+	assert.Equal(t, "/path", (*hc.Path))
+}
+
+func TestMaxConsecutiveFailures(t *testing.T) {
+	hc := new(HealthCheck)
+	hc.SetMaxConsecutiveFailures(3)
+	assert.Equal(t, 3, (*hc.MaxConsecutiveFailures))
+}
+
+func TestIgnoreHTTP1xx(t *testing.T) {
+	hc := new(HealthCheck)
+	hc.SetIgnoreHTTP1xx(true)
+	assert.True(t, (*hc.IgnoreHTTP1xx))
+}
diff --git a/info.go b/info.go
index e38cc9e..45f5d68 100644
--- a/info.go
+++ b/info.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/info_test.go b/info_test.go
index 6963cb5..2ea6811 100644
--- a/info_test.go
+++ b/info_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/last_task_failure.go b/last_task_failure.go
index 1870f28..357deee 100644
--- a/last_task_failure.go
+++ b/last_task_failure.go
@@ -1,4 +1,5 @@
 /*
+Copyright 2015 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -20,6 +21,7 @@ type LastTaskFailure struct {
 	AppID     string `json:"appId,omitempty"`
 	Host      string `json:"host,omitempty"`
 	Message   string `json:"message,omitempty"`
+	SlaveID   string `json:"slaveId,omitempty"`
 	State     string `json:"state,omitempty"`
 	TaskID    string `json:"taskId,omitempty"`
 	Timestamp string `json:"timestamp,omitempty"`
diff --git a/network.go b/network.go
new file mode 100644
index 0000000..6804996
--- /dev/null
+++ b/network.go
@@ -0,0 +1,124 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+// PodNetworkMode is the mode of a network descriptor
+type PodNetworkMode string
+
+const (
+	ContainerNetworkMode PodNetworkMode = "container"
+	BridgeNetworkMode    PodNetworkMode = "container/bridge"
+	HostNetworkMode      PodNetworkMode = "host"
+)
+
+// PodNetwork contains network descriptors for a pod
+type PodNetwork struct {
+	Name   string            `json:"name,omitempty"`
+	Mode   PodNetworkMode    `json:"mode,omitempty"`
+	Labels map[string]string `json:"labels,omitempty"`
+}
+
+// PodEndpoint describes an endpoint for a pod's container
+type PodEndpoint struct {
+	Name          string            `json:"name,omitempty"`
+	ContainerPort int               `json:"containerPort,omitempty"`
+	HostPort      int               `json:"hostPort,omitempty"`
+	Protocol      []string          `json:"protocol,omitempty"`
+	Labels        map[string]string `json:"labels,omitempty"`
+}
+
+// NewPodNetwork creates an empty PodNetwork
+func NewPodNetwork(name string) *PodNetwork {
+	return &PodNetwork{
+		Name:   name,
+		Labels: map[string]string{},
+	}
+}
+
+// NewPodEndpoint creates an empty PodEndpoint
+func NewPodEndpoint() *PodEndpoint {
+	return &PodEndpoint{
+		Protocol: []string{},
+		Labels:   map[string]string{},
+	}
+}
+
+// NewBridgePodNetwork creates a PodNetwork for a container in bridge mode
+func NewBridgePodNetwork() *PodNetwork {
+	pn := NewPodNetwork("")
+	return pn.SetMode(BridgeNetworkMode)
+}
+
+// NewContainerPodNetwork creates a PodNetwork for a container
+func NewContainerPodNetwork(name string) *PodNetwork {
+	pn := NewPodNetwork(name)
+	return pn.SetMode(ContainerNetworkMode)
+}
+
+// NewHostPodNetwork creates a PodNetwork for a container in host mode
+func NewHostPodNetwork() *PodNetwork {
+	pn := NewPodNetwork("")
+	return pn.SetMode(HostNetworkMode)
+}
+
+// SetName sets the name of a PodNetwork
+func (n *PodNetwork) SetName(name string) *PodNetwork {
+	n.Name = name
+	return n
+}
+
+// SetMode sets the mode of a PodNetwork
+func (n *PodNetwork) SetMode(mode PodNetworkMode) *PodNetwork {
+	n.Mode = mode
+	return n
+}
+
+// Label sets a label of a PodNetwork
+func (n *PodNetwork) Label(key, value string) *PodNetwork {
+	n.Labels[key] = value
+	return n
+}
+
+// SetName sets the name for a PodEndpoint
+func (e *PodEndpoint) SetName(name string) *PodEndpoint {
+	e.Name = name
+	return e
+}
+
+// SetContainerPort sets the container port for a PodEndpoint
+func (e *PodEndpoint) SetContainerPort(port int) *PodEndpoint {
+	e.ContainerPort = port
+	return e
+}
+
+// SetHostPort sets the host port for a PodEndpoint
+func (e *PodEndpoint) SetHostPort(port int) *PodEndpoint {
+	e.HostPort = port
+	return e
+}
+
+// AddProtocol appends a protocol for a PodEndpoint
+func (e *PodEndpoint) AddProtocol(protocol string) *PodEndpoint {
+	e.Protocol = append(e.Protocol, protocol)
+	return e
+}
+
+// Label sets a label for a PodEndpoint
+func (e *PodEndpoint) Label(key, value string) *PodEndpoint {
+	e.Labels[key] = value
+	return e
+}
diff --git a/offer.go b/offer.go
new file mode 100644
index 0000000..d46f124
--- /dev/null
+++ b/offer.go
@@ -0,0 +1,51 @@
+/*
+Copyright 2019 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package marathon
+
+// based on https://github.com/mesosphere/marathon/blob/e7b1456ad0cfba23c9fdfa3c5d638a4b9aeb60d0/docs/docs/rest-api/public/api/v2/types/offer.raml
+
+// Offer describes a Mesos offer to a framework
+type Offer struct {
+	ID         string           `json:"id"`
+	Hostname   string           `json:"hostname"`
+	AgentID    string           `json:"agentId"`
+	Resources  []OfferResource  `json:"resources"`
+	Attributes []AgentAttribute `json:"attributes"`
+}
+
+// OfferResource describes a resource that is part of an offer
+type OfferResource struct {
+	Name   string        `json:"name"`
+	Role   string        `json:"role"`
+	Scalar *float64      `json:"scalar,omitempty"`
+	Ranges []NumberRange `json:"ranges,omitempty"`
+	Set    []string      `json:"set,omitempty"`
+}
+
+// NumberRange is a range of numbers
+type NumberRange struct {
+	Begin int64 `json:"begin"`
+	End   int64 `json:"end"`
+}
+
+// AgentAttribute describes an attribute of an agent node
+type AgentAttribute struct {
+	Name   string        `json:"name"`
+	Text   *string       `json:"text,omitempty"`
+	Scalar *float64      `json:"scalar,omitempty"`
+	Ranges []NumberRange `json:"ranges,omitempty"`
+	Set    []string      `json:"set,omitempty"`
+}
diff --git a/pod.go b/pod.go
new file mode 100644
index 0000000..b3402c7
--- /dev/null
+++ b/pod.go
@@ -0,0 +1,278 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"fmt"
+)
+
+// Pod is the definition for an pod in marathon
+type Pod struct {
+	ID      string            `json:"id,omitempty"`
+	Labels  map[string]string `json:"labels,omitempty"`
+	Version string            `json:"version,omitempty"`
+	User    string            `json:"user,omitempty"`
+	// Non-secret environment variables. Actual secrets are stored in Secrets
+	// Magic happens at marshaling/unmarshaling to get them into the correct schema
+	Env               map[string]string    `json:"-"`
+	Secrets           map[string]Secret    `json:"-"`
+	Containers        []*PodContainer      `json:"containers,omitempty"`
+	Volumes           []*PodVolume         `json:"volumes,omitempty"`
+	Networks          []*PodNetwork        `json:"networks,omitempty"`
+	Scaling           *PodScalingPolicy    `json:"scaling,omitempty"`
+	Scheduling        *PodSchedulingPolicy `json:"scheduling,omitempty"`
+	ExecutorResources *ExecutorResources   `json:"executorResources,omitempty"`
+	Role              *string              `json:"role,omitempty"`
+}
+
+// PodScalingPolicy is the scaling policy of the pod
+type PodScalingPolicy struct {
+	Kind         string `json:"kind"`
+	Instances    int    `json:"instances"`
+	MaxInstances int    `json:"maxInstances,omitempty"`
+}
+
+// NewPod create an empty pod
+func NewPod() *Pod {
+	return &Pod{
+		Labels:     map[string]string{},
+		Env:        map[string]string{},
+		Containers: []*PodContainer{},
+		Secrets:    map[string]Secret{},
+		Volumes:    []*PodVolume{},
+		Networks:   []*PodNetwork{},
+	}
+}
+
+// Name sets the name / ID of the pod i.e. the identifier for this pod
+func (p *Pod) Name(id string) *Pod {
+	p.ID = validateID(id)
+	return p
+}
+
+// SetUser sets the user to run the pod as
+func (p *Pod) SetUser(user string) *Pod {
+	p.User = user
+	return p
+}
+
+// EmptyLabels empties the labels in a pod
+func (p *Pod) EmptyLabels() *Pod {
+	p.Labels = make(map[string]string)
+	return p
+}
+
+// AddLabel adds a label to a pod
+func (p *Pod) AddLabel(key, value string) *Pod {
+	p.Labels[key] = value
+	return p
+}
+
+// SetLabels sets the labels for a pod
+func (p *Pod) SetLabels(labels map[string]string) *Pod {
+	p.Labels = labels
+	return p
+}
+
+// EmptyEnvs empties the environment variables for a pod
+func (p *Pod) EmptyEnvs() *Pod {
+	p.Env = make(map[string]string)
+	return p
+}
+
+// AddEnv adds an environment variable to a pod
+func (p *Pod) AddEnv(name, value string) *Pod {
+	if p.Env == nil {
+		p = p.EmptyEnvs()
+	}
+	p.Env[name] = value
+	return p
+}
+
+// ExtendEnv extends the environment with the new environment variables
+func (p *Pod) ExtendEnv(env map[string]string) *Pod {
+	if p.Env == nil {
+		p = p.EmptyEnvs()
+	}
+
+	for k, v := range env {
+		p.AddEnv(k, v)
+	}
+	return p
+}
+
+// AddContainer adds a container to a pod
+func (p *Pod) AddContainer(container *PodContainer) *Pod {
+	p.Containers = append(p.Containers, container)
+	return p
+}
+
+// EmptySecrets empties the secret sources in a pod
+func (p *Pod) EmptySecrets() *Pod {
+	p.Secrets = make(map[string]Secret)
+	return p
+}
+
+// GetSecretSource gets the source of the named secret
+func (p *Pod) GetSecretSource(name string) (string, error) {
+	if val, ok := p.Secrets[name]; ok {
+		return val.Source, nil
+	}
+	return "", fmt.Errorf("secret does not exist")
+}
+
+// AddSecret adds the secret to the pod
+func (p *Pod) AddSecret(envVar, secretName, sourceName string) *Pod {
+	if p.Secrets == nil {
+		p = p.EmptySecrets()
+	}
+	p.Secrets[secretName] = Secret{EnvVar: envVar, Source: sourceName}
+	return p
+}
+
+// AddVolume adds a volume to a pod
+func (p *Pod) AddVolume(vol *PodVolume) *Pod {
+	p.Volumes = append(p.Volumes, vol)
+	return p
+}
+
+// AddNetwork adds a PodNetwork to a pod
+func (p *Pod) AddNetwork(net *PodNetwork) *Pod {
+	p.Networks = append(p.Networks, net)
+	return p
+}
+
+// Count sets the count of the pod
+func (p *Pod) Count(count int) *Pod {
+	p.Scaling = &PodScalingPolicy{
+		Kind:      "fixed",
+		Instances: count,
+	}
+	return p
+}
+
+// SetPodSchedulingPolicy sets the PodSchedulingPolicy of a pod
+func (p *Pod) SetPodSchedulingPolicy(policy *PodSchedulingPolicy) *Pod {
+	p.Scheduling = policy
+	return p
+}
+
+// SetExecutorResources sets the resources for the pod executor
+func (p *Pod) SetExecutorResources(resources *ExecutorResources) *Pod {
+	p.ExecutorResources = resources
+	return p
+}
+
+// SupportsPods determines if this version of marathon supports pods
+// If HEAD returns 200 it does
+func (r *marathonClient) SupportsPods() (bool, error) {
+	if err := r.apiHead(marathonAPIPods, nil); err != nil {
+		// If we get a 404 we can return a strict false, otherwise it could be
+		// a valid error
+		if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
+			return false, nil
+		}
+		return false, err
+	}
+
+	return true, nil
+}
+
+// Pod gets a pod object from marathon by name
+func (r *marathonClient) Pod(name string) (*Pod, error) {
+	uri := buildPodURI(name)
+	result := new(Pod)
+	if err := r.apiGet(uri, nil, result); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// Pods gets all pods from marathon
+func (r *marathonClient) Pods() ([]Pod, error) {
+	var result []Pod
+	if err := r.apiGet(marathonAPIPods, nil, &result); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// CreatePod creates a new pod in Marathon
+func (r *marathonClient) CreatePod(pod *Pod) (*Pod, error) {
+	result := new(Pod)
+	if err := r.ApiPost(marathonAPIPods, &pod, result); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// DeletePod deletes a pod from marathon
+func (r *marathonClient) DeletePod(name string, force bool) (*DeploymentID, error) {
+	uri := fmt.Sprintf("%s?force=%v", buildPodURI(name), force)
+
+	deployID := new(DeploymentID)
+	if err := r.apiDelete(uri, nil, deployID); err != nil {
+		return nil, err
+	}
+
+	return deployID, nil
+}
+
+// UpdatePod creates a new pod in Marathon
+func (r *marathonClient) UpdatePod(pod *Pod, force bool) (*Pod, error) {
+	uri := fmt.Sprintf("%s?force=%v", buildPodURI(pod.ID), force)
+	result := new(Pod)
+
+	if err := r.apiPut(uri, pod, result); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// PodVersions gets all the deployed versions of a pod
+func (r *marathonClient) PodVersions(name string) ([]string, error) {
+	uri := buildPodVersionURI(name)
+	var result []string
+	if err := r.apiGet(uri, nil, &result); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// PodByVersion gets a pod by a version identifier
+func (r *marathonClient) PodByVersion(name, version string) (*Pod, error) {
+	uri := fmt.Sprintf("%s/%s", buildPodVersionURI(name), version)
+	result := new(Pod)
+	if err := r.apiGet(uri, nil, result); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func buildPodVersionURI(name string) string {
+	return fmt.Sprintf("%s/%s::versions", marathonAPIPods, trimRootPath(name))
+}
+
+func buildPodURI(path string) string {
+	return fmt.Sprintf("%s/%s", marathonAPIPods, trimRootPath(path))
+}
diff --git a/pod_container.go b/pod_container.go
new file mode 100644
index 0000000..b8e4252
--- /dev/null
+++ b/pod_container.go
@@ -0,0 +1,193 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+// PodContainer describes a container in a pod
+type PodContainer struct {
+	Name         string             `json:"name,omitempty"`
+	Exec         *PodExec           `json:"exec,omitempty"`
+	Resources    *Resources         `json:"resources,omitempty"`
+	Endpoints    []*PodEndpoint     `json:"endpoints,omitempty"`
+	Image        *PodContainerImage `json:"image,omitempty"`
+	Env          map[string]string  `json:"-"`
+	Secrets      map[string]Secret  `json:"-"`
+	User         string             `json:"user,omitempty"`
+	HealthCheck  *PodHealthCheck    `json:"healthCheck,omitempty"`
+	VolumeMounts []*PodVolumeMount  `json:"volumeMounts,omitempty"`
+	Artifacts    []*PodArtifact     `json:"artifacts,omitempty"`
+	Labels       map[string]string  `json:"labels,omitempty"`
+	Lifecycle    PodLifecycle       `json:"lifecycle,omitempty"`
+}
+
+// PodLifecycle describes the lifecycle of a pod
+type PodLifecycle struct {
+	KillGracePeriodSeconds *float64 `json:"killGracePeriodSeconds,omitempty"`
+}
+
+// PodCommand is the command to run as the entrypoint of the container
+type PodCommand struct {
+	Shell string `json:"shell,omitempty"`
+}
+
+// PodExec contains the PodCommand
+type PodExec struct {
+	Command PodCommand `json:"command,omitempty"`
+}
+
+// PodArtifact describes how to obtain a generic artifact for a pod
+type PodArtifact struct {
+	URI        string `json:"uri,omitempty"`
+	Extract    *bool  `json:"extract,omitempty"`
+	Executable *bool  `json:"executable,omitempty"`
+	Cache      *bool  `json:"cache,omitempty"`
+	DestPath   string `json:"destPath,omitempty"`
+}
+
+// NewPodContainer creates an empty PodContainer
+func NewPodContainer() *PodContainer {
+	return &PodContainer{
+		Endpoints:    []*PodEndpoint{},
+		Env:          map[string]string{},
+		VolumeMounts: []*PodVolumeMount{},
+		Artifacts:    []*PodArtifact{},
+		Labels:       map[string]string{},
+		Resources:    NewResources(),
+	}
+}
+
+// SetName sets the name of a pod container
+func (p *PodContainer) SetName(name string) *PodContainer {
+	p.Name = name
+	return p
+}
+
+// SetCommand sets the shell command of a pod container
+func (p *PodContainer) SetCommand(name string) *PodContainer {
+	p.Exec = &PodExec{
+		Command: PodCommand{
+			Shell: name,
+		},
+	}
+	return p
+}
+
+// CPUs sets the CPUs of a pod container
+func (p *PodContainer) CPUs(cpu float64) *PodContainer {
+	p.Resources.Cpus = cpu
+	return p
+}
+
+// Memory sets the memory of a pod container
+func (p *PodContainer) Memory(memory float64) *PodContainer {
+	p.Resources.Mem = memory
+	return p
+}
+
+// Storage sets the storage capacity of a pod container
+func (p *PodContainer) Storage(disk float64) *PodContainer {
+	p.Resources.Disk = disk
+	return p
+}
+
+// GPUs sets the GPU requirements of a pod container
+func (p *PodContainer) GPUs(gpu int32) *PodContainer {
+	p.Resources.Gpus = gpu
+	return p
+}
+
+// AddEndpoint appends an endpoint for a pod container
+func (p *PodContainer) AddEndpoint(endpoint *PodEndpoint) *PodContainer {
+	p.Endpoints = append(p.Endpoints, endpoint)
+	return p
+}
+
+// SetImage sets the image of a pod container
+func (p *PodContainer) SetImage(image *PodContainerImage) *PodContainer {
+	p.Image = image
+	return p
+}
+
+// EmptyEnvs initialized env to empty
+func (p *PodContainer) EmptyEnvs() *PodContainer {
+	p.Env = make(map[string]string)
+	return p
+}
+
+// AddEnv adds an environment variable for a pod container
+func (p *PodContainer) AddEnv(name, value string) *PodContainer {
+	if p.Env == nil {
+		p = p.EmptyEnvs()
+	}
+	p.Env[name] = value
+	return p
+}
+
+// ExtendEnv extends the environment for a pod container
+func (p *PodContainer) ExtendEnv(env map[string]string) *PodContainer {
+	if p.Env == nil {
+		p = p.EmptyEnvs()
+	}
+	for k, v := range env {
+		p.AddEnv(k, v)
+	}
+	return p
+}
+
+// AddSecret adds a secret to the environment for a pod container
+func (p *PodContainer) AddSecret(name, secretName string) *PodContainer {
+	if p.Env == nil {
+		p = p.EmptyEnvs()
+	}
+	p.Env[name] = secretName
+	return p
+}
+
+// SetUser sets the user to run the pod as
+func (p *PodContainer) SetUser(user string) *PodContainer {
+	p.User = user
+	return p
+}
+
+// SetHealthCheck sets the health check of a pod container
+func (p *PodContainer) SetHealthCheck(healthcheck *PodHealthCheck) *PodContainer {
+	p.HealthCheck = healthcheck
+	return p
+}
+
+// AddVolumeMount appends a volume mount to a pod container
+func (p *PodContainer) AddVolumeMount(mount *PodVolumeMount) *PodContainer {
+	p.VolumeMounts = append(p.VolumeMounts, mount)
+	return p
+}
+
+// AddArtifact appends an artifact to a pod container
+func (p *PodContainer) AddArtifact(artifact *PodArtifact) *PodContainer {
+	p.Artifacts = append(p.Artifacts, artifact)
+	return p
+}
+
+// AddLabel adds a label to a pod container
+func (p *PodContainer) AddLabel(key, value string) *PodContainer {
+	p.Labels[key] = value
+	return p
+}
+
+// SetLifecycle sets the lifecycle of a pod container
+func (p *PodContainer) SetLifecycle(lifecycle PodLifecycle) *PodContainer {
+	p.Lifecycle = lifecycle
+	return p
+}
diff --git a/pod_container_image.go b/pod_container_image.go
new file mode 100644
index 0000000..83eb2db
--- /dev/null
+++ b/pod_container_image.go
@@ -0,0 +1,65 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+// ImageType represents the image format type
+type ImageType string
+
+const (
+	// ImageTypeDocker is the docker format
+	ImageTypeDocker ImageType = "DOCKER"
+
+	// ImageTypeAppC is the appc format
+	ImageTypeAppC ImageType = "APPC"
+)
+
+// PodContainerImage describes how to retrieve the container image
+type PodContainerImage struct {
+	Kind       ImageType   `json:"kind,omitempty"`
+	ID         string      `json:"id,omitempty"`
+	ForcePull  *bool       `json:"forcePull,omitempty"`
+	PullConfig *PullConfig `json:"pullConfig,omitempty"`
+}
+
+// NewPodContainerImage creates an empty PodContainerImage
+func NewPodContainerImage() *PodContainerImage {
+	return &PodContainerImage{}
+}
+
+// SetKind sets the Kind of the image
+func (i *PodContainerImage) SetKind(typ ImageType) *PodContainerImage {
+	i.Kind = typ
+	return i
+}
+
+// SetID sets the ID of the image
+func (i *PodContainerImage) SetID(id string) *PodContainerImage {
+	i.ID = id
+	return i
+}
+
+// SetPullConfig adds *PullConfig to PodContainerImage
+func (i *PodContainerImage) SetPullConfig(pullConfig *PullConfig) *PodContainerImage {
+	i.PullConfig = pullConfig
+
+	return i
+}
+
+// NewDockerPodContainerImage creates a docker PodContainerImage
+func NewDockerPodContainerImage() *PodContainerImage {
+	return NewPodContainerImage().SetKind(ImageTypeDocker)
+}
diff --git a/pod_container_marshalling.go b/pod_container_marshalling.go
new file mode 100644
index 0000000..917aeac
--- /dev/null
+++ b/pod_container_marshalling.go
@@ -0,0 +1,94 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// PodContainerAlias aliases the PodContainer struct so that it will be marshaled/unmarshaled automatically
+type PodContainerAlias PodContainer
+
+// UnmarshalJSON unmarshals the given PodContainer JSON as expected except for environment variables and secrets.
+// Environment variables are stored in the Env field. Secrets, including the environment variable part,
+// are stored in the Secrets field.
+func (p *PodContainer) UnmarshalJSON(b []byte) error {
+	aux := &struct {
+		*PodContainerAlias
+		Env map[string]interface{} `json:"environment"`
+	}{
+		PodContainerAlias: (*PodContainerAlias)(p),
+	}
+	if err := json.Unmarshal(b, aux); err != nil {
+		return fmt.Errorf("malformed pod container definition %v", err)
+	}
+	env := map[string]string{}
+	secrets := map[string]Secret{}
+
+	for envName, genericEnvValue := range aux.Env {
+		switch envValOrSecret := genericEnvValue.(type) {
+		case string:
+			env[envName] = envValOrSecret
+		case map[string]interface{}:
+			for secret, secretStore := range envValOrSecret {
+				if secStore, ok := secretStore.(string); ok && secret == "secret" {
+					secrets[secStore] = Secret{EnvVar: envName}
+					break
+				}
+				return fmt.Errorf("unexpected secret field %v of value type %T", secret, envValOrSecret[secret])
+			}
+		default:
+			return fmt.Errorf("unexpected environment variable type %T", envValOrSecret)
+		}
+	}
+	p.Env = env
+	for k, v := range aux.Secrets {
+		tmp := secrets[k]
+		tmp.Source = v.Source
+		secrets[k] = tmp
+	}
+	p.Secrets = secrets
+	return nil
+}
+
+// MarshalJSON marshals the given PodContainer as expected except for environment variables and secrets,
+// which are marshaled from specialized structs.  The environment variable piece of the secrets and other
+// normal environment variables are combined and marshaled to the env field.  The secrets and the related
+// source are marshaled into the secrets field.
+func (p *PodContainer) MarshalJSON() ([]byte, error) {
+	env := make(map[string]interface{})
+	secrets := make(map[string]TmpSecret)
+
+	if p.Env != nil {
+		for k, v := range p.Env {
+			env[string(k)] = string(v)
+		}
+	}
+	if p.Secrets != nil {
+		for k, v := range p.Secrets {
+			env[v.EnvVar] = TmpEnvSecret{Secret: k}
+			secrets[k] = TmpSecret{v.Source}
+		}
+	}
+	aux := &struct {
+		*PodContainerAlias
+		Env map[string]interface{} `json:"environment,omitempty"`
+	}{PodContainerAlias: (*PodContainerAlias)(p), Env: env}
+
+	return json.Marshal(aux)
+}
diff --git a/pod_instance.go b/pod_instance.go
new file mode 100644
index 0000000..5df0afa
--- /dev/null
+++ b/pod_instance.go
@@ -0,0 +1,105 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"fmt"
+	"time"
+)
+
+// PodInstance is the representation of an instance as returned by deleting an instance
+type PodInstance struct {
+	InstanceID          PodInstanceID              `json:"instanceId"`
+	AgentInfo           PodAgentInfo               `json:"agentInfo"`
+	TasksMap            map[string]PodTask         `json:"tasksMap"`
+	RunSpecVersion      time.Time                  `json:"runSpecVersion"`
+	State               PodInstanceStateHistory    `json:"state"`
+	UnreachableStrategy EnabledUnreachableStrategy `json:"unreachableStrategy"`
+}
+
+// PodInstanceStateHistory is the pod instance's state
+type PodInstanceStateHistory struct {
+	Condition   PodTaskCondition `json:"condition"`
+	Since       time.Time        `json:"since"`
+	ActiveSince time.Time        `json:"activeSince"`
+}
+
+// PodInstanceID contains the instance ID
+type PodInstanceID struct {
+	ID string `json:"idString"`
+}
+
+// PodAgentInfo contains info about the agent the instance is running on
+type PodAgentInfo struct {
+	Host       string   `json:"host"`
+	AgentID    string   `json:"agentId"`
+	Attributes []string `json:"attributes"`
+}
+
+// PodTask contains the info about the specific task within the instance
+type PodTask struct {
+	TaskID         string        `json:"taskId"`
+	RunSpecVersion time.Time     `json:"runSpecVersion"`
+	Status         PodTaskStatus `json:"status"`
+}
+
+// PodTaskStatus is the current status of the task
+type PodTaskStatus struct {
+	StagedAt    time.Time        `json:"stagedAt"`
+	StartedAt   time.Time        `json:"startedAt"`
+	MesosStatus string           `json:"mesosStatus"`
+	Condition   PodTaskCondition `json:"condition"`
+	NetworkInfo PodNetworkInfo   `json:"networkInfo"`
+}
+
+// PodTaskCondition contains a string representation of the condition
+type PodTaskCondition struct {
+	Str string `json:"str"`
+}
+
+// PodNetworkInfo contains the network info for a task
+type PodNetworkInfo struct {
+	HostName    string      `json:"hostName"`
+	HostPorts   []int       `json:"hostPorts"`
+	IPAddresses []IPAddress `json:"ipAddresses"`
+}
+
+// DeletePodInstances deletes all instances of the named pod
+func (r *marathonClient) DeletePodInstances(name string, instances []string) ([]*PodInstance, error) {
+	uri := buildPodInstancesURI(name)
+	var result []*PodInstance
+	if err := r.apiDelete(uri, instances, &result); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// DeletePodInstance deletes a specific instance of a pod
+func (r *marathonClient) DeletePodInstance(name, instance string) (*PodInstance, error) {
+	uri := fmt.Sprintf("%s/%s", buildPodInstancesURI(name), instance)
+	result := new(PodInstance)
+	if err := r.apiDelete(uri, nil, result); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func buildPodInstancesURI(path string) string {
+	return fmt.Sprintf("%s/%s::instances", marathonAPIPods, trimRootPath(path))
+}
diff --git a/pod_instance_status.go b/pod_instance_status.go
new file mode 100644
index 0000000..cc89155
--- /dev/null
+++ b/pod_instance_status.go
@@ -0,0 +1,89 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+// PodInstanceState is the state of a specific pod instance
+type PodInstanceState string
+
+const (
+	// PodInstanceStatePending is when an instance is pending scheduling
+	PodInstanceStatePending PodInstanceState = "PENDING"
+
+	// PodInstanceStateStaging is when an instance is staged to be scheduled
+	PodInstanceStateStaging PodInstanceState = "STAGING"
+
+	// PodInstanceStateStable is when an instance is stably running
+	PodInstanceStateStable PodInstanceState = "STABLE"
+
+	// PodInstanceStateDegraded is when an instance is degraded status
+	PodInstanceStateDegraded PodInstanceState = "DEGRADED"
+
+	// PodInstanceStateTerminal is when an instance is terminal
+	PodInstanceStateTerminal PodInstanceState = "TERMINAL"
+)
+
+// PodInstanceStatus is the status of a pod instance
+type PodInstanceStatus struct {
+	AgentHostname string              `json:"agentHostname,omitempty"`
+	Conditions    []*StatusCondition  `json:"conditions,omitempty"`
+	Containers    []*ContainerStatus  `json:"containers,omitempty"`
+	ID            string              `json:"id,omitempty"`
+	LastChanged   string              `json:"lastChanged,omitempty"`
+	LastUpdated   string              `json:"lastUpdated,omitempty"`
+	Message       string              `json:"message,omitempty"`
+	Networks      []*PodNetworkStatus `json:"networks,omitempty"`
+	Resources     *Resources          `json:"resources,omitempty"`
+	SpecReference string              `json:"specReference,omitempty"`
+	Status        PodInstanceState    `json:"status,omitempty"`
+	StatusSince   string              `json:"statusSince,omitempty"`
+}
+
+// PodNetworkStatus is the networks attached to a pod instance
+type PodNetworkStatus struct {
+	Addresses []string `json:"addresses,omitempty"`
+	Name      string   `json:"name,omitempty"`
+}
+
+// StatusCondition describes info about a status change
+type StatusCondition struct {
+	Name        string `json:"name,omitempty"`
+	Value       string `json:"value,omitempty"`
+	Reason      string `json:"reason,omitempty"`
+	LastChanged string `json:"lastChanged,omitempty"`
+	LastUpdated string `json:"lastUpdated,omitempty"`
+}
+
+// ContainerStatus contains all status information for a container instance
+type ContainerStatus struct {
+	Conditions  []*StatusCondition         `json:"conditions,omitempty"`
+	ContainerID string                     `json:"containerId,omitempty"`
+	Endpoints   []*PodEndpoint             `json:"endpoints,omitempty"`
+	LastChanged string                     `json:"lastChanged,omitempty"`
+	LastUpdated string                     `json:"lastUpdated,omitempty"`
+	Message     string                     `json:"message,omitempty"`
+	Name        string                     `json:"name,omitempty"`
+	Resources   *Resources                 `json:"resources,omitempty"`
+	Status      string                     `json:"status,omitempty"`
+	StatusSince string                     `json:"statusSince,omitempty"`
+	Termination *ContainerTerminationState `json:"termination,omitempty"`
+}
+
+// ContainerTerminationState describes why a container terminated
+type ContainerTerminationState struct {
+	ExitCode int    `json:"exitCode,omitempty"`
+	Message  string `json:"message,omitempty"`
+}
diff --git a/pod_instance_test.go b/pod_instance_test.go
new file mode 100644
index 0000000..d79755a
--- /dev/null
+++ b/pod_instance_test.go
@@ -0,0 +1,45 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+const fakePodInstanceName = "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003"
+
+func TestDeletePodInstance(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	podInstance, err := endpoint.Client.DeletePodInstance(fakePodName, fakePodInstanceName)
+	require.NoError(t, err)
+	assert.Equal(t, podInstance.InstanceID.ID, fakePodInstanceName)
+}
+
+func TestDeletePodInstances(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	instances := []string{fakePodInstanceName}
+	podInstances, err := endpoint.Client.DeletePodInstances(fakePodName, instances)
+	require.NoError(t, err)
+	assert.Equal(t, podInstances[0].InstanceID.ID, fakePodInstanceName)
+}
diff --git a/pod_marshalling.go b/pod_marshalling.go
new file mode 100644
index 0000000..de1cb5f
--- /dev/null
+++ b/pod_marshalling.go
@@ -0,0 +1,100 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// PodAlias aliases the Pod struct so that it will be marshaled/unmarshaled automatically
+type PodAlias Pod
+
+// UnmarshalJSON unmarshals the given Pod JSON as expected except for environment variables and secrets.
+// Environment variables are stored in the Env field. Secrets, including the environment variable part,
+// are stored in the Secrets field.
+func (p *Pod) UnmarshalJSON(b []byte) error {
+	aux := &struct {
+		*PodAlias
+		Env     map[string]interface{} `json:"environment"`
+		Secrets map[string]TmpSecret   `json:"secrets"`
+	}{
+		PodAlias: (*PodAlias)(p),
+	}
+	if err := json.Unmarshal(b, aux); err != nil {
+		return fmt.Errorf("malformed pod definition %v", err)
+	}
+	env := map[string]string{}
+	secrets := map[string]Secret{}
+
+	for envName, genericEnvValue := range aux.Env {
+		switch envValOrSecret := genericEnvValue.(type) {
+		case string:
+			env[envName] = envValOrSecret
+		case map[string]interface{}:
+			for secret, secretStore := range envValOrSecret {
+				if secStore, ok := secretStore.(string); ok && secret == "secret" {
+					secrets[secStore] = Secret{EnvVar: envName}
+					break
+				}
+				return fmt.Errorf("unexpected secret field %v of value type %T", secret, envValOrSecret[secret])
+			}
+		default:
+			return fmt.Errorf("unexpected environment variable type %T", envValOrSecret)
+		}
+	}
+	p.Env = env
+	for k, v := range aux.Secrets {
+		tmp := secrets[k]
+		tmp.Source = v.Source
+		secrets[k] = tmp
+	}
+	p.Secrets = secrets
+	return nil
+}
+
+// MarshalJSON marshals the given Pod as expected except for environment variables and secrets,
+// which are marshaled from specialized structs.  The environment variable piece of the secrets and other
+// normal environment variables are combined and marshaled to the env field.  The secrets and the related
+// source are marshaled into the secrets field.
+func (p *Pod) MarshalJSON() ([]byte, error) {
+	env := make(map[string]interface{})
+	secrets := make(map[string]TmpSecret)
+
+	if p.Env != nil {
+		for k, v := range p.Env {
+			env[string(k)] = string(v)
+		}
+	}
+	if p.Secrets != nil {
+		for k, v := range p.Secrets {
+			// Only add it to the root level pod environment if it's used
+			// Otherwise it's likely in one of the container environments
+			if v.EnvVar != "" {
+				env[v.EnvVar] = TmpEnvSecret{Secret: k}
+			}
+			secrets[k] = TmpSecret{v.Source}
+		}
+	}
+	aux := &struct {
+		*PodAlias
+		Env     map[string]interface{} `json:"environment,omitempty"`
+		Secrets map[string]TmpSecret   `json:"secrets,omitempty"`
+	}{PodAlias: (*PodAlias)(p), Env: env, Secrets: secrets}
+
+	return json.Marshal(aux)
+}
diff --git a/pod_marshalling_test.go b/pod_marshalling_test.go
new file mode 100644
index 0000000..bd7008e
--- /dev/null
+++ b/pod_marshalling_test.go
@@ -0,0 +1,118 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"encoding/json"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestPodEnvironmentVariableUnmarshal(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	pod, err := endpoint.Client.Pod(fakePodName)
+	require.NoError(t, err)
+
+	env := pod.Env
+	secrets := pod.Secrets
+
+	require.NotNil(t, env)
+	assert.Equal(t, "value", env["key1"])
+	assert.Equal(t, "key2", secrets["secret0"].EnvVar)
+	assert.Equal(t, "source0", secrets["secret0"].Source)
+
+	assert.Equal(t, "value3", pod.Containers[0].Env["key3"])
+	assert.Equal(t, "key4", pod.Containers[0].Secrets["secret1"].EnvVar)
+	assert.Equal(t, "source1", secrets["secret1"].Source)
+}
+
+func TestPodMalformedPayloadUnmarshal(t *testing.T) {
+	var tests = []struct {
+		expected    string
+		given       []byte
+		description string
+	}{
+		{
+			expected:    "unexpected secret field",
+			given:       []byte(`{"environment": {"FOO": "bar", "SECRET": {"not_secret": "secret1"}}, "secrets": {"secret1": {"source": "/path/to/secret"}}}`),
+			description: "Field in environment secret not equal to secret.",
+		},
+		{
+			expected:    "unexpected secret field",
+			given:       []byte(`{"environment": {"FOO": "bar", "SECRET": {"secret": 1}}, "secrets": {"secret1": {"source": "/path/to/secret"}}}`),
+			description: "Invalid value in environment secret.",
+		},
+		{
+			expected:    "unexpected environment variable type",
+			given:       []byte(`{"environment": {"FOO": 1, "SECRET": {"secret": "secret1"}}, "secrets": {"secret1": {"source": "/path/to/secret"}}}`),
+			description: "Invalid environment variable type.",
+		},
+		{
+			expected:    "malformed pod definition",
+			given:       []byte(`{"environment": "value"}`),
+			description: "Bad pod definition.",
+		},
+	}
+
+	for _, test := range tests {
+		tmpPod := new(Pod)
+
+		err := json.Unmarshal(test.given, &tmpPod)
+		if assert.Error(t, err, test.description) {
+			assert.True(t, strings.HasPrefix(err.Error(), test.expected), test.description)
+		}
+	}
+}
+
+func TestPodEnvironmentVariableMarshal(t *testing.T) {
+	testPod := new(Pod)
+	targetString := []byte(`{"containers":[{"lifecycle":{},"environment":{"FOO2":"bar2","TOP2":"secret1"}}],"environment":{"FOO":"bar","TOP":{"secret":"secret1"}},"secrets":{"secret1":{"source":"/path/to/secret"}}}`)
+
+	testPod.AddEnv("FOO", "bar")
+	testPod.AddSecret("TOP", "secret1", "/path/to/secret")
+
+	testContainer := new(PodContainer)
+	testContainer.AddSecret("TOP2", "secret1")
+	testContainer.AddEnv("FOO2", "bar2")
+	testPod.AddContainer(testContainer)
+
+	pod, err := json.Marshal(testPod)
+	if assert.NoError(t, err) {
+		assert.Equal(t, targetString, pod)
+	}
+}
+
+func TestPodContainerArtifactBoolMarshal(t *testing.T) {
+	targetString := `{"containers":[{"artifacts":[{"extract":false}],"lifecycle":{}}]}`
+
+	testPod := new(Pod)
+	testArtifact := new(PodArtifact)
+	testArtifact.Extract = Bool(false)
+	testContainer := new(PodContainer)
+	testContainer.AddArtifact(testArtifact)
+	testPod.AddContainer(testContainer)
+
+	pod, err := json.Marshal(testPod)
+	if assert.NoError(t, err) {
+		assert.Equal(t, targetString, string(pod))
+	}
+}
diff --git a/pod_scheduling.go b/pod_scheduling.go
new file mode 100644
index 0000000..a196c13
--- /dev/null
+++ b/pod_scheduling.go
@@ -0,0 +1,157 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+// PodBackoff describes the backoff for re-run attempts of a pod
+type PodBackoff struct {
+	Backoff        *float64 `json:"backoff,omitempty"`
+	BackoffFactor  *float64 `json:"backoffFactor,omitempty"`
+	MaxLaunchDelay *float64 `json:"maxLaunchDelay,omitempty"`
+}
+
+// PodUpgrade describes the policy for upgrading a pod in-place
+type PodUpgrade struct {
+	MinimumHealthCapacity *float64 `json:"minimumHealthCapacity,omitempty"`
+	MaximumOverCapacity   *float64 `json:"maximumOverCapacity,omitempty"`
+}
+
+// PodPlacement supports constraining which hosts a pod is placed on
+type PodPlacement struct {
+	Constraints           *[]Constraint `json:"constraints"`
+	AcceptedResourceRoles []string      `json:"acceptedResourceRoles,omitempty"`
+}
+
+// PodSchedulingPolicy is the overarching pod scheduling policy
+type PodSchedulingPolicy struct {
+	Backoff             *PodBackoff          `json:"backoff,omitempty"`
+	Upgrade             *PodUpgrade          `json:"upgrade,omitempty"`
+	Placement           *PodPlacement        `json:"placement,omitempty"`
+	UnreachableStrategy *UnreachableStrategy `json:"unreachableStrategy,omitempty"`
+	KillSelection       string               `json:"killSelection,omitempty"`
+}
+
+// Constraint describes the constraint for pod placement
+type Constraint struct {
+	FieldName string `json:"fieldName"`
+	Operator  string `json:"operator"`
+	Value     string `json:"value,omitempty"`
+}
+
+// NewPodPlacement creates an empty PodPlacement
+func NewPodPlacement() *PodPlacement {
+	return &PodPlacement{
+		Constraints:           &[]Constraint{},
+		AcceptedResourceRoles: []string{},
+	}
+}
+
+// AddConstraint adds a new constraint
+//		constraints:	the constraint definition, one constraint per array element
+func (p *PodPlacement) AddConstraint(constraint Constraint) *PodPlacement {
+	c := *p.Constraints
+	c = append(c, constraint)
+	p.Constraints = &c
+
+	return p
+}
+
+// NewPodSchedulingPolicy creates an empty PodSchedulingPolicy
+func NewPodSchedulingPolicy() *PodSchedulingPolicy {
+	return &PodSchedulingPolicy{
+		Placement: NewPodPlacement(),
+	}
+}
+
+// NewPodBackoff creates an empty PodBackoff
+func NewPodBackoff() *PodBackoff {
+	return &PodBackoff{}
+}
+
+// NewPodUpgrade creates a new PodUpgrade
+func NewPodUpgrade() *PodUpgrade {
+	return &PodUpgrade{}
+}
+
+// SetBackoff sets the base backoff interval for failed pod launches, in seconds
+func (p *PodBackoff) SetBackoff(backoffSeconds float64) *PodBackoff {
+	p.Backoff = &backoffSeconds
+	return p
+}
+
+// SetBackoffFactor sets the backoff interval growth factor for failed pod launches
+func (p *PodBackoff) SetBackoffFactor(backoffFactor float64) *PodBackoff {
+	p.BackoffFactor = &backoffFactor
+	return p
+}
+
+// SetMaxLaunchDelay sets the maximum backoff interval for failed pod launches, in seconds
+func (p *PodBackoff) SetMaxLaunchDelay(maxLaunchDelaySeconds float64) *PodBackoff {
+	p.MaxLaunchDelay = &maxLaunchDelaySeconds
+	return p
+}
+
+// SetMinimumHealthCapacity sets the minimum amount of pod instances for healthy operation, expressed as a fraction of instance count
+func (p *PodUpgrade) SetMinimumHealthCapacity(capacity float64) *PodUpgrade {
+	p.MinimumHealthCapacity = &capacity
+	return p
+}
+
+// SetMaximumOverCapacity sets the maximum amount of pod instances above the instance count, expressed as a fraction of instance count
+func (p *PodUpgrade) SetMaximumOverCapacity(capacity float64) *PodUpgrade {
+	p.MaximumOverCapacity = &capacity
+	return p
+}
+
+// SetBackoff sets the pod's backoff settings
+func (p *PodSchedulingPolicy) SetBackoff(backoff *PodBackoff) *PodSchedulingPolicy {
+	p.Backoff = backoff
+	return p
+}
+
+// SetUpgrade sets the pod's upgrade settings
+func (p *PodSchedulingPolicy) SetUpgrade(upgrade *PodUpgrade) *PodSchedulingPolicy {
+	p.Upgrade = upgrade
+	return p
+}
+
+// SetPlacement sets the pod's placement settings
+func (p *PodSchedulingPolicy) SetPlacement(placement *PodPlacement) *PodSchedulingPolicy {
+	p.Placement = placement
+	return p
+}
+
+// SetKillSelection sets the pod's kill selection criteria when terminating pod instances
+func (p *PodSchedulingPolicy) SetKillSelection(killSelection string) *PodSchedulingPolicy {
+	p.KillSelection = killSelection
+	return p
+}
+
+// SetUnreachableStrategy sets the pod's unreachable strategy for lost instances
+func (p *PodSchedulingPolicy) SetUnreachableStrategy(strategy EnabledUnreachableStrategy) *PodSchedulingPolicy {
+	p.UnreachableStrategy = &UnreachableStrategy{
+		EnabledUnreachableStrategy: strategy,
+	}
+	return p
+}
+
+// SetUnreachableStrategyDisabled disables the pod's unreachable strategy
+func (p *PodSchedulingPolicy) SetUnreachableStrategyDisabled() *PodSchedulingPolicy {
+	p.UnreachableStrategy = &UnreachableStrategy{
+		AbsenceReason: UnreachableStrategyAbsenceReasonDisabled,
+	}
+	return p
+}
diff --git a/pod_status.go b/pod_status.go
new file mode 100644
index 0000000..227868e
--- /dev/null
+++ b/pod_status.go
@@ -0,0 +1,108 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"fmt"
+	"time"
+)
+
+// PodState defines the state of a pod
+type PodState string
+
+const (
+	// PodStateDegraded is a degraded pod
+	PodStateDegraded PodState = "DEGRADED"
+
+	// PodStateStable is a stable pod
+	PodStateStable PodState = "STABLE"
+
+	// PodStateTerminal is a terminal pod
+	PodStateTerminal PodState = "TERMINAL"
+)
+
+// PodStatus describes the pod status
+type PodStatus struct {
+	ID                 string                   `json:"id,omitempty"`
+	Spec               *Pod                     `json:"spec,omitempty"`
+	Status             PodState                 `json:"status,omitempty"`
+	StatusSince        string                   `json:"statusSince,omitempty"`
+	Message            string                   `json:"message,omitempty"`
+	Instances          []*PodInstanceStatus     `json:"instances,omitempty"`
+	TerminationHistory []*PodTerminationHistory `json:"terminationHistory,omitempty"`
+	LastUpdated        string                   `json:"lastUpdated,omitempty"`
+	LastChanged        string                   `json:"lastChanged,omitempty"`
+}
+
+// PodTerminationHistory is the termination history of the pod
+type PodTerminationHistory struct {
+	InstanceID   string                         `json:"instanceId,omitempty"`
+	StartedAt    string                         `json:"startedAt,omitempty"`
+	TerminatedAt string                         `json:"terminatedAt,omitempty"`
+	Message      string                         `json:"message,omitempty"`
+	Containers   []*ContainerTerminationHistory `json:"containers,omitempty"`
+}
+
+// ContainerTerminationHistory is the termination history of a container in a pod
+type ContainerTerminationHistory struct {
+	ContainerID    string                     `json:"containerId,omitempty"`
+	LastKnownState string                     `json:"lastKnownState,omitempty"`
+	Termination    *ContainerTerminationState `json:"termination,omitempty"`
+}
+
+// PodStatus retrieves the pod configuration from marathon
+func (r *marathonClient) PodStatus(name string) (*PodStatus, error) {
+	var podStatus PodStatus
+
+	if err := r.apiGet(buildPodStatusURI(name), nil, &podStatus); err != nil {
+		return nil, err
+	}
+
+	return &podStatus, nil
+}
+
+// PodStatuses retrieves all pod configuration from marathon
+func (r *marathonClient) PodStatuses() ([]*PodStatus, error) {
+	var podStatuses []*PodStatus
+
+	if err := r.apiGet(buildPodStatusURI(""), nil, &podStatuses); err != nil {
+		return nil, err
+	}
+
+	return podStatuses, nil
+}
+
+// WaitOnPod blocks until a pod to be deployed
+func (r *marathonClient) WaitOnPod(name string, timeout time.Duration) error {
+	return r.wait(name, timeout, r.PodIsRunning)
+}
+
+// PodIsRunning returns whether the pod is stably running
+func (r *marathonClient) PodIsRunning(name string) bool {
+	podStatus, err := r.PodStatus(name)
+	if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
+		return false
+	}
+	if err == nil && podStatus.Status == PodStateStable {
+		return true
+	}
+	return false
+}
+
+func buildPodStatusURI(path string) string {
+	return fmt.Sprintf("%s/%s::status", marathonAPIPods, trimRootPath(path))
+}
diff --git a/pod_status_test.go b/pod_status_test.go
new file mode 100644
index 0000000..3689171
--- /dev/null
+++ b/pod_status_test.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestGetPodStatus(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	podStatus, err := endpoint.Client.PodStatus(fakePodName)
+	require.NoError(t, err)
+
+	if assert.NotNil(t, podStatus) {
+		assert.Equal(t, podStatus.Spec.ID, fakePodName)
+	}
+}
+
+func TestGetAllPodStatus(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	podStatuses, err := endpoint.Client.PodStatuses()
+	require.NoError(t, err)
+	assert.Equal(t, podStatuses[0].Spec.ID, fakePodName)
+}
+
+func TestWaitOnPod(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	err := endpoint.Client.WaitOnPod(fakePodName, 1*time.Microsecond)
+	require.NoError(t, err)
+}
+
+func TestPodIsRunning(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	exists := endpoint.Client.PodIsRunning(fakePodName)
+	assert.True(t, exists)
+
+	exists = endpoint.Client.PodIsRunning("not_existing")
+	assert.False(t, exists)
+
+	exists = endpoint.Client.PodIsRunning(secondFakePodName)
+	assert.False(t, exists)
+}
diff --git a/pod_test.go b/pod_test.go
new file mode 100644
index 0000000..0c733c4
--- /dev/null
+++ b/pod_test.go
@@ -0,0 +1,183 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+const key = "testKey"
+const val = "testValue"
+
+const fakePodName = "/fake-pod"
+const secondFakePodName = "/fake-pod2"
+
+func TestPodLabels(t *testing.T) {
+	pod := NewPod()
+	pod.AddLabel(key, val)
+	if assert.Equal(t, len(pod.Labels), 1) {
+		assert.Equal(t, pod.Labels[key], val)
+	}
+
+	pod.EmptyLabels()
+	assert.Equal(t, len(pod.Labels), 0)
+}
+
+func TestPodEnvironmentVars(t *testing.T) {
+	pod := NewPod()
+	pod.AddEnv(key, val)
+
+	newVal, ok := pod.Env[key]
+	assert.Equal(t, newVal, val)
+	assert.Equal(t, ok, true)
+
+	badVal, ok := pod.Env["fakeKey"]
+	assert.Equal(t, badVal, "")
+	assert.Equal(t, ok, false)
+
+	pod.EmptyEnvs()
+	assert.Equal(t, len(pod.Env), 0)
+}
+
+func TestSecrets(t *testing.T) {
+	pod := NewPod()
+	pod.AddSecret("randomVar", key, val)
+
+	newVal, err := pod.GetSecretSource(key)
+	assert.Equal(t, newVal, val)
+	assert.Equal(t, err, nil)
+
+	badVal, err := pod.GetSecretSource("fakeKey")
+	assert.Equal(t, badVal, "")
+	assert.NotNil(t, err)
+
+	pod.EmptySecrets()
+	assert.Equal(t, len(pod.Env), 0)
+}
+
+func TestSupportsPod(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+
+	supports, err := endpoint.Client.SupportsPods()
+	if assert.Nil(t, err) {
+
+		assert.Equal(t, supports, true)
+	}
+
+	// Manually closing to test lack of support
+	endpoint.Close()
+
+	supports, err = endpoint.Client.SupportsPods()
+	assert.NotNil(t, err)
+	assert.Equal(t, supports, false)
+}
+func TestGetPod(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	pod, err := endpoint.Client.Pod(fakePodName)
+	require.NoError(t, err)
+	if assert.NotNil(t, pod) {
+		assert.Equal(t, pod.ID, fakePodName)
+	}
+}
+
+func TestGetAllPods(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	pods, err := endpoint.Client.Pods()
+	require.NoError(t, err)
+	if assert.Equal(t, len(pods), 2) {
+		assert.Equal(t, pods[0].ID, fakePodName)
+		assert.Equal(t, pods[1].ID, secondFakePodName)
+	}
+}
+
+func TestCreatePod(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	pod := NewPod().Name(fakePodName)
+	pod, err := endpoint.Client.CreatePod(pod)
+	require.NoError(t, err)
+	if assert.NotNil(t, pod) {
+		assert.Equal(t, pod.ID, fakePodName)
+	}
+}
+
+func TestUpdatePod(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	pod := NewPod().Name(fakePodName)
+	pod, err := endpoint.Client.CreatePod(pod)
+	require.NoError(t, err)
+
+	pod, err = endpoint.Client.UpdatePod(pod, true)
+	require.NoError(t, err)
+
+	if assert.NotNil(t, pod) {
+		assert.Equal(t, pod.ID, fakePodName)
+		assert.Equal(t, pod.Scaling.Instances, 2)
+	}
+}
+
+func TestDeletePod(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	id, err := endpoint.Client.DeletePod(fakePodName, true)
+	require.NoError(t, err)
+
+	if assert.NotNil(t, id) {
+		assert.Equal(t, id.DeploymentID, "c0e7434c-df47-4d23-99f1-78bd78662231")
+	}
+}
+
+func TestVersions(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	versions, err := endpoint.Client.PodVersions(fakePodName)
+	require.NoError(t, err)
+	assert.Equal(t, versions[0], "2014-08-18T22:36:41.451Z")
+}
+
+func TestGetPodByVersion(t *testing.T) {
+	endpoint := newFakeMarathonEndpoint(t, nil)
+	defer endpoint.Close()
+
+	pod, err := endpoint.Client.PodByVersion(fakePodName, "2014-08-18T22:36:41.451Z")
+	require.NoError(t, err)
+	assert.Equal(t, pod.ID, fakePodName)
+}
+
+func TestAddPodImagePullConfig(t *testing.T) {
+	container := new(PodContainer)
+	container.Image = new(PodContainerImage)
+	pullConfig := NewPullConfig("pullConfig-secret")
+
+	container.Image.SetPullConfig(pullConfig)
+
+	if assert.NotNil(t, container.Image.PullConfig) {
+		assert.Equal(t, "pullConfig-secret", container.Image.PullConfig.Secret)
+	}
+}
diff --git a/port_definition.go b/port_definition.go
index 4cf3c1f..6a5dc6d 100644
--- a/port_definition.go
+++ b/port_definition.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2016 Rohith All rights reserved.
+Copyright 2016 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -27,15 +27,39 @@ type PortDefinition struct {
 }
 
 // SetPort sets the given port for the PortDefinition
-func (p PortDefinition) SetPort(port int) PortDefinition {
+func (p *PortDefinition) SetPort(port int) *PortDefinition {
+	if p.Port == nil {
+		p.EmptyPort()
+	}
+	p.Port = &port
+	return p
+}
+
+// EmptyPort sets the port to 0 for the PortDefinition
+func (p *PortDefinition) EmptyPort() *PortDefinition {
+	port := 0
 	p.Port = &port
 	return p
 }
 
+// SetProtocol sets the protocol for the PortDefinition
+// protocol: the protocol as a string
+func (p *PortDefinition) SetProtocol(protocol string) *PortDefinition {
+	p.Protocol = protocol
+	return p
+}
+
+// SetName sets the name for the PortDefinition
+// name: the name of the PortDefinition
+func (p *PortDefinition) SetName(name string) *PortDefinition {
+	p.Name = name
+	return p
+}
+
 // AddLabel adds a label to the PortDefinition
 //		name: the name of the label
 //		value: value for this label
-func (p PortDefinition) AddLabel(name, value string) PortDefinition {
+func (p *PortDefinition) AddLabel(name, value string) *PortDefinition {
 	if p.Labels == nil {
 		p.EmptyLabels()
 	}
diff --git a/queue.go b/queue.go
index dfd8c7e..b15cf1c 100644
--- a/queue.go
+++ b/queue.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2016 Rohith All rights reserved.
+Copyright 2016 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -27,17 +27,46 @@ type Queue struct {
 
 // Item is the definition of element in the queue
 type Item struct {
-	Count       int         `json:"count"`
-	Delay       Delay       `json:"delay"`
-	Application Application `json:"app"`
+	Count                  int                    `json:"count"`
+	Delay                  Delay                  `json:"delay"`
+	Application            *Application           `json:"app"`
+	Pod                    *Pod                   `json:"pod"`
+	Role                   string                 `json:"role"`
+	Since                  string                 `json:"since"`
+	ProcessedOffersSummary ProcessedOffersSummary `json:"processedOffersSummary"`
+	LastUnusedOffers       []UnusedOffer          `json:"lastUnusedOffers,omitempty"`
 }
 
-// Delay cotains the application postpone infomation
+// Delay cotains the application postpone information
 type Delay struct {
 	Overdue         bool `json:"overdue"`
 	TimeLeftSeconds int  `json:"timeLeftSeconds"`
 }
 
+// ProcessedOffersSummary contains statistics for processed offers.
+type ProcessedOffersSummary struct {
+	ProcessedOffersCount       int32               `json:"processedOffersCount"`
+	UnusedOffersCount          int32               `json:"unusedOffersCount"`
+	LastUnusedOfferAt          *string             `json:"lastUnusedOfferAt,omitempty"`
+	LastUsedOfferAt            *string             `json:"lastUsedOfferAt,omitempty"`
+	RejectSummaryLastOffers    []DeclinedOfferStep `json:"rejectSummaryLastOffers,omitempty"`
+	RejectSummaryLaunchAttempt []DeclinedOfferStep `json:"rejectSummaryLaunchAttempt,omitempty"`
+}
+
+// DeclinedOfferStep contains how often an offer was declined for a specific reason
+type DeclinedOfferStep struct {
+	Reason    string `json:"reason"`
+	Declined  int32  `json:"declined"`
+	Processed int32  `json:"processed"`
+}
+
+// UnusedOffer contains which offers weren't used and why
+type UnusedOffer struct {
+	Offer     Offer    `json:"offer"`
+	Reason    []string `json:"reason"`
+	Timestamp string   `json:"timestamp"`
+}
+
 // Queue retrieves content of the marathon launch queue
 func (r *marathonClient) Queue() (*Queue, error) {
 	var queue *Queue
@@ -51,10 +80,6 @@ func (r *marathonClient) Queue() (*Queue, error) {
 // DeleteQueueDelay resets task launch delay of the specific application
 //		appID:		the ID of the application
 func (r *marathonClient) DeleteQueueDelay(appID string) error {
-	uri := fmt.Sprintf("%s/%s/delay", marathonAPIQueue, trimRootPath(appID))
-	err := r.apiDelete(uri, nil, nil)
-	if err != nil {
-		return err
-	}
-	return nil
+	path := fmt.Sprintf("%s/%s/delay", marathonAPIQueue, trimRootPath(appID))
+	return r.apiDelete(path, nil, nil)
 }
diff --git a/queue_test.go b/queue_test.go
index 3c07b96..de45533 100644
--- a/queue_test.go
+++ b/queue_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2016 Rohith All rights reserved.
+Copyright 2016 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/readiness.go b/readiness.go
new file mode 100644
index 0000000..ffb0aa1
--- /dev/null
+++ b/readiness.go
@@ -0,0 +1,99 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import "time"
+
+// ReadinessCheck represents a readiness check.
+type ReadinessCheck struct {
+	Name                    *string `json:"name,omitempty"`
+	Protocol                string  `json:"protocol,omitempty"`
+	Path                    string  `json:"path,omitempty"`
+	PortName                string  `json:"portName,omitempty"`
+	IntervalSeconds         int     `json:"intervalSeconds,omitempty"`
+	TimeoutSeconds          int     `json:"timeoutSeconds,omitempty"`
+	HTTPStatusCodesForReady *[]int  `json:"httpStatusCodesForReady,omitempty"`
+	PreserveLastResponse    *bool   `json:"preserveLastResponse,omitempty"`
+}
+
+// SetName sets the name on the readiness check.
+func (rc *ReadinessCheck) SetName(name string) *ReadinessCheck {
+	rc.Name = &name
+	return rc
+}
+
+// SetProtocol sets the protocol on the readiness check.
+func (rc *ReadinessCheck) SetProtocol(proto string) *ReadinessCheck {
+	rc.Protocol = proto
+	return rc
+}
+
+// SetPath sets the path on the readiness check.
+func (rc *ReadinessCheck) SetPath(p string) *ReadinessCheck {
+	rc.Path = p
+	return rc
+}
+
+// SetPortName sets the port name on the readiness check.
+func (rc *ReadinessCheck) SetPortName(name string) *ReadinessCheck {
+	rc.PortName = name
+	return rc
+}
+
+// SetInterval sets the interval on the readiness check.
+func (rc *ReadinessCheck) SetInterval(interval time.Duration) *ReadinessCheck {
+	secs := int(interval.Seconds())
+	rc.IntervalSeconds = secs
+	return rc
+}
+
+// SetTimeout sets the timeout on the readiness check.
+func (rc *ReadinessCheck) SetTimeout(timeout time.Duration) *ReadinessCheck {
+	secs := int(timeout.Seconds())
+	rc.TimeoutSeconds = secs
+	return rc
+}
+
+// SetHTTPStatusCodesForReady sets the HTTP status codes for ready on the
+// readiness check.
+func (rc *ReadinessCheck) SetHTTPStatusCodesForReady(codes []int) *ReadinessCheck {
+	rc.HTTPStatusCodesForReady = &codes
+	return rc
+}
+
+// SetPreserveLastResponse sets the preserve last response flag on the
+// readiness check.
+func (rc *ReadinessCheck) SetPreserveLastResponse(preserve bool) *ReadinessCheck {
+	rc.PreserveLastResponse = &preserve
+	return rc
+}
+
+// ReadinessLastResponse holds the result of the last response embedded in a
+// readiness check result.
+type ReadinessLastResponse struct {
+	Body        string `json:"body"`
+	ContentType string `json:"contentType"`
+	Status      int    `json:"status"`
+}
+
+// ReadinessCheckResult is the result of a readiness check.
+type ReadinessCheckResult struct {
+	Name         string                `json:"name"`
+	TaskID       string                `json:"taskId"`
+	Ready        bool                  `json:"ready"`
+	LastResponse ReadinessLastResponse `json:"lastResponse,omitempty"`
+}
diff --git a/readiness_test.go b/readiness_test.go
new file mode 100644
index 0000000..e793d4d
--- /dev/null
+++ b/readiness_test.go
@@ -0,0 +1,51 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestReadinessCheck(t *testing.T) {
+	rc := ReadinessCheck{}
+	rc.SetName("readiness").
+		SetProtocol("HTTP").
+		SetPath("/ready").
+		SetPortName("http").
+		SetInterval(3 * time.Second).
+		SetTimeout(5 * time.Second).
+		SetHTTPStatusCodesForReady([]int{200, 201}).
+		SetPreserveLastResponse(true)
+
+	if assert.NotNil(t, rc.Name) {
+		assert.Equal(t, "readiness", *rc.Name)
+	}
+	assert.Equal(t, rc.Protocol, "HTTP")
+	assert.Equal(t, rc.Path, "/ready")
+	assert.Equal(t, rc.PortName, "http")
+	assert.Equal(t, rc.IntervalSeconds, 3)
+	assert.Equal(t, rc.TimeoutSeconds, 5)
+	if assert.NotNil(t, rc.HTTPStatusCodesForReady) {
+		assert.Equal(t, *rc.HTTPStatusCodesForReady, []int{200, 201})
+	}
+	if assert.NotNil(t, rc.PreserveLastResponse) {
+		assert.True(t, *rc.PreserveLastResponse)
+	}
+}
diff --git a/residency.go b/residency.go
new file mode 100644
index 0000000..9fc94ce
--- /dev/null
+++ b/residency.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import "time"
+
+// TaskLostBehaviorType sets action taken when the resident task is lost
+type TaskLostBehaviorType string
+
+const (
+	// TaskLostBehaviorTypeWaitForever indicates to not take any action when the resident task is lost
+	TaskLostBehaviorTypeWaitForever TaskLostBehaviorType = "WAIT_FOREVER"
+	// TaskLostBehaviorTypeRelaunchAfterTimeout indicates to try relaunching the lost resident task on
+	// another node after the relaunch escalation timeout has elapsed
+	TaskLostBehaviorTypeRelaunchAfterTimeout TaskLostBehaviorType = "RELAUNCH_AFTER_TIMEOUT"
+)
+
+// Residency defines how terminal states of tasks with local persistent volumes are handled
+type Residency struct {
+	TaskLostBehavior                 TaskLostBehaviorType `json:"taskLostBehavior,omitempty"`
+	RelaunchEscalationTimeoutSeconds int                  `json:"relaunchEscalationTimeoutSeconds,omitempty"`
+}
+
+// SetTaskLostBehavior sets the residency behavior
+func (r *Residency) SetTaskLostBehavior(behavior TaskLostBehaviorType) *Residency {
+	r.TaskLostBehavior = behavior
+	return r
+}
+
+// SetRelaunchEscalationTimeout sets the residency relaunch escalation timeout with seconds precision
+func (r *Residency) SetRelaunchEscalationTimeout(timeout time.Duration) *Residency {
+	r.RelaunchEscalationTimeoutSeconds = int(timeout.Seconds())
+	return r
+}
diff --git a/residency_test.go b/residency_test.go
new file mode 100644
index 0000000..09231ca
--- /dev/null
+++ b/residency_test.go
@@ -0,0 +1,49 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestResidency(t *testing.T) {
+	app := NewDockerApplication()
+
+	app = app.SetResidency(TaskLostBehaviorTypeWaitForever)
+
+	if assert.NotNil(t, app.Residency) {
+		res := app.Residency
+
+		assert.Equal(t, res.TaskLostBehavior, TaskLostBehaviorTypeWaitForever)
+
+		res.SetRelaunchEscalationTimeout(2525 * time.Millisecond)
+		// should be trimmed to seconds precision
+		assert.Equal(t, app.Residency.RelaunchEscalationTimeoutSeconds, 2)
+
+		res.SetTaskLostBehavior(TaskLostBehaviorTypeRelaunchAfterTimeout)
+		assert.Equal(t, res.TaskLostBehavior, TaskLostBehaviorTypeRelaunchAfterTimeout)
+	}
+
+	app = app.EmptyResidency()
+
+	if assert.NotNil(t, app.Residency) {
+		assert.Equal(t, app.Residency, &Residency{})
+	}
+}
diff --git a/resources.go b/resources.go
new file mode 100644
index 0000000..a2aaeed
--- /dev/null
+++ b/resources.go
@@ -0,0 +1,37 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+// ExecutorResources are the resources supported by an executor (a task running a pod)
+type ExecutorResources struct {
+	Cpus float64 `json:"cpus,omitempty"`
+	Mem  float64 `json:"mem,omitempty"`
+	Disk float64 `json:"disk,omitempty"`
+}
+
+// Resources are the full set of resources for a task
+type Resources struct {
+	Cpus float64 `json:"cpus"`
+	Mem  float64 `json:"mem"`
+	Disk float64 `json:"disk,omitempty"`
+	Gpus int32   `json:"gpus,omitempty"`
+}
+
+// NewResources creates an empty Resources
+func NewResources() *Resources {
+	return &Resources{}
+}
diff --git a/subscription.go b/subscription.go
index c84b769..1c20855 100644
--- a/subscription.go
+++ b/subscription.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -162,40 +162,33 @@ func (r *marathonClient) registerCallbackSubscription() error {
 	return nil
 }
 
+// registerSSESubscription starts a go routine that continuously tries to
+// connect to the SSE stream and to process the received events. To establish
+// the connection it tries the active cluster members until no more member is
+// active. When this happens it will retry to get a connection every 5 seconds.
 func (r *marathonClient) registerSSESubscription() error {
-	// Prevent multiple SSE subscriptions
 	if r.subscribedToSSE {
 		return nil
 	}
-	// Get a member from the cluster
-	marathon, err := r.hosts.getMember()
-	if err != nil {
-		return err
-	}
 
-	request, err := r.buildAPIRequest("GET", fmt.Sprintf("%s/%s", marathon, marathonAPIEventStream), nil)
-	if err != nil {
-		return err
-	}
-
-	// Try to connect to stream, reusing the http client settings
-	stream, err := eventsource.SubscribeWith("", r.httpClient, request)
-	if err != nil {
-		return err
+	if r.config.HTTPSSEClient.Timeout != 0 {
+		return fmt.Errorf(
+			"global timeout must not be set for SSE connections (found %s) -- remove global timeout from HTTP client or provide separate SSE HTTP client without global timeout",
+			r.config.HTTPSSEClient.Timeout,
+		)
 	}
 
 	go func() {
 		for {
-			select {
-			case ev := <-stream.Events:
-				if err := r.handleEvent(ev.Data()); err != nil {
-					// TODO let the user handle this error instead of logging it here
-					r.debugLog.Printf("registerSSESubscription(): failed to handle event: %v\n", err)
-				}
-			case err := <-stream.Errors:
-				// TODO let the user handle this error instead of logging it here
-				r.debugLog.Printf("registerSSESubscription(): failed to receive event: %v\n", err)
+			stream, err := r.connectToSSE()
+			if err != nil {
+				r.debugLog("Error connecting SSE subscription: %s", err)
+				<-time.After(5 * time.Second)
+				continue
 			}
+			err = r.listenToSSE(stream)
+			stream.Close()
+			r.debugLog("Error on SSE subscription: %s", err)
 		}
 	}()
 
@@ -203,11 +196,61 @@ func (r *marathonClient) registerSSESubscription() error {
 	return nil
 }
 
+// connectToSSE tries to establish an *eventsource.Stream to any of the Marathon cluster members, marking the
+// member as down on connection failure, until there is no more active member in the cluster.
+// Given the http request can not be built, it will panic as this case should never happen.
+func (r *marathonClient) connectToSSE() (*eventsource.Stream, error) {
+	for {
+		request, member, err := r.buildAPIRequest("GET", marathonAPIEventStream, nil)
+		if err != nil {
+			switch err.(type) {
+			case newRequestError:
+				panic(fmt.Sprintf("Requests for SSE subscriptions should never fail to be created: %s", err.Error()))
+			default:
+				return nil, err
+			}
+		}
+
+		// The event source library manipulates the HTTPClient. So we create a new one and copy
+		// its underlying fields for performance reasons. See note that at least the Transport
+		// should be reused here: https://golang.org/pkg/net/http/#Client
+		httpClient := &http.Client{
+			Transport:     r.config.HTTPSSEClient.Transport,
+			CheckRedirect: r.config.HTTPSSEClient.CheckRedirect,
+			Jar:           r.config.HTTPSSEClient.Jar,
+			Timeout:       r.config.HTTPSSEClient.Timeout,
+		}
+
+		stream, err := eventsource.SubscribeWith("", httpClient, request)
+		if err != nil {
+			r.debugLog("Error subscribing to Marathon event stream: %s", err)
+			r.hosts.markDown(member)
+			continue
+		}
+
+		return stream, nil
+	}
+}
+
+func (r *marathonClient) listenToSSE(stream *eventsource.Stream) error {
+	for {
+		select {
+		case ev := <-stream.Events:
+			if err := r.handleEvent(ev.Data()); err != nil {
+				r.debugLog("listenToSSE(): failed to handle event: %v", err)
+			}
+		case err := <-stream.Errors:
+			return err
+
+		}
+	}
+}
+
 // Subscribe adds a URL to Marathon's callback facility
 //	callback	: the URL you wish to subscribe
 func (r *marathonClient) Subscribe(callback string) error {
-	uri := fmt.Sprintf("%s?callbackUrl=%s", marathonAPISubscription, callback)
-	return r.apiPost(uri, "", nil)
+	path := fmt.Sprintf("%s?callbackUrl=%s", marathonAPISubscription, callback)
+	return r.ApiPost(path, "", nil)
 
 }
 
@@ -282,12 +325,12 @@ func (r *marathonClient) handleCallbackEvent(writer http.ResponseWriter, request
 	body, err := ioutil.ReadAll(request.Body)
 	if err != nil {
 		// TODO should this return a 500?
-		r.debugLog.Printf("handleCallbackEvent(): failed to read request body, error: %s\n", err)
+		r.debugLog("handleCallbackEvent(): failed to read request body, error: %s", err)
 		return
 	}
 
 	if err := r.handleEvent(string(body[:])); err != nil {
 		// TODO should this return a 500?
-		r.debugLog.Printf("handleCallbackEvent(): failed to handle event: %v\n", err)
+		r.debugLog("handleCallbackEvent(): failed to handle event: %v", err)
 	}
 }
diff --git a/subscription_test.go b/subscription_test.go
index 51b34d8..abc4c08 100644
--- a/subscription_test.go
+++ b/subscription_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -17,14 +17,19 @@ limitations under the License.
 package marathon
 
 import (
+	"net"
 	"net/http"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
-const eventPublishTimeout time.Duration = 250 * time.Millisecond
+const (
+	eventPublishTimeout time.Duration = 250 * time.Millisecond
+	SSEConnectWaitTime  time.Duration = 250 * time.Millisecond
+)
 
 type testCaseList []testCase
 
@@ -293,18 +298,16 @@ func TestUnsubscribe(t *testing.T) {
 	assert.NoError(t, err)
 }
 
-func TestEventStreamConnectionErrorsForwarded(t *testing.T) {
+func TestSSEWithGlobalTimeout(t *testing.T) {
 	clientCfg := NewDefaultConfig()
-	config := &configContainer{
+	clientCfg.HTTPSSEClient = &http.Client{
+		Timeout: 1 * time.Second,
+	}
+	config := configContainer{
 		client: &clientCfg,
 	}
 	config.client.EventsTransport = EventsTransportSSE
-	config.client.URL = "http://non-existing-marathon-host.local:5555"
-	// Reduce timeout to speed up test execution time.
-	config.client.HTTPClient = &http.Client{
-		Timeout: 100 * time.Millisecond,
-	}
-	endpoint := newFakeMarathonEndpoint(t, config)
+	endpoint := newFakeMarathonEndpoint(t, &config)
 	defer endpoint.Close()
 
 	_, err := endpoint.Client.AddEventsListener(EventIDApplications)
@@ -312,9 +315,7 @@ func TestEventStreamConnectionErrorsForwarded(t *testing.T) {
 }
 
 func TestEventStreamEventsReceived(t *testing.T) {
-	if !assert.True(t, len(testCases) > 1, "must have at least 2 test cases to end prematurely") {
-		return
-	}
+	require.True(t, len(testCases) > 1, "must have at least 2 test cases to end prematurely")
 
 	clientCfg := NewDefaultConfig()
 	config := configContainer{
@@ -330,6 +331,9 @@ func TestEventStreamEventsReceived(t *testing.T) {
 	almostAllTestCases := testCases[:len(testCases)-1]
 	finalTestCase := testCases[len(testCases)-1]
 
+	// Give it a bit of time so that the subscription can be set up
+	time.Sleep(SSEConnectWaitTime)
+
 	// Publish all but one test event.
 	for _, testCase := range almostAllTestCases {
 		endpoint.Server.PublishEvent(testCase.source)
@@ -369,3 +373,87 @@ func TestEventStreamEventsReceived(t *testing.T) {
 		assert.Fail(t, "channel was not closed")
 	}
 }
+
+func TestConnectToSSESuccess(t *testing.T) {
+	clientCfg := NewDefaultConfig()
+	// Use non-existent address as first cluster member
+	clientCfg.URL = "http://127.0.0.1:11111"
+	clientCfg.EventsTransport = EventsTransportSSE
+	config := configContainer{client: &clientCfg}
+
+	endpoint := newFakeMarathonEndpoint(t, &config)
+	defer endpoint.Close()
+
+	client := endpoint.Client.(*marathonClient)
+	// Add real server as member to the cluster
+	client.hosts.members = append(client.hosts.members, &member{endpoint: endpoint.Server.httpSrv.URL})
+
+	// Connection should work as one of the Marathon members is up
+	stream, err := client.connectToSSE()
+	if assert.NoError(t, err, "expected no error in connectToSSE") {
+		stream.Close()
+	}
+}
+
+func TestConnectToSSEFailure(t *testing.T) {
+	clientCfg := NewDefaultConfig()
+	clientCfg.EventsTransport = EventsTransportSSE
+	config := configContainer{client: &clientCfg}
+
+	endpoint := newFakeMarathonEndpoint(t, &config)
+	endpoint.Close()
+
+	client := endpoint.Client.(*marathonClient)
+
+	// No Marathon member is up, we should get an error
+	stream, err := client.connectToSSE()
+	if !assert.Error(t, err, "expected error in connectToSSE when all cluster members are down") {
+		stream.Close()
+	}
+}
+
+func TestRegisterSEESubscriptionReconnectsStreamOnError(t *testing.T) {
+	clientCfg := NewDefaultConfig()
+	clientCfg.HTTPSSEClient = &http.Client{
+		Transport: &http.Transport{
+			Dial: (&net.Dialer{
+				// set timeout to a small fraction of SSEConnectWaitTime to give client enough time
+				// to detect error and reconnect during sleep
+				Timeout: SSEConnectWaitTime / 10,
+			}).Dial,
+		},
+	}
+	clientCfg.EventsTransport = EventsTransportSSE
+	config := configContainer{client: &clientCfg}
+
+	endpoint1 := newFakeMarathonEndpoint(t, &config)
+	endpoint2 := newFakeMarathonEndpoint(t, &config)
+	defer endpoint2.Close()
+
+	client1 := endpoint1.Client.(*marathonClient)
+	// Add the second server to the cluster members
+	client1.hosts.members = append(client1.hosts.members, &member{endpoint: endpoint2.Server.httpSrv.URL})
+
+	events, err := endpoint1.Client.AddEventsListener(EventIDApplications)
+	require.NoError(t, err)
+
+	// Give it a bit of time so that the subscription can be set up
+	time.Sleep(SSEConnectWaitTime)
+
+	// This should make the SSE subscription fail and reconnect to another cluster member
+	endpoint1.Close()
+
+	// Give it a bit of time so that the subscription can reconnect
+	time.Sleep(SSEConnectWaitTime)
+
+	// Now that our SSE subscription failed over, we can publish on the second server and the message should be consumed
+	endpoint2.Server.PublishEvent(testCases[0].source)
+
+	select {
+	case event := <-events:
+		tc := testCases.find(event.Name)
+		assert.NotNil(t, tc, "received unknown event: %s", event.Name)
+	case <-time.After(eventPublishTimeout):
+		assert.Fail(t, "did not receive event in time")
+	}
+}
diff --git a/task.go b/task.go
index 336ed37..4b5a320 100644
--- a/task.go
+++ b/task.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -69,6 +69,7 @@ type KillApplicationTasksOpts struct {
 type KillTaskOpts struct {
 	Scale bool `url:"scale,omitempty"`
 	Force bool `url:"force,omitempty"`
+	Wipe  bool `url:"wipe,omitempty"`
 }
 
 // HasHealthCheckResults checks if the task has any health checks
@@ -79,13 +80,13 @@ func (r *Task) HasHealthCheckResults() bool {
 // AllTasks lists tasks of all applications.
 //		opts: 		AllTasksOpts request payload
 func (r *marathonClient) AllTasks(opts *AllTasksOpts) (*Tasks, error) {
-	u, err := addOptions(marathonAPITasks, opts)
+	path, err := addOptions(marathonAPITasks, opts)
 	if err != nil {
 		return nil, err
 	}
 
 	tasks := new(Tasks)
-	if err := r.apiGet(u, nil, tasks); err != nil {
+	if err := r.apiGet(path, nil, tasks); err != nil {
 		return nil, err
 	}
 
@@ -107,14 +108,14 @@ func (r *marathonClient) Tasks(id string) (*Tasks, error) {
 //		id:		the id of the application
 //		opts: 		KillApplicationTasksOpts request payload
 func (r *marathonClient) KillApplicationTasks(id string, opts *KillApplicationTasksOpts) (*Tasks, error) {
-	u := fmt.Sprintf("%s/%s/tasks", marathonAPIApps, trimRootPath(id))
-	u, err := addOptions(u, opts)
+	path := fmt.Sprintf("%s/%s/tasks", marathonAPIApps, trimRootPath(id))
+	path, err := addOptions(path, opts)
 	if err != nil {
 		return nil, err
 	}
 
 	tasks := new(Tasks)
-	if err := r.apiDelete(u, nil, tasks); err != nil {
+	if err := r.apiDelete(path, nil, tasks); err != nil {
 		return nil, err
 	}
 
@@ -129,8 +130,8 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err
 	appName = strings.Replace(appName, "_", "/", -1)
 	taskID = strings.Replace(taskID, "/", "_", -1)
 
-	u := fmt.Sprintf("%s/%s/tasks/%s", marathonAPIApps, appName, taskID)
-	u, err := addOptions(u, opts)
+	path := fmt.Sprintf("%s/%s/tasks/%s", marathonAPIApps, appName, taskID)
+	path, err := addOptions(path, opts)
 	if err != nil {
 		return nil, err
 	}
@@ -139,7 +140,7 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err
 		Task Task `json:"task"`
 	})
 
-	if err := r.apiDelete(u, nil, wrappedTask); err != nil {
+	if err := r.apiDelete(path, nil, wrappedTask); err != nil {
 		return nil, err
 	}
 
@@ -150,8 +151,8 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err
 //	tasks:		the array of task ids
 //	opts:		KillTaskOpts request payload
 func (r *marathonClient) KillTasks(tasks []string, opts *KillTaskOpts) error {
-	u := fmt.Sprintf("%s/delete", marathonAPITasks)
-	u, err := addOptions(u, opts)
+	path := fmt.Sprintf("%s/delete", marathonAPITasks)
+	path, err := addOptions(path, opts)
 	if err != nil {
 		return nil
 	}
@@ -161,7 +162,7 @@ func (r *marathonClient) KillTasks(tasks []string, opts *KillTaskOpts) error {
 	}
 	post.IDs = tasks
 
-	return r.apiPost(u, &post, nil)
+	return r.ApiPost(path, &post, nil)
 }
 
 // TaskEndpoints gets the endpoints i.e. HOST_IP:DYNAMIC_PORT for a specific application service
@@ -186,7 +187,10 @@ func (r *marathonClient) TaskEndpoints(name string, port int, healthCheck bool)
 	// step: we need to get the port index of the service we are interested in
 	portIndex, err := application.Container.Docker.ServicePortIndex(port)
 	if err != nil {
-		return nil, err
+		portIndex, err = application.Container.ServicePortIndex(port)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	// step: do we have any tasks?
@@ -217,7 +221,7 @@ func (r *Task) allHealthChecksAlive() bool {
 	}
 	// step: check the health results then
 	for _, check := range r.HealthCheckResults {
-		if check.Alive == false {
+		if !check.Alive {
 			return false
 		}
 	}
diff --git a/task_test.go b/task_test.go
index 86e4d29..630d4e8 100644
--- a/task_test.go
+++ b/task_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/testing_utils_test.go b/testing_utils_test.go
index 852c8b4..40338f0 100644
--- a/testing_utils_test.go
+++ b/testing_utils_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -49,8 +49,9 @@ var (
 )
 
 type indexedResponse struct {
-	Index   int    `yaml:"index,omitempty"`
-	Content string `yaml:"content,omitempty"`
+	Index   int               `yaml:"index,omitempty"`
+	Content string            `yaml:"content,omitempty"`
+	Headers map[string]string `yaml:"headers,omitempty"`
 }
 
 type responseIndices struct {
@@ -74,6 +75,8 @@ type restMethod struct {
 	ContentSequence []indexedResponse `yaml:"contentSequence,omitempty"`
 	// the test scope
 	Scope string `yaml:"scope,omitempty"`
+	// headers in the response
+	Headers map[string]string `yaml:"headers,omitempty"`
 }
 
 // serverConfig holds the Marathon server configuration
@@ -159,6 +162,10 @@ func newFakeMarathonEndpoint(t *testing.T, configs *configContainer) *endpoint {
 				// Index < 0 indicates a static response.
 				if response.Index < 0 || response.Index == fakeRespIndex {
 					writer.Header().Add("Content-Type", "application/json")
+					for k, v := range response.Headers {
+						writer.Header().Add(k, v)
+					}
+
 					writer.Write([]byte(response.Content))
 					return
 				}
@@ -258,18 +265,17 @@ func authMiddleware(server *serverConfig, next http.HandlerFunc) func(http.Respo
 // initFakeMarathonResponses reads in the marathon fake responses from the yaml file
 func initFakeMarathonResponses(t *testing.T) {
 	once.Do(func() {
-		fakeResponses = make(map[string][]indexedResponse, 0)
+		fakeResponses = make(map[string][]indexedResponse)
 		var methods []*restMethod
 
 		// step: read in the test method specification
 		methodSpec, err := ioutil.ReadFile("./tests/rest-api/methods.yml")
 		if err != nil {
-			t.Fatalf("failed to read in the fake yaml responses")
+			t.Fatalf("failed to read in the fake yaml responses: %s", err)
 		}
 
-		err = yaml.Unmarshal([]byte(methodSpec), &methods)
-		if err != nil {
-			t.Fatalf("failed to unmarshal the response")
+		if err = yaml.Unmarshal([]byte(methodSpec), &methods); err != nil {
+			t.Fatalf("failed to unmarshal the response: %s", err)
 		}
 		for _, method := range methods {
 			key := fakeResponseMapKey(method.Method, method.URI, method.Scope)
@@ -287,6 +293,7 @@ func initFakeMarathonResponses(t *testing.T) {
 						// Index -1 indicates a static response.
 						Index:   -1,
 						Content: method.Content,
+						Headers: method.Headers,
 					},
 				}
 			}
@@ -314,12 +321,11 @@ func (s *fakeServer) PublishEvent(event string) {
 	s.eventSrv.Publish([]string{"event"}, fakeEvent{event})
 }
 
-func (s *fakeServer) Close() error {
+func (s *fakeServer) Close() {
 	s.eventSrv.Close()
 	s.httpSrv.Close()
-	return nil
 }
 
-func (e *endpoint) Close() error {
-	return e.Server.Close()
+func (e *endpoint) Close() {
+	e.Server.Close()
 }
diff --git a/tests/app-definitions/TestApplicationString-1.5-output.json b/tests/app-definitions/TestApplicationString-1.5-output.json
new file mode 100644
index 0000000..1667f19
--- /dev/null
+++ b/tests/app-definitions/TestApplicationString-1.5-output.json
@@ -0,0 +1,52 @@
+{
+  "id": "/my-app",
+  "args": [
+    "/usr/sbin/apache2ctl",
+    "-D",
+    "FOREGROUND"
+  ],
+  "container": {
+    "type": "DOCKER",
+    "docker": {
+      "image": "quay.io/gambol99/apache-php:latest"
+    },
+    "portMappings": [
+      {
+        "containerPort": 80,
+        "hostPort": 0,
+        "protocol": "tcp"
+      },
+      {
+        "containerPort": 443,
+        "hostPort": 0,
+        "protocol": "tcp"
+      }
+    ]
+  },
+  "cpus": 0.1,
+  "disk": 0,
+  "networks": [
+    {
+      "mode": "container/bridge"
+    }
+  ],
+  "healthChecks": [
+    {
+      "portIndex": 0,
+      "path": "/health",
+      "maxConsecutiveFailures": 3,
+      "protocol": "HTTP",
+      "gracePeriodSeconds": 30,
+      "intervalSeconds": 5,
+      "timeoutSeconds": 5
+    }
+  ],
+  "instances": 2,
+  "mem": 64,
+  "ports": null,
+  "dependencies": null,
+  "env": {
+    "NAME": "frontend_http",
+    "SERVICE_80_NAME": "test_http"
+  }
+}
diff --git a/tests/app-definitions/TestApplicationString-output.json b/tests/app-definitions/TestApplicationString-output.json
new file mode 100644
index 0000000..0725dc6
--- /dev/null
+++ b/tests/app-definitions/TestApplicationString-output.json
@@ -0,0 +1,48 @@
+{
+  "id": "/my-app",
+  "args": [
+    "/usr/sbin/apache2ctl",
+    "-D",
+    "FOREGROUND"
+  ],
+  "container": {
+    "type": "DOCKER",
+    "docker": {
+      "image": "quay.io/gambol99/apache-php:latest",
+      "network": "BRIDGE",
+      "portMappings": [
+        {
+          "containerPort": 80,
+          "hostPort": 0,
+          "protocol": "tcp"
+        },
+        {
+          "containerPort": 443,
+          "hostPort": 0,
+          "protocol": "tcp"
+        }
+      ]
+    }
+  },
+  "cpus": 0.1,
+  "disk": 0,
+  "healthChecks": [
+    {
+      "portIndex": 0,
+      "path": "/health",
+      "maxConsecutiveFailures": 3,
+      "protocol": "HTTP",
+      "gracePeriodSeconds": 30,
+      "intervalSeconds": 5,
+      "timeoutSeconds": 5
+    }
+  ],
+  "instances": 2,
+  "mem": 64,
+  "ports": null,
+  "dependencies": null,
+  "env": {
+    "NAME": "frontend_http",
+    "SERVICE_80_NAME": "test_http"
+  }
+}
diff --git a/tests/rest-api/methods.yml b/tests/rest-api/methods.yml
index 0ec842e..523e11c 100644
--- a/tests/rest-api/methods.yml
+++ b/tests/rest-api/methods.yml
@@ -57,16 +57,16 @@
       "id": "/fake-app",
       "instances": 2,
       "mem": 50.0,
-      "ports": [
-          0
-      ],
       "requirePorts": false,
+      "residency" : {
+          "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT",
+          "relaunchEscalationTimeoutSeconds" : 60
+      },
       "storeUrls": [],
       "upgradeStrategy": {
           "minimumHealthCapacity": 0.5,
           "maximumOverCapacity": 0.5
       },
-      "uris": [],
       "user": null,
       "version": "2014-08-18T22:36:41.451Z"
     }
@@ -83,22 +83,21 @@
             "constraints": [],
             "container": {
                 "docker": {
-                    "image": "python:3",
-                    "network": "BRIDGE",
-                    "portMappings": [
-                        {
-                            "containerPort": 8080,
-                            "hostPort": 0,
-                            "servicePort": 9000,
-                            "protocol": "tcp"
-                        },
-                        {
-                            "containerPort": 161,
-                            "hostPort": 0,
-                            "protocol": "udp"
-                        }
-                    ]
+                    "image": "python:3"
                 },
+                "portMappings": [
+                    {
+                        "containerPort": 8080,
+                        "hostPort": 0,
+                        "servicePort": 9000,
+                        "protocol": "tcp"
+                    },
+                    {
+                        "containerPort": 161,
+                        "hostPort": 0,
+                        "protocol": "udp"
+                    }
+                ],
                 "type": "DOCKER",
                 "volumes": []
             },
@@ -106,7 +105,17 @@
             "dependencies": [],
             "deployments": [],
             "disk": 0.0,
-            "env": {},
+            "networks": [
+                {
+                    "mode": "container/bridge"
+                }
+            ],
+            "env": {
+                "VAR": "VALUE",
+                "SECRET1": {
+                    "secret": "secret0"
+                }
+            },
             "executor": "",
             "healthChecks": [
                 {
@@ -123,18 +132,18 @@
             "id": "/fake-app",
             "instances": 2,
             "mem": 64.0,
-            "ports": [
-                10000,
-                10001
-            ],
             "requirePorts": false,
+            "secrets": {
+                "secret0": {
+                  "source": "secret/definition/id"
+                }
+            },
             "storeUrls": [],
             "tasksRunning": 2,
             "tasksStaged": 0,
             "upgradeStrategy": {
                 "minimumHealthCapacity": 1.0
             },
-            "uris": [],
             "user": null,
             "version": "2014-09-25T02:26:59.256Z"
         },
@@ -146,22 +155,21 @@
             "constraints": [],
             "container": {
                 "docker": {
-                    "image": "python:3",
-                    "network": "BRIDGE",
-                    "portMappings": [
-                        {
-                            "containerPort": 8080,
-                            "hostPort": 0,
-                            "servicePort": 9000,
-                            "protocol": "tcp"
-                        },
-                        {
-                            "containerPort": 161,
-                            "hostPort": 0,
-                            "protocol": "udp"
-                        }
-                    ]
+                    "image": "python:3"
                 },
+                "portMappings": [
+                    {
+                        "containerPort": 8080,
+                        "hostPort": 0,
+                        "servicePort": 9000,
+                        "protocol": "tcp"
+                    },
+                    {
+                        "containerPort": 161,
+                        "hostPort": 0,
+                        "protocol": "udp"
+                    }
+                ],
                 "type": "DOCKER",
                 "volumes": []
             },
@@ -169,6 +177,11 @@
             "dependencies": [],
             "deployments": [],
             "disk": 0.0,
+            "networks": [
+                {
+                    "mode": "container/bridge"
+                }
+            ],
             "env": {},
             "executor": "",
             "healthChecks": [
@@ -186,18 +199,17 @@
             "id": "/fake-app-broken",
             "instances": 2,
             "mem": 64.0,
-            "ports": [
-                10000,
-                10001
-            ],
             "requirePorts": false,
+            "residency" : {
+                "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT",
+                "relaunchEscalationTimeoutSeconds" : 60
+            },
             "storeUrls": [],
             "tasksRunning": 2,
             "tasksStaged": 0,
             "upgradeStrategy": {
                 "minimumHealthCapacity": 1.0
             },
-            "uris": [],
             "user": null,
             "version": "2014-09-25T02:26:59.256Z"
         }
@@ -295,17 +307,16 @@
         "constraints": [],
         "container": {
             "docker": {
-                "image": "python:3",
-                "network": "BRIDGE",
-                "portMappings": [
-                    {
-                        "containerPort": 8080,
-                        "hostPort": 0,
-                        "servicePort": 9000,
-                        "protocol": "tcp"
-                    }
-                ]
+                "image": "python:3"
             },
+            "portMappings": [
+                {
+                    "containerPort": 8080,
+                    "hostPort": 0,
+                    "servicePort": 9000,
+                    "protocol": "tcp"
+                }
+            ],
             "type": "DOCKER",
             "volumes": []
         },
@@ -313,6 +324,11 @@
         "dependencies": [],
         "deployments": [],
         "disk": 0.0,
+        "networks": [
+            {
+                "mode": "container/bridge"
+            }
+        ],
         "env": {},
         "executor": "",
         "healthChecks": [
@@ -333,16 +349,18 @@
             "appId": "/toggle",
             "host": "10.141.141.10",
             "message": "Abnormal executor termination",
+            "slaveId": "14ac45bf-9a40-42cf-94ec-695130865592-S0",
             "state": "TASK_FAILED",
             "taskId": "toggle.cc427e60-5046-11e4-9e34-56847afe9799",
             "timestamp": "2014-09-12T23:23:41.711Z",
             "version": "2014-09-12T23:28:21.737Z"
         },
         "mem": 32.0,
-        "ports": [
-            10000
-        ],
         "requirePorts": false,
+        "residency" : {
+            "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT",
+            "relaunchEscalationTimeoutSeconds" : 60
+        },
         "storeUrls": [],
         "tasks": [
             {
@@ -393,9 +411,6 @@
         "upgradeStrategy": {
             "minimumHealthCapacity": 1.0
         },
-        "uris": [
-            "http://downloads.mesosphere.com/misc/toggle.tgz"
-        ],
         "user": null,
         "version": "2014-09-12T23:28:21.737Z"
     }
@@ -411,17 +426,16 @@
         "constraints": [],
         "container": {
             "docker": {
-                "image": "python:3",
-                "network": "BRIDGE",
-                "portMappings": [
-                    {
-                        "containerPort": 8080,
-                        "hostPort": 0,
-                        "servicePort": 9000,
-                        "protocol": "tcp"
-                    }
-                ]
+                "image": "python:3"
             },
+             "portMappings": [
+                {
+                    "containerPort": 8080,
+                    "hostPort": 0,
+                    "servicePort": 9000,
+                    "protocol": "tcp"
+                }
+            ],
             "type": "DOCKER",
             "volumes": []
         },
@@ -429,6 +443,11 @@
         "dependencies": [],
         "deployments": [],
         "disk": 0.0,
+        "networks": [
+            {
+                "mode": "container/bridge"
+            }
+        ],
         "env": {},
         "executor": "",
         "healthChecks": [
@@ -449,16 +468,18 @@
             "appId": "/toggle",
             "host": "10.141.141.10",
             "message": "Abnormal executor termination",
+            "slaveId": "14ac45bf-9a40-42cf-94ec-695130865592-S0",
             "state": "TASK_FAILED",
             "taskId": "toggle.cc427e60-5046-11e4-9e34-56847afe9799",
             "timestamp": "2014-09-12T23:23:41.711Z",
             "version": "2014-09-12T23:28:21.737Z"
         },
         "mem": 32.0,
-        "ports": [
-            10000
-        ],
         "requirePorts": false,
+        "residency" : {
+            "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT",
+            "relaunchEscalationTimeoutSeconds" : 60
+        },
         "storeUrls": [],
         "tasks": [
             {
@@ -509,9 +530,6 @@
         "upgradeStrategy": {
             "minimumHealthCapacity": 1.0
         },
-        "uris": [
-            "http://downloads.mesosphere.com/misc/toggle.tgz"
-        ],
         "user": null,
         "version": "2014-09-12T23:28:21.737Z"
     }
@@ -540,17 +558,16 @@
         "constraints": [],
         "container": {
             "docker": {
-                "image": "python:3",
-                "network": "BRIDGE",
-                "portMappings": [
-                    {
-                        "containerPort": 8080,
-                        "hostPort": 0,
-                        "servicePort": 9000,
-                        "protocol": "tcp"
-                    }
-                ]
+                "image": "python:3"
             },
+            "portMappings": [
+                {
+                    "containerPort": 8080,
+                    "hostPort": 0,
+                    "servicePort": 9000,
+                    "protocol": "tcp"
+                }
+            ],
             "type": "DOCKER",
             "volumes": []
         },
@@ -558,6 +575,11 @@
         "dependencies": [],
         "deployments": [],
         "disk": 0.0,
+        "networks": [
+            {
+                "mode": "container/bridge"
+            }
+        ],
         "env": {},
         "executor": "",
         "healthChecks": [
@@ -584,10 +606,11 @@
             "version": "2014-09-12T23:28:21.737Z"
         },
         "mem": 32.0,
-        "ports": [
-            10000
-        ],
         "requirePorts": false,
+        "residency" : {
+            "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT",
+            "relaunchEscalationTimeoutSeconds" : 60
+        },
         "storeUrls": [],
         "tasks": [
             {
@@ -638,9 +661,6 @@
         "upgradeStrategy": {
             "minimumHealthCapacity": 1.0
         },
-        "uris": [
-            "http://downloads.mesosphere.com/misc/toggle.tgz"
-        ],
         "user": null,
         "version": "2014-09-12T23:28:21.737Z"
     }
@@ -667,6 +687,586 @@
       "deploymentId": "83b215a6-4e26-4e44-9333-5c385eda6438",
       "version": "2014-08-26T07:37:50.462Z"
     }
+- uri: /v2/pods
+  method: HEAD
+- uri: /v2/pods/fake-pod::status
+  method: GET
+  content: |
+    {
+      "id": "/fake-pod",
+      "spec": {
+        "id": "/fake-pod",
+        "labels": {
+          "key": "value"
+        },
+        "version": "2014-08-18T22:36:41.451Z",
+        "user": "nobody",
+        "environment": {
+          "key": {
+            "secret": "secret0"
+          }
+        },
+        "containers": [],
+        "secrets": {
+          "secret0": {
+            "source": "source0"
+          }
+        },
+        "volumes": [],
+        "networks": [],
+        "scaling": {
+          "kind": "fixed",
+          "instances": 1
+        },
+        "scheduling": {}
+      },
+      "status": "STABLE",
+      "statusSince": "2017-07-13T21:33:17.349Z",
+      "instances": [
+        {
+          "id": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003",
+          "status": "STABLE",
+          "statusSince": "2017-07-13T21:33:17.349Z",
+          "conditions": [],
+          "agentHostname": "192.168.99.100",
+          "resources": {
+            "cpus": 0.1,
+            "mem": 64,
+            "disk": 0,
+            "gpus": 0
+          },
+          "networks": [],
+          "containers": [
+            {
+              "name": "container1",
+              "status": "TASK_RUNNING",
+              "statusSince": "2017-07-13T21:33:17.349Z",
+              "conditions": [],
+              "containerId": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003.container1",
+              "endpoints": [],
+              "resources": {
+                "cpus": 0.1,
+                "mem": 32,
+                "disk": 0,
+                "gpus": 0
+              },
+              "lastUpdated": "2017-07-13T21:33:17.349Z",
+              "lastChanged": "2017-07-13T21:33:17.349Z"
+            }
+          ],
+          "specReference": "/v2/pods/fake-pod::versions/2017-07-13T21:33:07.389Z",
+          "lastUpdated": "2017-07-13T21:33:17.349Z",
+          "lastChanged": "2017-07-13T21:33:17.349Z"
+        }
+      ],
+      "terminationHistory": [],
+      "lastUpdated": "2017-07-13T22:13:46.635Z",
+      "lastChanged": "2017-07-13T21:33:17.349Z"
+    }
+- uri: /v2/pods/fake-pod2::status
+  method: GET
+  content: |
+    {
+      "id": "/fake-pod2",
+      "spec": {
+        "id": "/fake-pod2",
+        "labels": {
+          "key": "value"
+        },
+        "version": "2014-08-18T22:36:41.451Z",
+        "user": "nobody",
+        "environment": {
+          "key": {
+            "secret": "secret0"
+          }
+        },
+        "containers": [],
+        "secrets": {
+          "secret0": {
+            "source": "source0"
+          }
+        },
+        "volumes": [],
+        "networks": [],
+        "scaling": {
+          "kind": "fixed",
+          "instances": 1
+        },
+        "scheduling": {}
+      },
+      "status": "DEGRADED",
+      "statusSince": "2017-07-13T21:33:17.349Z",
+      "instances": [
+        {
+          "id": "fake-pod2.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003",
+          "status": "DEGRADED",
+          "statusSince": "2017-07-13T21:33:17.349Z",
+          "conditions": [],
+          "agentHostname": "192.168.99.100",
+          "resources": {
+            "cpus": 0.1,
+            "mem": 64,
+            "disk": 0,
+            "gpus": 0
+          },
+          "networks": [],
+          "containers": [
+            {
+              "name": "container1",
+              "status": "TASK_RUNNING",
+              "statusSince": "2017-07-13T21:33:17.349Z",
+              "conditions": [],
+              "containerId": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003.container1",
+              "endpoints": [],
+              "resources": {
+                "cpus": 0.1,
+                "mem": 32,
+                "disk": 0,
+                "gpus": 0
+              },
+              "lastUpdated": "2017-07-13T21:33:17.349Z",
+              "lastChanged": "2017-07-13T21:33:17.349Z"
+            }
+          ],
+          "specReference": "/v2/pods/fake-pod::versions/2017-07-13T21:33:07.389Z",
+          "lastUpdated": "2017-07-13T21:33:17.349Z",
+          "lastChanged": "2017-07-13T21:33:17.349Z"
+        }
+      ],
+      "terminationHistory": [],
+      "lastUpdated": "2017-07-13T22:13:46.635Z",
+      "lastChanged": "2017-07-13T21:33:17.349Z"
+    }
+- uri: /v2/pods/::status
+  method: GET
+  content: |
+    [
+      {
+        "id": "/fake-pod",
+        "spec": {
+          "id": "/fake-pod",
+          "labels": {
+            "key": "value"
+          },
+          "version": "2014-08-18T22:36:41.451Z",
+          "user": "nobody",
+          "environment": {
+            "key": {
+              "secret": "secret0"
+            }
+          },
+          "containers": [],
+          "secrets": {
+            "secret0": {
+              "source": "source0"
+            }
+          },
+          "volumes": [],
+          "networks": [],
+          "scaling": {
+            "kind": "fixed",
+            "instances": 1
+          },
+          "scheduling": {}
+        },
+        "status": "STABLE",
+        "statusSince": "2017-07-13T21:33:17.349Z",
+        "instances": [
+          {
+            "id": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003",
+            "status": "STABLE",
+            "statusSince": "2017-07-13T21:33:17.349Z",
+            "conditions": [],
+            "agentHostname": "192.168.99.100",
+            "resources": {
+              "cpus": 0.1,
+              "mem": 64,
+              "disk": 0,
+              "gpus": 0
+            },
+            "networks": [],
+            "containers": [
+              {
+                "name": "container1",
+                "status": "TASK_RUNNING",
+                "statusSince": "2017-07-13T21:33:17.349Z",
+                "conditions": [],
+                "containerId": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003.container1",
+                "endpoints": [],
+                "resources": {
+                  "cpus": 0.1,
+                  "mem": 32,
+                  "disk": 0,
+                  "gpus": 0
+                },
+                "lastUpdated": "2017-07-13T21:33:17.349Z",
+                "lastChanged": "2017-07-13T21:33:17.349Z"
+              }
+            ],
+            "specReference": "/v2/pods/fake-pod::versions/2017-07-13T21:33:07.389Z",
+            "lastUpdated": "2017-07-13T21:33:17.349Z",
+            "lastChanged": "2017-07-13T21:33:17.349Z"
+          }
+        ],
+        "terminationHistory": [],
+        "lastUpdated": "2017-07-13T22:13:46.635Z",
+        "lastChanged": "2017-07-13T21:33:17.349Z"
+      }
+    ]
+- uri: /v2/pods/fake-pod
+  method: GET
+  content: |
+    {
+      "id": "/fake-pod",
+      "labels": {
+          "key": "value"
+      },
+      "version": "2014-08-18T22:36:41.451Z",
+      "user": "nobody",
+      "environment": {
+          "key1": "value",
+          "key2": {
+              "secret": "secret0"
+          }
+      },
+      "containers": [
+          {
+              "environment": {
+                  "key3": "value3",
+                  "key4": {
+                      "secret": "secret1"
+                  }
+              }
+          }
+      ],
+      "secrets": {
+          "secret0": {
+              "source": "source0"
+          },
+          "secret1": {
+              "source": "source1"
+          }
+      },
+      "volumes": [
+
+      ],
+      "networks": [
+
+      ],
+      "scaling": {
+          "kind": "fixed",
+          "instances": 1
+      },
+      "scheduling": {
+
+      }
+    }
+- uri: /v2/pods
+  method: GET
+  content: |
+    [
+      {
+        "id": "/fake-pod",
+        "labels": {
+            "key": "value"
+        },
+        "version": "2014-08-18T22:36:41.451Z",
+        "user": "nobody",
+        "environment": {
+            "key1": "value",
+            "key2": {
+                "secret": "secret0"
+            }
+        },
+        "containers": [
+        ],
+        "secrets": {
+            "secret0": {
+                "source": "source0"
+            }
+        },
+        "volumes": [
+
+        ],
+        "networks": [
+
+        ],
+        "scaling": {
+            "kind": "fixed",
+            "instances": 1
+        },
+        "scheduling": {
+
+        }
+      },
+      {
+        "id": "/fake-pod2",
+        "labels": {
+            "key": "value"
+        },
+        "version": "2014-08-18T22:36:41.451Z",
+        "user": "nobody",
+        "environment": {
+            "key": "value",
+            "key": {
+                "secret": "secret0"
+            }
+        },
+        "containers": [
+        ],
+        "secrets": {
+            "secret0": {
+                "source": "source0"
+            }
+        },
+        "volumes": [
+
+        ],
+        "networks": [
+
+        ],
+        "scaling": {
+            "kind": "fixed",
+            "instances": 1
+        },
+        "scheduling": {
+
+        }
+      }
+    ]
+- uri: /v2/pods
+  method: POST
+  content: |
+    {
+      "id": "/fake-pod",
+      "labels": {
+          "key": "value"
+      },
+      "version": "2014-08-18T22:36:41.451Z",
+      "user": "nobody",
+      "environment": {
+          "key": "value",
+          "key": {
+              "secret": "secret0"
+          }
+      },
+      "containers": [
+      ],
+      "secrets": {
+          "secret0": {
+              "source": "source0"
+          }
+      },
+      "volumes": [
+
+      ],
+      "networks": [
+
+      ],
+      "scaling": {
+          "kind": "fixed",
+          "instances": 1
+      },
+      "scheduling": {
+
+      }
+    }
+- uri: /v2/pods/fake-pod?force=true
+  method: PUT
+  content: |
+    {
+      "id": "/fake-pod",
+      "labels": {
+          "key": "value"
+      },
+      "version": "2014-08-18T22:36:41.451Z",
+      "user": "nobody",
+      "environment": {
+          "key": "value",
+          "key": {
+              "secret": "secret0"
+          }
+      },
+      "containers": [
+      ],
+      "secrets": {
+          "secret0": {
+              "source": "source0"
+          }
+      },
+      "volumes": [
+
+      ],
+      "networks": [
+
+      ],
+      "scaling": {
+          "kind": "fixed",
+          "instances": 2
+      },
+      "scheduling": {
+
+      }
+    }
+- uri: /v2/pods/fake-pod?force=true
+  method: DELETE
+  headers:
+    "Marathon-Deployment-Id": "c0e7434c-df47-4d23-99f1-78bd78662231"
+- uri: /v2/pods/fake-pod::versions
+  method: GET
+  content: |
+    [
+        "2014-08-18T22:36:41.451Z"
+    ]
+- uri: /v2/pods/fake-pod::versions/2014-08-18T22:36:41.451Z
+  method: GET
+  content: |
+    {
+      "id": "/fake-pod",
+      "labels": {
+          "key": "value"
+      },
+      "version": "2014-08-18T22:36:41.451Z",
+      "user": "nobody",
+      "environment": {
+          "key": "value",
+          "key": {
+              "secret": "secret0"
+          }
+      },
+      "containers": [
+      ],
+      "secrets": {
+          "secret0": {
+              "source": "source0"
+          }
+      },
+      "volumes": [
+
+      ],
+      "networks": [
+
+      ],
+      "scaling": {
+          "kind": "fixed",
+          "instances": 1
+      },
+      "scheduling": {
+
+      }
+    }
+- uri: /v2/pods/fake-pod::instances/fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003
+  method: DELETE
+  content: |
+    {
+      "instanceId": {
+        "idString": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003"
+      },
+      "agentInfo": {
+        "host": "192.168.99.100",
+        "agentId": "c3856950-ab1a-4aea-b1a8-168397c0fb33-S9",
+        "attributes": [
+          "CgR6b25lEAMqBgoEcGR4Mw==",
+          "CgZyZWdpb24QABoJCQAAAAAAAAhA",
+          "Cg1pbnN0YW5jZV90eXBlEAMqCQoHY29tcHV0ZQ==",
+          "CgNzZG4QAyoKCghjb250cmFpbA==",
+          "CgJvcxADKgkKB2NlbnRvczc="
+        ]
+      },
+      "tasksMap": {
+        "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003.container1": {
+          "taskId": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003.container1",
+          "runSpecVersion": "2017-07-13T23:04:05.835Z",
+          "status": {
+            "stagedAt": "2017-07-13T23:04:05.939Z",
+            "startedAt": "2017-07-13T23:04:07.767Z",
+            "mesosStatus": "Ck0KS3JjbHVzdGVyX2Rjb3Mtc2lkZWNhci5pbnN0YW5jZS05MWU5YzE1OC02ODFmLTExZTctYTE4ZS03MGIzZDU4MDAwMDMuc2lkZWNhchABKikKJ2MzODU2OTUwLWFiMWEtNGFlYS1iMWE4LTE2ODM5N2MwZmIzMy1TOTGxw/AZ/1nWQTpFCkNpbnN0YW5jZS1yY2x1c3Rlcl9kY29zLXNpZGVjYXIuOTFlOWMxNTgtNjgxZi0xMWU3LWExOGUtNzBiM2Q1ODAwMDAzSAJaEOVRUUkSVEbRi5mOLzx0bnxqkgIKvAEimAEKLgoRcmNsdXN0ZXIubG9jYXRpb24SGWdsb2JhbHJpb3QucGR4Mi5yY2x1c3RlcjEKGgoOcmNsdXN0ZXIuZ3JvdXASCHJjbHVzdGVyCiQKFHJjbHVzdGVyLmFwcGxpY2F0aW9uEgxkY29zLXNpZGVjYXIKJAoKRENPU19TUEFDRRIWL3JjbHVzdGVyL2Rjb3Mtc2lkZWNhcioRCAESDTEwLjQwLjI1My4xMDAyDHJjbHVzdGVyLWNuaRiiWyJOCiRiYjgyOWJiZS02OWExLTRiMmQtYWE4Zi0zNGY5MDQ4YjNiMjUSJgokY2UyZDFjOWMtZGQ5MC00MDYyLWI1M2QtYzdkNTRiZGI2ODI1",
+            "condition": {
+              "str": "running"
+            },
+            "networkInfo": {
+              "hostName": "192.168.99.100",
+              "hostPorts": [],
+              "ipAddresses": [
+                {
+                  "ipAddress": "192.168.99.100",
+                  "protocol": "IPv4"
+                }
+              ]
+            }
+          }
+        }
+      },
+      "runSpecVersion": "2017-07-13T23:04:05.835Z",
+      "state": {
+        "condition": {
+          "str": "running"
+        },
+        "since": "2017-07-13T23:04:07.767Z",
+        "activeSince": "2017-07-13T23:04:07.767Z"
+      },
+      "unreachableStrategy": {
+        "inactiveAfterSeconds": 300,
+        "expungeAfterSeconds": 600
+      }
+    }
+- uri: /v2/pods/fake-pod::instances
+  method: DELETE
+  content: |
+    [
+      {
+        "instanceId": {
+          "idString": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003"
+        },
+        "agentInfo": {
+          "host": "192.168.99.100",
+          "agentId": "c3856950-ab1a-4aea-b1a8-168397c0fb33-S9",
+          "attributes": [
+            "CgR6b25lEAMqBgoEcGR4Mw==",
+            "CgZyZWdpb24QABoJCQAAAAAAAAhA",
+            "Cg1pbnN0YW5jZV90eXBlEAMqCQoHY29tcHV0ZQ==",
+            "CgNzZG4QAyoKCghjb250cmFpbA==",
+            "CgJvcxADKgkKB2NlbnRvczc="
+          ]
+        },
+        "tasksMap": {
+          "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003.container1": {
+            "taskId": "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d5800003.container1",
+            "runSpecVersion": "2017-07-13T23:04:05.835Z",
+            "status": {
+              "stagedAt": "2017-07-13T23:04:05.939Z",
+              "startedAt": "2017-07-13T23:04:07.767Z",
+              "mesosStatus": "Ck0KS3JjbHVzdGVyX2Rjb3Mtc2lkZWNhci5pbnN0YW5jZS05MWU5YzE1OC02ODFmLTExZTctYTE4ZS03MGIzZDU4MDAwMDMuc2lkZWNhchABKikKJ2MzODU2OTUwLWFiMWEtNGFlYS1iMWE4LTE2ODM5N2MwZmIzMy1TOTGxw/AZ/1nWQTpFCkNpbnN0YW5jZS1yY2x1c3Rlcl9kY29zLXNpZGVjYXIuOTFlOWMxNTgtNjgxZi0xMWU3LWExOGUtNzBiM2Q1ODAwMDAzSAJaEOVRUUkSVEbRi5mOLzx0bnxqkgIKvAEimAEKLgoRcmNsdXN0ZXIubG9jYXRpb24SGWdsb2JhbHJpb3QucGR4Mi5yY2x1c3RlcjEKGgoOcmNsdXN0ZXIuZ3JvdXASCHJjbHVzdGVyCiQKFHJjbHVzdGVyLmFwcGxpY2F0aW9uEgxkY29zLXNpZGVjYXIKJAoKRENPU19TUEFDRRIWL3JjbHVzdGVyL2Rjb3Mtc2lkZWNhcioRCAESDTEwLjQwLjI1My4xMDAyDHJjbHVzdGVyLWNuaRiiWyJOCiRiYjgyOWJiZS02OWExLTRiMmQtYWE4Zi0zNGY5MDQ4YjNiMjUSJgokY2UyZDFjOWMtZGQ5MC00MDYyLWI1M2QtYzdkNTRiZGI2ODI1",
+              "condition": {
+                "str": "running"
+              },
+              "networkInfo": {
+                "hostName": "192.168.99.100",
+                "hostPorts": [],
+                "ipAddresses": [
+                  {
+                    "ipAddress": "192.168.99.100",
+                    "protocol": "IPv4"
+                  }
+                ]
+              }
+            }
+          }
+        },
+        "runSpecVersion": "2017-07-13T23:04:05.835Z",
+        "state": {
+          "condition": {
+            "str": "running"
+          },
+          "since": "2017-07-13T23:04:07.767Z",
+          "activeSince": "2017-07-13T23:04:07.767Z"
+        },
+        "unreachableStrategy": {
+          "inactiveAfterSeconds": 300,
+          "expungeAfterSeconds": 600
+        }
+      }
+    ]
 - uri: /v2/groups
   method: GET
   content: |
@@ -693,15 +1293,11 @@
                         "id": "/test/app",
                         "instances": 1,
                         "mem": 128.0,
-                        "ports": [
-                            10000
-                        ],
                         "requirePorts": false,
                         "storeUrls": [],
                         "upgradeStrategy": {
                             "minimumHealthCapacity": 1.0
                         },
-                        "uris": [],
                         "user": null,
                         "version": "2014-08-28T01:05:40.586Z"
                     }
@@ -737,15 +1333,11 @@
                 "id": "/test/app",
                 "instances": 1,
                 "mem": 128.0,
-                "ports": [
-                    10000
-                ],
                 "requirePorts": false,
                 "storeUrls": [],
                 "upgradeStrategy": {
                     "minimumHealthCapacity": 1.0
                 },
-                "uris": [],
                 "user": null,
                 "version": "2014-08-28T01:05:40.586Z"
             }
@@ -769,9 +1361,9 @@
               "container": {
                 "type": "DOCKER",
                 "docker": {
-                  "image": "quay.io/gambol99/apache-php:latest",
-                  "network": "BRIDGE",
-                  "portMappings": [
+                  "image": "quay.io/gambol99/apache-php:latest"
+                },
+                "portMappings": [
                     {
                       "containerPort": 80,
                       "hostPort": 0,
@@ -782,8 +1374,7 @@
                       "hostPort": 0,
                       "protocol": "tcp"
                     }
-                  ]
-                }
+                ]
               },
               "healthChecks": [
                 {
@@ -799,6 +1390,11 @@
               "id": "apache",
               "mem": 64,
               "args": [],
+              "networks": [
+                {
+                  "mode": "container/bridge"
+                }
+              ],
               "env": {
                 "ENVIRONMENT": "qa",
                 "SERVICE_80_NAME": "apache_http-qa-1",
@@ -816,12 +1412,11 @@
               "container": {
                 "type": "DOCKER",
                 "docker": {
-                  "image": "tutum/mysql",
-                  "network": "BRIDGE",
-                  "portMappings": [
-                    { "containerPort": 3306, "hostPort": 0, "protocol": "tcp" }
-                  ]
-                }
+                  "image": "tutum/mysql"
+                },
+                "portMappings": [
+                  { "containerPort": 3306, "hostPort": 0, "protocol": "tcp" }
+                ]
               },
               "healthChecks": [
                 {
@@ -836,6 +1431,11 @@
               "id": "mysql",
               "mem": 1024,
               "cmd": "",
+              "networks": [
+                {
+                  "mode": "container/bridge"
+                }
+              ],
               "env": {
                 "ENVIRONMENT": "qa",
                 "SERVICE_NAME": "dbmaster",
@@ -849,16 +1449,15 @@
               "container": {
                 "type": "DOCKER",
                 "docker": {
-                  "image": "redis",
-                  "network": "BRIDGE",
-                  "portMappings": [
+                  "image": "redis"
+                },
+                "portMappings": [
                     {
                       "containerPort": 6379,
                       "hostPort": 0,
                       "protocol": "tcp"
                     }
-                  ]
-                }
+                ]
               },
               "healthChecks": [
                 {
@@ -873,6 +1472,11 @@
               "id": "caching",
               "cmd": "",
               "mem": 128,
+              "networks": [
+                {
+                  "mode": "container/bridge"
+                }
+              ],
               "env": {
                 "ENVIRONMENT": "qa",
                 "SERVICE_6379_NAME": "redis-qa-1"
@@ -1015,7 +1619,18 @@
           {
             "action": "ScaleApplication",
             "app": "/test-app-v1",
-            "readinessCheckResults": []
+            "readinessCheckResults": [
+              {
+                "name": "myReadyCheck",
+                "taskId": "test_frontend_app1.c9de6033",
+                "ready": false,
+                "lastResponse": {
+                  "body": "{}",
+                  "contentType": "application/json",
+                  "status": 500
+                }
+              }
+            ]
           }
         ],
         "currentStep": 2,
@@ -1030,6 +1645,11 @@
         "deploymentId": "0b1467fc-d5cd-4bbc-bac2-2805351cee1e",
         "version": "2014-08-26T08:20:26.171Z"
     }
+- uri: /v2/deployments/867ed450-f6a8-4d33-9b0e-e11c5513990b?force=true
+  method: DELETE
+  content: |
+    {
+    }
 - uri: /v2/eventSubscriptions?callbackUrl=http://localhost:9292/callback
   method: POST
   content: |
@@ -1089,15 +1709,11 @@
                     "id": "/test",
                     "instances": 3,
                     "mem": 32.0,
-                    "ports": [10000],
                     "requirePorts": false,
                     "storeUrls": [],
                     "upgradeStrategy": {
                         "minimumHealthCapacity": 1.0
                     },
-                    "uris": [
-                        "http://downloads.mesosphere.com/misc/toggle.tgz"
-                    ],
                     "user": null,
                     "version": "2014-08-26T05:04:49.766Z"
                 }
@@ -1222,18 +1838,17 @@
             "id": "/fake-app",
             "instances": 2,
             "mem": 64.0,
-            "ports": [
-                10000,
-                10001
-            ],
             "requirePorts": false,
+            "residency" : {
+                "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT",
+                "relaunchEscalationTimeoutSeconds" : 60
+            },
             "storeUrls": [],
             "tasksRunning": 2,
             "tasksStaged": 0,
             "upgradeStrategy": {
                 "minimumHealthCapacity": 1.0
             },
-            "uris": [],
             "user": null,
             "version": "2014-09-25T02:26:59.256Z"
         },
@@ -1285,10 +1900,6 @@
             "id": "/fake-app-broken",
             "instances": 2,
             "mem": 64.0,
-            "ports": [
-                10000,
-                10001
-            ],
             "requirePorts": false,
             "storeUrls": [],
             "tasksRunning": 2,
@@ -1296,7 +1907,6 @@
             "upgradeStrategy": {
                 "minimumHealthCapacity": 1.0
             },
-            "uris": [],
             "user": null,
             "version": "2014-09-25T02:26:59.256Z"
         }
@@ -1322,9 +1932,6 @@
         "id": "/no-health-check-results-app",
         "instances": 2,
         "mem": 32.0,
-        "ports": [
-            10000
-        ],
         "tasks": [
             {
                 "appId": "/no-health-check-results-app",
@@ -1350,3 +1957,69 @@
         "version": "2014-09-12T23:28:21.737Z"
     }
     }
+- uri: /v2/apps?embed=apps.readiness
+  method: GET
+  content: |
+    {
+    "apps": [
+      {
+        "id": "/fake-app",
+        "readinessCheckResults": [
+          {
+            "name": "myReadyCheck",
+            "taskId": "test_frontend_app1.c9de6033",
+            "ready": false,
+            "lastResponse": {
+              "body": "{}",
+              "contentType": "application/json",
+              "status": 500
+            }
+          }
+        ]
+      }
+    ]
+    }
+- uri: /v2/apps/fake-app
+  method: GET
+  scope: unreachablestrategy-present
+  content: |
+    {
+    "app": {
+          "id": "/fake-app",
+          "unreachableStrategy": {
+            "inactiveAfterSeconds": 3.0,
+            "expungeAfterSeconds": 4.0
+          }
+      }
+    }
+- uri: /v2/apps/fake-app
+  method: GET
+  scope: unreachablestrategy-absent
+  content: |
+    {
+    "app": {
+          "id": "/fake-app",
+          "unreachableStrategy": "disabled"
+      }
+    }
+
+- uri: /v2/apps/fake-app
+  method: GET
+  scope: environment-variables
+  content: |
+    {
+    "app": {
+          "id": "/fake-app",
+          "env": {
+            "FOO": "bar",
+            "TOP": {
+              "secret": "secret"
+              }
+            },
+          "secrets":{
+            "secret": {
+              "source": "/path/to/secret"
+              }
+            }
+      }
+    }
diff --git a/unreachable_strategy.go b/unreachable_strategy.go
new file mode 100644
index 0000000..6563239
--- /dev/null
+++ b/unreachable_strategy.go
@@ -0,0 +1,78 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// UnreachableStrategyAbsenceReasonDisabled signifies the reason of disabled unreachable strategy
+const UnreachableStrategyAbsenceReasonDisabled = "disabled"
+
+// UnreachableStrategy is the unreachable strategy applied to an application.
+type UnreachableStrategy struct {
+	EnabledUnreachableStrategy
+	AbsenceReason string
+}
+
+// EnabledUnreachableStrategy covers parameters pertaining to present unreachable strategies.
+type EnabledUnreachableStrategy struct {
+	InactiveAfterSeconds *float64 `json:"inactiveAfterSeconds,omitempty"`
+	ExpungeAfterSeconds  *float64 `json:"expungeAfterSeconds,omitempty"`
+}
+
+type unreachableStrategy UnreachableStrategy
+
+// UnmarshalJSON unmarshals the given JSON into an UnreachableStrategy. It
+// populates parameters for present strategies, and otherwise only sets the
+// absence reason.
+func (us *UnreachableStrategy) UnmarshalJSON(b []byte) error {
+	var u unreachableStrategy
+	var errEnabledUS, errNonEnabledUS error
+	if errEnabledUS = json.Unmarshal(b, &u); errEnabledUS == nil {
+		*us = UnreachableStrategy(u)
+		return nil
+	}
+
+	if errNonEnabledUS = json.Unmarshal(b, &us.AbsenceReason); errNonEnabledUS == nil {
+		return nil
+	}
+
+	return fmt.Errorf("failed to unmarshal unreachable strategy: unmarshaling into enabled returned error '%s'; unmarshaling into non-enabled returned error '%s'", errEnabledUS, errNonEnabledUS)
+}
+
+// MarshalJSON marshals the unreachable strategy.
+func (us *UnreachableStrategy) MarshalJSON() ([]byte, error) {
+	if us.AbsenceReason == "" {
+		return json.Marshal(us.EnabledUnreachableStrategy)
+	}
+
+	return json.Marshal(us.AbsenceReason)
+}
+
+// SetInactiveAfterSeconds sets the period after which instance will be marked as inactive.
+func (us *UnreachableStrategy) SetInactiveAfterSeconds(cap float64) *UnreachableStrategy {
+	us.InactiveAfterSeconds = &cap
+	return us
+}
+
+// SetExpungeAfterSeconds sets the period after which instance will be expunged.
+func (us *UnreachableStrategy) SetExpungeAfterSeconds(cap float64) *UnreachableStrategy {
+	us.ExpungeAfterSeconds = &cap
+	return us
+}
diff --git a/unreachable_strategy_test.go b/unreachable_strategy_test.go
new file mode 100644
index 0000000..ce23c2d
--- /dev/null
+++ b/unreachable_strategy_test.go
@@ -0,0 +1,133 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestUnreachableStrategyAPI(t *testing.T) {
+	app := Application{}
+	require.Nil(t, app.UnreachableStrategy)
+	us := new(UnreachableStrategy)
+	us.SetExpungeAfterSeconds(30.0).SetInactiveAfterSeconds(5.0)
+	app.SetUnreachableStrategy(*us)
+	testUs := app.UnreachableStrategy
+	assert.Equal(t, 30.0, *testUs.ExpungeAfterSeconds)
+	assert.Equal(t, 5.0, *testUs.InactiveAfterSeconds)
+
+	app.EmptyUnreachableStrategy()
+	us = app.UnreachableStrategy
+	require.NotNil(t, us)
+	assert.Nil(t, us.ExpungeAfterSeconds)
+	assert.Nil(t, us.InactiveAfterSeconds)
+}
+
+func TestUnreachableStrategyUnmarshalEnabled(t *testing.T) {
+	defaultConfig := NewDefaultConfig()
+	configs := &configContainer{
+		client: &defaultConfig,
+		server: &serverConfig{
+			scope: "unreachablestrategy-present",
+		},
+	}
+
+	endpoint := newFakeMarathonEndpoint(t, configs)
+	defer endpoint.Close()
+
+	application, err := endpoint.Client.Application(fakeAppName)
+	require.NoError(t, err)
+
+	us := application.UnreachableStrategy
+	require.NotNil(t, us)
+	assert.Empty(t, us.AbsenceReason)
+	if assert.NotNil(t, us.InactiveAfterSeconds) {
+		assert.Equal(t, 3.0, *us.InactiveAfterSeconds)
+	}
+	if assert.NotNil(t, us.ExpungeAfterSeconds) {
+		assert.Equal(t, 4.0, *us.ExpungeAfterSeconds)
+	}
+}
+
+func TestUnreachableStrategyUnmarshalNonEnabled(t *testing.T) {
+	defaultConfig := NewDefaultConfig()
+	configs := &configContainer{
+		client: &defaultConfig,
+		server: &serverConfig{
+			scope: "unreachablestrategy-absent",
+		},
+	}
+
+	endpoint := newFakeMarathonEndpoint(t, configs)
+	defer endpoint.Close()
+
+	application, err := endpoint.Client.Application(fakeAppName)
+	require.NoError(t, err)
+
+	us := application.UnreachableStrategy
+	require.NotNil(t, us)
+	assert.Equal(t, UnreachableStrategyAbsenceReasonDisabled, us.AbsenceReason)
+}
+
+func TestUnreachableStrategyUnmarshalIllegal(t *testing.T) {
+	j := []byte(`{false}`)
+	us := UnreachableStrategy{}
+	assert.Error(t, us.UnmarshalJSON(j))
+}
+
+func TestUnreachableStrategyMarshal(t *testing.T) {
+	tests := []struct {
+		name     string
+		us       UnreachableStrategy
+		wantJSON string
+	}{
+		{
+			name: "present",
+			us: UnreachableStrategy{
+				EnabledUnreachableStrategy: EnabledUnreachableStrategy{
+					InactiveAfterSeconds: float64p(3.5),
+					ExpungeAfterSeconds:  float64p(4.5),
+				},
+				AbsenceReason: "",
+			},
+			wantJSON: `{"inactiveAfterSeconds":3.5,"expungeAfterSeconds":4.5}`,
+		},
+		{
+			name: "absent",
+			us: UnreachableStrategy{
+				AbsenceReason: UnreachableStrategyAbsenceReasonDisabled,
+			},
+			wantJSON: fmt.Sprintf(`"%s"`, UnreachableStrategyAbsenceReasonDisabled),
+		},
+	}
+
+	for _, test := range tests {
+		label := fmt.Sprintf("test: %s", test.name)
+		j, err := test.us.MarshalJSON()
+		if assert.NoError(t, err, label) {
+			assert.Equal(t, test.wantJSON, string(j), label)
+		}
+	}
+}
+
+func float64p(f float64) *float64 {
+	return &f
+}
diff --git a/update_strategy.go b/upgrade_strategy.go
similarity index 83%
rename from update_strategy.go
rename to upgrade_strategy.go
index f964f08..d4d7598 100644
--- a/update_strategy.go
+++ b/upgrade_strategy.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -23,13 +23,13 @@ type UpgradeStrategy struct {
 }
 
 // SetMinimumHealthCapacity sets the minimum health capacity.
-func (us UpgradeStrategy) SetMinimumHealthCapacity(cap float64) UpgradeStrategy {
+func (us *UpgradeStrategy) SetMinimumHealthCapacity(cap float64) *UpgradeStrategy {
 	us.MinimumHealthCapacity = &cap
 	return us
 }
 
 // SetMaximumOverCapacity sets the maximum over capacity.
-func (us UpgradeStrategy) SetMaximumOverCapacity(cap float64) UpgradeStrategy {
+func (us *UpgradeStrategy) SetMaximumOverCapacity(cap float64) *UpgradeStrategy {
 	us.MaximumOverCapacity = &cap
 	return us
 }
diff --git a/utils.go b/utils.go
index 278f499..e967a21 100644
--- a/utils.go
+++ b/utils.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -133,3 +133,8 @@ func addOptions(s string, opt interface{}) (string, error) {
 	u.RawQuery = qs.Encode()
 	return u.String(), nil
 }
+
+// Bool returns a pointer to the passed in bool value
+func Bool(b bool) *bool {
+	return &b
+}
diff --git a/utils_test.go b/utils_test.go
index 88929f3..18ad1b2 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/volume.go b/volume.go
new file mode 100644
index 0000000..9e7cb29
--- /dev/null
+++ b/volume.go
@@ -0,0 +1,62 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+// PodVolume describes a volume on the host
+type PodVolume struct {
+	Name       string            `json:"name,omitempty"`
+	Host       string            `json:"host,omitempty"`
+	Secret     string            `json:"secret,omitempty"`
+	Persistent *PersistentVolume `json:"persistent,omitempty"`
+}
+
+// PodVolumeMount describes how to mount a volume into a task
+type PodVolumeMount struct {
+	Name      string `json:"name,omitempty"`
+	MountPath string `json:"mountPath,omitempty"`
+	ReadOnly  *bool  `json:"readOnly,omitempty"`
+}
+
+// NewPodVolume creates a new PodVolume
+func NewPodVolume(name, path string) *PodVolume {
+	return &PodVolume{
+		Name: name,
+		Host: path,
+	}
+}
+
+// NewPodVolume creates a new PodVolume for file based secrets
+func NewPodVolumeSecret(name, secretPath string) *PodVolume {
+	return &PodVolume{
+		Name:   name,
+		Secret: secretPath,
+	}
+}
+
+// NewPodVolumeMount creates a new PodVolumeMount
+func NewPodVolumeMount(name, mount string) *PodVolumeMount {
+	return &PodVolumeMount{
+		Name:      name,
+		MountPath: mount,
+	}
+}
+
+// SetPersistentVolume sets the persistence settings of a PodVolume
+func (pv *PodVolume) SetPersistentVolume(p *PersistentVolume) *PodVolume {
+	pv.Persistent = p
+	return pv
+}

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/application_marshalling.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/application_marshalling_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/health_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/network.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/offer.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_container.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_container_image.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_container_marshalling.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_instance.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_instance_status.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_instance_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_marshalling.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_marshalling_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_scheduling.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_status.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_status_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/pod_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/readiness.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/readiness_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/residency.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/residency_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/resources.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/tests/app-definitions/TestApplicationString-1.5-output.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/tests/app-definitions/TestApplicationString-output.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/unreachable_strategy.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/unreachable_strategy_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/upgrade_strategy.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/volume.go

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/gambol99/go-marathon/update_strategy.go

No differences were encountered in the control files

More details

Full run details