marathon-api 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +2 -2
  3. data/.travis.yml +1 -1
  4. data/README.md +40 -14
  5. data/bin/marathon +242 -0
  6. data/fixtures/marathon_docker_sample_2.json +2 -1
  7. data/fixtures/vcr/Marathon_App/_restart/restarts_an_app.yml +32 -0
  8. data/fixtures/vcr/Marathon_Group/_changes/changes_the_group.yml +61 -0
  9. data/fixtures/vcr/Marathon_Group/_delete/deletes_the_group.yml +32 -0
  10. data/fixtures/vcr/Marathon_Group/_delete/fails_deleting_not_existing_app.yml +32 -0
  11. data/fixtures/vcr/Marathon_Group/_get/fails_getting_not_existing_app.yml +32 -0
  12. data/fixtures/vcr/Marathon_Group/_get/gets_the_group.yml +32 -0
  13. data/fixtures/vcr/Marathon_Group/_list/lists_apps.yml +33 -0
  14. data/fixtures/vcr/Marathon_Group/_start/fails_getting_not_existing_group.yml +32 -0
  15. data/fixtures/vcr/Marathon_Group/_start/starts_the_group.yml +35 -0
  16. data/lib/marathon.rb +33 -5
  17. data/lib/marathon/app.rb +72 -22
  18. data/lib/marathon/base.rb +32 -0
  19. data/lib/marathon/connection.rb +32 -22
  20. data/lib/marathon/constraint.rb +39 -0
  21. data/lib/marathon/container.rb +41 -0
  22. data/lib/marathon/container_docker.rb +33 -0
  23. data/lib/marathon/container_docker_port_mapping.rb +32 -0
  24. data/lib/marathon/container_volume.rb +31 -0
  25. data/lib/marathon/deployment.rb +5 -15
  26. data/lib/marathon/deployment_info.rb +20 -0
  27. data/lib/marathon/error.rb +8 -2
  28. data/lib/marathon/group.rb +166 -0
  29. data/lib/marathon/health_check.rb +35 -0
  30. data/lib/marathon/queue.rb +4 -13
  31. data/lib/marathon/task.rb +15 -12
  32. data/lib/marathon/util.rb +47 -3
  33. data/lib/marathon/version.rb +1 -1
  34. data/marathon-api.gemspec +4 -3
  35. data/spec/marathon/app_spec.rb +108 -50
  36. data/spec/marathon/base_spec.rb +53 -0
  37. data/spec/marathon/connection_spec.rb +1 -1
  38. data/spec/marathon/constraint_spec.rb +27 -0
  39. data/spec/marathon/container_docker_port_mapping_spec.rb +55 -0
  40. data/spec/marathon/container_docker_spec.rb +42 -0
  41. data/spec/marathon/container_spec.rb +40 -0
  42. data/spec/marathon/container_volume_spec.rb +50 -0
  43. data/spec/marathon/deployment_info_spec.rb +43 -0
  44. data/spec/marathon/deployment_spec.rb +15 -16
  45. data/spec/marathon/error_spec.rb +17 -0
  46. data/spec/marathon/group_spec.rb +172 -0
  47. data/spec/marathon/health_check_spec.rb +50 -0
  48. data/spec/marathon/marathon_spec.rb +31 -0
  49. data/spec/marathon/queue_spec.rb +2 -2
  50. data/spec/marathon/task_spec.rb +24 -11
  51. data/spec/marathon/util_spec.rb +21 -1
  52. metadata +58 -6
@@ -0,0 +1,32 @@
1
+ # Base class for all the API specific classes.
2
+ class Marathon::Base
3
+
4
+ include Marathon::Error
5
+
6
+ attr_reader :info
7
+
8
+ # Create the object
9
+ # ++hash++: object returned from API. May be Hash or Array.
10
+ # ++attr_readers++: List of attribute readers.
11
+ def initialize(hash, attr_readers = [])
12
+ raise ArgumentError, 'hash must be a Hash' if attr_readers and attr_readers.size > 0 and not hash.is_a?(Hash)
13
+ raise ArgumentError, 'hash must be Hash or Array' unless hash.is_a?(Hash) or hash.is_a?(Array)
14
+ raise ArgumentError, 'attr_readers must be an Array' unless attr_readers.is_a?(Array)
15
+ @info = Marathon::Util.keywordize_hash(hash)
16
+ attr_readers.each { |e| add_attr_reader(e) }
17
+ end
18
+
19
+ # Return application as JSON formatted string.
20
+ def to_json
21
+ info.to_json
22
+ end
23
+
24
+ private
25
+
26
+ # Create attr_reader for @info[key].
27
+ # ++key++: key in @info
28
+ def add_attr_reader(key)
29
+ sym = key.to_sym
30
+ self.class.send(:define_method, sym.id2name) { |*args, &block| info[sym] }
31
+ end
32
+ end
@@ -13,12 +13,22 @@ class Marathon::Connection
13
13
  default_timeout 5
14
14
  maintain_method_across_redirects
15
15
 
16
- attr_reader :url
16
+ attr_reader :url, :options
17
17
 
18
18
  # Create a new API connection.
19
19
  # ++url++: URL of the marathon API.
20
- def initialize(url)
20
+ # ++options++: Hash with options for marathon API.
21
+ def initialize(url, options = {})
21
22
  @url = url
23
+ @options = options
24
+ if @options[:username] and @options[:password]
25
+ @options[:basic_auth] = {
26
+ :username => @options[:username],
27
+ :password => @options[:password]
28
+ }
29
+ @options.delete(:username)
30
+ @options.delete(:password)
31
+ end
22
32
  end
23
33
 
24
34
  # Delegate all HTTP methods to the #request.
@@ -27,7 +37,7 @@ class Marathon::Connection
27
37
  end
28
38
 
29
39
  def to_s
30
- "Marathon::Connection { :url => #{url} }"
40
+ "Marathon::Connection { :url => #{url} :options => #{options} }"
31
41
  end
32
42
 
33
43
  private
@@ -58,16 +68,25 @@ private
58
68
  end
59
69
 
60
70
  # Create full URL with query parameters.
61
- # ++url++: Base URL.
62
- # ++query++: Hash of query parameters.
63
- def build_url(url, query)
64
- url = URI.escape(url)
65
- if query.size > 0
66
- url += '?' + query_params(query)
71
+ # ++request++: hash containing :url and optional :query
72
+ def build_url(request)
73
+ url = URI.escape(request[:url])
74
+ if request[:query].size > 0
75
+ url += '?' + query_params(request[:query])
67
76
  end
68
77
  url
69
78
  end
70
79
 
80
+ # Parse response or raise error.
81
+ # ++response++: response from HTTParty call.
82
+ def parse_response(response)
83
+ if response.success?
84
+ response.parsed_response
85
+ else
86
+ raise Marathon::Error.from_response(response)
87
+ end
88
+ end
89
+
71
90
  # Send a request to the server and parse response.
72
91
  # ++http_method++: GET/POST/PUT/DELETE.
73
92
  # ++path++: Relative path to connection's URL.
@@ -75,19 +94,10 @@ private
75
94
  # ++opts++: Optional options. Ex. opts[:body] is used for PUT/POST request.
76
95
  def request(*args)
77
96
  request = compile_request_params(*args)
78
- url = build_url(request[:url], request[:query])
79
- response = self.class.send(request[:method], url, request)
80
- if response.success?
81
- response.parsed_response
82
- else
83
- raise Marathon::Error.from_response(response)
84
- end
85
- rescue MarathonError => e
86
- raise e
87
- rescue SocketError => e
88
- raise IOError, "HTTP call failed: #{e.message}"
89
- rescue SystemCallError => e
90
- if e.class.name.start_with?('Errno::')
97
+ url = build_url(request)
98
+ parse_response(self.class.send(request[:method], url, request))
99
+ rescue => e
100
+ if e.class == SocketError or e.class.name.start_with?('Errno::')
91
101
  raise IOError, "HTTP call failed: #{e.message}"
92
102
  else
93
103
  raise e
@@ -0,0 +1,39 @@
1
+ # This class represents a Marathon Constraint.
2
+ # See https://mesosphere.github.io/marathon/docs/constraints.html for full details.
3
+ class Marathon::Constraint < Marathon::Base
4
+
5
+ # Create a new constraint object.
6
+ # ++array++: Array returned by API, holds attribute, operator and parameter.
7
+ def initialize(array)
8
+ raise Marathon::Error::ArgumentError, 'array must be an Array' unless array.is_a?(Array)
9
+ raise Marathon::Error::ArgumentError,
10
+ 'array must be [attribute, operator, parameter] where only parameter is optional' \
11
+ unless array.size != 2 or array.size != 3
12
+ super
13
+ end
14
+
15
+ def attribute
16
+ info[0]
17
+ end
18
+
19
+ def operator
20
+ info[1]
21
+ end
22
+
23
+ def parameter
24
+ info[2]
25
+ end
26
+
27
+ def to_s
28
+ if parameter
29
+ "Marathon::Constraint { :attribute => #{attribute} :operator => #{operator} :parameter => #{parameter} }"
30
+ else
31
+ "Marathon::Constraint { :attribute => #{attribute} :operator => #{operator} }"
32
+ end
33
+ end
34
+
35
+ # Returns a string for listing the constraint.
36
+ def to_pretty_s
37
+ info.join(':')
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ # This class represents a Marathon Container information.
2
+ # It is included in App's definition.
3
+ # See https://mesosphere.github.io/marathon/docs/native-docker.html for full details.
4
+ class Marathon::Container < Marathon::Base
5
+
6
+ SUPPERTED_TYPES = %w[ DOCKER ]
7
+ ACCESSORS = %w[ type ]
8
+ DEFAULTS = {
9
+ :type => 'DOCKER',
10
+ :volumes => []
11
+ }
12
+
13
+ # Create a new container object.
14
+ # ++hash++: Hash returned by API.
15
+ def initialize(hash)
16
+ super(Marathon::Util.merge_keywordized_hash(DEFAULTS, hash), ACCESSORS)
17
+ Marathon::Util.validate_choice('type', type, SUPPERTED_TYPES)
18
+ end
19
+
20
+ # Get container's docker information.
21
+ # This is read only!
22
+ def docker
23
+ if @info[:docker]
24
+ Marathon::ContainerDocker.new(@info[:docker])
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ # Get container's volumes.
31
+ # This is read only!
32
+ def volumes
33
+ @info[:volumes].map { |e| Marathon::ContainerVolume.new(e) }
34
+ end
35
+
36
+ def to_s
37
+ "Marathon::Container { :type => #{type} :docker => #{Marathon::Util.items_to_pretty_s(docker)}"\
38
+ + " :volumes => #{Marathon::Util.items_to_pretty_s(volumes)} }"
39
+ end
40
+
41
+ end
@@ -0,0 +1,33 @@
1
+ # This class represents a Marathon Container docker information.
2
+ # See https://mesosphere.github.io/marathon/docs/native-docker.html for full details.
3
+ class Marathon::ContainerDocker < Marathon::Base
4
+
5
+ ACCESSORS = %w[ image network ]
6
+ DEFAULTS = {
7
+ :network => 'BRIDGE',
8
+ :portMappings => []
9
+ }
10
+
11
+ # Create a new container docker object.
12
+ # ++hash++: Hash returned by API.
13
+ def initialize(hash)
14
+ super(Marathon::Util.merge_keywordized_hash(DEFAULTS, hash), ACCESSORS)
15
+ Marathon::Util.validate_choice('network', network, %w[BRIDGE HOST])
16
+ raise Marathon::Error::ArgumentError, 'image must not be nil' unless image
17
+ end
18
+
19
+ # Get container's port mappings.
20
+ # This is read only!
21
+ def portMappings
22
+ @info[:portMappings].map { |e| Marathon::ContainerDockerPortMapping.new(e) }
23
+ end
24
+
25
+ def to_pretty_s
26
+ "#{image}"
27
+ end
28
+
29
+ def to_s
30
+ "Marathon::ContainerDocker { :image => #{image} }"
31
+ end
32
+
33
+ end
@@ -0,0 +1,32 @@
1
+ # This class represents a Marathon Container docker information.
2
+ # See https://mesosphere.github.io/marathon/docs/native-docker.html for full details.
3
+ class Marathon::ContainerDockerPortMapping < Marathon::Base
4
+
5
+ ACCESSORS = %w[ containerPort hostPort servicePort protocol ]
6
+ DEFAULTS = {
7
+ :protocol => 'tcp',
8
+ :hostPort => 0
9
+ }
10
+
11
+ # Create a new container docker port mappint object.
12
+ # ++hash++: Hash returned by API.
13
+ def initialize(hash)
14
+ super(Marathon::Util.merge_keywordized_hash(DEFAULTS, hash), ACCESSORS)
15
+ Marathon::Util.validate_choice('protocol', protocol, %w[tcp udp])
16
+ raise Marathon::Error::ArgumentError, 'containerPort must not be nil' unless containerPort
17
+ raise Marathon::Error::ArgumentError, 'containerPort must be a positive number' \
18
+ unless containerPort.is_a?(Integer) and containerPort > 0
19
+ raise Marathon::Error::ArgumentError, 'hostPort must be a non negative number' \
20
+ unless hostPort.is_a?(Integer) and hostPort >= 0
21
+ end
22
+
23
+ def to_pretty_s
24
+ "#{protocol}/#{containerPort}:#{hostPort}"
25
+ end
26
+
27
+ def to_s
28
+ "Marathon::ContainerDockerPortMapping { :protocol => #{protocol} " \
29
+ + ":containerPort => #{containerPort} :hostPort => #{hostPort} }"
30
+ end
31
+
32
+ end
@@ -0,0 +1,31 @@
1
+ # This class represents a Marathon Container Volume information.
2
+ # See https://mesosphere.github.io/marathon/docs/native-docker.html for full details.
3
+ class Marathon::ContainerVolume < Marathon::Base
4
+
5
+ ACCESSORS = %w[ containerPath hostPath mode ]
6
+ DEFAULTS = {
7
+ :mode => 'RW'
8
+ }
9
+
10
+ # Create a new container volume object.
11
+ # ++hash++: Hash returned by API.
12
+ def initialize(hash)
13
+ super(Marathon::Util.merge_keywordized_hash(DEFAULTS, hash), ACCESSORS)
14
+ Marathon::Util.validate_choice('mode', mode, %w[RW RO])
15
+ raise Marathon::Error::ArgumentError, 'containerPath must not be nil' unless containerPath
16
+ raise Marathon::Error::ArgumentError, 'containerPath must be an absolute path' \
17
+ unless Pathname.new(containerPath).absolute?
18
+ raise Marathon::Error::ArgumentError, 'hostPath must not be nil' unless hostPath
19
+ raise Marathon::Error::ArgumentError, 'hostPath must be an absolute path' \
20
+ unless Pathname.new(hostPath).absolute?
21
+ end
22
+
23
+ def to_pretty_s
24
+ "#{containerPath}:#{hostPath}:#{mode}"
25
+ end
26
+
27
+ def to_s
28
+ "Marathon::ContainerVolume { :containerPath => #{containerPath} :hostPath => #{hostPath} :mode => #{mode} }"
29
+ end
30
+
31
+ end
@@ -1,19 +1,14 @@
1
1
  # This class represents a Marathon Deployment.
2
2
  # See https://mesosphere.github.io/marathon/docs/rest-api.html#deployments for full list of API's methods.
3
- class Marathon::Deployment
3
+ class Marathon::Deployment < Marathon::Base
4
4
 
5
- attr_reader :info
5
+ ACCESSORS = %w[ id affectedApps steps currentActions version currentStep totalSteps ]
6
6
 
7
7
  # Create a new deployment object.
8
8
  # ++hash++: Hash including all attributes.
9
9
  # See https://mesosphere.github.io/marathon/docs/rest-api.html#get-/v2/deployments for full details.
10
- def initialize(hash = {})
11
- @info = hash
12
- end
13
-
14
- # Shortcuts for reaching attributes
15
- %w[ id affectedApps steps currentActions version currentStep totalSteps ].each do |method|
16
- define_method(method) { |*args, &block| info[method] }
10
+ def initialize(hash)
11
+ super(hash, ACCESSORS)
17
12
  end
18
13
 
19
14
  # Cancel the deployment.
@@ -30,11 +25,6 @@ class Marathon::Deployment
30
25
  + ":id => #{id} :affectedApps => #{affectedApps} :currentStep => #{currentStep} :totalSteps => #{totalSteps} }"
31
26
  end
32
27
 
33
- # Return deployment as JSON formatted string.
34
- def to_json
35
- info.to_json
36
- end
37
-
38
28
  class << self
39
29
 
40
30
  # List running deployments.
@@ -52,7 +42,7 @@ class Marathon::Deployment
52
42
  query = {}
53
43
  query[:force] = true if force
54
44
  json = Marathon.connection.delete("/v2/deployments/#{id}")
55
- # TODO parse deploymentId + version
45
+ Marathon::DeploymentInfo.new(json)
56
46
  end
57
47
  alias :cancel :delete
58
48
  alias :remove :delete
@@ -0,0 +1,20 @@
1
+ # This class represents a Marathon Deployment information.
2
+ # It is returned by asynchronious deployment calls.
3
+ class Marathon::DeploymentInfo < Marathon::Base
4
+
5
+ # Create a new deployment info object.
6
+ # ++hash++: Hash returned by API, including 'deploymentId' and 'version'
7
+ def initialize(hash)
8
+ super(hash, %w[deploymentId version])
9
+ raise Marathon::Error::ArgumentError, 'version must not be nil' unless version
10
+ end
11
+
12
+ def to_s
13
+ if deploymentId
14
+ "Marathon::DeploymentInfo { :version => #{version} :deploymentId => #{deploymentId} }"
15
+ else
16
+ "Marathon::DeploymentInfo { :version => #{version} }"
17
+ end
18
+ end
19
+
20
+ end
@@ -7,7 +7,7 @@ module Marathon::Error
7
7
  # Raised when invalid arguments are passed to a method.
8
8
  class ArgumentError < MarathonError; end
9
9
 
10
- # Raised when a request returns a 400.
10
+ # Raised when a request returns a 400 or 422.
11
11
  class ClientError < MarathonError; end
12
12
 
13
13
  # Raised when a request returns a 404.
@@ -39,6 +39,8 @@ module Marathon::Error
39
39
  case response.code
40
40
  when 400
41
41
  ClientError
42
+ when 422
43
+ ClientError
42
44
  when 404
43
45
  NotFoundError
44
46
  else
@@ -50,8 +52,12 @@ module Marathon::Error
50
52
  # ++response++: HTTParty response object.
51
53
  def error_message(response)
52
54
  body = response.parsed_response
53
- if body.is_a?(Hash)
55
+ if not body.is_a?(Hash)
56
+ body
57
+ elsif body['message']
54
58
  body['message']
59
+ elsif body['errors']
60
+ body['errors']
55
61
  else
56
62
  body
57
63
  end
@@ -0,0 +1,166 @@
1
+ # This class represents a Marathon Group.
2
+ # See https://mesosphere.github.io/marathon/docs/rest-api.html#groups for full list of API's methods.
3
+ class Marathon::Group < Marathon::Base
4
+
5
+ ACCESSORS = %w[ id dependencies version ]
6
+
7
+ DEFAULTS = {
8
+ :apps => [],
9
+ :dependencies => [],
10
+ :groups => []
11
+ }
12
+
13
+ # Create a new group object.
14
+ # ++hash++: Hash including all attributes.
15
+ # See https://mesosphere.github.io/marathon/docs/rest-api.html#post-/v2/groups for full details.
16
+ def initialize(hash)
17
+ super(Marathon::Util.merge_keywordized_hash(DEFAULTS, hash), ACCESSORS)
18
+ raise ArgumentError, 'Group must have an id' unless id
19
+ raise ArgumentError, 'Group can have either groups or apps, not both' if apps.size > 0 and groups.size > 0
20
+ end
21
+
22
+ # Get apps.
23
+ # This is read only!
24
+ def apps
25
+ @info[:apps].map { |e| Marathon::App.new(e) }
26
+ end
27
+
28
+ # Get groups.
29
+ # This is read only!
30
+ def groups
31
+ @info[:groups].map { |e| Marathon::Group.new(e) }
32
+ end
33
+
34
+ # Reload attributes from marathon API.
35
+ def refresh
36
+ new_app = self.class.get(id)
37
+ @info = new_app.info
38
+ end
39
+
40
+ # Create and start a the application group. Application groups can contain other application groups.
41
+ # An application group can either hold other groups or applications, but can not be mixed in one.
42
+ # Since the deployment of the group can take a considerable amount of time,
43
+ # this endpoint returns immediatly with a version. The failure or success of the action is signalled via event.
44
+ # There is a group_change_success and group_change_failed event with the given version.
45
+ def start!
46
+ self.class.start(info)
47
+ end
48
+
49
+ # Change parameters of a deployed application group.
50
+ # Changes to application parameters will result in a restart of this application.
51
+ # A new application added to the group is started.
52
+ # An existing application removed from the group gets stopped.
53
+ # If there are no changes to the application definition, no restart is triggered.
54
+ # During restart marathon keeps track, that the configured amount of minimal running instances are always available.
55
+ # A deployment can run forever. This is the case, when the new application has a problem and does not become healthy.
56
+ # In this case, human interaction is needed with 2 possible choices:
57
+ # Rollback to an existing older version (send an existing version in the body)
58
+ # Update with a newer version of the group which does not have the problems of the old one.
59
+ # If there is an upgrade process already in progress, a new update will be rejected unless the force flag is set.
60
+ # With the force flag given, a running upgrade is terminated and a new one is started.
61
+ # Since the deployment of the group can take a considerable amount of time,
62
+ # this endpoint returns immediatly with a version. The failure or success of the action is signalled via event.
63
+ # There is a group_change_success and group_change_failed event with the given version.
64
+ # ++hash++: Hash of attributes to change.
65
+ # ++force++: If the group is affected by a running deployment, then the update operation will fail.
66
+ # The current deployment can be overridden by setting the `force` query parameter.
67
+ def change!(hash, force = false)
68
+ self.class.change(id, hash, force)
69
+ end
70
+
71
+ # Create a new version with parameters of an old version.
72
+ # Currently running tasks are restarted, while maintaining the minimumHealthCapacity.
73
+ # ++version++: Version name of the old version.
74
+ # ++force++: If the group is affected by a running deployment, then the update operation will fail.
75
+ # The current deployment can be overridden by setting the `force` query parameter.
76
+ def roll_back!(version, force = false)
77
+ change!({'version' => version}, force)
78
+ end
79
+
80
+ def to_s
81
+ "Marathon::Group { :id => #{id} }"
82
+ end
83
+
84
+ # Returns a string for listing the group.
85
+ def to_pretty_s
86
+ %Q[
87
+ Group ID: #{id}
88
+ #{pretty_array(apps)}
89
+ #{pretty_array(groups)}
90
+ Version: #{version}
91
+ ].gsub(/\n\n+/, "\n").strip
92
+ end
93
+
94
+ private
95
+
96
+ def pretty_array(array)
97
+ array.map { |e| e.to_pretty_s.split("\n").map { |e| " #{e}" }}.join("\n")
98
+ end
99
+
100
+ class << self
101
+
102
+ # List the group with the specified ID.
103
+ # ++id++: Group's id.
104
+ def get(id)
105
+ json = Marathon.connection.get("/v2/groups/#{id}")
106
+ new(json)
107
+ end
108
+
109
+ # List all groups.
110
+ def list
111
+ json = Marathon.connection.get('/v2/groups')
112
+ new(json)
113
+ end
114
+
115
+ # Delete the application group with id.
116
+ # ++id++: Group's id.
117
+ # ++force++: If the group is affected by a running deployment, then the update operation will fail.
118
+ # The current deployment can be overridden by setting the `force` query parameter.
119
+ def delete(id, force = false)
120
+ query = {}
121
+ query[:force] = true if force
122
+ Marathon.connection.delete("/v2/groups/#{id}", query)
123
+ end
124
+ alias :remove :delete
125
+
126
+ # Create and start a new application group. Application groups can contain other application groups.
127
+ # An application group can either hold other groups or applications, but can not be mixed in one.
128
+ # Since the deployment of the group can take a considerable amount of time,
129
+ # this endpoint returns immediatly with a version. The failure or success of the action is signalled via event.
130
+ # There is a group_change_success and group_change_failed event with the given version.
131
+ # ++hash++: Hash including all attributes
132
+ # see https://mesosphere.github.io/marathon/docs/rest-api.html#post-/v2/groups for full details
133
+ def start(hash)
134
+ json = Marathon.connection.post('/v2/groups', nil, :body => hash)
135
+ Marathon::DeploymentInfo.new(json)
136
+ end
137
+ alias :create :start
138
+
139
+ # Change parameters of a deployed application group.
140
+ # Changes to application parameters will result in a restart of this application.
141
+ # A new application added to the group is started.
142
+ # An existing application removed from the group gets stopped.
143
+ # If there are no changes to the application definition, no restart is triggered.
144
+ # During restart marathon keeps track, that the configured amount of minimal running instances are always available.
145
+ # A deployment can run forever. This is the case,
146
+ # when the new application has a problem and does not become healthy.
147
+ # In this case, human interaction is needed with 2 possible choices:
148
+ # Rollback to an existing older version (send an existing version in the body)
149
+ # Update with a newer version of the group which does not have the problems of the old one.
150
+ # If there is an upgrade process already in progress, a new update will be rejected unless the force flag is set.
151
+ # With the force flag given, a running upgrade is terminated and a new one is started.
152
+ # Since the deployment of the group can take a considerable amount of time,
153
+ # this endpoint returns immediatly with a version. The failure or success of the action is signalled via event.
154
+ # There is a group_change_success and group_change_failed event with the given version.
155
+ # ++id++: Group's id.
156
+ # ++hash++: Hash of attributes to change.
157
+ # ++force++: If the group is affected by a running deployment, then the update operation will fail.
158
+ # The current deployment can be overridden by setting the `force` query parameter.
159
+ def change(id, hash, force = false)
160
+ query = {}
161
+ query[:force] = true if force
162
+ json = Marathon.connection.put("/v2/groups/#{id}", query, :body => hash)
163
+ Marathon::DeploymentInfo.new(json)
164
+ end
165
+ end
166
+ end