minionizer 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,193 @@
1
+ require 'test_helper'
2
+ require 'fileutils'
3
+
4
+ module Minionizer
5
+ class MinionTestFailure < StandardError; end
6
+ class CoreLibraryTest < MiniTest::Test
7
+ roll_back_to_blank_snapshot if minion_available?
8
+
9
+ describe 'core library' do
10
+ let(:fqdn) { '192.168.49.181' }
11
+ let(:username) { 'vagrant' }
12
+ let(:password) { 'vagrant' }
13
+ let(:credentials) {{ 'username' => username, 'password' => password }}
14
+ let(:session) { Session.new(fqdn, credentials) }
15
+ let(:minionization) { Minionization.new([fqdn], Configuration.instance) }
16
+ let(:minions) {{ fqdn => { 'ssh' => credentials, 'roles' => ['test_role'] } }}
17
+
18
+ before do
19
+ skip unless minion_available?
20
+ Configuration.instance.instance_variable_set(:@minions, nil)
21
+ write_file('config/minions.yml', minions.to_yaml)
22
+ create_role(code)
23
+ end
24
+
25
+ describe UserCreation do
26
+ let(:new_name) { 'Test User' }
27
+ let(:new_username) { 'testuser' }
28
+ let(:options) {{ name: new_name, username: new_username }}
29
+ let(:code) { %Q{
30
+ session.sudo do
31
+ Minionizer::UserCreation.new( session, #{options}).call
32
+ end
33
+ } }
34
+
35
+ before do
36
+ refute_user_exists(new_username)
37
+ end
38
+
39
+ it 'creates a user' do
40
+ 2.times { assert_throws(:high_five) { minionization.call } }
41
+ assert_user_exists(new_username)
42
+ end
43
+ end
44
+
45
+ describe FolderCreation do
46
+ let(:filename) { "foo/dir" }
47
+ let(:ownername) { 'otheruser' }
48
+ let(:path) { "/home/vagrant/#{filename}" }
49
+ let(:options) {{ path: path, mode: '0700', owner: ownername, group: ownername }}
50
+ let(:code) { %Q{
51
+ session.sudo do
52
+ Minionizer::FolderCreation.new( session, #{options}).call
53
+ end
54
+ } }
55
+
56
+ before do
57
+ refute_directory_exists(path)
58
+ session.sudo("adduser --disabled-password --gecos '#{ownername}' #{ownername}")
59
+ end
60
+
61
+ after do
62
+ skip unless minion_available?
63
+ session.sudo("userdel #{ownername}")
64
+ end
65
+
66
+ it 'creates a folder' do
67
+ 2.times { assert_throws(:high_five) { minionization.call } }
68
+ assert_directory_exists(path)
69
+ mode = session.exec("stat --format=%a #{path}")[:stdout]
70
+ assert_equal('700',mode)
71
+ owner = session.exec("stat --format=%U #{path}")[:stdout]
72
+ assert_equal(ownername, owner)
73
+ group = session.exec("stat --format=%G #{path}")[:stdout]
74
+ assert_equal(ownername, group)
75
+ end
76
+ end
77
+
78
+ describe FileInjection do
79
+ let(:filename) { 'foobar.txt' }
80
+ let(:source_path) { "/some/source/#{filename}" }
81
+ let(:target_path) { "/home/vagrant/#{filename}" }
82
+ let(:ownername) { 'otheruser' }
83
+ let(:options) {{
84
+ source_path: source_path,
85
+ target_path: target_path,
86
+ mode: '0700',
87
+ owner: ownername,
88
+ group: ownername
89
+ }}
90
+ let(:code) { %Q{
91
+ session.sudo do
92
+ Minionizer::FileInjection.new( session, #{options}).call
93
+ end
94
+ } }
95
+
96
+ before do
97
+ refute_file_exists(target_path)
98
+ write_file(source_path, 'FooBar')
99
+ session.sudo("adduser --disabled-password --gecos '#{ownername}' #{ownername}")
100
+ end
101
+
102
+ after do
103
+ skip unless minion_available?
104
+ session.sudo("userdel #{ownername}")
105
+ end
106
+
107
+ it 'injects a file' do
108
+ 2.times { assert_throws(:high_five) { minionization.call } }
109
+ assert_file_exists(target_path)
110
+ mode = session.exec("stat --format=%a #{target_path}")[:stdout]
111
+ assert_equal('700',mode)
112
+ owner = session.exec("stat --format=%U #{target_path}")[:stdout]
113
+ assert_equal(ownername, owner)
114
+ group = session.exec("stat --format=%G #{target_path}")[:stdout]
115
+ assert_equal(ownername, group)
116
+ end
117
+ end
118
+
119
+ describe PublicSshKeyInjection do
120
+ let(:source_path) { "data/public_keys" }
121
+ let(:target_username) { 'otheruser' }
122
+ let(:options) {{ target_username: target_username }}
123
+ let(:code) { %Q{
124
+ session.sudo do |sudo_session|
125
+ Minionizer::PublicSshKeyInjection.new(sudo_session, #{options}).call
126
+ end
127
+ } }
128
+
129
+ before do
130
+ Dir.mkdir('/tmp')
131
+ refute_file_exists("~#{target_username}/.ssh/authorized_keys")
132
+ write_file("#{source_path}/foobar.pubkey", 'FooBar')
133
+ write_file("#{source_path}/foobaz.pubkey", 'FooBaz')
134
+ session.exec("sudo adduser --disabled-password --gecos '#{target_username}' #{target_username}")
135
+ end
136
+
137
+ after do
138
+ File.delete("#{source_path}/foobar.pubkey")
139
+ File.delete("#{source_path}/foobaz.pubkey")
140
+ end
141
+
142
+ it 'injects public keys' do
143
+ 2.times { assert_throws(:high_five) { minionization.call } }
144
+ assert_file_exists("~#{target_username}/.ssh/authorized_keys")
145
+ end
146
+ end
147
+
148
+ #######
149
+ private
150
+ #######
151
+
152
+ def create_role(injected_code)
153
+ role_code = without_fakefs do
154
+ ERB.new(File.open('test/role_template.rb.erb').read.strip).result(binding)
155
+ end
156
+ write_file('roles/test_role.rb', role_code)
157
+ end
158
+
159
+ def assert_file_exists(path)
160
+ assert(link_exists?(path, :f), "#{path} file expected to exist")
161
+ end
162
+
163
+ def refute_file_exists(path)
164
+ refute(link_exists?(path, :f), "#{path} file NOT expected to exist")
165
+ end
166
+
167
+ def assert_directory_exists(path)
168
+ assert(link_exists?(path, :d), "#{path} directory expected to exist")
169
+ end
170
+
171
+ def refute_directory_exists(path)
172
+ refute(link_exists?(path, :d), "#{path} directory NOT expected to exist")
173
+ end
174
+
175
+ def link_exists?(path, parameter = :e)
176
+ session.exec("[ -#{parameter} #{path} ] && echo 'yes' || echo 'no'")[:stdout] == 'yes'
177
+ end
178
+
179
+ def assert_user_exists(username)
180
+ assert(user_exists?(username), "User '#{username}' expected to exist")
181
+ end
182
+
183
+ def refute_user_exists(username)
184
+ refute(user_exists?(username), "User '#{username}' expected to NOT exist")
185
+ end
186
+
187
+ def user_exists?(username)
188
+ session.exec("id #{username} || echo 'no'")[:stdout] != 'no'
189
+ end
190
+
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,16 @@
1
+ class TestRole < Minionizer::RoleTemplate
2
+
3
+ def call
4
+ if hostname == 'precise32'
5
+ <%= injected_code %>
6
+ throw :high_five
7
+ else
8
+ raise Minionizer::MinionTestFailure.new("Whawhawhaaaa... #{hostname}")
9
+ end
10
+ end
11
+
12
+ def hostname
13
+ @hostname ||= session.exec(:hostname)[:stdout]
14
+ end
15
+
16
+ end
@@ -1,13 +1,24 @@
1
1
  require 'rubygems'
2
+ require 'simplecov'
3
+ require 'coveralls'
2
4
  require 'minitest/autorun'
3
5
  require 'fakefs/safe'
4
6
  require 'socket'
7
+ require 'tempfile'
5
8
  require 'timeout'
6
9
 
10
+ if Coveralls.will_run?
11
+ Coveralls.wear!
12
+ else
13
+ SimpleCov.start
14
+ end
15
+
16
+ PRE_REQUIRED_LIBS = %w{tempfile}
17
+
7
18
  require_relative '../lib/minionizer'
8
19
 
9
20
  module Minionizer
10
- class MiniTest::Unit::TestCase
21
+ class MiniTest::Test
11
22
 
12
23
  def before_setup
13
24
  super
@@ -17,6 +28,9 @@ module Minionizer
17
28
  def after_teardown
18
29
  super
19
30
  FakeFS.deactivate!
31
+ Kernel.class_eval do
32
+ alias_method :require, :real_require
33
+ end
20
34
  end
21
35
 
22
36
  #######
@@ -25,17 +39,44 @@ module Minionizer
25
39
 
26
40
  def initialize_fakefs
27
41
  FakeFS.activate!
28
- FakeFS::FileSystem.clear
29
42
  Kernel.class_eval do
43
+ def fake_require(path)
44
+ if PRE_REQUIRED_LIBS.include?(path)
45
+ return false #real require returns false if library is already loaded
46
+ else
47
+ File.open(path, "r") {|f| Object.class_eval f.read, path, 1 }
48
+ end
49
+ end
50
+ alias_method :real_require, :require
30
51
  alias_method :require, :fake_require
31
52
  end
32
53
  end
33
54
 
55
+ def without_fakefs
56
+ FakeFS.deactivate!
57
+ yield
58
+ ensure
59
+ FakeFS.activate!
60
+ end
61
+
34
62
  def minion_available?
35
- Timeout.timeout(1) do
36
- @@minion_available ||= TCPSocket.new('192.168.49.181', 22)
63
+ self.class.minion_available?
64
+ end
65
+
66
+ def self.minion_available?
67
+ if MinionMonitor.minion_available?
68
+ return true
69
+ else
70
+ Timeout.timeout(1) do
71
+ if Net::SSH.start('192.168.49.181', 'vagrant', password: 'vagrant')
72
+ MinionMonitor.minion_available!
73
+ return true
74
+ else
75
+ return false
76
+ end
77
+ end
37
78
  end
38
- rescue Errno::ECONNREFUSED, Timeout::Error
79
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error
39
80
  return false
40
81
  end
41
82
 
@@ -43,10 +84,8 @@ module Minionizer
43
84
  @@previously_initialized ||= `cd #{File.dirname(__FILE__)}; vagrant up`
44
85
  end
45
86
 
46
- def roll_back_to_blank_snapshot
47
- FakeFS.deactivate!
87
+ def self.roll_back_to_blank_snapshot
48
88
  `cd #{File.dirname(__FILE__)}; vagrant snapshot go blank-test-slate`
49
- FakeFS.activate!
50
89
  end
51
90
 
52
91
  def write_role_file(name)
@@ -64,38 +103,92 @@ module Minionizer
64
103
  Object.const_set(name.classify, Class.new)
65
104
  end
66
105
 
67
- end
68
- end
106
+ def quacks_like(klass)
107
+ mock("Mock(#{klass.to_s})").tap do |object|
108
+ object.responds_like(klass)
109
+ end
110
+ end
111
+
112
+ def quacks_like_instance_of(klass)
113
+ mock("InstanceMock(#{klass.to_s})").tap do |object|
114
+ object.responds_like_instance_of(klass)
115
+ end
116
+ end
69
117
 
70
- module Kernel
118
+ def assert_equal(first, second)
119
+ assert(first === second, "'#{second}' expected to be equal to '#{first}'")
120
+ end
71
121
 
72
- def fake_require(path)
73
- File.open(path, "r") {|f| Object.class_eval f.read, path, 1 }
122
+ def sudoized(command)
123
+ %Q{sudo bash -c "#{command}"}
124
+ end
74
125
  end
75
-
76
126
  end
77
127
 
78
- module MiniTest
79
- class NamedMock < Mock
80
- attr_reader :name
81
-
82
- def initialize(name)
83
- @name = name
84
- super()
85
- end
128
+ require 'mocha/setup'
86
129
 
87
- # Because you ought to be able to
88
- # test two effing mocks for equality.
89
- def ==(x)
90
- object_id == x.object_id
91
- end
130
+ module MinionMonitor
131
+ def self.minion_available?; !!@minion_available; end
132
+ def self.minion_available!; @minion_available = true; end
133
+ end
92
134
 
93
- def method_missing(sym, *args, &block)
94
- super(sym, *args, &block)
95
- rescue NoMethodError, MockExpectationError, ArgumentError => error
96
- raise(error.class, "#{error.message} (mock:#{name}) ")
135
+ ## Only need this until we have FakeFS > 0.5.2 that includes this commit.
136
+ ## https://github.com/defunkt/fakefs/commit/06eb002da7fb8119a60fef7d50307bd3358c85f3
137
+ module FakeFS
138
+ class Dir
139
+ if RUBY_VERSION >= '2.1'
140
+ module Tmpname # :nodoc:
141
+ module_function
142
+
143
+ def tmpdir
144
+ Dir.tmpdir
145
+ end
146
+
147
+ def make_tmpname(prefix_suffix, n)
148
+ case prefix_suffix
149
+ when String
150
+ prefix = prefix_suffix
151
+ suffix = ""
152
+ when Array
153
+ prefix = prefix_suffix[0]
154
+ suffix = prefix_suffix[1]
155
+ else
156
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
157
+ end
158
+ t = Time.now.strftime("%Y%m%d")
159
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
160
+ path << "-#{n}" if n
161
+ path << suffix
162
+ end
163
+
164
+ def create(basename, *rest)
165
+ if opts = Hash.try_convert(rest[-1])
166
+ opts = opts.dup if rest.pop.equal?(opts)
167
+ max_try = opts.delete(:max_try)
168
+ opts = [opts]
169
+ else
170
+ opts = []
171
+ end
172
+ tmpdir, = *rest
173
+ if $SAFE > 0 and tmpdir.tainted?
174
+ tmpdir = '/tmp'
175
+ else
176
+ tmpdir ||= tmpdir()
177
+ end
178
+ n = nil
179
+ begin
180
+ path = File.join(tmpdir, make_tmpname(basename, n))
181
+ yield(path, n, *opts)
182
+ rescue Errno::EEXIST
183
+ n ||= 0
184
+ n += 1
185
+ retry if !max_try or n < max_try
186
+ raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'"
187
+ end
188
+ path
189
+ end
190
+ end
97
191
  end
98
192
  end
99
193
  end
100
194
 
101
- require 'mocha/setup'
@@ -1,29 +1,62 @@
1
1
  require 'test_helper'
2
2
 
3
3
  module Minionizer
4
- class FileInjectionTest < MiniTest::Unit::TestCase
4
+ class FileInjectionTest < MiniTest::Test
5
5
 
6
6
  describe FileInjection do
7
7
  let(:session) { 'MockSession' }
8
- let(:injection) { FileInjection.new(session) }
9
-
10
- it 'instantiates' do
11
- assert_kind_of(FileInjection, injection)
12
- end
8
+ let(:source_path) { 'data/source_file.txt'}
9
+ let(:target_path) { '/var/target_file.txt'}
10
+ let(:injection) { FileInjection.new(session, options) }
13
11
 
14
12
  describe '#call' do
15
13
  let(:source_contents) { 'Source Contents' }
16
- let(:source) { 'data/source_file.txt'}
17
- let(:target) { '/var/target_file.txt'}
18
14
 
19
15
  before do
20
- write_file(source, source_contents)
21
- session.expects(:exec).with(%Q{echo '#{source_contents}' > #{target}})
16
+ write_file(source_path, source_contents)
17
+ session.expects(:exec).with(%Q{mkdir --parents #{File.dirname(target_path)}})
18
+ session.expects(:exec).with(%Q{echo '#{source_contents}' > #{target_path}})
19
+ end
20
+
21
+ describe 'only source and target are provided' do
22
+ let(:options) {{ source_path: source_path, target_path: target_path }}
23
+
24
+ it 'sends a command to session' do
25
+ injection.call
26
+ end
27
+
28
+ end
29
+
30
+ describe 'mode is provided' do
31
+ let(:mode) { '0700' }
32
+ let(:options) {{ source_path: source_path, target_path: target_path, mode: mode }}
33
+
34
+ it 'sets the file permissions' do
35
+ session.expects(:exec).with(%Q{chmod #{mode} #{target_path}})
36
+ injection.call
37
+ end
22
38
  end
23
39
 
24
- it 'sends a command to session' do
25
- injection.inject(source, target)
40
+ describe 'owner is provided' do
41
+ let(:ownername) { 'otheruser' }
42
+ let(:options) {{ source_path: source_path, target_path: target_path, owner: ownername }}
43
+
44
+ it 'sets the file owner' do
45
+ session.expects(:exec).with(%Q{chown #{ownername} #{target_path}})
46
+ injection.call
47
+ end
26
48
  end
49
+
50
+ describe 'group is provided' do
51
+ let(:groupname) { 'othergroup' }
52
+ let(:options) {{ source_path: source_path, target_path: target_path, group: groupname }}
53
+
54
+ it 'sets the file group' do
55
+ session.expects(:exec).with(%Q{chgrp #{groupname} #{target_path}})
56
+ injection.call
57
+ end
58
+ end
59
+
27
60
  end
28
61
 
29
62
  end