aws-asg-fleet 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in asg-fleet.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Zach Wily
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # AWS Auto Scaling Fleet
2
+
3
+ Helps manage several Auto-Scaling Groups that share common
4
+ configuration.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'aws-asg-fleet'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install aws-asg-fleet
19
+
20
+ ## Usage
21
+
22
+ Initialize:
23
+
24
+ ```ruby
25
+ require 'aws/auto_scaling/fleets'
26
+
27
+ auto_scaling = AWS::AutoScaling.new(
28
+ :access_key_id => 'YOUR_ACCESS_KEY_ID',
29
+ :secret_access_key => 'YOUR_SECRET_ACCESS_KEY')
30
+ ```
31
+
32
+ First create an Auto Scaling group (with a Launch Configuration, Scaling
33
+ Policy, and an Alarm to trigger the Policy.) This ASG (and related
34
+ objects) will act as a template for future additions to the fleet:
35
+
36
+ ```ruby
37
+ launch_config = auto_scaling.launch_configurations.create(
38
+ 'my-launch-config',
39
+ 'ami-123456',
40
+ 't1.micro')
41
+
42
+ group = auto_scaling.groups.create('my-group',
43
+ :launch_configuration => launch_config,
44
+ :availability_zones => [ 'us-east-1a' ],
45
+ :min_size => 1,
46
+ :max_size => 4,
47
+ :load_balancers => [ 'my-main-elb' ])
48
+
49
+ up_policy = group.scaling_policies.create('my-scaleup-policy',
50
+ :adjustment_type => 'ChangeInCapacity',
51
+ :scaling_adjustment => 1)
52
+
53
+ down_policy => group.scaling_policies.create('my-scaledown-policy',
54
+ :adjustment_type => 'ChangeInCapacity',
55
+ :scaling_adjustment => -1)
56
+
57
+ cloudwatch = AWS::CloudWatch.new
58
+ up_alarm = cloudwatch.alarms.create('cpu-high-alarm',
59
+ :namespace => "AWS/EC2",
60
+ :metric_name => "CPUUtilization",
61
+ :dimensions => [{
62
+ :name => "AutoScalingGroupName",
63
+ :value => group.name
64
+ }],
65
+ :comparison_operator => "GreaterThanOrEqualToThreshold",
66
+ :evaluation_periods => 2,
67
+ :period => 120,
68
+ :statistic => "Average",
69
+ :threshold => 80,
70
+ :alarm_actions => [ up_policy.arn ])
71
+
72
+ down_alarm = cloudwatch.alarms.create('cpu-low-alarm',
73
+ :namespace => "AWS/EC2",
74
+ :metric_name => "CPUUtilization",
75
+ :dimensions => [{
76
+ :name => "AutoScalingGroupName",
77
+ :value => group.name
78
+ }],
79
+ :comparison_operator => "LessThanOrEqualToThreshold",
80
+ :evaluation_periods => 2,
81
+ :period => 300,
82
+ :statistic => "Average",
83
+ :threshold => 40,
84
+ :alarm_actions => [ down_policy.arn ])
85
+ ```
86
+
87
+ Phew - that was a lot of stuff. But we now have a configured Auto
88
+ Scaling group we can use in our fleet.
89
+
90
+ Create the fleet:
91
+
92
+ ```ruby
93
+ fleet = auto_scaling.fleets.create('my-fleet', group)
94
+ ```
95
+
96
+ Add another group to the fleet:
97
+
98
+ ```ruby
99
+ # (creation of other_group not shown)
100
+ fleet.groups << other_group
101
+ ```
102
+
103
+ Now that you have a fleet with multiple groups you can perform actions
104
+ across the fleet.
105
+
106
+ Suspend and resume scaling activities across all groups:
107
+
108
+ ```ruby
109
+ fleet.suspend_all_processes
110
+ # do stuff where you don't want scaling activities
111
+ fleet.resume_all_processes
112
+ ```
113
+
114
+ Update the launch configuration for all groups:
115
+
116
+ ```ruby
117
+ fleet.update_launch_configuration('new-launch-config',
118
+ :image_id => 'ami-8765432')
119
+ ```
120
+
121
+ That will create a new launch configuration, and add it to all the
122
+ groups, replacing the old one.
123
+
124
+ Above, we showed how to add a group you've already created to the fleet.
125
+ You can also create a group *based on the existing template group*. This
126
+ method will create a new group using options you pass in, but any
127
+ options you don't specify will be pulled from the template group. Policies
128
+ and alarms also get duplicated for the new group:
129
+
130
+ ```ruby
131
+ fleet.groups.create('yet-another-group',
132
+ :load_balancers => [ 'my-other-elb' ])
133
+ ```
134
+
135
+ ## How It Works
136
+
137
+ Fleets are identified using tags on Auto Scaling groups. When you create
138
+ a new fleet 'my-fleet', it adds the tag key "asgfleet:my-fleet" with
139
+ value "template". Other groups added to the fleet get tagged with key
140
+ "asgfleet:my-fleet" and value "member".
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/asg-fleet.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ #require 'asg/fleet/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "aws-asg-fleet"
8
+ spec.version = "0.0.1"
9
+ spec.authors = ["Zach Wily"]
10
+ spec.email = ["zach@zwily.com"]
11
+ spec.description = %q{AWS Auto Scaling Fleets}
12
+ spec.summary = %q{Provides a mechanism to group together Auto Scaling groups and perform actions on them in bulk.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "aws-sdk", "1.11.3"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,88 @@
1
+ module AWS
2
+ class AutoScaling
3
+ class Fleet < Core::Resource
4
+
5
+ def initialize name, options = {}
6
+ @name = name
7
+ super
8
+ end
9
+
10
+ # @return [String]
11
+ attr_reader :name
12
+
13
+ # @return [Group]
14
+ def template_group
15
+ tag = TagCollection.new(:config => config)
16
+ .filter(:key, "asgfleet:#{name}")
17
+ .filter(:value, "template")
18
+ .first
19
+ return nil unless tag
20
+ tag.resource
21
+ end
22
+
23
+ def exists?
24
+ !template_group.nil?
25
+ end
26
+
27
+ def groups
28
+ FleetGroupCollection.new(self)
29
+ end
30
+
31
+ # Suspends all scaling processes in all Auto Scaling groups in the
32
+ # fleet.
33
+ def suspend_all_processes
34
+ groups.each do |group|
35
+ group.suspend_all_processes
36
+ end
37
+ end
38
+
39
+ # Resumes all scaling processes in all Auto Scaling groups in the
40
+ # fleet.
41
+ def resume_all_processes
42
+ groups.each do |group|
43
+ group.resume_all_processes
44
+ end
45
+ end
46
+
47
+ # Creates a new launch configuration and applies it to all the
48
+ # Auto Scaling groups in the fleet. Any options not specified will
49
+ # be pulled from the Launch Configuration currently attached to
50
+ # the template Auto Scaling group.
51
+ #
52
+ # @param [String] name The name of the new launch configuration
53
+ #
54
+ # @param [Hash] options Options for the new launch configuration.
55
+ # Any options not specified in this hash will be pulled from the
56
+ # existing launch configuration on the template scaling group.
57
+ #
58
+ def update_launch_configuration name, options = {}
59
+ old_lc = template_group.launch_configuration
60
+ image_id = options[:image_id] || old_lc.image_id
61
+ instance_type = options[:instance_type] || old_lc.instance_type
62
+ [ :block_device_mappings, :detailed_instance_monitoring, :kernel_id,
63
+ :key_pair, :ramdisk_id, :security_groups, :user_data,
64
+ :iam_instance_profile, :spot_price ].each do |k|
65
+ existing_value = old_lc.send(k)
66
+ next if existing_value == nil || existing_value == []
67
+ options[k] ||= existing_value
68
+ end
69
+
70
+ launch_configurations = LaunchConfigurationCollection.new(:config => config)
71
+ puts options
72
+ new_lc = launch_configurations.create(name, image_id, instance_type, options)
73
+
74
+ groups.each do |group|
75
+ group.update(:launch_configuration => new_lc)
76
+ end
77
+
78
+ new_lc
79
+ end
80
+
81
+ protected
82
+
83
+ def resource_identifiers
84
+ [[:name, name]]
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,50 @@
1
+ module AWS
2
+ class AutoScaling
3
+ class FleetCollection
4
+
5
+ include Core::Collection::Simple
6
+
7
+ # Create an ASG Fleet.
8
+ #
9
+ # To create a Fleet, you must supply an already-constructed
10
+ # Auto Scaling group to be used as the template for new groups
11
+ # added to the fleet.
12
+ #
13
+ # fleet = auto_scaling.fleets.create('fleet-name',
14
+ # auto_scaling.groups['my-asg-group'])
15
+ #
16
+ # @param [String] name The name of the new fleet to create.
17
+ # Must be unique in your account.
18
+ #
19
+ # @param [Group] group The group to be used as a template
20
+ # for future groups and fleet changes.
21
+ #
22
+ # @return [Fleet]
23
+ #
24
+ def create name, template_group
25
+ raise ArgumentError, "Fleet #{name} already exists" if self[name].exists?
26
+ raise ArgumentError, "Group is already in a fleet" if template_group.fleet
27
+
28
+ template_group.set_fleet(name, "template")
29
+ self[name]
30
+ end
31
+
32
+ # @param [String] name The name of the ASG fleet.
33
+ # @return [Fleet]
34
+ def [] name
35
+ Fleet.new(name, :config => config)
36
+ end
37
+
38
+ protected
39
+
40
+ def _each_item options
41
+ TagCollection.new(:config => config).filter(:value, "template").each do |tag|
42
+ if tag[:key] =~ /^asgfleet:/
43
+ name = tag[:key].split(':', 2)[1]
44
+ yield Fleet.new(name, :config => config)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,145 @@
1
+ module AWS
2
+ class AutoScaling
3
+ class FleetGroupCollection
4
+
5
+ include Core::Collection::Simple
6
+
7
+ def initialize fleet, options = {}
8
+ @fleet = fleet
9
+ super
10
+ end
11
+
12
+ # @return [Fleet]
13
+ attr_reader :fleet
14
+
15
+ # Add an existing group to a Fleet.
16
+ #
17
+ # @param [Group] The group to add.
18
+ def << group
19
+ group.set_fleet @fleet.name
20
+ end
21
+
22
+ # Create a group within a Fleet.
23
+ #
24
+ # To create a group you supply a name, and any desired Auto
25
+ # Scaling group options. Any options not specified will be pulled
26
+ # from the template group for the fleet. Scaling policies and
27
+ # alarms will also be created for the new Group, based on those
28
+ # associated with the template.
29
+ #
30
+ # @param [String] name The name of the new group to create in the
31
+ # fleet. Must be unique in your account.
32
+ #
33
+ # @param [Hash] options
34
+ #
35
+ # @option (see GroupOptions#group_options)
36
+ #
37
+ # @return [Group]
38
+ def create name, options = {}
39
+ ## Clone the group
40
+ template_group = @fleet.template_group
41
+
42
+ options = options_from(template_group,
43
+ :load_balancers, :min_size, :max_size, :launch_configuration,
44
+ :availability_zones, :default_cooldown, :desired_capacity,
45
+ :health_check_grace_period, :health_check_type, :placement_group,
46
+ :termination_policies, :subnets).merge(options)
47
+
48
+ # merge together tags from options and the template
49
+ new_tags = template_group.tags.to_a.map do |t|
50
+ {
51
+ :key => t[:key],
52
+ :value => t[:value],
53
+ :propagate_at_launch => t[:propagate_at_launch]
54
+ }
55
+ end
56
+ if options[:tags]
57
+ options[:tags].each do |tag|
58
+ existing_tag = new_tags.find {|t| t[:key] == tag[:key] }
59
+ if existing_tag
60
+ existing_tag.merge! tag
61
+ else
62
+ new_tags << tag
63
+ end
64
+ end
65
+ end
66
+ # change the fleet tag value from "template" to "member"
67
+ fleet_tag = new_tags.find {|t| t[:key] == "asgfleet:#{@fleet.name}" }
68
+ fleet_tag[:value] = "member"
69
+ options[:tags] = new_tags
70
+
71
+ group = GroupCollection.new(:config => config).create name, options
72
+
73
+ ## Clone the scaling policies and alarms from the group
74
+ cloudwatch = AWS::CloudWatch.new(:config => config)
75
+ template_group.scaling_policies.each do |template_policy|
76
+ policy_options = options_from(template_policy,
77
+ :adjustment_type, :scaling_adjustment, :cooldown, :min_adjustment_step)
78
+
79
+ policy = group.scaling_policies.create template_policy.name, policy_options
80
+
81
+ template_policy.alarms.keys.each do |template_alarm_name|
82
+ template_alarm = cloudwatch.alarms[template_alarm_name]
83
+ alarm_name = "#{template_alarm.name}-#{group.name}"
84
+ alarm_options = options_from(template_alarm,
85
+ :namespace, :metric_name, :comparison_operator, :evaluation_periods,
86
+ :period, :statistic, :threshold, :actions_enabled, :alarm_description,
87
+ :unit)
88
+
89
+ # For dimensions, copy them all except for the one
90
+ # referencing the ASG - replace that with the right group.
91
+ alarm_options[:dimensions] = template_alarm.dimensions.map do |dimension|
92
+ if dimension[:name] == "AutoScalingGroupName"
93
+ { :name => "AutoScalingGroupName", :value => group.name }
94
+ else
95
+ dimension
96
+ end
97
+ end
98
+
99
+ # For actions, we want to go through them all, replacing any
100
+ # ARNs referencing the template_policy with ones pointing to
101
+ # our new policy. For ARNs we don't recognize, we just copy
102
+ # them, assuming the user wants those ARNs to continue to
103
+ # fire.
104
+ [ :insufficient_data_actions, :ok_actions, :alarm_actions ].each do |key|
105
+ new_actions = template_alarm.send(key).map do |arn|
106
+ if arn == template_policy.arn
107
+ policy.arn
108
+ else
109
+ arn
110
+ end
111
+ end
112
+
113
+ unless new_actions.empty?
114
+ alarm_options[key] = new_actions
115
+ end
116
+ end
117
+
118
+ cloudwatch.alarms.create alarm_name, alarm_options
119
+ end
120
+ end
121
+
122
+ group
123
+ end
124
+
125
+ protected
126
+
127
+ def _each_item options
128
+ TagCollection.new(:config => config).filter(:key, "asgfleet:#{@fleet.name}").each do |tag|
129
+ yield tag.resource
130
+ end
131
+ end
132
+
133
+ def options_from(obj, *attributes)
134
+ opts = {}
135
+ attributes.each do |key|
136
+ value = obj.send(key)
137
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
138
+ value = value.to_a if value.is_a? Array
139
+ opts[key] ||= value
140
+ end
141
+ opts
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,49 @@
1
+ require 'aws/auto_scaling'
2
+ require 'aws/version'
3
+
4
+ module AWS
5
+ class AutoScaling
6
+
7
+ autoload :Fleet, 'aws/auto_scaling/fleet'
8
+ autoload :FleetCollection, 'aws/auto_scaling/fleet_collection'
9
+ autoload :FleetGroupCollection, 'aws/auto_scaling/fleet_group_collection'
10
+
11
+ # @return [FleetCollection]
12
+ def fleets
13
+ FleetCollection.new(:config => config)
14
+ end
15
+
16
+ class Group
17
+ # stupid hack to get an attribute added to Group. It'll be fixed in
18
+ # 1.11.4, so remove this and set 1.11.4 as the required version when
19
+ # released.
20
+ if AWS::VERSION == '1.11.3'
21
+ attribute :termination_policies
22
+ end
23
+
24
+ def fleet
25
+ fleet_tag = tags.find {|t| t[:key] =~ /^asgfleet:/ }
26
+ return nil unless fleet_tag
27
+
28
+ fleet_name = fleet_tag[:key].split(':', 2)[1]
29
+ Fleet.new(fleet_name, :config => config)
30
+ end
31
+
32
+ def set_fleet fleet, role = "member"
33
+ if fleet && self.fleet
34
+ raise ArgumentError, "Group already belongs to a fleet"
35
+ end
36
+
37
+ if fleet.nil?
38
+ tags.find {|t| t[:key] =~ /^asgfleet:/ }.delete
39
+ else
40
+ self.update(:tags => [{
41
+ :key => "asgfleet:#{fleet.is_a?(Fleet) ? fleet.name : fleet}",
42
+ :value => role,
43
+ :propagate_at_launch => false
44
+ }])
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-asg-fleet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Zach Wily
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: aws-sdk
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.11.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.11.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.3'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: AWS Auto Scaling Fleets
63
+ email:
64
+ - zach@zwily.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - asg-fleet.gemspec
75
+ - lib/aws/auto_scaling/fleet.rb
76
+ - lib/aws/auto_scaling/fleet_collection.rb
77
+ - lib/aws/auto_scaling/fleet_group_collection.rb
78
+ - lib/aws/auto_scaling/fleets.rb
79
+ homepage: ''
80
+ licenses:
81
+ - MIT
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 1.8.23
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Provides a mechanism to group together Auto Scaling groups and perform actions
104
+ on them in bulk.
105
+ test_files: []
106
+ has_rdoc: