instance_selector 0.0.19 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 87f301b11ff2afccccda9580ee7f09007265c341
4
- data.tar.gz: 70a879d4c4e8f49c5b017cb1d5ffeeb4a3fea344
3
+ metadata.gz: 01b470486e6f8d771f2fb28b3b07c4f6e53907f2
4
+ data.tar.gz: 2c6af22e2ad09b6ba88c67ce048dde13652b4cda
5
5
  SHA512:
6
- metadata.gz: 0a312b89c6fbc5f383c1a083687af8bcbf4550e08289f38732a5b87b841a3fcbc52ae543481e9187ba22022d61307eaa0f1b0595536b651e0f305c77fb66a8ad
7
- data.tar.gz: f913ba1966b80808ac468f16d02cdbb5039d35e9e1fbccd5ebda10c270225c898a3582370dd84d89ef503570fc36ee961bc1ce42471b96bd1d3e0898e8359fd6
6
+ metadata.gz: edb58fda3b7905562b078699c81f6bd6df27971e7ec71af0ca1eba881e3de752f4afde83cb08fa9b38d6fbf8cb2c86f1336c8d7e386c70e36197956be85064ca
7
+ data.tar.gz: 70fac2a50eb5ab61094a4d71923bb886169d682dbfc8780c77336cb6e8b7ee2a784bdf62bb23a797e8b6ffab0d5d5b4dcfdb1235bb3fca0f790fac054af408a1
@@ -0,0 +1,3 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Kevin McFadden
1
+ Copyright (c) 2016 Kevin McFadden
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Remove the configuration pain when deploying to cloud servers.
4
4
 
5
- When deploying applications with Capistrano, you need to itemize the servers for each role. When you only have a server or two, this is easily managed. However, if you have many servers provisioned via a CM tool, keeping track of them becomes difficult.
5
+ When deploying applications with Capistrano, you need to itemize the servers for each role. When you only have a server or two this is easily managed, but keeping track of them becomes difficult if you have many servers provisioned via a CM tool or autoscaling.
6
6
 
7
7
  By tagging servers with some meta data when they are created, you can filter
8
8
  the server list to only the ones pertaining to the current deploy.
@@ -15,7 +15,7 @@ Currently, only EC2 instances are supported.
15
15
 
16
16
  Add this line to your application's Gemfile:
17
17
 
18
- gem 'instance_selector'
18
+ gem 'instance_selector', require: false
19
19
 
20
20
  And then execute:
21
21
 
@@ -31,24 +31,33 @@ By default, only running instances will be included in the results. Overriding
31
31
 
32
32
  ### Standalone
33
33
 
34
- require 'instance_selector/connection'
35
- conn = InstanceSelector::Connection.factory(:aws)
36
- instances = connection.instances(:tags => {"Environment" => "staging", "Role" => "web"})
34
+ require 'instance_selector/provider'
35
+ provider = InstanceSelector::Provider.factory(:aws)
36
+ instances = provider.instances(:tags => {"Environment" => "staging", "Role" => "web"})
37
37
 
38
38
  ### With Capistrano
39
39
 
40
40
  require 'instance_selector/capistrano'
41
+
42
+ # Assign all web servers to the app role
41
43
  instance_selector :app, :aws, :tags => {"Environment" => "staging", "Role" => "web"}
42
44
 
45
+ # an exception is thrown if exactly one instance is not returned
46
+ instance_selector :db,
47
+ :aws,
48
+ :tags => {"Environment" => "staging", "Role" => "cron"},
49
+ role_options: { primary: true },
50
+ expect_count: 1
51
+
43
52
  ### Generic with Capistrano
44
53
 
45
54
  # Centralized instance selector config
46
55
  on :after, only: stages do
47
56
  @logger.log 1, "Selecting instances from the cloud"
48
- instance_selector :app, :aws, tags: {"Environment" => stage, "Role" => "social-web"}
49
- instance_selector :sidekiq, :aws, tags: {"Environment" => stage, "Role" => "social-sidekiq"}
57
+ instance_selector :app, :aws, tags: {"Environment" => stage, "Role" => "web"}
58
+ instance_selector :sidekiq, :aws, tags: {"Environment" => stage, "Role" => "sidekiq"}
50
59
  # NOTE: Only one cron host is supported! Tag appropriately.
51
- instance_selector :cron, :aws, tags: {"Environment" => stage, "CronRole" => "social-web"}
60
+ instance_selector :cron, :aws, tags: {"Environment" => stage, "CronRole" => "web"}
52
61
  end
53
62
 
54
63
  ### Filters
@@ -71,25 +80,3 @@ http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-Describ
71
80
  3. Commit your changes (`git commit -am 'Add some feature'`)
72
81
  4. Push to the branch (`git push origin my-new-feature`)
73
82
  5. Create new Pull Request
74
-
75
- ## License
76
-
77
- Copyright (c) 2013 Kevin McFadden
78
-
79
- Permission is hereby granted, free of charge, to any person obtaining a copy
80
- of this software and associated documentation files (the "Software"), to deal
81
- in the Software without restriction, including without limitation the rights
82
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
83
- copies of the Software, and to permit persons to whom the Software is
84
- furnished to do so, subject to the following conditions:
85
-
86
- The above copyright notice and this permission notice shall be included in all
87
- copies or substantial portions of the Software.
88
-
89
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
95
- SOFTWARE.
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
@@ -4,27 +4,25 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'instance_selector/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "instance_selector"
7
+ spec.name = 'instance_selector'
8
8
  spec.version = InstanceSelector::VERSION
9
- spec.authors = ["Kevin McFadden"]
10
- spec.email = ["kmcfadden@gmail.com"]
11
- spec.description = %q{Retrieve cloud instance DNS names from metadata search}
12
- spec.summary = %q{}
13
- spec.homepage = "https://github.com/n3bulous/instance_selector"
14
- spec.license = "MIT"
9
+ spec.authors = ['Kevin McFadden']
10
+ spec.email = ['kmcfadden@gmail.com']
11
+ spec.description = 'Retrieve cloud instance DNS names from metadata search'
12
+ spec.summary = ''
13
+ spec.homepage = 'https://github.com/n3bulous/instance_selector'
14
+ spec.license = 'MIT'
15
15
 
16
- spec.files = `git ls-files`.split($/)
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
20
20
 
21
-
22
- spec.add_dependency "fog", "~> 1.4"
21
+ spec.add_dependency 'fog-aws', '~> 0.9.2'
23
22
  spec.add_dependency('capistrano', '~> 2.1')
24
23
  spec.add_dependency('slop', '~> 3.4')
25
- spec.add_dependency('nokogiri', '~> 1.5.0')
26
-
24
+ spec.add_dependency('nokogiri', '~> 1.5')
27
25
 
28
- spec.add_development_dependency "bundler", "~> 1.3"
29
- spec.add_development_dependency "rake"
26
+ spec.add_development_dependency 'bundler', '~> 1.3'
27
+ spec.add_development_dependency 'rake'
30
28
  end
@@ -1,7 +1,11 @@
1
- __LIB_DIR__ = File.expand_path(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift __LIB_DIR__ unless $LOAD_PATH.include?(__LIB_DIR__)
1
+ $LOAD_PATH.unshift __LIB_DIR__ unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
3
2
 
4
- require "instance_selector/version"
5
- require "instance_selector/connection"
3
+ class UnexpectedInstanceCount < StandardError
4
+ end
5
+
6
+ require 'instance_selector/version'
7
+ require 'instance_selector/provider'
8
+ require 'instance_selector/providers/provider'
6
9
  require 'instance_selector/providers/aws'
7
- require 'fog'
10
+ require 'instance_selector/providers/override'
11
+ require 'fog/aws'
@@ -4,19 +4,26 @@ Capistrano::Configuration.instance(:must_exist).load do
4
4
  # Yes, this is a hack
5
5
  @instance_selector_instances = {}
6
6
 
7
- def instance_selector(cap_role, provider, args={})
8
- client = InstanceSelector::Connection.factory(provider)
9
- instances = client.instances(client.args_to_filters(args))
10
- role(cap_role, *instances.keys)
7
+ def instance_selector(cap_role, provider, args = {})
8
+ role_options = args.delete(:role_options) || {}
9
+ provider = :override if ENV['HOSTS']
10
+ client = InstanceSelector::Provider.factory(provider)
11
11
 
12
+ begin
13
+ instances = client.instances(args)
14
+ rescue UnexpectedInstanceCount => e
15
+ abort("#{cap_role}: #{e.message}")
16
+ end
17
+
18
+ role(cap_role, *instances.keys, role_options)
12
19
  @instance_selector_instances.merge!(instances)
13
20
  end
14
21
 
15
22
  # Not namespaced due to collision with the above method.
16
- desc "List all cloud instances for a stage"
23
+ desc 'List all cloud instances for a stage'
17
24
  task :instance_selector_list do
18
25
  puts
19
- @instance_selector_instances.sort_by {|k,v| v[:name].to_s}.each do |k, v|
26
+ @instance_selector_instances.sort_by { |_k, v| v[:name].to_s }.each do |k, v|
20
27
  puts k + "\t" + v.values.join("\t")
21
28
  end
22
29
  end
@@ -0,0 +1,20 @@
1
+ module InstanceSelector
2
+ class UnsupportedProviderException < RuntimeError; end
3
+
4
+ # Provider factory for different cloud APIs
5
+ class Provider
6
+ def self.factory(provider, options = {})
7
+ provider = :override if ENV['HOSTS']
8
+
9
+ case provider
10
+ when :aws
11
+ Providers::AWS.new(options)
12
+ when :override
13
+ Providers::Override.new(options)
14
+ else
15
+ raise UnsupportedProviderException,
16
+ "#{provider} is not a supported provider"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,71 +1,75 @@
1
1
  module InstanceSelector
2
2
  module Providers
3
- class AWS
4
- def initialize(options={})
3
+ # AWS Provider
4
+ class AWS < AbstractProvider
5
+ def initialize(options = {})
5
6
  @fog = options.delete(:fog_client)
6
7
 
7
8
  unless @fog
8
9
  @options = {
9
- region: 'us-east-1',
10
+ region: 'us-east-1'
10
11
  }.merge(options)
11
12
 
12
13
  connect
13
14
  end
14
15
  end
15
16
 
17
+ def instances(args = {})
18
+ expect_count = args.delete(:expect_count)
19
+ filters = args_to_filters(args)
20
+ instances = on_demand_instances(filters).merge(spot_instances(filters))
21
+
22
+ if expect_count && expect_count != instances.size
23
+ raise UnexpectedInstanceCount, "Expected #{expect_count}, got #{instances.size}"
24
+ end
25
+
26
+ instances
27
+ end
28
+
29
+ private
16
30
 
17
31
  def dns_from_instance_reservation_set(reservation_set)
18
- reservation_set.inject({}) do |memo, i|
32
+ reservation_set.each_with_object({}) do |i, memo|
19
33
  # Each instancesSet can have multiple instances
20
34
  # Odd, but explains why it's plural.
21
35
  i['instancesSet'].each do |instance|
22
- key = instance['dnsName'].empty? ? instance['ipAddress'] : instance['dnsName']
23
- memo[key] = {name: instance['tagSet']['Name'], instance_id: instance['instanceId']}
24
- end
36
+ key = instance['dnsName'] || instance['ipAddress'] || instance['privateIpAddress']
25
37
 
26
- memo
38
+ memo[key] = { name: instance['tagSet']['Name'],
39
+ identifier: instance['instanceId'] }
40
+ end
27
41
  end
28
42
  end
29
43
 
30
- def on_demand_instances(filters={})
31
- instances = @fog.describe_instances({"instance-state-name" => "running"}.merge(filters))
44
+ def on_demand_instances(filters = {})
45
+ instances = @fog.describe_instances({ 'instance-state-name' => 'running' }.merge(filters))
32
46
 
33
47
  dns_from_instance_reservation_set instances.body['reservationSet']
34
48
  end
35
49
 
36
- def spot_instances(filters={})
37
- requests = @fog.describe_spot_instance_requests({'state' => 'active'}.merge(filters))
38
- requests.body['spotInstanceRequestSet'].inject({}) do |memo, req|
39
-
50
+ def spot_instances(filters = {})
51
+ requests = @fog.describe_spot_instance_requests({ 'state' => 'active' }.merge(filters))
52
+ requests.body['spotInstanceRequestSet'].each_with_object({}) do |req, memo|
40
53
  if req['instanceId'] && !req['instanceId'].empty?
41
54
  instances = @fog.describe_instances('instance-id' => req['instanceId'])
42
55
  memo.merge! dns_from_instance_reservation_set(instances.body['reservationSet'])
43
56
  end
44
-
45
- memo
46
57
  end
47
58
  end
48
59
 
49
-
50
- def instances(filters={})
51
- on_demand_instances(filters).merge(spot_instances(filters))
52
- end
53
-
54
60
  def connect
55
- begin
56
- @fog = Fog::Compute.new(provider: 'AWS',
57
- region: @options[:region],
58
- aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
59
- aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'])
60
- rescue
61
- @fog = Fog::Compute.new(provider: 'AWS', region: @options[:region])
62
- ensure
63
- unless @fog
64
- abort <<-EOS
65
- Could not authenticate with AWS.
66
- Please create a .fog file or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
67
- EOS
68
- end
61
+ @fog = Fog::Compute.new(provider: 'AWS',
62
+ region: @options[:region],
63
+ aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
64
+ aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'])
65
+ rescue
66
+ @fog = Fog::Compute.new(provider: 'AWS', region: @options[:region])
67
+ ensure
68
+ unless @fog
69
+ abort <<-EOS
70
+ Could not authenticate with AWS.
71
+ Please create a .fog file or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
72
+ EOS
69
73
  end
70
74
  end
71
75
 
@@ -77,17 +81,15 @@ module InstanceSelector
77
81
 
78
82
  def parse_spot_request_id(spot_request_id)
79
83
  return {} unless spot_request_id
80
- { "spot-instance-request-id" => spot_request_id }
84
+ { 'spot-instance-request-id' => spot_request_id }
81
85
  end
82
86
 
83
87
  def parse_tags(tags)
84
88
  return {} unless tags
85
- tags.inject({}) do |memo, tag|
89
+ tags.each_with_object({}) do |tag, memo|
86
90
  memo["tag:#{tag[0]}"] = tag[1]
87
- memo
88
91
  end
89
92
  end
90
-
91
93
  end
92
94
  end
93
95
  end
@@ -0,0 +1,19 @@
1
+ module InstanceSelector
2
+ module Providers
3
+ # Uses the HOSTS environment variable instead of a cloud provider
4
+ class Override < AbstractProvider
5
+ def initialize(_options = {})
6
+ @hosts = ENV['HOSTS']
7
+ end
8
+
9
+ def instances(_args = {})
10
+ results = {}
11
+ ENV['HOSTS'].split(',').each do |host|
12
+ results[host] = { name: host, identifier: 'N/A' }
13
+ end
14
+
15
+ results
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module InstanceSelector
2
+ module Providers
3
+ # Abstract class for providers
4
+ class AbstractProvider
5
+ # Returns a hash of instance network resources and the associated attributes.
6
+ # e.g. (not realistic data):
7
+ # {
8
+ # '127.0.0.1' => { name: 'localhost', identifier: 'N/A' }
9
+ # '10.10.1.1' => { name: 'aws name tag', identifier: 'i-abcdef01' }
10
+ # }
11
+ def instances(_args = {})
12
+ raise NotImplementedError, 'A provider must implement #instances'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module InstanceSelector
2
- VERSION = "0.0.19"
2
+ VERSION = '0.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,97 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: instance_selector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.19
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin McFadden
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-12 00:00:00.000000000 Z
11
+ date: 2016-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: fog
14
+ name: fog-aws
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.4'
19
+ version: 0.9.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.4'
26
+ version: 0.9.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: capistrano
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '2.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: slop
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '3.4'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.4'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: nokogiri
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.5.0
61
+ version: '1.5'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 1.5.0
68
+ version: '1.5'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: '1.3'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ~>
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.3'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  description: Retrieve cloud instance DNS names from metadata search
@@ -102,7 +102,8 @@ executables:
102
102
  extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
- - .gitignore
105
+ - ".gitignore"
106
+ - ".rubocop.yml"
106
107
  - Gemfile
107
108
  - LICENSE.txt
108
109
  - README.md
@@ -111,8 +112,10 @@ files:
111
112
  - instance_selector.gemspec
112
113
  - lib/instance_selector.rb
113
114
  - lib/instance_selector/capistrano.rb
114
- - lib/instance_selector/connection.rb
115
+ - lib/instance_selector/provider.rb
115
116
  - lib/instance_selector/providers/aws.rb
117
+ - lib/instance_selector/providers/override.rb
118
+ - lib/instance_selector/providers/provider.rb
116
119
  - lib/instance_selector/version.rb
117
120
  homepage: https://github.com/n3bulous/instance_selector
118
121
  licenses:
@@ -124,17 +127,17 @@ require_paths:
124
127
  - lib
125
128
  required_ruby_version: !ruby/object:Gem::Requirement
126
129
  requirements:
127
- - - '>='
130
+ - - ">="
128
131
  - !ruby/object:Gem::Version
129
132
  version: '0'
130
133
  required_rubygems_version: !ruby/object:Gem::Requirement
131
134
  requirements:
132
- - - '>='
135
+ - - ">="
133
136
  - !ruby/object:Gem::Version
134
137
  version: '0'
135
138
  requirements: []
136
139
  rubyforge_project:
137
- rubygems_version: 2.0.5
140
+ rubygems_version: 2.4.5.1
138
141
  signing_key:
139
142
  specification_version: 4
140
143
  summary: ''
@@ -1,14 +0,0 @@
1
- module InstanceSelector
2
- class UnsupportedProviderException < Exception; end;
3
-
4
- class Connection
5
- def self.factory(provider, options={})
6
- conn = case provider
7
- when :aws
8
- Providers::AWS.new(options)
9
- else
10
- raise UnsupportedProviderException, "#{provider} is not a supported provider"
11
- end
12
- end
13
- end
14
- end