Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

add require and exclude methods, licence and readme

  • Loading branch information...
commit e71dbae614077d16fab0a2dcca0812f4a8900cca 1 parent eb4ccab
danmiley authored
View
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2011 Dan MIley
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
106 README.markdown
@@ -3,12 +3,114 @@ cap-elb
Capistrano plugin or deploying to Amazon EC2 instances behind ELBs
-
Introduction
============
-This capistrano plugin
+This capistrano plugin lets you perform capistrano deployment tasks on either complete or highly segmented AWS instance sets within an AWS load balancer under your control.
Installation
============
+`cap-elb` is a Capistrano plug-in configured as Ruby gem. You can install from rubygems.com or build from source via GitHub fork or downlaod.
+
+RubyGems install:
+---------
+ $ gem install cap-elb
+
+How to Use
+=====
+
+In order to use the `cap-elb` plugin, you must require it in your `deploy.rb`:
+
+ require 'cap-elb'
+
+If you have already been doing capistrano deploys to your AWS instances, you probably already have your
+AWD credentials configured. Either add your credentials to your ~/.caprc :
+
+ set :aws_access_key_id, 'YOUR_AWS_ACCESS_KEY_ID'
+ set :aws_secret_access_key, 'YOUR_AWS_SECRET_ACCESS_KEY'
+
+or, directly in your deploy file:
+
+ set :aws_access_key_id, 'YOUR_AWS_ACCESS_KEY_ID'
+ set :aws_secret_access_key, 'YOUR_AWS_SECRET_ACCESS_KEY'
+
+If you wish, you can also set other AWS specfic parameters:
+
+ set :aws_params, :region => 'us-east-1'
+
+In order to define your instance groups, you must specify the security group name, the roles and params:
+Next you will set up your instance sets associated with a named load balancer instance in your AWS account.
+You will call out the load balancer name (e.g. 'lb_webserver'), the capistrano role associated with that load balancer (.e.g. 'web'),
+and any optional params.
+
+ loadbalancer :lb_webserver, :web
+ loadbalancer :lb_appserver, :app
+ loadbalancer :lb_dbserver, :db, :port => 22000
+
+There are two special parameters you can add, :require and :exclude.
+
+AWS instances have top level metadata and user defined tag data, and this data can be used by your loadbalancer rule
+ to include or exclude certain instances from the instance set.
+
+Take the :require keyword; Lets say we only want to deploy to AWS instances which are in the 'running' state. To do that:
+
+ loadbalancer :lb_appserver, :app, :require => { :aws_state => "running" }
+
+The server set defined here for role :app are all instances in the loadbalancer 'lb_appserver' with aws_state set to 'running'.
+
+Perhaps you have added tags to your instances, if so, you might want to deploy to only the instances meeting a specific tag value:
+
+ loadbalancer :lb_appserver, :app, :require => { :aws_state => "running", :tags => {'fleet_color' => "green", 'tier' => 'free'} }
+
+The server set defined here for role :app are all instances in the loadbalancer 'lb_appserver' with aws_state set to 'running',
+and that have the named 2 tags set, with exactly those values for each. There can be other tags in the instance, but the named tags in the rule must be present
+for the given instance to make it into the server set.
+
+Now consider the :exclude keyword; Lets say we do not want to deploy to AWS instances which are 'micro' sized. To do that:
+
+ loadbalancer :lb_appserver, :app, :exclude => { :aws_instance_type => "t1.micro" }
+
+You can exclude instances that have certain tags:
+
+ loadbalancer :lb_appserver, :app, :exclude => { :aws_instance_type => "t1.micro", :tags => {'state' => 'dontdeploy' } }
+
+When your capistrono script is complete, you can deploy to all instances within the ELB that meet your criteria with:
+
+ % cap deploy
+
+Here's an example of a task that does a quick list of the instance ids (if any) within the load balancer associated with the 'app' role
+that meets the criteria you laid out in the loadbalancer definition line,
+add this to your cap deploy file:
+
+ # run with cap ec2:list
+ namespace :ec2 do
+ desc "list instances"
+ task :list, :roles => :app do
+ run "hostname"
+ end
+ end
+
+This will give you the list of hosts behind the load balancer that meet the criteria.
+ % cap ec2:list
+
+Documentation
+=============
+Additional Ruby class/method documentation is available at: [http://rubydoc.info/gems/cap-elb/frames] (http://rubydoc.info/gems/cap-elb/frames)
+
+* capistrano: [http://capify.org](http://capify.org)
+* Amazon AWS: [http://aws.amazon.com](http://aws.amazon.com)
+* Amazon AMI instance metadata: [http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/index.html?AESDG-chapter-instancedata.html](http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/index.html?AESDG-chapter-instancedata.html)
+* Amazon AMI Tags: [http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/Using_Tags.html[(http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/Using_Tags.html)
+
+Credits
+=======
+* capistrano-ec2group: [Logan Raarup](http://github.com/logandk)
+* capistrano: [Jamis Buck](http://github.com/jamis/capistrano)
+
+
+###Thanks to###
+* [Logan Raarup](http://github.com/logandk - Logan's work with cap deploy using security group abstraction got me going on how to do an AWS oriented cap plug-in, thank you!
+
+
+Copyright (c) 2011 Dan Miley, released under the MIT license
View
4 cap-elb.gemspec
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
s.authors = ["Dan Miley"]
s.email = ["dan.miley@gmail.com"]
s.homepage = "http://github.com/danmiley/cap-elb"
- s.summary = %q{HEAVY DEVELOPMENT AT THIS TIME: Capistrano can perform tasks on Amazon ELB instances.}
- s.description = %q{Capistrano can perform tasks on Amazon ELB instances; various arguments to allow instance tags to determine whether task should be applied on the given tag}
+ s.summary = %q{UNDEREAVY DEVELOPMENT AT THIS TIME: Capistrano can perform tasks on Amazon ELB instances.}
+ s.description = %q{(UNDER HEAVY ITERATIVE DEVELOPMENT AT THIS TIME, recommend wait for a 0.x release for usage.) Capistrano can perform tasks on Amazon ELB instances; various arguments to allow instance tags to determine whether task should be applied on the given tag}
s.rubyforge_project = "cap-elb"
View
71 lib/cap-elb.rb
@@ -1,5 +1,5 @@
-require "cap-elb/version"
require 'right_aws'
+require "cap-elb/version"
unless Capistrano::Configuration.respond_to?(:instance)
abort "cap-elb requires Capistrano 2"
@@ -17,42 +17,75 @@ module LoadBalancers
# group :webserver, :web
# group :app_myappname, :app
# group "MySQL Servers", :db, :port => 22000
- def loadbalancer (which, *args)
+ def loadbalancer (named_load_balancer, *args)
+ require_arglist = args[1][:require] rescue {}
+ exclude_arglist = args[1][:exclude] rescue {}
# list of all the instances assoc'ed with this account
@ec2_api ||= RightAws::Ec2.new(fetch(:aws_access_key_id), fetch(:aws_secret_access_key), fetch(:aws_params, {}))
# fetch a raw list all the load balancers
@elb_api ||= RightAws::ElbInterface.new(fetch(:aws_access_key_id), fetch(:aws_secret_access_key))
+
# only get the named load balancer
- named_elb_instance = @elb_api.describe_load_balancers.delete_if{ |i| i[:load_balancer_name] != which.to_s }
+ named_elb_instance = @elb_api.describe_load_balancers.delete_if{ |instance| instance[:load_balancer_name] != named_load_balancer.to_s }
- print "named elb instnaces" + named_elb_instance.to_s
+ # must exit if no load balancer on record for this account by given name in cap config file
+ raise Exception, "No load balancer named: #{named_load_balancer.to_s} for aws account with this access key: #{:aws_access_key_id}" if named_elb_instance.empty?
elb_ec2_instances = named_elb_instance[0][:instances] rescue {}
-# print "named elb ec2 instnaces" + elb_ec2_instances.to_s
- print "here are our ARGSXXX" + args[1].to_s
+ # get the full instance list for account, this is necessary to subsquently fish out the :dns_name for the instances that survive our reduction steps
+ account_instance_list = @ec2_api.describe_instances
+
+ # reduce to only the instances in the named ELB
+ account_instance_list.delete_if { |i| ! elb_ec2_instances.include?(i[:aws_instance_id]) }
- # this is the target state we extract from param, unless that param is present in the instance, we dont update
- run_state = args[1][:state] rescue 'run'
+ # reduce against 'require' args, if an instance doesnt have the args in require_arglist, remove
+ account_instance_list.delete_if { |i| ! all_args_within_instance(i, require_arglist) } unless require_arglist.nil? or require_arglist.empty?
- #now, we have a hash of either zero or one ELBs, assuming unique names
- @ec2_api.describe_instances.delete_if{ |i| i[:aws_state] != "running"}.each do |instance|
- # unless this ec2 instance is in the LB, nuke it
- if elb_ec2_instances.include?(instance[:aws_instance_id]) && instance[:tags]['state'] == run_state
- server(instance[:dns_name], *args)
- end
+ # reduce against 'exclude_arglist', if an instance has any of the args in exclude_arglist, remove
+ account_instance_list.delete_if { |i| any_args_within_instance(i, exclude_arglist) } unless exclude_arglist.nil? or exclude_arglist.empty?
+
+ # finally load the derived instances into the serverlist used by capistrano tasks
+ account_instance_list.each do |instance|
+ server(instance[:dns_name], *args)
end
end
+
+ private
+
+ def any_args_within_instance(instance, exclude_arglist)
+ exargs = exclude_arglist.clone # must copy since delete transcends scope; if we don't copy, subsequent 'map'ped enum arglists would be side-effected
+ tag_exclude_state = nil # default assumption
+ # pop off a :tags arg to treat separately, its a separate namespace
+ tag_exclude_arglist = exargs.delete(:tags)
+
+ tag_exclude_state = tag_exclude_arglist.map { |k, v| (instance[:tags][k] == v rescue nil) }.inject(nil) { |inj, el| el || inj } if !tag_exclude_arglist.nil?
+ # we want all nils for the result here, so we logical-or the result map, and invert it
+ tag_exclude_state || exargs.map { |k, v| instance[k] == v }.inject(nil) { |inj, el| inj || el }
+ end
+
+ # the instance has attributes
+ def all_args_within_instance(instance, require_arglist)
+ reqargs = require_arglist.clone # must copy since delete transcends scope; if we don't copy, subsequent 'map'ped enum arglists would be side-effected
+ tag_require_state = true # default assumption
+ # pop off a :tags arg to treat separately, effectively a separate namespace to be checked agains
+ tag_require_arglist = reqargs.delete(:tags)
+ tag_require_state = tag_require_arglist.map { |k, v| (instance[:tags][k] == v rescue nil) }.inject(nil) { |inj, el| el || inj } if !tag_require_arglist.nil?
+
+ # require arglist is a hash with k/v's, each of those need to be in the instance
+ tag_require_state && reqargs.map { |k, v| instance[k] == v }.inject(true) { |inj, el| inj && el }
+ end
+
end
include LoadBalancers
end
end
-# module Cap
-# module Elb
-# # Your code goes here...
-# end
-# end
+# stub for future extensions
+ module Cap
+ module Elb
+ end
+ end
View
2  lib/cap-elb/version.rb
@@ -1,5 +1,5 @@
module Cap
module Elb
- VERSION = "0.0.3"
+ VERSION = "0.0.4"
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.