minionizer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|