knife-solo 0.0.15 → 0.1.0.pre1
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/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
|
+
|