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.
Files changed (62) hide show
  1. data/CHANGELOG.md +216 -0
  2. data/LICENSE +7 -0
  3. data/README.rdoc +127 -0
  4. data/Rakefile +31 -0
  5. data/lib/chef/knife/cook.rb +4 -131
  6. data/lib/chef/knife/kitchen.rb +4 -49
  7. data/lib/chef/knife/prepare.rb +4 -50
  8. data/lib/chef/knife/solo_bootstrap.rb +51 -0
  9. data/lib/chef/knife/solo_clean.rb +25 -0
  10. data/lib/chef/knife/solo_cook.rb +137 -0
  11. data/lib/chef/knife/solo_init.rb +59 -0
  12. data/lib/chef/knife/solo_prepare.rb +56 -0
  13. data/lib/chef/knife/wash_up.rb +4 -15
  14. data/lib/knife-solo/bootstraps.rb +1 -1
  15. data/lib/knife-solo/bootstraps/linux.rb +6 -4
  16. data/lib/knife-solo/bootstraps/sun_os.rb +9 -0
  17. data/lib/knife-solo/deprecated_command.rb +19 -0
  18. data/lib/knife-solo/info.rb +1 -1
  19. data/lib/knife-solo/kitchen_command.rb +5 -10
  20. data/lib/knife-solo/node_config_command.rb +17 -3
  21. data/lib/knife-solo/ssh_command.rb +3 -3
  22. data/test/bootstraps_test.rb +90 -0
  23. data/test/deprecated_command_test.rb +46 -0
  24. data/test/integration/cases/apache2_bootstrap.rb +15 -0
  25. data/test/integration/cases/apache2_cook.rb +28 -0
  26. data/test/integration/cases/empty_cook.rb +8 -0
  27. data/test/integration/cases/encrypted_data_bag.rb +27 -0
  28. data/test/integration/centos5_6_test.rb +19 -0
  29. data/test/integration/gentoo2011_test.rb +18 -0
  30. data/test/integration/omnios_r151004_test.rb +14 -0
  31. data/test/integration/scientific_linux_63_test.rb +15 -0
  32. data/test/integration/sles_11_test.rb +14 -0
  33. data/test/integration/ubuntu10_04_test.rb +15 -0
  34. data/test/integration/ubuntu12_04_bootstrap_test.rb +17 -0
  35. data/test/integration/ubuntu12_04_test.rb +15 -0
  36. data/test/integration_helper.rb +16 -0
  37. data/test/kitchen_command_test.rb +31 -0
  38. data/test/minitest/parallel.rb +41 -0
  39. data/test/node_config_command_test.rb +101 -0
  40. data/test/solo_bootstrap_test.rb +32 -0
  41. data/test/solo_clean_test.rb +12 -0
  42. data/test/solo_cook_test.rb +67 -0
  43. data/test/solo_init_test.rb +40 -0
  44. data/test/solo_prepare_test.rb +50 -0
  45. data/test/ssh_command_test.rb +100 -0
  46. data/test/support/config.yml.example +4 -0
  47. data/test/support/data_bag_key +1 -0
  48. data/test/support/ec2_runner.rb +122 -0
  49. data/test/support/integration_test.rb +94 -0
  50. data/test/support/issue_files/gentoo2011 +3 -0
  51. data/test/support/issue_files/sles11-sp1 +4 -0
  52. data/test/support/issue_files/ubuntu +2 -0
  53. data/test/support/kitchen_helper.rb +22 -0
  54. data/test/support/loggable.rb +18 -0
  55. data/test/support/secret_cookbook/metadata.rb +5 -0
  56. data/test/support/secret_cookbook/recipes/default.rb +8 -0
  57. data/test/support/ssh_config +3 -0
  58. data/test/support/test_case.rb +24 -0
  59. data/test/support/validation_helper.rb +39 -0
  60. data/test/test_helper.rb +7 -0
  61. metadata +99 -11
  62. 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,4 @@
1
+ aws:
2
+ key_name: knife-solo
3
+ access_key: YOUR_ACCESS_KEY
4
+ secret: YOUR_SECRET
@@ -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,3 @@
1
+
2
+ This is \n.\O (\s \m \r) \t
3
+
@@ -0,0 +1,4 @@
1
+
2
+ Welcome to SUSE Linux Enterprise Server 11 SP1 (x86_64) - Kernel \r (\l).
3
+
4
+
@@ -0,0 +1,2 @@
1
+ Ubuntu 10.04.3 LTS \n \l
2
+
@@ -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
+
@@ -0,0 +1,5 @@
1
+ maintainer "Mat Schaffer"
2
+ maintainer_email "mat@schaffer.me"
3
+ license "MIT"
4
+ description "Spits out a file containing a secret data bag value"
5
+ version "0.0.1"
@@ -0,0 +1,8 @@
1
+ passwords = Chef::EncryptedDataBagItem.load("dev", "passwords")
2
+
3
+ file "/etc/admin_password" do
4
+ # This mode is a terrible idea for passwords
5
+ # but makes verification easier
6
+ mode 0644
7
+ content passwords["admin"]
8
+ end