instance_selector 0.0.19 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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