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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd7dc40fdbe374f013b0e9e807c3bc974215a951
4
- data.tar.gz: ddcb859dd3f2d985341553cf923cc4de4422b19d
3
+ metadata.gz: 82bef054c00cc362c8c6bde7b0557b1ec7ea8966
4
+ data.tar.gz: 496df270bcb0c2c4fe65d3bb291ef5d9d9673e69
5
5
  SHA512:
6
- metadata.gz: 2556cc919d6e3dc13fafd41406726bdd8bc640c5b27d75ca67614a4b14a06c9d465f08558e3943caa6daf0752e1a49d4044672996cfc6206db660336a922ed6f
7
- data.tar.gz: b73ba6895d71e3b8d3aa9bd37dc3c01368bd160f71383cb09feb49cb7112738346620d283b5448dbcb4eb024184eee9bce9322c9c14ec0afa3f022f9edfb200d
6
+ metadata.gz: b437e19e86a6bc97c546ba32193a77812c0710c50b375927ddd0c49b129fab0e96b4b91ab9e6456a409b10061e7ca04398220bb125c4a85a7c56382d06d67621
7
+ data.tar.gz: ef1cc521975e07f8fbee54cd3e3f5415d0610f2dfb6bc6cb5a0f5b79725d05942f4c9f1baf8e915729ecb6a9d32a9bdf111c936f233f7c76dd9abf1797668588
@@ -0,0 +1 @@
1
+ service_name: 'travis-ci'
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  *.swp
2
2
  test/.vagrant
3
+ coverage
3
4
 
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.0.0"
4
+ - "2.1.1"
5
+ # uncomment this line if your project needs to run something other than `rake`:
6
+ # # script: bundle exec rspec spec
data/Gemfile CHANGED
@@ -1,2 +1,4 @@
1
1
  source 'http://rubygems.org'
2
2
  gemspec
3
+
4
+ gem 'coveralls', require: false
@@ -2,35 +2,57 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  minionizer (0.0.1)
5
- activesupport
6
- net-ssh
5
+ activesupport (~> 4.1)
6
+ net-ssh (~> 2.9)
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
10
10
  specs:
11
- activesupport (4.0.4)
11
+ activesupport (4.1.1)
12
12
  i18n (~> 0.6, >= 0.6.9)
13
- minitest (~> 4.2)
14
- multi_json (~> 1.3)
13
+ json (~> 1.7, >= 1.7.7)
14
+ minitest (~> 5.1)
15
15
  thread_safe (~> 0.1)
16
- tzinfo (~> 0.3.37)
17
- atomic (1.1.16)
16
+ tzinfo (~> 1.1)
17
+ coveralls (0.7.0)
18
+ multi_json (~> 1.3)
19
+ rest-client
20
+ simplecov (>= 0.7)
21
+ term-ansicolor
22
+ thor
23
+ docile (1.1.3)
18
24
  fakefs (0.5.2)
19
25
  i18n (0.6.9)
26
+ json (1.8.1)
20
27
  metaclass (0.0.4)
21
- minitest (4.7.5)
28
+ mime-types (2.2)
29
+ minitest (5.3.3)
22
30
  mocha (1.0.0)
23
31
  metaclass (~> 0.0.1)
24
- multi_json (1.9.2)
25
- net-ssh (2.8.0)
26
- thread_safe (0.3.1)
27
- atomic (>= 1.1.7, < 2)
28
- tzinfo (0.3.39)
32
+ multi_json (1.10.0)
33
+ net-ssh (2.9.0)
34
+ rake (10.3.1)
35
+ rest-client (1.6.7)
36
+ mime-types (>= 1.16)
37
+ simplecov (0.8.2)
38
+ docile (~> 1.1.0)
39
+ multi_json
40
+ simplecov-html (~> 0.8.0)
41
+ simplecov-html (0.8.0)
42
+ term-ansicolor (1.3.0)
43
+ tins (~> 1.0)
44
+ thor (0.19.1)
45
+ thread_safe (0.3.3)
46
+ tins (1.1.0)
47
+ tzinfo (1.1.0)
48
+ thread_safe (~> 0.1)
29
49
 
30
50
  PLATFORMS
31
51
  ruby
32
52
 
33
53
  DEPENDENCIES
34
- fakefs
54
+ coveralls
55
+ fakefs (~> 0.5)
35
56
  minionizer!
36
- mocha
57
+ mocha (~> 1.0)
58
+ rake (~> 10.3)
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
1
  [![Code Climate](https://codeclimate.com/github/jsgarvin/minionizer.png)](https://codeclimate.com/github/jsgarvin/minionizer)
2
+ [![Build Status](https://travis-ci.org/jsgarvin/minionizer.svg?branch=master)](https://travis-ci.org/jsgarvin/minionizer)
3
+ [![Coverage Status](https://coveralls.io/repos/jsgarvin/minionizer/badge.png)](https://coveralls.io/r/jsgarvin/minionizer)
4
+ [![Gem Version](https://badge.fury.io/rb/minionizer.svg)](http://badge.fury.io/rb/minionizer)
5
+ [![Dependency Status](https://gemnasium.com/jsgarvin/minionizer.svg)](https://gemnasium.com/jsgarvin/minionizer)
2
6
 
3
7
  # Minionizer
4
8
 
data/Rakefile CHANGED
@@ -18,6 +18,9 @@ namespace :test do
18
18
  relay_output(vagrant_command(:up))
19
19
  unless snapshot_plugin_installed?
20
20
  relay_output(vagrant_command('plugin install vagrant-vbox-snapshot'))
21
+ end
22
+ unless test_snapshot_exists?
23
+ sleep 5
21
24
  relay_output(vagrant_command('snapshot take blank-test-slate'))
22
25
  end
23
26
  end
@@ -36,6 +39,10 @@ def snapshot_plugin_installed?
36
39
  Gem::Version.new(vagrant_plugins['vagrant-vbox-snapshot']) >= Gem::Version.new('0.0.4')
37
40
  end
38
41
 
42
+ def test_snapshot_exists?
43
+ vagrant_snapshots.include?('blank-test-slate')
44
+ end
45
+
39
46
  def vagrant_plugins
40
47
  Hash.new.tap do |hash|
41
48
  `cd #{vagrant_path}; vagrant plugin list`.split("\n").each do |plugin_string|
@@ -46,6 +53,16 @@ def vagrant_plugins
46
53
  end
47
54
  end
48
55
 
56
+ def vagrant_snapshots
57
+ Array.new.tap do |snapshots|
58
+ `cd #{vagrant_path}; vagrant snapshot list`.split("\n").each do |snapshot_string|
59
+ if snapshot_string.match(/Name\: ([^\(]+)/)
60
+ snapshots << $1
61
+ end
62
+ end
63
+ end
64
+ end
65
+
49
66
  def vagrant_path
50
67
  File.expand_path('../test', __FILE__)
51
68
  end
@@ -1,19 +1,22 @@
1
1
  module Minionizer
2
- class FileInjection
3
- attr_reader :session
2
+ class FileInjection < TaskTemplate
4
3
 
5
- def initialize(session)
6
- @session = session
7
- end
8
-
9
- def inject(source, target)
10
- session.exec("echo '#{contents_from(source)}' > #{target}")
4
+ def call
5
+ session.exec("mkdir --parents #{target_directory}")
6
+ session.exec("echo '#{contents_from(source_path)}' > #{target_path}")
7
+ session.exec("chmod #{mode} #{target_path}") if respond_to?(:mode)
8
+ session.exec("chown #{owner} #{target_path}") if respond_to?(:owner)
9
+ session.exec("chgrp #{group} #{target_path}") if respond_to?(:group)
11
10
  end
12
11
 
13
12
  #######
14
13
  private
15
14
  #######
16
15
 
16
+ def target_directory
17
+ File.dirname(target_path)
18
+ end
19
+
17
20
  def contents_from(source)
18
21
  File.open(source).read.strip
19
22
  end
@@ -0,0 +1,12 @@
1
+ module Minionizer
2
+ class FolderCreation < TaskTemplate
3
+
4
+ def call
5
+ session.exec("mkdir --parents #{path}")
6
+ session.exec("chmod #{mode} #{path}") if respond_to?(:mode)
7
+ session.exec("chown #{owner} #{path}") if respond_to?(:owner)
8
+ session.exec("chgrp #{group} #{path}") if respond_to?(:group)
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,40 @@
1
+ module Minionizer
2
+ class PublicSshKeyInjection < TaskTemplate
3
+
4
+ def call
5
+ file_injection.call
6
+ ensure
7
+ temp_file.unlink
8
+ end
9
+
10
+ #######
11
+ private
12
+ #######
13
+
14
+ def file_injection
15
+ @file_injection ||= file_injection_creator.new(session, file_injection_options)
16
+ end
17
+
18
+ def file_injection_creator
19
+ options[:file_injection_creator] ||= FileInjection
20
+ end
21
+
22
+ def file_injection_options
23
+ {
24
+ source_path: temp_file.path,
25
+ target_path: "~#{target_username}/.ssh/authorized_keys",
26
+ owner: target_username,
27
+ group: target_username
28
+ }
29
+ end
30
+
31
+ def temp_file
32
+ @temp_file ||= Tempfile.new('MinionizerPublicKeys').tap do |temp_file|
33
+ Dir.glob("data/public_keys/*.pubkey") do |key_file|
34
+ temp_file.puts File.open(key_file).read
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ module Minionizer
2
+ class TaskTemplate
3
+ attr_reader :session, :options
4
+
5
+ def initialize(session, options = {})
6
+ @session = session
7
+ @options = options.with_indifferent_access
8
+ end
9
+
10
+ def method_missing(method_name, *arguments, &block)
11
+ if options.key?(method_name)
12
+ options[method_name]
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def respond_to?(method_name, include_private = false)
19
+ options.key?(method_name) || super
20
+ end
21
+
22
+ end
23
+ end
24
+
25
+
@@ -0,0 +1,18 @@
1
+ module Minionizer
2
+ class UserCreation < TaskTemplate
3
+
4
+ def call
5
+ unless user_exists?
6
+ session.exec("adduser --disabled-password --gecos '#{name}' #{username}")
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def user_exists?
13
+ session.exec("id #{username}")
14
+ rescue CommandExecution::CommandError
15
+ return false
16
+ end
17
+ end
18
+ end
@@ -1,6 +1,9 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
1
2
  require 'active_support/inflector'
3
+ require 'erb'
2
4
  require 'net/ssh'
3
5
  require 'singleton'
4
6
  require 'yaml'
5
7
 
8
+ require_relative 'core/task_template'
6
9
  Dir[File.dirname(__FILE__) + '/**/*.rb'].each { |file| require file }
@@ -0,0 +1,60 @@
1
+ module Minionizer
2
+
3
+ class CommandExecution
4
+ class CommandError < StandardError; end
5
+ class InvocationError < StandardError; end
6
+
7
+ attr_reader :connection, :command
8
+
9
+ def initialize(connection, command)
10
+ @connection = connection
11
+ @command = command
12
+ end
13
+
14
+ def call
15
+ execute_command
16
+ check_exit_code
17
+ return results
18
+ end
19
+
20
+ private
21
+
22
+ def execute_command
23
+ connection.open_channel do |channel|
24
+ execute_command_inside_channel(channel)
25
+ end
26
+ connection.loop
27
+ end
28
+
29
+ def execute_command_inside_channel(channel)
30
+ channel.exec(command) do |_, success|
31
+ if success
32
+ compile_results(channel)
33
+ else
34
+ raise InvocationError.new("Failed to invoke command: #{command} ")
35
+ end
36
+ end
37
+ end
38
+
39
+ def check_exit_code
40
+ if exit_failure?
41
+ raise CommandError.new("\"#{command}\" returned exit code #{results[:exit_code]}/#{results[:exit_signal]}/#{results[:stderr]}")
42
+ end
43
+ end
44
+
45
+ def exit_failure?
46
+ results[:exit_code].to_i != 0
47
+ end
48
+
49
+ def results
50
+ @results ||= {stdout: '', stderr: ''}
51
+ end
52
+
53
+ def compile_results(channel)
54
+ channel.on_data { |_, data| results[:stdout] += data.strip }
55
+ channel.on_extended_data { |_, data| results[:stderr] += data.to_s }
56
+ channel.on_request('exit-status') { |_,data| results[:exit_code] = data.read_long }
57
+ channel.on_request('exit-signal') { |_,data| results[:exit_signal] = data.read_string }
58
+ end
59
+ end
60
+ end
@@ -1,39 +1,54 @@
1
1
  module Minionizer
2
2
  class Session
3
- attr_reader :fqdn, :username, :password, :connector
3
+ attr_reader :fqdn, :username, :password, :connector, :command_executor
4
4
 
5
- def initialize(fqdn, credentials, connector = Net::SSH)
5
+ def initialize(fqdn, credentials, connector = Net::SSH, command_executor = CommandExecution)
6
6
  @fqdn = fqdn
7
7
  @username = credentials['username']
8
8
  @password = credentials['password']
9
9
  @connector = connector
10
+ @command_executor = command_executor
10
11
  end
11
12
 
12
- def exec(arg)
13
- if arg.is_a?(Array)
14
- arg.map { |command| exec_single_command(command) }
13
+ def sudo(*commands)
14
+ @with_sudo = true
15
+ if commands.any?
16
+ return exec(*commands)
15
17
  else
16
- exec_single_command(arg)
18
+ yield self
17
19
  end
20
+ ensure
21
+ @with_sudo = false
22
+ end
23
+
24
+ def exec(*commands)
25
+ results = commands.map { |command| execution(command).call }
26
+ results.length == 1 ? results.first : results
18
27
  end
19
28
 
20
29
  #######
21
30
  private
22
31
  #######
23
32
 
24
- def exec_single_command(command)
25
- connection.exec(command) do |channel, stream, output|
26
- if stream == :stdout
27
- return output.strip
28
- else
29
- raise StandardError.new(output)
30
- end
33
+ def execution(command)
34
+ if with_sudo?
35
+ command_executor.new(connection, prefix_sudo(command))
36
+ else
37
+ command_executor.new(connection, command)
31
38
  end
32
- connection.loop
33
39
  end
34
40
 
35
41
  def connection
36
42
  @connection ||= connector.start(fqdn, username, password: password)
37
43
  end
44
+
45
+ def prefix_sudo(command)
46
+ %Q{sudo bash -c "#{command}"}
47
+ end
48
+
49
+ def with_sudo?
50
+ @with_sudo
51
+ end
52
+
38
53
  end
39
54
  end
@@ -1,3 +1,3 @@
1
1
  module Minionizer
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -10,11 +10,12 @@ Gem::Specification.new do |s|
10
10
  s.summary = %q{Simple server provisioning and management.}
11
11
  s.description = %q{Minionizer aims to be a light weight server provisioning tool without bloat or steep learning curves.}
12
12
 
13
- s.add_dependency('activesupport')
14
- s.add_dependency('net-ssh')
13
+ s.add_dependency('activesupport', '~> 4.1')
14
+ s.add_dependency('net-ssh', '~> 2.9')
15
15
 
16
- s.add_development_dependency('fakefs')
17
- s.add_development_dependency('mocha')
16
+ s.add_development_dependency('fakefs', '~> 0.5')
17
+ s.add_development_dependency('mocha', '~> 1.0')
18
+ s.add_development_dependency('rake', '~> 10.3')
18
19
 
19
20
  s.files = `git ls-files`.split("\n")
20
21
  s.test_files = `git ls-files -- test/*`.split("\n")