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
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