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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 868630ce53b3e30dee642175d2e3f9269c40749c
4
- data.tar.gz: 692a72ec3e1df5c55e7c679837548584096c3bb0
3
+ metadata.gz: d30b81b24cc0fc7e8bb04cc88f243fc83983dd25
4
+ data.tar.gz: 5b94aabed9dcb6f17c89e03bd2092d73d79067ae
5
5
  SHA512:
6
- metadata.gz: e48e212a2175b404f1cfd1d2b122841051902f3f868509b1321096a9e1315440da686113cf8f7b209b872028ed068d50af947dbf3d8e2c97746f80987dea3b73
7
- data.tar.gz: 71963bcbed7a13a30aadd4bc17a2bace65ea8ff694924999213c0a63cdf9e2b72b727d41d515dd69e535212e1fe042d0be28f6a5e26d022a8e3d6fba2398657c
6
+ metadata.gz: 0d75af1889780c695a60813c9fd8613f60ca0149151e24d39f5ebbdb27ae172eb797c5c570fd92fee1f5de4d877a466ca3079b9affc477ee241ca16168710269
7
+ data.tar.gz: 6ad96e33cbd4c23b673d075b1b6efd8944218ba7cb2c28756a692d5151c45265f67018b3ebc661bfbc0285a7169544c0c92cef1197a0ece52c51475f05363e4f
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ test/version_tmp
17
17
  tmp
18
18
  sandi_meter/
19
19
  examples/
20
+ .vagrant
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - "2.1.0"
3
+ - "2.1.1"
4
+ env:
5
+ - CODECLIMATE_REPO_TOKEN=5d0f3c23f6e2c737a1e4138fba4dd4db00dbe87bc037771402624b6151948c14
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Jisota changelog
2
+
3
+ ## 0.0.2 (current)
4
+
5
+ ### Features
6
+ * Create and Update actions on file upload
7
+ * Params have scope of the entire package, allowing e.g. upload action blocks to access package params
8
+ * Can now specify a private ssh key to use
9
+
10
+ ### Improvements
11
+ * Better DSL blocks.
12
+ * Stuff parsed from script to script are now wrapped in a ScriptContext object
13
+ * Fixed ruby package by ensuring build-essential is installed
14
+ * Runs acceptance tests inside a vagrant
15
+
16
+ ### Packages
17
+ * gem_install
18
+ * nginx_passenger
19
+
20
+ ## 0.0.1
21
+ Initial version with basic functionality
data/README.md CHANGED
@@ -1,3 +1,9 @@
1
+ [![Gem Version](https://badge.fury.io/rb/jisota.png)](http://badge.fury.io/rb/jisota)
2
+ [![Code Climate](https://codeclimate.com/github/lasseebert/jisota.png)](https://codeclimate.com/github/lasseebert/jisota)
3
+ [![Code Climate](https://codeclimate.com/github/lasseebert/jisota/coverage.png)](https://codeclimate.com/github/lasseebert/jisota)
4
+ [![Build Status](https://travis-ci.org/lasseebert/jisota.svg)](https://travis-ci.org/lasseebert/jisota)
5
+ [![Dependency Status](https://gemnasium.com/lasseebert/jisota.svg)](https://gemnasium.com/lasseebert/jisota)
6
+
1
7
  # Jisota
2
8
 
3
9
  Jisota is a simple provisioning tool meant for smaller projects.
@@ -5,6 +11,8 @@ Jisota is a simple provisioning tool meant for smaller projects.
5
11
  Provisioning, in this context, is the act of turning an empty server
6
12
  into a working machine tailored to your needs.
7
13
 
14
+ ***Note: Jisota is still considered unstable. Breaking changes may occur in patch version updates.***
15
+
8
16
  ## Installation
9
17
 
10
18
  Add this line to your application's Gemfile:
@@ -41,6 +49,10 @@ Run the script with the ruby executable:
41
49
 
42
50
  $ ruby config/provision.rb
43
51
 
52
+ ## Ruby version
53
+
54
+ Jisota is cutting edge and requires Ruby 2.1.0 or later.
55
+
44
56
  ## Defining a package
45
57
 
46
58
  Inside a `Jisota.config` block, use the `package` method to define a package.
@@ -133,6 +145,15 @@ Example:
133
145
  apt :vim, :git, command: "sudo apt-get install -y :package"
134
146
  end
135
147
 
148
+ ## Defining a server
149
+
150
+ A server needs at least a host, a user and a role:
151
+
152
+ server "mydomain.com", user: "john", roles: :app
153
+
154
+ Other options:
155
+
156
+ * `key`: File path to ssh private key
136
157
 
137
158
  ## Atomic script operations
138
159
 
@@ -151,6 +172,43 @@ Uploads the file to the server. Example:
151
172
 
152
173
  upload from: "path/to/nginx.conf", to: "/etc/nginx.conf"
153
174
 
175
+ You can register script blocks to be called if the file was created or updated.
176
+ If the file on the server is identical with the uploaded file, no action is
177
+ done and the file will not be overwritten.
178
+
179
+ upload from: "foo", to: "bar" do
180
+ create { cmd 'echo "File was created!"' }
181
+ update { cmd 'echo "File was updated!"' }
182
+ end
183
+
184
+ You can disable creation or update of the file:
185
+
186
+ upload from: "foo", to: "bar" do
187
+ create false # Only update is allowed. File will not be created if it does not exists
188
+ end
189
+
190
+ - OR -
191
+
192
+ upload from: "foo", to: "bar", create: false
193
+
194
+ You can also use package params and call packages inside these blocks:
195
+
196
+ package :nginx_config do
197
+ param :config_file, require: true
198
+ run do
199
+ upload from: config_file, to: "/etc/nginx.conf" do
200
+ update do
201
+ cmd %q{echo "#{config_file} was updated. Restarting nginx..."}
202
+ nginx_restart
203
+ end
204
+ create do
205
+ cmd %q{echo "#{config_file} was started. Starting nginx..."}
206
+ nginx_start
207
+ end
208
+ end
209
+ end
210
+ end
211
+
154
212
  ## Complete list of build-in packages
155
213
 
156
214
  ### ruby
@@ -164,6 +222,17 @@ Uploads the file to the server. Example:
164
222
  description "Installs packages with apt-get"
165
223
  param :packages, required: true, splat: true
166
224
 
225
+ ### gem_install
226
+
227
+ description "Installs a gem"
228
+ param :gem_name, required: true
229
+ param :sudo, default: true
230
+
231
+ ### nginx_passenger
232
+
233
+ description "Install nginx with passenger module"
234
+ param :config_file, required: true
235
+
167
236
  ### More packages?
168
237
 
169
238
  Jisota is a young gem. Please contribute with any packages that you think
@@ -193,7 +262,11 @@ is equivalent to:
193
262
 
194
263
  ## Full example
195
264
 
196
- An example using most of the features of Jisota:
265
+ An example using most of the features of Jisota.
266
+
267
+ Note: This was not tested and probably won't make sense in a real application.
268
+ This is just meant to show some features. A good place to look for examples are
269
+ in the build-in packages
197
270
 
198
271
  require 'jisota'
199
272
 
@@ -220,7 +293,10 @@ An example using most of the features of Jisota:
220
293
  param :config_file, required: true
221
294
  run do
222
295
  cmd "sudo apt get install nginx"
223
- upload from: config_file, to: /etc/nginx.conf
296
+ upload from: config_file, to: /etc/nginx.conf do
297
+ update { cmd "sudo service nginx restart" }
298
+ create { cmd "sudo service nginx start" }
299
+ end
224
300
  end
225
301
  end
226
302
 
@@ -243,6 +319,34 @@ An example using most of the features of Jisota:
243
319
 
244
320
  Jisota.run(config)
245
321
 
322
+ ## Testing
323
+
324
+ ### Unit tests
325
+
326
+ Run with `rake`
327
+
328
+ ### Acceptance tests
329
+
330
+ Acceptance tests are not meant to be run regularly. Some of them might take a
331
+ long time to finish. They are a way to run Jisota and the build-in packages in
332
+ a near-real environment that is easy to destroy and rebuild.
333
+
334
+ Start the vagrant:
335
+
336
+ $ (cd spec/vagrant && vagrant up)
337
+
338
+ Then run acceptance specs
339
+
340
+ $ rake spec:acceptance
341
+
342
+ Or run individual specs. Some of them might take a while:
343
+
344
+ $ rspec --tag type:acceptance spec/acceptance/some_spec.rb
345
+
346
+ To kill vagrant and start with a fresh machine:
347
+
348
+ $ (cd spec/vagrant && vagrant destroy --force && vagrant up)
349
+
246
350
  ## Contributing
247
351
 
248
352
  Any pull requests, suggestions, bug reports and feedback are most welcome :)
data/Rakefile CHANGED
@@ -1 +1,11 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ namespace :spec do
6
+ RSpec::Core::RakeTask.new(:acceptance) do |config|
7
+ config.rspec_opts = "--tag type:acceptance"
8
+ end
9
+ end
10
+
11
+ task :default => :spec
data/jisota.gemspec CHANGED
@@ -27,4 +27,5 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency "byebug"
28
28
  spec.add_development_dependency "rspec", "~> 3.0.0.beta2"
29
29
  spec.add_development_dependency "guard-rspec", "~> 4.2.8"
30
+ spec.add_development_dependency "codeclimate-test-reporter"
30
31
  end
data/lib/jisota.rb CHANGED
@@ -5,11 +5,11 @@ module Jisota
5
5
  #
6
6
  # Options allow default depenedencies to be overridden. Example:
7
7
  #
8
- # Jisota.run(config, logger: Jisota::Logger.new(verbose: true))
8
+ # Jisota.run(config, logger: Jisota::Output.new(verbose: true))
9
9
  #
10
10
  def self.run(configuration, options = {})
11
11
  provisioner = options.fetch(:provisioner) { Provisioner.new }
12
- logger = options.fetch(:logger) { Logger.new }
12
+ logger = options.fetch(:logger) { Output.new }
13
13
  provisioner.run(configuration, logger)
14
14
  end
15
15
 
@@ -59,5 +59,7 @@ module Jisota
59
59
  end
60
60
  end
61
61
 
62
+ require_relative 'jisota/dsl_base'
63
+ require_relative 'jisota/output'
62
64
  Dir[File.expand_path("../jisota/*.rb", __FILE__)].each { |file| require file }
63
65
  Dir[File.expand_path("../jisota/packages/*.rb", __FILE__)].each { |file| require file }
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module Jisota
2
4
 
3
5
  ##
@@ -36,10 +38,6 @@ module Jisota
36
38
  @items.first[1]
37
39
  end
38
40
 
39
- def last
40
- @items.last[1]
41
- end
42
-
43
41
  def each(&block)
44
42
  @items.values.each(&block)
45
43
  end
@@ -10,8 +10,9 @@ module Jisota
10
10
  @command = command
11
11
  end
12
12
 
13
- def execute(ssh_session, logger = nil)
14
- ssh_session.command(command, logger)
13
+ def execute(context)
14
+ exit_code = context.command(command)
15
+ exit_code == 0
15
16
  end
16
17
  end
17
18
  end
@@ -12,9 +12,9 @@ module Jisota
12
12
  @scripts = []
13
13
  end
14
14
 
15
- def execute(ssh_session, logger = nil)
15
+ def execute(context)
16
16
  scripts.each do |inner|
17
- result = inner.execute(ssh_session, logger)
17
+ result = inner.execute(context)
18
18
  return false unless result
19
19
  end
20
20
  true
@@ -11,8 +11,8 @@ module Jisota
11
11
  @packages = options.fetch(:packages) { Collection.new }
12
12
  @roles = options.fetch(:role) { Collection.new }
13
13
  @servers = options.fetch(:servers) { [] }
14
- @ssh_engine = options.fetch(:ssh_engine) { SSHEngine }
15
- DSL.new(self).instance_eval(&block) if block_given?
14
+ @ssh_engine = options.fetch(:ssh_engine) { SSHEngine.new }
15
+ DSL.new(self).evaluate(&block) if block_given?
16
16
  end
17
17
 
18
18
  def add_package(package)
@@ -39,7 +39,7 @@ module Jisota
39
39
  servers.each(&block)
40
40
  end
41
41
 
42
- class DSL
42
+ class DSL < DSLBase
43
43
  def initialize(target)
44
44
  @target = target
45
45
  end
@@ -0,0 +1,12 @@
1
+ module Jisota
2
+ class DSLBase
3
+ def evaluate(&block)
4
+ @outer = eval("self", block.binding)
5
+ instance_eval(&block)
6
+ end
7
+
8
+ def method_missing(method, *args, &block)
9
+ @outer.send(method, *args, &block)
10
+ end
11
+ end
12
+ end
@@ -1,21 +1,119 @@
1
+ require 'forwardable'
2
+
1
3
  module Jisota
2
4
  ##
3
5
  # Part of the Script duck type
4
6
  #
5
7
  # Uploads a file on execution
6
8
  class FileScript
7
- attr_accessor :file
9
+ attr_accessor :from, :to, :create, :update
10
+
11
+ def initialize(from: , to: , create: true, update: true, &block)
12
+ @from = from
13
+ @to = to
14
+ @create = create
15
+ @update = update
16
+ if block_given?
17
+ dsl = DSL.new(self)
18
+ dsl.evaluate(&block)
19
+ end
20
+ end
21
+
22
+ def execute(context)
23
+ Executor.new(script: self, context: context).run
24
+ end
25
+
26
+ class DSL < DSLBase
27
+ def initialize(target)
28
+ @target = target
29
+ end
30
+
31
+ def create(value = nil, &block)
32
+ set_callback(:create=, value, &block)
33
+ end
34
+
35
+ def update(value = nil, &block)
36
+ set_callback(:update=, value, &block)
37
+ end
38
+
39
+ private
8
40
 
9
- def initialize(file)
10
- @file = file
41
+ def set_callback(method, value, &block)
42
+ raise "Can not use both a value and block on create or update" if !value.nil? && block_given?
43
+ if value.nil?
44
+ @target.send(method, ScriptBlock.new(&block))
45
+ else
46
+ @target.send(method, value)
47
+ end
48
+ end
11
49
  end
12
50
 
13
- def execute(ssh_session, logger = nil)
14
- logger.prefixed_message("File #{file.from} -> #{file.to}") if logger
15
- logger.indent if logger
16
- result = ssh_session.upload(file, logger)
17
- logger.outdent if logger
18
- result
51
+ class Executor
52
+ extend Forwardable
53
+ attr_reader :script, :context
54
+ def_delegators :script, :from, :to, :create, :update
55
+ def_delegators :context, :ssh_session, :logger
56
+
57
+ def initialize(script: , context: )
58
+ @script = script
59
+ @context = context
60
+ end
61
+
62
+ def tmp_file
63
+ @tmp_file ||= "#{tmp_dir}/#{SecureRandom.hex}"
64
+ end
65
+
66
+ def tmp_dir
67
+ "tmp/jisota"
68
+ end
69
+
70
+ def run
71
+ logger.system_message("File #{from} -> #{to}") do
72
+ make_temp_dir
73
+ upload
74
+ handle_file_move
75
+ end
76
+ end
77
+
78
+ def make_temp_dir
79
+ ssh_session.command("mkdir -p #{tmp_dir}", logger)
80
+ end
81
+
82
+ def upload
83
+ logger.system_message("Uploading #{from} -> #{tmp_file}")
84
+ ssh_session.upload(from: from, to: tmp_file)
85
+ end
86
+
87
+ def handle_file_move
88
+ result = ssh_session.command("cmp -s #{tmp_file} #{to}")
89
+ case result
90
+ when 0 then same_file
91
+ when 1 then move_with_callback(update, "File exists and update is not allowed. Skipping.")
92
+ when 2 then move_with_callback(create, "File does not exist and create is not allowed. Skipping.")
93
+ end
94
+ end
95
+
96
+ def same_file
97
+ logger.system_message("Files are the same. Skipping move.")
98
+ true
99
+ end
100
+
101
+ def move_with_callback(callback, callback_not_found_message)
102
+ if callback
103
+ result = ssh_session.command("sudo mkdir -p `dirname #{to}` && sudo mv #{tmp_file} #{to}", logger)
104
+ if result == 0
105
+ if callback.is_a?(ScriptBlock)
106
+ callback_script = callback.evaluate(context)
107
+ callback_script.execute(context)
108
+ else
109
+ true
110
+ end
111
+ end
112
+ else
113
+ logger.system_message(callback_not_found_message)
114
+ true
115
+ end
116
+ end
19
117
  end
20
118
  end
21
119
  end