marathon-api 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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