cloudfinder-ec2 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ # Autodetect text files, normalize to LF on commit
2
+ * text=auto
3
+
4
+ # Explicitly define files which should be LF when checked out and on commit
5
+ *.md text eol=lf
6
+ *.sh text eol=lf
7
+ *.rb text eol=lf diff=ruby
8
+ *.php text eol=lf diff=php
9
+ *.yml text eol=lf
10
+ *.sql text eol=lf
11
+ *.xml text eol=lf
12
+ *.xsd text eol=lf
13
+ *.css text eol=lf
14
+ *.js text eol=lf
15
+ *.pem text eol=lf
16
+ LICENSE text eol=lf
17
+ README text eol=lf
18
+
19
+ # Declare files that will always have CRLF line endings on checkout (still stored LF)
20
+ *.bat text eol=crlf
21
+
22
+ # Denote all files that are truly binary and should not be modified.
23
+ *.png binary
24
+ *.jpg binary
25
+ *.gif binary
26
+ *.ttf binary
27
+ *.swf binary
28
+ *.zip binary
29
+ *.sqlite binary
@@ -0,0 +1,22 @@
1
+ /.vagrant
2
+ # rcov generated
3
+ coverage
4
+ coverage.data
5
+
6
+ # rdoc generated
7
+ rdoc
8
+
9
+ # yard generated
10
+ doc
11
+ .yardoc
12
+
13
+ # bundler
14
+ .bundle
15
+
16
+ # jeweler generated
17
+ pkgtmp/
18
+ tmp/
19
+
20
+ # Don't include Gemfile.lock in gems
21
+ Gemfile.lock
22
+ pkg/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require spec_helper
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.1.1
@@ -0,0 +1,43 @@
1
+ We love pull requests. Here's a quick guide:
2
+
3
+ 1. Fork the repo, and make your changes.
4
+
5
+ 2. Once you're ready to submit, rebase your branch against our master branch to
6
+ maintain a clean history and ensure you're up to date with all the most recent
7
+ changes.
8
+
9
+ 3. Run the tests. We only take pull requests with passing tests, and it's great
10
+ to know that you have a clean slate: `bundle && rake`
11
+
12
+ 4. Add a test for your change. Only refactoring and documentation changes
13
+ require no new tests. If you are adding functionality or fixing a bug, we need
14
+ a test!
15
+
16
+ 5. Make the test pass.
17
+
18
+ 6. Push to your fork and submit a pull request.
19
+
20
+
21
+ At this point you're waiting on us. We like to at least comment on, if not
22
+ accept, pull requests within three business days (and, typically, one business
23
+ day). We may suggest some changes or improvements or alternatives. Your pull request
24
+ will be built by [Travis](https://travis-ci.org/edbookfest/cloudfinder-ec2) first -
25
+ if the build fails please fix that first.
26
+
27
+ Some things that will increase the chance that your pull request is accepted,
28
+ taken straight from the Ruby on Rails guide:
29
+
30
+ * Include tests that fail without your code, and pass with it
31
+ * Update the documentation, the surrounding one, examples elsewhere, guides,
32
+ whatever is affected by your contribution
33
+
34
+ Syntax:
35
+
36
+ * Two spaces, no tabs.
37
+ * No trailing whitespace. Blank lines should not have any space.
38
+ * Prefer &&/|| over and/or.
39
+ * MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
40
+ * a = b and not a=b.
41
+ * Follow the conventions you see used in the source already.
42
+
43
+ And in case we didn't emphasize it enough: we love tests!
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Dependencies specified in the gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'coveralls', :require => false
8
+ end
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd:'bundle exec rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/cloudfinder-ec2/(.+)\.rb$}) { |m| "spec/cloudfinder-ec2/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
@@ -0,0 +1,29 @@
1
+ Copyright (c) 2015 Edinburgh International Book Festival Ltd, a
2
+ a company limited by guarantee (no SC 79939) with charitable status
3
+ (SC 010120). Registered office 5a Charlotte Square, Edinburgh, EH2 4DR.
4
+
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without modification, are
8
+ permitted provided that the following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list
11
+ of conditions and the following disclaimer.
12
+
13
+ 2. Redistributions in binary form must reproduce the above copyright notice, this
14
+ list of conditions and the following disclaimer in the documentation and/or other
15
+ materials provided with the distribution.
16
+
17
+ 3. Neither the name of the copyright holder nor the names of its contributors may
18
+ be used to endorse or promote products derived from this software without specific
19
+ prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
22
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
24
+ SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
26
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
28
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,138 @@
1
+ # Cloudfinder::EC2
2
+
3
+ [![Build Status](https://travis-ci.org/edbookfest/cloudfinder-ec2.png)](https://travis-ci.org/edbookfest/cloudfinder-ec2)
4
+ [![Coverage Status](https://coveralls.io/repos/edbookfest/cloudfinder-ec2/badge.png?branch=master)](https://coveralls.io/r/edbookfest/cloudfinder-ec2)
5
+ [![Gem Version](https://badge.fury.io/rb/cloudfinder-ec2.png)](http://badge.fury.io/rb/cloudfinder-ec2)
6
+ [![Code Climate](https://codeclimate.com/github/edbookfest/cloudfinder-ec2.png)](https://codeclimate.com/github/edbookfest/cloudfinder-ec2)
7
+ [![Dependency Status](https://gemnasium.com/edbookfest/cloudfinder-ec2.png)](https://gemnasium.com/edbookfest/cloudfinder-ec2)
8
+
9
+ Uses EC2 instance tags to locate all the running instances in a given cluster, grouped by role.
10
+
11
+ ## Tagging your instances
12
+
13
+ An instance can belong to a single cluster, with a single role. These are identified based on two EC2 tags with the
14
+ keys `cloudfinder-cluster` and the `cloudfinder-role`. Add the tags to your instances using the AWS API, command line
15
+ tools, console, or as part of an auto-scaling-group launch configuration.
16
+
17
+ ## API credentials
18
+
19
+ cloudfinder-ec2 needs access to a set of AWS credentials with read-only access to your EC2 instances. When running
20
+ on EC2, the best way to provide this is with an instance IAM role. Local AWS credentials files and environment
21
+ variables are also supported. For further information on credential management see the
22
+ [aws-sdk documentation](http://docs.aws.amazon.com/sdkforruby/api/index.html#Credentials)
23
+
24
+ ## Cluster detection
25
+
26
+ By default, cloudfinder-ec2 will query the EC2 instance metadata API to attempt to detect the name and EC2 region
27
+ of the cluster containing the running instance.
28
+
29
+ If you are running outside EC2, or want to load details of a different cluster (for example if you are running
30
+ the command on an EC2 server that is not part of the cluster you're interested in) you can specify the cluster
31
+ name and region to load.
32
+
33
+ If you do not provide a cluster name and region, and you are not on EC2, then the command will fail.
34
+
35
+ ## Usage
36
+
37
+ ### As a shell command
38
+
39
+ Install the cloudfinder-ec2 binary by adding the gem to your system in the usual way
40
+ (`gem install cloudfinder-ec2`).
41
+
42
+ Running `cloudfinder-ec2 list` will output a JSON hash of the running cluster, grouped by role, which
43
+ looks something like this:
44
+
45
+ ```json
46
+ {
47
+ "cluster_name": "production",
48
+ "roles": {
49
+ "loadbalancer": [
50
+ {
51
+ "instance_id": "i-00000001",
52
+ "public_ip": "46.137.96.1",
53
+ "public_dns": "ec2-46-137-96-1.eu-west-1.compute.amazonaws.com",
54
+ "private_dns": "ip-10-248-183-1.eu-west-1.compute.internal",
55
+ "private_ip": "10.248.183.1",
56
+ "role": "loadbalancer"
57
+ }
58
+ ],
59
+ "app-server": [
60
+ {
61
+ "instance_id": "i-00000002",
62
+ "public_ip": "54.74.200.2",
63
+ "public_dns": "ec2-54-74-200-2.eu-west-1.compute.amazonaws.com",
64
+ "private_dns": "ip-10-56-18-2.eu-west-1.compute.internal",
65
+ "private_ip": "10.56.18.2",
66
+ "role": "app-server"
67
+ },
68
+ {
69
+ "instance_id": "i-00000003",
70
+ "public_ip": "54.74.200.3",
71
+ "public_dns": "ec2-54-74-200-3.eu-west-1.compute.amazonaws.com",
72
+ "private_dns": "ip-10-56-18-3.eu-west-1.compute.internal",
73
+ "private_ip": "10.56.18.3",
74
+ "role": "app-server"
75
+ }
76
+ ],
77
+ "db-server": [
78
+ {
79
+ "instance_id": "i-00000004",
80
+ "public_ip": "54.220.216.4",
81
+ "public_dns": "ec2-54-220-216-4.eu-west-1.compute.amazonaws.com",
82
+ "private_dns": "ip-10-86-141-4.eu-west-1.compute.internal",
83
+ "private_ip": "10.86.141.4",
84
+ "role": "db-server"
85
+ }
86
+ ]
87
+ }
88
+ }
89
+ ```
90
+
91
+ This output is designed to be piped to a file or captured by any other process that needs information about
92
+ the composition of a given cluster.
93
+
94
+ ### As a library
95
+
96
+ You can also use cloudfinder-ec2 as a gem within your ruby application. Add it to your project's Gemfile, and
97
+ then:
98
+
99
+ ```ruby
100
+ require 'cloudfinder-ec2'
101
+ cluster = Cloudfinder::EC2::Clusterfinder.new.find(cluster_name: 'production', region: 'eu-west-1)
102
+ # returns a Cloudfinder::EC2::Cluster instance with useful methods to interact with your cluster
103
+
104
+ if cluster.running?
105
+ puts cluster.list_roles
106
+ puts cluster.has_role?(:db)
107
+ puts cluster.list_role_instances(:db)
108
+ end
109
+ ```
110
+
111
+ You can also autodetect the cluster if required. This will throw an exception if the instance metadata is
112
+ not available (eg because you are not on an EC2 instance).
113
+
114
+ ```ruby
115
+ require 'cloudfinder-ec2'
116
+ puts Cloudfinder::EC2::Clusterfinder.new.detect_cluster # {cluster_name: 'production', region: 'eu-west-1'}
117
+ ```
118
+
119
+ ### Within a chef recipe
120
+
121
+ ```ruby
122
+ chef_gem 'cloudfinder-ec2'
123
+ require 'cloudfinder-ec2'
124
+ # and use as a library as above
125
+ ```
126
+
127
+ ## Limitations
128
+
129
+ * Exceptions raised by the instance metadata service and/or AWS API are allowed to bubble, there is no
130
+ retry or recovery within the library.
131
+ * Currently we only fetch a single page of results from the AWS API. If you have more instances that fit
132
+ in a single describe instances call, you probably need a more sophisticated cluster discovery tool
133
+ * We only search for cluster instances in a single AWS region - again if you have multi-region clusters
134
+ you may be better with a more sophisticated tool
135
+
136
+ ## Copyright
137
+
138
+ Copyright (c) 2015 Edinburgh International Book Festival Ltd. See LICENSE.txt for further details.
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env rake
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'rspec/core/rake_task'
5
+
6
+ # Change to the directory of this file.
7
+ Dir.chdir(File.expand_path("../", __FILE__))
8
+
9
+ # This installs the tasks that help with gem creation and
10
+ # publishing.
11
+ Bundler::GemHelper.install_tasks
12
+
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new do |t|
15
+ t.pattern = "spec/**/*_spec.rb"
16
+ end
17
+
18
+ task :default => :spec
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ require 'cloudfinder-ec2'
3
+ require 'slop'
4
+
5
+ opts = Slop.new strict: true, help: true do
6
+ banner (<<-eos).gsub(/^ +/m, '')
7
+ Finds running EC2 instances using cloudfinder-cluster and cloudfinder-role tags
8
+
9
+ Requires credentials authorised for read-only access to the AWS API - by default
10
+ will look in:
11
+
12
+ * ENV['AWS_ACCESS_KEY_ID'] and ENV['AWS_SECRET_ACCESS_KEY'] (not recommended)
13
+ * a credentials file at ~/.aws/credentials
14
+ * an instance's configured IAM role when running on EC2 (recommended)
15
+
16
+ Credentials are more fully documented at
17
+ http://docs.aws.amazon.com/sdkforruby/api/index.html#Credentials
18
+ eos
19
+
20
+ command :list do
21
+ description 'Output details of the running cluster as JSON'
22
+ banner (<<-eos).gsub(/^ +/m, '')
23
+ Display the details of a running cluster as JSON
24
+ ------------------------------------------------
25
+ Usage: cloudfinder-ec2 list [--cluster-name=production] [--region=eu-west-1]
26
+
27
+ By default, attempts to autodetect the name and/or region of the cluster from the EC2
28
+ metadata of this instance. If this is not an EC2 instance this will fail.
29
+
30
+ If you specify both the --cluster-name and --region arguments then autodetection will
31
+ be skipped.
32
+ eos
33
+
34
+ on '--cluster-name', 'Name of the cluster to load instead of autodetecting', argument: :optional
35
+ on '--region', 'EC2 region to search instead of autodetecting', argument: :optional
36
+
37
+ run do |opts|
38
+ Cloudfinder::EC2::Command::List.factory.execute(
39
+ cluster_name: opts['cluster-name'],
40
+ region: opts['region']
41
+ )
42
+ end
43
+ end
44
+
45
+ on '--version', 'Display the version' do
46
+ puts Cloudfinder::EC2::VERSION
47
+ end
48
+
49
+ # Treat running without a command as an error
50
+ run do
51
+ raise(Slop::InvalidCommandError, 'ERROR: You must specify the cloudfinder-command to run')
52
+ end
53
+
54
+ end
55
+
56
+ # Run the command
57
+ begin
58
+ opts.parse
59
+ rescue Slop::Error => e
60
+ STDERR.puts e.message
61
+ puts ''
62
+ puts opts # print help
63
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cloudfinder-ec2/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "cloudfinder-ec2"
8
+ gem.version = Cloudfinder::EC2::VERSION
9
+ gem.authors = ["Andrew Coulton"]
10
+ gem.email = ["andrew@ingenerator.com"]
11
+ gem.license = 'BSD-3-Clause'
12
+ gem.description = 'Uses EC2 instance tags to locate all the running instances in a given cluster, grouped by role.'
13
+ gem.summary = gem.description
14
+ gem.homepage = "https://github.com/edbookfest/cloudfinder-ec2"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_dependency 'aws-sdk', '~>2.0'
22
+ gem.add_dependency 'slop', '~>3.6'
23
+
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'rspec'
26
+ gem.add_development_dependency 'guard'
27
+ gem.add_development_dependency 'guard-rspec'
28
+ end
@@ -0,0 +1,8 @@
1
+ require 'aws-sdk'
2
+ require 'cloudfinder-ec2/version'
3
+ require 'cloudfinder-ec2/consts'
4
+ require 'cloudfinder-ec2/instance'
5
+ require 'cloudfinder-ec2/cluster'
6
+ require 'cloudfinder-ec2/clusterfinder'
7
+ require 'cloudfinder-ec2/detector'
8
+ require 'cloudfinder-ec2/command/list'
@@ -0,0 +1,88 @@
1
+ module Cloudfinder
2
+ module EC2
3
+ class Cluster
4
+
5
+ attr_reader(:cluster_name)
6
+
7
+ # @param [Hash] args containing the cluster name and the instances
8
+ # @return [Cloudfinder::EC2::Cluster]
9
+ def initialize(args)
10
+ @cluster_name = args[:cluster_name]
11
+ @instances = args[:instances]
12
+ end
13
+
14
+ # @return [bool]
15
+ def empty?
16
+ instances.empty?
17
+ end
18
+
19
+ # @return [bool]
20
+ def running?
21
+ !empty?
22
+ end
23
+
24
+ # @param [symbol] role
25
+ # @return [bool]
26
+ def has_role?(role)
27
+ list_roles.include?(role)
28
+ end
29
+
30
+ # @param [string] instance_id
31
+ # @return [bool]
32
+ def has_instance?(instance_id)
33
+ instances.any? { |instance| instance.instance_id == instance_id }
34
+ end
35
+
36
+ def get_instance(instance_id)
37
+ found_instances = instances.select { |instance| instance.instance_id == instance_id }
38
+ raise(RangeError, "#{instance_id} is not part of the #{@cluster_name} cluster") if found_instances.empty?
39
+ found_instances.first
40
+ end
41
+
42
+ # @return [Array<symbol>]
43
+ def list_roles
44
+ instances.group_by { |instance| instance.role }.keys
45
+ end
46
+
47
+ # @param [symbol] role
48
+ # @return [Array<Cloudfinder::EC2::Instance>]
49
+ def list_role_instances(role)
50
+ instances.select { |instance| instance.role === role }
51
+ end
52
+
53
+ # Return the current cluster as a simple nested hash, suitable for rendering to JSON or similar
54
+ #
55
+ # {
56
+ # cluster_name: 'production',
57
+ # roles: {
58
+ # db: [
59
+ # {instance_id: 'i-00000001', public_ip: '123.456.789.123',...}
60
+ # ]
61
+ # }
62
+ # }
63
+ #
64
+ # @return [Hash]
65
+ def to_hash
66
+ hash = {
67
+ cluster_name: @cluster_name,
68
+ roles: {}
69
+ }
70
+
71
+ instances.each do |instance|
72
+ hash[:roles][instance.role] = [] unless hash[:roles][instance.role]
73
+ hash[:roles][instance.role] << instance.to_hash
74
+ end
75
+
76
+ hash
77
+ end
78
+
79
+ private
80
+
81
+ # @return [Array<Cloudfinder::EC2::Instance>]
82
+ def instances
83
+ @instances
84
+ end
85
+
86
+ end
87
+ end
88
+ end