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
================================================
[](https://travis-ci.org/gambol99/go-marathon)
[](http://godoc.org/github.com/gambol99/go-marathon)
[](https://goreportcard.com/report/github.com/katallaxie/go-marathon)
[](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 = ¶meters
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
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
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": "[](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.