├── .gitignore ├── .ruby-gemset ├── .ruby-version ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── cap-ec2.gemspec └── lib └── cap-ec2 ├── capistrano.rb ├── ec2-handler.rb ├── status-table.rb ├── tasks └── ec2.rake ├── utils.rb └── version.rb /.gitignore: -------------------------------------------------------------------------------- 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 18 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | cap-ec2 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.0 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Cap-EC2 changelog 2 | 3 | ## 1.1.2 4 | 5 | * Allow using aws-sdk v3 [@magnusvk](https://github.com/magnusvk) 6 | * Fix NoMethodError undefined method call for Hash [@ur5us](https://github.com/ur5us) 7 | * Allow tag value delimiter to be configurable [@erez-rabih](https://github.com/erez-rabih) 8 | 9 | ## 1.1.1 10 | 11 | Require aws-sdk v2 instead of v1 12 | 13 | * Require aws-sdk v2 instead of v1 [@hajder](https://github.com/hajder) 14 | 15 | ## 1.1.0 16 | 17 | Upgrade to AWS's v2 SDK 18 | 19 | * Upgrade to AWS's v2 SDK [@kylev](https://github.com/kylev) 20 | 21 | ## 1.0.0 22 | 23 | Cap-EC2 is pretty stable, and the rate of PRs has decreased, so I've 24 | decided to bump the version to 1.0.0. 25 | 26 | * Remove the require of `capistrano/setup`, so that people can make 27 | use of `capistrano-multiconfig`. [@ashleybrown](https://github.com/ashleybrown) 28 | 29 | ## 0.0.19 30 | 31 | * Stop using the `colored` gem, switch to `colorize` instead. [@freakphp][https://github.com/freakphp] 32 | 33 | ## 0.0.18 34 | 35 | * Update gemspec to explicitly use the AWS v1 SDK. [@Tomtomgo](https://github.com/Tomtomgo) 36 | * Fix available roles for newer Capistrano versions. [@AmirKremer](https://github.com/AmirKremer) 37 | 38 | ## 0.0.17 39 | 40 | * Provide access to EC2 server tags within Capistrano recipes [@eightbitraptor](https://github.com/eightbitraptor) 41 | * Fix sorting of servers when there is no Name tag [@johnf](https://github.com/johnf) 42 | 43 | ## 0.0.16 44 | 45 | * Don't colorize status table output if STDOUT is not a TTY. [@jcoglan](https://github.com/jcoglan) 46 | 47 | ## 0.0.15 48 | 49 | * Add `ec2_filter_by_status_ok?` to filter out instances that aren't returning `OK` 50 | for their EC2 status checks. [@tomconroy](https://github.com/tomconroy) 51 | 52 | ## 0.0.14 53 | 54 | * Fix issue when tag was present in EC2 but had no value. [@tomconroy](https://github.com/tomconroy) 55 | 56 | ## 0.0.13 57 | 58 | * Use AWS.memoize to speed up communication with AWS [@cheald](https://github.com/cheald) 59 | 60 | ## 0.0.12 61 | 62 | * Use the instance's named state for searching for instances, rather than the code [@ronny](https://github.com/ronny) 63 | 64 | ## 0.0.11 65 | 66 | * Allow instances to have multiple projects deployed to them. [@rsslldnphy](https://github.com/rsslldnphy) 67 | * Fix the way instance tag matching works; the previous regex was not sufficient to ensure 68 | absolute matching of a given tag. [@rsslldnphy](https://github.com/rsslldnphy) 69 | 70 | ## 0.0.10 71 | 72 | * Allow configurable setting of the EC2 contact point [@christianblunden](https://github.com/christianblunden) 73 | 74 | ## 0.0.9 75 | 76 | * Handle no configured regions, (or specifically nil). 77 | 78 | ## 0.0.8 79 | 80 | * Made `config/ec2.yml` optional, set all options by Capistrano variable. [@rjocoleman](https://github.com/rjocoleman) 81 | * Remove requirement for default region to be set. [@rjocoleman](https://github.com/rjocoleman) 82 | 83 | ## 0.0.7 84 | 85 | * Removed monkey patching of `Capistrano::TaskEnhancements` [@rjocoleman](https://github.com/rjocoleman) 86 | * Instances don't always have a name tag, would cause `ec2:status` to blow up [@rjocoleman](https://github.com/rjocoleman) 87 | 88 | ## 0.0.6 89 | 90 | * Unbreak listing instances 91 | 92 | ## 0.0.5 93 | 94 | * Don't return terminated instances when looking up instances from EC2 95 | * Fix documentation to refer to correct tag for Stages [@shaneog](https://github.com/shaneog) 96 | 97 | ## 0.0.4 98 | 99 | * If you modified any of the tag names, the `ec2:status` table would blow up 100 | * Fixed a bug with stages 101 | 102 | ## 0.0.3 103 | 104 | * Rename the default tag name used for determining to 'Stages' from 'Stage' 105 | 106 | ## 0.0.2 107 | 108 | * Allow servers to be in multiple stages 109 | 110 | ## 0.0.1 111 | 112 | * Initial release 113 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in capify-v3-ec2.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Andy Sykes 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. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is no longer maintained. 2 | 3 | Forward3D has not used cap-ec2 in production for several years, and thus we will no longer be maintaining this. 4 | 5 | # Cap-EC2 6 | 7 | [![Gem Version](https://badge.fury.io/rb/cap-ec2.svg)](http://badge.fury.io/rb/cap-ec2) [![Code Climate](https://codeclimate.com/github/forward3d/cap-ec2.png)](https://codeclimate.com/github/forward3d/cap-ec2) 8 | 9 | Cap-EC2 is used to generate Capistrano namespaces and tasks from Amazon EC2 instance tags, 10 | dynamically building the list of servers to be deployed to. 11 | 12 | ## Notes 13 | 14 | Cap-EC2 is only compatible with Capistrano 3.x or later; if you want the Capistrano 2.x version, 15 | use [Capify-EC2](https://github.com/forward/capify-ec2). Note that the configuration file (`config/ec2.yml`) 16 | is not compatible between versions either. 17 | 18 | This documentation assumes familiarity with Capistrano 3.x. 19 | 20 | A number of features that are in Capify-EC2 are not yet available in Cap-EC2, due to the 21 | architectural changes in Capistrano 3.x. The following features are missing (this is not 22 | an exhaustive list!): 23 | * rolling deploy (this should be implemented via [SSHKit](https://github.com/capistrano/sshkit)) 24 | * ELB registration/de-registration (not widely used) 25 | * Variables set by EC2 tags 26 | * Connecting to instances via SSH using a convenience task 27 | 28 | Pull requests for these would be welcomed, as would sending feedback via the Issues on this project about 29 | features you would like. 30 | 31 | ## Installation 32 | 33 | gem install cap-ec2 34 | 35 | or add the gem to your project's Gemfile. 36 | 37 | You also need to add the gem to your Capfile: 38 | 39 | ```ruby 40 | require "cap-ec2/capistrano" 41 | ``` 42 | 43 | 44 | ## Configuration 45 | 46 | Configurable options, shown here with defaults: 47 | 48 | ```ruby 49 | set :ec2_config, 'config/ec2.yml' 50 | 51 | set :ec2_project_tag, 'Project' 52 | set :ec2_roles_tag, 'Roles' 53 | set :ec2_stages_tag, 'Stages' 54 | set :ec2_tag_delimiter, "," 55 | 56 | set :ec2_profile, 'myservice' # use ~/.aws/credentials with profile_name 57 | set :ec2_access_key_id, nil 58 | set :ec2_secret_access_key, nil 59 | set :ec2_region, %w{} # REQUIRED 60 | set :ec2_contact_point, nil 61 | 62 | set :ec2_filter_by_status_ok?, nil 63 | ``` 64 | 65 | #### Order of inheritance 66 | 67 | `cap-ec2` supports multiple methods of configuration. The order of inheritance is: 68 | YAML File > ~/.aws/credentials > User Capistrano Config > Default Capistrano Config > ENV variables. 69 | 70 | #### Regions 71 | 72 | `:ec2_region` is an array of 73 | [AWS regions](http://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region) 74 | and is required. Only list regions which you wish to query for 75 | instances; extra values simply slow down queries. 76 | 77 | If `:ec2_access_key_id` or `:ec2_secret_access_key` are not set in any 78 | configuration the environment variables `AWS_ACCESS_KEY_ID`, 79 | `AWS_SECRET_ACCESS_KEY` and `AWS_REGION` will be checked and the 80 | default credential load order (including instance profiles 81 | credentials) will be honored. 82 | 83 | #### Misc settings 84 | 85 | * project_tag 86 | 87 | Cap-EC2 will look for a tag with this name when searching for instances that belong to this project. 88 | Cap-EC2 will look for a value which matches the :application setting in your deploy.rb. 89 | The tag name defaults to "Project" and must be set on your instances. 90 | 91 | * stages_tag 92 | 93 | Cap-EC2 will look for a tag with this name to determine which instances belong to 94 | a given stage. The tag name defaults to "Stages". 95 | 96 | * roles_tag 97 | 98 | Cap-EC2 will look for a tag with this name to determine which instances belong to 99 | a given role. The tag name defaults to "Roles". 100 | 101 | * tag_delimiter 102 | 103 | When Cap-EC2 reads a tag value, this will be the default delimiter. 104 | For example, for a Roles tag with web,db and tag_delimiter set to ,(comma) 105 | the server will have the web and db roles. 106 | 107 | * filter_by_status_ok? 108 | 109 | If this is set to `true`, then Cap-EC2 will not return instances which do not have both EC2 status 110 | checks as `OK`. By default this is set to `nil`, so Cap-EC2 can return you instances which don't have 111 | `OK` status checks. Be warned that just-launched instances take a while to start returning `OK`. 112 | 113 | ### YAML Configuration 114 | 115 | 116 | If you'd prefer do your configuration via a YAML file `config/ec2.yml` can be used, (or an alternative name/location set via `set :ec2_config`): 117 | 118 | If so YAML file will look like this: 119 | 120 | ```ruby 121 | access_key_id: "YOUR ACCESS KEY" 122 | secret_access_key: "YOUR SECRET KEY" 123 | regions: 124 | - 'eu-west-1' 125 | project_tag: "Project" 126 | roles_tag: "Roles" 127 | stages_tag: "Stages" 128 | ``` 129 | 130 | 131 | Your `config/ec2.yml` file can contain (`access_key_id`, `secret_access_key`, `regions`) - if a value is omitted then the order of inheritance is followed. 132 | 133 | 134 | ## Usage 135 | 136 | Imagine you have four servers on EC2 named and tagged as follows: 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 |
'Name' Tag'Roles' Tag'Stages' Tag
server-1webproduction
server-2web,appproduction
server-3app,dbproduction
server-4web,db,appstaging
165 | 166 | Imagine also that we've called our app "testapp", as defined in `config/deploy.rb` like so: 167 | 168 | set :application, "testapp" 169 | 170 | ### Defining the roles in `config/deploy/[stage].rb` 171 | 172 | To define a role, edit `config/deploy/[stage].rb` and add the following: 173 | 174 | ec2_role :web 175 | 176 | Let's say we edited `config/deploy/production.rb`. Adding this configuration to the file would assign 177 | the role `:web` to any instance that has the following properties: 178 | * has a tag called "Roles" that contains the string "web" 179 | * has a tag called "Project" that contains the string "testapp" 180 | * has a tag called "Stages" that contains the current stage we're executing (in this case, "production") 181 | 182 | Looking at the above table, we can see we would match `server-1` and `server-2`. (You can have multiple 183 | roles in tag separated by commas.) 184 | 185 | Now we can define the other roles: 186 | 187 | ec2_role :app 188 | ec2_role :db 189 | 190 | In the "production" stage, the `:app` role would apply to `server-2` and `server-3`, and the `:db` 191 | role would apply to `server-3`. 192 | 193 | In the "staging" stage, all roles would apply *only* to `server-4`. 194 | 195 | ### Servers belonging to multiple projects 196 | 197 | If you require your servers to have multiple projects deployed to them, you can simply specify 198 | all the project names you want to the server to be part of in the 'Projects' tag, separated 199 | by commas. For example, you could place a server in the `testapp` and `myapp` projects by 200 | setting the 'Projects' tag to `testapp,myapp`. 201 | 202 | ### Servers in multiple stages 203 | 204 | If your use-case requires servers to be in multiple stages, simply specify all the stages you want 205 | the server to be in 'Stages' tag, separated by commas. For example, you could place a server in 206 | the `production` and `staging` stages by setting the 'Stages' tag to `production,staging`. 207 | 208 | ### Passing options to roles 209 | 210 | You can pass options when defining your roles. The options are *exactly* the same as the options that 211 | the Capistrano native `role` definition takes, since they are passed straight through to Capistrano. 212 | For example: 213 | 214 | ec2_role :app, 215 | user: 'user_name', 216 | ssh_options: { 217 | user: 'user_name', # overrides user setting above 218 | keys: %w(/home/user_name/.ssh/id_rsa), 219 | forward_agent: false, 220 | auth_methods: %w(publickey password) 221 | password: 'please use keys' 222 | } 223 | 224 | See the example config files Capistrano builds for you in `config/deploy` for more details. 225 | 226 | Note that at the moment there's no way to pass variables in from EC2 tags - but it would be 227 | trivial to add. 228 | 229 | ### Tasks and deployment 230 | 231 | You can now define your tasks for these roles in exactly the same way as you would if you weren't 232 | using this gem. 233 | 234 | ### Contacting instances 235 | 236 | By default, Cap-EC2 will attempt to communicate with the EC2 instance using the following instance 237 | interfaces in order: 238 | 239 | 1. Public DNS (`:public_dns`) 240 | 2. Public IP (`:public_ip`) 241 | 3. Private IP (`:private_ip`) 242 | 243 | This can be configured using the Capistrano variable `:ec2_contact_point`, and supplying one 244 | of the above symbols. For example: 245 | 246 | ```ruby 247 | set :ec2_contact_point, :private_ip 248 | ``` 249 | 250 | This would cause Cap-EC2 to try communicating with the instance on its private IP address. If you leave 251 | this variable unset, the behaviour is as in previous Cap-EC2 instances (falling through the lookup list 252 | as specified above). 253 | 254 | ## Utility tasks 255 | 256 | Cap-EC2 adds a few utility tasks to Capistrano for displaying information about the instances that 257 | you will be deploying to. Note that unlike Capistrano 2.x, all tasks *require* a stage. 258 | 259 | ### View instances 260 | 261 | This command will show you information all the instances your configuration matches for a given stage. 262 | 263 | cap [stage] ec2:status 264 | 265 | Example: 266 | 267 | $ cap production ec2:status 268 | 269 | Num Name ID Type DNS Zone Roles Stage 270 | 00: server-1-20131030-1144-0 i-abcdefgh m1.small 192.168.202.248 us-west-2c banana,apple production 271 | 01: server-2-20131118-1839-0 i-hgfedcba m1.small 192.168.200.60 us-west-2a banana production 272 | 273 | ### View server names 274 | 275 | This command will show you the server names of the instances matching the given stage: 276 | 277 | cap [stage] ec2:server_names 278 | 279 | Example: 280 | 281 | $ cap production ec2:server_names 282 | server-1-20131030-1144-0 283 | server-2-20131118-1839-0 284 | 285 | ### View server instance IDs 286 | 287 | This command will show the instance IDs of the instances matching the given stage: 288 | 289 | cap [stage] ec2:instance_ids 290 | 291 | Example: 292 | 293 | $ cap production ec2:instance_ids 294 | i-abcdefgh 295 | i-hgfedcba 296 | 297 | ## Acknowledgements 298 | 299 | Thanks to [Rylon](https://github.com/Rylon) for maintaining Capify-EC2 and 300 | reviewing my thought processes for this project. 301 | 302 | ## Contributing 303 | 304 | 1. Fork it 305 | 2. Create your feature branch (`git checkout -b my-new-feature`) 306 | 3. Commit your changes (`git commit -am 'Add some feature'`) 307 | 4. Push to the branch (`git push origin my-new-feature`) 308 | 5. Create new Pull Request 309 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /cap-ec2.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'cap-ec2/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "cap-ec2" 8 | spec.version = CapEC2::VERSION 9 | spec.authors = ["Andy Sykes", "Robert Coleman", "Forward3D Developers"] 10 | spec.email = ["github@tinycat.co.uk", "github@robert.net.nz", "developers@forward3d.com"] 11 | spec.description = %q{Cap-EC2 is used to generate Capistrano namespaces and tasks from Amazon EC2 instance tags, dynamically building the list of servers to be deployed to.} 12 | spec.summary = %q{Cap-EC2 is used to generate Capistrano namespaces and tasks from Amazon EC2 instance tags, dynamically building the list of servers to be deployed to.} 13 | spec.homepage = "https://github.com/forward3d/cap-ec2" 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_development_dependency "bundler", "~> 1.3" 22 | spec.add_development_dependency "rake" 23 | 24 | spec.add_dependency "aws-sdk", ">= 2.0" 25 | spec.add_dependency "capistrano", ">= 3.0" 26 | spec.add_dependency "terminal-table" 27 | spec.add_dependency "colorize" 28 | end 29 | -------------------------------------------------------------------------------- /lib/cap-ec2/capistrano.rb: -------------------------------------------------------------------------------- 1 | require 'capistrano/configuration' 2 | require 'aws-sdk' 3 | require 'colorize' 4 | require 'terminal-table' 5 | require 'yaml' 6 | require_relative 'utils' 7 | require_relative 'ec2-handler' 8 | require_relative 'status-table' 9 | 10 | # Load extra tasks 11 | load File.expand_path("../tasks/ec2.rake", __FILE__) 12 | 13 | module Capistrano 14 | module DSL 15 | module Ec2 16 | 17 | def ec2_handler 18 | @ec2_handler ||= CapEC2::EC2Handler.new 19 | end 20 | 21 | def ec2_role(name, options={}) 22 | ec2_handler.get_servers_for_role(name).each do |server| 23 | env.role(name, CapEC2::Utils.contact_point(server), 24 | options_with_instance_id(options, server)) 25 | end 26 | end 27 | 28 | def env 29 | Configuration.env 30 | end 31 | 32 | private 33 | 34 | def options_with_instance_id(options, server) 35 | options.merge({aws_instance_id: server.instance_id}) 36 | end 37 | 38 | end 39 | end 40 | end 41 | 42 | self.extend Capistrano::DSL::Ec2 43 | 44 | Capistrano::Configuration::Server.send(:include, CapEC2::Utils::Server) 45 | -------------------------------------------------------------------------------- /lib/cap-ec2/ec2-handler.rb: -------------------------------------------------------------------------------- 1 | require 'aws-sdk' 2 | 3 | module CapEC2 4 | class EC2Handler 5 | include CapEC2::Utils 6 | 7 | def initialize 8 | load_config 9 | configured_regions = get_regions(fetch(:ec2_region)) 10 | @ec2 = {} 11 | configured_regions.each do |region| 12 | @ec2[region] = ec2_connect(region) 13 | end 14 | end 15 | 16 | def ec2_connect(region=nil) 17 | Aws::EC2::Client.new( 18 | access_key_id: fetch(:ec2_access_key_id), 19 | secret_access_key: fetch(:ec2_secret_access_key), 20 | region: region 21 | ) 22 | end 23 | 24 | def status_table 25 | CapEC2::StatusTable.new( 26 | defined_roles.map {|r| get_servers_for_role(r)}.flatten.uniq {|i| i.instance_id} 27 | ) 28 | end 29 | 30 | def server_names 31 | puts defined_roles.map {|r| get_servers_for_role(r)} 32 | .flatten 33 | .uniq {|i| i.instance_id} 34 | .map {|i| tag_value(i, 'Name')} 35 | .join("\n") 36 | end 37 | 38 | def instance_ids 39 | puts defined_roles.map {|r| get_servers_for_role(r)} 40 | .flatten 41 | .uniq {|i| i.instance_id} 42 | .map {|i| i.instance_id} 43 | .join("\n") 44 | end 45 | 46 | def defined_roles 47 | roles(:all).flat_map(&:roles_array).uniq.sort 48 | end 49 | 50 | def stage 51 | Capistrano::Configuration.env.fetch(:stage).to_s 52 | end 53 | 54 | def application 55 | Capistrano::Configuration.env.fetch(:application).to_s 56 | end 57 | 58 | def tag(tag_name) 59 | "tag:#{tag_name}" 60 | end 61 | 62 | def get_servers_for_role(role) 63 | filters = [ 64 | {name: 'tag-key', values: [stages_tag, project_tag]}, 65 | {name: tag(project_tag), values: ["*#{application}*"]}, 66 | {name: 'instance-state-name', values: %w(running)} 67 | ] 68 | 69 | servers = [] 70 | @ec2.each do |_, ec2| 71 | ec2.describe_instances(filters: filters).reservations.each do |r| 72 | servers += r.instances.select do |i| 73 | instance_has_tag?(i, roles_tag, role) && 74 | instance_has_tag?(i, stages_tag, stage) && 75 | instance_has_tag?(i, project_tag, application) && 76 | (fetch(:ec2_filter_by_status_ok?) ? instance_status_ok?(i) : true) 77 | end 78 | end 79 | end 80 | 81 | servers.sort_by { |s| tag_value(s, 'Name') || '' } 82 | end 83 | 84 | def get_server(instance_id) 85 | @ec2.reduce([]) do |acc, (_, ec2)| 86 | acc << ec2.instances[instance_id] 87 | end.flatten.first 88 | end 89 | 90 | private 91 | 92 | def instance_has_tag?(instance, key, value) 93 | (tag_value(instance, key) || '').split(tag_delimiter).map(&:strip).include?(value.to_s) 94 | end 95 | 96 | def instance_status_ok?(instance) 97 | @ec2.any? do |_, ec2| 98 | ec2.describe_instance_status( 99 | instance_ids: [instance.instance_id], 100 | filters: [{ name: 'instance-status.status', values: %w(ok) }] 101 | ).instance_statuses.length == 1 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/cap-ec2/status-table.rb: -------------------------------------------------------------------------------- 1 | module CapEC2 2 | class StatusTable 3 | include CapEC2::Utils 4 | 5 | def initialize(instances) 6 | @instances = instances 7 | output 8 | end 9 | 10 | def header_row 11 | [ 12 | bold("Num"), 13 | bold("Name"), 14 | bold("ID"), 15 | bold("Type"), 16 | bold("DNS"), 17 | bold("Zone"), 18 | bold("Roles"), 19 | bold("Stages") 20 | ] 21 | end 22 | 23 | def output 24 | table = Terminal::Table.new( 25 | :style => { 26 | :border_x => "", 27 | :border_i => "", 28 | :border_y => "" 29 | } 30 | ) 31 | table.add_row header_row 32 | @instances.each_with_index do |instance,index| 33 | table.add_row instance_to_row(instance, index) 34 | end 35 | puts table.to_s 36 | end 37 | 38 | def instance_to_row(instance, index) 39 | [ 40 | sprintf("%02d:", index), 41 | green(tag_value(instance, 'Name') || ''), 42 | red(instance.instance_id), 43 | cyan(instance.instance_type), 44 | bold(blue(CapEC2::Utils.contact_point(instance))), 45 | magenta(instance.placement.availability_zone), 46 | yellow(tag_value(instance, roles_tag)), 47 | yellow(tag_value(instance, stages_tag)) 48 | ] 49 | end 50 | 51 | private 52 | 53 | (String.colors + String.modes).each do |format| 54 | define_method(format) do |string| 55 | if $stdout.tty? 56 | string.__send__(format) 57 | else 58 | string 59 | end 60 | end 61 | end 62 | 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/cap-ec2/tasks/ec2.rake: -------------------------------------------------------------------------------- 1 | namespace :ec2 do 2 | 3 | desc "Show all information about EC2 instances that match this project" 4 | task :status do 5 | ec2_handler.status_table 6 | end 7 | 8 | desc "Show EC2 server names that match this project" 9 | task :server_names do 10 | ec2_handler.server_names 11 | end 12 | 13 | desc "Show EC2 instance IDs that match this project" 14 | task :instance_ids do 15 | ec2_handler.instance_ids 16 | end 17 | 18 | end 19 | 20 | 21 | namespace :load do 22 | task :defaults do 23 | 24 | set :ec2_config, 'config/ec2.yml' 25 | 26 | set :ec2_project_tag, 'Project' 27 | set :ec2_roles_tag, 'Roles' 28 | set :ec2_stages_tag, 'Stages' 29 | set :ec2_tag_delimiter, "," 30 | 31 | set :ec2_access_key_id, nil 32 | set :ec2_secret_access_key, nil 33 | set :ec2_region, %w{} 34 | 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/cap-ec2/utils.rb: -------------------------------------------------------------------------------- 1 | require 'aws-sdk' 2 | 3 | module CapEC2 4 | module Utils 5 | 6 | module Server 7 | def ec2_tags 8 | id = self.properties.fetch(:aws_instance_id) 9 | ec2_handler.get_server(id).tags 10 | end 11 | end 12 | 13 | def project_tag 14 | fetch(:ec2_project_tag) 15 | end 16 | 17 | def roles_tag 18 | fetch(:ec2_roles_tag) 19 | end 20 | 21 | def stages_tag 22 | fetch(:ec2_stages_tag) 23 | end 24 | 25 | def tag_delimiter 26 | fetch(:ec2_tag_delimiter) 27 | end 28 | 29 | def tag_value(instance, key) 30 | instance.tags.find(-> { {} }) { |t| t[:key] == key.to_s }[:value] 31 | end 32 | 33 | def self.contact_point_mapping 34 | { 35 | :public_dns => :public_dns_name, 36 | :public_ip => :public_ip_address, 37 | :private_ip => :private_ip_address 38 | } 39 | end 40 | 41 | def self.contact_point(instance) 42 | ec2_interface = contact_point_mapping[fetch(:ec2_contact_point)] 43 | return instance.send(ec2_interface) if ec2_interface 44 | 45 | instance.public_dns_name || instance.public_ip_address || instance.private_ip_address 46 | end 47 | 48 | def load_config 49 | if fetch(:ec2_profile) 50 | credentials = Aws::SharedCredentials.new(profile_name: fetch(:ec2_profile)).credentials 51 | if credentials 52 | set :ec2_access_key_id, credentials.access_key_id 53 | set :ec2_secret_access_key, credentials.secret_access_key 54 | end 55 | end 56 | 57 | config_location = File.expand_path(fetch(:ec2_config), Dir.pwd) if fetch(:ec2_config) 58 | if config_location && File.exists?(config_location) 59 | config = YAML.load(ERB.new(File.read(fetch(:ec2_config)))) 60 | if config 61 | set :ec2_project_tag, config['project_tag'] if config['project_tag'] 62 | set :ec2_roles_tag, config['roles_tag'] if config['roles_tag'] 63 | set :ec2_stages_tag, config['stages_tag'] if config['stages_tag'] 64 | set :ec2_tag_delimiter, config['tag_delimiter'] if config['tag_delimiter'] 65 | 66 | set :ec2_access_key_id, config['access_key_id'] if config['access_key_id'] 67 | set :ec2_secret_access_key, config['secret_access_key'] if config['secret_access_key'] 68 | set :ec2_region, config['regions'] if config['regions'] 69 | 70 | set :ec2_filter_by_status_ok?, config['filter_by_status_ok?'] if config['filter_by_status_ok?'] 71 | end 72 | end 73 | end 74 | 75 | def get_regions(regions_array=nil) 76 | unless regions_array.nil? || regions_array.empty? 77 | return regions_array 78 | else 79 | fail "You must specify at least one EC2 region." 80 | end 81 | end 82 | 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/cap-ec2/version.rb: -------------------------------------------------------------------------------- 1 | module CapEC2 2 | VERSION = '1.1.2' 3 | end 4 | --------------------------------------------------------------------------------