minionizer 0.0.1 → 0.1.0
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.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +37 -15
- data/README.md +4 -0
- data/Rakefile +17 -0
- data/lib/core/file_injection.rb +11 -8
- data/lib/core/folder_creation.rb +12 -0
- data/lib/core/public_ssh_key_injection.rb +40 -0
- data/lib/core/task_template.rb +25 -0
- data/lib/core/user_creation.rb +18 -0
- data/lib/minionizer.rb +3 -0
- data/lib/minionizer/command_execution.rb +60 -0
- data/lib/minionizer/session.rb +29 -14
- data/lib/minionizer/version.rb +1 -1
- data/minionizer.gemspec +5 -4
- data/test/integration/core_library_test.rb +193 -0
- data/test/role_template.rb.erb +16 -0
- data/test/test_helper.rb +125 -32
- data/test/unit/lib/core/file_injection_test.rb +45 -12
- data/test/unit/lib/core/folder_creation_test.rb +58 -0
- data/test/unit/lib/core/public_ssh_key_injection_test.rb +46 -0
- data/test/unit/lib/core/task_template_test.rb +35 -0
- data/test/unit/lib/core/user_creation_test.rb +44 -0
- data/test/unit/lib/minionizer/command_execution_test.rb +85 -0
- data/test/unit/lib/minionizer/configuration_test.rb +4 -2
- data/test/unit/lib/minionizer/minion_test.rb +3 -3
- data/test/unit/lib/minionizer/minionization_test.rb +8 -8
- data/test/unit/lib/minionizer/role_template_test.rb +2 -2
- data/test/unit/lib/minionizer/session_test.rb +65 -20
- metadata +53 -20
- data/test/integration/acceptance_test.rb +0 -82
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Minionizer
|
4
|
+
class FolderCreationTest < MiniTest::Test
|
5
|
+
describe FolderCreation do
|
6
|
+
let(:session) { 'MockSession' }
|
7
|
+
let(:path) { '/foo/bar' }
|
8
|
+
let(:folder_creation) { FolderCreation.new(session, options) }
|
9
|
+
|
10
|
+
describe '#call' do
|
11
|
+
|
12
|
+
describe 'only path is provided' do
|
13
|
+
let(:options) {{ path: path }}
|
14
|
+
|
15
|
+
it 'sends the mkdir command' do
|
16
|
+
session.expects(:exec).with(%Q{mkdir --parents #{path}})
|
17
|
+
folder_creation.call
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'mode is provided' do
|
23
|
+
let(:mode) { '0700' }
|
24
|
+
let(:options) {{ path: path, mode: mode }}
|
25
|
+
|
26
|
+
it 'sets the folder permissions' do
|
27
|
+
session.expects(:exec).with(%Q{mkdir --parents #{path}})
|
28
|
+
session.expects(:exec).with(%Q{chmod #{mode} #{path}})
|
29
|
+
folder_creation.call
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'owner is provided' do
|
34
|
+
let(:ownername) { 'otherowner' }
|
35
|
+
let(:options) {{ path: path, owner: ownername }}
|
36
|
+
|
37
|
+
it 'sets the folder owner' do
|
38
|
+
session.expects(:exec).with(%Q{mkdir --parents #{path}})
|
39
|
+
session.expects(:exec).with(%Q{chown #{ownername} #{path}})
|
40
|
+
folder_creation.call
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'group is provided' do
|
45
|
+
let(:groupname) { 'othergroup' }
|
46
|
+
let(:options) {{ path: path, group: groupname }}
|
47
|
+
|
48
|
+
it 'sets the folder group' do
|
49
|
+
session.expects(:exec).with(%Q{mkdir --parents #{path}})
|
50
|
+
session.expects(:exec).with(%Q{chgrp #{groupname} #{path}})
|
51
|
+
folder_creation.call
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Minionizer
|
4
|
+
class PublicSshKeyInjectionTest < MiniTest::Test
|
5
|
+
|
6
|
+
describe PublicSshKeyInjection do
|
7
|
+
|
8
|
+
describe '#call' do
|
9
|
+
let(:temp_file_path) { '/tmp/foobar' }
|
10
|
+
let(:temp_file_pointer) { OpenStruct.new(path: temp_file_path) }
|
11
|
+
let(:file_injection) { 'MockFileInjection' }
|
12
|
+
let(:file_injection_creator) { 'MockFileInjectionCreator' }
|
13
|
+
let(:session) { 'MockSession' }
|
14
|
+
let(:username) { 'foouser' }
|
15
|
+
let(:key_injection_options) {{
|
16
|
+
target_username: username,
|
17
|
+
file_injection_creator: file_injection_creator
|
18
|
+
}}
|
19
|
+
let(:key_injection) { PublicSshKeyInjection.new(session, key_injection_options) }
|
20
|
+
let(:expected_file_injection_options) {[
|
21
|
+
session,
|
22
|
+
{
|
23
|
+
:source_path => temp_file_path,
|
24
|
+
:target_path => "~#{username}/.ssh/authorized_keys",
|
25
|
+
:owner => username,
|
26
|
+
:group => username
|
27
|
+
}
|
28
|
+
]}
|
29
|
+
|
30
|
+
before do
|
31
|
+
temp_file_pointer.expects(:unlink)
|
32
|
+
Tempfile.expects(:new).yields(temp_file_pointer).returns(temp_file_pointer)
|
33
|
+
write_file('data/public_keys/foobar.pubkey', 'foobar')
|
34
|
+
temp_file_pointer.expects(:puts).with('foobar')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'injects the file' do
|
38
|
+
file_injection_creator.expects(:new).with(*expected_file_injection_options).returns(file_injection)
|
39
|
+
file_injection.expects(:call)
|
40
|
+
|
41
|
+
key_injection.call
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Minionizer
|
4
|
+
class TaskTemplateTest < MiniTest::Test
|
5
|
+
describe TaskTemplate do
|
6
|
+
let(:session) { 'MockSession' }
|
7
|
+
let(:template) { TaskTemplate.new(session, :foo => 'bar') }
|
8
|
+
|
9
|
+
describe '#method_missing' do
|
10
|
+
|
11
|
+
it 'catches messages that match options' do
|
12
|
+
assert_equal('bar', template.foo)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'forwards unrecognized messages to super' do
|
16
|
+
assert_raises(NoMethodError) do
|
17
|
+
template.foobar
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#respond_to?' do
|
24
|
+
|
25
|
+
it 'recognizes passed in options as respondable methods' do
|
26
|
+
assert(template.respond_to?(:foo))
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'properly responds to unrecognized methods' do
|
30
|
+
refute(template.respond_to?(:foobar))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Minionizer
|
4
|
+
class UserCreationTest < MiniTest::Test
|
5
|
+
describe UserCreation do
|
6
|
+
let(:session) { 'MockSession' }
|
7
|
+
let(:full_name) { 'Test User' }
|
8
|
+
let(:username) { 'testuser' }
|
9
|
+
let(:options) {{ name: full_name, username: username }}
|
10
|
+
let(:user_creation) { UserCreation.new(session, options) }
|
11
|
+
|
12
|
+
describe '#call' do
|
13
|
+
|
14
|
+
describe 'user does not already exist' do
|
15
|
+
|
16
|
+
before do
|
17
|
+
session.stubs(:exec).with(%Q{id #{username}}).raises(CommandExecution::CommandError.new)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'creates the user' do
|
21
|
+
session.expects(:exec).with(%Q{adduser --disabled-password --gecos '#{full_name}' #{username}})
|
22
|
+
user_creation.call
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'user already exists' do
|
28
|
+
|
29
|
+
before do
|
30
|
+
session.stubs(:exec).with(%Q{id #{username}}).returns(true)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not create the user' do
|
34
|
+
session.expects(:exec).with(%Q{adduser --disabled-password --gecos '#{full_name}' #{username}}).never
|
35
|
+
user_creation.call
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Minionizer
|
4
|
+
class CommandExecutionTest < MiniTest::Test
|
5
|
+
|
6
|
+
describe CommandExecution do
|
7
|
+
let(:command) { 'foo --bar' }
|
8
|
+
let(:connection) { 'MockConnection' }
|
9
|
+
let(:execution) { CommandExecution.new(connection, command) }
|
10
|
+
|
11
|
+
describe '#call' do
|
12
|
+
let(:channel) { 'MockChannel' }
|
13
|
+
let(:stdout_data) { 'stdout' }
|
14
|
+
let(:stderr_data) { 'stderr' }
|
15
|
+
let(:exit_code) { 0 }
|
16
|
+
let(:exit_signal) { 'exit_signal' }
|
17
|
+
|
18
|
+
before do
|
19
|
+
connection.stubs(:open_channel).yields(channel)
|
20
|
+
channel.stubs(:on_data).yields(nil, stdout_data)
|
21
|
+
channel.stubs(:on_extended_data).yields(nil, stderr_data)
|
22
|
+
channel.
|
23
|
+
stubs(:on_request).
|
24
|
+
with('exit-status').
|
25
|
+
yields(nil, OpenStruct.new(:read_long => exit_code))
|
26
|
+
channel.
|
27
|
+
stubs(:on_request).
|
28
|
+
with('exit-signal').
|
29
|
+
yields(nil, OpenStruct.new(:read_long => exit_signal))
|
30
|
+
connection.stubs(:loop)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'command runs successfully' do
|
34
|
+
|
35
|
+
before do
|
36
|
+
channel.
|
37
|
+
expects(:exec).
|
38
|
+
with(command).
|
39
|
+
yields(channel, true)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'runs the command' do
|
43
|
+
execution.call
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'command fails to be invoked' do
|
49
|
+
|
50
|
+
before do
|
51
|
+
channel.
|
52
|
+
expects(:exec).
|
53
|
+
with(command).
|
54
|
+
yields(channel, false)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises InvocationError' do
|
58
|
+
assert_raises(CommandExecution::InvocationError) do
|
59
|
+
execution.call
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'command returns non-zero exit status' do
|
66
|
+
let(:exit_code) { 1 }
|
67
|
+
|
68
|
+
before do
|
69
|
+
channel.
|
70
|
+
expects(:exec).
|
71
|
+
with(command).
|
72
|
+
yields(channel, true)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'raises CommandError' do
|
76
|
+
assert_raises(CommandExecution::CommandError) do
|
77
|
+
execution.call
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -1,14 +1,16 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Minionizer
|
4
|
-
class ConfigurationTest < MiniTest::
|
4
|
+
class ConfigurationTest < MiniTest::Test
|
5
5
|
|
6
6
|
describe Configuration do
|
7
7
|
let(:config) { Configuration.instance }
|
8
|
+
|
8
9
|
let(:minions) {{ 'foo.bar.com' => { :ssh => { :username => 'foo', :password => 'bar' } } }}
|
9
10
|
|
10
11
|
before do
|
11
|
-
|
12
|
+
config.instance_variable_set('@minions', nil)
|
13
|
+
write_file('./config/minions.yml', minions.to_yaml)
|
12
14
|
end
|
13
15
|
|
14
16
|
it 'instantiates a configuration' do
|
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Minionizer
|
4
|
-
class MinionTest < MiniTest::
|
4
|
+
class MinionTest < MiniTest::Test
|
5
5
|
describe Minion do
|
6
6
|
let(:username) { 'foo' }
|
7
7
|
let(:password) { 'bar' }
|
8
8
|
let(:credentials) {{ 'username' => username, 'password' => password }}
|
9
9
|
let(:config) { Configuration.instance }
|
10
10
|
let(:fqdn) { 'foo.bar.com' }
|
11
|
-
let(:session_constructor) {
|
11
|
+
let(:session_constructor) { quacks_like(Session) }
|
12
12
|
let(:minion) { Minion.new(fqdn, config, session_constructor) }
|
13
13
|
let(:roles) { %w(foo bar) }
|
14
14
|
let (:minion_config) {{ fqdn => { 'roles' => roles , 'ssh' => credentials } }}
|
@@ -22,7 +22,7 @@ module Minionizer
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe '#session' do
|
25
|
-
let(:session) {
|
25
|
+
let(:session) { quacks_like_instance_of(Session) }
|
26
26
|
|
27
27
|
it 'creates a session' do
|
28
28
|
session_constructor.expects(:new).with(fqdn, credentials).returns(session)
|
@@ -1,28 +1,28 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Minionizer
|
4
|
-
class MinionizationTest < MiniTest::
|
4
|
+
class MinionizationTest < MiniTest::Test
|
5
5
|
|
6
6
|
describe Minionization do
|
7
7
|
let(:fqdn) { 'foo.bar.com' }
|
8
8
|
let(:config) { Configuration.instance }
|
9
9
|
let(:role_name) { 'web_server' }
|
10
10
|
let(:role_class) { get_dynamic_class(role_name) }
|
11
|
-
let(:minion) {
|
11
|
+
let(:minion) { quacks_like_instance_of(Minion) }
|
12
12
|
let(:minionization) { Minionization.new(arguments, config, minion_constructor) }
|
13
13
|
let(:minion_roles) {{ fqdn => { 'roles' => [role_name] }}}
|
14
|
-
let(:minion_constructor) {
|
15
|
-
let(:session) {
|
16
|
-
let(:role) {
|
14
|
+
let(:minion_constructor) { quacks_like(Minion) }
|
15
|
+
let(:session) { quacks_like_instance_of(Session) }
|
16
|
+
let(:role) { quacks_like_instance_of(RoleTemplate) }
|
17
17
|
|
18
18
|
before do
|
19
19
|
config.stubs(:minions).returns(minion_roles)
|
20
|
-
minion.
|
20
|
+
minion.expects(:roles).returns([role_name])
|
21
|
+
minion.expects(:session).returns(session)
|
21
22
|
minion_constructor.expects(:new).with(fqdn, config).returns(minion)
|
22
23
|
role_class.expects(:new).with(session).returns(role)
|
23
|
-
role.
|
24
|
+
role.expects(:call)
|
24
25
|
minionization.expects(:require).with("/roles/#{role_name}.rb")
|
25
|
-
minion.expect(:session, session)
|
26
26
|
end
|
27
27
|
|
28
28
|
describe 'calling with a valid minion name' do
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Minionizer
|
4
|
-
class RoleTemplateTest < MiniTest::
|
4
|
+
class RoleTemplateTest < MiniTest::Test
|
5
5
|
|
6
6
|
describe RoleTemplate do
|
7
|
-
let(:session) {
|
7
|
+
let(:session) { quacks_like_instance_of(Session) }
|
8
8
|
let(:template) { RoleTemplate.new(session) }
|
9
9
|
|
10
10
|
it 'initilizes' do
|
@@ -1,17 +1,18 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Minionizer
|
4
|
-
class SessionTest < MiniTest::
|
4
|
+
class SessionTest < MiniTest::Test
|
5
5
|
|
6
6
|
describe Session do
|
7
7
|
let(:fqdn) { 'foo.bar.com' }
|
8
8
|
let(:username) { 'foo' }
|
9
9
|
let(:password) { 'bar' }
|
10
10
|
let(:credentials) {{ 'username' => username, 'password' => password }}
|
11
|
-
let(:connector) {
|
12
|
-
let(:
|
13
|
-
let(:
|
14
|
-
let(:
|
11
|
+
let(:connector) { mock('connector') }
|
12
|
+
let(:command_executor) { mock('CommandExecution') }
|
13
|
+
let(:execution) { mock('execution') }
|
14
|
+
let(:connection) { mock('connection') }
|
15
|
+
let(:session) { Session.new(fqdn, credentials, connector, command_executor) }
|
15
16
|
let(:start_args) { [fqdn, username, { password: password }]}
|
16
17
|
|
17
18
|
it 'instantiates' do
|
@@ -22,33 +23,77 @@ module Minionizer
|
|
22
23
|
let(:command) { 'foobar' }
|
23
24
|
|
24
25
|
before do
|
25
|
-
connector.
|
26
|
+
connector.expects(:start).with(*start_args).returns(connection)
|
26
27
|
end
|
27
28
|
|
28
|
-
describe '
|
29
|
+
describe '#sudo' do
|
30
|
+
|
31
|
+
describe 'with block argument' do
|
32
|
+
|
33
|
+
it 'prepends sudo onto the command line' do
|
34
|
+
command_executor.
|
35
|
+
expects(:new).
|
36
|
+
with(connection, sudoized(command)).
|
37
|
+
returns(OpenStruct.new(:call => true))
|
38
|
+
session.sudo do
|
39
|
+
session.exec(command)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'with single command passed directly' do
|
45
|
+
|
46
|
+
it 'prepends sudo onto the command line' do
|
47
|
+
command_executor.
|
48
|
+
expects(:new).
|
49
|
+
with(connection, sudoized(command)).
|
50
|
+
returns(OpenStruct.new(:call => true))
|
51
|
+
session.sudo(command)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
29
55
|
|
30
|
-
|
31
|
-
|
32
|
-
|
56
|
+
describe 'with multiple commands passed directly' do
|
57
|
+
let(:commands) { %w{foo bar} }
|
58
|
+
|
59
|
+
it 'prepends sudo onto each command line' do
|
60
|
+
commands.each do |command|
|
61
|
+
command_executor.
|
62
|
+
expects(:new).
|
63
|
+
with(connection, sudoized(command)).
|
64
|
+
returns(OpenStruct.new(:call => true))
|
65
|
+
end
|
66
|
+
session.sudo(*commands)
|
67
|
+
end
|
33
68
|
end
|
34
69
|
|
35
|
-
|
36
|
-
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'when a single command is passed' do
|
73
|
+
|
74
|
+
let(:exit_code) { 0 }
|
75
|
+
|
76
|
+
it 'passes the command to the executor' do
|
77
|
+
command_executor.
|
78
|
+
expects(:new).
|
79
|
+
with(connection, command).
|
80
|
+
returns(OpenStruct.new(:call => true))
|
81
|
+
session.exec(command)
|
37
82
|
end
|
83
|
+
|
38
84
|
end
|
39
85
|
|
40
86
|
describe 'when multiple commands are passed' do
|
41
|
-
let(:commands) { %w
|
87
|
+
let(:commands) { %w{foo bar} }
|
42
88
|
|
43
|
-
|
89
|
+
it 'passes each command individually to the executor' do
|
44
90
|
commands.each do |command|
|
45
|
-
|
91
|
+
command_executor.
|
92
|
+
expects(:new).
|
93
|
+
with(connection, command).
|
94
|
+
returns(OpenStruct.new(:call => true))
|
46
95
|
end
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'returns multiple results' do
|
51
|
-
assert_kind_of(Array, session.exec(commands))
|
96
|
+
session.exec(*commands)
|
52
97
|
end
|
53
98
|
end
|
54
99
|
|