jisota 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +21 -0
- data/README.md +106 -2
- data/Rakefile +10 -0
- data/jisota.gemspec +1 -0
- data/lib/jisota.rb +4 -2
- data/lib/jisota/collection.rb +2 -4
- data/lib/jisota/command_script.rb +3 -2
- data/lib/jisota/composite_script.rb +2 -2
- data/lib/jisota/configuration.rb +3 -3
- data/lib/jisota/dsl_base.rb +12 -0
- data/lib/jisota/file_script.rb +107 -9
- data/lib/jisota/nil_output.rb +14 -0
- data/lib/jisota/{logger.rb → output.rb} +16 -35
- data/lib/jisota/package.rb +2 -2
- data/lib/jisota/package_script.rb +28 -20
- data/lib/jisota/packages/gem_install.rb +17 -0
- data/lib/jisota/packages/nginx_passenger.rb +34 -0
- data/lib/jisota/packages/ruby.rb +2 -2
- data/lib/jisota/param_parser.rb +30 -19
- data/lib/jisota/provisioner.rb +7 -3
- data/lib/jisota/script_block.rb +13 -14
- data/lib/jisota/script_context.rb +24 -0
- data/lib/jisota/server.rb +2 -1
- data/lib/jisota/ssh_engine.rb +6 -2
- data/lib/jisota/ssh_session.rb +10 -15
- data/lib/jisota/version.rb +1 -1
- data/package_files/nginx_passenger/nginx_service +65 -0
- data/spec/acceptance/ruby_passenger_nginx_spec.rb +24 -0
- data/spec/acceptance/simple_script_spec.rb +8 -24
- data/spec/acceptance/upload_blocks_spec.rb +34 -0
- data/spec/lib/jisota/collection_spec.rb +10 -0
- data/spec/lib/jisota/command_script_spec.rb +4 -3
- data/spec/lib/jisota/composite_script_spec.rb +8 -6
- data/spec/lib/jisota/configuration_spec.rb +1 -3
- data/spec/lib/jisota/dsl_base_spec.rb +37 -0
- data/spec/lib/jisota/file_script_spec.rb +63 -8
- data/spec/lib/jisota/output_spec.rb +84 -0
- data/spec/lib/jisota/package_script_spec.rb +20 -8
- data/spec/lib/jisota/package_spec.rb +2 -6
- data/spec/lib/jisota/packages/apt_spec.rb +7 -4
- data/spec/lib/jisota/packages/gem_install_spec.rb +18 -0
- data/spec/lib/jisota/packages/nginx_passenger_spec.rb +17 -0
- data/spec/lib/jisota/packages/ruby_spec.rb +6 -3
- data/spec/lib/jisota/param_parser_spec.rb +105 -0
- data/spec/lib/jisota/provisioner_spec.rb +30 -0
- data/spec/lib/jisota/role_spec.rb +1 -3
- data/spec/lib/jisota/script_block_spec.rb +7 -4
- data/spec/lib/jisota/ssh_engine_spec.rb +26 -0
- data/spec/lib/jisota/ssh_session_spec.rb +53 -0
- data/spec/spec_helper.rb +11 -1
- data/spec/support/acceptance_helpers.rb +45 -0
- data/spec/test_files/nginx_default.conf +121 -0
- data/spec/vagrant/Vagrantfile +118 -0
- data/spec/vagrant/ssh_key +27 -0
- metadata +55 -7
- data/lib/jisota/upload_file.rb +0 -3
- data/spec/lib/jisota/logger_spec.rb +0 -34
data/lib/jisota/server.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module Jisota
|
2
2
|
class Server
|
3
|
-
attr_accessor :host, :user, :roles
|
3
|
+
attr_accessor :host, :user, :roles, :key
|
4
4
|
|
5
5
|
def initialize(host = nil, options = {})
|
6
6
|
@roles = []
|
7
7
|
@host = host
|
8
8
|
@user = options[:user]
|
9
9
|
@roles = Array(options[:roles]) if options[:roles]
|
10
|
+
@key = options[:key]
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
data/lib/jisota/ssh_engine.rb
CHANGED
@@ -2,8 +2,12 @@ require 'net/ssh'
|
|
2
2
|
|
3
3
|
module Jisota
|
4
4
|
class SSHEngine
|
5
|
-
def
|
6
|
-
|
5
|
+
def initialize(options = {})
|
6
|
+
@engine = options.fetch(:engine) { Net::SSH }
|
7
|
+
end
|
8
|
+
|
9
|
+
def start(user: , host: , **options)
|
10
|
+
@engine.start(host, user, options) do |ssh_session|
|
7
11
|
yield SSHSession.new(ssh_session)
|
8
12
|
end
|
9
13
|
end
|
data/lib/jisota/ssh_session.rb
CHANGED
@@ -10,39 +10,34 @@ module Jisota
|
|
10
10
|
@scp_engine = options.fetch(:scp_engine) { Net::SCP }
|
11
11
|
end
|
12
12
|
|
13
|
-
def command(command, logger =
|
13
|
+
def command(command, logger = NilOutput.new)
|
14
14
|
exit_code = nil
|
15
15
|
exit_signal = nil
|
16
16
|
error_message = ""
|
17
17
|
|
18
|
-
logger.
|
18
|
+
logger.system_message("Executing #{command}")
|
19
19
|
|
20
20
|
@session.open_channel do |channel|
|
21
21
|
channel.exec(command) do
|
22
|
-
channel.on_data { |_, data| logger.info(data)
|
23
|
-
channel.on_extended_data { |_, _, data| logger.warn(data)
|
22
|
+
channel.on_data { |_, data| logger.info(data) }
|
23
|
+
channel.on_extended_data { |_, _, data| logger.warn(data); error_message << data }
|
24
24
|
channel.on_request("exit-status") { |_, data| exit_code = data.read_long }
|
25
25
|
channel.on_request("exit-signal") { |_, data| exit_signal = data.read_long }
|
26
26
|
end
|
27
27
|
end
|
28
28
|
@session.loop
|
29
29
|
|
30
|
-
if exit_code
|
31
|
-
true
|
32
|
-
else
|
30
|
+
if exit_code > 0
|
33
31
|
logger.error("Error running #{command}:")
|
34
32
|
logger.error(error_message)
|
35
|
-
false
|
36
33
|
end
|
34
|
+
|
35
|
+
exit_code
|
37
36
|
end
|
38
37
|
|
39
|
-
def upload(
|
40
|
-
raise FileNotFoundError, "Upload file not found: #{
|
41
|
-
|
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)
|
38
|
+
def upload(from: , to: )
|
39
|
+
raise FileNotFoundError, "Upload file not found: #{from}" unless File.exist?(from)
|
40
|
+
@scp_engine.new(@session).upload!(from, to, recursive: false)
|
46
41
|
end
|
47
42
|
end
|
48
43
|
end
|
data/lib/jisota/version.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
#
|
3
|
+
# How-to:
|
4
|
+
#
|
5
|
+
# Symlink to init.d:
|
6
|
+
# ln -s /path/to/nginx_service /etc/init.d/nginx
|
7
|
+
#
|
8
|
+
# Register service:
|
9
|
+
# sudo update-rc.d nginx defaults 99
|
10
|
+
# (99 is the boot order of all startup services making sure this boots last)
|
11
|
+
#
|
12
|
+
|
13
|
+
### BEGIN INIT INFO
|
14
|
+
# Provides: nginx
|
15
|
+
# Required-Start: $local_fs, $syslog
|
16
|
+
# Required-Stop: $local_fs, $syslog
|
17
|
+
# Default-Start: 2 3 4 5
|
18
|
+
# Default-Stop: 0 1 6
|
19
|
+
# Short-Description: Start nginx at boot time
|
20
|
+
# Description: Enable service provided by nginx
|
21
|
+
### END INIT INFO
|
22
|
+
|
23
|
+
RETVAL=0;
|
24
|
+
bin=/opt/nginx/sbin/nginx
|
25
|
+
|
26
|
+
start() {
|
27
|
+
echo "Starting nginx"
|
28
|
+
$bin
|
29
|
+
}
|
30
|
+
|
31
|
+
stop() {
|
32
|
+
echo "Stopping nginx"
|
33
|
+
$bin -s stop
|
34
|
+
}
|
35
|
+
|
36
|
+
restart() {
|
37
|
+
echo "Restarting nginx"
|
38
|
+
$bin -s reload
|
39
|
+
}
|
40
|
+
|
41
|
+
reload() {
|
42
|
+
echo "Restarting nginx"
|
43
|
+
$bin -s reload
|
44
|
+
}
|
45
|
+
|
46
|
+
case "$1" in
|
47
|
+
start)
|
48
|
+
start
|
49
|
+
;;
|
50
|
+
stop)
|
51
|
+
stop
|
52
|
+
;;
|
53
|
+
restart)
|
54
|
+
restart
|
55
|
+
;;
|
56
|
+
reload)
|
57
|
+
reload
|
58
|
+
;;
|
59
|
+
*)
|
60
|
+
|
61
|
+
echo $"Usage: $0 {start|stop|restart|reload}"
|
62
|
+
exit 1
|
63
|
+
esac
|
64
|
+
|
65
|
+
exit $RETVAL
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
module Jisota
|
5
|
+
describe "ruby + passenger", type: :acceptance do
|
6
|
+
example "install ruby and nginx_passenger" do
|
7
|
+
config = Jisota.config do
|
8
|
+
role :app do
|
9
|
+
ruby version: "2.1.1"
|
10
|
+
nginx_passenger config_file: "spec/test_files/nginx_default.conf"
|
11
|
+
end
|
12
|
+
|
13
|
+
server host, user: user, roles: :app, key: key
|
14
|
+
end
|
15
|
+
|
16
|
+
Jisota.run(config, logger: Output.new(verbose: true))
|
17
|
+
|
18
|
+
webpage = URI("http://#{host}").read
|
19
|
+
expect(webpage).to match(/Welcome to nginx!/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -1,24 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
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
|
-
|
4
|
+
describe "Execute a simple script", type: :acceptance do
|
13
5
|
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
6
|
Jisota.global_config do
|
23
7
|
package :touch_global do
|
24
8
|
param :target
|
@@ -39,17 +23,17 @@ module Jisota
|
|
39
23
|
upload from: "spec/test_files/foo", to: "uploads/foo"
|
40
24
|
end
|
41
25
|
|
42
|
-
server host, user: user, roles: :app
|
26
|
+
server host, user: user, key: key, roles: :app
|
43
27
|
end
|
44
28
|
|
45
|
-
config.ssh_engine = ssh_engine unless run_for_real
|
46
29
|
Jisota.run(config)
|
47
30
|
|
48
|
-
|
49
|
-
expect(
|
50
|
-
expect(ssh_session
|
51
|
-
expect(ssh_session
|
52
|
-
expect(ssh_session
|
31
|
+
create_ssh_session do |ssh_session|
|
32
|
+
expect(ssh_session.command("[ -f foo ]")).to eq(0)
|
33
|
+
expect(ssh_session.command("[ -f bar ]")).to eq(0)
|
34
|
+
expect(ssh_session.command("[ -f baz ]")).to eq(0)
|
35
|
+
expect(ssh_session.command("[ -f uploads/foo ]")).to eq(0)
|
36
|
+
expect(ssh_session.command('[ `cat uploads/foo` = "foo" ]')).to eq(0)
|
53
37
|
end
|
54
38
|
end
|
55
39
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Jisota
|
4
|
+
describe 'calling upload blocks', type: :acceptance do
|
5
|
+
example "update and create blocks inside package with params" do
|
6
|
+
config = Jisota.config do
|
7
|
+
package :upload_file do
|
8
|
+
param :file
|
9
|
+
run do
|
10
|
+
upload from: file, to: "foo" do
|
11
|
+
create { cmd %Q{echo "File #{file} created"} }
|
12
|
+
update { cmd %Q{echo "File #{file} updated"} }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
role :app do
|
18
|
+
upload_file "spec/test_files/foo"
|
19
|
+
end
|
20
|
+
|
21
|
+
server host, user: user, roles: :app, key: key
|
22
|
+
end
|
23
|
+
|
24
|
+
create_ssh_session { |ssh_session| ssh_session.command("rm foo") }
|
25
|
+
Jisota.run(config, logger: logger)
|
26
|
+
|
27
|
+
create_ssh_session { |ssh_session| ssh_session.command("echo omg > foo") }
|
28
|
+
Jisota.run(config, logger: logger)
|
29
|
+
|
30
|
+
expect(logger.stdout.writes).to include("File spec/test_files/foo created\n")
|
31
|
+
expect(logger.stdout.writes).to include("File spec/test_files/foo updated\n")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -40,5 +40,15 @@ module Jisota
|
|
40
40
|
expect(result.map(&:value)).to eq([1, 2, 4])
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
describe "#first" do
|
45
|
+
it "returns the first item in the collection" do
|
46
|
+
collection = Collection.new
|
47
|
+
collection.add(double(key: :foo))
|
48
|
+
collection.add(double(key: :bar))
|
49
|
+
|
50
|
+
expect(collection.first.key).to eq(:foo)
|
51
|
+
end
|
52
|
+
end
|
43
53
|
end
|
44
54
|
end
|
@@ -11,10 +11,11 @@ module Jisota
|
|
11
11
|
describe "#execute" do
|
12
12
|
it "asks ssh_session to execute" do
|
13
13
|
script = CommandScript.new(:foo)
|
14
|
-
ssh_session = instance_double(SSHSession, command:
|
15
|
-
|
14
|
+
ssh_session = instance_double(SSHSession, command: 0)
|
15
|
+
context = ScriptContext.new(ssh_session: ssh_session)
|
16
|
+
result = script.execute(context)
|
16
17
|
|
17
|
-
expect(ssh_session).to have_received(:command).with(:foo,
|
18
|
+
expect(ssh_session).to have_received(:command).with(:foo, anything)
|
18
19
|
expect(result).to be true
|
19
20
|
end
|
20
21
|
end
|
@@ -10,15 +10,17 @@ module Jisota
|
|
10
10
|
|
11
11
|
describe "execute" do
|
12
12
|
let(:ssh_session) { instance_double(SSHSession) }
|
13
|
+
let(:context) { ScriptContext.new(ssh_session: ssh_session) }
|
13
14
|
let(:script) { CompositeScript.new }
|
15
|
+
|
14
16
|
it "executes all scripts" do
|
15
17
|
inner_1 = double(execute: true)
|
16
18
|
inner_2 = double(execute: true)
|
17
19
|
script.scripts << inner_1 << inner_2
|
18
|
-
result = script.execute(
|
20
|
+
result = script.execute(context)
|
19
21
|
|
20
|
-
expect(inner_1).to have_received(:execute).with(
|
21
|
-
expect(inner_2).to have_received(:execute).with(
|
22
|
+
expect(inner_1).to have_received(:execute).with(context)
|
23
|
+
expect(inner_2).to have_received(:execute).with(context)
|
22
24
|
expect(result).to be true
|
23
25
|
end
|
24
26
|
|
@@ -26,10 +28,10 @@ module Jisota
|
|
26
28
|
inner_1 = double(execute: false)
|
27
29
|
inner_2 = double(execute: true)
|
28
30
|
script.scripts << inner_1 << inner_2
|
29
|
-
result = script.execute(
|
31
|
+
result = script.execute(context)
|
30
32
|
|
31
|
-
expect(inner_1).to have_received(:execute).with(
|
32
|
-
expect(inner_2).to_not have_received(:execute).with(
|
33
|
+
expect(inner_1).to have_received(:execute).with(context)
|
34
|
+
expect(inner_2).to_not have_received(:execute).with(context)
|
33
35
|
expect(result).to be false
|
34
36
|
end
|
35
37
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Jisota
|
4
|
+
|
5
|
+
class SomeDSL < DSLBase
|
6
|
+
def set(value)
|
7
|
+
@value = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def get
|
11
|
+
@value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe DSLBase do
|
16
|
+
describe "#evaluate" do
|
17
|
+
it "evaluates like a true closure" do
|
18
|
+
foo = "bar"
|
19
|
+
dsl = SomeDSL.new
|
20
|
+
dsl.evaluate do
|
21
|
+
set foo
|
22
|
+
end
|
23
|
+
|
24
|
+
expect(dsl.get).to eq("bar")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "handles method_missing" do
|
28
|
+
dsl = SomeDSL.new
|
29
|
+
expect {
|
30
|
+
dsl.evaluate do
|
31
|
+
foo
|
32
|
+
end
|
33
|
+
}.to raise_exception(NameError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -2,21 +2,76 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Jisota
|
4
4
|
describe FileScript do
|
5
|
-
describe "#
|
6
|
-
it "initializes with
|
7
|
-
|
5
|
+
describe "#initialization" do
|
6
|
+
it "initializes with from and to" do
|
7
|
+
script = FileScript.new(from: "foo", to: "bar")
|
8
|
+
expect(script.from).to eq("foo")
|
9
|
+
expect(script.to).to eq("bar")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "accepts create and update args" do
|
13
|
+
script = FileScript.new(from: "foo", to: "bar", create: true, update: :foo)
|
14
|
+
expect(script.create).to be true
|
15
|
+
expect(script.update).to eq(:foo)
|
8
16
|
end
|
9
17
|
end
|
10
18
|
|
11
19
|
describe "#execute" do
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
20
|
+
let(:script) { FileScript.new(from: "foo", to: "bar") }
|
21
|
+
let(:ssh_session) { instance_double(SSHSession, upload: nil, command: 0) }
|
22
|
+
let(:context) { ScriptContext.new(ssh_session: ssh_session) }
|
23
|
+
|
24
|
+
it "uploads and moves the file" do
|
25
|
+
allow(ssh_session).to receive(:command).with(a_string_starting_with("cmp")) { 1 }
|
26
|
+
result = script.execute(context)
|
16
27
|
|
17
|
-
expect(ssh_session).to have_received(:
|
28
|
+
expect(ssh_session).to have_received(:command).with("mkdir -p tmp/jisota", anything)
|
29
|
+
expect(ssh_session).to have_received(:upload) do |options|
|
30
|
+
expect(options[:from]).to eq("foo")
|
31
|
+
expect(options[:to]).to start_with("tmp/jisota/")
|
32
|
+
end
|
33
|
+
expect(ssh_session).to have_received(:command).with(a_string_matching(/sudo mkdir -p .* sudo mv .* bar/), anything)
|
18
34
|
expect(result).to be true
|
19
35
|
end
|
36
|
+
|
37
|
+
it "does not move file if it already exists and is identical" do
|
38
|
+
allow(ssh_session).to receive(:command).with(a_string_starting_with("cmp")) { 0 }
|
39
|
+
result = script.execute(context)
|
40
|
+
|
41
|
+
expect(ssh_session).to_not have_received(:command).with(a_string_matching(/sudo mv/), anything)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "does not move file when target does not exist and create is false" do
|
45
|
+
script.create = false
|
46
|
+
allow(ssh_session).to receive(:command).with(a_string_starting_with("cmp")) { 2 }
|
47
|
+
result = script.execute(context)
|
48
|
+
|
49
|
+
expect(ssh_session).to_not have_received(:command).with(a_string_matching(/sudo mv/), anything)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "does not move file when target exists and update is false" do
|
53
|
+
script.update = false
|
54
|
+
allow(ssh_session).to receive(:command).with(a_string_starting_with("cmp")) { 1 }
|
55
|
+
result = script.execute(context)
|
56
|
+
|
57
|
+
expect(ssh_session).to_not have_received(:command).with(a_string_matching(/sudo mv/), anything)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "runs the callbacks" do
|
61
|
+
allow(ssh_session).to receive(:command).with(a_string_starting_with("cmp")) { 2 }
|
62
|
+
script.create = ScriptBlock.new { cmd "foo" }
|
63
|
+
result = script.execute(context)
|
64
|
+
expect(ssh_session).to have_received(:command).with("foo", anything)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "DSL" do
|
69
|
+
it "can set simple values on callback" do
|
70
|
+
script = FileScript.new(from: "foo", to: "bar") do
|
71
|
+
create false
|
72
|
+
end
|
73
|
+
expect(script.create).to be false
|
74
|
+
end
|
20
75
|
end
|
21
76
|
end
|
22
77
|
end
|