knife-solo 0.0.15 → 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +216 -0
- data/LICENSE +7 -0
- data/README.rdoc +127 -0
- data/Rakefile +31 -0
- data/lib/chef/knife/cook.rb +4 -131
- data/lib/chef/knife/kitchen.rb +4 -49
- data/lib/chef/knife/prepare.rb +4 -50
- data/lib/chef/knife/solo_bootstrap.rb +51 -0
- data/lib/chef/knife/solo_clean.rb +25 -0
- data/lib/chef/knife/solo_cook.rb +137 -0
- data/lib/chef/knife/solo_init.rb +59 -0
- data/lib/chef/knife/solo_prepare.rb +56 -0
- data/lib/chef/knife/wash_up.rb +4 -15
- data/lib/knife-solo/bootstraps.rb +1 -1
- data/lib/knife-solo/bootstraps/linux.rb +6 -4
- data/lib/knife-solo/bootstraps/sun_os.rb +9 -0
- data/lib/knife-solo/deprecated_command.rb +19 -0
- data/lib/knife-solo/info.rb +1 -1
- data/lib/knife-solo/kitchen_command.rb +5 -10
- data/lib/knife-solo/node_config_command.rb +17 -3
- data/lib/knife-solo/ssh_command.rb +3 -3
- data/test/bootstraps_test.rb +90 -0
- data/test/deprecated_command_test.rb +46 -0
- data/test/integration/cases/apache2_bootstrap.rb +15 -0
- data/test/integration/cases/apache2_cook.rb +28 -0
- data/test/integration/cases/empty_cook.rb +8 -0
- data/test/integration/cases/encrypted_data_bag.rb +27 -0
- data/test/integration/centos5_6_test.rb +19 -0
- data/test/integration/gentoo2011_test.rb +18 -0
- data/test/integration/omnios_r151004_test.rb +14 -0
- data/test/integration/scientific_linux_63_test.rb +15 -0
- data/test/integration/sles_11_test.rb +14 -0
- data/test/integration/ubuntu10_04_test.rb +15 -0
- data/test/integration/ubuntu12_04_bootstrap_test.rb +17 -0
- data/test/integration/ubuntu12_04_test.rb +15 -0
- data/test/integration_helper.rb +16 -0
- data/test/kitchen_command_test.rb +31 -0
- data/test/minitest/parallel.rb +41 -0
- data/test/node_config_command_test.rb +101 -0
- data/test/solo_bootstrap_test.rb +32 -0
- data/test/solo_clean_test.rb +12 -0
- data/test/solo_cook_test.rb +67 -0
- data/test/solo_init_test.rb +40 -0
- data/test/solo_prepare_test.rb +50 -0
- data/test/ssh_command_test.rb +100 -0
- data/test/support/config.yml.example +4 -0
- data/test/support/data_bag_key +1 -0
- data/test/support/ec2_runner.rb +122 -0
- data/test/support/integration_test.rb +94 -0
- data/test/support/issue_files/gentoo2011 +3 -0
- data/test/support/issue_files/sles11-sp1 +4 -0
- data/test/support/issue_files/ubuntu +2 -0
- data/test/support/kitchen_helper.rb +22 -0
- data/test/support/loggable.rb +18 -0
- data/test/support/secret_cookbook/metadata.rb +5 -0
- data/test/support/secret_cookbook/recipes/default.rb +8 -0
- data/test/support/ssh_config +3 -0
- data/test/support/test_case.rb +24 -0
- data/test/support/validation_helper.rb +39 -0
- data/test/test_helper.rb +7 -0
- metadata +99 -11
- data/lib/knife-solo/knife_solo_error.rb +0 -3
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'support/kitchen_helper'
|
3
|
+
require 'support/validation_helper'
|
4
|
+
|
5
|
+
require 'chef/knife/solo_prepare'
|
6
|
+
|
7
|
+
class SoloPrepareTest < TestCase
|
8
|
+
include KitchenHelper
|
9
|
+
include ValidationHelper::ValidationTests
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@host = 'someuser@somehost.domain.com'
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_will_specify_omnibus_version
|
16
|
+
in_kitchen do
|
17
|
+
run_command = command(@host, "--omnibus-version", "'0.10.8-3'")
|
18
|
+
assert_match "0.10.8-3", run_command.config[:omnibus_version]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_run_raises_if_operating_system_is_not_supported
|
23
|
+
in_kitchen do
|
24
|
+
run_command = command(@host)
|
25
|
+
run_command.stubs(:required_files_present?).returns(true)
|
26
|
+
run_command.stubs(:operating_system).returns('MythicalOS')
|
27
|
+
assert_raises KnifeSolo::Bootstraps::OperatingSystemNotImplementedError do
|
28
|
+
run_command.run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_run_calls_bootstrap
|
34
|
+
in_kitchen do
|
35
|
+
run_command = command(@host)
|
36
|
+
bootstrap_instance = mock('mock OS bootstrap instance')
|
37
|
+
run_command.stubs(:required_files_present?).returns(true)
|
38
|
+
run_command.stubs(:operating_system).returns('MythicalOS')
|
39
|
+
run_command.stubs(:bootstrap).returns(bootstrap_instance)
|
40
|
+
|
41
|
+
bootstrap_instance.expects(:bootstrap!)
|
42
|
+
|
43
|
+
run_command.run
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def command(*args)
|
48
|
+
knife_command(Chef::Knife::SoloPrepare, *args)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'knife-solo/ssh_command'
|
4
|
+
require 'chef/knife'
|
5
|
+
require 'net/ssh'
|
6
|
+
|
7
|
+
class DummySshCommand < Chef::Knife
|
8
|
+
include KnifeSolo::SshCommand
|
9
|
+
end
|
10
|
+
|
11
|
+
class SshCommandTest < TestCase
|
12
|
+
def test_separates_user_and_host
|
13
|
+
assert_equal "ubuntu", command("ubuntu@10.0.0.1").user
|
14
|
+
assert_equal "10.0.0.1", command("ubuntu@10.0.0.1").host
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_defaults_to_system_user
|
18
|
+
ENV['USER'] = "test"
|
19
|
+
assert_equal "test", command("10.0.0.1").user
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_host_regex_rejects_invalid_hostnames
|
23
|
+
%w[@name @@name.com name@ name@@ joe@@example.com joe@name@example.com].each do |invalid|
|
24
|
+
cmd = command(invalid)
|
25
|
+
refute cmd.first_cli_arg_is_a_hostname?, "#{invalid} should have been rejected"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_host_regex_accpets_valid_hostnames
|
30
|
+
%w[name.com name joe@example.com].each do |valid|
|
31
|
+
cmd = command(valid)
|
32
|
+
assert cmd.first_cli_arg_is_a_hostname?, "#{valid} should have been accepted"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_prompts_for_password_if_not_provided
|
37
|
+
cmd = command("10.0.0.1")
|
38
|
+
cmd.ui.expects(:ask).returns("testpassword")
|
39
|
+
assert_equal "testpassword", cmd.password
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_falls_back_to_password_authentication_after_keys
|
43
|
+
cmd = command("10.0.0.1", "--ssh-password=test")
|
44
|
+
cmd.expects(:try_connection).raises(Net::SSH::AuthenticationFailed)
|
45
|
+
cmd.detect_authentication_method
|
46
|
+
assert_equal "test", cmd.connection_options[:password]
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_uses_default_keys_if_conncetion_succeeds
|
50
|
+
cmd = command("10.0.0.1")
|
51
|
+
assert_equal({}, cmd.connection_options)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_uses_ssh_config_if_matched
|
55
|
+
ssh_config = Pathname.new(__FILE__).dirname.join('support', 'ssh_config')
|
56
|
+
cmd = command("10.0.0.1", "--ssh-config-file=#{ssh_config}")
|
57
|
+
|
58
|
+
assert_equal "bob", cmd.connection_options[:user]
|
59
|
+
assert_equal "id_rsa_bob", cmd.connection_options[:keys].first
|
60
|
+
assert_equal "bob", cmd.user
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_handles_port_specification
|
64
|
+
cmd = command("10.0.0.1", "-p", "2222")
|
65
|
+
assert_equal "2222", cmd.connection_options[:port]
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_handle_startup_script
|
69
|
+
cmd = command("10.0.0.1", "--startup-script=~/.bashrc")
|
70
|
+
assert_equal "source ~/.bashrc && echo $TEST_PROP", cmd.processed_command("echo $TEST_PROP")
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_builds_cli_ssh_args
|
74
|
+
DummySshCommand.any_instance.stubs(:try_connection)
|
75
|
+
|
76
|
+
cmd = command("10.0.0.1")
|
77
|
+
assert_equal "#{ENV['USER']}@10.0.0.1", cmd.ssh_args
|
78
|
+
|
79
|
+
cmd = command("usertest@10.0.0.1", "--ssh-config-file=myconfig")
|
80
|
+
assert_equal "usertest@10.0.0.1 -F myconfig", cmd.ssh_args
|
81
|
+
|
82
|
+
cmd = command("usertest@10.0.0.1", "--ssh-identity=my_rsa")
|
83
|
+
assert_equal "usertest@10.0.0.1 -i my_rsa", cmd.ssh_args
|
84
|
+
|
85
|
+
cmd = command("usertest@10.0.0.1", "--ssh-port=222")
|
86
|
+
assert_equal "usertest@10.0.0.1 -p 222", cmd.ssh_args
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_barks_without_atleast_a_hostname
|
90
|
+
cmd = command
|
91
|
+
cmd.ui.expects(:err).with(regexp_matches(/hostname.*argument/))
|
92
|
+
$stdout.stubs(:puts)
|
93
|
+
assert_exits { cmd.validate_first_cli_arg_is_a_hostname! }
|
94
|
+
end
|
95
|
+
|
96
|
+
def command(*args)
|
97
|
+
Net::SSH::Config.stubs(:default_files)
|
98
|
+
knife_command(DummySshCommand, *args)
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
dZkd2eosIBc+4dWfDjFgwyc+sj0XlCBRR5/nVbA23sA4RAlg0pd+RIh4GiPpwp3IGPslJhapNX36bkPbjXjsyGzxKt2kZth9Z5Ucw9tC8jEnWtfxs66xABmZQvE/WGEdBZ3UDEbBaEBSjznVpHlXI//oLd1bU83nyV21i4OYW6eDc7h47mdbu59tvY8q4Doks8nwmoTMbTqzWkD2CRxx2Z5x2kqBb+HAXRI6KZK7vqvhAGYcm1d/vLkCGp/2ntDvRHEGmjbtJHDVD+0qJIf7w5W9jumhVkFTOPBym7P5i3OkAbO3RSsKHb2iRUlAw4Sld21pZBtBc5sDXJ/AUVzV+c/rsmvZDf01TWg9RoIN82928qnlCfJohmJGHTVt0Q5rf/DYOtk7DBCSsOkOChYdX8pRJG2+A5ACvALjXmd/5qUYUGuHe9m7PzNHV+37GOnlHBYqkZ5EU0LGjLMqq0Tgs/KzWpeotRK/VPNwmCrYCMwYwJtRKi1oMexUwDLrBcTilivp+62e9HFhSg0BRV0JlJcPgYRWqJ2RyBkrmOXv7lD10tPVnMzbwi+5yW131e9JbvgdBreJ2Ph314XyxjQvFNkQ/vny6iiisJEquwVBJTnqW+HdjIH07MeqkdaVytdMLlTSdgqGr/rIf1ZNS37jeq0AT8Y8YeihQNsnx559hu4=
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# A custom runner that serves as a common point for EC2 control
|
4
|
+
class EC2Runner < MiniTest::Unit
|
5
|
+
include Loggable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
create_key_pair
|
10
|
+
end
|
11
|
+
|
12
|
+
def skip_destroy?
|
13
|
+
ENV['SKIP_DESTROY']
|
14
|
+
end
|
15
|
+
|
16
|
+
def user
|
17
|
+
ENV['USER']
|
18
|
+
end
|
19
|
+
|
20
|
+
# Gets a server for the given tests
|
21
|
+
# See http://bit.ly/MJRpfQ for information on what filters can be specified.
|
22
|
+
def get_server(test)
|
23
|
+
server = compute.servers.all("tag-key" => "name",
|
24
|
+
"tag-value" => test.server_name,
|
25
|
+
"instance-state-name" => "running").first
|
26
|
+
if server
|
27
|
+
logger.info "Reusing active server tagged #{test.server_name}"
|
28
|
+
else
|
29
|
+
logger.info "Starting server for #{test.class}..."
|
30
|
+
server = compute.servers.create(:tags => {
|
31
|
+
:name => test.server_name,
|
32
|
+
:knife_solo_integration_user => ENV['USER']
|
33
|
+
},
|
34
|
+
:image_id => test.image_id,
|
35
|
+
:flavor_id => test.flavor_id,
|
36
|
+
:key_name => key_name)
|
37
|
+
end
|
38
|
+
server.wait_for { ready? }
|
39
|
+
logger.info "#{test.class} server (#{server.dns_name}) reported ready, trying to connect to ssh..."
|
40
|
+
server.wait_for do
|
41
|
+
`nc #{public_ip_address} 22 -w 1 -q 0 </dev/null`
|
42
|
+
$?.success?
|
43
|
+
end
|
44
|
+
logger.info "Sleeping 10s to avoid Net::SSH locking up by connecting too early..."
|
45
|
+
logger.info " (if you know a better way, please send me a note at https://github.com/matschaffer/knife-solo)"
|
46
|
+
# These may have better ways:
|
47
|
+
# http://rubydoc.info/gems/fog/Fog/Compute/AWS/Server:setup
|
48
|
+
# http://rubydoc.info/gems/knife-ec2/Chef/Knife/Ec2ServerCreate:tcp_test_ssh
|
49
|
+
sleep 10
|
50
|
+
server
|
51
|
+
end
|
52
|
+
|
53
|
+
# Adds a knife_solo_prepared tag to the server so we can know not to re-prepare it
|
54
|
+
def tag_as_prepared(server)
|
55
|
+
compute.tags.create(resource_id: server.identity,
|
56
|
+
key: :knife_solo_prepared,
|
57
|
+
value: true)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Cleans up all the servers tagged as knife solo servers for this user.
|
61
|
+
# Specify SKIP_DESTROY environment variable to skip this step and leave servers
|
62
|
+
# running for inspection or reuse.
|
63
|
+
def run_ec2_cleanup
|
64
|
+
servers = compute.servers.all("tag-key" => "knife_solo_integration_user",
|
65
|
+
"tag-value" => user,
|
66
|
+
"instance-state-name" => "running")
|
67
|
+
if skip_destroy?
|
68
|
+
puts "\nSKIP_DESTROY specified, leaving #{servers.size} instances running"
|
69
|
+
else
|
70
|
+
puts <<-TXT.gsub(/^\s*/, '')
|
71
|
+
===
|
72
|
+
About to terminate the following instances. Please cancel (Control-C)
|
73
|
+
NOW if you want to leave them running. Use SKIP_DESTROY=true to
|
74
|
+
skip this step.
|
75
|
+
TXT
|
76
|
+
servers.each do |server|
|
77
|
+
puts " * #{server.id}"
|
78
|
+
end
|
79
|
+
sleep 20
|
80
|
+
servers.each do |server|
|
81
|
+
logger.info "Destroying #{server.public_ip_address}..."
|
82
|
+
server.destroy
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Attempts to create the keypair used for integration testing
|
88
|
+
# unless the key file is already present locally.
|
89
|
+
def create_key_pair
|
90
|
+
return if key_file.exist?
|
91
|
+
begin
|
92
|
+
key = compute.key_pairs.create(:name => key_name)
|
93
|
+
key.write(key_file)
|
94
|
+
rescue Fog::Compute::AWS::Error => e
|
95
|
+
raise "Unable to create KeyPair 'knife-solo', please create the keypair and save it to #{key_file}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def key_name
|
100
|
+
config['aws']['key_name']
|
101
|
+
end
|
102
|
+
|
103
|
+
def key_file
|
104
|
+
$base_dir.join('support', "#{key_name}.pem")
|
105
|
+
end
|
106
|
+
|
107
|
+
def config_file
|
108
|
+
$base_dir.join('support', 'config.yml')
|
109
|
+
end
|
110
|
+
|
111
|
+
def config
|
112
|
+
@config ||= YAML.load_file(config_file)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Provides a Fog compute resource associated with the
|
116
|
+
# AWS account credentials provided in test/support/config.yml
|
117
|
+
def compute
|
118
|
+
@compute ||= Fog::Compute.new({:provider => 'AWS',
|
119
|
+
:aws_access_key_id => config['aws']['access_key'],
|
120
|
+
:aws_secret_access_key => config['aws']['secret']})
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
require 'support/test_case'
|
5
|
+
|
6
|
+
case_pattern = $base_dir.join('integration', 'cases', '*.rb')
|
7
|
+
Dir[case_pattern].each do |use_case|
|
8
|
+
require use_case
|
9
|
+
end
|
10
|
+
|
11
|
+
# Base class for EC2 integration tests
|
12
|
+
class IntegrationTest < TestCase
|
13
|
+
include Loggable
|
14
|
+
|
15
|
+
# Returns a name for the current test's server
|
16
|
+
# that should be fairly unique.
|
17
|
+
def server_name
|
18
|
+
"knife_solo-#{image_id}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Shortcut to access the test runner
|
22
|
+
def runner
|
23
|
+
MiniTest::Unit.runner
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the server for this test, retrieved from the test runner
|
27
|
+
def server
|
28
|
+
return @server if @server
|
29
|
+
@server = runner.get_server(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
# The flavor to run this test on
|
33
|
+
def flavor_id
|
34
|
+
"m1.small"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sets up a kitchen directory to work in
|
38
|
+
def setup
|
39
|
+
@kitchen = $base_dir.join('support', 'kitchens', self.class.to_s)
|
40
|
+
FileUtils.remove_entry_secure(@kitchen, true)
|
41
|
+
@kitchen.dirname.mkpath
|
42
|
+
system "knife solo init #{@kitchen} >> #{log_file}"
|
43
|
+
@start_dir = Dir.pwd
|
44
|
+
Dir.chdir(@kitchen)
|
45
|
+
prepare_server
|
46
|
+
end
|
47
|
+
|
48
|
+
# Gets back to the start dir
|
49
|
+
def teardown
|
50
|
+
Dir.chdir(@start_dir)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Writes out the given node hash as a json file
|
54
|
+
def write_nodefile(node)
|
55
|
+
write_json_file("nodes/#{server.public_ip_address}.json", node)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Writes out an object to the given file as JSON
|
59
|
+
def write_json_file(file, data)
|
60
|
+
FileUtils.mkpath(File.dirname(file))
|
61
|
+
File.open(file, 'w') do |f|
|
62
|
+
f.print data.to_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Prepares the server unless it has already been marked as such
|
67
|
+
def prepare_server
|
68
|
+
return if server.tags["knife_solo_prepared"]
|
69
|
+
assert_subcommand prepare_command
|
70
|
+
runner.tag_as_prepared(server)
|
71
|
+
end
|
72
|
+
|
73
|
+
# The prepare command to use on this server
|
74
|
+
def prepare_command
|
75
|
+
"prepare"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Provides the path to the runner's key file
|
79
|
+
def key_file
|
80
|
+
runner.key_file
|
81
|
+
end
|
82
|
+
|
83
|
+
# The ssh-style connection string used to connect to the current node
|
84
|
+
def connection_string
|
85
|
+
"-i #{key_file} #{user}@#{server.public_ip_address}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Asserts that a prepare or cook command is successful
|
89
|
+
def assert_subcommand(subcommand)
|
90
|
+
system "knife solo #{subcommand} #{connection_string} -VV >> #{log_file}"
|
91
|
+
assert $?.success?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
require 'chef/knife/solo_init'
|
4
|
+
|
5
|
+
module KitchenHelper
|
6
|
+
|
7
|
+
def in_kitchen
|
8
|
+
outside_kitchen do
|
9
|
+
knife_command(Chef::Knife::SoloInit, ".").run
|
10
|
+
yield
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def outside_kitchen
|
15
|
+
Dir.mktmpdir do |dir|
|
16
|
+
Dir.chdir(dir) do
|
17
|
+
yield
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
# A module that provides logger and log_file attached to an integration log file
|
4
|
+
module Loggable
|
5
|
+
# The file that logs will go do, using the class name as a differentiator
|
6
|
+
def log_file
|
7
|
+
return @log_file if @log_file
|
8
|
+
@log_file = $base_dir.join('..', 'log', "#{self.class}-integration.log")
|
9
|
+
@log_file.dirname.mkpath
|
10
|
+
@log_file
|
11
|
+
end
|
12
|
+
|
13
|
+
# The logger object so you can say logger.info to log messages
|
14
|
+
def logger
|
15
|
+
@logger ||= Logger.new(log_file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|