knife-instance 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.
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +46 -0
- data/Rakefile +9 -0
- data/knife-instance.gemspec +26 -0
- data/lib/chef/knife/instance_create.rb +202 -0
- data/lib/knife-instance.rb +2 -0
- data/lib/knife-instance/aws.rb +37 -0
- data/lib/knife-instance/bootstrap_generator.rb +59 -0
- data/lib/knife-instance/templates/boot.sh.erb +55 -0
- data/lib/knife-instance/version.rb +5 -0
- data/lib/knife-instance/zestknife.rb +278 -0
- data/spec/lib/chef/knife/instance_create_spec.rb +95 -0
- data/spec/lib/knife-instance/zestknife_spec.rb +212 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/databag.key +0 -0
- metadata +156 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 ZestFinance
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# knife-instance [](https://codeclimate.com/github/ZestFinance/knife-instance) [](https://travis-ci.org/ZestFinance/knife-instance)
|
2
|
+
|
3
|
+
Manage EC2 instances with Chef from the command line
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'knife-instance'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install knife-instance
|
18
|
+
|
19
|
+
## Configuration
|
20
|
+
|
21
|
+
Add this to your environment ~/.bashrc or ~/.zshrc
|
22
|
+
|
23
|
+
```shell
|
24
|
+
export aws_access_key_id='Put your key here'
|
25
|
+
export aws_secret_access_key='Put your key here'
|
26
|
+
```
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
```shell
|
31
|
+
bundle exec knife instance create -E development \
|
32
|
+
--image my_ec2-image \
|
33
|
+
-t myclustertag \
|
34
|
+
--group my_security_group
|
35
|
+
```
|
36
|
+
|
37
|
+
## Contributing
|
38
|
+
|
39
|
+
1. Fork it ( http://github.com/ZestFinance/knife-instance/fork )
|
40
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
41
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
42
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
43
|
+
5. Create new Pull Request
|
44
|
+
|
45
|
+
## Credits
|
46
|
+
Inspired by [knife-ec2 gem](https://github.com/opscode/knife-ec2)
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'knife-instance/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "knife-instance"
|
8
|
+
spec.version = Knife::Instance::VERSION
|
9
|
+
spec.authors = ["Alexander Tamoykin", "Val Brodsky"]
|
10
|
+
spec.email = ["at@zestfinance.com", "vlb@zestfinance.com"]
|
11
|
+
spec.summary = %q{Manage EC2 instances with Chef from the command line}
|
12
|
+
spec.description = %q{Manage EC2 instances with Chef from the command line}
|
13
|
+
spec.homepage = "https://github.com/ZestFinance/knife-instance"
|
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_dependency 'chef', '>= 0.10.24'
|
22
|
+
spec.add_dependency 'fog', '>= 1.9.0'
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "require_all"
|
26
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require "knife-instance/zestknife"
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class InstanceCreate < ::ZestKnife
|
6
|
+
banner "knife instance create (options)"
|
7
|
+
|
8
|
+
deps do
|
9
|
+
require 'fog'
|
10
|
+
require 'fog/aws/models/dns/record'
|
11
|
+
require 'readline'
|
12
|
+
require 'chef/json_compat'
|
13
|
+
require 'chef/node'
|
14
|
+
require 'chef/api_client'
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor :hostname
|
18
|
+
|
19
|
+
with_opts :aws_ssh_key_id, :encrypted_data_bag_secret, :wait_for_it
|
20
|
+
with_validated_opts :cluster_tag, :environment, :base_domain, :region
|
21
|
+
|
22
|
+
option :flavor,
|
23
|
+
:short => "-f FLAVOR",
|
24
|
+
:long => "--flavor FLAVOR",
|
25
|
+
:description => "The flavor of server (m1.small, m1.medium, etc)",
|
26
|
+
:default => "m1.small"
|
27
|
+
|
28
|
+
option :image,
|
29
|
+
:short => "-I IMAGE",
|
30
|
+
:long => "--image IMAGE",
|
31
|
+
:description => "The AMI for the server"
|
32
|
+
|
33
|
+
option :iam_role,
|
34
|
+
:long => "--iam-role IAM_ROLE",
|
35
|
+
:description => "Assign a node to an IAM Role. Set to 'default_role' by default",
|
36
|
+
:default => 'default_role'
|
37
|
+
|
38
|
+
option :security_groups,
|
39
|
+
:short => "-G X,Y,Z",
|
40
|
+
:long => "--groups X,Y,Z",
|
41
|
+
:description => "The security groups for this server"
|
42
|
+
|
43
|
+
option :availability_zone,
|
44
|
+
:short => "-Z ZONE",
|
45
|
+
:long => "--availability-zone ZONE",
|
46
|
+
:description => "The Availability Zone"
|
47
|
+
|
48
|
+
option :hostname,
|
49
|
+
:short => "-h NAME",
|
50
|
+
:long => "--node-name NAME",
|
51
|
+
:description => "The Chef node name for your new node"
|
52
|
+
|
53
|
+
option :run_list,
|
54
|
+
:short => "-r RUN_LIST",
|
55
|
+
:long => "--run-list RUN_LIST",
|
56
|
+
:description => "(Required) Comma separated list of roles/recipes to apply",
|
57
|
+
:default => ["role[base]"],
|
58
|
+
:proc => lambda { |o| o.split(/[\s,]+/) }
|
59
|
+
|
60
|
+
option :show_server_options,
|
61
|
+
:short => "-D",
|
62
|
+
:long => "--server-dry-run",
|
63
|
+
:description => "Show the options used to create the server and exit before running",
|
64
|
+
:boolean => true,
|
65
|
+
:default => false
|
66
|
+
|
67
|
+
def run
|
68
|
+
$stdout.sync = true
|
69
|
+
setup_config
|
70
|
+
|
71
|
+
@environment = config[:environment]
|
72
|
+
@base_domain = config[:base_domain]
|
73
|
+
@hostname = config[:hostname] || generate_hostname(@environment)
|
74
|
+
@color = config[:cluster_tag]
|
75
|
+
@region = config[:region]
|
76
|
+
|
77
|
+
validate!
|
78
|
+
|
79
|
+
get_user_data
|
80
|
+
|
81
|
+
if config[:show_server_options]
|
82
|
+
details = create_server_def
|
83
|
+
ui.info(
|
84
|
+
ui.color("Creating server with options\n", :bold) +
|
85
|
+
ui.color(JSON.pretty_generate(details.reject { |k, v| k == :user_data }), :blue) +
|
86
|
+
ui.color("\nWith user script\n", :bold) +
|
87
|
+
ui.color(details[:user_data], :cyan)
|
88
|
+
)
|
89
|
+
exit 0
|
90
|
+
end
|
91
|
+
|
92
|
+
server = ZestKnife.aws_for_region(@region).compute.servers.create(create_server_def)
|
93
|
+
|
94
|
+
msg_pair("Zest Hostname", fqdn(hostname))
|
95
|
+
msg_pair("Environment", @environment)
|
96
|
+
msg_pair("Run List", config[:run_list].join(', '))
|
97
|
+
msg_pair("Instance ID", server.id)
|
98
|
+
msg_pair("Flavor", server.flavor_id)
|
99
|
+
msg_pair("Image", server.image_id)
|
100
|
+
msg_pair("Region", @region)
|
101
|
+
msg_pair("Availability Zone", server.availability_zone)
|
102
|
+
msg_pair("Security Groups", server.groups.join(", "))
|
103
|
+
msg_pair("SSH Key", server.key_name)
|
104
|
+
|
105
|
+
return unless config[:wait_for_it]
|
106
|
+
|
107
|
+
print "\n#{ui.color("Waiting for server", :magenta)}"
|
108
|
+
|
109
|
+
# wait for it to be ready to do stuff
|
110
|
+
server.wait_for { print "."; ready? }
|
111
|
+
|
112
|
+
puts "\n"
|
113
|
+
msg_pair("Public DNS Name", server.dns_name)
|
114
|
+
msg_pair("Public IP Address", server.public_ip_address)
|
115
|
+
msg_pair("Private DNS Name", server.private_dns_name)
|
116
|
+
msg_pair("Private IP Address", server.private_ip_address)
|
117
|
+
msg_pair("Instance ID", server.id)
|
118
|
+
msg_pair("Flavor", server.flavor_id)
|
119
|
+
msg_pair("Image", server.image_id)
|
120
|
+
msg_pair("Region", @region)
|
121
|
+
msg_pair("Availability Zone", server.availability_zone)
|
122
|
+
msg_pair("Security Groups", server.groups.join(", "))
|
123
|
+
msg_pair("SSH Key", server.key_name)
|
124
|
+
msg_pair("Root Device Type", server.root_device_type)
|
125
|
+
|
126
|
+
zone.records.create(:name => fqdn(hostname), :type => 'A', :value => server.private_ip_address, :ttl => 300)
|
127
|
+
|
128
|
+
server
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.new_with_defaults environment, region, color, base_domain, opts
|
132
|
+
new.tap do |ic|
|
133
|
+
ic.config[:environment] = environment
|
134
|
+
ic.config[:cluster_tag] = color
|
135
|
+
ic.config[:region] = region
|
136
|
+
ic.config[:base_domain] = base_domain
|
137
|
+
ic.config[:aws_ssh_key_id] = opts[:aws_ssh_key_id]
|
138
|
+
ic.config[:aws_access_key_id] = opts[:aws_access_key_id]
|
139
|
+
ic.config[:aws_secret_access_key] = opts[:aws_secret_access_key]
|
140
|
+
ic.config[:availability_zone] = opts[:availability_zone]
|
141
|
+
ic.config[:encrypted_data_bag_secret] = opts[:encrypted_data_bag_secret]
|
142
|
+
ic.config[:wait_for_it] = opts[:wait_for_it]
|
143
|
+
ic.config[:image] = opts[:image]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_server_def
|
148
|
+
server_def = {
|
149
|
+
:image_id => image,
|
150
|
+
:groups => security_group,
|
151
|
+
:flavor_id => config[:flavor],
|
152
|
+
:key_name => config[:aws_ssh_key_id],
|
153
|
+
:availability_zone => availability_zone,
|
154
|
+
:tags => {
|
155
|
+
'Name' => hostname,
|
156
|
+
'environment' => @environment
|
157
|
+
},
|
158
|
+
:user_data => config[:without_user_data] ? "" : get_user_data,
|
159
|
+
:iam_instance_profile_name => config[:iam_role]
|
160
|
+
}
|
161
|
+
|
162
|
+
server_def
|
163
|
+
end
|
164
|
+
|
165
|
+
def image
|
166
|
+
config[:image]
|
167
|
+
end
|
168
|
+
|
169
|
+
def security_group
|
170
|
+
config[:security_groups]
|
171
|
+
end
|
172
|
+
|
173
|
+
def availability_zone
|
174
|
+
config[:availability_zone]
|
175
|
+
end
|
176
|
+
|
177
|
+
def ami
|
178
|
+
@ami ||= ZestKnife.aws_for_region(@region).compute.images.get(image)
|
179
|
+
end
|
180
|
+
|
181
|
+
def validate!
|
182
|
+
unless File.exists?(config[:encrypted_data_bag_secret])
|
183
|
+
errors << "Could not find encrypted data bag secret. Tried #{config[:encrypted_data_bag_secret]}"
|
184
|
+
end
|
185
|
+
|
186
|
+
if ami.nil?
|
187
|
+
errors << "You have not provided a valid image. Tried to find '#{image}'."
|
188
|
+
end
|
189
|
+
|
190
|
+
validate_hostname hostname
|
191
|
+
|
192
|
+
super([:aws_access_key_id, :aws_secret_access_key,
|
193
|
+
:flavor, :aws_ssh_key_id, :run_list])
|
194
|
+
end
|
195
|
+
|
196
|
+
def get_user_data
|
197
|
+
generator = Zest::BootstrapGenerator.new(Chef::Config[:validation_key], Chef::Config[:validation_client_name], Chef::Config[:chef_server_url], @environment, config[:run_list], hostname, @color, @base_domain, config[:encrypted_data_bag_secret])
|
198
|
+
generator.generate
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'fog'
|
2
|
+
Excon.defaults[:ssl_verify_peer] = false
|
3
|
+
|
4
|
+
module Zest
|
5
|
+
class AWS
|
6
|
+
attr_accessor :aws_access_key_id, :aws_secret_access_key, :region
|
7
|
+
|
8
|
+
def initialize aws_access_key_id, aws_secret_access_key, region
|
9
|
+
@aws_access_key_id, @aws_secret_access_key, @region = aws_access_key_id, aws_secret_access_key, region
|
10
|
+
end
|
11
|
+
|
12
|
+
def compute
|
13
|
+
@compute ||= begin
|
14
|
+
Fog::Compute.new(
|
15
|
+
:provider => 'AWS',
|
16
|
+
:aws_access_key_id => aws_access_key_id,
|
17
|
+
:aws_secret_access_key => aws_secret_access_key,
|
18
|
+
:region => region
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def servers
|
24
|
+
@servers ||= compute.servers
|
25
|
+
end
|
26
|
+
|
27
|
+
def dns
|
28
|
+
@dns ||= begin
|
29
|
+
Fog::DNS.new(
|
30
|
+
:provider => 'AWS',
|
31
|
+
:aws_access_key_id => aws_access_key_id,
|
32
|
+
:aws_secret_access_key => aws_secret_access_key
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# This class generates the ec2 user-data bootstrap script
|
2
|
+
module Zest
|
3
|
+
class BootstrapGenerator
|
4
|
+
CONFIG_FILE_TEMPLATE = File.expand_path 'templates/boot.sh.erb', File.dirname(__FILE__)
|
5
|
+
|
6
|
+
def initialize(validation_key_file, validation_client_name, chef_server_url, environment, run_list, hostname, color, base_domain, encrypted_databag_secret_file)
|
7
|
+
@validation_client_name = validation_client_name
|
8
|
+
@validation_key_file = validation_key_file
|
9
|
+
@chef_server_url = chef_server_url
|
10
|
+
@environment = environment
|
11
|
+
@run_list = run_list
|
12
|
+
@hostname = hostname
|
13
|
+
@color = color
|
14
|
+
@base_domain = base_domain
|
15
|
+
@encrypted_databag_secret_file = encrypted_databag_secret_file
|
16
|
+
end
|
17
|
+
|
18
|
+
def first_boot
|
19
|
+
{
|
20
|
+
"run_list" => @run_list,
|
21
|
+
"assigned_hostname" => @hostname,
|
22
|
+
"rails" => {"cluster" => {"color" => @color}},
|
23
|
+
"base_domain" => @base_domain
|
24
|
+
}.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def validation_key
|
28
|
+
File.read(@validation_key_file)
|
29
|
+
end
|
30
|
+
|
31
|
+
def encrypted_data_bag_secret
|
32
|
+
File.read @encrypted_databag_secret_file
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate
|
36
|
+
template = File.read(CONFIG_FILE_TEMPLATE)
|
37
|
+
Erubis::Eruby.new(template).evaluate(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def config_content
|
41
|
+
<<-CONFIG
|
42
|
+
require 'syslog-logger'
|
43
|
+
Logger::Syslog.class_eval do
|
44
|
+
attr_accessor :sync, :formatter
|
45
|
+
end
|
46
|
+
|
47
|
+
log_level :info
|
48
|
+
log_location Logger::Syslog.new("chef-client")
|
49
|
+
chef_server_url "#{@chef_server_url}"
|
50
|
+
validation_client_name "#{@validation_client_name}"
|
51
|
+
node_name "#{@hostname}"
|
52
|
+
CONFIG
|
53
|
+
end
|
54
|
+
|
55
|
+
def start_chef
|
56
|
+
"/usr/bin/chef-client -j /etc/chef/first-boot.json -E #{@environment}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
exec > >(tee /var/log/user-data.log|logger -t chef-client -s 2>/dev/console) 2>&1
|
3
|
+
|
4
|
+
set -e -x
|
5
|
+
|
6
|
+
if [ ! -f /usr/bin/chef-client ]; then
|
7
|
+
apt-get update
|
8
|
+
apt-get install -y ruby ruby1.8-dev build-essential wget libruby-extras libruby1.8-extras
|
9
|
+
cd /tmp
|
10
|
+
wget http://production.cf.rubygems.org/rubygems/rubygems-1.8.10.tgz
|
11
|
+
tar zxf rubygems-1.8.10.tgz
|
12
|
+
cd rubygems-1.8.10
|
13
|
+
ruby setup.rb --no-format-executable
|
14
|
+
cd ..
|
15
|
+
rm -Rf /tmp/rubygems-1.8.10
|
16
|
+
fi
|
17
|
+
|
18
|
+
gem update --no-rdoc --no-ri
|
19
|
+
gem install ohai --no-rdoc --no-ri --verbose
|
20
|
+
gem install mime-types --no-rdoc --no-ri --verbose --version 1.25
|
21
|
+
gem install chef --no-rdoc --no-ri --verbose --version 10.24.0
|
22
|
+
gem install tzinfo --no-rdoc --no-ri --verbose --version 0.3.33
|
23
|
+
gem install syslog-logger --no-rdoc --no-ri --verbose --version 1.6.8
|
24
|
+
|
25
|
+
mkdir -p /etc/chef
|
26
|
+
|
27
|
+
<%# Remember to suppress a newline at the end of an erb statement for keys --> -%>
|
28
|
+
|
29
|
+
(
|
30
|
+
cat <<'EOP'
|
31
|
+
<%= validation_key -%>
|
32
|
+
EOP
|
33
|
+
) > /tmp/validation.pem
|
34
|
+
awk NF /tmp/validation.pem > /etc/chef/validation.pem
|
35
|
+
rm /tmp/validation.pem
|
36
|
+
|
37
|
+
(
|
38
|
+
cat <<'EOP'
|
39
|
+
<%= config_content %>
|
40
|
+
EOP
|
41
|
+
) > /etc/chef/client.rb
|
42
|
+
|
43
|
+
(
|
44
|
+
cat <<'EOP'
|
45
|
+
<%= first_boot %>
|
46
|
+
EOP
|
47
|
+
) > /etc/chef/first-boot.json
|
48
|
+
|
49
|
+
(
|
50
|
+
cat <<'EOP'
|
51
|
+
<%= encrypted_data_bag_secret -%>
|
52
|
+
EOP
|
53
|
+
) > /etc/chef/encrypted_data_bag_secret
|
54
|
+
|
55
|
+
<%= start_chef %>
|
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
require 'knife-instance/aws'
|
3
|
+
require 'knife-instance/bootstrap_generator'
|
4
|
+
|
5
|
+
class ZestKnife < Chef::Knife
|
6
|
+
attr_accessor :base_domain
|
7
|
+
attr_accessor :internal_domain
|
8
|
+
|
9
|
+
def msg_pair(label, value, color=:cyan)
|
10
|
+
if value && !value.to_s.empty?
|
11
|
+
puts "#{ui.color(label, color)}: #{value}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.aws_for_region(region)
|
16
|
+
Zest::AWS.new(Chef::Config[:knife][:aws_access_key_id], Chef::Config[:knife][:aws_secret_access_key], region)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.AWS_REGIONS
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.in_all_aws_regions
|
24
|
+
self.AWS_REGIONS.each do |region|
|
25
|
+
yield self.aws_for_region(region)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_item(klass, name)
|
30
|
+
begin
|
31
|
+
object = klass.load(name)
|
32
|
+
return [object]
|
33
|
+
rescue Net::HTTPServerException
|
34
|
+
return []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_ec2(name)
|
39
|
+
nodes = {}
|
40
|
+
self.class.in_all_aws_regions do |zest_aws|
|
41
|
+
nodes = nodes.merge(zest_aws.compute.servers.group_by { |s| s.tags["Name"] })
|
42
|
+
end
|
43
|
+
nodes[name].nil? ? [] : nodes[name]
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_r53 name
|
47
|
+
in_zone = zone_from_name(name)
|
48
|
+
if in_zone.nil?
|
49
|
+
in_zone = zone
|
50
|
+
name = fqdn(name)
|
51
|
+
end
|
52
|
+
name = "#{name}." unless name[-1] == "."
|
53
|
+
recs = in_zone.records.select {|r| r.name == name }.to_a
|
54
|
+
recs.empty? ? [in_zone.records.get(name)].compact : recs
|
55
|
+
end
|
56
|
+
|
57
|
+
def zone
|
58
|
+
unless @zone
|
59
|
+
self.class.in_all_aws_regions do |zest_aws|
|
60
|
+
@zone ||= zest_aws.dns.zones.detect { |z| z.domain.downcase == domain }
|
61
|
+
end
|
62
|
+
raise "Could not find DNS zone" unless @zone
|
63
|
+
end
|
64
|
+
|
65
|
+
@zone
|
66
|
+
end
|
67
|
+
|
68
|
+
def zone_from_name dns_name
|
69
|
+
name, tld = dns_name.split(".")[-2..-1]
|
70
|
+
if name && tld
|
71
|
+
dns_domain = "#{name}.#{tld}"
|
72
|
+
zone = nil
|
73
|
+
|
74
|
+
self.class.in_all_aws_regions do |zest_aws|
|
75
|
+
zone1 = zest_aws.dns.zones.select {|x| x.domain =~ /^#{dns_domain}/ }.first
|
76
|
+
zone = zone1 if zone1
|
77
|
+
end
|
78
|
+
|
79
|
+
zone
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def fqdn(name)
|
84
|
+
return '' if name.nil? || name.empty?
|
85
|
+
"#{name}.#{domain}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def domain
|
89
|
+
@internal_domain || ""
|
90
|
+
end
|
91
|
+
|
92
|
+
def generate_hostname env
|
93
|
+
name = nil
|
94
|
+
|
95
|
+
5.times do |i|
|
96
|
+
name = random_hostname env
|
97
|
+
break if check_services(name).empty?
|
98
|
+
|
99
|
+
name = nil
|
100
|
+
srand # re-seed rand so we don't get stuck in a sequence
|
101
|
+
end
|
102
|
+
|
103
|
+
errors << "Unable to find available hostname in 5 tries" if name.nil?
|
104
|
+
name
|
105
|
+
end
|
106
|
+
|
107
|
+
def validate_hostname hostname
|
108
|
+
errors << "hostname can't be blank" and return if (hostname.nil? || hostname.empty?)
|
109
|
+
check_services(hostname).each do |service|
|
110
|
+
errors << "#{hostname} in #{service.class} already exists. Delete first."
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def check_services hostname
|
115
|
+
find_item(Chef::Node, hostname) +
|
116
|
+
find_item(Chef::ApiClient, hostname) +
|
117
|
+
find_ec2(hostname) +
|
118
|
+
find_r53(hostname)
|
119
|
+
end
|
120
|
+
|
121
|
+
def random_hostname env
|
122
|
+
"#{domain_prefix}#{environment_prefix env}#{random_three_digit_number}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def domain_prefix
|
126
|
+
base_domain[0]
|
127
|
+
end
|
128
|
+
|
129
|
+
def environment_prefix env
|
130
|
+
env[0]
|
131
|
+
end
|
132
|
+
|
133
|
+
def random_three_digit_number
|
134
|
+
sprintf("%03d", rand(1000))
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.with_opts(*args)
|
138
|
+
invalid_args = args.select {|arg| !OPTS.keys.include? arg }
|
139
|
+
raise "Invalid option(s) passed to with_opts: #{invalid_args.join(", ")}" unless invalid_args.empty?
|
140
|
+
|
141
|
+
args.each do |arg|
|
142
|
+
option arg, OPTS[arg]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class << self; attr_accessor :validated_opts end
|
147
|
+
|
148
|
+
def self.with_validated_opts(*args)
|
149
|
+
with_opts(*args)
|
150
|
+
validates(*args)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.validates(*args)
|
154
|
+
raise "Invalid argument(s) passed to validates: #{args - VALIDATORS.keys}" unless (args - VALIDATORS.keys).empty?
|
155
|
+
self.validated_opts ||= []
|
156
|
+
self.validated_opts.concat args
|
157
|
+
end
|
158
|
+
|
159
|
+
def setup_config(keys=[:aws_access_key_id, :aws_secret_access_key])
|
160
|
+
keys.each do |k|
|
161
|
+
Chef::Config[:knife][k] = ENV[k.to_s] if Chef::Config[:knife][k].nil? && ENV[k.to_s]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def errors
|
166
|
+
@errors ||= []
|
167
|
+
end
|
168
|
+
|
169
|
+
def errors?
|
170
|
+
!errors.empty?
|
171
|
+
end
|
172
|
+
|
173
|
+
def validate!(keys=[:aws_access_key_id, :aws_secret_access_key])
|
174
|
+
keys.each do |k|
|
175
|
+
if Chef::Config[:knife][k].nil? & config[k].nil?
|
176
|
+
errors << "You did not provide a valid '#{k}' value."
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
self.class.validated_opts.each do |opt|
|
181
|
+
send VALIDATORS[opt]
|
182
|
+
end if self.class.validated_opts
|
183
|
+
|
184
|
+
if errors.each { |e| ui.error(e) }.any?
|
185
|
+
exit 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def validate_env
|
190
|
+
end
|
191
|
+
|
192
|
+
def validate_domain
|
193
|
+
end
|
194
|
+
|
195
|
+
def validate_region
|
196
|
+
end
|
197
|
+
|
198
|
+
def validate_force_deploy
|
199
|
+
end
|
200
|
+
|
201
|
+
def validate_color
|
202
|
+
unless @color
|
203
|
+
errors << "You must provide a cluster_tag with the -t option"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def validate_prod
|
208
|
+
end
|
209
|
+
|
210
|
+
OPTS = {
|
211
|
+
:aws_access_key_id => {
|
212
|
+
:short => "-A ID",
|
213
|
+
:long => "--aws-access-key-id KEY",
|
214
|
+
:description => "Your AWS Access Key ID",
|
215
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key }
|
216
|
+
},
|
217
|
+
:aws_secret_access_key => {
|
218
|
+
:short => "-K SECRET",
|
219
|
+
:long => "--aws-secret-access-key SECRET",
|
220
|
+
:description => "Your AWS API Secret Access Key",
|
221
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key }
|
222
|
+
},
|
223
|
+
:cluster_tag => {
|
224
|
+
:short => "-t TAG",
|
225
|
+
:long => "--cluster-tag TAG",
|
226
|
+
:description => "Tag that identifies this node as part of the <TAG> cluster"
|
227
|
+
},
|
228
|
+
:environment => {
|
229
|
+
:short => "-E CHEF_ENV",
|
230
|
+
:long => "--environment CHEF_ENV",
|
231
|
+
:description => "Chef environment"
|
232
|
+
},
|
233
|
+
:region => {
|
234
|
+
:long => "--region REGION",
|
235
|
+
:short => '-R REGION',
|
236
|
+
:description => "Your AWS region",
|
237
|
+
:default => ENV['AWS_REGION'],
|
238
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:region] = key }
|
239
|
+
},
|
240
|
+
:encrypted_data_bag_secret => {
|
241
|
+
:short => "-B FILE",
|
242
|
+
:long => "--encrypted_data_bag_secret FILE",
|
243
|
+
:description => "Path to the secret key to unlock encrypted chef data bags",
|
244
|
+
:default => ENV['DATABAG_KEY_PATH']
|
245
|
+
},
|
246
|
+
:aws_ssh_key_id => {
|
247
|
+
:short => "-S KEY",
|
248
|
+
:long => "--aws-ssh-key KEY",
|
249
|
+
:description => "AWS EC2 SSH Key Pair Name",
|
250
|
+
:default => ENV["aws_ssh_key"]
|
251
|
+
},
|
252
|
+
:base_domain => {
|
253
|
+
:long => "--base-domain DOMAIN",
|
254
|
+
:description => "The domain to be used for this node.",
|
255
|
+
:default => ENV["default_base_domain"] || ""
|
256
|
+
},
|
257
|
+
:wait_for_it => {
|
258
|
+
:short => "-W",
|
259
|
+
:long => "--wait-for-it",
|
260
|
+
:description => "Wait for EC2 to return extended details about the host and register DNS",
|
261
|
+
:boolean => true,
|
262
|
+
:default => false
|
263
|
+
},
|
264
|
+
:prod => {
|
265
|
+
:long => "--prod",
|
266
|
+
:description => "If the environment for your command is production, you must also pass this parameter. This is to make it slightly harder to do something unintentionally to production."
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
VALIDATORS = {
|
271
|
+
:environment => :validate_env,
|
272
|
+
:base_domain => :validate_domain,
|
273
|
+
:cluster_tag => :validate_color,
|
274
|
+
:prod => :validate_prod,
|
275
|
+
:force_deploy => :validate_force_deploy,
|
276
|
+
:region => :validate_region
|
277
|
+
}
|
278
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chef::Knife::InstanceCreate do
|
4
|
+
let(:internal_domain) { 'internal.com.' }
|
5
|
+
|
6
|
+
before do
|
7
|
+
described_class::load_deps
|
8
|
+
@instance = described_class.new
|
9
|
+
@instance.merge_configs
|
10
|
+
@instance.internal_domain = internal_domain
|
11
|
+
#TODO: refactor
|
12
|
+
@instance.class.stub(:AWS_REGIONS).and_return(['some_region'])
|
13
|
+
@instance.config[:environment] = "development"
|
14
|
+
@instance.config[:cluster_tag] = "purplish"
|
15
|
+
@instance.config[:base_domain] = "example.com"
|
16
|
+
@instance.config[:hostname] = 'd999'
|
17
|
+
|
18
|
+
@compute = double
|
19
|
+
@dns = double
|
20
|
+
@zone = double
|
21
|
+
@records = double
|
22
|
+
@record = double
|
23
|
+
@servers = double
|
24
|
+
@server = double
|
25
|
+
|
26
|
+
@server_attribs = {
|
27
|
+
:id => 'i-12345',
|
28
|
+
:flavor_id => 'vanilla',
|
29
|
+
:image_id => 'ami-12345',
|
30
|
+
:availability_zone => 'available',
|
31
|
+
:key_name => 'my_ssh_key',
|
32
|
+
:groups => ['group1', 'group2'],
|
33
|
+
:dns_name => 'dns.name.com',
|
34
|
+
:ip_address => '1.1.1.1',
|
35
|
+
:private_dns_name => 'ip-1-1-1-1.ec2.internal',
|
36
|
+
:private_ip_address => '1.1.1.1',
|
37
|
+
:public_ip_address => '8.8.8.8',
|
38
|
+
:root_device_type => 'instance',
|
39
|
+
:environment => 'development',
|
40
|
+
:hostname => 'd999'
|
41
|
+
}
|
42
|
+
|
43
|
+
@server_attribs.each_pair do |attrib, value|
|
44
|
+
@server.stub(attrib).and_return(value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "run" do
|
49
|
+
before do
|
50
|
+
Fog::Compute::AWS.stub(:new).and_return(@compute)
|
51
|
+
@compute.stub(:servers)
|
52
|
+
@compute.stub(:images).and_return(@images)
|
53
|
+
@zone.stub(:domain).and_return(internal_domain)
|
54
|
+
@instance.stub(:puts)
|
55
|
+
@instance.ui.stub(:error)
|
56
|
+
@instance.stub(:get_user_data)
|
57
|
+
@instance.stub(:ami).and_return(double)
|
58
|
+
Chef::Config[:knife][:hostname] = @server.hostname
|
59
|
+
@instance.stub(:find_ec2).and_return([])
|
60
|
+
@instance.stub(:find_item).and_return([])
|
61
|
+
@instance.stub(:find_r53).and_return([])
|
62
|
+
end
|
63
|
+
|
64
|
+
it "creates an EC2 instance and bootstraps it" do
|
65
|
+
@compute.should_receive(:servers).and_return(@servers)
|
66
|
+
@servers.should_receive(:create).and_return(@server)
|
67
|
+
@instance.run
|
68
|
+
end
|
69
|
+
|
70
|
+
it "waits for aws response and registers host with route 53" do
|
71
|
+
@compute.should_receive(:servers).and_return(@servers)
|
72
|
+
@servers.should_receive(:create).and_return(@server)
|
73
|
+
@instance.config[:wait_for_it] = true
|
74
|
+
@records.should_receive(:create).and_return(true)
|
75
|
+
@zone.should_receive(:records).and_return(@records)
|
76
|
+
@dns.should_receive(:zones).at_least(:once).and_return([@zone])
|
77
|
+
Fog::DNS::AWS.should_receive(:new).and_return(@dns)
|
78
|
+
@server.should_receive(:wait_for).and_return(true)
|
79
|
+
@instance.run
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#validate!" do
|
83
|
+
context "when there is ec2 host already" do
|
84
|
+
it "should exit with errors" do
|
85
|
+
@instance.should_receive(:find_ec2).and_return([double])
|
86
|
+
@instance.setup_config
|
87
|
+
@instance.config[:environment] = "development"
|
88
|
+
@instance.hostname = 'd999'
|
89
|
+
expect { @instance.validate! }.to raise_error SystemExit
|
90
|
+
@instance.errors.should include "d999 in RSpec::Mocks::Mock already exists. Delete first."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ZestKnife do
|
4
|
+
subject { described_class.new }
|
5
|
+
|
6
|
+
before do
|
7
|
+
subject.stub(:domain).and_return('internal.com.')
|
8
|
+
subject.class.stub(:AWS_REGIONS).and_return(['some_region'])
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#fqdn" do
|
12
|
+
it { subject.fqdn('').should == '' }
|
13
|
+
it { subject.fqdn(nil).should == '' }
|
14
|
+
it "provides a valid fqdn" do
|
15
|
+
fqdn = subject.fqdn('d999')
|
16
|
+
fqdn.should == 'd999.internal.com.'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#zone" do
|
21
|
+
let(:our_zone) { double('our dns zone', :domain => "internal.com.") }
|
22
|
+
let(:some_other_zone) { double('other dns zone', :domain => "zambocom.net.") }
|
23
|
+
|
24
|
+
before(:each) do
|
25
|
+
aws_double = double
|
26
|
+
Zest::AWS.stub(:new).and_return(aws_double)
|
27
|
+
dns_double = double
|
28
|
+
aws_double.stub(:dns).and_return(dns_double)
|
29
|
+
dns_double.stub(:zones).and_return([our_zone, some_other_zone])
|
30
|
+
end
|
31
|
+
|
32
|
+
its(:zone) { should == our_zone }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#domain" do
|
36
|
+
its(:domain) { should match(/[a-z]+\.[a-z]+\./) }
|
37
|
+
it { subject.domain[-1].should == '.' }
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#errors?" do
|
41
|
+
it "should be true when there are any errors" do
|
42
|
+
subject.errors << "Hello"
|
43
|
+
subject.errors?.should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be false when there are no errors" do
|
47
|
+
subject.errors?.should be_false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#setup_config" do
|
52
|
+
after { Chef::Config[:knife].delete :foo }
|
53
|
+
|
54
|
+
it "should find env var and add to Chef::Config" do
|
55
|
+
ENV["FOO"] = "bar"
|
56
|
+
subject.setup_config(['FOO'])
|
57
|
+
Chef::Config[:knife]['FOO'].should == "bar"
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should prefer Chef::Config[:knife] options over ENV options" do
|
61
|
+
ENV["foo"] = "bar"
|
62
|
+
subject.setup_config(["foo"])
|
63
|
+
Chef::Config[:knife]["foo"].should == "bar"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#validate!" do
|
68
|
+
before do
|
69
|
+
subject.ui.stub(:error)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should add validation errors to error list" do
|
73
|
+
lambda { subject.validate!([:foo]) }.should raise_error SystemExit
|
74
|
+
subject.errors.should have(1).error
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should report existing errors" do
|
78
|
+
subject.errors << "Hello"
|
79
|
+
lambda { subject.validate!([]) }.should raise_error SystemExit
|
80
|
+
subject.errors.should have(1).error
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#errors" do
|
85
|
+
it "should not be assignable" do
|
86
|
+
lambda { subject.errors = ['some-new-errors'] }.should raise_error
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should be appendable" do
|
90
|
+
subject.errors.should respond_to :<<
|
91
|
+
subject.errors << "Hello"
|
92
|
+
subject.errors[0].should == "Hello"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#find_r53" do
|
97
|
+
let(:r53_cname_record) do
|
98
|
+
double('Fog::DNS::AWS::Record',
|
99
|
+
:class => 'Fog::DNS::AWS::Record',
|
100
|
+
:name => "d999.internal.com.",
|
101
|
+
:type => "CNAME",
|
102
|
+
:value => "127-1-1-1.compute-1.amazonaws.com")
|
103
|
+
end
|
104
|
+
|
105
|
+
before do
|
106
|
+
Fog::DNS.stub(:new) { double('fog dns client',
|
107
|
+
:zones => [double('fake dns zone',
|
108
|
+
:records => r53_records,
|
109
|
+
:domain => "internal.com.")])}
|
110
|
+
end
|
111
|
+
|
112
|
+
context "instance exists" do
|
113
|
+
let(:r53_records) { [r53_cname_record] }
|
114
|
+
it { subject.find_r53('d999').should == [ r53_cname_record ] }
|
115
|
+
end
|
116
|
+
|
117
|
+
context "there is no such instance" do
|
118
|
+
let(:r53_records) { double('Fake records', :select => [], :get => nil) }
|
119
|
+
it { subject.find_r53('d999').should == [] }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#find_ec2" do
|
124
|
+
let(:ec2_servers) { [ec2_instance] }
|
125
|
+
let(:ec2_instance) do
|
126
|
+
double('Fog::Compute::AWS::Server',
|
127
|
+
:class => 'Fog::Compute::AWS::Server',
|
128
|
+
:id => 'my_ec2_instance',
|
129
|
+
:tags => {'Name' => 'd999'})
|
130
|
+
end
|
131
|
+
|
132
|
+
before do
|
133
|
+
Fog::Compute.stub(:new) { double('fog compute client', :servers => ec2_servers) }
|
134
|
+
end
|
135
|
+
|
136
|
+
context "instance exists" do
|
137
|
+
let(:ec2_servers) { [ec2_instance] }
|
138
|
+
it { subject.find_ec2('d999').should == [ ec2_instance ] }
|
139
|
+
end
|
140
|
+
|
141
|
+
context "there is no such instance" do
|
142
|
+
let(:ec2_servers) { [] }
|
143
|
+
it { subject.find_ec2('d999').should == [] }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#find_item" do
|
148
|
+
let(:client) { double }
|
149
|
+
|
150
|
+
context "when class can be loaded" do
|
151
|
+
let(:remote_resource) { double }
|
152
|
+
before { client.should_receive(:load).with('my-name').and_return remote_resource }
|
153
|
+
it { subject.find_item(client, 'my-name').should == [remote_resource] }
|
154
|
+
end
|
155
|
+
|
156
|
+
context "when class cannot be loaded" do
|
157
|
+
let(:exception) { Net::HTTPServerException.new "example.com", 1234 }
|
158
|
+
before { client.should_receive(:load).with('my-name').and_raise exception }
|
159
|
+
it { subject.find_item(client, 'my-name').should == [] }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#generate_hostname" do
|
164
|
+
before { subject.instance_variable_set :@base_domain, 'example.com' }
|
165
|
+
|
166
|
+
it "should generate a valid hostname" do
|
167
|
+
subject.should_receive(:check_services).exactly(2).times.and_return([])
|
168
|
+
subject.validate_hostname subject.generate_hostname("production")
|
169
|
+
subject.errors?.should be_false
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should fail if the hostname is taken" do
|
173
|
+
subject.should_receive(:check_services).exactly(5).times.and_return(["fake_error"])
|
174
|
+
subject.generate_hostname "production"
|
175
|
+
subject.errors?.should be_true
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#validate_hostname" do
|
180
|
+
let(:hostname) { 'd123' }
|
181
|
+
before { subject.should_receive(:check_services).and_return(existing_services) }
|
182
|
+
|
183
|
+
context "there is no such a host" do
|
184
|
+
let(:existing_services) { [] }
|
185
|
+
|
186
|
+
it "is valid" do
|
187
|
+
subject.validate_hostname hostname
|
188
|
+
subject.errors?.should be_false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "when the same host already exists" do
|
193
|
+
let(:existing_services) { [hostname] }
|
194
|
+
|
195
|
+
it "is not valid" do
|
196
|
+
subject.validate_hostname hostname
|
197
|
+
subject.errors?.should be_true
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe '#random_hostname' do
|
203
|
+
context 'environment is production' do
|
204
|
+
let(:env) { 'production' }
|
205
|
+
before { subject.stub(:base_domain).and_return('example.com') }
|
206
|
+
|
207
|
+
context 'external domain is example.com' do
|
208
|
+
it { subject.random_hostname(env).should =~ /^ep\d\d\d$/ }
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$:.unshift File.expand_path '../lib', File.dirname(__FILE__)
|
2
|
+
ENV['aws_access_key_id'] = '12345'
|
3
|
+
ENV['aws_secret_access_key'] = '12345'
|
4
|
+
ENV['aws_ssh_key'] = 'some_key'
|
5
|
+
ENV['DATABAG_KEY_PATH'] = File.expand_path 'support/databag.key', File.dirname(__FILE__)
|
6
|
+
|
7
|
+
Bundler.require :default, :development
|
8
|
+
require_all 'lib/chef'
|
9
|
+
require_all 'lib/knife-instance'
|
10
|
+
require 'fog'
|
11
|
+
|
12
|
+
Fog.mock!
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.after(:each) do
|
16
|
+
Chef::Config[:environment] = nil
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: knife-instance
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alexander Tamoykin
|
9
|
+
- Val Brodsky
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-12-17 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: chef
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.10.24
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 0.10.24
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: fog
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 1.9.0
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.9.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rspec
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: require_all
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
type: :development
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
description: Manage EC2 instances with Chef from the command line
|
96
|
+
email:
|
97
|
+
- at@zestfinance.com
|
98
|
+
- vlb@zestfinance.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- .gitignore
|
104
|
+
- .travis.yml
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- knife-instance.gemspec
|
110
|
+
- lib/chef/knife/instance_create.rb
|
111
|
+
- lib/knife-instance.rb
|
112
|
+
- lib/knife-instance/aws.rb
|
113
|
+
- lib/knife-instance/bootstrap_generator.rb
|
114
|
+
- lib/knife-instance/templates/boot.sh.erb
|
115
|
+
- lib/knife-instance/version.rb
|
116
|
+
- lib/knife-instance/zestknife.rb
|
117
|
+
- spec/lib/chef/knife/instance_create_spec.rb
|
118
|
+
- spec/lib/knife-instance/zestknife_spec.rb
|
119
|
+
- spec/spec_helper.rb
|
120
|
+
- spec/support/databag.key
|
121
|
+
homepage: https://github.com/ZestFinance/knife-instance
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
segments:
|
135
|
+
- 0
|
136
|
+
hash: -3631599552630895710
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
segments:
|
144
|
+
- 0
|
145
|
+
hash: -3631599552630895710
|
146
|
+
requirements: []
|
147
|
+
rubyforge_project:
|
148
|
+
rubygems_version: 1.8.23
|
149
|
+
signing_key:
|
150
|
+
specification_version: 3
|
151
|
+
summary: Manage EC2 instances with Chef from the command line
|
152
|
+
test_files:
|
153
|
+
- spec/lib/chef/knife/instance_create_spec.rb
|
154
|
+
- spec/lib/knife-instance/zestknife_spec.rb
|
155
|
+
- spec/spec_helper.rb
|
156
|
+
- spec/support/databag.key
|