minionizer 0.1.0 → 0.2.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/.travis.yml +1 -1
- data/Gemfile.lock +29 -19
- data/README.md +13 -10
- data/Rakefile +2 -0
- data/bin/minionize +11 -1
- data/lib/core/file_injection.rb +31 -3
- data/lib/core/public_ssh_key_injection.rb +29 -8
- data/lib/core/task_template.rb +3 -0
- data/lib/minionizer/command_execution.rb +17 -4
- data/lib/minionizer/cryptographer.rb +84 -0
- data/lib/minionizer/initialization.rb +55 -0
- data/lib/minionizer/session.rb +28 -11
- data/lib/minionizer/version.rb +1 -1
- data/lib/minionizer.rb +2 -0
- data/minionizer.gemspec +3 -0
- data/test/integration/core_library_test.rb +9 -4
- data/test/test_helper.rb +0 -1
- data/test/unit/lib/core/file_injection_test.rb +66 -29
- data/test/unit/lib/core/public_ssh_key_injection_test.rb +22 -11
- data/test/unit/lib/core/task_template_test.rb +9 -1
- data/test/unit/lib/minionizer/cryptographer_test.rb +145 -0
- data/test/unit/lib/minionizer/initialization_test.rb +113 -0
- data/test/unit/lib/minionizer/session_test.rb +51 -9
- metadata +50 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0f6f2ff87e40cfacb8163853951e4b6d2851dd3
|
4
|
+
data.tar.gz: aaa855643097d24799b62ed3b6f1fc187670a224
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: caf5ec31a1e30efac84dc898af40c4a7d9e2e247fcfdae6110d5bb93c9ab657f48348f664f5dc0dec5a40721c2181f85f4bc337e055e989ccf38abbcde596f70
|
7
|
+
data.tar.gz: 6a366d4b175747590bc289741068d20523b32716580ecae1244f4dd84aa2d91d4bbaf013f1632ca123fd528369932aef0d7f71cdd749ea1221c40ad1e4a44476
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,50 +1,59 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
minionizer (0.0
|
4
|
+
minionizer (0.2.0)
|
5
5
|
activesupport (~> 4.1)
|
6
|
+
binding_of_caller (~> 0.7)
|
7
|
+
net-scp (~> 1.2)
|
6
8
|
net-ssh (~> 2.9)
|
7
9
|
|
8
10
|
GEM
|
9
11
|
remote: http://rubygems.org/
|
10
12
|
specs:
|
11
|
-
activesupport (4.1.
|
13
|
+
activesupport (4.1.6)
|
12
14
|
i18n (~> 0.6, >= 0.6.9)
|
13
15
|
json (~> 1.7, >= 1.7.7)
|
14
16
|
minitest (~> 5.1)
|
15
17
|
thread_safe (~> 0.1)
|
16
18
|
tzinfo (~> 1.1)
|
17
|
-
|
19
|
+
binding_of_caller (0.7.2)
|
20
|
+
debug_inspector (>= 0.0.1)
|
21
|
+
coveralls (0.7.1)
|
18
22
|
multi_json (~> 1.3)
|
19
23
|
rest-client
|
20
24
|
simplecov (>= 0.7)
|
21
25
|
term-ansicolor
|
22
26
|
thor
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
debug_inspector (0.0.2)
|
28
|
+
docile (1.1.5)
|
29
|
+
fakefs (0.6.0)
|
30
|
+
i18n (0.6.11)
|
26
31
|
json (1.8.1)
|
27
32
|
metaclass (0.0.4)
|
28
|
-
mime-types (2.
|
29
|
-
minitest (5.
|
30
|
-
mocha (1.
|
33
|
+
mime-types (2.4.3)
|
34
|
+
minitest (5.4.2)
|
35
|
+
mocha (1.1.0)
|
31
36
|
metaclass (~> 0.0.1)
|
32
|
-
multi_json (1.10.
|
33
|
-
net-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
multi_json (1.10.1)
|
38
|
+
net-scp (1.2.1)
|
39
|
+
net-ssh (>= 2.6.5)
|
40
|
+
net-ssh (2.9.1)
|
41
|
+
netrc (0.8.0)
|
42
|
+
rake (10.3.2)
|
43
|
+
rest-client (1.7.2)
|
44
|
+
mime-types (>= 1.16, < 3.0)
|
45
|
+
netrc (~> 0.7)
|
46
|
+
simplecov (0.9.1)
|
38
47
|
docile (~> 1.1.0)
|
39
|
-
multi_json
|
48
|
+
multi_json (~> 1.0)
|
40
49
|
simplecov-html (~> 0.8.0)
|
41
50
|
simplecov-html (0.8.0)
|
42
51
|
term-ansicolor (1.3.0)
|
43
52
|
tins (~> 1.0)
|
44
53
|
thor (0.19.1)
|
45
|
-
thread_safe (0.3.
|
46
|
-
tins (1.
|
47
|
-
tzinfo (1.
|
54
|
+
thread_safe (0.3.4)
|
55
|
+
tins (1.3.3)
|
56
|
+
tzinfo (1.2.2)
|
48
57
|
thread_safe (~> 0.1)
|
49
58
|
|
50
59
|
PLATFORMS
|
@@ -54,5 +63,6 @@ DEPENDENCIES
|
|
54
63
|
coveralls
|
55
64
|
fakefs (~> 0.5)
|
56
65
|
minionizer!
|
66
|
+
minitest (~> 5.4)
|
57
67
|
mocha (~> 1.0)
|
58
68
|
rake (~> 10.3)
|
data/README.md
CHANGED
@@ -9,9 +9,9 @@
|
|
9
9
|
Minionizer aims to be a light weight, yet powerful, server provisioning tool with minimum learning
|
10
10
|
curve.
|
11
11
|
|
12
|
-
Minionizer is still in
|
12
|
+
Minionizer is still in beta development and is not yet ready for anything resembling production use.
|
13
13
|
|
14
|
-
|
14
|
+
## Overview
|
15
15
|
|
16
16
|
Minionizer allows you keep all of your provisioning "recipies" for a set of servers, along with any
|
17
17
|
data those recipies may need (such as config files), in a single git repository.
|
@@ -31,27 +31,30 @@ packages, etc. You can use these core commands to build more complex recipies, o
|
|
31
31
|
minionizer plugins that WILL BE available, such as posgresql installationi/upgrade, ruby
|
32
32
|
installation/upgrade, etc.
|
33
33
|
|
34
|
-
|
34
|
+
## Installation
|
35
35
|
|
36
36
|
gem install minionizer
|
37
37
|
|
38
|
-
|
38
|
+
## Demonstration
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
A demonstration repo is available at [https://github.com/jsgarvin/minionizer-demo](https://github.com/jsgarvin/minionizer-demo).
|
41
|
+
|
42
|
+
## Usage
|
43
|
+
|
44
|
+
### Setup a new provisioning project in the current folder.
|
42
45
|
|
43
46
|
minionize --init subfolder_name
|
44
47
|
|
45
48
|
Creates `subfolder_name` and initializes it with some initial folders and files to get you started.
|
46
49
|
|
47
|
-
|
50
|
+
### Modify config/minions.yml
|
48
51
|
|
49
52
|
The minions.yml file is where you define what servers this project will manage and what roles
|
50
53
|
each server will play.
|
51
54
|
|
52
55
|
You will probably want assign each server multiple roles, such as `['production', 'db']`.
|
53
56
|
|
54
|
-
|
57
|
+
### Create role instructions
|
55
58
|
|
56
59
|
A sample role file WILL BE provided in the ./roles folder to get you started. Each role file defines
|
57
60
|
what servers assigned that role should do on each (re)provisioning.
|
@@ -61,7 +64,7 @@ file. You will likely have some roles, such as "production", that are mearly a m
|
|
61
64
|
several servers together and won't have a corresponding role file. You will need at least one role,
|
62
65
|
though, such as "db" or "webserver", that will have a corresponding role file.
|
63
66
|
|
64
|
-
|
67
|
+
### Provision Servers
|
65
68
|
|
66
69
|
To provision all of the servers that are assigned a particular role, run...
|
67
70
|
|
@@ -79,7 +82,7 @@ or
|
|
79
82
|
This will loop through each role that is assigned to just that server, and any corresponding role
|
80
83
|
files will be run.
|
81
84
|
|
82
|
-
|
85
|
+
## Contribute
|
83
86
|
|
84
87
|
To contribute to Minionizer development you will need to install [vagrant](http://www.vagrantup.com/)
|
85
88
|
and [VirtualBox](https://www.virtualbox.org/) in order to be able to run acceptance tests.
|
data/Rakefile
CHANGED
@@ -14,6 +14,7 @@ end
|
|
14
14
|
|
15
15
|
namespace :test do
|
16
16
|
namespace :vm do
|
17
|
+
desc 'Start Test VM'
|
17
18
|
task :start do
|
18
19
|
relay_output(vagrant_command(:up))
|
19
20
|
unless snapshot_plugin_installed?
|
@@ -24,6 +25,7 @@ namespace :test do
|
|
24
25
|
relay_output(vagrant_command('snapshot take blank-test-slate'))
|
25
26
|
end
|
26
27
|
end
|
28
|
+
desc 'Stop Test VM'
|
27
29
|
task :stop do
|
28
30
|
relay_output(vagrant_command(:halt))
|
29
31
|
end
|
data/bin/minionize
CHANGED
@@ -1,3 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require_relative '../lib/minionizer'
|
3
|
-
|
3
|
+
if ARGV[0] == '--init'
|
4
|
+
Minionizer::Initialization.create(ARGV[1])
|
5
|
+
elsif ['-gk','--generate-key'].include?(ARGV[0])
|
6
|
+
Minionizer::Cryptographer.new.generate_key
|
7
|
+
elsif ['-es','--encrypt-secrets'].include?(ARGV[0])
|
8
|
+
Minionizer::Cryptographer.new.encrypt_secrets
|
9
|
+
elsif ['-ds','--decrypt-secrets'].include?(ARGV[0])
|
10
|
+
Minionizer::Cryptographer.new.decrypt_secrets
|
11
|
+
else
|
12
|
+
Minionizer::Minionization.new(ARGV, Minionizer::Configuration.instance).call
|
13
|
+
end
|
data/lib/core/file_injection.rb
CHANGED
@@ -3,7 +3,7 @@ module Minionizer
|
|
3
3
|
|
4
4
|
def call
|
5
5
|
session.exec("mkdir --parents #{target_directory}")
|
6
|
-
session.
|
6
|
+
session.scp(string_io_creator.new(contents), target_path)
|
7
7
|
session.exec("chmod #{mode} #{target_path}") if respond_to?(:mode)
|
8
8
|
session.exec("chown #{owner} #{target_path}") if respond_to?(:owner)
|
9
9
|
session.exec("chgrp #{group} #{target_path}") if respond_to?(:group)
|
@@ -17,8 +17,36 @@ module Minionizer
|
|
17
17
|
File.dirname(target_path)
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
20
|
+
def contents
|
21
|
+
options[:contents] ||= processed_contents_from_source_path
|
22
|
+
end
|
23
|
+
|
24
|
+
def processed_contents_from_source_path
|
25
|
+
if source_file_requires_erb_processing?
|
26
|
+
ERB.new(contents_from_source_path).result(erb_binding)
|
27
|
+
else
|
28
|
+
contents_from_source_path
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def source_file_requires_erb_processing?
|
33
|
+
File.extname(source_path) == '.erb'
|
34
|
+
end
|
35
|
+
|
36
|
+
def contents_from_source_path
|
37
|
+
File.open(source_path).read.strip
|
38
|
+
end
|
39
|
+
|
40
|
+
def erb_binding
|
41
|
+
level = 1
|
42
|
+
until binding.of_caller(level).eval('self.class') != self.class
|
43
|
+
level += 1
|
44
|
+
end
|
45
|
+
binding.of_caller(level)
|
46
|
+
end
|
47
|
+
|
48
|
+
def string_io_creator
|
49
|
+
options[:string_io_creator] ||= StringIO
|
22
50
|
end
|
23
51
|
end
|
24
52
|
end
|
@@ -2,36 +2,57 @@ module Minionizer
|
|
2
2
|
class PublicSshKeyInjection < TaskTemplate
|
3
3
|
|
4
4
|
def call
|
5
|
+
folder_creation.call
|
5
6
|
file_injection.call
|
6
|
-
ensure
|
7
|
-
temp_file.unlink
|
8
7
|
end
|
9
8
|
|
10
9
|
#######
|
11
10
|
private
|
12
11
|
#######
|
13
12
|
|
13
|
+
def folder_creation
|
14
|
+
@folder_creation ||= folder_creation_creator.new(session, folder_creation_options)
|
15
|
+
end
|
16
|
+
|
14
17
|
def file_injection
|
15
18
|
@file_injection ||= file_injection_creator.new(session, file_injection_options)
|
16
19
|
end
|
17
20
|
|
21
|
+
def folder_creation_creator
|
22
|
+
options[:folder_creation_creator] ||= FolderCreation
|
23
|
+
end
|
24
|
+
|
18
25
|
def file_injection_creator
|
19
26
|
options[:file_injection_creator] ||= FileInjection
|
20
27
|
end
|
21
28
|
|
29
|
+
def folder_creation_options
|
30
|
+
{
|
31
|
+
path: target_folder,
|
32
|
+
owner: target_username,
|
33
|
+
group: target_username,
|
34
|
+
mode: 700
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
22
38
|
def file_injection_options
|
23
39
|
{
|
24
|
-
|
25
|
-
target_path: "
|
40
|
+
contents: combined_keys,
|
41
|
+
target_path: "#{target_folder}/authorized_keys",
|
26
42
|
owner: target_username,
|
27
|
-
group: target_username
|
43
|
+
group: target_username,
|
44
|
+
mode: 600
|
28
45
|
}
|
29
46
|
end
|
30
47
|
|
31
|
-
def
|
32
|
-
|
48
|
+
def target_folder
|
49
|
+
"/home/#{target_username}/.ssh"
|
50
|
+
end
|
51
|
+
|
52
|
+
def combined_keys
|
53
|
+
String.new.tap do |string|
|
33
54
|
Dir.glob("data/public_keys/*.pubkey") do |key_file|
|
34
|
-
|
55
|
+
string << File.open(key_file).read
|
35
56
|
end
|
36
57
|
end
|
37
58
|
end
|
data/lib/core/task_template.rb
CHANGED
@@ -4,11 +4,12 @@ module Minionizer
|
|
4
4
|
class CommandError < StandardError; end
|
5
5
|
class InvocationError < StandardError; end
|
6
6
|
|
7
|
-
attr_reader :connection, :command
|
7
|
+
attr_reader :connection, :command, :options
|
8
8
|
|
9
|
-
def initialize(connection, command)
|
9
|
+
def initialize(connection, command, options={})
|
10
10
|
@connection = connection
|
11
11
|
@command = command
|
12
|
+
@options = options
|
12
13
|
end
|
13
14
|
|
14
15
|
def call
|
@@ -27,6 +28,7 @@ module Minionizer
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def execute_command_inside_channel(channel)
|
31
|
+
inform("Running: #{command}")
|
30
32
|
channel.exec(command) do |_, success|
|
31
33
|
if success
|
32
34
|
compile_results(channel)
|
@@ -51,10 +53,21 @@ module Minionizer
|
|
51
53
|
end
|
52
54
|
|
53
55
|
def compile_results(channel)
|
54
|
-
channel
|
55
|
-
channel.on_extended_data { |_, data| results[:stderr] += data.to_s }
|
56
|
+
read_stdout(channel)
|
57
|
+
channel.on_extended_data { |_, _, data| results[:stderr] += data.to_s }
|
56
58
|
channel.on_request('exit-status') { |_,data| results[:exit_code] = data.read_long }
|
57
59
|
channel.on_request('exit-signal') { |_,data| results[:exit_signal] = data.read_string }
|
58
60
|
end
|
61
|
+
|
62
|
+
def read_stdout(channel)
|
63
|
+
channel.on_data do |_, data|
|
64
|
+
inform("STDOUT: #{data}")
|
65
|
+
results[:stdout] += data.strip
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def inform(message)
|
70
|
+
puts message if options[:verbose]
|
71
|
+
end
|
59
72
|
end
|
60
73
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Minionizer
|
2
|
+
class Cryptographer
|
3
|
+
KEYRING_PATH = './data/public_keyring.gpg'
|
4
|
+
SECRET_FOLDER = './data/secrets'
|
5
|
+
SAFE_FOLDER = './data/encrypted_secrets'
|
6
|
+
|
7
|
+
def generate_key
|
8
|
+
system("#{gpg_command} --gen-key")
|
9
|
+
end
|
10
|
+
|
11
|
+
def encrypt_secrets
|
12
|
+
Dir.foreach(SECRET_FOLDER) do |filename|
|
13
|
+
next if ['.','..'].include?(filename)
|
14
|
+
encrypt_and_rehash(filename)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrypt_secrets
|
19
|
+
Dir.foreach(SAFE_FOLDER) do |filename|
|
20
|
+
next unless filename.match(/\.enc$/)
|
21
|
+
decrypt(filename)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def encrypt_and_rehash(filename)
|
28
|
+
if shas_match?(filename)
|
29
|
+
puts "Skipping: #{filename} (shas match)"
|
30
|
+
else
|
31
|
+
puts "Encrypting: #{filename}"
|
32
|
+
system("#{gpg_command} --output #{SAFE_FOLDER}/#{filename}.enc #{recipient_args.join(' ')} --encrypt #{SECRET_FOLDER}/#{filename}")
|
33
|
+
hash(filename)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def hash(filename)
|
38
|
+
puts "Hashing: #{filename}"
|
39
|
+
system("sha512sum #{SECRET_FOLDER}/#{filename} > #{SAFE_FOLDER}/#{filename}.sha")
|
40
|
+
end
|
41
|
+
|
42
|
+
def decrypt(filename)
|
43
|
+
if shas_match?(decrypted_filename(filename))
|
44
|
+
puts "Skipping: #{filename} (shas match)"
|
45
|
+
else
|
46
|
+
puts "Decrypting: #{filename}"
|
47
|
+
system("#{gpg_command} --output #{SECRET_FOLDER}/#{decrypted_filename(filename)} --decrypt #{SAFE_FOLDER}/#{filename}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def recipient_args
|
52
|
+
recipient_fingerprints.split("\n").map {|fingerprint| "--recipient #{fingerprint}" }
|
53
|
+
end
|
54
|
+
|
55
|
+
def recipient_fingerprints
|
56
|
+
"fingerprint\n"
|
57
|
+
`#{gpg_command} --fingerprint --with-colons | grep fpr | cut -d ":" -f10`
|
58
|
+
end
|
59
|
+
|
60
|
+
def decrypted_filename(filename)
|
61
|
+
filename.match(/(.+)\.enc$/)[1]
|
62
|
+
end
|
63
|
+
|
64
|
+
def gpg_command
|
65
|
+
"gpg --keyring #{KEYRING_PATH} --no-default-keyring"
|
66
|
+
end
|
67
|
+
|
68
|
+
def shas_match?(filename)
|
69
|
+
File.exists?(stored_sha_path(filename)) && local_sha(filename) == stored_sha(filename)
|
70
|
+
end
|
71
|
+
|
72
|
+
def local_sha(filename)
|
73
|
+
`sha512sum #{SECRET_FOLDER}/#{filename}`
|
74
|
+
end
|
75
|
+
|
76
|
+
def stored_sha(filename)
|
77
|
+
`cat #{stored_sha_path(filename)}`
|
78
|
+
end
|
79
|
+
|
80
|
+
def stored_sha_path(filename)
|
81
|
+
"#{SAFE_FOLDER}/#{filename}.sha"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Minionizer
|
2
|
+
class Initialization
|
3
|
+
SUBFOLDERS = [
|
4
|
+
'', #root folder
|
5
|
+
'/config',
|
6
|
+
'/data/encrypted_secrets',
|
7
|
+
'/data/public_keys',
|
8
|
+
'/data/secrets',
|
9
|
+
'/lib',
|
10
|
+
'/roles'
|
11
|
+
]
|
12
|
+
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
def self.create(path)
|
16
|
+
new(path).save
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(path)
|
20
|
+
@path = path
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
create_directory_structure
|
25
|
+
touch_config
|
26
|
+
ignore_secrets
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def create_directory_structure
|
32
|
+
SUBFOLDERS.each do |folder|
|
33
|
+
system("mkdir -p #{path}#{folder}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def touch_config
|
38
|
+
system("touch #{path}/config/minions.yml")
|
39
|
+
end
|
40
|
+
|
41
|
+
def ignore_secrets
|
42
|
+
system("touch #{gitignore_path}")
|
43
|
+
system("echo 'data/secrets' > #{gitignore_path}") unless secrets_ignored?
|
44
|
+
end
|
45
|
+
|
46
|
+
def secrets_ignored?
|
47
|
+
File.readlines(gitignore_path).grep(/^data\/secrets$/).any?
|
48
|
+
end
|
49
|
+
|
50
|
+
def gitignore_path
|
51
|
+
"#{path}/.gitignore"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/minionizer/session.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
require 'securerandom'
|
1
2
|
module Minionizer
|
2
3
|
class Session
|
3
|
-
attr_reader :fqdn, :username, :password, :
|
4
|
+
attr_reader :fqdn, :username, :password, :ssh_connector, :scp_connector, :command_executor
|
4
5
|
|
5
|
-
def initialize(fqdn, credentials,
|
6
|
+
def initialize(fqdn, credentials, ssh_connector=Net::SSH, scp_connector=Net::SCP, command_executor=CommandExecution)
|
6
7
|
@fqdn = fqdn
|
7
8
|
@username = credentials['username']
|
8
9
|
@password = credentials['password']
|
9
|
-
@
|
10
|
+
@ssh_connector = ssh_connector
|
11
|
+
@scp_connector = scp_connector
|
10
12
|
@command_executor = command_executor
|
11
13
|
end
|
12
14
|
|
@@ -22,26 +24,33 @@ module Minionizer
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def exec(*commands)
|
25
|
-
|
27
|
+
options = commands.last.is_a?(Hash) ? commands.pop : {}
|
28
|
+
results = commands.map { |command| execution(command, options).call }
|
26
29
|
results.length == 1 ? results.first : results
|
27
30
|
end
|
28
31
|
|
32
|
+
def scp(local_path, remote_path)
|
33
|
+
if with_sudo?
|
34
|
+
tmp_filename = "#{SecureRandom.hex}.minionizer_tempfile"
|
35
|
+
scp_connection.upload!(local_path, "#{tmp_filename}")
|
36
|
+
exec("mv #{tmp_filename} #{remote_path}")
|
37
|
+
else
|
38
|
+
scp_connection.upload!(local_path, remote_path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
29
42
|
#######
|
30
43
|
private
|
31
44
|
#######
|
32
45
|
|
33
|
-
def execution(command)
|
46
|
+
def execution(command, options={})
|
34
47
|
if with_sudo?
|
35
|
-
command_executor.new(
|
48
|
+
command_executor.new(ssh_connection, prefix_sudo(command), options)
|
36
49
|
else
|
37
|
-
command_executor.new(
|
50
|
+
command_executor.new(ssh_connection, command, options)
|
38
51
|
end
|
39
52
|
end
|
40
53
|
|
41
|
-
def connection
|
42
|
-
@connection ||= connector.start(fqdn, username, password: password)
|
43
|
-
end
|
44
|
-
|
45
54
|
def prefix_sudo(command)
|
46
55
|
%Q{sudo bash -c "#{command}"}
|
47
56
|
end
|
@@ -50,5 +59,13 @@ module Minionizer
|
|
50
59
|
@with_sudo
|
51
60
|
end
|
52
61
|
|
62
|
+
def ssh_connection
|
63
|
+
@ssh_connection ||= ssh_connector.start(fqdn, username, password: password)
|
64
|
+
end
|
65
|
+
|
66
|
+
def scp_connection
|
67
|
+
@scp_connection ||= scp_connector.start(fqdn, username, password: password)
|
68
|
+
end
|
69
|
+
|
53
70
|
end
|
54
71
|
end
|
data/lib/minionizer/version.rb
CHANGED
data/lib/minionizer.rb
CHANGED
data/minionizer.gemspec
CHANGED
@@ -11,10 +11,13 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.description = %q{Minionizer aims to be a light weight server provisioning tool without bloat or steep learning curves.}
|
12
12
|
|
13
13
|
s.add_dependency('activesupport', '~> 4.1')
|
14
|
+
s.add_dependency('binding_of_caller', '~> 0.7')
|
14
15
|
s.add_dependency('net-ssh', '~> 2.9')
|
16
|
+
s.add_dependency('net-scp', '~> 1.2')
|
15
17
|
|
16
18
|
s.add_development_dependency('fakefs', '~> 0.5')
|
17
19
|
s.add_development_dependency('mocha', '~> 1.0')
|
20
|
+
s.add_development_dependency('minitest', '~> 5.4')
|
18
21
|
s.add_development_dependency('rake', '~> 10.3')
|
19
22
|
|
20
23
|
s.files = `git ls-files`.split("\n")
|
@@ -88,8 +88,8 @@ module Minionizer
|
|
88
88
|
group: ownername
|
89
89
|
}}
|
90
90
|
let(:code) { %Q{
|
91
|
-
session.sudo do
|
92
|
-
Minionizer::FileInjection.new(
|
91
|
+
session.sudo do |sudo_session|
|
92
|
+
Minionizer::FileInjection.new(sudo_session, #{options}).call
|
93
93
|
end
|
94
94
|
} }
|
95
95
|
|
@@ -107,6 +107,7 @@ module Minionizer
|
|
107
107
|
it 'injects a file' do
|
108
108
|
2.times { assert_throws(:high_five) { minionization.call } }
|
109
109
|
assert_file_exists(target_path)
|
110
|
+
assert_equal('FooBar', session.sudo("cat #{target_path}")[:stdout])
|
110
111
|
mode = session.exec("stat --format=%a #{target_path}")[:stdout]
|
111
112
|
assert_equal('700',mode)
|
112
113
|
owner = session.exec("stat --format=%U #{target_path}")[:stdout]
|
@@ -137,11 +138,15 @@ module Minionizer
|
|
137
138
|
after do
|
138
139
|
File.delete("#{source_path}/foobar.pubkey")
|
139
140
|
File.delete("#{source_path}/foobaz.pubkey")
|
141
|
+
skip unless minion_available?
|
142
|
+
session.sudo("userdel #{target_username}")
|
140
143
|
end
|
141
144
|
|
142
145
|
it 'injects public keys' do
|
143
146
|
2.times { assert_throws(:high_five) { minionization.call } }
|
144
|
-
assert_file_exists("
|
147
|
+
assert_file_exists("/home/#{target_username}/.ssh/authorized_keys")
|
148
|
+
assert_match(/FooBar/, session.sudo("cat ~#{target_username}/.ssh/authorized_keys")[:stdout])
|
149
|
+
assert_match(/FooBaz/, session.sudo("cat ~#{target_username}/.ssh/authorized_keys")[:stdout])
|
145
150
|
end
|
146
151
|
end
|
147
152
|
|
@@ -173,7 +178,7 @@ module Minionizer
|
|
173
178
|
end
|
174
179
|
|
175
180
|
def link_exists?(path, parameter = :e)
|
176
|
-
session.
|
181
|
+
session.sudo("[ -#{parameter} #{path} ] && echo 'yes' || echo 'no'")[:stdout] == 'yes'
|
177
182
|
end
|
178
183
|
|
179
184
|
def assert_user_exists(username)
|