Full Code of gambol99/go-marathon for AI

master 94e7bcb625cd cached
79 files
414.9 KB
109.6k tokens
807 symbols
1 requests
Download .txt
Showing preview only (437K chars total). Download the full file or copy to clipboard to get everything.
Repository: gambol99/go-marathon
Branch: master
Commit: 94e7bcb625cd
Files: 79
Total size: 414.9 KB

Directory structure:
gitextract_zhlni1rs/

├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── application.go
├── application_marshalling.go
├── application_marshalling_test.go
├── application_test.go
├── client.go
├── client_test.go
├── cluster.go
├── cluster_test.go
├── config.go
├── const.go
├── deployment.go
├── deployment_test.go
├── docker.go
├── docker_test.go
├── error.go
├── error_test.go
├── events.go
├── examples/
│   ├── Makefile
│   ├── applications/
│   │   └── main.go
│   ├── docker-compose.yml
│   ├── events_callback_transport/
│   │   └── main.go
│   ├── events_sse_transport/
│   │   └── main.go
│   ├── glog/
│   │   └── main.go
│   ├── groups/
│   │   └── main.go
│   ├── multiple_endpoints/
│   │   └── main.go
│   ├── pods/
│   │   └── main.go
│   ├── queue/
│   │   └── main.go
│   └── tasks/
│       └── main.go
├── group.go
├── group_test.go
├── health.go
├── health_test.go
├── info.go
├── info_test.go
├── last_task_failure.go
├── network.go
├── offer.go
├── pod.go
├── pod_container.go
├── pod_container_image.go
├── pod_container_marshalling.go
├── pod_instance.go
├── pod_instance_status.go
├── pod_instance_test.go
├── pod_marshalling.go
├── pod_marshalling_test.go
├── pod_scheduling.go
├── pod_status.go
├── pod_status_test.go
├── pod_test.go
├── port_definition.go
├── queue.go
├── queue_test.go
├── readiness.go
├── readiness_test.go
├── residency.go
├── residency_test.go
├── resources.go
├── subscription.go
├── subscription_test.go
├── task.go
├── task_test.go
├── testing_utils_test.go
├── tests/
│   ├── app-definitions/
│   │   ├── TestApplicationString-1.5-output.json
│   │   └── TestApplicationString-output.json
│   └── rest-api/
│       └── methods.yml
├── unreachable_strategy.go
├── unreachable_strategy_test.go
├── upgrade_strategy.go
├── utils.go
├── utils_test.go
└── volume.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
go-marathon.iml
.idea/
Gemfile.lock
thin.*
examples/applications/applications
examples/events_callback_transport/events_callback_transport
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
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof

tests/rest-api/rest-api


================================================
FILE: .travis.yml
================================================
env:
  global:
    secure: YiSCbBUz0VMONSBZ6TfRaSM9bFBuT5xvaknt9WxWczPSiSgiY8+dGYlsOaX2jzI26J4zA8KxIyxOihN1UE28tkkGoXRkRovoQuOl9YUYp+VCtZdaeksZ7tJ/j/b6aYGpGN3GRRfxkuIhXw1ghZLgqdCVtqfmD3GODlmeuFE01ug=
language: go
go:
- 1.6
- 1.7
- 1.8
- 1.9
- "1.10"
- "1.11"
install:
- 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


================================================
FILE: CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.

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
- [#261][PR261] Fix URL parsing for Go 1.8.

## [0.7.0] - 2017-02-17
### Added
- [#256][PR256] Expose task state.

### Changed
- [#259][PR259] Add 'omitempty' to UpgradeStrategy properties.

## [0.6.0] - 2016-12-14
### Added
- [#246][PR246] Add TaskKillGracePeriodSeconds support.
- [#244][PR244] Add taskStats support.

### Changed
- [#242][PR242] Pointerize IPAddressPerTask.Discovery.

## [0.5.1] - 2016-11-09
### Fixed
- [#239][PR239] Fix scheme-less endpoint with port.

## [0.5.0] - 2016-11-07
### Fixed
- [#231][PR231] Fixed Marathon cluster code
- [#229][PR229] Add LastFailureCause field to HealthCheckResult.

## [0.4.0] - 2016-10-28
### Added
- [#223][PR223] Add support for IP-per-task.
- [#220][PR220] Add external volume definition to container.
- [#211][PR211] Close event channel on event listener removal.

### Fixed
- [#218][PR218] Remove TimeWaitPolling from marathonClient.
- [#214][PR214] Remove extra pointer layers when passing to r.api*.

## [0.3.0] - 2016-09-28
- [#201][PR201]: Subscribe method is now exposed on the client to allow subscription of callback URL's

### Fixed
- [#205][PR205]: Fix memory leak by signalling goroutine termination on event listener removal.

### Changed
- [#205][PR205]: Change AddEventsListener to return event channel instead of taking one.

## [0.2.0] - 2016-09-23
### Added
- [#196][PR196]: Port definitions.
- [#191][PR191]: name and labels to portMappings.

### Changed
- [#191][PR191] ExposePort() now takes a portMapping instance.

### Fixed
- [#202][PR202]: Timeout error in WaitOnApplication.

## [0.1.1] - 2016-09-07
### Fixed
- Drop question mark-only query parameter in Applications(url.Values) manually
  due to changed behavior in Go 1.7's net/url.Parse.

## [0.1.0] - 2016-08-01
### Added
- Field `message` to the EventStatusUpdate struct.
- Method `Host()` to set host mode explicitly.
- Field `port` to HealthCheck.
- Support for launch queues.
- Convenience method `AddFetchURIs()`.
- Support for forced operations across all methods.
- Filtering method variants (`*By`-suffixed).
- Support for Marathon DCOS token.
- Basic auth and HTTP client settings.
- Marshalling of `Deployment.DeploymentStep` for Marathon v1.X.
- Field `ipAddresses` to tasks and events.
- Field `slaveId` to tasks.
- Convenience methods to populate/clear pointerized values.
- Method `ApplicationByVersion()` to retrieve version-specific apps.
- Support for fetch URIs.
- Parse API error responses on all error types for programmatic evaluation.

### Changed
- Consider app as unhealthy in ApplicationOK if health check is missing. (Ensures result stability during all phases of deployment.)
- Various identifiers violating golint rules.
- Do not set "bridged" mode on Docker containers by default.

### Fixed
- Flawed unmarshalling of `CurrentStep` in events.
- Missing omitempty tag modifiers on `Application.Uris`.
- Missing leading slash in path used by `Ping()`.
- Flawed `KillTask()` in case of hierarchical app ID path.
- Missing omitempty tag modifier on `PortMapping.Protocol`.
- Nil dereference on empty debug log.
- Various occasions where omitted and empty fields could not be distinguished.

## 0.0.1 - 2016-01-27
### Added
- Initial SemVer release.

[Unreleased]: https://github.com/gambol99/go-marathon/compare/v0.7.1...HEAD
[0.7.1]: https://github.com/gambol99/go-marathon/compare/v0.7.0...v0.7.1
[0.7.0]: https://github.com/gambol99/go-marathon/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/gambol99/go-marathon/compare/v0.5.1...v0.6.0
[0.5.1]: https://github.com/gambol99/go-marathon/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/gambol99/go-marathon/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/gambol99/go-marathon/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/gambol99/go-marathon/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/gambol99/go-marathon/compare/v0.1.1...v0.2.0
[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
[PR246]: https://github.com/gambol99/go-marathon/pull/246
[PR244]: https://github.com/gambol99/go-marathon/pull/244
[PR242]: https://github.com/gambol99/go-marathon/pull/242
[PR239]: https://github.com/gambol99/go-marathon/pull/239
[PR231]: https://github.com/gambol99/go-marathon/pull/231
[PR229]: https://github.com/gambol99/go-marathon/pull/229
[PR223]: https://github.com/gambol99/go-marathon/pull/223
[PR220]: https://github.com/gambol99/go-marathon/pull/220
[PR218]: https://github.com/gambol99/go-marathon/pull/218
[PR214]: https://github.com/gambol99/go-marathon/pull/214
[PR211]: https://github.com/gambol99/go-marathon/pull/211
[PR205]: https://github.com/gambol99/go-marathon/pull/205
[PR202]: https://github.com/gambol99/go-marathon/pull/202
[PR201]: https://github.com/gambol99/go-marathon/pull/201
[PR196]: https://github.com/gambol99/go-marathon/pull/196
[PR191]: https://github.com/gambol99/go-marathon/pull/191


================================================
FILE: CONTRIBUTING.md
================================================
# Contribution Guidelines

## Pre-Development
- Look for an existing Github issue describing the bug you have found/feature request you would like to see getting implemented.
- If no issue exists and there is reason to believe that your (non-trivial) contribution might be subject to an up-front design discussion, file an issue first and propose your idea.

## Development
- Fork the repository.
- Create a feature branch (`git checkout -b my-new-feature master`).
- Commit your changes, preferring one commit per logical unit of work. Often times, this simply means having a single commit.
- If applicable, update the documentation in the [README file](README.md).
- In the vast majority of cases, you should add/amend a (regression) test for your bug fix/feature.
- Push your branch (`git push origin my-new-feature`).
- Create a new pull request.
- Address any comments your reviewer raises, pushing additional commits onto your branch along the way. In particular, refrain from amending/force-pushing until you receive an LGTM (Looks Good To Me) from your reviewer. This will allow for a better review experience.


================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!)  The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright {yyyy} {name of copyright owner}

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.


================================================
FILE: Makefile
================================================
#
#   Author: Rohith (gambol99@gmail.com)
#   Date: 2015-02-10 15:35:14 +0000 (Tue, 10 Feb 2015)
#
#  vim:ts=2:sw=2:et
#
HARDWARE=$(shell uname -m)
VERSION=$(shell awk '/const Version/ { print $$4 }' version.go | sed 's/"//g')
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 changelog check-format coverage cover

build:
	go build

deps:
	@echo "--> Installing build dependencies"
	@go get -d -v ./... $(DEPS)

lint:
	@echo "--> Running golint"
	@which golint 2>/dev/null ; if [ $$? -eq 1 ]; then \
		go get -u github.com/golang/lint/golint; \
	fi
	@golint .

vet:
	@echo "--> Running go tool vet $(VETARGS) ."
	@go tool vet 2>/dev/null ; if [ $$? -eq 3 ]; then \
		go get golang.org/x/tools/cmd/vet; \
	fi
	@go tool vet $(VETARGS) .

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)

check-format:
	@echo "--> Checking format"
	@if gofmt -l . 2>&1 | grep -q '.go'; then \
		echo "found unformatted files:"; \
		echo; \
		gofmt -l .; \
		exit 1; \
	fi

test: deps vet
	@echo "--> Running go tests"
	@go test -race -v
	@$(MAKE) cover

examples:
	make -C examples all

changelog: release
	git log $(shell git tag | tail -n1)..HEAD --no-merges --format=%B > changelog


================================================
FILE: README.md
================================================
[![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

Go-marathon is a API library for working with [Marathon](https://mesosphere.github.io/marathon/).
It currently supports

- Application and group deployment
- 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.6 or higher.

## Code Examples

There is also an examples directory in the source which shows hints and snippets of code of how to use it —
which is probably the best place to start.

You can use `examples/docker-compose.yml` in order to start a test cluster.

### Creating a client

```go
import (
	marathon "github.com/gambol99/go-marathon"
)

marathonURL := "http://10.241.1.71:8080"
config := marathon.NewDefaultConfig()
config.URL = marathonURL
client, err := marathon.NewClient(config)
if err != nil {
	log.Fatalf("Failed to create a client for marathon, error: %s", err)
}

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
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.

You can also pass a custom path to the URL, which is especially needed in case of DCOS:

```go
marathonURL := "http://10.241.1.71:8080/cluster,10.241.1.72:8080/cluster,10.241.1.73:8080/cluster"
```

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
config.HTTPClient = &http.Client{
    Timeout: (time.Duration(10) * time.Second),
    Transport: &http.Transport{
        Dial: (&net.Dialer{
            Timeout:   10 * time.Second,
            KeepAlive: 10 * time.Second,
        }).Dial,
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: true,
        },
    },
}
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(nil)
if err != nil {
	log.Fatalf("Failed to list applications: %s", err)
}

log.Printf("Found %d application(s) running", len(applications.Apps))
for _, application := range applications.Apps {
	log.Printf("Application: %s", application)
	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("application %s has task: %s", appID, task)
		}
	}
}
```

### Creating a new application

```go
log.Printf("Deploying a new application")
application := marathon.NewDockerApplication().
  Name(applicationName).
  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").
  CheckHTTP("/health", 10, 5)

application.
  Container.Docker.Container("quay.io/gambol99/apache-php:latest").
  Bridged().
  Expose(80).
  Expose(443)

if _, err := client.CreateApplication(application); err != nil {
	log.Fatalf("Failed to create application: %s, error: %s", application, err)
} else {
	log.Printf("Created the application: %s", application)
}
```

Note: Applications may also be defined by means of initializing a `marathon.Application` struct instance directly. However, go-marathon's DSL as shown above provides a more concise way to achieve the same.

### Scaling application

Change the number of application instances to 4

```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)
} else {
	client.WaitOnApplication(application.ID, 30 * time.Second)
	log.Printf("Successfully scaled the application")
}
```

### 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
changes and failures. There are two different event transports controlled by `EventsTransport`
setting with the following possible values: `EventsTransportSSE` and `EventsTransportCallback` (default value).
See [Event Stream](https://mesosphere.github.io/marathon/docs/rest-api.html#event-stream) and
[Event Subscriptions](https://mesosphere.github.io/marathon/docs/rest-api.html#event-subscriptions) for details.

Event subscriptions can also be individually controlled with the `Subscribe` and `Unsubscribe` functions. See [Controlling subscriptions](#controlling-subscriptions) for more details.

#### Event Stream

Only available in Marathon >= 0.9.0. Does not require any special configuration or prerequisites.

```go
// Configure client
config := marathon.NewDefaultConfig()
config.URL = marathonURL
config.EventsTransport = marathon.EventsTransportSSE

client, err := marathon.NewClient(config)
if err != nil {
	log.Fatalf("Failed to create a client for marathon, error: %s", err)
}

// Register for events
events, err = client.AddEventsListener(marathon.EventIDApplications)
if err != nil {
	log.Fatalf("Failed to register for events, %s", err)
}

timer := time.After(60 * time.Second)
done := false

// Receive events from channel for 60 seconds
for {
	if done {
		break
	}
	select {
	case <-timer:
		log.Printf("Exiting the loop")
		done = true
	case event := <-events:
		log.Printf("Received event: %s", event)
	}
}

// Unsubscribe from Marathon events
client.RemoveEventsListener(events)
```

#### Event Subscriptions

Requires to start a built-in web server accessible by Marathon to connect and push events to. Consider the following
additional settings:

- `EventsInterface` — the interface we should be listening on for events. Default `"eth0"`.
- `EventsPort` — built-in web server port. Default `10001`.
- `CallbackURL` — custom callback URL. Default `""`.

```go
// Configure client
config := marathon.NewDefaultConfig()
config.URL = marathonURL
config.EventsInterface = marathonInterface
config.EventsPort = marathonPort

client, err := marathon.NewClient(config)
if err != nil {
	log.Fatalf("Failed to create a client for marathon, error: %s", err)
}

// Register for events
events, err = client.AddEventsListener(marathon.EventIDApplications)
if err != nil {
	log.Fatalf("Failed to register for events, %s", err)
}

timer := time.After(60 * time.Second)
done := false

// Receive events from channel for 60 seconds
for {
	if done {
		break
	}
	select {
	case <-timer:
		log.Printf("Exiting the loop")
		done = true
	case event := <-events:
		log.Printf("Received event: %s", event)
	}
}

// Unsubscribe from Marathon events
client.RemoveEventsListener(events)
```

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
// Configure client
config := marathon.NewDefaultConfig()
config.URL = marathonURL

client, err := marathon.NewClient(config)
if err != nil {
	log.Fatalf("Failed to create a client for marathon, error: %s", err)
}

// Register an event subscriber via a callback URL
callbackURL := "http://10.241.1.71:9494"
if err := client.Subscribe(callbackURL); err != nil {
	log.Fatalf("Unable to register the callbackURL [%s], error: %s", callbackURL, err)
}

// Deregister the same subscriber
if err := client.Unsubscribe(callbackURL); err != nil {
	log.Fatalf("Unable to deregister the callbackURL [%s], error: %s", callbackURL, err)
}
```

## Contributing

See the [contribution guidelines](CONTRIBUTING.md).

## Development

### Marathon Fake

go-marathon employs a [fake Marathon implementation](https://github.com/gambol99/go-marathon/blob/master/testing_utils_test.go) for testing purposes. It [maintains a YML-encoded list of HTTP response messages](https://github.com/gambol99/go-marathon/blob/master/tests/rest-api/methods.yml) which are returned upon a successful match based upon a number of attributes, the so-called _message identifier_:

- HTTP URI (without the protocol and the hostname, e.g., `/v2/apps`)
- HTTP method (e.g., `GET`)
- response content (i.e., the message returned)
- scope (see below)

#### Response Content

The response content can be provided in one of two forms:

- **static:** A pure response message returned on every match, including repeated queries.
- **index:** A list of response messages associated to a particular (indexed) sequence order. A message will be returned _iff_ it matches and its zero-based index equals the current request count.

An example for a trivial static response content is

```yaml
- uri: /v2/apps
  method: POST
  content: |
		{
		"app": {
		}
		}
```

which would be returned for every POST request targetting `/v2/apps`.

An indexed response content would look like:

```yaml
- uri: /v2/apps
  method: POST
  contentSequence:
		- index: 1
		- content: |
			{
			"app": {
				"id": "foo"
			}
			}
		- index: 3
		- content: |
			{
			"app": {
				"id": "bar"
			}
			}
```

What this means is that the first POST request to `/v2/apps` would yield a 404, the second one the _foo_ app, the third one 404 again, the fourth one _bar_, and every following request thereafter a 404 again. Indexed responses enable more flexible testing required by some use cases.

Trying to define both a static and indexed response content constitutes an error and leads to `panic`.

#### Scope

By default, all responses are defined globally: Every message can be queried by any request across all tests. This enables reusability and allows to keep the YML definition fairly short. For certain cases, however, it is desirable to define a set of responses that are delivered exclusively for a particular test. Scopes offer a means to do so by representing a concept similar to [namespaces](https://en.wikipedia.org/wiki/Namespace). Combined with indexed responses, they allow to return different responses for message identifiers already defined at the global level.

Scopes do not have a particular format -- they are just strings. A scope must be defined in two places: The message specification and the server configuration. They are pure strings without any particular structure. Given the messages specification

```yaml
- uri: /v2/apps
  method: GET
	# Note: no scope defined.
  content: |
		{
		"app": {
			"id": "foo"
		}
		}
- uri: /v2/apps
  method: GET
	scope: v1.1.1  # This one does have a scope.
  contentSequence:
		- index: 1
		- content: |
			{
			"app": {
				"id": "bar"
			}
			}
```

and the tests

```go
func TestFoo(t * testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)  // No custom configs given.
	defer endpoint.Close()
	app, err := endpoint.Client.Applications(nil)
	// Do something with "foo"
}

func TestFoo(t * testing.T) {
	endpoint := newFakeMarathonEndpoint(t, &configContainer{
		server: &serverConfig{
			scope: "v1.1.1",		// Matches the message spec's scope.
		},
	})
	defer endpoint.Close()
	app, err := endpoint.Client.Applications(nil)
	// Do something with "bar"
}
```

The "foo" response can be used by all tests using the default fake endpoint (such as `TestFoo`), while the "bar" response is only visible by tests that explicitly set the scope to `1.1.1` (as `TestBar` does) and query the endpoint twice.


================================================
FILE: application.go
================================================
/*
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.
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"
	"errors"
	"fmt"
	"net/url"
	"time"
)

var (
	// ErrNoApplicationContainer is thrown when a container has been specified yet
	ErrNoApplicationContainer = errors.New("you have not specified a docker container yet")
)

// Applications is a collection of applications
type Applications struct {
	Apps []Application `json:"apps"`
}

// IPAddressPerTask is used by IP-per-task functionality https://mesosphere.github.io/marathon/docs/ip-per-task.html
type IPAddressPerTask struct {
	Groups      *[]string          `json:"groups,omitempty"`
	Labels      *map[string]string `json:"labels,omitempty"`
	Discovery   *Discovery         `json:"discovery,omitempty"`
	NetworkName string             `json:"networkName,omitempty"`
}

// Discovery provides info about ports expose by IP-per-task functionality
type Discovery struct {
	Ports *[]Port `json:"ports,omitempty"`
}

// Port provides info about ports used by IP-per-task
type Port struct {
	Number   int    `json:"number,omitempty"`
	Name     string `json:"name,omitempty"`
	Protocol string `json:"protocol,omitempty"`
}

// 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"`
	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
type ApplicationVersions struct {
	Versions []string `json:"versions"`
}

// ApplicationVersion is the application version response from marathon
type ApplicationVersion struct {
	Version string `json:"version"`
}

// VersionInfo is the application versioning details from marathon
type VersionInfo struct {
	LastScalingAt      string `json:"lastScalingAt,omitempty"`
	LastConfigChangeAt string `json:"lastConfigChangeAt,omitempty"`
}

// Fetch will download URI before task starts
type Fetch struct {
	URI        string `json:"uri"`
	Executable bool   `json:"executable"`
	Extract    bool   `json:"extract"`
	Cache      bool   `json:"cache"`
}

// GetAppOpts contains a payload for Application method
//		embed:	Embeds nested resources that match the supplied path.
// 				You can specify this parameter multiple times with different values
type GetAppOpts struct {
	Embed []string `url:"embed,omitempty"`
}

// DeleteAppOpts contains a payload for DeleteApplication method
//		force:		overrides a currently running deployment.
type DeleteAppOpts struct {
	Force bool `url:"force,omitempty"`
}

// TaskStats is a container for Stats
type TaskStats struct {
	Stats Stats `json:"stats"`
}

// Stats is a collection of aggregate statistics about an application's tasks
type Stats struct {
	Counts   map[string]int     `json:"counts"`
	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.
func (r *Application) SetIPAddressPerTask(ipAddressPerTask IPAddressPerTask) *Application {
	r.Ports = make([]int, 0)
	r.EmptyPortDefinitions()
	r.IPAddressPerTask = &ipAddressPerTask

	return r
}

// NewDockerApplication creates a default docker application
func NewDockerApplication() *Application {
	application := new(Application)
	application.Container = NewDockerContainer()
	return application
}

// Name sets the name / ID of the application i.e. the identifier for this application
func (r *Application) Name(id string) *Application {
	r.ID = validateID(id)
	return r
}

// Command sets the cmd of the application
func (r *Application) Command(cmd string) *Application {
	r.Cmd = &cmd
	return r
}

// CPU set the amount of CPU shares per instance which is assigned to the application
//		cpu:	the CPU shared (check Docker docs) per instance
func (r *Application) CPU(cpu float64) *Application {
	r.CPUs = cpu
	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
func (r *Application) Storage(disk float64) *Application {
	r.Disk = &disk
	return r
}

// AllTaskRunning checks to see if all the application tasks are running, i.e. the instances is equal
// to the number of running tasks
func (r *Application) AllTaskRunning() bool {
	if r.Instances == nil || *r.Instances == 0 {
		return true
	}
	if r.Tasks == nil {
		return false
	}
	if r.TasksRunning == *r.Instances {
		return true
	}
	return false
}

// DependsOn adds one or more dependencies for this application. Note, if you want to wait for
// an application dependency to actually be UP, i.e. not just deployed, you need a health check
// on the dependant app.
//		names:	the application id(s) this application depends on
func (r *Application) DependsOn(names ...string) *Application {
	if r.Dependencies == nil {
		r.Dependencies = make([]string, 0)
	}
	r.Dependencies = append(r.Dependencies, names...)

	return r
}

// Memory sets he amount of memory the application can consume per instance
//		memory:	the amount of MB to assign
func (r *Application) Memory(memory float64) *Application {
	r.Mem = &memory

	return r
}

// AddPortDefinition adds a port definition. Port definitions are used to define ports that
// should be considered part of a resource. They are necessary when you are using HOST
// networking and no port mappings are specified.
func (r *Application) AddPortDefinition(portDefinition PortDefinition) *Application {
	if r.PortDefinitions == nil {
		r.EmptyPortDefinitions()
	}

	portDefinitions := *r.PortDefinitions
	portDefinitions = append(portDefinitions, portDefinition)
	r.PortDefinitions = &portDefinitions
	return r
}

// EmptyPortDefinitions explicitly empties port definitions -- use this if you need to empty
// port definitions of an application that already has port definitions set (setting port definitions to nil will
// keep the current value)
func (r *Application) EmptyPortDefinitions() *Application {
	r.PortDefinitions = &[]PortDefinition{}

	return r
}

// Count sets the number of instances of the application to run
//		count:	the number of instances to run
func (r *Application) Count(count int) *Application {
	r.Instances = &count

	return r
}

// SetTaskKillGracePeriod sets the number of seconds between escalating from SIGTERM to SIGKILL
// when signalling tasks to terminate. Using this grace period, tasks should perform orderly shut down
// immediately upon receiving SIGTERM.
//		seconds:	the number of seconds
func (r *Application) SetTaskKillGracePeriod(seconds float64) *Application {
	r.TaskKillGracePeriodSeconds = &seconds

	return r
}

// AddArgs adds one or more arguments to the applications
//		arguments:	the argument(s) you are adding
func (r *Application) AddArgs(arguments ...string) *Application {
	if r.Args == nil {
		r.EmptyArgs()
	}

	args := *r.Args
	args = append(args, arguments...)
	r.Args = &args

	return r
}

// EmptyArgs explicitly empties arguments -- use this if you need to empty
// arguments of an application that already has arguments set (setting args to nil will
// keep the current value)
func (r *Application) EmptyArgs() *Application {
	r.Args = &[]string{}

	return r
}

// AddConstraint adds a new constraint
//		constraints:	the constraint definition, one constraint per array element
func (r *Application) AddConstraint(constraints ...string) *Application {
	if r.Constraints == nil {
		r.EmptyConstraints()
	}

	c := *r.Constraints
	c = append(c, constraints)
	r.Constraints = &c

	return r
}

// 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 (r *Application) EmptyConstraints() *Application {
	r.Constraints = &[][]string{}

	return r
}

// AddLabel adds a label to the application
//		name:	the name of the label
//		value: value for this label
func (r *Application) AddLabel(name, value string) *Application {
	if r.Labels == nil {
		r.EmptyLabels()
	}
	(*r.Labels)[name] = value

	return r
}

// EmptyLabels explicitly empties the labels -- use this if you need to empty
// the labels of an application that already has labels set (setting labels to nil will
// keep the current value)
func (r *Application) EmptyLabels() *Application {
	r.Labels = &map[string]string{}

	return r
}

// AddEnv adds an environment variable to the application
// 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()
	}
	(*r.Env)[name] = value

	return r
}

// EmptyEnvs explicitly empties the envs -- use this if you need to empty
// the environments of an application that already has environments set (setting env to nil will
// keep the current value)
func (r *Application) EmptyEnvs() *Application {
	r.Env = &map[string]string{}

	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

	return r
}

// AddHealthCheck adds a health check
// 	healthCheck the health check that should be added
func (r *Application) AddHealthCheck(healthCheck HealthCheck) *Application {
	if r.HealthChecks == nil {
		r.EmptyHealthChecks()
	}

	healthChecks := *r.HealthChecks
	healthChecks = append(healthChecks, healthCheck)
	r.HealthChecks = &healthChecks

	return r
}

// EmptyHealthChecks explicitly empties health checks -- use this if you need to empty
// health checks of an application that already has health checks set (setting health checks to nil will
// keep the current value)
func (r *Application) EmptyHealthChecks() *Application {
	r.HealthChecks = &[]HealthCheck{}

	return r
}

// HasHealthChecks is a helper method, used to check if an application has health checks
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

	if r.Deployments == nil {
		return deployments
	}

	// step: extract the deployment id from the result
	for _, deploy := range r.Deployments {
		if id, found := deploy["id"]; found {
			deployment := &DeploymentID{
				Version:      r.Version,
				DeploymentID: id,
			}
			deployments = append(deployments, deployment)
		}
	}

	return deployments
}

// 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(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 {
		portIndex, err = r.Container.ServicePortIndex(port)
		if err != nil {
			return nil, err
		}
	}
	health := NewDefaultHealthCheck()
	health.IntervalSeconds = interval
	*health.Path = path
	*health.PortIndex = portIndex
	// step: add to the checks
	r.AddHealthCheck(*health)

	return r, nil
}

// CheckTCP adds a TCP check to an application; note the port mapping must already exist, or an
// error will thrown
//		port: 		the port the check should, err, check
// 		interval:	the interval in seconds the check should be performed
func (r *Application) CheckTCP(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 {
		portIndex, err = r.Container.ServicePortIndex(port)
		if err != nil {
			return nil, err
		}
	}
	health := NewDefaultHealthCheck()
	health.Protocol = "TCP"
	health.IntervalSeconds = interval
	*health.PortIndex = portIndex
	// step: add to the checks
	r.AddHealthCheck(*health)

	return r, nil
}

// AddUris adds one or more uris to the applications
//		arguments:	the uri(s) you are adding
func (r *Application) AddUris(newUris ...string) *Application {
	if r.Uris == nil {
		r.EmptyUris()
	}

	uris := *r.Uris
	uris = append(uris, newUris...)
	r.Uris = &uris

	return r
}

// EmptyUris explicitly empties uris -- use this if you need to empty
// uris of an application that already has uris set (setting uris to nil will
// keep the current value)
func (r *Application) EmptyUris() *Application {
	r.Uris = &[]string{}

	return r
}

// AddFetchURIs adds one or more fetch URIs to the application.
//		fetchURIs:	the fetch URI(s) to add.
func (r *Application) AddFetchURIs(fetchURIs ...Fetch) *Application {
	if r.Fetch == nil {
		r.EmptyFetchURIs()
	}

	fetch := *r.Fetch
	fetch = append(fetch, fetchURIs...)
	r.Fetch = &fetch

	return r
}

// EmptyFetchURIs explicitly empties fetch URIs -- use this if you need to empty
// fetch URIs of an application that already has fetch URIs set.
// Setting fetch URIs to nil will keep the current value.
func (r *Application) EmptyFetchURIs() *Application {
	r.Fetch = &[]Fetch{}

	return r
}

// SetUpgradeStrategy sets the upgrade strategy.
func (r *Application) SetUpgradeStrategy(us UpgradeStrategy) *Application {
	r.UpgradeStrategy = &us
	return r
}

// EmptyUpgradeStrategy explicitly empties the upgrade strategy -- use this if
// you need to empty the upgrade strategy of an application that already has
// the upgrade strategy set (setting it to nil will keep the current value).
func (r *Application) EmptyUpgradeStrategy() *Application {
	r.UpgradeStrategy = &UpgradeStrategy{}
	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, "", "  ")
	if err != nil {
		return fmt.Sprintf(`{"error": "error decoding type into json: %s"}`, err)
	}

	return string(s)
}

// Applications retrieves an array of all the applications which are running in marathon
func (r *marathonClient) Applications(v url.Values) (*Applications, error) {
	query := v.Encode()
	if query != "" {
		query = "?" + query
	}

	applications := new(Applications)
	err := r.apiGet(marathonAPIApps+query, nil, applications)
	if err != nil {
		return nil, err
	}

	return applications, nil
}

// ListApplications retrieves an array of the application names currently running in marathon
func (r *marathonClient) ListApplications(v url.Values) ([]string, error) {
	applications, err := r.Applications(v)
	if err != nil {
		return nil, err
	}
	var list []string
	for _, application := range applications.Apps {
		list = append(list, application.ID)
	}

	return list, nil
}

// HasApplicationVersion checks to see if the application version exists in Marathon
// 		name: 		the id used to identify the application
//		version: 	the version (normally a timestamp) your looking for
func (r *marathonClient) HasApplicationVersion(name, version string) (bool, error) {
	id := trimRootPath(name)
	versions, err := r.ApplicationVersions(id)
	if err != nil {
		return false, err
	}

	return contains(versions.Versions, version), nil
}

// 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) {
	path := fmt.Sprintf("%s/versions", buildPath(name))
	versions := new(ApplicationVersions)
	if err := r.apiGet(path, nil, versions); err != nil {
		return nil, err
	}
	return versions, nil
}

// SetApplicationVersion changes the version of the application
// 		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) {
	path := buildPath(name)
	deploymentID := new(DeploymentID)
	if err := r.apiPut(path, version, deploymentID); err != nil {
		return nil, err
	}

	return deploymentID, nil
}

// Application retrieves the application configuration from marathon
// 		name: 		the id used to identify the application
func (r *marathonClient) Application(name string) (*Application, error) {
	var wrapper struct {
		Application *Application `json:"app"`
	}

	if err := r.apiGet(buildPath(name), nil, &wrapper); err != nil {
		return nil, err
	}

	return wrapper.Application, nil
}

// ApplicationBy retrieves the application configuration from marathon
// 		name: 		the id used to identify the application
//		opts:		GetAppOpts request payload
func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Application, error) {
	path, err := addOptions(buildPath(name), opts)
	if err != nil {
		return nil, err
	}
	var wrapper struct {
		Application *Application `json:"app"`
	}

	if err := r.apiGet(path, nil, &wrapper); err != nil {
		return nil, err
	}

	return wrapper.Application, nil
}

// ApplicationByVersion retrieves the application configuration from marathon
// 		name: 		the id used to identify the application
// 		version:  the version of the configuration you would like to receive
func (r *marathonClient) ApplicationByVersion(name, version string) (*Application, error) {
	app := new(Application)

	path := fmt.Sprintf("%s/versions/%s", buildPath(name), version)
	if err := r.apiGet(path, nil, app); err != nil {
		return nil, err
	}

	return app, nil
}

// ApplicationOK validates that the application, or more appropriately it's tasks have passed all the health checks.
// If no health checks exist, we simply return true
// 		name: 		the id used to identify the application
func (r *marathonClient) ApplicationOK(name string) (bool, error) {
	// step: get the application
	application, err := r.Application(name)
	if err != nil {
		return false, err
	}

	// step: check if all the tasks are running?
	if !application.AllTaskRunning() {
		return false, nil
	}

	// step: if the application has not health checks, just return true
	if application.HealthChecks == nil || len(*application.HealthChecks) == 0 {
		return true, nil
	}

	// step: iterate the application checks and look for false
	for _, task := range application.Tasks {
		// Health check results may not be available immediately. Assume
		// non-healthiness if they are missing for any task.
		if task.HealthCheckResults == nil {
			return false, nil
		}

		for _, check := range task.HealthCheckResults {
			//When a task is flapping in Marathon, this is sometimes nil
			if check == nil || !check.Alive {
				return false, nil
			}
		}
	}

	return true, nil
}

// ApplicationDeployments retrieves an array of Deployment IDs for an application
//       name:       the id used to identify the application
func (r *marathonClient) ApplicationDeployments(name string) ([]*DeploymentID, error) {
	application, err := r.Application(name)
	if err != nil {
		return nil, err
	}

	return application.DeploymentIDs(), nil
}

// CreateApplication creates a new application in Marathon
// 		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 {
		return nil, err
	}

	return result, nil
}

// WaitOnApplication waits for an application to be deployed
//		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 {
	return r.wait(name, timeout, r.appExistAndRunning)
}

func (r *marathonClient) appExistAndRunning(name string) bool {
	app, err := r.Application(name)
	if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
		return false
	}
	if err == nil && app.AllTaskRunning() {
		return true
	}
	return false
}

// DeleteApplication deletes an application from marathon
// 		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) {
	path := buildPathWithForceParam(name, force)
	// step: check of the application already exists
	deployID := new(DeploymentID)
	if err := r.apiDelete(path, nil, deployID); err != nil {
		return nil, err
	}

	return deployID, nil
}

// RestartApplication performs a rolling restart of marathon application
// 		name: 		the id used to identify the application
func (r *marathonClient) RestartApplication(name string, force bool) (*DeploymentID, error) {
	deployment := new(DeploymentID)
	var options struct{}
	path := buildPathWithForceParam(fmt.Sprintf("%s/restart", name), force)
	if err := r.ApiPost(path, &options, deployment); err != nil {
		return nil, err
	}

	return deployment, nil
}

// ScaleApplicationInstances changes the number of instance an application is running
// 		name: 		the id used to identify the application
// 		instances:	the number of instances you wish to change to
//    force: used to force the scale operation in case of blocked deployment
func (r *marathonClient) ScaleApplicationInstances(name string, instances int, force bool) (*DeploymentID, error) {
	changes := new(Application)
	changes.ID = validateID(name)
	changes.Instances = &instances
	path := buildPathWithForceParam(name, force)
	deployID := new(DeploymentID)
	if err := r.apiPut(path, changes, deployID); err != nil {
		return nil, err
	}

	return deployID, nil
}

// UpdateApplication updates an application in Marathon
// 		application:		the structure holding the application configuration
func (r *marathonClient) UpdateApplication(application *Application, force bool) (*DeploymentID, error) {
	result := new(DeploymentID)
	path := buildPathWithForceParam(application.ID, force)
	if err := r.apiPut(path, application, result); err != nil {
		return nil, err
	}
	return result, nil
}

func buildPathWithForceParam(rootPath string, force bool) string {
	path := buildPath(rootPath)
	if force {
		path += "?force=true"
	}
	return path
}

func buildPath(path string) string {
	return fmt.Sprintf("%s/%s", marathonAPIApps, trimRootPath(path))
}

// EmptyLabels explicitly empties labels -- use this if you need to empty
// labels of an application that already has IP per task with labels defined
func (i *IPAddressPerTask) EmptyLabels() *IPAddressPerTask {
	i.Labels = &map[string]string{}
	return i
}

// AddLabel adds a label to an IPAddressPerTask
//    name: The label name
//   value: The label value
func (i *IPAddressPerTask) AddLabel(name, value string) *IPAddressPerTask {
	if i.Labels == nil {
		i.EmptyLabels()
	}
	(*i.Labels)[name] = value
	return i
}

// EmptyGroups explicitly empties groups -- use this if you need to empty
// groups of an application that already has IP per task with groups defined
func (i *IPAddressPerTask) EmptyGroups() *IPAddressPerTask {
	i.Groups = &[]string{}
	return i
}

// AddGroup adds a group to an IPAddressPerTask
//  group: The group name
func (i *IPAddressPerTask) AddGroup(group string) *IPAddressPerTask {
	if i.Groups == nil {
		i.EmptyGroups()
	}

	groups := *i.Groups
	groups = append(groups, group)
	i.Groups = &groups

	return i
}

// SetDiscovery define the discovery to an IPAddressPerTask
//  discovery: The discovery struct
func (i *IPAddressPerTask) SetDiscovery(discovery Discovery) *IPAddressPerTask {
	i.Discovery = &discovery
	return i
}

// EmptyPorts explicitly empties discovey port -- use this if you need to empty
// discovey port of an application that already has IP per task with discovey ports
// defined
func (d *Discovery) EmptyPorts() *Discovery {
	d.Ports = &[]Port{}
	return d
}

// AddPort adds a port to the discovery info of a IP per task applicable
//   port: The discovery port
func (d *Discovery) AddPort(port Port) *Discovery {
	if d.Ports == nil {
		d.EmptyPorts()
	}
	ports := *d.Ports
	ports = append(ports, port)
	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
}


================================================
FILE: application_marshalling.go
================================================
/*
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)
}


================================================
FILE: application_marshalling_test.go
================================================
/*
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)
	}
}


================================================
FILE: application_test.go
================================================
/*
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.
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"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestApplicationDependsOn(t *testing.T) {
	app := NewDockerApplication()
	app.DependsOn("fake-app")
	app.DependsOn("fake-app1", "fake-app2")
	assert.Equal(t, 3, len(app.Dependencies))
}

func TestApplicationMemory(t *testing.T) {
	app := NewDockerApplication()
	app.Memory(50.0)
	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)
	app.Count(1)
	assert.Equal(t, 1, *app.Instances)
}

func TestApplicationStorage(t *testing.T) {
	app := NewDockerApplication()
	assert.Nil(t, app.Disk)
	app.Storage(0.10)
	assert.Equal(t, 0.10, *app.Disk)
}

func TestApplicationAllTaskRunning(t *testing.T) {
	app := NewDockerApplication()

	app.Instances = nil
	app.Tasks = nil

	assert.True(t, app.AllTaskRunning())

	var cnt int
	app.Instances = &cnt

	cnt = 0
	assert.True(t, app.AllTaskRunning())

	cnt = 1
	assert.False(t, app.AllTaskRunning())

	app.Tasks = []*Task{}
	app.TasksRunning = 1
	assert.True(t, app.AllTaskRunning())

	cnt = 2
	app.TasksRunning = 1
	assert.False(t, app.AllTaskRunning())
}

func TestApplicationName(t *testing.T) {
	app := NewDockerApplication()
	assert.Equal(t, "", app.ID)
	app.Name(fakeAppName)
	assert.Equal(t, fakeAppName, app.ID)
}

func TestApplicationCommand(t *testing.T) {
	app := NewDockerApplication()
	assert.Equal(t, "", app.ID)
	app.Command("format C:")
	assert.Equal(t, "format C:", *app.Cmd)
}

func TestApplicationCPU(t *testing.T) {
	app := NewDockerApplication()
	assert.Equal(t, 0.0, app.CPUs)
	app.CPU(0.1)
	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)
	app.AddArgs("-p").AddArgs("option", "-v")
	assert.Equal(t, 3, len(*app.Args))
	assert.Equal(t, "-p", (*app.Args)[0])
	assert.Equal(t, "option", (*app.Args)[1])
	assert.Equal(t, "-v", (*app.Args)[2])

	app.EmptyArgs()
	assert.NotNil(t, app.Args)
	assert.Equal(t, 0, len(*app.Args))
}

func ExampleApplication_AddConstraint() {
	app := NewDockerApplication()

	// add two constraints
	app.AddConstraint("hostname", "UNIQUE").
		AddConstraint("rack_id", "CLUSTER", "rack-1")
}

func TestApplicationConstraints(t *testing.T) {
	app := NewDockerApplication()
	assert.Nil(t, app.Constraints)
	app.AddConstraint("hostname", "UNIQUE").
		AddConstraint("rack_id", "CLUSTER", "rack-1")

	assert.Equal(t, 2, len(*app.Constraints))
	assert.Equal(t, []string{"hostname", "UNIQUE"}, (*app.Constraints)[0])
	assert.Equal(t, []string{"rack_id", "CLUSTER", "rack-1"}, (*app.Constraints)[1])

	app.EmptyConstraints()
	assert.NotNil(t, app.Constraints)
	assert.Equal(t, 0, len(*app.Constraints))
}

func TestApplicationLabels(t *testing.T) {
	app := NewDockerApplication()
	assert.Nil(t, app.Labels)

	app.AddLabel("hello", "world").AddLabel("foo", "bar")
	assert.Equal(t, 2, len(*app.Labels))
	assert.Equal(t, "world", (*app.Labels)["hello"])
	assert.Equal(t, "bar", (*app.Labels)["foo"])

	app.EmptyLabels()
	assert.NotNil(t, app.Labels)
	assert.Equal(t, 0, len(*app.Labels))
}

func TestApplicationEnvs(t *testing.T) {
	app := NewDockerApplication()
	assert.Nil(t, app.Env)

	app.AddEnv("hello", "world").AddEnv("foo", "bar")
	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)

	app.SetExecutor("executor")
	assert.Equal(t, "executor", *app.Executor)

	app.SetExecutor("")
	assert.Equal(t, "", *app.Executor)
}

func TestApplicationHealthChecks(t *testing.T) {
	app := NewDockerApplication()
	assert.Nil(t, app.HealthChecks)
	hc1 := NewDefaultHealthCheck()
	hc2 := NewDefaultHealthCheck()
	app.AddHealthCheck(*hc1).AddHealthCheck(*hc2)

	assert.Equal(t, 2, len(*app.HealthChecks))
	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)
	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, *pd1, (*app.PortDefinitions)[0])
	assert.Equal(t, 1, len(*(*app.PortDefinitions)[0].Labels))
	assert.Equal(t, *pd2, (*app.PortDefinitions)[1])
	assert.Nil(t, (*app.PortDefinitions)[1].Labels)

	(*app.PortDefinitions)[0].EmptyLabels()
	assert.NotNil(t, (*app.PortDefinitions)[0].Labels)
	assert.Equal(t, 0, len(*(*app.PortDefinitions)[0].Labels))

	app.EmptyPortDefinitions()
	assert.NotNil(t, app.PortDefinitions)
	assert.Equal(t, 0, len(*app.PortDefinitions))
}

func TestHasHealthChecks(t *testing.T) {
	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) {
	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) {
	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) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	application := NewDockerApplication()
	application.Name(fakeAppName)
	app, err := endpoint.Client.CreateApplication(application)
	assert.NoError(t, err)
	assert.NotNil(t, app)
	assert.Equal(t, application.ID, fakeAppName)
	assert.Equal(t, app.Deployments[0]["id"], "f44fd4fc-4330-4600-a68b-99c7bd33014a")
}

func TestUpdateApplication(t *testing.T) {
	for _, force := range []bool{false, true} {
		endpoint := newFakeMarathonEndpoint(t, nil)
		defer endpoint.Close()

		application := NewDockerApplication()
		application.Name(fakeAppName)
		id, err := endpoint.Client.UpdateApplication(application, force)
		assert.NoError(t, err)
		assert.Equal(t, id.DeploymentID, "83b215a6-4e26-4e44-9333-5c385eda6438")
		assert.Equal(t, id.Version, "2014-08-26T07:37:50.462Z")
	}
}

func TestApplications(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	applications, err := endpoint.Client.Applications(nil)
	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")
	applications, err = endpoint.Client.Applications(v)
	assert.NoError(t, err)
	assert.NotNil(t, applications)
	assert.Equal(t, len(applications.Apps), 1)
}

func TestApplicationsEmbedTaskStats(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	v := url.Values{}
	v.Set("embed", "apps.taskStats")
	applications, err := endpoint.Client.Applications(v)
	assert.NoError(t, err)
	assert.NotNil(t, applications)
	assert.Equal(t, len(applications.Apps), 1)
	assert.NotNil(t, applications.Apps[0].TaskStats)
	assert.Equal(t, applications.Apps[0].TaskStats["startedAfterLastScaling"].Stats.Counts["healthy"], 1)
	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()

	applications, err := endpoint.Client.ListApplications(nil)
	assert.NoError(t, err)
	assert.NotNil(t, applications)
	assert.Equal(t, len(applications), 2)
	assert.Equal(t, applications[0], fakeAppName)
	assert.Equal(t, applications[1], fakeAppNameBroken)
}

func TestApplicationVersions(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	versions, err := endpoint.Client.ApplicationVersions(fakeAppName)
	assert.NoError(t, err)
	assert.NotNil(t, versions)
	assert.NotNil(t, versions.Versions)
	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 */
	_, err = endpoint.Client.ApplicationVersions("/not/there")
	assert.Error(t, err)
}

func TestRestartApplication(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	id, err := endpoint.Client.RestartApplication(fakeAppName, false)
	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.RestartApplication("/not/there", false)
	assert.Error(t, err)
	assert.Nil(t, id)
}

func TestApplicationUris(t *testing.T) {
	app := NewDockerApplication()
	assert.Nil(t, app.Uris)
	app.AddUris("file://uri1.tar.gz").AddUris("file://uri2.tar.gz", "file://uri3.tar.gz")
	assert.Equal(t, 3, len(*app.Uris))
	assert.Equal(t, "file://uri1.tar.gz", (*app.Uris)[0])
	assert.Equal(t, "file://uri2.tar.gz", (*app.Uris)[1])
	assert.Equal(t, "file://uri3.tar.gz", (*app.Uris)[2])

	app.EmptyUris()
	assert.NotNil(t, app.Uris)
	assert.Equal(t, 0, len(*app.Uris))
}

func TestApplicationFetchURIs(t *testing.T) {
	app := NewDockerApplication()
	assert.Nil(t, app.Fetch)
	app.AddFetchURIs(Fetch{URI: "file://uri1.tar.gz"}).
		AddFetchURIs(Fetch{URI: "file://uri2.tar.gz"}, Fetch{URI: "file://uri3.tar.gz"})
	assert.Equal(t, 3, len(*app.Fetch))
	assert.Equal(t, Fetch{URI: "file://uri1.tar.gz"}, (*app.Fetch)[0])
	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.EmptyFetchURIs()
	assert.NotNil(t, app.Fetch)
	assert.Equal(t, 0, len(*app.Fetch))
}

func TestSetApplicationVersion(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	deployment, err := endpoint.Client.SetApplicationVersion(fakeAppName, &ApplicationVersion{Version: "2014-08-26T07:37:50.462Z"})
	assert.NoError(t, err)
	assert.NotNil(t, deployment)
	assert.NotNil(t, deployment.Version)
	assert.NotNil(t, deployment.DeploymentID)
	assert.Equal(t, deployment.Version, "2014-08-26T07:37:50.462Z")
	assert.Equal(t, deployment.DeploymentID, "83b215a6-4e26-4e44-9333-5c385eda6438")
	_, err = endpoint.Client.SetApplicationVersion("/not/there", &ApplicationVersion{Version: "2014-04-04T06:25:31.399Z"})
	assert.Error(t, err)
}

func TestHasApplicationVersion(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	found, err := endpoint.Client.HasApplicationVersion(fakeAppName, "2014-04-04T06:25:31.399Z")
	assert.NoError(t, err)
	assert.True(t, found)
	found, err = endpoint.Client.HasApplicationVersion(fakeAppName, "###2015-04-04T06:25:31.399Z")
	assert.NoError(t, err)
	assert.False(t, found)
}

func TestDeleteApplication(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	for _, force := range []bool{false, true} {
		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)
		_, err = endpoint.Client.DeleteApplication("no_such_app", force)
		assert.Error(t, err)
	}
}

func TestApplicationOK(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	ok, err := endpoint.Client.ApplicationOK(fakeAppName)
	assert.NoError(t, err)
	assert.True(t, ok)
	ok, err = endpoint.Client.ApplicationOK(fakeAppNameBroken)
	assert.NoError(t, err)
	assert.False(t, ok)
	ok, err = endpoint.Client.ApplicationOK(fakeAppNameUnhealthy)
	assert.NoError(t, err)
	assert.False(t, ok)
}

func verifyApplication(application *Application, t *testing.T) {
	assert.NotNil(t, application)
	assert.Equal(t, application.ID, fakeAppName)
	assert.NotNil(t, application.HealthChecks)
	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) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	application, err := endpoint.Client.Application(fakeAppName)
	assert.NoError(t, err)
	verifyApplication(application, t)

	_, err = endpoint.Client.Application("no_such_app")
	assert.Error(t, err)
	apiErr, ok := err.(*APIError)
	assert.True(t, ok)
	assert.Equal(t, ErrCodeNotFound, apiErr.ErrCode)

	config := NewDefaultConfig()
	config.URL = "http://non-existing-marathon-host.local:5555"
	// Reduce timeout to speed up test execution time.
	config.HTTPClient = &http.Client{
		Timeout: 100 * time.Millisecond,
	}
	endpoint = newFakeMarathonEndpoint(t, &configContainer{
		client: &config,
	})
	defer endpoint.Close()

	_, err = endpoint.Client.Application(fakeAppName)
	assert.Error(t, err)
	_, ok = err.(*APIError)
	assert.False(t, ok)
}

func TestApplicationConfiguration(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	application, err := endpoint.Client.ApplicationByVersion(fakeAppName, "2014-09-12T23:28:21.737Z")
	assert.NoError(t, err)
	verifyApplication(application, t)

	_, err = endpoint.Client.ApplicationByVersion(fakeAppName, "no_such_version")
	assert.Error(t, err)
	apiErr, ok := err.(*APIError)
	assert.True(t, ok)
	assert.Equal(t, ErrCodeNotFound, apiErr.ErrCode)

	_, err = endpoint.Client.ApplicationByVersion("no_such_app", "latest")
	assert.Error(t, err)
	apiErr, ok = err.(*APIError)
	assert.True(t, ok)
	assert.Equal(t, ErrCodeNotFound, apiErr.ErrCode)
}

func TestWaitOnApplication(t *testing.T) {
	waitTime := 100 * time.Millisecond

	tests := []struct {
		desc          string
		timeout       time.Duration
		appName       string
		testScope     string
		shouldSucceed bool
	}{
		{
			desc:          "initially existing app",
			timeout:       0,
			appName:       fakeAppName,
			shouldSucceed: true,
		},

		{
			desc:          "delayed existing app | timeout > ticker",
			timeout:       200 * time.Millisecond,
			appName:       fakeAppName,
			testScope:     "wait-on-app",
			shouldSucceed: true,
		},

		{
			desc:          "delayed existing app | timeout < ticker",
			timeout:       50 * time.Millisecond,
			appName:       fakeAppName,
			testScope:     "wait-on-app",
			shouldSucceed: false,
		},
		{
			desc:          "missing app | timeout > ticker",
			timeout:       200 * time.Millisecond,
			appName:       "no_such_app",
			shouldSucceed: false,
		},
		{
			desc:          "missing app | timeout < ticker",
			timeout:       50 * time.Millisecond,
			appName:       "no_such_app",
			shouldSucceed: false,
		},
	}

	for _, test := range tests {
		defaultConfig := NewDefaultConfig()
		defaultConfig.PollingWaitTime = waitTime
		configs := &configContainer{
			client: &defaultConfig,
			server: &serverConfig{
				scope: test.testScope,
			},
		}

		endpoint := newFakeMarathonEndpoint(t, configs)
		defer endpoint.Close()

		errCh := make(chan error)
		go func() {
			errCh <- endpoint.Client.WaitOnApplication(test.appName, test.timeout)
		}()

		select {
		case <-time.After(400 * time.Millisecond):
			assert.Fail(t, fmt.Sprintf("%s: WaitOnApplication did not complete in time", test.desc))
		case err := <-errCh:
			if test.shouldSucceed {
				assert.NoError(t, err, test.desc)
			} else {
				assert.IsType(t, err, ErrTimeoutError, test.desc)
			}
		}
	}
}

func TestAppExistAndRunning(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()
	client := endpoint.Client.(*marathonClient)
	assert.True(t, client.appExistAndRunning(fakeAppName))
	assert.False(t, client.appExistAndRunning("no_such_app"))
}

func TestSetIPPerTask(t *testing.T) {
	app := Application{}
	app.Ports = append(app.Ports, 10)
	app.AddPortDefinition(PortDefinition{})
	assert.Nil(t, app.IPAddressPerTask)
	assert.Equal(t, 1, len(app.Ports))
	assert.Equal(t, 1, len(*app.PortDefinitions))

	app.SetIPAddressPerTask(IPAddressPerTask{})
	assert.NotNil(t, app.IPAddressPerTask)
	assert.Equal(t, 0, len(app.Ports))
	assert.Equal(t, 0, len(*app.PortDefinitions))
}

func TestIPAddressPerTask(t *testing.T) {
	ipPerTask := IPAddressPerTask{}
	assert.Nil(t, ipPerTask.Groups)
	assert.Nil(t, ipPerTask.Labels)
	assert.Nil(t, ipPerTask.Discovery)

	ipPerTask.
		AddGroup("label").
		AddLabel("key", "value").
		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)

	ipPerTask.EmptyGroups()
	assert.Equal(t, 0, len(*ipPerTask.Groups))

	ipPerTask.EmptyLabels()
	assert.Equal(t, 0, len(*ipPerTask.Labels))
}

func TestIPAddressPerTaskDiscovery(t *testing.T) {
	disc := Discovery{}
	assert.Nil(t, disc.Ports)

	disc.AddPort(Port{})
	assert.NotNil(t, disc.Ports)
	assert.Equal(t, 1, len(*disc.Ports))

	disc.EmptyPorts()
	assert.NotNil(t, disc.Ports)
	assert.Equal(t, 0, len(*disc.Ports))

}

func TestUpgradeStrategy(t *testing.T) {
	app := Application{}
	assert.Nil(t, app.UpgradeStrategy)
	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
	assert.NotNil(t, us)
	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)
}


================================================
FILE: client.go
================================================
/*
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.
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 (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"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
	ListApplications(url.Values) ([]string, error)
	// a list of application versions
	ApplicationVersions(name string) (*ApplicationVersions, error)
	// check a application version exists
	HasApplicationVersion(name, version string) (bool, error)
	// change an application to a different version
	SetApplicationVersion(name string, version *ApplicationVersion) (*DeploymentID, error)
	// check if an application is ok
	ApplicationOK(name string) (bool, error)
	// create an application in marathon
	CreateApplication(application *Application) (*Application, error)
	// delete an application
	DeleteApplication(name string, force bool) (*DeploymentID, error)
	// update an application in marathon
	UpdateApplication(application *Application, force bool) (*DeploymentID, error)
	// a list of deployments on a application
	ApplicationDeployments(name string) ([]*DeploymentID, error)
	// scale a application
	ScaleApplicationInstances(name string, instances int, force bool) (*DeploymentID, error)
	// restart an application
	RestartApplication(name string, force bool) (*DeploymentID, error)
	// get a list of applications from marathon
	Applications(url.Values) (*Applications, error)
	// get an application by name
	Application(name string) (*Application, error)
	// get an application by options
	ApplicationBy(name string, opts *GetAppOpts) (*Application, error)
	// get an application by name and version
	ApplicationByVersion(name, version string) (*Application, error)
	// 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
	Tasks(application string) (*Tasks, error)
	// get a list of all tasks
	AllTasks(opts *AllTasksOpts) (*Tasks, error)
	// get the endpoints for a service on a application
	TaskEndpoints(name string, port int, healthCheck bool) ([]string, error)
	// kill all the tasks for any application
	KillApplicationTasks(applicationID string, opts *KillApplicationTasksOpts) (*Tasks, error)
	// kill a single task
	KillTask(taskID string, opts *KillTaskOpts) (*Task, error)
	// kill the given array of tasks
	KillTasks(taskIDs []string, opts *KillTaskOpts) error

	// --- GROUPS ---

	// list all the groups in the system
	Groups() (*Groups, error)
	// retrieve a specific group from marathon
	Group(name string) (*Group, error)
	// list all groups in marathon by options
	GroupsBy(opts *GetGroupOpts) (*Groups, error)
	// retrieve a specific group from marathon by options
	GroupBy(name string, opts *GetGroupOpts) (*Group, error)
	// create a group deployment
	CreateGroup(group *Group) error
	// delete a group
	DeleteGroup(name string, force bool) (*DeploymentID, error)
	// update a groups
	UpdateGroup(id string, group *Group, force bool) (*DeploymentID, error)
	// check if a group exists
	HasGroup(name string) (bool, error)
	// wait for an group to be deployed
	WaitOnGroup(name string, timeout time.Duration) error

	// --- DEPLOYMENTS ---

	// get a list of the deployments
	Deployments() ([]*Deployment, error)
	// delete a deployment
	DeleteDeployment(id string, force bool) (*DeploymentID, error)
	// check to see if a deployment exists
	HasDeployment(id string) (bool, error)
	// wait of a deployment to finish
	WaitOnDeployment(id string, timeout time.Duration) error

	// --- SUBSCRIPTIONS ---

	// a list of current subscriptions
	Subscriptions() (*Subscriptions, error)
	// add a events listener
	AddEventsListener(filter int) (EventsChannel, error)
	// remove a events listener
	RemoveEventsListener(channel EventsChannel)
	// Subscribe a callback URL
	Subscribe(string) error
	// Unsubscribe a callback URL
	Unsubscribe(string) error

	// --- QUEUE ---
	// get marathon launch queue
	Queue() (*Queue, error)
	// resets task launch delay of the specific application
	DeleteQueueDelay(appID string) error

	// --- MISC ---

	// get the marathon url
	GetMarathonURL() string
	// ping the marathon
	Ping() (bool, error)
	// grab the marathon server info
	Info() (*Info, error)
	// retrieve the leader info
	Leader() (string, error)
	// cause the current leader to abdicate
	AbdicateLeader() (string, error)
}

var (
	// 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.
type EventsChannelContext struct {
	filter     int
	done       chan struct{}
	completion *sync.WaitGroup
}

type marathonClient struct {
	sync.RWMutex
	// the configuration for the client
	config Config
	// the flag used to prevent multiple SSE subscriptions
	subscribedToSSE bool
	// the ip address of the client
	ipAddress string
	// the http server
	eventsHTTP *http.Server
	// the marathon hosts
	hosts *cluster
	// a map of service you wish to listen to
	listeners map[EventsChannel]EventsChannelContext
	// 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 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 = defaultHTTPClient
	}

	// step: if no polling wait time is set, default to 500 milliseconds.
	if config.PollingWaitTime == 0 {
		config.PollingWaitTime = defaultPollingWaitTime
	}

	// step: setup shared client
	client := &httpClient{config: config}

	// step: create a new cluster
	hosts, err := newCluster(client, config.URL, config.DCOSToken != "")
	if err != nil {
		return nil, err
	}

	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,
		debugLog:  debugLog,
		client:    client,
	}, nil
}

// GetMarathonURL retrieves the marathon url
func (r *marathonClient) GetMarathonURL() string {
	return r.config.URL
}

// Ping pings the current marathon endpoint (note, this is not a ICMP ping, but a rest api call)
func (r *marathonClient) Ping() (bool, error) {
	if err := r.apiGet(marathonAPIPing, nil, nil); err != nil {
		return false, err
	}
	return true, nil
}

func (r *marathonClient) apiHead(path string, result interface{}) error {
	return r.apiCall("HEAD", path, nil, result)
}

func (r *marathonClient) apiGet(path string, post, result interface{}) error {
	return r.apiCall("GET", path, post, result)
}

func (r *marathonClient) apiPut(path string, post, result interface{}) error {
	return r.apiCall("PUT", path, post, result)
}

func (r *marathonClient) ApiPost(path string, post, result interface{}) error {
	return r.apiCall("POST", path, post, result)
}

func (r *marathonClient) apiDelete(path string, post, result interface{}) error {
	return r.apiCall("DELETE", path, post, result)
}

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, member, err := r.buildAPIRequest(method, path, bytes.NewReader(requestBody))
		if err != nil {
			return err
		}

		// 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("apiCall(): request failed on host: %s, error: %s, trying another", member, err)
			continue
		}
		defer response.Body.Close()

		// step: read the response body
		respBody, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return err
		}

		if len(requestBody) > 0 {
			r.debugLog("apiCall(): %v %v %s returned %v %s", request.Method, request.URL.String(), requestBody, response.Status, oneLogLine(respBody))
		} else {
			r.debugLog("apiCall(): %v %v returned %v %s", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
		}

		// step: check for a successful response
		if response.StatusCode >= 200 && response.StatusCode <= 299 {
			if result != nil {
				// 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
		}

		// step: if the member node returns a >= 500 && <= 599 we should try another node?
		if response.StatusCode >= 500 && response.StatusCode <= 599 {
			// step: mark the host as down
			r.hosts.markDown(member)
			r.debugLog("apiCall(): request failed, host: %s, status: %d, trying another", member, response.StatusCode)
			continue
		}

		return NewAPIError(response.StatusCode, respBody)
	}
}

// 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 rc.config.HTTPBasicAuthUser != "" && rc.config.HTTPBasicPassword != "" {
		request.SetBasicAuth(rc.config.HTTPBasicAuthUser, rc.config.HTTPBasicPassword)
	}

	if rc.config.DCOSToken != "" {
		request.Header.Add("Authorization", "token="+rc.config.DCOSToken)
	}

	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
// escapes new line characters.
func oneLogLine(in []byte) []byte {
	return bytes.Replace(oneLogLineRegex.ReplaceAll(in, nil), []byte("\n"), []byte("\\n "), -1)
}


================================================
FILE: client_test.go
================================================
/*
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.
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 (
	"bytes"
	"testing"

	"net/http"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewClient(t *testing.T) {
	config := Config{
		URL: "http://marathon",
	}
	cl, err := NewClient(config)

	if !assert.Nil(t, err) {
		return
	}

	conf := cl.(*marathonClient).config

	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()

	pong, err := endpoint.Client.Ping()
	assert.NoError(t, err)
	assert.True(t, pong)
}

func TestGetMarathonURL(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	assert.Equal(t, endpoint.Client.GetMarathonURL(), endpoint.URL)
}

func TestAPIRequest(t *testing.T) {
	cases := []struct {
		Username       string
		Password       string
		ServerUsername string
		ServerPassword string
		Ok             bool
	}{
		{
			Username:       "should_pass",
			Password:       "",
			ServerUsername: "",
			ServerPassword: "",
			Ok:             true,
		},
		{
			Username:       "bad_username",
			Password:       "",
			ServerUsername: "test",
			ServerPassword: "password",
			Ok:             false,
		},
		{
			Username:       "test",
			Password:       "bad_password",
			ServerUsername: "test",
			ServerPassword: "password",
			Ok:             false,
		},
		{
			Username:       "",
			Password:       "",
			ServerUsername: "test",
			ServerPassword: "password",
			Ok:             false,
		},
		{
			Username:       "test",
			Password:       "password",
			ServerUsername: "test",
			ServerPassword: "password",
			Ok:             true,
		},
	}
	for i, x := range cases {
		var endpoint *endpoint

		config := NewDefaultConfig()
		config.HTTPBasicAuthUser = x.Username
		config.HTTPBasicPassword = x.Password

		endpoint = newFakeMarathonEndpoint(t, &configContainer{
			client: &config,
			server: &serverConfig{
				username: x.ServerUsername,
				password: x.ServerPassword,
			},
		})

		_, err := endpoint.Client.Applications(nil)

		if x.Ok && err != nil {
			t.Errorf("case %d, did not expect an error: %s", i, err)
		}
		if !x.Ok && err == nil {
			t.Errorf("case %d, expected to received an error", i)
		}

		endpoint.Close()
	}
}

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
	b    c
	d\n
	  efgh
	i\r\n
	j\t
	{"json":  "works",
		"f o o": "ba    r"
	}
	`
	assert.Equal(t, `a\n b    c\n d\n\n efgh\n i\r\n\n j\t\n {"json":  "works",\n "f o o": "ba    r"\n }\n `, string(oneLogLine([]byte(in))))
}

func TestAPIRequestDCOS(t *testing.T) {
	cases := []struct {
		DCOSToken       string
		ServerDCOSToken string
		ServerUsername  string
		ServerPassword  string
		Ok              bool
	}{
		{
			DCOSToken:       "should_pass",
			ServerDCOSToken: "should_pass",
			ServerUsername:  "",
			ServerPassword:  "",
			Ok:              true,
		},
		{
			DCOSToken:       "should_pass",
			ServerDCOSToken: "",
			ServerUsername:  "",
			ServerPassword:  "",
			Ok:              true,
		},
		{
			DCOSToken:       "should_not_pass",
			ServerDCOSToken: "different_token",
			ServerUsername:  "",
			ServerPassword:  "",
			Ok:              false,
		},
	}
	for i, x := range cases {
		var endpoint *endpoint

		config := NewDefaultConfig()
		config.DCOSToken = x.DCOSToken

		endpoint = newFakeMarathonEndpoint(t, &configContainer{
			client: &config,
			server: &serverConfig{
				dcosToken: x.ServerDCOSToken,
				username:  x.ServerUsername,
				password:  x.ServerPassword,
			},
		})

		_, err := endpoint.Client.Applications(nil)

		if x.Ok && err != nil {
			t.Errorf("case %d, did not expect an error: %s", i, err)
		}
		if !x.Ok && err == nil {
			t.Errorf("case %d, expected to received an error", i)
		}

		endpoint.Close()
	}
}


================================================
FILE: cluster.go
================================================
/*
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.
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"
	"net/url"
	"strings"
	"sync"
	"time"
)

const (
	memberStatusUp   = 0
	memberStatusDown = 1
)

// the status of a member node
type memberStatus int

// cluster is a collection of marathon nodes
type cluster struct {
	sync.RWMutex
	// a collection of nodes
	members []*member
	// 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
type member struct {
	// the name / ip address of the host
	endpoint string
	// the status of the host
	status memberStatus
}

// newCluster returns a new marathon cluster
func newCluster(client *httpClient, marathonURL string, isDCOS bool) (*cluster, error) {
	// step: extract and basic validate the endpoints
	var members []*member
	var defaultProto string

	for _, endpoint := range strings.Split(marathonURL, ",") {
		// step: check for nothing
		if endpoint == "" {
			return nil, newInvalidEndpointError("endpoint is blank")
		}
		// step: prepend scheme if missing on (non-initial) endpoint.
		if !strings.HasPrefix(endpoint, "http://") && !strings.HasPrefix(endpoint, "https://") {
			if defaultProto == "" {
				return nil, newInvalidEndpointError("missing scheme on (first) endpoint")
			}

			endpoint = fmt.Sprintf("%s://%s", defaultProto, endpoint)
		}
		// step: parse the url
		u, err := url.Parse(endpoint)
		if err != nil {
			return nil, newInvalidEndpointError("invalid endpoint '%s': %s", endpoint, err)
		}
		if defaultProto == "" {
			defaultProto = u.Scheme
		}

		// step: check for empty hosts
		if u.Host == "" {
			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,
		healthCheckInterval: 5 * time.Second,
	}, nil
}

// retrieve the current member, i.e. the current endpoint in use
func (c *cluster) getMember() (string, error) {
	c.RLock()
	defer c.RUnlock()
	for _, n := range c.members {
		if n.status == memberStatusUp {
			return n.endpoint, nil
		}
	}

	return "", ErrMarathonDown
}

// markDown marks down the current endpoint
func (c *cluster) markDown(endpoint string) {
	c.Lock()
	defer c.Unlock()
	for _, n := range c.members {
		// step: check if this is the node and it's marked as up - The double  checking on the
		// nodes status ensures the multiple calls don't create multiple checks
		if n.status == memberStatusUp && n.endpoint == endpoint {
			n.status = memberStatusDown
			go c.healthCheckNode(n)
			break
		}
	}
}

// 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
	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
			}
		}
	}
}

// activeMembers returns a list of active members
func (c *cluster) activeMembers() []string {
	return c.membersList(memberStatusUp)
}

// nonActiveMembers returns a list of non-active members in the cluster
func (c *cluster) nonActiveMembers() []string {
	return c.membersList(memberStatusDown)
}

// memberList returns a list of members of a specified status
func (c *cluster) membersList(status memberStatus) []string {
	c.RLock()
	defer c.RUnlock()
	var list []string
	for _, m := range c.members {
		if m.status == status {
			list = append(list, m.endpoint)
		}
	}

	return list
}

// size returns the size of the cluster
func (c *cluster) size() int {
	return len(c.members)
}

// String returns a string representation
func (m member) String() string {
	status := "UP"
	if m.status == memberStatusDown {
		status = "DOWN"
	}

	return fmt.Sprintf("member: %s:%s", m.endpoint, status)
}


================================================
FILE: cluster_test.go
================================================
/*
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.
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 TestSize(t *testing.T) {
	cluster, err := newStandardCluster(fakeMarathonURL)
	assert.NoError(t, err)
	assert.Equal(t, cluster.size(), 3)
}

func TestActive(t *testing.T) {
	cluster, err := newStandardCluster(fakeMarathonURL)
	assert.NoError(t, err)
	assert.Equal(t, len(cluster.activeMembers()), 3)
}

func TestNonActive(t *testing.T) {
	cluster, err := newStandardCluster(fakeMarathonURL)
	assert.NoError(t, err)
	assert.Equal(t, len(cluster.nonActiveMembers()), 0)
}

func TestGetMember(t *testing.T) {
	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 := 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])
	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) {
	cs := []struct {
		URL    string
		Expect []string
	}{
		{
			URL:    "http://127.0.0.1",
			Expect: []string{"http://127.0.0.1"},
		},
		{
			URL:    "http://127.0.0.1:8080",
			Expect: []string{"http://127.0.0.1:8080"},
		},
		{
			URL:    "http://127.0.0.1:8080,http://127.0.0.2:8081",
			Expect: []string{"http://127.0.0.1:8080", "http://127.0.0.2:8081"},
		},
		{
			URL:    "https://127.0.0.1:8080,http://127.0.0.2:8081",
			Expect: []string{"https://127.0.0.1:8080", "http://127.0.0.2:8081"},
		},
		{
			URL:    "http://127.0.0.1:8080,127.0.0.2",
			Expect: []string{"http://127.0.0.1:8080", "http://127.0.0.2"},
		},
		{
			URL:    "https://127.0.0.1:8080,127.0.0.2",
			Expect: []string{"https://127.0.0.1:8080", "https://127.0.0.2"},
		},
		{
			URL:    "http://127.0.0.1:8080,127.0.0.2:8080",
			Expect: []string{"http://127.0.0.1:8080", "http://127.0.0.2:8080"},
		},
		{
			URL:    "http://127.0.0.1:8080,https://127.0.0.2",
			Expect: []string{"http://127.0.0.1:8080", "https://127.0.0.2"},
		},
		{
			URL:    "http://127.0.0.1:8080,https://127.0.0.2:8080",
			Expect: []string{"http://127.0.0.1:8080", "https://127.0.0.2:8080"},
		},
		{
			URL:    "http://127.0.0.1:8080/path1,127.0.0.2/path2",
			Expect: []string{"http://127.0.0.1:8080/path1", "http://127.0.0.2/path2"},
		},
	}
	for _, x := range cs {
		c, err := newStandardCluster(x.URL)
		if !assert.NoError(t, err, "URL '%s' should not have thrown an error: %s", x.URL, err) {
			continue
		}
		assert.Equal(t, x.Expect, c.activeMembers(), "URL '%s', expected: %v, got: %s", x.URL, x.Expect, c.activeMembers())
	}
}

func TestInvalidClusterHosts(t *testing.T) {
	for _, invalidHost := range []string{
		"",
		"://",
		"http://",
		"http://,,",
		"http://%42",
		"http://,127.0.0.1:3000,127.0.0.1:3000",
		"http://127.0.0.1:3000,,127.0.0.1:3000",
		"http://127.0.0.1:3000,127.0.0.1:3000,",
		"foo://127.0.0.1:3000",
	} {
		_, 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)
}


================================================
FILE: config.go
================================================
/*
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.
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 (
	"io"
	"io/ioutil"
	"net/http"
	"time"
)

const defaultPollingWaitTime = 500 * time.Millisecond

const defaultDCOSPath = "marathon"

// EventsTransport describes which transport should be used to deliver Marathon events
type EventsTransport int

// Config holds the settings and options for the client
type Config struct {
	// URL is the url for marathon
	URL string
	// EventsTransport is the events transport: EventsTransportCallback or EventsTransportSSE
	EventsTransport EventsTransport
	// EventsPort is the event handler port
	EventsPort int
	// the interface we should be listening on for events
	EventsInterface string
	// HTTPBasicAuthUser is the http basic auth
	HTTPBasicAuthUser string
	// HTTPBasicPassword is the http basic password
	HTTPBasicPassword string
	// CallbackURL custom callback url
	CallbackURL string
	// DCOSToken for DCOS environment, This will override the Authorization header
	DCOSToken string
	// LogOutput the output for debug log messages
	LogOutput io.Writer
	// 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
}

// NewDefaultConfig create a default client config
func NewDefaultConfig() Config {
	return Config{
		URL:             "http://127.0.0.1:8080",
		EventsTransport: EventsTransportCallback,
		EventsPort:      10001,
		EventsInterface: "eth0",
		LogOutput:       ioutil.Discard,
		PollingWaitTime: defaultPollingWaitTime,
	}
}


================================================
FILE: const.go
================================================
/*
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.
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

const (
	defaultEventsURL = "/event"

	/* --- api related constants --- */
	marathonAPIVersion      = "v2"
	marathonAPIEventStream  = marathonAPIVersion + "/events"
	marathonAPISubscription = marathonAPIVersion + "/eventSubscriptions"
	marathonAPIApps         = marathonAPIVersion + "/apps"
	marathonAPIPods         = marathonAPIVersion + "/pods"
	marathonAPITasks        = marathonAPIVersion + "/tasks"
	marathonAPIDeployments  = marathonAPIVersion + "/deployments"
	marathonAPIGroups       = marathonAPIVersion + "/groups"
	marathonAPIQueue        = marathonAPIVersion + "/queue"
	marathonAPIInfo         = marathonAPIVersion + "/info"
	marathonAPILeader       = marathonAPIVersion + "/leader"
	marathonAPIPing         = "ping"
)

const (
	// EventsTransportCallback activates callback events transport
	EventsTransportCallback EventsTransport = 1 << iota

	// EventsTransportSSE activates stream events transport
	EventsTransportSSE
)


================================================
FILE: deployment.go
================================================
/*
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.
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"
	"time"
)

// Deployment is a marathon deployment definition
type Deployment struct {
	ID             string              `json:"id"`
	Version        string              `json:"version"`
	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"`
}

// DeploymentID is the identifier for a application deployment
type DeploymentID struct {
	DeploymentID string `json:"deploymentId"`
	Version      string `json:"version"`
}

// DeploymentStep is a step in the application deployment plan
type DeploymentStep struct {
	Action                string                  `json:"action"`
	App                   string                  `json:"app"`
	ReadinessCheckResults *[]ReadinessCheckResult `json:"readinessCheckResults,omitempty"`
}

// StepActions is a series of deployment steps
type StepActions struct {
	Actions []struct {
		Action string `json:"action"` // 1.1.2 and after
		Type   string `json:"type"`   // 1.1.1 and before
		App    string `json:"app"`
	}
}

// DeploymentPlan is a collection of steps for application deployment
type DeploymentPlan struct {
	ID       string         `json:"id"`
	Version  string         `json:"version"`
	Original *Group         `json:"original"`
	Target   *Group         `json:"target"`
	Steps    []*StepActions `json:"steps"`
}

// Deployments retrieves a list of current deployments
func (r *marathonClient) Deployments() ([]*Deployment, error) {
	var deployments []*Deployment
	err := r.apiGet(marathonAPIDeployments, nil, &deployments)
	if err != nil {
		return nil, err
	}
	// Allows loading of deployment steps from the Marathon v1.X API
	// Implements a fix for issue https://github.com/gambol99/go-marathon/issues/153
	for _, deployment := range deployments {
		// Unmarshal pre-v1.X step
		if err := json.Unmarshal(deployment.XXStepsRaw, &deployment.Steps); err != nil {
			deployment.Steps = make([][]*DeploymentStep, 0)
			var steps []*StepActions
			// Unmarshal v1.X Marathon step
			if err := json.Unmarshal(deployment.XXStepsRaw, &steps); err != nil {
				return nil, err
			}
			for stepIndex, step := range steps {
				deployment.Steps = append(deployment.Steps, make([]*DeploymentStep, len(step.Actions)))
				for actionIndex, action := range step.Actions {
					var stepAction string
					if action.Type != "" {
						stepAction = action.Type
					} else {
						stepAction = action.Action
					}
					deployment.Steps[stepIndex][actionIndex] = &DeploymentStep{
						Action: stepAction,
						App:    action.App,
					}
				}
			}
		}
	}
	return deployments, nil
}

// DeleteDeployment delete a current deployment from marathon
// 	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(path, nil, deployment)

	if err != nil {
		return nil, err
	}

	return deployment, nil
}

// HasDeployment checks to see if a deployment exists
// 	id:		the deployment id you are looking for
func (r *marathonClient) HasDeployment(id string) (bool, error) {
	deployments, err := r.Deployments()
	if err != nil {
		return false, err
	}
	for _, deployment := range deployments {
		if deployment.ID == id {
			return true, nil
		}
	}
	return false, nil
}

// WaitOnDeployment waits on a deployment to finish
//  version:		the version of the application
// 	timeout:		the timeout to wait for the deployment to take, otherwise return an error
func (r *marathonClient) WaitOnDeployment(id string, timeout time.Duration) error {
	if found, err := r.HasDeployment(id); err != nil {
		return err
	} else if !found {
		return nil
	}

	nowTime := time.Now()
	stopTime := nowTime.Add(timeout)
	if timeout <= 0 {
		stopTime = nowTime.Add(time.Duration(900) * time.Second)
	}

	// step: a somewhat naive implementation, but it will work
	for {
		if time.Now().After(stopTime) {
			return ErrTimeoutError
		}
		found, err := r.HasDeployment(id)
		if err != nil {
			return err
		}
		if !found {
			return nil
		}
		time.Sleep(r.config.PollingWaitTime)
	}
}


================================================
FILE: deployment_test.go
================================================
/*
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.
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"
)

func TestDeployments(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)
	defer endpoint.Close()

	deployments, err := endpoint.Client.Deployments()
	require.NoError(t, err)
	require.NotNil(t, deployments)
	require.Equal(t, len(deployments), 1)
	deployment := deployments[0]
	require.NotNil(t, deployment)
	assert.Equal(t, deployment.ID, "867ed450-f6a8-4d33-9b0e-e11c5513990b")
	require.NotNil(t, deployment.Steps)
	assert.Equal(t, len(deployment.Steps), 1)
}

func TestDeploymentsV1(t *testing.T) {
	endpoint := newFakeMarathonEndpoint(t, &configContainer{
		server: &serverConfig{
			scope: "v1.1.1",
		},
	})
	defer endpoint.Close()
	deployments, err := endpoint.Client.Deployments()
	assert.NoError(t, err)
	assert.NotNil(t, deployments)
	assert.Equal(t, len(deployments), 1)
	deployment := deployments[0]
	assert.NotNil(t, deployment)
	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)
	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)
}


================================================
FILE: docker.go
================================================
/*
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.
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 (
	"errors"
	"fmt"
)

// 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"`
	PortMappings *[]PortMapping `json:"portMappings,omitempty"`
}

// PortMapping is the portmapping structure between container and mesos
type PortMapping struct {
	ContainerPort int                `json:"containerPort,omitempty"`
	HostPort      int                `json:"hostPort"`
	Labels        *map[string]string `json:"labels,omitempty"`
	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
type Parameters struct {
	Key   string `json:"key,omitempty"`
	Value string `json:"value,omitempty"`
}

// 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"`
	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
type ExternalVolume struct {
	Name     string             `json:"name,omitempty"`
	Provider string             `json:"provider,omitempty"`
	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"`
	Image          string         `json:"image,omitempty"`
	Network        string         `json:"network,omitempty"`
	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
//		host_path:			the path on the docker host to map
//		container_path:		the path inside the container to map the host volume
//		mode:				the mode to map the container
func (container *Container) Volume(hostPath, containerPath, mode string) *Container {
	if container.Volumes == nil {
		container.EmptyVolumes()
	}

	volumes := *container.Volumes
	volumes = append(volumes, Volume{
		ContainerPath: containerPath,
		HostPath:      hostPath,
		Mode:          mode,
	})

	container.Volumes = &volumes

	return container
}

// EmptyVolumes explicitly empties the volumes -- use this if you need to empty
// volumes of an application that already has volumes set (setting volumes to nil will
// keep the current value)
func (container *Container) EmptyVolumes() *Container {
	container.Volumes = &[]Volume{}
	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)
func (v *Volume) SetExternalVolume(name, provider string) *ExternalVolume {
	ev := &ExternalVolume{
		Name:     name,
		Provider: provider,
	}
	v.External = ev
	return ev
}

// EmptyExternalVolume emptys the external volume definition
func (v *Volume) EmptyExternalVolume() *Volume {
	v.External = &ExternalVolume{}
	return v
}

// AddOption adds an option to an ExternalVolume
//		name:  the name of the option
//		value: value for the option
func (ev *ExternalVolume) AddOption(name, value string) *ExternalVolume {
	if ev.Options == nil {
		ev.EmptyOptions()
	}
	(*ev.Options)[name] = value

	return ev
}

// EmptyOptions explicitly empties the options
func (ev *ExternalVolume) EmptyOptions() *ExternalVolume {
	ev.Options = &map[string]string{}

	return ev
}

// NewDockerContainer creates a default docker container for you
func NewDockerContainer() *Container {
	container := &Container{}
	container.Type = "DOCKER"
	container.Docker = &Docker{}

	return container
}

// SetForcePullImage sets whether the docker image should always be force pulled before
// starting an instance
//		forcePull:			true / false
func (docker *Docker) SetForcePullImage(forcePull bool) *Docker {
	docker.ForcePullImage = &forcePull

	return docker
}

// SetPrivileged sets whether the docker image should be started
// with privilege turned on
//		priv:			true / false
func (docker *Docker) SetPrivileged(priv bool) *Docker {
	docker.Privileged = &priv

	return docker
}

// Container sets the image of the container
//		image:			the image name you are using
func (docker *Docker) Container(image string) *Docker {
	docker.Image = image
	return docker
}

// Bridged sets the networking mode to bridged
func (docker *Docker) Bridged() *Docker {
	docker.Network = "BRIDGE"
	return docker
}

// Host sets the networking mode to host
func (docker *Docker) Host() *Docker {
	docker.Network = "HOST"
	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 {
	for _, port := range ports {
		docker.ExposePort(PortMapping{
			ContainerPort: port,
			HostPort:      0,
			ServicePort:   0,
			Protocol:      "tcp"})
	}
	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 {
	for _, port := range ports {
		docker.ExposePort(PortMapping{
			ContainerPort: port,
			HostPort:      0,
			ServicePort:   0,
			Protocol:      "udp"})
	}
	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 {
		docker.EmptyPortMappings()
	}

	portMappings := *docker.PortMappings
	portMappings = append(portMappings, portMapping)
	docker.PortMappings = &portMappings

	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)
func (docker *Docker) EmptyPortMappings() *Docker {
	docker.PortMappings = &[]PortMapping{}
	return docker
}

// AddLabel adds a label to a PortMapping
//		name:	the name of the label
//		value: value for this label
func (p *PortMapping) AddLabel(name, value string) *PortMapping {
	if p.Labels == nil {
		p.EmptyLabels()
	}
	(*p.Labels)[name] = value

	return p
}

// EmptyLabels explicitly empties the labels -- use this if you need to empty
// the labels of a port mapping that already has labels set (setting labels to
// nil will keep the current value)
func (p *PortMapping) EmptyLabels() *PortMapping {
	p.Labels = &map[string]string{}

	return p
}

// AddParameter adds a parameter to the docker execution line when creating the container
//		key:			the name of the option to add
//		value:		the value of the option
func (docker *Docker) AddParameter(key string, value string) *Docker {
	if docker.Parameters == nil {
		docker.EmptyParameters()
	}

	parameters := *docker.Parameters
	parameters = append(parameters, Parameters{
		Key:   key,
		Value: value})

	docker.Parameters = &parameters

	return docker
}

// EmptyParameters explicitly empties the parameters -- use this if you need to empty
// parameters of an application that already has parameters set (setting parameters to nil will
// keep the current value)
func (docker *Docker) EmptyParameters() *Docker {
	docker.Parameters = &[]Parameters{}
	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) {
	if docker.PortMappings == nil || len(*docker.PortMappings) == 0 {
		return 0, errors.New("The docker does not contain any port mappings to search")
	}

	// step: iterate and find the port
	for index, containerPort := range *docker.PortMappings {
		if containerPort.ContainerPort == port {
			return index, nil
		}
	}

	// step: we didn't find the port in the 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
}


================================================
FILE: docker_test.go
================================================
/*
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 marathon

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func createPortMapping(containerPort int, protocol string) *PortMapping {
	return &PortMapping{
		ContainerPort: containerPort,
		HostPort:      0,
		ServicePort:   0,
		Protocol:      protocol,
	}
}

func TestDockerAddParameter(t *testing.T) {
	docker := NewDockerApplication().Container.Docker
	docker.AddParameter("k1", "v1").AddParameter("k2", "v2")

	assert.Equal(t, 2, len(*docker.Parameters))
	assert.Equal(t, (*docker.Parameters)[0].Key, "k1")
	assert.Equal(t, (*docker.Parameters)[0].Value, "v1")
	assert.Equal(t, (*docker.Parameters)[1].Key, "k2")
	assert.Equal(t, (*docker.Parameters)[1].Value, "v2")

	docker.EmptyParameters()
	assert.NotNil(t, docker.Parameters)
	assert.Equal(t, 0, len(*docker.Parameters))
}
func TestDockerExpose(t *testing.T) {
	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 := []*[]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"), (*portMapping)[0])
		assert.Equal(t, *createPortMapping(80, "tcp"), (*portMapping)[1])
		assert.Equal(t, *createPortMapping(443, "tcp"), (*portMapping)[2])
	}
}

func TestDockerExposeUDP(t *testing.T) {
	apps := []*Application{
		NewDockerApplication(),
		NewDockerApplication(),
	}

	// 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) {
	pm := createPortMapping(80, "tcp")

	pm.AddLabel("hello", "world").AddLabel("foo", "bar")

	assert.Equal(t, 2, len(*pm.Labels))
	assert.Equal(t, "world", (*pm.Labels)["hello"])
	assert.Equal(t, "bar", (*pm.Labels)["foo"])

	pm.EmptyLabels()

	assert.NotNil(t, pm.Labels)
	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

	container.Volume("hp1", "cp1", "RW")
	container.Volume("hp2", "cp2", "R")

	assert.Equal(t, 2, len(*container.Volumes))
	assert.Equal(t, (*container.Volumes)[0].HostPath, "hp1")
	assert.Equal(t, (*container.Volumes)[0].ContainerPath, "cp1")
	assert.Equal(t, (*container.Volumes)[0].Mode, "RW")
	assert.Equal(t, (*container.Volumes)[1].HostPath, "hp2")
	assert.Equal(t, (*container.Volumes)[1].ContainerPath, "cp2")
	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

	container.Volume("", "cp", "RW")
	ev := (*container.Volumes)[0].SetExternalVolume("myVolume", "dvdi")

	ev.AddOption("prop", "pval")
	ev.AddOption("dvdi", "rexray")

	ev1 := (*container.Volumes)[0].External
	assert.Equal(t, ev1.Name, "myVolume")
	assert.Equal(t, ev1.Provider, "dvdi")
	if assert.Equal(t, len(*ev1.Options), 2) {
		assert.Equal(t, (*ev1.Options)["dvdi"], "rexray")
		assert.Equal(t, (*ev1.Options)["prop"], "pval")
	}

	// empty the external volume again
	(*container.Volumes)[0].EmptyExternalVolume()
	ev2 := (*container.Volumes)[0].External
	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)
	}
}


================================================
FILE: error.go
================================================
/*
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 marathon

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
)

const (
	// ErrCodeBadRequest specifies a 400 Bad Request error.
	ErrCodeBadRequest = iota
	// ErrCodeUnauthorized specifies a 401 Unauthorized error.
	ErrCodeUnauthorized
	// ErrCodeForbidden specifies a 403 Forbidden error.
	ErrCodeForbidden
	// ErrCodeNotFound specifies a 404 Not Found error.
	ErrCodeNotFound
	// ErrCodeDuplicateID specifies a PUT 409 Conflict error.
	ErrCodeDuplicateID
	// ErrCodeAppLocked specifies a POST 409 Conflict error.
	ErrCodeAppLocked
	// ErrCodeInvalidBean specifies a 422 UnprocessableEntity error.
	ErrCodeInvalidBean
	// ErrCodeServer specifies a 500+ Server error.
	ErrCodeServer
	// ErrCodeUnknown specifies an unknown error.
	ErrCodeUnknown
	// ErrCodeMethodNotAllowed specifies a 405 Method Not Allowed.
	ErrCodeMethodNotAllowed
)

// InvalidEndpointError indicates a endpoint error in the marathon urls
type InvalidEndpointError struct {
	message string
}

// Error returns the string message
func (e *InvalidEndpointError) Error() string {
	return e.message
}

// newInvalidEndpointError creates a new error
func newInvalidEndpointError(message string, args ...interface{}) error {
	return &InvalidEndpointError{message: fmt.Sprintf(message, args...)}
}

// APIError represents a generic API error.
type APIError struct {
	// ErrCode specifies the nature of the error.
	ErrCode int
	message string
}

func (e *APIError) Error() string {
	return fmt.Sprintf("Marathon API error: %s", e.message)
}

// NewAPIError creates a new APIError instance from the given response code and content.
func NewAPIError(code int, content []byte) error {
	var errDef errorDefinition
	switch {
	case code == http.StatusBadRequest:
		errDef = &badRequestDef{}
	case code == http.StatusUnauthorized:
		errDef = &simpleErrDef{code: ErrCodeUnauthorized}
	case code == http.StatusForbidden:
		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:
		errDef = &unprocessableEntityDef{}
	case code >= http.StatusInternalServerError:
		errDef = &simpleErrDef{code: ErrCodeServer}
	default:
		errDef = &simpleErrDef{code: ErrCodeUnknown}
	}

	return parseContent(errDef, content)
}

type errorDefinition interface {
	message() string
	errCode() int
}

func parseContent(errDef errorDefinition, content []byte) error {
	// If the content cannot be JSON-unmarshalled, we assume that it's not JSON
	// and encode it into the APIError instance as-is.
	errMessage := string(content)
	if err := json.Unmarshal(content, errDef); err == nil {
		errMessage = errDef.message()
	}

	return &APIError{message: errMessage, ErrCode: errDef.errCode()}
}

type simpleErrDef struct {
	Message string `json:"message"`
	code    int
}

func (def *simpleErrDef) message() string {
	return def.Message
}

func (def *simpleErrDef) errCode() int {
	return def.code
}

type detailDescription struct {
	Path   string   `json:"path"`
	Errors []string `json:"errors"`
}

func (d detailDescription) String() string {
	return fmt.Sprintf("path: '%s' errors: %s", d.Path, strings.Join(d.Errors, ", "))
}

type badRequestDef struct {
	Message string              `json:"message"`
	Details []detailDescription `json:"details"`
}

func (def *badRequestDef) message() string {
	var details []string
	for _, detail := range def.Details {
		details = append(details, detail.String())
	}

	return fmt.Sprintf("%s (%s)", def.Message, strings.Join(details, "; "))
}

func (def *badRequestDef) errCode() int {
	return ErrCodeBadRequest
}

type conflictDef struct {
	Message     string `json:"message"`
	Deployments []struct {
		ID string `json:"id"`
	} `json:"deployments"`
}

func (def *conflictDef) message() string {
	if len(def.Deployments) == 0 {
		// 409 Conflict response to "POST /v2/apps".
		return def.Message
	}

	// 409 Conflict response to "PUT /v2/apps/{appId}".
	var ids []string
	for _, deployment := range def.Deployments {
		ids = append(ids, deployment.ID)
	}
	return fmt.Sprintf("%s (locking deployment IDs: %s)", def.Message, strings.Join(ids, ", "))
}

func (def *conflictDef) errCode() int {
	if len(def.Deployments) == 0 {
		return ErrCodeDuplicateID
	}

	return ErrCodeAppLocked
}

type unprocessableEntityDetails []struct {
	// Used in Marathon >= 1.0.0-RC1.
	detailDescription
	// Used in Marathon < 1.0.0-RC1.
	Attribute string `json:"attribute"`
	Error     string `json:"error"`
}

type unprocessableEntityDef struct {
	Message string `json:"message"`
	// Name used in Marathon >= 0.15.0.
	Details unprocessableEntityDetails `json:"details"`
	// Name used in Marathon < 0.15.0.
	Errors unprocessableEntityDetails `json:"errors"`
}

func (def *unprocessableEntityDef) message() string {
	joinDetails := func(details unprocessableEntityDetails) []string {
		var res []string
		for _, detail := range details {
			res = append(res, fmt.Sprintf("attribute '%s': %s", detail.Attribute, detail.Error))
		}
		return res
	}

	var details []string
	switch {
	case len(def.Errors) > 0:
		details = joinDetails(def.Errors)
	case len(def.Details) > 0 && len(def.Details[0].Attribute) > 0:
		details = joinDetails(def.Details)
	default:
		for _, detail := range def.Details {
			details = append(details, detail.detailDescription.String())
		}
	}

	return fmt.Sprintf("%s (%s)", def.Message, strings.Join(details, "; "))
}

func (def *unprocessableEntityDef) errCode() int {
	return ErrCodeInvalidBean
}


================================================
FILE: error_test.go
================================================
/*
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 marathon

import (
	"fmt"
	"net/http"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestErrors(t *testing.T) {
	tests := []struct {
		httpCode   int
		nameSuffix string
		errCode    int
		errText    string
		content    string
	}{
		// 400
		{
			httpCode: http.StatusBadRequest,
			errCode:  ErrCodeBadRequest,
			errText:  "Invalid JSON (path: '/id' errors: error.expected.jsstring, error.something.else; path: '/name' errors: error.not.inventive)",
			content:  content400(),
		},
		// 401
		{
			httpCode: http.StatusUnauthorized,
			errCode:  ErrCodeUnauthorized,
			errText:  "invalid username or password.",
			content:  `{"message": "invalid username or password."}`,
		},
		// 403
		{
			httpCode: http.StatusForbidden,
			errCode:  ErrCodeForbidden,
			errText:  "Not Authorized to perform this action!",
			content:  `{"message": "Not Authorized to perform this action!"}`,
		},
		// 404
		{
			httpCode: http.StatusNotFound,
			errCode:  ErrCodeNotFound,
			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,
			nameSuffix: "POST",
			errCode:    ErrCodeDuplicateID,
			errText:    "An app with id [/existing_app] already exists.",
			content:    `{"message": "An app with id [/existing_app] already exists."}`,
		},
		// 409 PUT
		{
			httpCode:   http.StatusConflict,
			nameSuffix: "PUT",
			errCode:    ErrCodeAppLocked,
			errText:    "App is locked (locking deployment IDs: 97c136bf-5a28-4821-9d94-480d9fbb01c8)",
			content:    `{"message":"App is locked", "deployments": [ { "id": "97c136bf-5a28-4821-9d94-480d9fbb01c8" } ] }`,
		},
		// 422 pre-1.0 "details" key
		{
			httpCode:   422,
			nameSuffix: "pre-1.0 details key",
			errCode:    ErrCodeInvalidBean,
			errText:    "Something is not valid (attribute 'upgradeStrategy.minimumHealthCapacity': is greater than 1; attribute 'foobar': foo does not have enough bar)",
			content:    content422("details"),
		},
		// 422 pre-1.0 "errors" key
		{
			httpCode:   422,
			nameSuffix: "pre-1.0 errors key",
			errCode:    ErrCodeInvalidBean,
			errText:    "Something is not valid (attribute 'upgradeStrategy.minimumHealthCapacity': is greater than 1; attribute 'foobar': foo does not have enough bar)",
			content:    content422("errors"),
		},
		// 422 1.0 "invalid object"
		{
			httpCode:   422,
			nameSuffix: "invalid object",
			errCode:    ErrCodeInvalidBean,
			errText:    "Object is not valid (path: 'upgradeStrategy.minimumHealthCapacity' errors: is greater than 1; path: '/value' errors: service port conflict app /app1, service port conflict app /app2)",
			content:    content422V1(),
		},
		// 499 unknown error
		{
			httpCode:   499,
			nameSuffix: "unknown error",
			errCode:    ErrCodeUnknown,
			errText:    "unknown error",
			content:    `{"message": "unknown error"}`,
		},
		// 500
		{
			httpCode: http.StatusInternalServerError,
			errCode:  ErrCodeServer,
			errText:  "internal server error",
			content:  `{"message": "internal server error"}`,
		},
		// 503 (no JSON)
		{
			httpCode:   http.StatusServiceUnavailable,
			nameSuffix: "no JSON",
			errCode:    ErrCodeServer,
			errText:    "No server is available to handle this request.",
			content:    `No server is available to handle this request.`,
		},
	}

	for _, test := range tests {
		name := fmt.Sprintf("%d", test.httpCode)
		if len(test.nameSuffix) > 0 {
			name = fmt.Sprintf("%s (%s)", name, test.nameSuffix)
		}
		apiErr := NewAPIError(test.httpCode, []byte(test.content))
		gotErrCode := apiErr.(*APIError).ErrCode
		assert.Equal(t, test.errCode, gotErrCode, fmt.Sprintf("HTTP code %s (error code): got %d, want %d", name, gotErrCode, test.errCode))
		pureErrText := strings.TrimPrefix(apiErr.Error(), "Marathon API error: ")
		assert.Equal(t, pureErrText, test.errText, fmt.Sprintf("HTTP code %s (error text)", name))
	}
}

func content400() string {
	return `{
	"message": "Invalid JSON",
	"details": [
		{
			"path": "/id",
			"errors": ["error.expected.jsstring", "error.something.else"]
		},
		{
			"path": "/name",
			"errors": ["error.not.inventive"]
		}
	]
}`
}

func content422(detailsPropKey string) string {
	return fmt.Sprintf(`{
	"message": "Something is not valid",
	"%s": [
		{
			"attribute": "upgradeStrategy.minimumHealthCapacity",
			"error": "is greater than 1"
		},
		{
			"attribute": "foobar",
			"error": "foo does not have enough bar"
		}
	]
}`, detailsPropKey)
}

func content422V1() string {
	return `{
	"message": "Object is not valid",
	"details": [
		{
			"path": "upgradeStrategy.minimumHealthCapacity",
			"errors": ["is greater than 1"]
		},
		{
			"path": "/value",
			"errors": ["service port conflict app /app1", "service port conflict app /app2"]
		}
	]
}`
}


================================================
FILE: events.go
================================================
/*
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.
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"

// EventType is a wrapper for a marathon event
type EventType struct {
	EventType string `json:"eventType"`
}

const (
	// EventIDAPIRequest is the event listener ID for the corresponding event.
	EventIDAPIRequest = 1 << iota
	// EventIDStatusUpdate is the event listener ID for the corresponding event.
	EventIDStatusUpdate
	// EventIDFrameworkMessage is the event listener ID for the corresponding event.
	EventIDFrameworkMessage
	// EventIDSubscription is the event listener ID for the corresponding event.
	EventIDSubscription
	// EventIDUnsubscribed is the event listener ID for the corresponding event.
	EventIDUnsubscribed
	// EventIDStreamAttached is the event listener ID for the corresponding event.
	EventIDStreamAttached
	// EventIDStreamDetached is the event listener ID for the corresponding event.
	EventIDStreamDetached
	// EventIDAddHealthCheck is the event listener ID for the corresponding event.
	EventIDAddHealthCheck
	// EventIDRemoveHealthCheck is the event listener ID for the corresponding event.
	EventIDRemoveHealthCheck
	// EventIDFailedHealthCheck is the event listener ID for the corresponding event.
	EventIDFailedHealthCheck
	// EventIDChangedHealthCheck is the event listener ID for the corresponding event.
	EventIDChangedHealthCheck
	// EventIDGroupChangeSuccess is the event listener ID for the corresponding event.
	EventIDGroupChangeSuccess
	// EventIDGroupChangeFailed is the event listener ID for the corresponding event.
	EventIDGroupChangeFailed
	// EventIDDeploymentSuccess is the event listener ID for the corresponding event.
	EventIDDeploymentSuccess
	// EventIDDeploymentFailed is the event listener ID for the corresponding event.
	EventIDDeploymentFailed
	// EventIDDeploymentInfo is the event listener ID for the corresponding event.
	EventIDDeploymentInfo
	// EventIDDeploymentStepSuccess is the event listener ID for the corresponding event.
	EventIDDeploymentStepSuccess
	// EventIDDeploymentStepFailed is the event listener ID for the corresponding event.
	EventIDDeploymentStepFailed
	// EventIDAppTerminated is the event listener ID for the corresponding event.
	EventIDAppTerminated
	//EventIDApplications comprises all listener IDs for application events.
	EventIDApplications = EventIDStatusUpdate | EventIDChangedHealthCheck | EventIDFailedHealthCheck | EventIDAppTerminated
	//EventIDSubscriptions comprises all listener IDs for subscription events.
	EventIDSubscriptions = EventIDSubscription | EventIDUnsubscribed | EventIDStreamAttached | EventIDStreamDetached
)

var (
	eventTypesMap map[string]int
)

func init() {
	eventTypesMap = map[string]int{
		"api_post_event":              EventIDAPIRequest,
		"status_update_event":         EventIDStatusUpdate,
		"framework_message_event":     EventIDFrameworkMessage,
		"subscribe_event":             EventIDSubscription,
		"unsubscribe_event":           EventIDUnsubscribed,
		"event_stream_attached":       EventIDStreamAttached,
		"event_stream_detached":       EventIDStreamDetached,
		"add_health_check_event":      EventIDAddHealthCheck,
		"remove_health_check_event":   EventIDRemoveHealthCheck,
		"failed_health_check_event":   EventIDFailedHealthCheck,
		"health_status_changed_event": EventIDChangedHealthCheck,
		"group_change_success":        EventIDGroupChangeSuccess,
		"group_change_failed":         EventIDGroupChangeFailed,
		"deployment_success":          EventIDDeploymentSuccess,
		"deployment_failed":           EventIDDeploymentFailed,
		"deployment_info":             EventIDDeploymentInfo,
		"deployment_step_success":     EventIDDeploymentStepSuccess,
		"deployment_step_failure":     EventIDDeploymentStepFailed,
		"app_terminated_event":        EventIDAppTerminated,
	}
}

//
//  Events taken from: https://mesosphere.github.io/marathon/docs/event-bus.html
//

// Event is the definition for a event in marathon
type Event struct {
	ID    int
	Name  string
	Event interface{}
}

func (event *Event) String() string {
	return fmt.Sprintf("type: %s, event: %s", event.Name, event.Event)
}

// EventsChannel is a channel to receive events upon
type EventsChannel chan *Event

/* --- API Request --- */

// EventAPIRequest describes an 'api_post_event' event.
type EventAPIRequest struct {
	EventType     string       `json:"eventType"`
	ClientIP      string       `json:"clientIp"`
	Timestamp     string       `json:"timestamp"`
	URI           string       `json:"uri"`
	AppDefinition *Application `json:"appDefinition"`
}

/* --- Status Update --- */

// EventStatusUpdate describes a 'status_update_event' event.
type EventStatusUpdate struct {
	EventType   string       `json:"eventType"`
	Timestamp   string       `json:"timestamp,omitempty"`
	SlaveID     string       `json:"slaveId,omitempty"`
	TaskID      string       `json:"taskId"`
	TaskStatus  string       `json:"taskStatus"`
	Message     string       `json:"message,omitempty"`
	AppID       string       `json:"appId"`
	Host        string       `json:"host"`
	Ports       []int        `json:"ports,omitempty"`
	IPAddresses []*IPAddress `json:"ipAddresses"`
	Version     string       `json:"version,omitempty"`
}

// EventAppTerminated describes an 'app_terminated_event' event.
type EventAppTerminated struct {
	EventType string `json:"eventType"`
	Timestamp string `json:"timestamp,omitempty"`
	AppID     string `json:"appId"`
}

/* --- Framework Message --- */

// EventFrameworkMessage describes a 'framework_message_event' event.
type EventFrameworkMessage struct {
	EventType  string `json:"eventType"`
	ExecutorID string `json:"executorId"`
	Message    string `json:"message"`
	SlaveID    string `json:"slaveId"`
	Timestamp  string `json:"timestamp"`
}

/* --- Event Subscription --- */

// EventSubscription describes a 'subscribe_event' event.
type EventSubscription struct {
	CallbackURL string `json:"callbackUrl"`
	ClientIP    string `json:"clientIp"`
	EventType   string `json:"eventType"`
	Timestamp   string `json:"timestamp"`
}

// EventUnsubscription describes an 'unsubscribe_event' event.
type EventUnsubscription struct {
	CallbackURL string `json:"callbackUrl"`
	ClientIP    string `json:"clientIp"`
	EventType   string `json:"eventType"`
	Timestamp   string `json:"timestamp"`
}

// EventStreamAttached describes an 'event_stream_attached' event.
type EventStreamAttached struct {
	RemoteAddress string `json:"remoteAddress"`
	EventType     string `json:"eventType"`
	Timestamp     string `json:"timestamp"`
}

// EventStreamDetached describes an 'event_stream_detached' event.
type EventStreamDetached struct {
	RemoteAddress string `json:"remoteAddress"`
	EventType     string `json:"eventType"`
	Timestamp     string `json:"timestamp"`
}

/* --- Health Checks --- */

// EventAddHealthCheck describes an 'add_health_check_event' event.
type EventAddHealthCheck struct {
	AppID       string `json:"appId"`
	EventType   string `json:"eventType"`
	HealthCheck struct {
		GracePeriodSeconds     float64 `json:"gracePeriodSeconds"`
		IntervalSeconds        float64 `json:"intervalSeconds"`
		MaxConsecutiveFailures float64 `json:"maxConsecutiveFailures"`
		Path                   string  `json:"path"`
		PortIndex              float64 `json:"portIndex"`
		Protocol               string  `json:"protocol"`
		TimeoutSeconds         float64 `json:"timeoutSeconds"`
	} `json:"healthCheck"`
	Timestamp string `json:"timestamp"`
}

// EventRemoveHealthCheck describes a 'remove_health_check_event' event.
type EventRemoveHealthCheck struct {
	AppID       string `json:"appId"`
	EventType   string `json:"eventType"`
	HealthCheck struct {
		GracePeriodSeconds     float64 `json:"gracePeriodSeconds"`
		IntervalSeconds        float64 `json:"intervalSeconds"`
		MaxConsecutiveFailures float64 `json:"maxConsecutiveFailures"`
		Path                   string  `json:"path"`
		PortIndex              float64 `json:"portIndex"`
		Protocol               string  `json:"protocol"`
		TimeoutSeconds         float64 `json:"timeoutSeconds"`
	} `json:"healthCheck"`
	Timestamp string `json:"timestamp"`
}

// EventFailedHealthCheck describes a 'failed_health_check_event' event.
type EventFailedHealthCheck struct {
	AppID       string `json:"appId"`
	EventType   string `json:"eventType"`
	HealthCheck struct {
		GracePeriodSeconds     float64 `json:"gracePeriodSeconds"`
		IntervalSeconds        float64 `json:"intervalSeconds"`
		MaxConsecutiveFailures float64 `json:"maxConsecutiveFailures"`
		Path                   string  `json:"path"`
		PortIndex              float64 `json:"portIndex"`
		Protocol               string  `json:"protocol"`
		TimeoutSeconds         float64 `json:"timeoutSeconds"`
	} `json:"healthCheck"`
	Timestamp string `json:"timestamp"`
}

// EventHealthCheckChanged describes a 'health_status_changed_event' event.
type EventHealthCheckChanged struct {
	EventType string `json:"eventType"`
	Timestamp string `json:"timestamp,omitempty"`
	AppID     string `json:"appId"`
	TaskID    string `json:"taskId"`
	Version   string `json:"version,omitempty"`
	Alive     bool   `json:"alive"`
}

/* --- Deployments --- */

// EventGroupChangeSuccess describes a 'group_change_success' event.
type EventGroupChangeSuccess struct {
	EventType string `json:"eventType"`
	GroupID   string `json:"groupId"`
	Timestamp string `json:"timestamp"`
	Version   string `json:"version"`
}

// EventGroupChangeFailed describes a 'group_change_failed' event.
type EventGroupChangeFailed struct {
	EventType string `json:"eventType"`
	GroupID   string `json:"groupId"`
	Timestamp string `json:"timestamp"`
	Version   string `json:"version"`
	Reason    string `json:"reason"`
}

// EventDeploymentSuccess describes a 'deployment_success' event.
type EventDeploymentSuccess struct {
	ID        string          `json:"id"`
	EventType string          `json:"eventType"`
	Timestamp string          `json:"timestamp"`
	Plan      *DeploymentPlan `json:"plan"`
}

// EventDeploymentFailed describes a 'deployment_failed' event.
type EventDeploymentFailed struct {
	ID        string `json:"id"`
	EventType string `json:"eventType"`
	Timestamp string `json:"timestamp"`
}

// EventDeploymentInfo describes a 'deployment_info' event.
type EventDeploymentInfo struct {
	EventType   string          `json:"eventType"`
	CurrentStep *StepActions    `json:"currentStep"`
	Timestamp   string          `json:"timestamp"`
	Plan        *DeploymentPlan `json:"plan"`
}

// EventDeploymentStepSuccess describes a 'deployment_step_success' event.
type EventDeploymentStepSuccess struct {
	EventType   string          `json:"eventType"`
	CurrentStep *StepActions    `json:"currentStep"`
	Timestamp   string          `json:"timestamp"`
	Plan        *DeploymentPlan `json:"plan"`
}

// EventDeploymentStepFailure describes a 'deployment_step_failure' event.
type EventDeploymentStepFailure struct {
	EventType   string          `json:"eventType"`
	CurrentStep *StepActions    `json:"currentStep"`
	Timestamp   string          `json:"timestamp"`
	Plan        *DeploymentPlan `json:"plan"`
}

// GetEvent returns allocated empty event object which corresponds to provided event type
//		eventType:			the type of Marathon event
func GetEvent(eventType string) (*Event, error) {
	// step: check it's supported
	id, found := eventTypesMap[eventType]
	if found {
		event := new(Event)
		event.ID = id
		event.Name = eventType
		switch eventType {
		case "api_post_event":
			event.Event = new(EventAPIRequest)
		case "status_update_event":
			event.Event = new(EventStatusUpdate)
		case "framework_message_event":
			event.Event = new(EventFrameworkMessage)
		case "subscribe_event":
			event.Event = new(EventSubscription)
		case "unsubscribe_event":
			event.Event = new(EventUnsubscription)
		case "event_stream_attached":
			event.Event = new(EventStreamAttached)
		case "event_stream_detached":
			event.Event = new(EventStreamDetached)
		case "add_health_check_event":
			event.Event = new(EventAddHealthCheck)
		case "remove_health_check_event":
			event.Event = new(EventRemoveHealthCheck)
		case "failed_health_check_event":
			event.Event = new(EventFailedHealthCheck)
		case "health_status_changed_event":
			event.Event = new(EventHealthCheckChanged)
		case "group_change_success":
			event.Event = new(EventGroupChangeSuccess)
		case "group_change_failed":
			event.Event = new(EventGroupChangeFailed)
		case "deployment_success":
			event.Event = new(EventDeploymentSuccess)
		case "deployment_failed":
			event.Event = new(EventDeploymentFailed)
		case "deployment_info":
			event.Event = new(EventDeploymentInfo)
		case "deployment_step_success":
			event.Event = new(EventDeploymentStepSuccess)
		case "deployment_step_failure":
			event.Event = new(EventDeploymentStepFailure)
		case "app_terminated_event":
			event.Event = new(EventAppTerminated)
		}
		return event, nil
	}

	return nil, fmt.Errorf("the event type: %s was not found or supported", eventType)
}


================================================
FILE: examples/Makefile
================================================
all:
	find * -type d -exec bash -exc "cd {}; go build . || kill $${PPID}" \;


================================================
FILE: examples/applications/main.go
================================================
/*
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.
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"
	"log"
	"time"

	marathon "github.com/gambol99/go-marathon"
)

var marathonURL string

func init() {
	flag.StringVar(&marathonURL, "url", "http://127.0.0.1:8080", "the url for the marathon endpoint")
}

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 main() {
	flag.Parse()
	config := marathon.NewDefaultConfig()
	config.URL = marathonURL
	client, err := marathon.NewClient(config)
	assert(err)
	applications, err := client.Applications(nil)
	assert(err)

	log.Printf("Found %d application running", len(applications.Apps))
	for _, application := range applications.Apps {
		log.Printf("Application: %v", application)
		details, err := client.Application(application.ID)
		assert(err)
		if details.Tasks != nil && len(details.Tasks) > 0 {
			for _, task := range details.Tasks {
				log.Printf("task: %v", task)
			}
			health, err := client.ApplicationOK(details.ID)
			assert(err)
			log.Printf("Application: %s, healthy: %t", details.ID, health)
		}
	}

	applicationName := "/my/product"

	if _, err := client.Application(applicationName); err == nil {
		deployID, err := client.DeleteApplication(applicationName, false)
		assert(err)
		waitOnDeployment(client, deployID)
	}

	log.Printf("Deploying a new application")
	application := marathon.NewDockerApplication().
		Name(applicationName).
		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")

	application.
		Container.Docker.Container("quay.io/gambol99/apache-php:latest").
		Bridged().
		Expose(80).
		Expose(443)

	*application.RequirePorts = true
	_, err = client.CreateApplication(application)
	assert(err)
	client.WaitOnApplication(application.ID, 30*time.Second)

	log.Printf("Scaling the application to 4 instances")
	deployID, err := client.ScaleApplicationInstances(application.ID, 4, false)
	assert(err)
	client.WaitOnApplication(application.ID, 30*time.Second)
	log.Printf("Successfully scaled the application, deployID: %s", deployID.DeploymentID)

	log.Printf("Deleting the application: %s", applicationName)
	deployID, err = client.DeleteApplication(application.ID, true)
	assert(err)
	waitOnDeployment(client, deployID)
	log.Printf("Successfully deleted the application")

	log.Printf("Starting the application again")
	_, err = client.CreateApplication(application)
	assert(err)
	log.Printf("Created the application: %s", application.ID)

	log.Printf("Delete all the tasks")
	_, err = client.KillApplicationTasks(application.ID, nil)
	assert(err)
}


================================================
FILE: examples/docker-compose.yml
================================================
# Based on https://github.com/meltwater/docker-mesos

zookeeper:
  image: mesoscloud/zookeeper:3.4.6-centos-7
  ports:
    - "2181:2181"
    - "2888:2888"
    - "3888:3888"
  environment:
    SERVERS: server.1=127.0.0.1
    MYID: 1

mesosmaster:
  image: mesoscloud/mesos-master:0.24.1-centos-7
  net: host
  environment:
    MESOS_ZK: zk://localhost:2181/mesos
    MESOS_QUORUM: 1
    MESOS_CLUSTER: local
    MESOS_HOSTNAME: localhost

mesosslave:
  image: mesoscloud/mesos-slave:0.24.1-centos-7
  net: host
  privileged: true
  volumes:
    - /sys:/sys
# /cgroup is needed on some older Linux versions
#    - /cgroup:/cgroup
# /usr/bin/docker is needed if you're running an older docker version
#    - /usr/local/bin/docker:/usr/bin/docker:r
    - /var/run/docker.sock:/var/run/docker.sock:rw
  environment:
    MESOS_MASTER: zk://localhost:2181/mesos
    MESOS_EXECUTOR_SHUTDOWN_GRACE_PERIOD: 90secs
    MESOS_DOCKER_STOP_TIMEOUT: 60secs
# If your workstation doesn't have a resolvable hostname/FQDN then $MESOS_HOSTNAME needs to be set to its IP-address
#    MESOS_HOSTNAME: 192.168.178.39

marathon:
  image: mesoscloud/marathon:0.11.0-centos-7
  net: host
  environment:
    MARATHON_ZK: zk://localhost:2181/marathon
    MARATHON_MASTER: zk://localhost:2181/mesos
    MARATHON_EVENT_SUBSCRIBER: http_callback
    MARATHON_TASK_LAUNCH_TIMEOUT: 300000


================================================
FILE: examples/events_callback_transport/main.go
================================================
/*
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.
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"
	"log"
	"time"

	marathon "github.com/gambol99/go-marathon"
)

var marathonURL string
var marathonInterface string
var marathonPort int
var timeout int

func init() {
	flag.StringVar(&marathonURL, "url", "http://127.0.0.1:8080", "the url for the Marathon endpoint")
	flag.StringVar(&marathonInterface, "interface", "eth0", "the interface we should use for events")
	flag.IntVar(&marathonPort, "port", 19999, "the port the events service should run on")
	flag.IntVar(&timeout, "timeout", 60, "listen to events for x seconds")
}

func assert(err error) {
	if err != nil {
		log.Fatalf("Failed, error: %s", err)
	}
}

func main() {
	flag.Parse()
	config := marathon.NewDefaultConfig()
	config.URL = marathonURL
	config.EventsInterface = marathonInterface
	config.EventsPort = marathonPort
	log.Printf("Creating a client, Marathon: %s", marathonURL)

	client, err := marathon.NewClient(config)
	assert(err)

	// Register for events
	events, err := client.AddEventsListener(marathon.EventIDApplications)
	assert(err)
	deployments, err := client.AddEventsListener(marathon.EventIDDeploymentStepSuccess)
	assert(err)

	// Listen for x seconds and then split
	timer := time.After(time.Duration(timeout) * time.Second)
	done := false
	for {
		if done {
			break
		}
		select {
		case <-timer:
			log.Printf("Exiting the loop")
			done = true
		case event := <-events:
			log.Printf("Received application event: %s", event)
		case event := <-deployments:
			log.Printf("Received deployment event: %v", event)
			var deployment *marathon.EventDeploymentStepSuccess
			deployment = event.Event.(*marathon.EventDeploymentStepSuccess)
			log.Printf("deployment step: %v", deployment.CurrentStep)
		}
	}

	log.Printf("Removing our subscription")
	client.RemoveEventsListener(events)
	client.RemoveEventsListener(deployments)
}


================================================
FILE: examples/events_sse_transport/main.go
================================================
/*
Copyright 2015 The go-maratho
Download .txt
gitextract_zhlni1rs/

├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── application.go
├── application_marshalling.go
├── application_marshalling_test.go
├── application_test.go
├── client.go
├── client_test.go
├── cluster.go
├── cluster_test.go
├── config.go
├── const.go
├── deployment.go
├── deployment_test.go
├── docker.go
├── docker_test.go
├── error.go
├── error_test.go
├── events.go
├── examples/
│   ├── Makefile
│   ├── applications/
│   │   └── main.go
│   ├── docker-compose.yml
│   ├── events_callback_transport/
│   │   └── main.go
│   ├── events_sse_transport/
│   │   └── main.go
│   ├── glog/
│   │   └── main.go
│   ├── groups/
│   │   └── main.go
│   ├── multiple_endpoints/
│   │   └── main.go
│   ├── pods/
│   │   └── main.go
│   ├── queue/
│   │   └── main.go
│   └── tasks/
│       └── main.go
├── group.go
├── group_test.go
├── health.go
├── health_test.go
├── info.go
├── info_test.go
├── last_task_failure.go
├── network.go
├── offer.go
├── pod.go
├── pod_container.go
├── pod_container_image.go
├── pod_container_marshalling.go
├── pod_instance.go
├── pod_instance_status.go
├── pod_instance_test.go
├── pod_marshalling.go
├── pod_marshalling_test.go
├── pod_scheduling.go
├── pod_status.go
├── pod_status_test.go
├── pod_test.go
├── port_definition.go
├── queue.go
├── queue_test.go
├── readiness.go
├── readiness_test.go
├── residency.go
├── residency_test.go
├── resources.go
├── subscription.go
├── subscription_test.go
├── task.go
├── task_test.go
├── testing_utils_test.go
├── tests/
│   ├── app-definitions/
│   │   ├── TestApplicationString-1.5-output.json
│   │   └── TestApplicationString-output.json
│   └── rest-api/
│       └── methods.yml
├── unreachable_strategy.go
├── unreachable_strategy_test.go
├── upgrade_strategy.go
├── utils.go
├── utils_test.go
└── volume.go
Download .txt
SYMBOL INDEX (807 symbols across 67 files)

FILE: application.go
  type Applications (line 33) | type Applications struct
  type IPAddressPerTask (line 38) | type IPAddressPerTask struct
    method EmptyLabels (line 905) | func (i *IPAddressPerTask) EmptyLabels() *IPAddressPerTask {
    method AddLabel (line 913) | func (i *IPAddressPerTask) AddLabel(name, value string) *IPAddressPerT...
    method EmptyGroups (line 923) | func (i *IPAddressPerTask) EmptyGroups() *IPAddressPerTask {
    method AddGroup (line 930) | func (i *IPAddressPerTask) AddGroup(group string) *IPAddressPerTask {
    method SetDiscovery (line 944) | func (i *IPAddressPerTask) SetDiscovery(discovery Discovery) *IPAddres...
  type Discovery (line 46) | type Discovery struct
    method EmptyPorts (line 952) | func (d *Discovery) EmptyPorts() *Discovery {
    method AddPort (line 959) | func (d *Discovery) AddPort(port Port) *Discovery {
  type Port (line 51) | type Port struct
  type Application (line 58) | type Application struct
    method SetIPAddressPerTask (line 169) | func (r *Application) SetIPAddressPerTask(ipAddressPerTask IPAddressPe...
    method Name (line 185) | func (r *Application) Name(id string) *Application {
    method Command (line 191) | func (r *Application) Command(cmd string) *Application {
    method CPU (line 198) | func (r *Application) CPU(cpu float64) *Application {
    method SetGPUs (line 205) | func (r *Application) SetGPUs(gpu float64) *Application {
    method EmptyGPUs (line 213) | func (r *Application) EmptyGPUs() *Application {
    method Storage (line 222) | func (r *Application) Storage(disk float64) *Application {
    method AllTaskRunning (line 229) | func (r *Application) AllTaskRunning() bool {
    method DependsOn (line 246) | func (r *Application) DependsOn(names ...string) *Application {
    method Memory (line 257) | func (r *Application) Memory(memory float64) *Application {
    method AddPortDefinition (line 266) | func (r *Application) AddPortDefinition(portDefinition PortDefinition)...
    method EmptyPortDefinitions (line 280) | func (r *Application) EmptyPortDefinitions() *Application {
    method Count (line 288) | func (r *Application) Count(count int) *Application {
    method SetTaskKillGracePeriod (line 298) | func (r *Application) SetTaskKillGracePeriod(seconds float64) *Applica...
    method AddArgs (line 306) | func (r *Application) AddArgs(arguments ...string) *Application {
    method EmptyArgs (line 321) | func (r *Application) EmptyArgs() *Application {
    method AddConstraint (line 329) | func (r *Application) AddConstraint(constraints ...string) *Application {
    method EmptyConstraints (line 344) | func (r *Application) EmptyConstraints() *Application {
    method AddLabel (line 353) | func (r *Application) AddLabel(name, value string) *Application {
    method EmptyLabels (line 365) | func (r *Application) EmptyLabels() *Application {
    method AddEnv (line 374) | func (r *Application) AddEnv(name, value string) *Application {
    method EmptyEnvs (line 386) | func (r *Application) EmptyEnvs() *Application {
    method AddSecret (line 396) | func (r *Application) AddSecret(envVar, name, source string) *Applicat...
    method EmptySecrets (line 408) | func (r *Application) EmptySecrets() *Application {
    method SetExecutor (line 415) | func (r *Application) SetExecutor(executor string) *Application {
    method AddHealthCheck (line 423) | func (r *Application) AddHealthCheck(healthCheck HealthCheck) *Applica...
    method EmptyHealthChecks (line 438) | func (r *Application) EmptyHealthChecks() *Application {
    method HasHealthChecks (line 445) | func (r *Application) HasHealthChecks() bool {
    method AddReadinessCheck (line 450) | func (r *Application) AddReadinessCheck(readinessCheck ReadinessCheck)...
    method EmptyReadinessChecks (line 463) | func (r *Application) EmptyReadinessChecks() *Application {
    method DeploymentIDs (line 470) | func (r *Application) DeploymentIDs() []*DeploymentID {
    method CheckHTTP (line 494) | func (r *Application) CheckHTTP(path string, port, interval int) (*App...
    method CheckTCP (line 520) | func (r *Application) CheckTCP(port, interval int) (*Application, erro...
    method AddUris (line 544) | func (r *Application) AddUris(newUris ...string) *Application {
    method EmptyUris (line 559) | func (r *Application) EmptyUris() *Application {
    method AddFetchURIs (line 567) | func (r *Application) AddFetchURIs(fetchURIs ...Fetch) *Application {
    method EmptyFetchURIs (line 582) | func (r *Application) EmptyFetchURIs() *Application {
    method SetUpgradeStrategy (line 589) | func (r *Application) SetUpgradeStrategy(us UpgradeStrategy) *Applicat...
    method EmptyUpgradeStrategy (line 597) | func (r *Application) EmptyUpgradeStrategy() *Application {
    method SetUnreachableStrategy (line 603) | func (r *Application) SetUnreachableStrategy(us UnreachableStrategy) *...
    method EmptyUnreachableStrategy (line 611) | func (r *Application) EmptyUnreachableStrategy() *Application {
    method SetResidency (line 618) | func (r *Application) SetResidency(whenLost TaskLostBehaviorType) *App...
    method EmptyResidency (line 628) | func (r *Application) EmptyResidency() *Application {
    method String (line 634) | func (r *Application) String() string {
    method EmptyNetworks (line 970) | func (r *Application) EmptyNetworks() *Application {
    method SetNetwork (line 976) | func (r *Application) SetNetwork(name string, mode PodNetworkMode) *Ap...
  type ApplicationVersions (line 111) | type ApplicationVersions struct
  type ApplicationVersion (line 116) | type ApplicationVersion struct
  type VersionInfo (line 121) | type VersionInfo struct
  type Fetch (line 127) | type Fetch struct
  type GetAppOpts (line 137) | type GetAppOpts struct
  type DeleteAppOpts (line 143) | type DeleteAppOpts struct
  type TaskStats (line 148) | type TaskStats struct
  type Stats (line 153) | type Stats struct
  type Secret (line 161) | type Secret struct
  function NewDockerApplication (line 178) | func NewDockerApplication() *Application {
  method Applications (line 644) | func (r *marathonClient) Applications(v url.Values) (*Applications, erro...
  method ListApplications (line 660) | func (r *marathonClient) ListApplications(v url.Values) ([]string, error) {
  method HasApplicationVersion (line 676) | func (r *marathonClient) HasApplicationVersion(name, version string) (bo...
  method ApplicationVersions (line 688) | func (r *marathonClient) ApplicationVersions(name string) (*ApplicationV...
  method SetApplicationVersion (line 700) | func (r *marathonClient) SetApplicationVersion(name string, version *App...
  method Application (line 712) | func (r *marathonClient) Application(name string) (*Application, error) {
  method ApplicationBy (line 727) | func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*...
  method ApplicationByVersion (line 746) | func (r *marathonClient) ApplicationByVersion(name, version string) (*Ap...
  method ApplicationOK (line 760) | func (r *marathonClient) ApplicationOK(name string) (bool, error) {
  method ApplicationDeployments (line 798) | func (r *marathonClient) ApplicationDeployments(name string) ([]*Deploym...
  method CreateApplication (line 809) | func (r *marathonClient) CreateApplication(application *Application) (*A...
  method WaitOnApplication (line 821) | func (r *marathonClient) WaitOnApplication(name string, timeout time.Dur...
  method appExistAndRunning (line 825) | func (r *marathonClient) appExistAndRunning(name string) bool {
  method DeleteApplication (line 839) | func (r *marathonClient) DeleteApplication(name string, force bool) (*De...
  method RestartApplication (line 852) | func (r *marathonClient) RestartApplication(name string, force bool) (*D...
  method ScaleApplicationInstances (line 867) | func (r *marathonClient) ScaleApplicationInstances(name string, instance...
  method UpdateApplication (line 882) | func (r *marathonClient) UpdateApplication(application *Application, for...
  function buildPathWithForceParam (line 891) | func buildPathWithForceParam(rootPath string, force bool) string {
  function buildPath (line 899) | func buildPath(path string) string {

FILE: application_marshalling.go
  type Alias (line 25) | type Alias
  type TmpEnvSecret (line 28) | type TmpEnvSecret struct
  type TmpSecret (line 33) | type TmpSecret struct
  method UnmarshalJSON (line 40) | func (app *Application) UnmarshalJSON(b []byte) error {
  method MarshalJSON (line 84) | func (app *Application) MarshalJSON() ([]byte, error) {

FILE: application_marshalling_test.go
  function TestEnvironmentVariableUnmarshal (line 28) | func TestEnvironmentVariableUnmarshal(t *testing.T) {
  function TestMalformedPayloadUnmarshal (line 52) | func TestMalformedPayloadUnmarshal(t *testing.T) {
  function TestEnvironmentVariableMarshal (line 90) | func TestEnvironmentVariableMarshal(t *testing.T) {

FILE: application_test.go
  function TestApplicationDependsOn (line 32) | func TestApplicationDependsOn(t *testing.T) {
  function TestApplicationMemory (line 39) | func TestApplicationMemory(t *testing.T) {
  function TestApplicationString (line 45) | func TestApplicationString(t *testing.T) {
  function TestApplicationCount (line 110) | func TestApplicationCount(t *testing.T) {
  function TestApplicationStorage (line 117) | func TestApplicationStorage(t *testing.T) {
  function TestApplicationAllTaskRunning (line 124) | func TestApplicationAllTaskRunning(t *testing.T) {
  function TestApplicationName (line 150) | func TestApplicationName(t *testing.T) {
  function TestApplicationCommand (line 157) | func TestApplicationCommand(t *testing.T) {
  function TestApplicationCPU (line 164) | func TestApplicationCPU(t *testing.T) {
  function TestApplicationSetGPUs (line 171) | func TestApplicationSetGPUs(t *testing.T) {
  function TestApplicationEmptyGPUs (line 178) | func TestApplicationEmptyGPUs(t *testing.T) {
  function TestApplicationArgs (line 185) | func TestApplicationArgs(t *testing.T) {
  function ExampleApplication_AddConstraint (line 199) | func ExampleApplication_AddConstraint() {
  function TestApplicationConstraints (line 207) | func TestApplicationConstraints(t *testing.T) {
  function TestApplicationLabels (line 222) | func TestApplicationLabels(t *testing.T) {
  function TestApplicationEnvs (line 236) | func TestApplicationEnvs(t *testing.T) {
  function TestApplicationSecrets (line 250) | func TestApplicationSecrets(t *testing.T) {
  function TestApplicationSetExecutor (line 265) | func TestApplicationSetExecutor(t *testing.T) {
  function TestApplicationHealthChecks (line 276) | func TestApplicationHealthChecks(t *testing.T) {
  function TestApplicationReadinessChecks (line 292) | func TestApplicationReadinessChecks(t *testing.T) {
  function TestApplicationPortDefinitions (line 307) | func TestApplicationPortDefinitions(t *testing.T) {
  function TestHasHealthChecks (line 331) | func TestHasHealthChecks(t *testing.T) {
  function TestApplicationCheckTCP (line 354) | func TestApplicationCheckTCP(t *testing.T) {
  function TestApplicationCheckHTTP (line 384) | func TestApplicationCheckHTTP(t *testing.T) {
  function TestCreateApplication (line 415) | func TestCreateApplication(t *testing.T) {
  function TestUpdateApplication (line 428) | func TestUpdateApplication(t *testing.T) {
  function TestApplications (line 442) | func TestApplications(t *testing.T) {
  function TestApplicationsEmbedTaskStats (line 461) | func TestApplicationsEmbedTaskStats(t *testing.T) {
  function TestApplicationsEmbedReadiness (line 476) | func TestApplicationsEmbedReadiness(t *testing.T) {
  function TestListApplications (line 502) | func TestListApplications(t *testing.T) {
  function TestApplicationVersions (line 514) | func TestApplicationVersions(t *testing.T) {
  function TestRestartApplication (line 529) | func TestRestartApplication(t *testing.T) {
  function TestApplicationUris (line 543) | func TestApplicationUris(t *testing.T) {
  function TestApplicationFetchURIs (line 557) | func TestApplicationFetchURIs(t *testing.T) {
  function TestSetApplicationVersion (line 572) | func TestSetApplicationVersion(t *testing.T) {
  function TestHasApplicationVersion (line 587) | func TestHasApplicationVersion(t *testing.T) {
  function TestDeleteApplication (line 599) | func TestDeleteApplication(t *testing.T) {
  function TestApplicationOK (line 614) | func TestApplicationOK(t *testing.T) {
  function verifyApplication (line 629) | func verifyApplication(application *Application, t *testing.T) {
  function TestApplication (line 642) | func TestApplication(t *testing.T) {
  function TestApplicationConfiguration (line 673) | func TestApplicationConfiguration(t *testing.T) {
  function TestWaitOnApplication (line 694) | func TestWaitOnApplication(t *testing.T) {
  function TestAppExistAndRunning (line 771) | func TestAppExistAndRunning(t *testing.T) {
  function TestSetIPPerTask (line 779) | func TestSetIPPerTask(t *testing.T) {
  function TestIPAddressPerTask (line 793) | func TestIPAddressPerTask(t *testing.T) {
  function TestIPAddressPerTaskDiscovery (line 818) | func TestIPAddressPerTaskDiscovery(t *testing.T) {
  function TestUpgradeStrategy (line 832) | func TestUpgradeStrategy(t *testing.T) {
  function TestBridgedNetworking (line 849) | func TestBridgedNetworking(t *testing.T) {
  function TestContainerNetworking (line 856) | func TestContainerNetworking(t *testing.T) {
  function TestHostNetworking (line 863) | func TestHostNetworking(t *testing.T) {

FILE: client.go
  type Marathon (line 37) | type Marathon interface
  type EventsChannelContext (line 217) | type EventsChannelContext struct
  type marathonClient (line 223) | type marathonClient struct
    method GetMarathonURL (line 302) | func (r *marathonClient) GetMarathonURL() string {
    method Ping (line 307) | func (r *marathonClient) Ping() (bool, error) {
    method apiHead (line 314) | func (r *marathonClient) apiHead(path string, result interface{}) error {
    method apiGet (line 318) | func (r *marathonClient) apiGet(path string, post, result interface{})...
    method apiPut (line 322) | func (r *marathonClient) apiPut(path string, post, result interface{})...
    method ApiPost (line 326) | func (r *marathonClient) ApiPost(path string, post, result interface{}...
    method apiDelete (line 330) | func (r *marathonClient) apiDelete(path string, post, result interface...
    method apiCall (line 334) | func (r *marathonClient) apiCall(method, path string, body, result int...
    method wait (line 411) | func (r *marathonClient) wait(name string, timeout time.Duration, fn f...
    method buildAPIRequest (line 433) | func (r *marathonClient) buildAPIRequest(method, path string, reader i...
  type httpClient (line 243) | type httpClient struct
    method buildMarathonJSONRequest (line 450) | func (rc *httpClient) buildMarathonJSONRequest(method, member, path st...
    method buildMarathonRequest (line 462) | func (rc *httpClient) buildMarathonRequest(method, member, path string...
    method Do (line 488) | func (rc *httpClient) Do(request *http.Request) (response *http.Respon...
  type newRequestError (line 249) | type newRequestError struct
  function NewClient (line 255) | func NewClient(config Config) (Marathon, error) {
  function oneLogLine (line 496) | func oneLogLine(in []byte) []byte {

FILE: client_test.go
  function TestNewClient (line 29) | func TestNewClient(t *testing.T) {
  function TestHTTPClientDefaults (line 47) | func TestHTTPClientDefaults(t *testing.T) {
  function TestLogOutput (line 100) | func TestLogOutput(t *testing.T) {
  function TestInvalidConfig (line 115) | func TestInvalidConfig(t *testing.T) {
  function TestPing (line 123) | func TestPing(t *testing.T) {
  function TestGetMarathonURL (line 132) | func TestGetMarathonURL(t *testing.T) {
  function TestAPIRequest (line 139) | func TestAPIRequest(t *testing.T) {
  function TestBuildApiRequestFailure (line 211) | func TestBuildApiRequestFailure(t *testing.T) {
  function TestOneLogLine (line 261) | func TestOneLogLine(t *testing.T) {
  function TestAPIRequestDCOS (line 276) | func TestAPIRequestDCOS(t *testing.T) {

FILE: cluster.go
  constant memberStatusUp (line 28) | memberStatusUp   = 0
  constant memberStatusDown (line 29) | memberStatusDown = 1
  type memberStatus (line 33) | type memberStatus
  type cluster (line 36) | type cluster struct
    method getMember (line 107) | func (c *cluster) getMember() (string, error) {
    method markDown (line 120) | func (c *cluster) markDown(endpoint string) {
    method healthCheckNode (line 135) | func (c *cluster) healthCheckNode(node *member) {
    method activeMembers (line 155) | func (c *cluster) activeMembers() []string {
    method nonActiveMembers (line 160) | func (c *cluster) nonActiveMembers() []string {
    method membersList (line 165) | func (c *cluster) membersList(status memberStatus) []string {
    method size (line 179) | func (c *cluster) size() int {
  type member (line 48) | type member struct
    method String (line 184) | func (m member) String() string {
  function newCluster (line 56) | func newCluster(client *httpClient, marathonURL string, isDCOS bool) (*c...

FILE: cluster_test.go
  function TestSize (line 27) | func TestSize(t *testing.T) {
  function TestActive (line 33) | func TestActive(t *testing.T) {
  function TestNonActive (line 39) | func TestNonActive(t *testing.T) {
  function TestGetMember (line 45) | func TestGetMember(t *testing.T) {
  function TestMarkDown (line 81) | func TestMarkDown(t *testing.T) {
  function TestValidClusterHosts (line 113) | func TestValidClusterHosts(t *testing.T) {
  function TestInvalidClusterHosts (line 168) | func TestInvalidClusterHosts(t *testing.T) {
  function newStandardCluster (line 187) | func newStandardCluster(url string) (*cluster, error) {

FILE: config.go
  constant defaultPollingWaitTime (line 26) | defaultPollingWaitTime = 500 * time.Millisecond
  constant defaultDCOSPath (line 28) | defaultDCOSPath = "marathon"
  type EventsTransport (line 31) | type EventsTransport
  type Config (line 34) | type Config struct
  function NewDefaultConfig (line 62) | func NewDefaultConfig() Config {

FILE: const.go
  constant defaultEventsURL (line 20) | defaultEventsURL = "/event"
  constant marathonAPIVersion (line 23) | marathonAPIVersion      = "v2"
  constant marathonAPIEventStream (line 24) | marathonAPIEventStream  = marathonAPIVersion + "/events"
  constant marathonAPISubscription (line 25) | marathonAPISubscription = marathonAPIVersion + "/eventSubscriptions"
  constant marathonAPIApps (line 26) | marathonAPIApps         = marathonAPIVersion + "/apps"
  constant marathonAPIPods (line 27) | marathonAPIPods         = marathonAPIVersion + "/pods"
  constant marathonAPITasks (line 28) | marathonAPITasks        = marathonAPIVersion + "/tasks"
  constant marathonAPIDeployments (line 29) | marathonAPIDeployments  = marathonAPIVersion + "/deployments"
  constant marathonAPIGroups (line 30) | marathonAPIGroups       = marathonAPIVersion + "/groups"
  constant marathonAPIQueue (line 31) | marathonAPIQueue        = marathonAPIVersion + "/queue"
  constant marathonAPIInfo (line 32) | marathonAPIInfo         = marathonAPIVersion + "/info"
  constant marathonAPILeader (line 33) | marathonAPILeader       = marathonAPIVersion + "/leader"
  constant marathonAPIPing (line 34) | marathonAPIPing         = "ping"
  constant EventsTransportCallback (line 39) | EventsTransportCallback EventsTransport = 1 << iota
  constant EventsTransportSSE (line 42) | EventsTransportSSE

FILE: deployment.go
  type Deployment (line 26) | type Deployment struct
  type DeploymentID (line 39) | type DeploymentID struct
  type DeploymentStep (line 45) | type DeploymentStep struct
  type StepActions (line 52) | type StepActions struct
  type DeploymentPlan (line 61) | type DeploymentPlan struct
  method Deployments (line 70) | func (r *marathonClient) Deployments() ([]*Deployment, error) {
  method DeleteDeployment (line 110) | func (r *marathonClient) DeleteDeployment(id string, force bool) (*Deplo...
  method HasDeployment (line 131) | func (r *marathonClient) HasDeployment(id string) (bool, error) {
  method WaitOnDeployment (line 147) | func (r *marathonClient) WaitOnDeployment(id string, timeout time.Durati...

FILE: deployment_test.go
  function TestDeployments (line 26) | func TestDeployments(t *testing.T) {
  function TestDeploymentsV1 (line 41) | func TestDeploymentsV1(t *testing.T) {
  function TestDeleteDeployment (line 77) | func TestDeleteDeployment(t *testing.T) {
  function TestDeleteDeploymentForce (line 86) | func TestDeleteDeploymentForce(t *testing.T) {

FILE: docker.go
  type Container (line 25) | type Container struct
    method Volume (line 157) | func (container *Container) Volume(hostPath, containerPath, mode strin...
    method EmptyVolumes (line 177) | func (container *Container) EmptyVolumes() *Container {
    method Expose (line 287) | func (container *Container) Expose(ports ...int) *Container {
    method ExposeUDP (line 313) | func (container *Container) ExposeUDP(ports ...int) *Container {
    method ExposePort (line 338) | func (container *Container) ExposePort(portMapping PortMapping) *Conta...
    method EmptyPortMappings (line 366) | func (container *Container) EmptyPortMappings() *Container {
    method ServicePortIndex (line 428) | func (container *Container) ServicePortIndex(port int) (int, error) {
  type PortMapping (line 33) | type PortMapping struct
    method AddLabel (line 382) | func (p *PortMapping) AddLabel(name, value string) *PortMapping {
    method EmptyLabels (line 394) | func (p *PortMapping) EmptyLabels() *PortMapping {
    method AddNetwork (line 471) | func (p *PortMapping) AddNetwork(name string) *PortMapping {
    method EmptyNetworkNames (line 483) | func (p *PortMapping) EmptyNetworkNames() *PortMapping {
  type Parameters (line 44) | type Parameters struct
  type Volume (line 50) | type Volume struct
    method SetPersistentVolume (line 183) | func (v *Volume) SetPersistentVolume() *PersistentVolume {
    method SetSecretVolume (line 190) | func (v *Volume) SetSecretVolume(containerPath, secret string) *Volume {
    method EmptyPersistentVolume (line 197) | func (v *Volume) EmptyPersistentVolume() *Volume {
    method SetExternalVolume (line 205) | func (v *Volume) SetExternalVolume(name, provider string) *ExternalVol...
    method EmptyExternalVolume (line 215) | func (v *Volume) EmptyExternalVolume() *Volume {
  type PersistentVolumeType (line 60) | type PersistentVolumeType
  constant PersistentVolumeTypeRoot (line 64) | PersistentVolumeTypeRoot PersistentVolumeType = "root"
  constant PersistentVolumeTypePath (line 66) | PersistentVolumeTypePath PersistentVolumeType = "path"
  constant PersistentVolumeTypeMount (line 68) | PersistentVolumeTypeMount PersistentVolumeType = "mount"
  type PersistentVolume (line 73) | type PersistentVolume struct
    method SetType (line 82) | func (p *PersistentVolume) SetType(tp PersistentVolumeType) *Persisten...
    method SetSize (line 89) | func (p *PersistentVolume) SetSize(size int) *PersistentVolume {
    method SetMaxSize (line 97) | func (p *PersistentVolume) SetMaxSize(maxSize int) *PersistentVolume {
    method AddConstraint (line 104) | func (p *PersistentVolume) AddConstraint(constraints ...string) *Persi...
    method EmptyConstraints (line 118) | func (p *PersistentVolume) EmptyConstraints() *PersistentVolume {
  type ExternalVolume (line 124) | type ExternalVolume struct
    method AddOption (line 223) | func (ev *ExternalVolume) AddOption(name, value string) *ExternalVolume {
    method EmptyOptions (line 233) | func (ev *ExternalVolume) EmptyOptions() *ExternalVolume {
  type PullConfig (line 131) | type PullConfig struct
  function NewPullConfig (line 136) | func NewPullConfig(secret string) *PullConfig {
  type Docker (line 143) | type Docker struct
    method SetForcePullImage (line 251) | func (docker *Docker) SetForcePullImage(forcePull bool) *Docker {
    method SetPrivileged (line 260) | func (docker *Docker) SetPrivileged(priv bool) *Docker {
    method Container (line 268) | func (docker *Docker) Container(image string) *Docker {
    method Bridged (line 274) | func (docker *Docker) Bridged() *Docker {
    method Host (line 280) | func (docker *Docker) Host() *Docker {
    method Expose (line 300) | func (docker *Docker) Expose(ports ...int) *Docker {
    method ExposeUDP (line 326) | func (docker *Docker) ExposeUDP(ports ...int) *Docker {
    method ExposePort (line 351) | func (docker *Docker) ExposePort(portMapping PortMapping) *Docker {
    method EmptyPortMappings (line 374) | func (docker *Docker) EmptyPortMappings() *Docker {
    method AddParameter (line 403) | func (docker *Docker) AddParameter(key string, value string) *Docker {
    method EmptyParameters (line 421) | func (docker *Docker) EmptyParameters() *Docker {
    method ServicePortIndex (line 446) | func (docker *Docker) ServicePortIndex(port int) (int, error) {
    method SetPullConfig (line 463) | func (docker *Docker) SetPullConfig(pullConfig *PullConfig) *Docker {
  function NewDockerContainer (line 240) | func NewDockerContainer() *Container {

FILE: docker_test.go
  function createPortMapping (line 26) | func createPortMapping(containerPort int, protocol string) *PortMapping {
  function TestDockerAddParameter (line 35) | func TestDockerAddParameter(t *testing.T) {
  function TestDockerExpose (line 49) | func TestDockerExpose(t *testing.T) {
  function TestDockerExposeUDP (line 75) | func TestDockerExposeUDP(t *testing.T) {
  function TestPortMappingLabels (line 100) | func TestPortMappingLabels(t *testing.T) {
  function TestPortMappingNetworkNames (line 115) | func TestPortMappingNetworkNames(t *testing.T) {
  function TestVolume (line 129) | func TestVolume(t *testing.T) {
  function TestSecretVolume (line 144) | func TestSecretVolume(t *testing.T) {
  function TestExternalVolume (line 157) | func TestExternalVolume(t *testing.T) {
  function TestDockerPersistentVolume (line 181) | func TestDockerPersistentVolume(t *testing.T) {
  function TestDockerPullConfig (line 210) | func TestDockerPullConfig(t *testing.T) {

FILE: error.go
  constant ErrCodeBadRequest (line 28) | ErrCodeBadRequest = iota
  constant ErrCodeUnauthorized (line 30) | ErrCodeUnauthorized
  constant ErrCodeForbidden (line 32) | ErrCodeForbidden
  constant ErrCodeNotFound (line 34) | ErrCodeNotFound
  constant ErrCodeDuplicateID (line 36) | ErrCodeDuplicateID
  constant ErrCodeAppLocked (line 38) | ErrCodeAppLocked
  constant ErrCodeInvalidBean (line 40) | ErrCodeInvalidBean
  constant ErrCodeServer (line 42) | ErrCodeServer
  constant ErrCodeUnknown (line 44) | ErrCodeUnknown
  constant ErrCodeMethodNotAllowed (line 46) | ErrCodeMethodNotAllowed
  type InvalidEndpointError (line 50) | type InvalidEndpointError struct
    method Error (line 55) | func (e *InvalidEndpointError) Error() string {
  function newInvalidEndpointError (line 60) | func newInvalidEndpointError(message string, args ...interface{}) error {
  type APIError (line 65) | type APIError struct
    method Error (line 71) | func (e *APIError) Error() string {
  function NewAPIError (line 76) | func NewAPIError(code int, content []byte) error {
  type errorDefinition (line 102) | type errorDefinition interface
  function parseContent (line 107) | func parseContent(errDef errorDefinition, content []byte) error {
  type simpleErrDef (line 118) | type simpleErrDef struct
    method message (line 123) | func (def *simpleErrDef) message() string {
    method errCode (line 127) | func (def *simpleErrDef) errCode() int {
  type detailDescription (line 131) | type detailDescription struct
    method String (line 136) | func (d detailDescription) String() string {
  type badRequestDef (line 140) | type badRequestDef struct
    method message (line 145) | func (def *badRequestDef) message() string {
    method errCode (line 154) | func (def *badRequestDef) errCode() int {
  type conflictDef (line 158) | type conflictDef struct
    method message (line 165) | func (def *conflictDef) message() string {
    method errCode (line 179) | func (def *conflictDef) errCode() int {
  type unprocessableEntityDetails (line 187) | type unprocessableEntityDetails
  type unprocessableEntityDef (line 195) | type unprocessableEntityDef struct
    method message (line 203) | func (def *unprocessableEntityDef) message() string {
    method errCode (line 227) | func (def *unprocessableEntityDef) errCode() int {

FILE: error_test.go
  function TestErrors (line 28) | func TestErrors(t *testing.T) {
  function content400 (line 149) | func content400() string {
  function content422 (line 165) | func content422(detailsPropKey string) string {
  function content422V1 (line 181) | func content422V1() string {

FILE: events.go
  type EventType (line 22) | type EventType struct
  constant EventIDAPIRequest (line 28) | EventIDAPIRequest = 1 << iota
  constant EventIDStatusUpdate (line 30) | EventIDStatusUpdate
  constant EventIDFrameworkMessage (line 32) | EventIDFrameworkMessage
  constant EventIDSubscription (line 34) | EventIDSubscription
  constant EventIDUnsubscribed (line 36) | EventIDUnsubscribed
  constant EventIDStreamAttached (line 38) | EventIDStreamAttached
  constant EventIDStreamDetached (line 40) | EventIDStreamDetached
  constant EventIDAddHealthCheck (line 42) | EventIDAddHealthCheck
  constant EventIDRemoveHealthCheck (line 44) | EventIDRemoveHealthCheck
  constant EventIDFailedHealthCheck (line 46) | EventIDFailedHealthCheck
  constant EventIDChangedHealthCheck (line 48) | EventIDChangedHealthCheck
  constant EventIDGroupChangeSuccess (line 50) | EventIDGroupChangeSuccess
  constant EventIDGroupChangeFailed (line 52) | EventIDGroupChangeFailed
  constant EventIDDeploymentSuccess (line 54) | EventIDDeploymentSuccess
  constant EventIDDeploymentFailed (line 56) | EventIDDeploymentFailed
  constant EventIDDeploymentInfo (line 58) | EventIDDeploymentInfo
  constant EventIDDeploymentStepSuccess (line 60) | EventIDDeploymentStepSuccess
  constant EventIDDeploymentStepFailed (line 62) | EventIDDeploymentStepFailed
  constant EventIDAppTerminated (line 64) | EventIDAppTerminated
  constant EventIDApplications (line 66) | EventIDApplications = EventIDStatusUpdate | EventIDChangedHealthCheck | ...
  constant EventIDSubscriptions (line 68) | EventIDSubscriptions = EventIDSubscription | EventIDUnsubscribed | Event...
  function init (line 75) | func init() {
  type Event (line 104) | type Event struct
    method String (line 110) | func (event *Event) String() string {
  type EventsChannel (line 115) | type EventsChannel
  type EventAPIRequest (line 120) | type EventAPIRequest struct
  type EventStatusUpdate (line 131) | type EventStatusUpdate struct
  type EventAppTerminated (line 146) | type EventAppTerminated struct
  type EventFrameworkMessage (line 155) | type EventFrameworkMessage struct
  type EventSubscription (line 166) | type EventSubscription struct
  type EventUnsubscription (line 174) | type EventUnsubscription struct
  type EventStreamAttached (line 182) | type EventStreamAttached struct
  type EventStreamDetached (line 189) | type EventStreamDetached struct
  type EventAddHealthCheck (line 198) | type EventAddHealthCheck struct
  type EventRemoveHealthCheck (line 214) | type EventRemoveHealthCheck struct
  type EventFailedHealthCheck (line 230) | type EventFailedHealthCheck struct
  type EventHealthCheckChanged (line 246) | type EventHealthCheckChanged struct
  type EventGroupChangeSuccess (line 258) | type EventGroupChangeSuccess struct
  type EventGroupChangeFailed (line 266) | type EventGroupChangeFailed struct
  type EventDeploymentSuccess (line 275) | type EventDeploymentSuccess struct
  type EventDeploymentFailed (line 283) | type EventDeploymentFailed struct
  type EventDeploymentInfo (line 290) | type EventDeploymentInfo struct
  type EventDeploymentStepSuccess (line 298) | type EventDeploymentStepSuccess struct
  type EventDeploymentStepFailure (line 306) | type EventDeploymentStepFailure struct
  function GetEvent (line 315) | func GetEvent(eventType string) (*Event, error) {

FILE: examples/applications/main.go
  function init (line 29) | func init() {
  function assert (line 33) | func assert(err error) {
  function waitOnDeployment (line 39) | func waitOnDeployment(client marathon.Marathon, id *marathon.DeploymentI...
  function main (line 43) | func main() {

FILE: examples/events_callback_transport/main.go
  function init (line 32) | func init() {
  function assert (line 39) | func assert(err error) {
  function main (line 45) | func main() {

FILE: examples/events_sse_transport/main.go
  function init (line 30) | func init() {
  function assert (line 35) | func assert(err error) {
  function main (line 41) | func main() {

FILE: examples/glog/main.go
  type logBridge (line 30) | type logBridge struct
    method Write (line 32) | func (l *logBridge) Write(b []byte) (n int, err error) {
  function init (line 37) | func init() {
  function main (line 41) | func main() {

FILE: examples/groups/main.go
  function init (line 29) | func init() {
  function assert (line 33) | func assert(err error) {
  function main (line 39) | func main() {

FILE: examples/multiple_endpoints/main.go
  constant waitTime (line 27) | waitTime = 5 * time.Second
  function init (line 31) | func init() {
  function main (line 35) | func main() {

FILE: examples/pods/main.go
  function init (line 31) | func init() {
  function assert (line 36) | func assert(err error) {
  function waitOnDeployment (line 42) | func waitOnDeployment(client marathon.Marathon, id *marathon.DeploymentI...
  function createRawPod (line 46) | func createRawPod() *marathon.Pod {
  function createConveniencePod (line 97) | func createConveniencePod() *marathon.Pod {
  function doPlayground (line 123) | func doPlayground(client marathon.Marathon, pod *marathon.Pod) {
  function main (line 158) | func main() {

FILE: examples/queue/main.go
  function init (line 30) | func init() {
  function main (line 34) | func main() {

FILE: examples/tasks/main.go
  constant marathonURL (line 26) | marathonURL = "http://127.0.0.1:8080"
  function main (line 28) | func main() {

FILE: group.go
  type Group (line 25) | type Group struct
    method Name (line 72) | func (r *Group) Name(name string) *Group {
    method App (line 79) | func (r *Group) App(application *Application) *Group {
  type Groups (line 33) | type Groups struct
  type GetGroupOpts (line 43) | type GetGroupOpts struct
  type DeleteGroupOpts (line 49) | type DeleteGroupOpts struct
  type UpdateGroupOpts (line 55) | type UpdateGroupOpts struct
  function NewApplicationGroup (line 61) | func NewApplicationGroup(name string) *Group {
  method Groups (line 88) | func (r *marathonClient) Groups() (*Groups, error) {
  method Group (line 98) | func (r *marathonClient) Group(name string) (*Group, error) {
  method GroupsBy (line 108) | func (r *marathonClient) GroupsBy(opts *GetGroupOpts) (*Groups, error) {
  method GroupBy (line 123) | func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Grou...
  method HasGroup (line 137) | func (r *marathonClient) HasGroup(name string) (bool, error) {
  method CreateGroup (line 151) | func (r *marathonClient) CreateGroup(group *Group) error {
  method WaitOnGroup (line 158) | func (r *marathonClient) WaitOnGroup(name string, timeout time.Duration)...
  method DeleteGroup (line 209) | func (r *marathonClient) DeleteGroup(name string, force bool) (*Deployme...
  method UpdateGroup (line 226) | func (r *marathonClient) UpdateGroup(name string, group *Group, force bo...

FILE: group_test.go
  function TestGroups (line 25) | func TestGroups(t *testing.T) {
  function TestNewGroup (line 37) | func TestNewGroup(t *testing.T) {

FILE: health.go
  type HealthCheck (line 20) | type HealthCheck struct
    method SetCommand (line 83) | func (h *HealthCheck) SetCommand(c Command) *HealthCheck {
    method SetPortIndex (line 89) | func (h *HealthCheck) SetPortIndex(i int) *HealthCheck {
    method SetPort (line 95) | func (h *HealthCheck) SetPort(i int) *HealthCheck {
    method SetPath (line 101) | func (h *HealthCheck) SetPath(p string) *HealthCheck {
    method SetMaxConsecutiveFailures (line 107) | func (h *HealthCheck) SetMaxConsecutiveFailures(i int) *HealthCheck {
    method SetIgnoreHTTP1xx (line 113) | func (h *HealthCheck) SetIgnoreHTTP1xx(ignore bool) *HealthCheck {
  type HTTPHealthCheck (line 34) | type HTTPHealthCheck struct
    method SetEndpoint (line 209) | func (h *HTTPHealthCheck) SetEndpoint(endpoint string) *HTTPHealthCheck {
    method SetPath (line 215) | func (h *HTTPHealthCheck) SetPath(path string) *HTTPHealthCheck {
    method SetScheme (line 221) | func (h *HTTPHealthCheck) SetScheme(scheme string) *HTTPHealthCheck {
  type TCPHealthCheck (line 41) | type TCPHealthCheck struct
    method SetEndpoint (line 227) | func (t *TCPHealthCheck) SetEndpoint(endpoint string) *TCPHealthCheck {
  type CommandHealthCheck (line 46) | type CommandHealthCheck struct
    method SetCommand (line 233) | func (c *CommandHealthCheck) SetCommand(p PodCommand) *CommandHealthCh...
  type PodHealthCheck (line 51) | type PodHealthCheck struct
    method SetHTTPHealthCheck (line 153) | func (p *PodHealthCheck) SetHTTPHealthCheck(h *HTTPHealthCheck) *PodHe...
    method SetTCPHealthCheck (line 162) | func (p *PodHealthCheck) SetTCPHealthCheck(t *TCPHealthCheck) *PodHeal...
    method SetExecHealthCheck (line 171) | func (p *PodHealthCheck) SetExecHealthCheck(e *CommandHealthCheck) *Po...
    method SetGracePeriod (line 179) | func (p *PodHealthCheck) SetGracePeriod(gracePeriodSeconds int) *PodHe...
    method SetInterval (line 185) | func (p *PodHealthCheck) SetInterval(intervalSeconds int) *PodHealthCh...
    method SetMaxConsecutiveFailures (line 191) | func (p *PodHealthCheck) SetMaxConsecutiveFailures(maxFailures int) *P...
    method SetTimeout (line 197) | func (p *PodHealthCheck) SetTimeout(timeoutSeconds int) *PodHealthCheck {
    method SetDelay (line 203) | func (p *PodHealthCheck) SetDelay(delaySeconds int) *PodHealthCheck {
  function NewPodHealthCheck (line 63) | func NewPodHealthCheck() *PodHealthCheck {
  function NewHTTPHealthCheck (line 68) | func NewHTTPHealthCheck() *HTTPHealthCheck {
  function NewTCPHealthCheck (line 73) | func NewTCPHealthCheck() *TCPHealthCheck {
  function NewCommandHealthCheck (line 78) | func NewCommandHealthCheck() *CommandHealthCheck {
  function NewDefaultHealthCheck (line 119) | func NewDefaultHealthCheck() *HealthCheck {
  type HealthCheckResult (line 136) | type HealthCheckResult struct
  type Command (line 147) | type Command struct

FILE: health_test.go
  function TestCommand (line 25) | func TestCommand(t *testing.T) {
  function TestPortIndex (line 32) | func TestPortIndex(t *testing.T) {
  function TestPort (line 38) | func TestPort(t *testing.T) {
  function TestPath (line 44) | func TestPath(t *testing.T) {
  function TestMaxConsecutiveFailures (line 50) | func TestMaxConsecutiveFailures(t *testing.T) {
  function TestIgnoreHTTP1xx (line 56) | func TestIgnoreHTTP1xx(t *testing.T) {

FILE: info.go
  type Info (line 20) | type Info struct
  method Info (line 68) | func (r *marathonClient) Info() (*Info, error) {
  method Leader (line 78) | func (r *marathonClient) Leader() (string, error) {
  method AbdicateLeader (line 90) | func (r *marathonClient) AbdicateLeader() (string, error) {

FILE: info_test.go
  function TestInfo (line 25) | func TestInfo(t *testing.T) {
  function TestLeader (line 36) | func TestLeader(t *testing.T) {
  function TestAbdicateLeader (line 45) | func TestAbdicateLeader(t *testing.T) {

FILE: last_task_failure.go
  type LastTaskFailure (line 20) | type LastTaskFailure struct

FILE: network.go
  type PodNetworkMode (line 20) | type PodNetworkMode
  constant ContainerNetworkMode (line 23) | ContainerNetworkMode PodNetworkMode = "container"
  constant BridgeNetworkMode (line 24) | BridgeNetworkMode    PodNetworkMode = "container/bridge"
  constant HostNetworkMode (line 25) | HostNetworkMode      PodNetworkMode = "host"
  type PodNetwork (line 29) | type PodNetwork struct
    method SetName (line 79) | func (n *PodNetwork) SetName(name string) *PodNetwork {
    method SetMode (line 85) | func (n *PodNetwork) SetMode(mode PodNetworkMode) *PodNetwork {
    method Label (line 91) | func (n *PodNetwork) Label(key, value string) *PodNetwork {
  type PodEndpoint (line 36) | type PodEndpoint struct
    method SetName (line 97) | func (e *PodEndpoint) SetName(name string) *PodEndpoint {
    method SetContainerPort (line 103) | func (e *PodEndpoint) SetContainerPort(port int) *PodEndpoint {
    method SetHostPort (line 109) | func (e *PodEndpoint) SetHostPort(port int) *PodEndpoint {
    method AddProtocol (line 115) | func (e *PodEndpoint) AddProtocol(protocol string) *PodEndpoint {
    method Label (line 121) | func (e *PodEndpoint) Label(key, value string) *PodEndpoint {
  function NewPodNetwork (line 45) | func NewPodNetwork(name string) *PodNetwork {
  function NewPodEndpoint (line 53) | func NewPodEndpoint() *PodEndpoint {
  function NewBridgePodNetwork (line 61) | func NewBridgePodNetwork() *PodNetwork {
  function NewContainerPodNetwork (line 67) | func NewContainerPodNetwork(name string) *PodNetwork {
  function NewHostPodNetwork (line 73) | func NewHostPodNetwork() *PodNetwork {

FILE: offer.go
  type Offer (line 21) | type Offer struct
  type OfferResource (line 30) | type OfferResource struct
  type NumberRange (line 39) | type NumberRange struct
  type AgentAttribute (line 45) | type AgentAttribute struct

FILE: pod.go
  type Pod (line 24) | type Pod struct
    method Name (line 62) | func (p *Pod) Name(id string) *Pod {
    method SetUser (line 68) | func (p *Pod) SetUser(user string) *Pod {
    method EmptyLabels (line 74) | func (p *Pod) EmptyLabels() *Pod {
    method AddLabel (line 80) | func (p *Pod) AddLabel(key, value string) *Pod {
    method SetLabels (line 86) | func (p *Pod) SetLabels(labels map[string]string) *Pod {
    method EmptyEnvs (line 92) | func (p *Pod) EmptyEnvs() *Pod {
    method AddEnv (line 98) | func (p *Pod) AddEnv(name, value string) *Pod {
    method ExtendEnv (line 107) | func (p *Pod) ExtendEnv(env map[string]string) *Pod {
    method AddContainer (line 119) | func (p *Pod) AddContainer(container *PodContainer) *Pod {
    method EmptySecrets (line 125) | func (p *Pod) EmptySecrets() *Pod {
    method GetSecretSource (line 131) | func (p *Pod) GetSecretSource(name string) (string, error) {
    method AddSecret (line 139) | func (p *Pod) AddSecret(envVar, secretName, sourceName string) *Pod {
    method AddVolume (line 148) | func (p *Pod) AddVolume(vol *PodVolume) *Pod {
    method AddNetwork (line 154) | func (p *Pod) AddNetwork(net *PodNetwork) *Pod {
    method Count (line 160) | func (p *Pod) Count(count int) *Pod {
    method SetPodSchedulingPolicy (line 169) | func (p *Pod) SetPodSchedulingPolicy(policy *PodSchedulingPolicy) *Pod {
    method SetExecutorResources (line 175) | func (p *Pod) SetExecutorResources(resources *ExecutorResources) *Pod {
  type PodScalingPolicy (line 43) | type PodScalingPolicy struct
  function NewPod (line 50) | func NewPod() *Pod {
  method SupportsPods (line 182) | func (r *marathonClient) SupportsPods() (bool, error) {
  method Pod (line 196) | func (r *marathonClient) Pod(name string) (*Pod, error) {
  method Pods (line 207) | func (r *marathonClient) Pods() ([]Pod, error) {
  method CreatePod (line 217) | func (r *marathonClient) CreatePod(pod *Pod) (*Pod, error) {
  method DeletePod (line 227) | func (r *marathonClient) DeletePod(name string, force bool) (*Deployment...
  method UpdatePod (line 239) | func (r *marathonClient) UpdatePod(pod *Pod, force bool) (*Pod, error) {
  method PodVersions (line 251) | func (r *marathonClient) PodVersions(name string) ([]string, error) {
  method PodByVersion (line 262) | func (r *marathonClient) PodByVersion(name, version string) (*Pod, error) {
  function buildPodVersionURI (line 272) | func buildPodVersionURI(name string) string {
  function buildPodURI (line 276) | func buildPodURI(path string) string {

FILE: pod_container.go
  type PodContainer (line 20) | type PodContainer struct
    method SetName (line 73) | func (p *PodContainer) SetName(name string) *PodContainer {
    method SetCommand (line 79) | func (p *PodContainer) SetCommand(name string) *PodContainer {
    method CPUs (line 89) | func (p *PodContainer) CPUs(cpu float64) *PodContainer {
    method Memory (line 95) | func (p *PodContainer) Memory(memory float64) *PodContainer {
    method Storage (line 101) | func (p *PodContainer) Storage(disk float64) *PodContainer {
    method GPUs (line 107) | func (p *PodContainer) GPUs(gpu int32) *PodContainer {
    method AddEndpoint (line 113) | func (p *PodContainer) AddEndpoint(endpoint *PodEndpoint) *PodContainer {
    method SetImage (line 119) | func (p *PodContainer) SetImage(image *PodContainerImage) *PodContainer {
    method EmptyEnvs (line 125) | func (p *PodContainer) EmptyEnvs() *PodContainer {
    method AddEnv (line 131) | func (p *PodContainer) AddEnv(name, value string) *PodContainer {
    method ExtendEnv (line 140) | func (p *PodContainer) ExtendEnv(env map[string]string) *PodContainer {
    method AddSecret (line 151) | func (p *PodContainer) AddSecret(name, secretName string) *PodContainer {
    method SetUser (line 160) | func (p *PodContainer) SetUser(user string) *PodContainer {
    method SetHealthCheck (line 166) | func (p *PodContainer) SetHealthCheck(healthcheck *PodHealthCheck) *Po...
    method AddVolumeMount (line 172) | func (p *PodContainer) AddVolumeMount(mount *PodVolumeMount) *PodConta...
    method AddArtifact (line 178) | func (p *PodContainer) AddArtifact(artifact *PodArtifact) *PodContainer {
    method AddLabel (line 184) | func (p *PodContainer) AddLabel(key, value string) *PodContainer {
    method SetLifecycle (line 190) | func (p *PodContainer) SetLifecycle(lifecycle PodLifecycle) *PodContai...
  type PodLifecycle (line 37) | type PodLifecycle struct
  type PodCommand (line 42) | type PodCommand struct
  type PodExec (line 47) | type PodExec struct
  type PodArtifact (line 52) | type PodArtifact struct
  function NewPodContainer (line 61) | func NewPodContainer() *PodContainer {

FILE: pod_container_image.go
  type ImageType (line 20) | type ImageType
  constant ImageTypeDocker (line 24) | ImageTypeDocker ImageType = "DOCKER"
  constant ImageTypeAppC (line 27) | ImageTypeAppC ImageType = "APPC"
  type PodContainerImage (line 31) | type PodContainerImage struct
    method SetKind (line 44) | func (i *PodContainerImage) SetKind(typ ImageType) *PodContainerImage {
    method SetID (line 50) | func (i *PodContainerImage) SetID(id string) *PodContainerImage {
    method SetPullConfig (line 56) | func (i *PodContainerImage) SetPullConfig(pullConfig *PullConfig) *Pod...
  function NewPodContainerImage (line 39) | func NewPodContainerImage() *PodContainerImage {
  function NewDockerPodContainerImage (line 63) | func NewDockerPodContainerImage() *PodContainerImage {

FILE: pod_container_marshalling.go
  type PodContainerAlias (line 25) | type PodContainerAlias
  method UnmarshalJSON (line 30) | func (p *PodContainer) UnmarshalJSON(b []byte) error {
  method MarshalJSON (line 73) | func (p *PodContainer) MarshalJSON() ([]byte, error) {

FILE: pod_instance.go
  type PodInstance (line 25) | type PodInstance struct
  type PodInstanceStateHistory (line 35) | type PodInstanceStateHistory struct
  type PodInstanceID (line 42) | type PodInstanceID struct
  type PodAgentInfo (line 47) | type PodAgentInfo struct
  type PodTask (line 54) | type PodTask struct
  type PodTaskStatus (line 61) | type PodTaskStatus struct
  type PodTaskCondition (line 70) | type PodTaskCondition struct
  type PodNetworkInfo (line 75) | type PodNetworkInfo struct
  method DeletePodInstances (line 82) | func (r *marathonClient) DeletePodInstances(name string, instances []str...
  method DeletePodInstance (line 93) | func (r *marathonClient) DeletePodInstance(name, instance string) (*PodI...
  function buildPodInstancesURI (line 103) | func buildPodInstancesURI(path string) string {

FILE: pod_instance_status.go
  type PodInstanceState (line 20) | type PodInstanceState
  constant PodInstanceStatePending (line 24) | PodInstanceStatePending PodInstanceState = "PENDING"
  constant PodInstanceStateStaging (line 27) | PodInstanceStateStaging PodInstanceState = "STAGING"
  constant PodInstanceStateStable (line 30) | PodInstanceStateStable PodInstanceState = "STABLE"
  constant PodInstanceStateDegraded (line 33) | PodInstanceStateDegraded PodInstanceState = "DEGRADED"
  constant PodInstanceStateTerminal (line 36) | PodInstanceStateTerminal PodInstanceState = "TERMINAL"
  type PodInstanceStatus (line 40) | type PodInstanceStatus struct
  type PodNetworkStatus (line 56) | type PodNetworkStatus struct
  type StatusCondition (line 62) | type StatusCondition struct
  type ContainerStatus (line 71) | type ContainerStatus struct
  type ContainerTerminationState (line 86) | type ContainerTerminationState struct

FILE: pod_instance_test.go
  constant fakePodInstanceName (line 26) | fakePodInstanceName = "fake-pod.instance-dc6cfe60-6812-11e7-a18e-70b3d58...
  function TestDeletePodInstance (line 28) | func TestDeletePodInstance(t *testing.T) {
  function TestDeletePodInstances (line 37) | func TestDeletePodInstances(t *testing.T) {

FILE: pod_marshalling.go
  type PodAlias (line 25) | type PodAlias
  method UnmarshalJSON (line 30) | func (p *Pod) UnmarshalJSON(b []byte) error {
  method MarshalJSON (line 74) | func (p *Pod) MarshalJSON() ([]byte, error) {

FILE: pod_marshalling_test.go
  function TestPodEnvironmentVariableUnmarshal (line 28) | func TestPodEnvironmentVariableUnmarshal(t *testing.T) {
  function TestPodMalformedPayloadUnmarshal (line 48) | func TestPodMalformedPayloadUnmarshal(t *testing.T) {
  function TestPodEnvironmentVariableMarshal (line 86) | func TestPodEnvironmentVariableMarshal(t *testing.T) {
  function TestPodContainerArtifactBoolMarshal (line 104) | func TestPodContainerArtifactBoolMarshal(t *testing.T) {

FILE: pod_scheduling.go
  type PodBackoff (line 20) | type PodBackoff struct
    method SetBackoff (line 90) | func (p *PodBackoff) SetBackoff(backoffSeconds float64) *PodBackoff {
    method SetBackoffFactor (line 96) | func (p *PodBackoff) SetBackoffFactor(backoffFactor float64) *PodBacko...
    method SetMaxLaunchDelay (line 102) | func (p *PodBackoff) SetMaxLaunchDelay(maxLaunchDelaySeconds float64) ...
  type PodUpgrade (line 27) | type PodUpgrade struct
    method SetMinimumHealthCapacity (line 108) | func (p *PodUpgrade) SetMinimumHealthCapacity(capacity float64) *PodUp...
    method SetMaximumOverCapacity (line 114) | func (p *PodUpgrade) SetMaximumOverCapacity(capacity float64) *PodUpgr...
  type PodPlacement (line 33) | type PodPlacement struct
    method AddConstraint (line 64) | func (p *PodPlacement) AddConstraint(constraint Constraint) *PodPlacem...
  type PodSchedulingPolicy (line 39) | type PodSchedulingPolicy struct
    method SetBackoff (line 120) | func (p *PodSchedulingPolicy) SetBackoff(backoff *PodBackoff) *PodSche...
    method SetUpgrade (line 126) | func (p *PodSchedulingPolicy) SetUpgrade(upgrade *PodUpgrade) *PodSche...
    method SetPlacement (line 132) | func (p *PodSchedulingPolicy) SetPlacement(placement *PodPlacement) *P...
    method SetKillSelection (line 138) | func (p *PodSchedulingPolicy) SetKillSelection(killSelection string) *...
    method SetUnreachableStrategy (line 144) | func (p *PodSchedulingPolicy) SetUnreachableStrategy(strategy EnabledU...
    method SetUnreachableStrategyDisabled (line 152) | func (p *PodSchedulingPolicy) SetUnreachableStrategyDisabled() *PodSch...
  type Constraint (line 48) | type Constraint struct
  function NewPodPlacement (line 55) | func NewPodPlacement() *PodPlacement {
  function NewPodSchedulingPolicy (line 73) | func NewPodSchedulingPolicy() *PodSchedulingPolicy {
  function NewPodBackoff (line 80) | func NewPodBackoff() *PodBackoff {
  function NewPodUpgrade (line 85) | func NewPodUpgrade() *PodUpgrade {

FILE: pod_status.go
  type PodState (line 25) | type PodState
  constant PodStateDegraded (line 29) | PodStateDegraded PodState = "DEGRADED"
  constant PodStateStable (line 32) | PodStateStable PodState = "STABLE"
  constant PodStateTerminal (line 35) | PodStateTerminal PodState = "TERMINAL"
  type PodStatus (line 39) | type PodStatus struct
  type PodTerminationHistory (line 52) | type PodTerminationHistory struct
  type ContainerTerminationHistory (line 61) | type ContainerTerminationHistory struct
  method PodStatus (line 68) | func (r *marathonClient) PodStatus(name string) (*PodStatus, error) {
  method PodStatuses (line 79) | func (r *marathonClient) PodStatuses() ([]*PodStatus, error) {
  method WaitOnPod (line 90) | func (r *marathonClient) WaitOnPod(name string, timeout time.Duration) e...
  method PodIsRunning (line 95) | func (r *marathonClient) PodIsRunning(name string) bool {
  function buildPodStatusURI (line 106) | func buildPodStatusURI(path string) string {

FILE: pod_status_test.go
  function TestGetPodStatus (line 27) | func TestGetPodStatus(t *testing.T) {
  function TestGetAllPodStatus (line 39) | func TestGetAllPodStatus(t *testing.T) {
  function TestWaitOnPod (line 48) | func TestWaitOnPod(t *testing.T) {
  function TestPodIsRunning (line 56) | func TestPodIsRunning(t *testing.T) {

FILE: pod_test.go
  constant key (line 26) | key = "testKey"
  constant val (line 27) | val = "testValue"
  constant fakePodName (line 29) | fakePodName = "/fake-pod"
  constant secondFakePodName (line 30) | secondFakePodName = "/fake-pod2"
  function TestPodLabels (line 32) | func TestPodLabels(t *testing.T) {
  function TestPodEnvironmentVars (line 43) | func TestPodEnvironmentVars(t *testing.T) {
  function TestSecrets (line 59) | func TestSecrets(t *testing.T) {
  function TestSupportsPod (line 75) | func TestSupportsPod(t *testing.T) {
  function TestGetPod (line 91) | func TestGetPod(t *testing.T) {
  function TestGetAllPods (line 102) | func TestGetAllPods(t *testing.T) {
  function TestCreatePod (line 114) | func TestCreatePod(t *testing.T) {
  function TestUpdatePod (line 126) | func TestUpdatePod(t *testing.T) {
  function TestDeletePod (line 143) | func TestDeletePod(t *testing.T) {
  function TestVersions (line 155) | func TestVersions(t *testing.T) {
  function TestGetPodByVersion (line 164) | func TestGetPodByVersion(t *testing.T) {
  function TestAddPodImagePullConfig (line 173) | func TestAddPodImagePullConfig(t *testing.T) {

FILE: port_definition.go
  type PortDefinition (line 22) | type PortDefinition struct
    method SetPort (line 30) | func (p *PortDefinition) SetPort(port int) *PortDefinition {
    method EmptyPort (line 39) | func (p *PortDefinition) EmptyPort() *PortDefinition {
    method SetProtocol (line 47) | func (p *PortDefinition) SetProtocol(protocol string) *PortDefinition {
    method SetName (line 54) | func (p *PortDefinition) SetName(name string) *PortDefinition {
    method AddLabel (line 62) | func (p *PortDefinition) AddLabel(name, value string) *PortDefinition {
    method EmptyLabels (line 74) | func (p *PortDefinition) EmptyLabels() *PortDefinition {

FILE: queue.go
  type Queue (line 24) | type Queue struct
  type Item (line 29) | type Item struct
  type Delay (line 41) | type Delay struct
  type ProcessedOffersSummary (line 47) | type ProcessedOffersSummary struct
  type DeclinedOfferStep (line 57) | type DeclinedOfferStep struct
  type UnusedOffer (line 64) | type UnusedOffer struct
  method Queue (line 71) | func (r *marathonClient) Queue() (*Queue, error) {
  method DeleteQueueDelay (line 82) | func (r *marathonClient) DeleteQueueDelay(appID string) error {

FILE: queue_test.go
  function TestQueue (line 25) | func TestQueue(t *testing.T) {
  function TestDeleteQueueDelay (line 41) | func TestDeleteQueueDelay(t *testing.T) {

FILE: readiness.go
  type ReadinessCheck (line 22) | type ReadinessCheck struct
    method SetName (line 34) | func (rc *ReadinessCheck) SetName(name string) *ReadinessCheck {
    method SetProtocol (line 40) | func (rc *ReadinessCheck) SetProtocol(proto string) *ReadinessCheck {
    method SetPath (line 46) | func (rc *ReadinessCheck) SetPath(p string) *ReadinessCheck {
    method SetPortName (line 52) | func (rc *ReadinessCheck) SetPortName(name string) *ReadinessCheck {
    method SetInterval (line 58) | func (rc *ReadinessCheck) SetInterval(interval time.Duration) *Readine...
    method SetTimeout (line 65) | func (rc *ReadinessCheck) SetTimeout(timeout time.Duration) *Readiness...
    method SetHTTPStatusCodesForReady (line 73) | func (rc *ReadinessCheck) SetHTTPStatusCodesForReady(codes []int) *Rea...
    method SetPreserveLastResponse (line 80) | func (rc *ReadinessCheck) SetPreserveLastResponse(preserve bool) *Read...
  type ReadinessLastResponse (line 87) | type ReadinessLastResponse struct
  type ReadinessCheckResult (line 94) | type ReadinessCheckResult struct

FILE: readiness_test.go
  function TestReadinessCheck (line 26) | func TestReadinessCheck(t *testing.T) {

FILE: residency.go
  type TaskLostBehaviorType (line 22) | type TaskLostBehaviorType
  constant TaskLostBehaviorTypeWaitForever (line 26) | TaskLostBehaviorTypeWaitForever TaskLostBehaviorType = "WAIT_FOREVER"
  constant TaskLostBehaviorTypeRelaunchAfterTimeout (line 29) | TaskLostBehaviorTypeRelaunchAfterTimeout TaskLostBehaviorType = "RELAUNC...
  type Residency (line 33) | type Residency struct
    method SetTaskLostBehavior (line 39) | func (r *Residency) SetTaskLostBehavior(behavior TaskLostBehaviorType)...
    method SetRelaunchEscalationTimeout (line 45) | func (r *Residency) SetRelaunchEscalationTimeout(timeout time.Duration...

FILE: residency_test.go
  function TestResidency (line 26) | func TestResidency(t *testing.T) {

FILE: resources.go
  type ExecutorResources (line 20) | type ExecutorResources struct
  type Resources (line 27) | type Resources struct
  function NewResources (line 35) | func NewResources() *Resources {

FILE: subscription.go
  type Subscriptions (line 33) | type Subscriptions struct
  method Subscriptions (line 38) | func (r *marathonClient) Subscriptions() (*Subscriptions, error) {
  method AddEventsListener (line 49) | func (r *marathonClient) AddEventsListener(filter int) (EventsChannel, e...
  method RemoveEventsListener (line 70) | func (r *marathonClient) RemoveEventsListener(channel EventsChannel) {
  method SubscriptionURL (line 92) | func (r *marathonClient) SubscriptionURL() string {
  method registerSubscription (line 101) | func (r *marathonClient) registerSubscription() error {
  method registerCallbackSubscription (line 112) | func (r *marathonClient) registerCallbackSubscription() error {
  method registerSSESubscription (line 169) | func (r *marathonClient) registerSSESubscription() error {
  method connectToSSE (line 202) | func (r *marathonClient) connectToSSE() (*eventsource.Stream, error) {
  method listenToSSE (line 235) | func (r *marathonClient) listenToSSE(stream *eventsource.Stream) error {
  method Subscribe (line 251) | func (r *marathonClient) Subscribe(callback string) error {
  method Unsubscribe (line 259) | func (r *marathonClient) Unsubscribe(callback string) error {
  method HasSubscription (line 266) | func (r *marathonClient) HasSubscription(callback string) (bool, error) {
  method handleEvent (line 282) | func (r *marathonClient) handleEvent(content string) error {
  method handleCallbackEvent (line 324) | func (r *marathonClient) handleCallbackEvent(writer http.ResponseWriter,...

FILE: subscription_test.go
  constant eventPublishTimeout (line 30) | eventPublishTimeout time.Duration = 250 * time.Millisecond
  constant SSEConnectWaitTime (line 31) | SSEConnectWaitTime  time.Duration = 250 * time.Millisecond
  type testCaseList (line 34) | type testCaseList
    method find (line 36) | func (l testCaseList) find(name string) *testCase {
  type testCase (line 45) | type testCase struct
  function TestSubscriptions (line 274) | func TestSubscriptions(t *testing.T) {
  function TestSubscribe (line 285) | func TestSubscribe(t *testing.T) {
  function TestUnsubscribe (line 293) | func TestUnsubscribe(t *testing.T) {
  function TestSSEWithGlobalTimeout (line 301) | func TestSSEWithGlobalTimeout(t *testing.T) {
  function TestEventStreamEventsReceived (line 317) | func TestEventStreamEventsReceived(t *testing.T) {
  function TestConnectToSSESuccess (line 377) | func TestConnectToSSESuccess(t *testing.T) {
  function TestConnectToSSEFailure (line 398) | func TestConnectToSSEFailure(t *testing.T) {
  function TestRegisterSEESubscriptionReconnectsStreamOnError (line 415) | func TestRegisterSEESubscriptionReconnectsStreamOnError(t *testing.T) {

FILE: task.go
  type Tasks (line 25) | type Tasks struct
  type Task (line 30) | type Task struct
    method HasHealthCheckResults (line 76) | func (r *Task) HasHealthCheckResults() bool {
    method allHealthChecksAlive (line 216) | func (r *Task) allHealthChecksAlive() bool {
  type IPAddress (line 46) | type IPAddress struct
  type AllTasksOpts (line 54) | type AllTasksOpts struct
  type KillApplicationTasksOpts (line 61) | type KillApplicationTasksOpts struct
  type KillTaskOpts (line 69) | type KillTaskOpts struct
  method AllTasks (line 82) | func (r *marathonClient) AllTasks(opts *AllTasksOpts) (*Tasks, error) {
  method Tasks (line 98) | func (r *marathonClient) Tasks(id string) (*Tasks, error) {
  method KillApplicationTasks (line 110) | func (r *marathonClient) KillApplicationTasks(id string, opts *KillAppli...
  method KillTask (line 128) | func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*T...
  method KillTasks (line 153) | func (r *marathonClient) KillTasks(tasks []string, opts *KillTaskOpts) e...
  method TaskEndpoints (line 180) | func (r *marathonClient) TaskEndpoints(name string, port int, healthChec...

FILE: task_test.go
  function TestHasHealthCheckResults (line 25) | func TestHasHealthCheckResults(t *testing.T) {
  function TestAllTasks (line 32) | func TestAllTasks(t *testing.T) {
  function TestTasks (line 49) | func TestTasks(t *testing.T) {
  function TestKillApplicationTasks (line 60) | func TestKillApplicationTasks(t *testing.T) {
  function TestKillTask (line 69) | func TestKillTask(t *testing.T) {
  function TestKillTasks (line 88) | func TestKillTasks(t *testing.T) {
  function TestTaskEndpoints (line 96) | func TestTaskEndpoints(t *testing.T) {

FILE: testing_utils_test.go
  constant fakeMarathonURL (line 35) | fakeMarathonURL         = "http://127.0.0.1:3000,127.0.0.1:3000,127.0.0....
  constant fakeMarathonURLWithPath (line 36) | fakeMarathonURLWithPath = "http://127.0.0.1:3000/path,127.0.0.1:3000/pat...
  constant fakeGroupName (line 37) | fakeGroupName           = "/test"
  constant fakeGroupName1 (line 38) | fakeGroupName1          = "/qa/product/1"
  constant fakeAppName (line 39) | fakeAppName             = "/fake-app"
  constant fakeTaskID (line 40) | fakeTaskID              = "fake-app.fake-task"
  constant fakeAppNameBroken (line 41) | fakeAppNameBroken       = "/fake-app-broken"
  constant fakeDeploymentID (line 42) | fakeDeploymentID        = "867ed450-f6a8-4d33-9b0e-e11c5513990b"
  constant fakeAppNameUnhealthy (line 43) | fakeAppNameUnhealthy    = "/no-health-check-results-app"
  type indexedResponse (line 51) | type indexedResponse struct
  type responseIndices (line 57) | type responseIndices struct
  function newResponseIndices (line 62) | func newResponseIndices() *responseIndices {
  type restMethod (line 67) | type restMethod struct
  type serverConfig (line 83) | type serverConfig struct
  type configContainer (line 96) | type configContainer struct
  type fakeServer (line 101) | type fakeServer struct
    method PublishEvent (line 320) | func (s *fakeServer) PublishEvent(event string) {
    method Close (line 324) | func (s *fakeServer) Close() {
  type endpoint (line 109) | type endpoint struct
    method Close (line 329) | func (e *endpoint) Close() {
  type fakeEvent (line 117) | type fakeEvent struct
    method Id (line 308) | func (t fakeEvent) Id() string {
    method Event (line 312) | func (t fakeEvent) Event() string {
    method Data (line 316) | func (t fakeEvent) Data() string {
  function getTestURL (line 121) | func getTestURL(urlString string) string {
  function newFakeMarathonEndpoint (line 129) | func newFakeMarathonEndpoint(t *testing.T, configs *configContainer) *en...
  function basicAuthMiddleware (line 203) | func basicAuthMiddleware(server *serverConfig, next http.HandlerFunc) fu...
  function authMiddleware (line 227) | func authMiddleware(server *serverConfig, next http.HandlerFunc) func(ht...
  function initFakeMarathonResponses (line 266) | func initFakeMarathonResponses(t *testing.T) {
  function fakeResponseMapKey (line 304) | func fakeResponseMapKey(method, uri, scope string) string {

FILE: unreachable_strategy.go
  constant UnreachableStrategyAbsenceReasonDisabled (line 25) | UnreachableStrategyAbsenceReasonDisabled = "disabled"
  type UnreachableStrategy (line 28) | type UnreachableStrategy struct
    method UnmarshalJSON (line 44) | func (us *UnreachableStrategy) UnmarshalJSON(b []byte) error {
    method MarshalJSON (line 60) | func (us *UnreachableStrategy) MarshalJSON() ([]byte, error) {
    method SetInactiveAfterSeconds (line 69) | func (us *UnreachableStrategy) SetInactiveAfterSeconds(cap float64) *U...
    method SetExpungeAfterSeconds (line 75) | func (us *UnreachableStrategy) SetExpungeAfterSeconds(cap float64) *Un...
  type EnabledUnreachableStrategy (line 34) | type EnabledUnreachableStrategy struct
  type unreachableStrategy (line 39) | type unreachableStrategy

FILE: unreachable_strategy_test.go
  function TestUnreachableStrategyAPI (line 27) | func TestUnreachableStrategyAPI(t *testing.T) {
  function TestUnreachableStrategyUnmarshalEnabled (line 44) | func TestUnreachableStrategyUnmarshalEnabled(t *testing.T) {
  function TestUnreachableStrategyUnmarshalNonEnabled (line 70) | func TestUnreachableStrategyUnmarshalNonEnabled(t *testing.T) {
  function TestUnreachableStrategyUnmarshalIllegal (line 90) | func TestUnreachableStrategyUnmarshalIllegal(t *testing.T) {
  function TestUnreachableStrategyMarshal (line 96) | func TestUnreachableStrategyMarshal(t *testing.T) {
  function float64p (line 131) | func float64p(f float64) *float64 {

FILE: upgrade_strategy.go
  type UpgradeStrategy (line 20) | type UpgradeStrategy struct
    method SetMinimumHealthCapacity (line 26) | func (us *UpgradeStrategy) SetMinimumHealthCapacity(cap float64) *Upgr...
    method SetMaximumOverCapacity (line 32) | func (us *UpgradeStrategy) SetMaximumOverCapacity(cap float64) *Upgrad...

FILE: utils.go
  type atomicSwitch (line 32) | type atomicSwitch
    method IsSwitched (line 34) | func (r *atomicSwitch) IsSwitched() bool {
    method SwitchOn (line 38) | func (r *atomicSwitch) SwitchOn() {
    method SwitchedOff (line 42) | func (r *atomicSwitch) SwitchedOff() {
  function validateID (line 46) | func validateID(id string) string {
  function trimRootPath (line 53) | func trimRootPath(id string) string {
  function deadline (line 60) | func deadline(timeout time.Duration, work func(chan bool) error) error {
  function getInterfaceAddress (line 80) | func getInterfaceAddress(name string) (string, error) {
  function contains (line 102) | func contains(elements []string, value string) bool {
  function parseIPAddr (line 111) | func parseIPAddr(addr net.Addr) string {
  function addOptions (line 117) | func addOptions(s string, opt interface{}) (string, error) {
  function Bool (line 138) | func Bool(b bool) *bool {

FILE: utils_test.go
  type stubAddr (line 27) | type stubAddr struct
    method Network (line 31) | func (sa stubAddr) Network() string {
    method String (line 35) | func (sa stubAddr) String() string {
  function TestUtilsAtomicIsSwitched (line 39) | func TestUtilsAtomicIsSwitched(t *testing.T) {
  function TestUtilsAtomicIsSwitchedOff (line 46) | func TestUtilsAtomicIsSwitchedOff(t *testing.T) {
  function TestUtilsDeadline (line 55) | func TestUtilsDeadline(t *testing.T) {
  function TestUtilsContains (line 71) | func TestUtilsContains(t *testing.T) {
  function TestUtilsValidateID (line 77) | func TestUtilsValidateID(t *testing.T) {
  function TestUtilsGetInterfaceAddress (line 84) | func TestUtilsGetInterfaceAddress(t *testing.T) {
  function TestUtilsTrimRootPath (line 104) | func TestUtilsTrimRootPath(t *testing.T) {
  function TestParseIPAddr (line 111) | func TestParseIPAddr(t *testing.T) {

FILE: volume.go
  type PodVolume (line 20) | type PodVolume struct
    method SetPersistentVolume (line 59) | func (pv *PodVolume) SetPersistentVolume(p *PersistentVolume) *PodVolu...
  type PodVolumeMount (line 28) | type PodVolumeMount struct
  function NewPodVolume (line 35) | func NewPodVolume(name, path string) *PodVolume {
  function NewPodVolumeSecret (line 43) | func NewPodVolumeSecret(name, secretPath string) *PodVolume {
  function NewPodVolumeMount (line 51) | func NewPodVolumeMount(name, mount string) *PodVolumeMount {
Condensed preview — 79 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (461K chars).
[
  {
    "path": ".gitignore",
    "chars": 640,
    "preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\ngo-marathon.iml\n.idea/\nGemfile.lock\nthin."
  },
  {
    "path": ".travis.yml",
    "chars": 502,
    "preview": "env:\n  global:\n    secure: YiSCbBUz0VMONSBZ6TfRaSM9bFBuT5xvaknt9WxWczPSiSgiY8+dGYlsOaX2jzI26J4zA8KxIyxOihN1UE28tkkGoXRkR"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 5644,
    "preview": "# Change Log\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1119,
    "preview": "# Contribution Guidelines\n\n## Pre-Development\n- Look for an existing Github issue describing the bug you have found/feat"
  },
  {
    "path": "LICENSE",
    "chars": 10255,
    "preview": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AN"
  },
  {
    "path": "Makefile",
    "chars": 1528,
    "preview": "#\n#   Author: Rohith (gambol99@gmail.com)\n#   Date: 2015-02-10 15:35:14 +0000 (Tue, 10 Feb 2015)\n#\n#  vim:ts=2:sw=2:et\n#"
  },
  {
    "path": "README.md",
    "chars": 14504,
    "preview": "[![Build Status](https://travis-ci.org/gambol99/go-marathon.svg?branch=master)](https://travis-ci.org/gambol99/go-marath"
  },
  {
    "path": "application.go",
    "chars": 31662,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "application_marshalling.go",
    "chars": 3421,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "application_marshalling_test.go",
    "chars": 3087,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "application_test.go",
    "chars": 24984,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "client.go",
    "chars": 15559,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "client_test.go",
    "chars": 7537,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "cluster.go",
    "chars": 5131,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "cluster_test.go",
    "chars": 5102,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "config.go",
    "chars": 2241,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "const.go",
    "chars": 1545,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "deployment.go",
    "chars": 5255,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "deployment_test.go",
    "chars": 2869,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "docker.go",
    "chars": 15032,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "docker_test.go",
    "chars": 6316,
    "preview": "/*\nCopyright 2015 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "error.go",
    "chars": 6218,
    "preview": "/*\nCopyright 2015 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "error_test.go",
    "chars": 5558,
    "preview": "/*\nCopyright 2015 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "events.go",
    "chars": 13471,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/Makefile",
    "chars": 77,
    "preview": "all:\n\tfind * -type d -exec bash -exc \"cd {}; go build . || kill $${PPID}\" \\;\n"
  },
  {
    "path": "examples/applications/main.go",
    "chars": 3355,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/docker-compose.yml",
    "chars": 1357,
    "preview": "# Based on https://github.com/meltwater/docker-mesos\n\nzookeeper:\n  image: mesoscloud/zookeeper:3.4.6-centos-7\n  ports:\n "
  },
  {
    "path": "examples/events_callback_transport/main.go",
    "chars": 2435,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/events_sse_transport/main.go",
    "chars": 2174,
    "preview": "/*\nCopyright 2015 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/glog/main.go",
    "chars": 1397,
    "preview": "/*\nCopyright 2015 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/groups/main.go",
    "chars": 3632,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/multiple_endpoints/main.go",
    "chars": 1408,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/pods/main.go",
    "chars": 3919,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/queue/main.go",
    "chars": 2141,
    "preview": "/*\nCopyright 2016 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "examples/tasks/main.go",
    "chars": 1570,
    "preview": "/*\nCopyright 2015 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "group.go",
    "chars": 7345,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "group_test.go",
    "chars": 2871,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "health.go",
    "chars": 7674,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "health_test.go",
    "chars": 1472,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "info.go",
    "chars": 3626,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "info_test.go",
    "chars": 1462,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "last_task_failure.go",
    "chars": 1077,
    "preview": "/*\nCopyright 2015 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "network.go",
    "chars": 3500,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "offer.go",
    "chars": 1798,
    "preview": "/*\nCopyright 2019 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod.go",
    "chars": 7446,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_container.go",
    "chars": 5540,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_container_image.go",
    "chars": 1906,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_container_marshalling.go",
    "chars": 3050,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_instance.go",
    "chars": 3453,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_instance_status.go",
    "chars": 3766,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_instance_test.go",
    "chars": 1427,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_marshalling.go",
    "chars": 3209,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_marshalling_test.go",
    "chars": 3746,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_scheduling.go",
    "chars": 5337,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_status.go",
    "chars": 3690,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_status_test.go",
    "chars": 1797,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "pod_test.go",
    "chars": 4462,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "port_definition.go",
    "chars": 2336,
    "preview": "/*\nCopyright 2016 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "queue.go",
    "chars": 3048,
    "preview": "/*\nCopyright 2016 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "queue_test.go",
    "chars": 1279,
    "preview": "/*\nCopyright 2016 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "readiness.go",
    "chars": 3273,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "readiness_test.go",
    "chars": 1461,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "residency.go",
    "chars": 1924,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "residency_test.go",
    "chars": 1385,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "resources.go",
    "chars": 1156,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "subscription.go",
    "chars": 10027,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "subscription_test.go",
    "chars": 12162,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "task.go",
    "chars": 7326,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "task_test.go",
    "chars": 3327,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "testing_utils_test.go",
    "chars": 9091,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "tests/app-definitions/TestApplicationString-1.5-output.json",
    "chars": 908,
    "preview": "{\n  \"id\": \"/my-app\",\n  \"args\": [\n    \"/usr/sbin/apache2ctl\",\n    \"-D\",\n    \"FOREGROUND\"\n  ],\n  \"container\": {\n    \"type\""
  },
  {
    "path": "tests/app-definitions/TestApplicationString-output.json",
    "chars": 893,
    "preview": "{\n  \"id\": \"/my-app\",\n  \"args\": [\n    \"/usr/sbin/apache2ctl\",\n    \"-D\",\n    \"FOREGROUND\"\n  ],\n  \"container\": {\n    \"type\""
  },
  {
    "path": "tests/rest-api/methods.yml",
    "chars": 57867,
    "preview": "- uri: /ping\n  method: GET\n  content: |\n    pong\n- uri: /v2/apps/fake-app/versions\n  method: GET\n  content: |\n    {\n    "
  },
  {
    "path": "unreachable_strategy.go",
    "chars": 2687,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "unreachable_strategy_test.go",
    "chars": 3552,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "upgrade_strategy.go",
    "chars": 1231,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "utils.go",
    "chars": 2937,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "utils_test.go",
    "chars": 2869,
    "preview": "/*\nCopyright 2014 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "volume.go",
    "chars": 1823,
    "preview": "/*\nCopyright 2017 The go-marathon Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"Lic"
  }
]

About this extraction

This page contains the full source code of the gambol99/go-marathon GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 79 files (414.9 KB), approximately 109.6k tokens, and a symbol index with 807 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!