knife-instance 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Code Climate](https://codeclimate.com/github/ZestFinance/knife-instance.png)](https://codeclimate.com/github/ZestFinance/knife-instance) [![Build Status](https://travis-ci.org/ZestFinance/knife-instance.png)](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
|