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.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Toby Clemson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Rollo
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to
4
+ be able to package up your Ruby library into a gem. Put your Ruby code in the
5
+ file `lib/rollo`. To experiment with that code, run `bin/console` for an
6
+ interactive prompt.
7
+
8
+ TODO: Delete this and the text above, and describe your gem
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'rollo'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install rollo
25
+
26
+ ## Usage
27
+
28
+ TODO: Write usage instructions here
29
+
30
+ ## Development
31
+
32
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
33
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
34
+ prompt that will allow you to experiment.
35
+
36
+ To install this gem onto your local machine, run `bundle exec rake install`. To
37
+ release a new version, update the version number in `version.rb`, and then run
38
+ `bundle exec rake release`, which will create a git tag for the version, push
39
+ git commits and tags, and push the `.gem` file to
40
+ [rubygems.org](https://rubygems.org).
41
+
42
+ ## Contributing
43
+
44
+ Bug reports and pull requests are welcome on GitHub at
45
+ https://github.com/infrablocks/rollo. This project is intended to be a safe,
46
+ welcoming space for collaboration, and contributors are expected to adhere to
47
+ the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
48
+
49
+ ## License
50
+
51
+ The gem is available as open source under the terms of the
52
+ [MIT License](https://opensource.org/licenses/MIT).
53
+
54
+ ## Code of Conduct
55
+
56
+ Everyone interacting in the Rollo project’s codebases, issue trackers, chat
57
+ rooms and mailing lists is expected to follow the [code of conduct](
58
+ https://github.com/[USERNAME]/rollo/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task :default => :spec
6
+
7
+ namespace :version do
8
+ desc "Bump version for specified type (pre, major, minor patch)"
9
+ task :bump, [:type] do |_, args|
10
+ bump_version_for(args.type)
11
+ end
12
+ end
13
+
14
+ desc "Release gem"
15
+ task :release do
16
+ sh "gem release --tag --push"
17
+ end
18
+
19
+ def bump_version_for(version_type)
20
+ sh "gem bump --version #{version_type} " +
21
+ "&& bundle install " +
22
+ "&& export LAST_MESSAGE=\"$(git log -1 --pretty=%B)\" " +
23
+ "&& git commit -a --amend -m \"${LAST_MESSAGE} [ci skip]\""
24
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rollo"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/rollo ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'thor'
3
+ require 'rollo'
4
+
5
+ Rollo::Commands::Main.start
data/go ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+
3
+ [ -n "$GO_DEBUG" ] && set -x
4
+ set -e
5
+
6
+ project_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
7
+
8
+ verbose="no"
9
+ skip_checks="no"
10
+ offline="no"
11
+
12
+ missing_dependency="no"
13
+
14
+ [ -n "$GO_DEBUG" ] && verbose="yes"
15
+ [ -n "$GO_SKIP_CHECKS" ] && skip_checks="yes"
16
+ [ -n "$GO_OFFLINE" ] && offline="yes"
17
+
18
+
19
+ if [[ "$skip_checks" = "no" ]]; then
20
+ echo "Checking for system dependencies."
21
+ ruby_version="$(cat "$project_dir"/.ruby-version)"
22
+ if ! type ruby >/dev/null 2>&1 || ! ruby -v | grep -q "$ruby_version"; then
23
+ echo "This codebase requires Ruby $ruby_version."
24
+ missing_dependency="yes"
25
+ fi
26
+
27
+ if ! type bundler >/dev/null 2>&1; then
28
+ echo "This codebase requires Bundler."
29
+ missing_dependency="yes"
30
+ fi
31
+
32
+ if [[ "$missing_dependency" = "yes" ]]; then
33
+ echo "Please install missing dependencies to continue."
34
+ exit 1
35
+ fi
36
+
37
+ echo "All system dependencies present. Continuing."
38
+ fi
39
+
40
+ if [[ "$offline" = "no" ]]; then
41
+ echo "Installing bundler."
42
+ if [[ "$verbose" = "yes" ]]; then
43
+ gem install --no-document bundler
44
+ else
45
+ gem install --no-document bundler > /dev/null
46
+ fi
47
+
48
+ echo "Installing ruby dependencies."
49
+ if [[ "$verbose" = "yes" ]]; then
50
+ bundle install
51
+ else
52
+ bundle install > /dev/null
53
+ fi
54
+ fi
55
+
56
+ echo "Starting rake."
57
+ if [[ "$verbose" = "yes" ]]; then
58
+ time bundle exec rake --verbose "$@"
59
+ else
60
+ time bundle exec rake "$@"
61
+ fi
@@ -0,0 +1,184 @@
1
+ require 'thor'
2
+ require_relative '../model'
3
+
4
+ module Rollo
5
+ module Commands
6
+ class HostCluster < Thor
7
+ def self.exit_on_failure?
8
+ true
9
+ end
10
+
11
+ desc(
12
+ 'expand REGION ASG_NAME ECS_CLUSTER_NAME',
13
+ '')
14
+ method_option(
15
+ :batch_size,
16
+ aliases: '-b',
17
+ type: :numeric,
18
+ default: 3,
19
+ desc: 'The number of hosts to add at a time.')
20
+ def expand(
21
+ region, asg_name, _,
22
+ host_cluster = nil)
23
+ batch_size = options[:batch_size]
24
+
25
+ host_cluster = host_cluster ||
26
+ Rollo::HostCluster.new(asg_name, region)
27
+
28
+ say("Increasing host cluster desired capacity by #{batch_size}...")
29
+ with_padding do
30
+ host_cluster.increase_capacity_by(batch_size) do |on|
31
+ on.prepare do |current, target|
32
+ say(
33
+ "Changing desired capacity from #{current} to " +
34
+ "#{target}...")
35
+ end
36
+ on.waiting_for_start do |attempt|
37
+ say(
38
+ 'Waiting for capacity change to start ' +
39
+ "(attempt #{attempt})...")
40
+ end
41
+ on.waiting_for_end do |attempt|
42
+ say(
43
+ 'Waiting for capacity change to complete ' +
44
+ "(attempt #{attempt})...")
45
+ end
46
+ on.waiting_for_health do |attempt|
47
+ say("Waiting for a healthy state (attempt #{attempt})")
48
+ end
49
+ end
50
+ end
51
+ say "Host cluster desired capacity increased, continuing..."
52
+ end
53
+
54
+ desc(
55
+ 'contract REGION ASG_NAME ECS_CLUSTER_NAME',
56
+ '')
57
+ method_option(
58
+ :batch_size,
59
+ aliases: '-b',
60
+ type: :numeric,
61
+ default: 3,
62
+ desc: 'The number of hosts to remove at a time.')
63
+ def contract(
64
+ region, asg_name, ecs_cluster_name,
65
+ host_cluster = nil, service_cluster = nil)
66
+ batch_size = options[:batch_size]
67
+
68
+ host_cluster = host_cluster ||
69
+ Rollo::HostCluster.new(asg_name, region)
70
+ service_cluster = service_cluster ||
71
+ Rollo::ServiceCluster.new(ecs_cluster_name, region)
72
+
73
+ say("Decreasing host cluster desired capacity by #{batch_size}...")
74
+ with_padding do
75
+ host_cluster.decrease_capacity_by(batch_size) do |on|
76
+ on.prepare do |current, target|
77
+ say(
78
+ "Changing desired capacity from #{current} to " +
79
+ "#{target}...")
80
+ end
81
+ on.waiting_for_start do |attempt|
82
+ say(
83
+ "Waiting for capacity change to start " +
84
+ "(attempt #{attempt})...")
85
+ end
86
+ on.waiting_for_end do |attempt|
87
+ say(
88
+ "Waiting for capacity change to complete " +
89
+ "(attempt #{attempt})...")
90
+ end
91
+ on.waiting_for_health do |attempt|
92
+ say(
93
+ "Waiting for host cluster to reach healthy state " +
94
+ "(attempt #{attempt})...")
95
+ end
96
+ end
97
+ service_cluster.with_replica_services do |on|
98
+ on.each_service do |service|
99
+ service.wait_for_service_health do |on|
100
+ on.waiting_for_health do |attempt|
101
+ say(
102
+ "Waiting for service #{service.name} to reach a " +
103
+ "steady state (attempt #{attempt})...")
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ say "Host cluster desired capacity decreased, continuing..."
110
+ end
111
+
112
+ desc(
113
+ 'terminate REGION ASG_NAME ECS_CLUSTER_NAME INSTANCE_IDS*',
114
+ '')
115
+ method_option(
116
+ :batch_size,
117
+ aliases: '-b',
118
+ type: :numeric,
119
+ default: 3,
120
+ desc: 'The number of hosts to add at a time.')
121
+ method_option(
122
+ :startup_time,
123
+ aliases: '-t',
124
+ type: :numeric,
125
+ default: 2,
126
+ desc: 'The number of minutes to wait for services to start up.')
127
+ def terminate(
128
+ region, asg_name, ecs_cluster_name, instance_ids,
129
+ host_cluster = nil, service_cluster = nil)
130
+ batch_size = options[:batch_size]
131
+
132
+ service_start_wait_minutes = options[:startup_time]
133
+ service_start_wait_seconds = 60 * service_start_wait_minutes
134
+
135
+ host_cluster = host_cluster ||
136
+ Rollo::HostCluster.new(asg_name, region)
137
+ service_cluster = service_cluster ||
138
+ Rollo::ServiceCluster.new(ecs_cluster_name, region)
139
+
140
+ hosts = host_cluster.hosts.select {|h| instance_ids.include?(h.id) }
141
+ host_batches = hosts.each_slice(batch_size).to_a
142
+
143
+ say(
144
+ 'Terminating old hosts in host cluster in batches of ' +
145
+ "#{batch_size}...")
146
+ with_padding do
147
+ host_batches.each_with_index do |host_batch, index|
148
+ say(
149
+ "Batch #{index + 1} contains hosts: " +
150
+ "\n\t\t[#{host_batch.map(&:id).join(",\n\t\t ")}]\n" +
151
+ 'Terminating...')
152
+ host_batch.each(&:terminate)
153
+ host_cluster.wait_for_capacity_health do |on|
154
+ on.waiting_for_health do |attempt|
155
+ say(
156
+ 'Waiting for host cluster to reach healthy state ' +
157
+ "(attempt #{attempt})")
158
+ end
159
+ end
160
+ service_cluster.with_replica_services do |on|
161
+ on.each_service do |service|
162
+ service.wait_for_service_health do |on|
163
+ on.waiting_for_health do |attempt|
164
+ say(
165
+ "Waiting for service #{service.name} to reach a " +
166
+ "steady state (attempt #{attempt})...")
167
+ end
168
+ end
169
+ end
170
+ end
171
+ say(
172
+ "Waiting #{service_start_wait_minutes} minute(s) for " +
173
+ 'services to finish starting...')
174
+ sleep(service_start_wait_seconds)
175
+ say(
176
+ "Waited #{service_start_wait_minutes} minute(s). " +
177
+ 'Continuing...')
178
+ end
179
+ end
180
+
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,93 @@
1
+ require 'thor'
2
+ require_relative '../model'
3
+ require_relative './host_cluster'
4
+ require_relative './service_cluster'
5
+
6
+ module Rollo
7
+ module Commands
8
+ class Main < Thor
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ desc('host-cluster', 'manages the host cluster')
14
+ subcommand "host-cluster", HostCluster
15
+
16
+ desc('service-cluster', 'manages the service cluster')
17
+ subcommand "service-cluster", ServiceCluster
18
+
19
+ desc('version', 'prints the version number of rollo')
20
+ def version
21
+ say Rollo::VERSION
22
+ end
23
+
24
+ desc('roll REGION ASG_NAME ECS_CLUSTER_NAME',
25
+ 'rolls all instances in an ECS cluster')
26
+ method_option(
27
+ :batch_size,
28
+ aliases: '-b',
29
+ type: :numeric,
30
+ default: 3,
31
+ desc:
32
+ 'The number of hosts / service instances to add / remove at ' +
33
+ 'a time.')
34
+ def roll(region, asg_name, ecs_cluster_name)
35
+ host_cluster = Rollo::HostCluster.new(asg_name, region)
36
+ service_cluster = Rollo::ServiceCluster.new(ecs_cluster_name, region)
37
+
38
+ initial_hosts = host_cluster.hosts
39
+
40
+ say(
41
+ "Rolling instances in host cluster #{host_cluster.name} for " +
42
+ "service cluster #{service_cluster.name}...")
43
+ with_padding do
44
+ unless host_cluster.has_desired_capacity?
45
+ say('ERROR: Host cluster is not in stable state.')
46
+ say('This may be due to scaling above or below the desired')
47
+ say('capacity or because hosts are not in service or are ')
48
+ say('unhealthy. Cowardly refusing to roll instances.')
49
+ exit 1
50
+ end
51
+
52
+ invoke(
53
+ "host-cluster:expand",
54
+ [
55
+ region, asg_name, ecs_cluster_name,
56
+ host_cluster
57
+ ])
58
+
59
+ invoke(
60
+ "service-cluster:expand",
61
+ [
62
+ region, asg_name, ecs_cluster_name,
63
+ service_cluster
64
+ ])
65
+
66
+ invoke(
67
+ "host-cluster:terminate",
68
+ [
69
+ region, asg_name, ecs_cluster_name, initial_hosts.map(&:id),
70
+ host_cluster, service_cluster
71
+ ])
72
+
73
+ invoke(
74
+ "host-cluster:contract",
75
+ [
76
+ region, asg_name, ecs_cluster_name,
77
+ host_cluster, service_cluster
78
+ ])
79
+
80
+ invoke(
81
+ "service-cluster:contract",
82
+ [
83
+ region, asg_name, ecs_cluster_name,
84
+ service_cluster
85
+ ])
86
+ end
87
+
88
+ say("Instances in host cluster #{host_cluster.name} rolled " +
89
+ "successfully.")
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,124 @@
1
+ require 'thor'
2
+ require_relative '../model'
3
+
4
+ module Rollo
5
+ module Commands
6
+ class ServiceCluster < Thor
7
+ def self.exit_on_failure?
8
+ true
9
+ end
10
+
11
+ desc(
12
+ 'expand REGION ASG_NAME ECS_CLUSTER_NAME',
13
+ '')
14
+ method_option(
15
+ :batch_size,
16
+ aliases: '-b',
17
+ type: :numeric,
18
+ default: 3,
19
+ desc: 'The number of service instances to add at a time.')
20
+ method_option(
21
+ :startup_time,
22
+ aliases: '-t',
23
+ type: :numeric,
24
+ default: 2,
25
+ desc: 'The number of minutes to wait for services to start up.')
26
+
27
+ def expand(
28
+ region, _, ecs_cluster_name,
29
+ service_cluster = nil)
30
+ batch_size = options[:batch_size]
31
+ service_start_wait_minutes = options[:startup_time]
32
+ service_start_wait_seconds = 60 * service_start_wait_minutes
33
+
34
+ service_cluster = service_cluster ||
35
+ Rollo::ServiceCluster.new(ecs_cluster_name, region)
36
+
37
+ say("Increasing service instance counts by #{batch_size}...")
38
+ with_padding do
39
+ service_cluster.with_replica_services do |on|
40
+ on.start do |services|
41
+ say(
42
+ 'Service cluster contains services:' +
43
+ "\n\t\t[#{services.map(&:name).join(",\n\t\t ")}]")
44
+ end
45
+ on.each_service do |service|
46
+ say(
47
+ "Increasing instance count by #{batch_size} " +
48
+ "for #{service.name}")
49
+ with_padding do
50
+ service.increase_instance_count_by(batch_size) do |on|
51
+ on.prepare do |current, target|
52
+ say(
53
+ "Changing instance count from #{current} " +
54
+ "to #{target}...")
55
+ end
56
+ on.waiting_for_health do |attempt|
57
+ say(
58
+ "Waiting for service to reach a steady state " +
59
+ "(attempt #{attempt})...")
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ say(
67
+ "Waiting #{service_start_wait_minutes} minute(s) for " +
68
+ 'services to finish starting...')
69
+ with_padding do
70
+ sleep(service_start_wait_seconds)
71
+ say(
72
+ "Waited #{service_start_wait_minutes} minute(s). " +
73
+ 'Continuing...')
74
+ end
75
+ say('Service instance counts increased, continuing...')
76
+ end
77
+
78
+ desc(
79
+ 'contract REGION ASG_NAME ECS_CLUSTER_NAME',
80
+ '')
81
+ method_option(
82
+ :batch_size,
83
+ aliases: '-b',
84
+ type: :numeric,
85
+ default: 3,
86
+ desc: 'The number of service instances to remove at a time.')
87
+
88
+ def contract(
89
+ region, _, ecs_cluster_name,
90
+ service_cluster = nil)
91
+ batch_size = options[:batch_size]
92
+
93
+ service_cluster = service_cluster ||
94
+ Rollo::ServiceCluster.new(ecs_cluster_name, region)
95
+
96
+ say("Decreasing service instance counts by #{batch_size}...")
97
+ with_padding do
98
+ service_cluster.with_replica_services do |on|
99
+ on.each_service do |service|
100
+ say(
101
+ "Decreasing instance count by #{batch_size} " +
102
+ "for #{service.name}")
103
+ with_padding do
104
+ service.decrease_instance_count_by(batch_size) do |on|
105
+ on.prepare do |current, target|
106
+ say(
107
+ "Changing instance count from #{current} " +
108
+ "to #{target}...")
109
+ end
110
+ on.waiting_for_health do |attempt|
111
+ say(
112
+ 'Waiting for service to reach a steady state ' +
113
+ "(attempt #{attempt})...")
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ say("Service instance counts decreased, continuing...")
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1 @@
1
+ require_relative 'commands/main'
@@ -0,0 +1,25 @@
1
+ module Rollo
2
+ module Model
3
+ class Host
4
+ def initialize(instance)
5
+ @instance = instance
6
+ end
7
+
8
+ def id
9
+ @instance.id
10
+ end
11
+
12
+ def terminate
13
+ @instance.terminate(should_decrement_desired_capacity: false)
14
+ end
15
+
16
+ def is_in_service?
17
+ @instance.lifecycle_state == 'InService'
18
+ end
19
+
20
+ def is_healthy?
21
+ @instance.health_status == 'Healthy'
22
+ end
23
+ end
24
+ end
25
+ end