minionizer 0.0.1 → 0.1.0

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