1/*
2Copyright 2014 The go-marathon Authors All rights reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package marathon
18
19import (
20	"encoding/json"
21	"errors"
22	"fmt"
23	"net/url"
24	"time"
25)
26
27var (
28	// ErrNoApplicationContainer is thrown when a container has been specified yet
29	ErrNoApplicationContainer = errors.New("you have not specified a docker container yet")
30)
31
32// Applications is a collection of applications
33type Applications struct {
34	Apps []Application `json:"apps"`
35}
36
37// IPAddressPerTask is used by IP-per-task functionality https://mesosphere.github.io/marathon/docs/ip-per-task.html
38type IPAddressPerTask struct {
39	Groups      *[]string          `json:"groups,omitempty"`
40	Labels      *map[string]string `json:"labels,omitempty"`
41	Discovery   *Discovery         `json:"discovery,omitempty"`
42	NetworkName string             `json:"networkName,omitempty"`
43}
44
45// Discovery provides info about ports expose by IP-per-task functionality
46type Discovery struct {
47	Ports *[]Port `json:"ports,omitempty"`
48}
49
50// Port provides info about ports used by IP-per-task
51type Port struct {
52	Number   int    `json:"number,omitempty"`
53	Name     string `json:"name,omitempty"`
54	Protocol string `json:"protocol,omitempty"`
55}
56
57// Application is the definition for an application in marathon
58type Application struct {
59	ID          string        `json:"id,omitempty"`
60	Cmd         *string       `json:"cmd,omitempty"`
61	Args        *[]string     `json:"args,omitempty"`
62	Constraints *[][]string   `json:"constraints,omitempty"`
63	Container   *Container    `json:"container,omitempty"`
64	CPUs        float64       `json:"cpus,omitempty"`
65	GPUs        *float64      `json:"gpus,omitempty"`
66	Disk        *float64      `json:"disk,omitempty"`
67	Networks    *[]PodNetwork `json:"networks,omitempty"`
68
69	// Contains non-secret environment variables. Secrets environment variables are part of the Secrets map.
70	Env                        *map[string]string  `json:"-"`
71	Executor                   *string             `json:"executor,omitempty"`
72	HealthChecks               *[]HealthCheck      `json:"healthChecks,omitempty"`
73	ReadinessChecks            *[]ReadinessCheck   `json:"readinessChecks,omitempty"`
74	Instances                  *int                `json:"instances,omitempty"`
75	Mem                        *float64            `json:"mem,omitempty"`
76	Tasks                      []*Task             `json:"tasks,omitempty"`
77	Ports                      []int               `json:"ports"`
78	PortDefinitions            *[]PortDefinition   `json:"portDefinitions,omitempty"`
79	RequirePorts               *bool               `json:"requirePorts,omitempty"`
80	BackoffSeconds             *float64            `json:"backoffSeconds,omitempty"`
81	BackoffFactor              *float64            `json:"backoffFactor,omitempty"`
82	MaxLaunchDelaySeconds      *float64            `json:"maxLaunchDelaySeconds,omitempty"`
83	TaskKillGracePeriodSeconds *float64            `json:"taskKillGracePeriodSeconds,omitempty"`
84	Deployments                []map[string]string `json:"deployments,omitempty"`
85	// Available when embedding readiness information through query parameter.
86	ReadinessCheckResults *[]ReadinessCheckResult `json:"readinessCheckResults,omitempty"`
87	Dependencies          []string                `json:"dependencies"`
88	TasksRunning          int                     `json:"tasksRunning,omitempty"`
89	TasksStaged           int                     `json:"tasksStaged,omitempty"`
90	TasksHealthy          int                     `json:"tasksHealthy,omitempty"`
91	TasksUnhealthy        int                     `json:"tasksUnhealthy,omitempty"`
92	TaskStats             map[string]TaskStats    `json:"taskStats,omitempty"`
93	User                  string                  `json:"user,omitempty"`
94	UpgradeStrategy       *UpgradeStrategy        `json:"upgradeStrategy,omitempty"`
95	UnreachableStrategy   *UnreachableStrategy    `json:"unreachableStrategy,omitempty"`
96	KillSelection         string                  `json:"killSelection,omitempty"`
97	Uris                  *[]string               `json:"uris,omitempty"`
98	Version               string                  `json:"version,omitempty"`
99	VersionInfo           *VersionInfo            `json:"versionInfo,omitempty"`
100	Labels                *map[string]string      `json:"labels,omitempty"`
101	AcceptedResourceRoles []string                `json:"acceptedResourceRoles,omitempty"`
102	LastTaskFailure       *LastTaskFailure        `json:"lastTaskFailure,omitempty"`
103	Fetch                 *[]Fetch                `json:"fetch,omitempty"`
104	IPAddressPerTask      *IPAddressPerTask       `json:"ipAddress,omitempty"`
105	Residency             *Residency              `json:"residency,omitempty"`
106	Secrets               *map[string]Secret      `json:"-"`
107}
108
109// ApplicationVersions is a collection of application versions for a specific app in marathon
110type ApplicationVersions struct {
111	Versions []string `json:"versions"`
112}
113
114// ApplicationVersion is the application version response from marathon
115type ApplicationVersion struct {
116	Version string `json:"version"`
117}
118
119// VersionInfo is the application versioning details from marathon
120type VersionInfo struct {
121	LastScalingAt      string `json:"lastScalingAt,omitempty"`
122	LastConfigChangeAt string `json:"lastConfigChangeAt,omitempty"`
123}
124
125// Fetch will download URI before task starts
126type Fetch struct {
127	URI        string `json:"uri"`
128	Executable bool   `json:"executable"`
129	Extract    bool   `json:"extract"`
130	Cache      bool   `json:"cache"`
131}
132
133// GetAppOpts contains a payload for Application method
134//		embed:	Embeds nested resources that match the supplied path.
135// 				You can specify this parameter multiple times with different values
136type GetAppOpts struct {
137	Embed []string `url:"embed,omitempty"`
138}
139
140// DeleteAppOpts contains a payload for DeleteApplication method
141//		force:		overrides a currently running deployment.
142type DeleteAppOpts struct {
143	Force bool `url:"force,omitempty"`
144}
145
146// TaskStats is a container for Stats
147type TaskStats struct {
148	Stats Stats `json:"stats"`
149}
150
151// Stats is a collection of aggregate statistics about an application's tasks
152type Stats struct {
153	Counts   map[string]int     `json:"counts"`
154	LifeTime map[string]float64 `json:"lifeTime"`
155}
156
157// Secret is the environment variable and secret store path associated with a secret.
158// The value for EnvVar is populated from the env field, and Source is populated from
159// the secrets field of the application json.
160type Secret struct {
161	EnvVar string
162	Source string
163}
164
165// SetIPAddressPerTask defines that the application will have a IP address defines by a external agent.
166// This configuration is not allowed to be used with Port or PortDefinitions. Thus, the implementation
167// clears both.
168func (r *Application) SetIPAddressPerTask(ipAddressPerTask IPAddressPerTask) *Application {
169	r.Ports = make([]int, 0)
170	r.EmptyPortDefinitions()
171	r.IPAddressPerTask = &ipAddressPerTask
172
173	return r
174}
175
176// NewDockerApplication creates a default docker application
177func NewDockerApplication() *Application {
178	application := new(Application)
179	application.Container = NewDockerContainer()
180	return application
181}
182
183// Name sets the name / ID of the application i.e. the identifier for this application
184func (r *Application) Name(id string) *Application {
185	r.ID = validateID(id)
186	return r
187}
188
189// Command sets the cmd of the application
190func (r *Application) Command(cmd string) *Application {
191	r.Cmd = &cmd
192	return r
193}
194
195// CPU set the amount of CPU shares per instance which is assigned to the application
196//		cpu:	the CPU shared (check Docker docs) per instance
197func (r *Application) CPU(cpu float64) *Application {
198	r.CPUs = cpu
199	return r
200}
201
202// SetGPUs set the amount of GPU per instance which is assigned to the application
203//		gpu:	the GPU (check MESOS docs) per instance
204func (r *Application) SetGPUs(gpu float64) *Application {
205	r.GPUs = &gpu
206	return r
207}
208
209// EmptyGPUs explicitly empties GPUs -- use this if you need to empty
210// gpus of an application that already has gpus set (setting port definitions to nil will
211// keep the current value)
212func (r *Application) EmptyGPUs() *Application {
213	g := 0.0
214	r.GPUs = &g
215	return r
216}
217
218// Storage sets the amount of disk space the application is assigned, which for docker
219// application I don't believe is relevant
220//		disk:	the disk space in MB
221func (r *Application) Storage(disk float64) *Application {
222	r.Disk = &disk
223	return r
224}
225
226// AllTaskRunning checks to see if all the application tasks are running, i.e. the instances is equal
227// to the number of running tasks
228func (r *Application) AllTaskRunning() bool {
229	if r.Instances == nil || *r.Instances == 0 {
230		return true
231	}
232	if r.Tasks == nil {
233		return false
234	}
235	if r.TasksRunning == *r.Instances {
236		return true
237	}
238	return false
239}
240
241// DependsOn adds one or more dependencies for this application. Note, if you want to wait for
242// an application dependency to actually be UP, i.e. not just deployed, you need a health check
243// on the dependant app.
244//		names:	the application id(s) this application depends on
245func (r *Application) DependsOn(names ...string) *Application {
246	if r.Dependencies == nil {
247		r.Dependencies = make([]string, 0)
248	}
249	r.Dependencies = append(r.Dependencies, names...)
250
251	return r
252}
253
254// Memory sets he amount of memory the application can consume per instance
255//		memory:	the amount of MB to assign
256func (r *Application) Memory(memory float64) *Application {
257	r.Mem = &memory
258
259	return r
260}
261
262// AddPortDefinition adds a port definition. Port definitions are used to define ports that
263// should be considered part of a resource. They are necessary when you are using HOST
264// networking and no port mappings are specified.
265func (r *Application) AddPortDefinition(portDefinition PortDefinition) *Application {
266	if r.PortDefinitions == nil {
267		r.EmptyPortDefinitions()
268	}
269
270	portDefinitions := *r.PortDefinitions
271	portDefinitions = append(portDefinitions, portDefinition)
272	r.PortDefinitions = &portDefinitions
273	return r
274}
275
276// EmptyPortDefinitions explicitly empties port definitions -- use this if you need to empty
277// port definitions of an application that already has port definitions set (setting port definitions to nil will
278// keep the current value)
279func (r *Application) EmptyPortDefinitions() *Application {
280	r.PortDefinitions = &[]PortDefinition{}
281
282	return r
283}
284
285// Count sets the number of instances of the application to run
286//		count:	the number of instances to run
287func (r *Application) Count(count int) *Application {
288	r.Instances = &count
289
290	return r
291}
292
293// SetTaskKillGracePeriod sets the number of seconds between escalating from SIGTERM to SIGKILL
294// when signalling tasks to terminate. Using this grace period, tasks should perform orderly shut down
295// immediately upon receiving SIGTERM.
296//		seconds:	the number of seconds
297func (r *Application) SetTaskKillGracePeriod(seconds float64) *Application {
298	r.TaskKillGracePeriodSeconds = &seconds
299
300	return r
301}
302
303// AddArgs adds one or more arguments to the applications
304//		arguments:	the argument(s) you are adding
305func (r *Application) AddArgs(arguments ...string) *Application {
306	if r.Args == nil {
307		r.EmptyArgs()
308	}
309
310	args := *r.Args
311	args = append(args, arguments...)
312	r.Args = &args
313
314	return r
315}
316
317// EmptyArgs explicitly empties arguments -- use this if you need to empty
318// arguments of an application that already has arguments set (setting args to nil will
319// keep the current value)
320func (r *Application) EmptyArgs() *Application {
321	r.Args = &[]string{}
322
323	return r
324}
325
326// AddConstraint adds a new constraint
327//		constraints:	the constraint definition, one constraint per array element
328func (r *Application) AddConstraint(constraints ...string) *Application {
329	if r.Constraints == nil {
330		r.EmptyConstraints()
331	}
332
333	c := *r.Constraints
334	c = append(c, constraints)
335	r.Constraints = &c
336
337	return r
338}
339
340// EmptyConstraints explicitly empties constraints -- use this if you need to empty
341// constraints of an application that already has constraints set (setting constraints to nil will
342// keep the current value)
343func (r *Application) EmptyConstraints() *Application {
344	r.Constraints = &[][]string{}
345
346	return r
347}
348
349// AddLabel adds a label to the application
350//		name:	the name of the label
351//		value: value for this label
352func (r *Application) AddLabel(name, value string) *Application {
353	if r.Labels == nil {
354		r.EmptyLabels()
355	}
356	(*r.Labels)[name] = value
357
358	return r
359}
360
361// EmptyLabels explicitly empties the labels -- use this if you need to empty
362// the labels of an application that already has labels set (setting labels to nil will
363// keep the current value)
364func (r *Application) EmptyLabels() *Application {
365	r.Labels = &map[string]string{}
366
367	return r
368}
369
370// AddEnv adds an environment variable to the application
371// name:	the name of the variable
372// value:	go figure, the value associated to the above
373func (r *Application) AddEnv(name, value string) *Application {
374	if r.Env == nil {
375		r.EmptyEnvs()
376	}
377	(*r.Env)[name] = value
378
379	return r
380}
381
382// EmptyEnvs explicitly empties the envs -- use this if you need to empty
383// the environments of an application that already has environments set (setting env to nil will
384// keep the current value)
385func (r *Application) EmptyEnvs() *Application {
386	r.Env = &map[string]string{}
387
388	return r
389}
390
391// AddSecret adds a secret declaration
392// envVar: the name of the environment variable
393// name:	the name of the secret
394// source:	the source ID of the secret
395func (r *Application) AddSecret(envVar, name, source string) *Application {
396	if r.Secrets == nil {
397		r.EmptySecrets()
398	}
399	(*r.Secrets)[name] = Secret{EnvVar: envVar, Source: source}
400
401	return r
402}
403
404// EmptySecrets explicitly empties the secrets -- use this if you need to empty
405// the secrets of an application that already has secrets set (setting secrets to nil will
406// keep the current value)
407func (r *Application) EmptySecrets() *Application {
408	r.Secrets = &map[string]Secret{}
409
410	return r
411}
412
413// SetExecutor sets the executor
414func (r *Application) SetExecutor(executor string) *Application {
415	r.Executor = &executor
416
417	return r
418}
419
420// AddHealthCheck adds a health check
421// 	healthCheck the health check that should be added
422func (r *Application) AddHealthCheck(healthCheck HealthCheck) *Application {
423	if r.HealthChecks == nil {
424		r.EmptyHealthChecks()
425	}
426
427	healthChecks := *r.HealthChecks
428	healthChecks = append(healthChecks, healthCheck)
429	r.HealthChecks = &healthChecks
430
431	return r
432}
433
434// EmptyHealthChecks explicitly empties health checks -- use this if you need to empty
435// health checks of an application that already has health checks set (setting health checks to nil will
436// keep the current value)
437func (r *Application) EmptyHealthChecks() *Application {
438	r.HealthChecks = &[]HealthCheck{}
439
440	return r
441}
442
443// HasHealthChecks is a helper method, used to check if an application has health checks
444func (r *Application) HasHealthChecks() bool {
445	return r.HealthChecks != nil && len(*r.HealthChecks) > 0
446}
447
448// AddReadinessCheck adds a readiness check.
449func (r *Application) AddReadinessCheck(readinessCheck ReadinessCheck) *Application {
450	if r.ReadinessChecks == nil {
451		r.EmptyReadinessChecks()
452	}
453
454	readinessChecks := *r.ReadinessChecks
455	readinessChecks = append(readinessChecks, readinessCheck)
456	r.ReadinessChecks = &readinessChecks
457
458	return r
459}
460
461// EmptyReadinessChecks empties the readiness checks.
462func (r *Application) EmptyReadinessChecks() *Application {
463	r.ReadinessChecks = &[]ReadinessCheck{}
464
465	return r
466}
467
468// DeploymentIDs retrieves the application deployments IDs
469func (r *Application) DeploymentIDs() []*DeploymentID {
470	var deployments []*DeploymentID
471
472	if r.Deployments == nil {
473		return deployments
474	}
475
476	// step: extract the deployment id from the result
477	for _, deploy := range r.Deployments {
478		if id, found := deploy["id"]; found {
479			deployment := &DeploymentID{
480				Version:      r.Version,
481				DeploymentID: id,
482			}
483			deployments = append(deployments, deployment)
484		}
485	}
486
487	return deployments
488}
489
490// CheckHTTP adds a HTTP check to an application
491//		port: 		the port the check should be checking
492// 		interval:	the interval in seconds the check should be performed
493func (r *Application) CheckHTTP(path string, port, interval int) (*Application, error) {
494	if r.Container == nil || r.Container.Docker == nil {
495		return nil, ErrNoApplicationContainer
496	}
497	// step: get the port index
498	portIndex, err := r.Container.Docker.ServicePortIndex(port)
499	if err != nil {
500		portIndex, err = r.Container.ServicePortIndex(port)
501		if err != nil {
502			return nil, err
503		}
504	}
505	health := NewDefaultHealthCheck()
506	health.IntervalSeconds = interval
507	*health.Path = path
508	*health.PortIndex = portIndex
509	// step: add to the checks
510	r.AddHealthCheck(*health)
511
512	return r, nil
513}
514
515// CheckTCP adds a TCP check to an application; note the port mapping must already exist, or an
516// error will thrown
517//		port: 		the port the check should, err, check
518// 		interval:	the interval in seconds the check should be performed
519func (r *Application) CheckTCP(port, interval int) (*Application, error) {
520	if r.Container == nil || r.Container.Docker == nil {
521		return nil, ErrNoApplicationContainer
522	}
523	// step: get the port index
524	portIndex, err := r.Container.Docker.ServicePortIndex(port)
525	if err != nil {
526		portIndex, err = r.Container.ServicePortIndex(port)
527		if err != nil {
528			return nil, err
529		}
530	}
531	health := NewDefaultHealthCheck()
532	health.Protocol = "TCP"
533	health.IntervalSeconds = interval
534	*health.PortIndex = portIndex
535	// step: add to the checks
536	r.AddHealthCheck(*health)
537
538	return r, nil
539}
540
541// AddUris adds one or more uris to the applications
542//		arguments:	the uri(s) you are adding
543func (r *Application) AddUris(newUris ...string) *Application {
544	if r.Uris == nil {
545		r.EmptyUris()
546	}
547
548	uris := *r.Uris
549	uris = append(uris, newUris...)
550	r.Uris = &uris
551
552	return r
553}
554
555// EmptyUris explicitly empties uris -- use this if you need to empty
556// uris of an application that already has uris set (setting uris to nil will
557// keep the current value)
558func (r *Application) EmptyUris() *Application {
559	r.Uris = &[]string{}
560
561	return r
562}
563
564// AddFetchURIs adds one or more fetch URIs to the application.
565//		fetchURIs:	the fetch URI(s) to add.
566func (r *Application) AddFetchURIs(fetchURIs ...Fetch) *Application {
567	if r.Fetch == nil {
568		r.EmptyFetchURIs()
569	}
570
571	fetch := *r.Fetch
572	fetch = append(fetch, fetchURIs...)
573	r.Fetch = &fetch
574
575	return r
576}
577
578// EmptyFetchURIs explicitly empties fetch URIs -- use this if you need to empty
579// fetch URIs of an application that already has fetch URIs set.
580// Setting fetch URIs to nil will keep the current value.
581func (r *Application) EmptyFetchURIs() *Application {
582	r.Fetch = &[]Fetch{}
583
584	return r
585}
586
587// SetUpgradeStrategy sets the upgrade strategy.
588func (r *Application) SetUpgradeStrategy(us UpgradeStrategy) *Application {
589	r.UpgradeStrategy = &us
590	return r
591}
592
593// EmptyUpgradeStrategy explicitly empties the upgrade strategy -- use this if
594// you need to empty the upgrade strategy of an application that already has
595// the upgrade strategy set (setting it to nil will keep the current value).
596func (r *Application) EmptyUpgradeStrategy() *Application {
597	r.UpgradeStrategy = &UpgradeStrategy{}
598	return r
599}
600
601// SetUnreachableStrategy sets the unreachable strategy.
602func (r *Application) SetUnreachableStrategy(us UnreachableStrategy) *Application {
603	r.UnreachableStrategy = &us
604	return r
605}
606
607// EmptyUnreachableStrategy explicitly empties the unreachable strategy -- use this if
608// you need to empty the unreachable strategy of an application that already has
609// the unreachable strategy set (setting it to nil will keep the current value).
610func (r *Application) EmptyUnreachableStrategy() *Application {
611	r.UnreachableStrategy = &UnreachableStrategy{}
612	return r
613}
614
615// SetResidency sets behavior for resident applications, an application is resident when
616// it has local persistent volumes set
617func (r *Application) SetResidency(whenLost TaskLostBehaviorType) *Application {
618	r.Residency = &Residency{
619		TaskLostBehavior: whenLost,
620	}
621	return r
622}
623
624// EmptyResidency explicitly empties the residency -- use this if
625// you need to empty the residency of an application that already has
626// the residency set (setting it to nil will keep the current value).
627func (r *Application) EmptyResidency() *Application {
628	r.Residency = &Residency{}
629	return r
630}
631
632// String returns the json representation of this application
633func (r *Application) String() string {
634	s, err := json.MarshalIndent(r, "", "  ")
635	if err != nil {
636		return fmt.Sprintf(`{"error": "error decoding type into json: %s"}`, err)
637	}
638
639	return string(s)
640}
641
642// Applications retrieves an array of all the applications which are running in marathon
643func (r *marathonClient) Applications(v url.Values) (*Applications, error) {
644	query := v.Encode()
645	if query != "" {
646		query = "?" + query
647	}
648
649	applications := new(Applications)
650	err := r.apiGet(marathonAPIApps+query, nil, applications)
651	if err != nil {
652		return nil, err
653	}
654
655	return applications, nil
656}
657
658// ListApplications retrieves an array of the application names currently running in marathon
659func (r *marathonClient) ListApplications(v url.Values) ([]string, error) {
660	applications, err := r.Applications(v)
661	if err != nil {
662		return nil, err
663	}
664	var list []string
665	for _, application := range applications.Apps {
666		list = append(list, application.ID)
667	}
668
669	return list, nil
670}
671
672// HasApplicationVersion checks to see if the application version exists in Marathon
673// 		name: 		the id used to identify the application
674//		version: 	the version (normally a timestamp) your looking for
675func (r *marathonClient) HasApplicationVersion(name, version string) (bool, error) {
676	id := trimRootPath(name)
677	versions, err := r.ApplicationVersions(id)
678	if err != nil {
679		return false, err
680	}
681
682	return contains(versions.Versions, version), nil
683}
684
685// ApplicationVersions is a list of versions which has been deployed with marathon for a specific application
686//		name:		the id used to identify the application
687func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions, error) {
688	path := fmt.Sprintf("%s/versions", buildPath(name))
689	versions := new(ApplicationVersions)
690	if err := r.apiGet(path, nil, versions); err != nil {
691		return nil, err
692	}
693	return versions, nil
694}
695
696// SetApplicationVersion changes the version of the application
697// 		name: 		the id used to identify the application
698//		version: 	the version (normally a timestamp) you wish to change to
699func (r *marathonClient) SetApplicationVersion(name string, version *ApplicationVersion) (*DeploymentID, error) {
700	path := buildPath(name)
701	deploymentID := new(DeploymentID)
702	if err := r.apiPut(path, version, deploymentID); err != nil {
703		return nil, err
704	}
705
706	return deploymentID, nil
707}
708
709// Application retrieves the application configuration from marathon
710// 		name: 		the id used to identify the application
711func (r *marathonClient) Application(name string) (*Application, error) {
712	var wrapper struct {
713		Application *Application `json:"app"`
714	}
715
716	if err := r.apiGet(buildPath(name), nil, &wrapper); err != nil {
717		return nil, err
718	}
719
720	return wrapper.Application, nil
721}
722
723// ApplicationBy retrieves the application configuration from marathon
724// 		name: 		the id used to identify the application
725//		opts:		GetAppOpts request payload
726func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Application, error) {
727	path, err := addOptions(buildPath(name), opts)
728	if err != nil {
729		return nil, err
730	}
731	var wrapper struct {
732		Application *Application `json:"app"`
733	}
734
735	if err := r.apiGet(path, nil, &wrapper); err != nil {
736		return nil, err
737	}
738
739	return wrapper.Application, nil
740}
741
742// ApplicationByVersion retrieves the application configuration from marathon
743// 		name: 		the id used to identify the application
744// 		version:  the version of the configuration you would like to receive
745func (r *marathonClient) ApplicationByVersion(name, version string) (*Application, error) {
746	app := new(Application)
747
748	path := fmt.Sprintf("%s/versions/%s", buildPath(name), version)
749	if err := r.apiGet(path, nil, app); err != nil {
750		return nil, err
751	}
752
753	return app, nil
754}
755
756// ApplicationOK validates that the application, or more appropriately it's tasks have passed all the health checks.
757// If no health checks exist, we simply return true
758// 		name: 		the id used to identify the application
759func (r *marathonClient) ApplicationOK(name string) (bool, error) {
760	// step: get the application
761	application, err := r.Application(name)
762	if err != nil {
763		return false, err
764	}
765
766	// step: check if all the tasks are running?
767	if !application.AllTaskRunning() {
768		return false, nil
769	}
770
771	// step: if the application has not health checks, just return true
772	if application.HealthChecks == nil || len(*application.HealthChecks) == 0 {
773		return true, nil
774	}
775
776	// step: iterate the application checks and look for false
777	for _, task := range application.Tasks {
778		// Health check results may not be available immediately. Assume
779		// non-healthiness if they are missing for any task.
780		if task.HealthCheckResults == nil {
781			return false, nil
782		}
783
784		for _, check := range task.HealthCheckResults {
785			//When a task is flapping in Marathon, this is sometimes nil
786			if check == nil || !check.Alive {
787				return false, nil
788			}
789		}
790	}
791
792	return true, nil
793}
794
795// ApplicationDeployments retrieves an array of Deployment IDs for an application
796//       name:       the id used to identify the application
797func (r *marathonClient) ApplicationDeployments(name string) ([]*DeploymentID, error) {
798	application, err := r.Application(name)
799	if err != nil {
800		return nil, err
801	}
802
803	return application.DeploymentIDs(), nil
804}
805
806// CreateApplication creates a new application in Marathon
807// 		application:		the structure holding the application configuration
808func (r *marathonClient) CreateApplication(application *Application) (*Application, error) {
809	result := new(Application)
810	if err := r.apiPost(marathonAPIApps, application, result); err != nil {
811		return nil, err
812	}
813
814	return result, nil
815}
816
817// WaitOnApplication waits for an application to be deployed
818//		name:		the id of the application
819//		timeout:	a duration of time to wait for an application to deploy
820func (r *marathonClient) WaitOnApplication(name string, timeout time.Duration) error {
821	return r.wait(name, timeout, r.appExistAndRunning)
822}
823
824func (r *marathonClient) appExistAndRunning(name string) bool {
825	app, err := r.Application(name)
826	if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
827		return false
828	}
829	if err == nil && app.AllTaskRunning() {
830		return true
831	}
832	return false
833}
834
835// DeleteApplication deletes an application from marathon
836// 		name: 		the id used to identify the application
837//		force:		used to force the delete operation in case of blocked deployment
838func (r *marathonClient) DeleteApplication(name string, force bool) (*DeploymentID, error) {
839	path := buildPathWithForceParam(name, force)
840	// step: check of the application already exists
841	deployID := new(DeploymentID)
842	if err := r.apiDelete(path, nil, deployID); err != nil {
843		return nil, err
844	}
845
846	return deployID, nil
847}
848
849// RestartApplication performs a rolling restart of marathon application
850// 		name: 		the id used to identify the application
851func (r *marathonClient) RestartApplication(name string, force bool) (*DeploymentID, error) {
852	deployment := new(DeploymentID)
853	var options struct{}
854	path := buildPathWithForceParam(fmt.Sprintf("%s/restart", name), force)
855	if err := r.apiPost(path, &options, deployment); err != nil {
856		return nil, err
857	}
858
859	return deployment, nil
860}
861
862// ScaleApplicationInstances changes the number of instance an application is running
863// 		name: 		the id used to identify the application
864// 		instances:	the number of instances you wish to change to
865//    force: used to force the scale operation in case of blocked deployment
866func (r *marathonClient) ScaleApplicationInstances(name string, instances int, force bool) (*DeploymentID, error) {
867	changes := new(Application)
868	changes.ID = validateID(name)
869	changes.Instances = &instances
870	path := buildPathWithForceParam(name, force)
871	deployID := new(DeploymentID)
872	if err := r.apiPut(path, changes, deployID); err != nil {
873		return nil, err
874	}
875
876	return deployID, nil
877}
878
879// UpdateApplication updates an application in Marathon
880// 		application:		the structure holding the application configuration
881func (r *marathonClient) UpdateApplication(application *Application, force bool) (*DeploymentID, error) {
882	result := new(DeploymentID)
883	path := buildPathWithForceParam(application.ID, force)
884	if err := r.apiPut(path, application, result); err != nil {
885		return nil, err
886	}
887	return result, nil
888}
889
890func buildPathWithForceParam(rootPath string, force bool) string {
891	path := buildPath(rootPath)
892	if force {
893		path += "?force=true"
894	}
895	return path
896}
897
898func buildPath(path string) string {
899	return fmt.Sprintf("%s/%s", marathonAPIApps, trimRootPath(path))
900}
901
902// EmptyLabels explicitly empties labels -- use this if you need to empty
903// labels of an application that already has IP per task with labels defined
904func (i *IPAddressPerTask) EmptyLabels() *IPAddressPerTask {
905	i.Labels = &map[string]string{}
906	return i
907}
908
909// AddLabel adds a label to an IPAddressPerTask
910//    name: The label name
911//   value: The label value
912func (i *IPAddressPerTask) AddLabel(name, value string) *IPAddressPerTask {
913	if i.Labels == nil {
914		i.EmptyLabels()
915	}
916	(*i.Labels)[name] = value
917	return i
918}
919
920// EmptyGroups explicitly empties groups -- use this if you need to empty
921// groups of an application that already has IP per task with groups defined
922func (i *IPAddressPerTask) EmptyGroups() *IPAddressPerTask {
923	i.Groups = &[]string{}
924	return i
925}
926
927// AddGroup adds a group to an IPAddressPerTask
928//  group: The group name
929func (i *IPAddressPerTask) AddGroup(group string) *IPAddressPerTask {
930	if i.Groups == nil {
931		i.EmptyGroups()
932	}
933
934	groups := *i.Groups
935	groups = append(groups, group)
936	i.Groups = &groups
937
938	return i
939}
940
941// SetDiscovery define the discovery to an IPAddressPerTask
942//  discovery: The discovery struct
943func (i *IPAddressPerTask) SetDiscovery(discovery Discovery) *IPAddressPerTask {
944	i.Discovery = &discovery
945	return i
946}
947
948// EmptyPorts explicitly empties discovey port -- use this if you need to empty
949// discovey port of an application that already has IP per task with discovey ports
950// defined
951func (d *Discovery) EmptyPorts() *Discovery {
952	d.Ports = &[]Port{}
953	return d
954}
955
956// AddPort adds a port to the discovery info of a IP per task applicable
957//   port: The discovery port
958func (d *Discovery) AddPort(port Port) *Discovery {
959	if d.Ports == nil {
960		d.EmptyPorts()
961	}
962	ports := *d.Ports
963	ports = append(ports, port)
964	d.Ports = &ports
965	return d
966}
967
968// EmptyNetworks explicitly empties networks
969func (r *Application) EmptyNetworks() *Application {
970	r.Networks = &[]PodNetwork{}
971	return r
972}
973
974// SetNetwork sets the networking mode
975func (r *Application) SetNetwork(name string, mode PodNetworkMode) *Application {
976	if r.Networks == nil {
977		r.EmptyNetworks()
978	}
979
980	network := PodNetwork{Name: name, Mode: mode}
981	networks := *r.Networks
982	networks = append(networks, network)
983	r.Networks = &networks
984	return r
985}
986