aws-asg-fleet 0.0.1

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/.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: