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