rollo 0.1.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.
@@ -0,0 +1,128 @@
1
+ require 'aws-sdk'
2
+ require 'hollerback'
3
+
4
+ require_relative './scaling_activity'
5
+ require_relative './host'
6
+
7
+ module Rollo
8
+ module Model
9
+ class HostCluster
10
+ attr_reader :last_scaling_activity
11
+
12
+ def initialize(asg_name, region, asg_resource = nil, waiter = nil)
13
+ @region = region
14
+ @asg_name = asg_name
15
+ @asg_resource = asg_resource ||
16
+ Aws::AutoScaling::Resource.new(region: region)
17
+ @asg = @asg_resource.group(@asg_name)
18
+ record_latest_scaling_activity
19
+
20
+ @waiter = waiter || Wait.new(attempts: 300, timeout: 30, delay: 5)
21
+ end
22
+
23
+ def reload
24
+ @asg.reload
25
+ end
26
+
27
+ def name
28
+ @asg_name
29
+ end
30
+
31
+ def desired_capacity
32
+ @asg.desired_capacity
33
+ end
34
+
35
+ def desired_capacity=(capacity)
36
+ @asg.set_desired_capacity({desired_capacity: capacity})
37
+ end
38
+
39
+ def has_desired_capacity?
40
+ hosts.size == desired_capacity &&
41
+ hosts.all? {|h| h.is_in_service? && h.is_healthy?}
42
+ end
43
+
44
+ def scaling_activities
45
+ @asg.activities.collect {|a| ScalingActivity.new(a)}
46
+ end
47
+
48
+ def has_started_changing_capacity?
49
+ scaling_activities
50
+ .select {|a| a.started_after_completion_of?(@last_scaling_activity)}
51
+ .size > 0
52
+ end
53
+
54
+ def has_completed_changing_capacity?
55
+ scaling_activities.all?(&:is_complete?)
56
+ end
57
+
58
+ def hosts
59
+ @asg.instances.collect {|h| Host.new(h)}
60
+ end
61
+
62
+ def increase_capacity_by(capacity_delta, &block)
63
+ initial = desired_capacity
64
+ increased = initial + capacity_delta
65
+
66
+ callbacks_for(block).try_respond_with(
67
+ :prepare, initial, increased)
68
+
69
+ ensure_capacity_changed_to(increased, &block)
70
+ end
71
+
72
+ def decrease_capacity_by(capacity_delta, &block)
73
+ initial = desired_capacity
74
+ decreased = initial - capacity_delta
75
+
76
+ callbacks_for(block).try_respond_with(
77
+ :prepare, initial, decreased)
78
+
79
+ ensure_capacity_changed_to(decreased, &block)
80
+ end
81
+
82
+ def ensure_capacity_changed_to(capacity, &block)
83
+ self.desired_capacity = capacity
84
+ wait_for_capacity_change_start(&block)
85
+ wait_for_capacity_change_end(&block)
86
+ wait_for_capacity_health(&block)
87
+ record_latest_scaling_activity
88
+ end
89
+
90
+ def wait_for_capacity_change_start(&block)
91
+ @waiter.until do |attempt|
92
+ reload
93
+ callbacks_for(block)
94
+ .try_respond_with(:waiting_for_start, attempt) if block
95
+ has_started_changing_capacity?
96
+ end
97
+ end
98
+
99
+ def wait_for_capacity_change_end(&block)
100
+ @waiter.until do |attempt|
101
+ reload
102
+ callbacks_for(block)
103
+ .try_respond_with(:waiting_for_end, attempt) if block
104
+ has_completed_changing_capacity?
105
+ end
106
+ end
107
+
108
+ def wait_for_capacity_health(&block)
109
+ @waiter.until do |attempt|
110
+ reload
111
+ callbacks_for(block)
112
+ .try_respond_with(:waiting_for_health, attempt) if block
113
+ has_desired_capacity?
114
+ end
115
+ end
116
+
117
+ def record_latest_scaling_activity
118
+ @last_scaling_activity = scaling_activities.first
119
+ end
120
+
121
+ private
122
+
123
+ def callbacks_for(block)
124
+ Hollerback::Callbacks.new(block)
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,32 @@
1
+ module Rollo
2
+ module Model
3
+ class ScalingActivity
4
+ def initialize(activity)
5
+ @activity = activity
6
+ end
7
+
8
+ def id
9
+ @activity.activity_id
10
+ end
11
+
12
+ def start_time
13
+ @activity.start_time
14
+ end
15
+
16
+ def end_time
17
+ @activity.end_time
18
+ end
19
+
20
+ def started_after_completion_of?(other)
21
+ self.id != other.id &&
22
+ !self.start_time.nil? &&
23
+ !other.end_time.nil? &&
24
+ self.start_time > other.end_time
25
+ end
26
+
27
+ def is_complete?
28
+ %w(Successful Failed Cancelled).include?(@activity.status_code)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,104 @@
1
+ require 'aws-sdk'
2
+ require 'wait'
3
+ require 'hollerback'
4
+
5
+ module Rollo
6
+ module Model
7
+ class Service
8
+ def initialize(
9
+ ecs_cluster_name, ecs_service_arn, region,
10
+ ecs_resource = nil, waiter = nil)
11
+ @ecs_cluster_name = ecs_cluster_name
12
+ @ecs_service_arn = ecs_service_arn
13
+ @ecs_resource = ecs_resource || Aws::ECS::Resource.new(region: region)
14
+ reload
15
+
16
+ @waiter = waiter || Wait.new(attempts: 300, timeout: 30, delay: 5)
17
+ end
18
+
19
+ def name
20
+ @ecs_service.service_name
21
+ end
22
+
23
+ def instance
24
+ @ecs_service
25
+ end
26
+
27
+ def reload
28
+ @ecs_service = get_ecs_service
29
+ end
30
+
31
+ def is_replica?
32
+ @ecs_service.scheduling_strategy == 'REPLICA'
33
+ end
34
+
35
+ def running_count
36
+ @ecs_service.running_count
37
+ end
38
+
39
+ def desired_count
40
+ @ecs_service.desired_count
41
+ end
42
+
43
+ def desired_count=(count)
44
+ @ecs_resource.client
45
+ .update_service(
46
+ cluster: @ecs_cluster_name,
47
+ service: @ecs_service_arn,
48
+ desired_count: count)
49
+ end
50
+
51
+ def has_desired_count?
52
+ running_count == desired_count
53
+ end
54
+
55
+ def increase_instance_count_by(count_delta, &block)
56
+ initial = desired_count
57
+ increased = initial + count_delta
58
+
59
+ callbacks_for(block).try_respond_with(
60
+ :prepare, initial, increased)
61
+
62
+ ensure_instance_count(increased, &block)
63
+ end
64
+
65
+ def decrease_instance_count_by(count_delta, &block)
66
+ initial = desired_count
67
+ decreased = initial - count_delta
68
+
69
+ callbacks_for(block).try_respond_with(
70
+ :prepare, initial, decreased)
71
+
72
+ ensure_instance_count(decreased, &block)
73
+ end
74
+
75
+ def ensure_instance_count(count, &block)
76
+ self.desired_count = count
77
+ wait_for_service_health(&block)
78
+ end
79
+
80
+ def wait_for_service_health(&block)
81
+ @waiter.until do |attempt|
82
+ reload
83
+ callbacks_for(block)
84
+ .try_respond_with(:waiting_for_health, attempt) if block
85
+ has_desired_count?
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def get_ecs_service
92
+ @ecs_resource.client
93
+ .describe_services(
94
+ cluster: @ecs_cluster_name,
95
+ services: [@ecs_service_arn])
96
+ .services[0]
97
+ end
98
+
99
+ def callbacks_for(block)
100
+ Hollerback::Callbacks.new(block)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,53 @@
1
+ require 'aws-sdk'
2
+ require 'hollerback'
3
+
4
+ require_relative './service'
5
+
6
+ module Rollo
7
+ module Model
8
+ class ServiceCluster
9
+ def initialize(ecs_cluster_name, region, ecs_resource = nil)
10
+ @region = region
11
+ @ecs_cluster_name = ecs_cluster_name
12
+ @ecs_resource = ecs_resource || Aws::ECS::Resource.new(region: region)
13
+ @ecs_cluster = get_ecs_cluster
14
+ end
15
+
16
+ def name
17
+ @ecs_cluster_name
18
+ end
19
+
20
+ def replica_services
21
+ get_ecs_service_arns
22
+ .collect {|arn| Service.new(@ecs_cluster_name, arn, @region)}
23
+ .select(&:is_replica?)
24
+ end
25
+
26
+ def with_replica_services(&block)
27
+ all_replica_services = replica_services
28
+
29
+ callbacks = Hollerback::Callbacks.new(block)
30
+ callbacks.try_respond_with(
31
+ :start, all_replica_services)
32
+
33
+ all_replica_services.each do |service|
34
+ callbacks.try_respond_with(:each_service, service)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def get_ecs_cluster
41
+ @ecs_resource.client
42
+ .describe_clusters(clusters: [@ecs_cluster_name])
43
+ .clusters[0]
44
+ end
45
+
46
+ def get_ecs_service_arns
47
+ @ecs_resource.client
48
+ .list_services(cluster: @ecs_cluster.cluster_name)
49
+ .inject([]) {|arns, response| arns + response.service_arns}
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'model/host_cluster'
2
+ require_relative 'model/service_cluster'
@@ -0,0 +1,3 @@
1
+ module Rollo
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rollo.rb ADDED
@@ -0,0 +1,5 @@
1
+ require_relative "rollo/version"
2
+ require_relative 'rollo/commands'
3
+
4
+ module Rollo
5
+ end
data/rollo.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'rollo/version'
4
+ require 'date'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rollo'
8
+ spec.version = Rollo::VERSION
9
+ spec.authors = ['Toby Clemson']
10
+ spec.email = ['tobyclemson@gmail.com']
11
+
12
+ spec.date = Date.today.to_s
13
+ spec.summary = 'Cluster / service roller for AWS ECS.'
14
+ spec.description = 'Strategies for rolling ECS container instance clusters.'
15
+ spec.homepage = 'https://github.com/infrablocks/rollo'
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f)}
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'aws-sdk', '~> 3.0'
26
+ spec.add_dependency 'aws-sdk-ecs', '~> 1.22'
27
+ spec.add_dependency 'wait', '~> 0.5'
28
+ spec.add_dependency 'hollerback', '~> 0.1'
29
+ spec.add_dependency 'thor', '~> 0.20'
30
+
31
+ spec.add_development_dependency 'bundler', '~> 1.17'
32
+ spec.add_development_dependency 'rake', '~> 12.3'
33
+ spec.add_development_dependency 'rspec', '~> 3.8'
34
+ spec.add_development_dependency 'aruba', '~> 0.14'
35
+ spec.add_development_dependency 'gem-release', '~> 2.0'
36
+ spec.add_development_dependency 'irbtools', '~> 2.2'
37
+ end
metadata ADDED
@@ -0,0 +1,227 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rollo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Toby Clemson
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-ecs
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.22'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.22'
41
+ - !ruby/object:Gem::Dependency
42
+ name: wait
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: hollerback
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.20'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.20'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.17'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.17'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '12.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '12.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.8'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.8'
125
+ - !ruby/object:Gem::Dependency
126
+ name: aruba
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.14'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.14'
139
+ - !ruby/object:Gem::Dependency
140
+ name: gem-release
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: irbtools
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.2'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.2'
167
+ description: Strategies for rolling ECS container instance clusters.
168
+ email:
169
+ - tobyclemson@gmail.com
170
+ executables:
171
+ - rollo
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - ".envrc"
176
+ - ".gitignore"
177
+ - ".rspec"
178
+ - ".ruby-version"
179
+ - ".travis.yml"
180
+ - CODE_OF_CONDUCT.md
181
+ - Gemfile
182
+ - Gemfile.lock
183
+ - LICENSE.txt
184
+ - README.md
185
+ - Rakefile
186
+ - bin/console
187
+ - bin/setup
188
+ - exe/rollo
189
+ - go
190
+ - lib/rollo.rb
191
+ - lib/rollo/commands.rb
192
+ - lib/rollo/commands/host_cluster.rb
193
+ - lib/rollo/commands/main.rb
194
+ - lib/rollo/commands/service_cluster.rb
195
+ - lib/rollo/model.rb
196
+ - lib/rollo/model/host.rb
197
+ - lib/rollo/model/host_cluster.rb
198
+ - lib/rollo/model/scaling_activity.rb
199
+ - lib/rollo/model/service.rb
200
+ - lib/rollo/model/service_cluster.rb
201
+ - lib/rollo/version.rb
202
+ - rollo.gemspec
203
+ homepage: https://github.com/infrablocks/rollo
204
+ licenses:
205
+ - MIT
206
+ metadata: {}
207
+ post_install_message:
208
+ rdoc_options: []
209
+ require_paths:
210
+ - lib
211
+ required_ruby_version: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ required_rubygems_version: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ requirements: []
222
+ rubyforge_project:
223
+ rubygems_version: 2.5.2.3
224
+ signing_key:
225
+ specification_version: 4
226
+ summary: Cluster / service roller for AWS ECS.
227
+ test_files: []