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.
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