jisota 0.0.1
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 +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
|