jisota 0.0.1 → 0.0.2

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -0
  4. data/CHANGELOG.md +21 -0
  5. data/README.md +106 -2
  6. data/Rakefile +10 -0
  7. data/jisota.gemspec +1 -0
  8. data/lib/jisota.rb +4 -2
  9. data/lib/jisota/collection.rb +2 -4
  10. data/lib/jisota/command_script.rb +3 -2
  11. data/lib/jisota/composite_script.rb +2 -2
  12. data/lib/jisota/configuration.rb +3 -3
  13. data/lib/jisota/dsl_base.rb +12 -0
  14. data/lib/jisota/file_script.rb +107 -9
  15. data/lib/jisota/nil_output.rb +14 -0
  16. data/lib/jisota/{logger.rb → output.rb} +16 -35
  17. data/lib/jisota/package.rb +2 -2
  18. data/lib/jisota/package_script.rb +28 -20
  19. data/lib/jisota/packages/gem_install.rb +17 -0
  20. data/lib/jisota/packages/nginx_passenger.rb +34 -0
  21. data/lib/jisota/packages/ruby.rb +2 -2
  22. data/lib/jisota/param_parser.rb +30 -19
  23. data/lib/jisota/provisioner.rb +7 -3
  24. data/lib/jisota/script_block.rb +13 -14
  25. data/lib/jisota/script_context.rb +24 -0
  26. data/lib/jisota/server.rb +2 -1
  27. data/lib/jisota/ssh_engine.rb +6 -2
  28. data/lib/jisota/ssh_session.rb +10 -15
  29. data/lib/jisota/version.rb +1 -1
  30. data/package_files/nginx_passenger/nginx_service +65 -0
  31. data/spec/acceptance/ruby_passenger_nginx_spec.rb +24 -0
  32. data/spec/acceptance/simple_script_spec.rb +8 -24
  33. data/spec/acceptance/upload_blocks_spec.rb +34 -0
  34. data/spec/lib/jisota/collection_spec.rb +10 -0
  35. data/spec/lib/jisota/command_script_spec.rb +4 -3
  36. data/spec/lib/jisota/composite_script_spec.rb +8 -6
  37. data/spec/lib/jisota/configuration_spec.rb +1 -3
  38. data/spec/lib/jisota/dsl_base_spec.rb +37 -0
  39. data/spec/lib/jisota/file_script_spec.rb +63 -8
  40. data/spec/lib/jisota/output_spec.rb +84 -0
  41. data/spec/lib/jisota/package_script_spec.rb +20 -8
  42. data/spec/lib/jisota/package_spec.rb +2 -6
  43. data/spec/lib/jisota/packages/apt_spec.rb +7 -4
  44. data/spec/lib/jisota/packages/gem_install_spec.rb +18 -0
  45. data/spec/lib/jisota/packages/nginx_passenger_spec.rb +17 -0
  46. data/spec/lib/jisota/packages/ruby_spec.rb +6 -3
  47. data/spec/lib/jisota/param_parser_spec.rb +105 -0
  48. data/spec/lib/jisota/provisioner_spec.rb +30 -0
  49. data/spec/lib/jisota/role_spec.rb +1 -3
  50. data/spec/lib/jisota/script_block_spec.rb +7 -4
  51. data/spec/lib/jisota/ssh_engine_spec.rb +26 -0
  52. data/spec/lib/jisota/ssh_session_spec.rb +53 -0
  53. data/spec/spec_helper.rb +11 -1
  54. data/spec/support/acceptance_helpers.rb +45 -0
  55. data/spec/test_files/nginx_default.conf +121 -0
  56. data/spec/vagrant/Vagrantfile +118 -0
  57. data/spec/vagrant/ssh_key +27 -0
  58. metadata +55 -7
  59. data/lib/jisota/upload_file.rb +0 -3
  60. 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
@@ -2,8 +2,12 @@ require 'net/ssh'
2
2
 
3
3
  module Jisota
4
4
  class SSHEngine
5
- def self.start(user: , host: )
6
- Net::SSH.start(host, user) do |ssh_session|
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
@@ -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 = nil)
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.command(command) if 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) if logger }
23
- channel.on_extended_data { |_, _, data| logger.warn(data) if logger; error_message << 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 == 0
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(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)
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
@@ -1,3 +1,3 @@
1
1
  module Jisota
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -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
- 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)
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: true)
15
- result = script.execute(ssh_session)
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, nil)
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(ssh_session)
20
+ result = script.execute(context)
19
21
 
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(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(ssh_session)
31
+ result = script.execute(context)
30
32
 
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(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
@@ -54,9 +54,7 @@ module Jisota
54
54
  describe "#role" do
55
55
  it "creates a role" do
56
56
  config = Configuration.new do
57
- role :app do
58
- cmd "touch foo"
59
- end
57
+ role(:app) { cmd "touch foo" }
60
58
  end
61
59
  role = config.roles.first
62
60
  expect(role.name).to eq(:app)
@@ -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 "#file" do
6
- it "initializes with one" do
7
- expect(FileScript.new(:foo).file).to eq(:foo)
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
- it "asks ssh_session to upload file" do
13
- script = FileScript.new(:foo)
14
- ssh_session = instance_double(SSHSession, upload: true)
15
- result = script.execute(ssh_session)
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(:upload).with(:foo, nil)
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