jisota 0.0.1 → 0.0.2

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