jisota 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +254 -0
- data/Rakefile +1 -0
- data/jisota.gemspec +30 -0
- data/lib/jisota/collection.rb +52 -0
- data/lib/jisota/command_script.rb +17 -0
- data/lib/jisota/composite_script.rb +23 -0
- data/lib/jisota/configuration.rb +60 -0
- data/lib/jisota/errors.rb +3 -0
- data/lib/jisota/file_script.rb +21 -0
- data/lib/jisota/logger.rb +74 -0
- data/lib/jisota/package.rb +35 -0
- data/lib/jisota/package_script.rb +54 -0
- data/lib/jisota/packages/apt.rb +17 -0
- data/lib/jisota/packages/ruby.rb +29 -0
- data/lib/jisota/param.rb +34 -0
- data/lib/jisota/param_parser.rb +70 -0
- data/lib/jisota/provisioner.rb +24 -0
- data/lib/jisota/role.rb +13 -0
- data/lib/jisota/script_block.rb +66 -0
- data/lib/jisota/server.rb +12 -0
- data/lib/jisota/ssh_engine.rb +11 -0
- data/lib/jisota/ssh_session.rb +48 -0
- data/lib/jisota/upload_file.rb +3 -0
- data/lib/jisota/version.rb +3 -0
- data/lib/jisota.rb +63 -0
- data/spec/acceptance/simple_script_spec.rb +56 -0
- data/spec/lib/jisota/collection_spec.rb +44 -0
- data/spec/lib/jisota/command_script_spec.rb +22 -0
- data/spec/lib/jisota/composite_script_spec.rb +37 -0
- data/spec/lib/jisota/configuration_spec.rb +82 -0
- data/spec/lib/jisota/file_script_spec.rb +22 -0
- data/spec/lib/jisota/logger_spec.rb +34 -0
- data/spec/lib/jisota/package_script_spec.rb +43 -0
- data/spec/lib/jisota/package_spec.rb +74 -0
- data/spec/lib/jisota/packages/apt_spec.rb +15 -0
- data/spec/lib/jisota/packages/ruby_spec.rb +14 -0
- data/spec/lib/jisota/role_spec.rb +31 -0
- data/spec/lib/jisota/script_block_spec.rb +51 -0
- data/spec/lib/jisota/server_spec.rb +37 -0
- data/spec/lib/jisota_spec.rb +34 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/test_files/foo +1 -0
- metadata +221 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
module Jisota
|
2
|
+
##
|
3
|
+
# Part of the Script duck type
|
4
|
+
#
|
5
|
+
# Executes an entire package, which in turn might execute other packages.
|
6
|
+
# The args are parsed with the ParamParser to match args with params.
|
7
|
+
#
|
8
|
+
# If the package has a `verify_block`, that will be executed first. If the
|
9
|
+
# result of verify is success, the `run_block` will not be executed.
|
10
|
+
class PackageScript
|
11
|
+
attr_accessor :package, :args, :packages
|
12
|
+
|
13
|
+
def initialize(package, args = [], packages = Collection.new)
|
14
|
+
@package = package
|
15
|
+
@args = args
|
16
|
+
@packages = packages
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(ssh_session, logger = nil)
|
20
|
+
logger.package(self) if logger
|
21
|
+
logger.indent if logger
|
22
|
+
result = execute_verify_and_run(ssh_session, logger)
|
23
|
+
logger.outdent if logger
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"#{package.name} #{args.map(&:inspect).join(", ")}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def execute_verify_and_run(ssh_session, logger)
|
34
|
+
parsed_params = ParamParser.new(package.params, args).parse
|
35
|
+
if package.verify_block
|
36
|
+
verify_script = package.verify_block.evaluate(parsed_params, packages)
|
37
|
+
result = verify_script.execute(ssh_session, logger)
|
38
|
+
if result
|
39
|
+
logger.package_cancelled_by_verify(self) if logger
|
40
|
+
true
|
41
|
+
else
|
42
|
+
execute_run(parsed_params, ssh_session, logger)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
execute_run(parsed_params, ssh_session, logger)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def execute_run(parsed_params, ssh_session, logger)
|
50
|
+
run_script = package.run_block.evaluate(parsed_params, packages)
|
51
|
+
run_script.execute(ssh_session, logger)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'jisota'
|
2
|
+
|
3
|
+
Jisota.global_config do
|
4
|
+
package :apt do
|
5
|
+
description "Installs packages with apt-get"
|
6
|
+
param :packages, required: true, splat: true
|
7
|
+
|
8
|
+
run do
|
9
|
+
cmd "sudo apt-get install -y #{packages.join(" ")}"
|
10
|
+
end
|
11
|
+
|
12
|
+
verify do
|
13
|
+
cmd "dpkg -s #{packages.join(" ")}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'jisota'
|
2
|
+
|
3
|
+
Jisota.global_config do
|
4
|
+
package :ruby do
|
5
|
+
description "Installs ruby from source"
|
6
|
+
param :version, required: true
|
7
|
+
param :tmp_dir, default: "~/tmp"
|
8
|
+
|
9
|
+
run do
|
10
|
+
minor_version = version.match(/\d+\.\d+/)[0]
|
11
|
+
|
12
|
+
apt *%w(libffi-dev libssl-dev zlib1g-dev libreadline-dev)
|
13
|
+
cmd %Q{
|
14
|
+
mkdir -p #{tmp_dir} &&
|
15
|
+
cd #{tmp_dir} &&
|
16
|
+
wget --continue --no-verbose http://cache.ruby-lang.org/pub/ruby/#{minor_version}/ruby-#{version}.tar.gz &&
|
17
|
+
tar -zxvf ruby-#{version}.tar.gz &&
|
18
|
+
cd ruby-#{version} &&
|
19
|
+
./configure &&
|
20
|
+
make &&
|
21
|
+
sudo make install &&
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
verify do
|
26
|
+
cmd "ruby -v | grep #{version}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/jisota/param.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Jisota
|
2
|
+
##
|
3
|
+
# A package param
|
4
|
+
#
|
5
|
+
# Options:
|
6
|
+
#
|
7
|
+
# [default] Provide a default value for the param
|
8
|
+
# [required] If true, an error is raised unless the param has a value
|
9
|
+
# [splat] Will get remaining unnamed arguments
|
10
|
+
class Param
|
11
|
+
attr_reader :name, :options
|
12
|
+
|
13
|
+
def initialize(name, options = {})
|
14
|
+
@name = name
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def default?
|
19
|
+
options.has_key?(:default)
|
20
|
+
end
|
21
|
+
|
22
|
+
def default
|
23
|
+
options[:default]
|
24
|
+
end
|
25
|
+
|
26
|
+
def required?
|
27
|
+
!!options[:required]
|
28
|
+
end
|
29
|
+
|
30
|
+
def splat?
|
31
|
+
!!options[:splat]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Jisota
|
2
|
+
class ParamParser
|
3
|
+
attr_reader :params, :args
|
4
|
+
|
5
|
+
def initialize(params, args)
|
6
|
+
@original_params = params.dup
|
7
|
+
@params = params.dup
|
8
|
+
@args = args.dup
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse
|
12
|
+
Hash.new.tap do |result|
|
13
|
+
init_splat_params(result)
|
14
|
+
add_implicit_args(result)
|
15
|
+
add_hash_args(result)
|
16
|
+
set_remaining_params(result)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def init_splat_params(result)
|
23
|
+
params.select(&:splat?).each do |param|
|
24
|
+
result[param.name] = []
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_implicit_args(result)
|
29
|
+
param = nil
|
30
|
+
while args.first && !args.first.is_a?(Hash)
|
31
|
+
param = params.shift unless param && param.splat?
|
32
|
+
arg = args.shift
|
33
|
+
raise ParameterError, "No parameter for implicit argument #{arg}" unless param
|
34
|
+
if param.splat?
|
35
|
+
result[param.name] << arg
|
36
|
+
else
|
37
|
+
result[param.name] = arg
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_hash_args(result)
|
43
|
+
return if args.empty?
|
44
|
+
raise ParameterError, "Hash parameters must be last in argument list" if args.size > 1
|
45
|
+
args.first.each do |key, value|
|
46
|
+
param = @original_params.select { |p| p.name == key }.first
|
47
|
+
raise ParameterError, "No parameter with name #{key.inspect}" unless param
|
48
|
+
if result.has_key?(key)
|
49
|
+
if param.splat?
|
50
|
+
raise ParameterError, "Splat parameter #{key.inspect} only accepts unnamed arguments"
|
51
|
+
else
|
52
|
+
raise ParameterError, "Parameter #{key.inspect} already set with an implicit argument"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
result[key] = value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_remaining_params(result)
|
60
|
+
params.each do |param|
|
61
|
+
unless result.has_key?(param.name)
|
62
|
+
if param.required? && !param.default?
|
63
|
+
raise ParameterError, "Parameter #{param.name.inspect} is required"
|
64
|
+
end
|
65
|
+
result[param.name] = param.default? ? param.default : nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Jisota
|
2
|
+
##
|
3
|
+
# Reads configuration and runs it
|
4
|
+
class Provisioner
|
5
|
+
def run(configuration, logger)
|
6
|
+
packages = configuration.packages.merge(Jisota.global_packages) { |_, left, _| left }
|
7
|
+
configuration.each_server do |server|
|
8
|
+
run_server(server, configuration.ssh_engine, configuration.roles, packages, logger)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def run_server(server, ssh, roles, packages, logger)
|
15
|
+
ssh.start(user: server.user, host: server.host) do |ssh_session|
|
16
|
+
server.roles.each do |role_name|
|
17
|
+
role = roles[role_name]
|
18
|
+
script = role.script_block.evaluate({}, packages)
|
19
|
+
script.execute(ssh_session, logger)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/jisota/role.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Jisota
|
2
|
+
##
|
3
|
+
# DSL for creating a CompositeScript
|
4
|
+
#
|
5
|
+
# Methods in the DSL:
|
6
|
+
#
|
7
|
+
# [cmd] Add a CommandScript
|
8
|
+
# [upload] Add a FileScript
|
9
|
+
# [<arg name>] The value of the argument
|
10
|
+
# [<package name>] Add a PackageScript
|
11
|
+
class ScriptBlock
|
12
|
+
attr_accessor :block
|
13
|
+
|
14
|
+
def initialize(options = {}, &block)
|
15
|
+
@block = block
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(args = {}, packages = Collection.new)
|
19
|
+
CompositeScript.new.tap do |script|
|
20
|
+
dsl = DSL.new(script, args, packages)
|
21
|
+
dsl.instance_eval(&block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class DSL
|
26
|
+
def initialize(script, args, packages)
|
27
|
+
@script = script
|
28
|
+
@args = args
|
29
|
+
@packages = packages
|
30
|
+
end
|
31
|
+
|
32
|
+
def cmd(command)
|
33
|
+
@script.scripts << CommandScript.new(command)
|
34
|
+
end
|
35
|
+
|
36
|
+
def upload(from:, to: )
|
37
|
+
@script.scripts << FileScript.new(UploadFile.new(from, to))
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(method, *args, &block)
|
41
|
+
if has_argument?(method)
|
42
|
+
get_argument(method)
|
43
|
+
else
|
44
|
+
add_package_script(method, args) || super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def has_argument?(name)
|
51
|
+
@args.has_key?(name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_argument(name)
|
55
|
+
@args.fetch(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_package_script(name, args)
|
59
|
+
if @packages.has_key?(name)
|
60
|
+
package = @packages[name]
|
61
|
+
@script.scripts << PackageScript.new(package, args, @packages)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'net/scp'
|
2
|
+
|
3
|
+
module Jisota
|
4
|
+
class SSHSession
|
5
|
+
|
6
|
+
class FileNotFoundError < StandardError; end
|
7
|
+
|
8
|
+
def initialize(session = nil, options = {})
|
9
|
+
@session = session
|
10
|
+
@scp_engine = options.fetch(:scp_engine) { Net::SCP }
|
11
|
+
end
|
12
|
+
|
13
|
+
def command(command, logger = nil)
|
14
|
+
exit_code = nil
|
15
|
+
exit_signal = nil
|
16
|
+
error_message = ""
|
17
|
+
|
18
|
+
logger.command(command) if logger
|
19
|
+
|
20
|
+
@session.open_channel do |channel|
|
21
|
+
channel.exec(command) do
|
22
|
+
channel.on_data { |_, data| logger.info(data) if logger }
|
23
|
+
channel.on_extended_data { |_, _, data| logger.warn(data) if logger; error_message << data }
|
24
|
+
channel.on_request("exit-status") { |_, data| exit_code = data.read_long }
|
25
|
+
channel.on_request("exit-signal") { |_, data| exit_signal = data.read_long }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@session.loop
|
29
|
+
|
30
|
+
if exit_code == 0
|
31
|
+
true
|
32
|
+
else
|
33
|
+
logger.error("Error running #{command}:")
|
34
|
+
logger.error(error_message)
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def upload(file, logger = nil)
|
40
|
+
raise FileNotFoundError, "Upload file not found: #{file.from}" unless File.exist?(file.from)
|
41
|
+
tmp_file = "tmp/jisota/#{SecureRandom.hex}"
|
42
|
+
command("mkdir -p tmp/jisota", logger)
|
43
|
+
logger.upload(from: file.from, to: tmp_file) if logger
|
44
|
+
@scp_engine.new(@session).upload!(file.from, tmp_file, recursive: false)
|
45
|
+
command("sudo mkdir -p `dirname #{file.to}` && sudo mv #{tmp_file} #{file.to}", logger)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/jisota.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Jisota
|
2
|
+
|
3
|
+
##
|
4
|
+
# Runs a provision with the given configuration
|
5
|
+
#
|
6
|
+
# Options allow default depenedencies to be overridden. Example:
|
7
|
+
#
|
8
|
+
# Jisota.run(config, logger: Jisota::Logger.new(verbose: true))
|
9
|
+
#
|
10
|
+
def self.run(configuration, options = {})
|
11
|
+
provisioner = options.fetch(:provisioner) { Provisioner.new }
|
12
|
+
logger = options.fetch(:logger) { Logger.new }
|
13
|
+
provisioner.run(configuration, logger)
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Shorthand for defining a new configuration
|
18
|
+
#
|
19
|
+
# Example:
|
20
|
+
#
|
21
|
+
# config = Jisota.config do
|
22
|
+
# role :app do
|
23
|
+
# ruby version: "2.1.1"
|
24
|
+
# postgresql
|
25
|
+
# end
|
26
|
+
# server "123.456.789.000", user: "john", roles: :app
|
27
|
+
# end
|
28
|
+
def self.config(&block)
|
29
|
+
Configuration.new(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Shorthand for defining packages in the global package manager
|
34
|
+
# The global package manager is useful when sharing a package between
|
35
|
+
# multiple projects or configurations
|
36
|
+
#
|
37
|
+
# Example:
|
38
|
+
#
|
39
|
+
# Jisota.global_config do
|
40
|
+
# package :apt do
|
41
|
+
# param :packages, splat: true, required: true
|
42
|
+
# run { cmd "sudo apt-get install -y #{packages.join(" ")}" }
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
def self.global_config(&block)
|
46
|
+
config = config(&block)
|
47
|
+
config.packages.each do |package|
|
48
|
+
global_packages << package
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# The global package manager
|
54
|
+
#
|
55
|
+
# All build-in packages also live here
|
56
|
+
# Local packages with the same name will have precedence over these.
|
57
|
+
def self.global_packages
|
58
|
+
@global_packages ||= Collection.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
Dir[File.expand_path("../jisota/*.rb", __FILE__)].each { |file| require file }
|
63
|
+
Dir[File.expand_path("../jisota/packages/*.rb", __FILE__)].each { |file| require file }
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Jisota
|
4
|
+
describe "Execute a simple script" do
|
5
|
+
let(:ssh_engine) do
|
6
|
+
class_double(SSHEngine).tap do |ssh_engine|
|
7
|
+
allow(ssh_engine).to receive(:start).and_yield(ssh_session)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:ssh_session) { instance_double(SSHSession, command: true, upload: true) }
|
12
|
+
|
13
|
+
example "Simple script with a package" do
|
14
|
+
run_for_real = ENV["ACCEPTANCE_TEST"] == "run_for_real"
|
15
|
+
|
16
|
+
host = run_for_real ? ENV["ACCEPTANCE_HOST"] : "test.jisota"
|
17
|
+
raise "Must set host with ACCEPTANCE_HOST=<host>" unless host
|
18
|
+
|
19
|
+
user = run_for_real ? ENV["ACCEPTANCE_USER"] : "john_doe"
|
20
|
+
raise "Must set user with ACCEPTANCE_USER=<user>" unless user
|
21
|
+
|
22
|
+
Jisota.global_config do
|
23
|
+
package :touch_global do
|
24
|
+
param :target
|
25
|
+
run { cmd "touch #{target}" }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
config = Jisota.config do
|
30
|
+
package :touch do
|
31
|
+
param :target
|
32
|
+
run { cmd "touch #{target}" }
|
33
|
+
end
|
34
|
+
|
35
|
+
role :app do
|
36
|
+
cmd "touch foo"
|
37
|
+
touch "bar"
|
38
|
+
touch_global "baz"
|
39
|
+
upload from: "spec/test_files/foo", to: "uploads/foo"
|
40
|
+
end
|
41
|
+
|
42
|
+
server host, user: user, roles: :app
|
43
|
+
end
|
44
|
+
|
45
|
+
config.ssh_engine = ssh_engine unless run_for_real
|
46
|
+
Jisota.run(config)
|
47
|
+
|
48
|
+
unless run_for_real
|
49
|
+
expect(ssh_engine).to have_received(:start).with(user: "john_doe", host: "test.jisota")
|
50
|
+
expect(ssh_session).to have_received(:command).with("touch foo", anything)
|
51
|
+
expect(ssh_session).to have_received(:command).with("touch bar", anything)
|
52
|
+
expect(ssh_session).to have_received(:upload).with(UploadFile.new("spec/test_files/foo", "uploads/foo"), anything)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Jisota
|
4
|
+
describe Collection do
|
5
|
+
describe "#add" do
|
6
|
+
it "adds the item" do
|
7
|
+
collection = Collection.new
|
8
|
+
item = double(key: :foo)
|
9
|
+
collection.add(item)
|
10
|
+
|
11
|
+
expect(collection[:foo]).to eq(item)
|
12
|
+
expect(collection.size).to eq(1)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "will not allow duplicate key" do
|
16
|
+
collection = Collection.new
|
17
|
+
item = double(key: :foo)
|
18
|
+
collection.add(item)
|
19
|
+
|
20
|
+
expect { collection.add(item) }.to raise_error(Collection::DuplicateKeyError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#merge" do
|
25
|
+
it "returns a new collection with all items" do
|
26
|
+
collection_1 = Collection.new
|
27
|
+
collection_1.add(double(key: :foo, value: 1))
|
28
|
+
collection_1.add(double(key: :bar, value: 2))
|
29
|
+
|
30
|
+
collection_2 = Collection.new
|
31
|
+
collection_2.add(double(key: :bar, value: 3))
|
32
|
+
collection_2.add(double(key: :baz, value: 4))
|
33
|
+
|
34
|
+
result = collection_1.merge(collection_2) { |_, left, _| left }
|
35
|
+
|
36
|
+
expect(collection_1.size).to eq(2)
|
37
|
+
expect(collection_2.size).to eq(2)
|
38
|
+
expect(result.size).to eq(3)
|
39
|
+
|
40
|
+
expect(result.map(&:value)).to eq([1, 2, 4])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Jisota
|
4
|
+
describe CommandScript do
|
5
|
+
describe "#command" do
|
6
|
+
it "initializes with a command" do
|
7
|
+
expect(CommandScript.new(:foo).command).to eq(:foo)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#execute" do
|
12
|
+
it "asks ssh_session to execute" do
|
13
|
+
script = CommandScript.new(:foo)
|
14
|
+
ssh_session = instance_double(SSHSession, command: true)
|
15
|
+
result = script.execute(ssh_session)
|
16
|
+
|
17
|
+
expect(ssh_session).to have_received(:command).with(:foo, nil)
|
18
|
+
expect(result).to be true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Jisota
|
4
|
+
describe CompositeScript do
|
5
|
+
describe "#scripts" do
|
6
|
+
it "initializes to empty array" do
|
7
|
+
expect(CompositeScript.new.scripts).to eq([])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "execute" do
|
12
|
+
let(:ssh_session) { instance_double(SSHSession) }
|
13
|
+
let(:script) { CompositeScript.new }
|
14
|
+
it "executes all scripts" do
|
15
|
+
inner_1 = double(execute: true)
|
16
|
+
inner_2 = double(execute: true)
|
17
|
+
script.scripts << inner_1 << inner_2
|
18
|
+
result = script.execute(ssh_session)
|
19
|
+
|
20
|
+
expect(inner_1).to have_received(:execute).with(ssh_session, nil)
|
21
|
+
expect(inner_2).to have_received(:execute).with(ssh_session, nil)
|
22
|
+
expect(result).to be true
|
23
|
+
end
|
24
|
+
|
25
|
+
it "stops execution if one script fails" do
|
26
|
+
inner_1 = double(execute: false)
|
27
|
+
inner_2 = double(execute: true)
|
28
|
+
script.scripts << inner_1 << inner_2
|
29
|
+
result = script.execute(ssh_session)
|
30
|
+
|
31
|
+
expect(inner_1).to have_received(:execute).with(ssh_session, nil)
|
32
|
+
expect(inner_2).to_not have_received(:execute).with(ssh_session, nil)
|
33
|
+
expect(result).to be false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|