cloudfinder-ec2 0.1.0

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.
@@ -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