test-kitchen 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +5 -0
  3. data/.travis.yml +8 -3
  4. data/CHANGELOG.md +66 -0
  5. data/Guardfile +3 -1
  6. data/README.md +3 -1
  7. data/Rakefile +1 -16
  8. data/features/kitchen_command.feature +1 -0
  9. data/features/kitchen_driver_create_command.feature +1 -0
  10. data/features/kitchen_driver_discover_command.feature +1 -0
  11. data/features/kitchen_help_command.feature +16 -0
  12. data/features/kitchen_init_command.feature +2 -0
  13. data/features/kitchen_list_command.feature +42 -0
  14. data/features/support/env.rb +25 -0
  15. data/lib/kitchen.rb +0 -1
  16. data/lib/kitchen/busser.rb +2 -2
  17. data/lib/kitchen/cli.rb +80 -233
  18. data/lib/kitchen/command.rb +117 -0
  19. data/lib/kitchen/command/action.rb +44 -0
  20. data/lib/kitchen/command/console.rb +51 -0
  21. data/lib/kitchen/command/diagnose.rb +51 -0
  22. data/lib/kitchen/command/driver_discover.rb +72 -0
  23. data/lib/kitchen/command/list.rb +86 -0
  24. data/lib/kitchen/command/login.rb +42 -0
  25. data/lib/kitchen/command/sink.rb +53 -0
  26. data/lib/kitchen/command/test.rb +50 -0
  27. data/lib/kitchen/driver/ssh_base.rb +2 -1
  28. data/lib/kitchen/loader/yaml.rb +67 -29
  29. data/lib/kitchen/provisioner/base.rb +67 -4
  30. data/lib/kitchen/provisioner/chef_base.rb +50 -65
  31. data/lib/kitchen/provisioner/chef_solo.rb +3 -2
  32. data/lib/kitchen/provisioner/chef_zero.rb +11 -9
  33. data/lib/kitchen/provisioner/shell.rb +88 -0
  34. data/lib/kitchen/state_file.rb +7 -2
  35. data/lib/kitchen/util.rb +1 -1
  36. data/lib/kitchen/version.rb +1 -1
  37. data/spec/kitchen/loader/yaml_spec.rb +327 -13
  38. data/spec/spec_helper.rb +2 -7
  39. data/support/chef-client-zero.rb +1 -0
  40. data/templates/driver/README.md.erb +1 -1
  41. data/test-kitchen.gemspec +2 -2
  42. metadata +59 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 64d47d36ddf40c8ecc6d85d959e3a57156532e47
4
- data.tar.gz: 2c60df4e026b0f2cad64dc5b4c3c3ba1341ac2a6
3
+ metadata.gz: b3c4db1c478015e6304dfe1664f758171403be58
4
+ data.tar.gz: 61c4f294a642cf6d300e9388a97919f712875be5
5
5
  SHA512:
6
- metadata.gz: a3de01b4c239c767b824c4d29b8645950ab2704f20c821dd42877d9034ec7024f9b669d4b1c41141ebb213f02f1fe33832adbd2eef804b089abdff78e1aa3d2f
7
- data.tar.gz: 3b6ad9625a57724367af1de37712d39b9e2bd6c2115a3f5a66bda655b180797f319888d5ac916a4425b013bf390178d21398aba2198c125779c487f92658f1ad
6
+ metadata.gz: b5b814ee625daaf11b9105476bc1f3c32f8079f22ee19298d787ad1fa41e090253d4a894227f64b10bbaee67775d4e0e755b925f2f3f9199c97d246650584d2a
7
+ data.tar.gz: 395cd37b2a48b85ee8e2005e27a6038159c2655a920f9d1e466717dd177c9b8adc58a6a0b13063b94d0c70fdc510ecceadc058444be82692b15911d2d9e2159c
data/.cane ADDED
@@ -0,0 +1,5 @@
1
+ --abc-max 20
2
+ --style-measure 120
3
+ --abc-exclude Kitchen::ThorTasks#define
4
+ --style-exclude lib/vendor/**/*.rb
5
+ --doc-exclude lib/vendor/**/.rb
data/.travis.yml CHANGED
@@ -1,13 +1,16 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 1.9.3
4
+ - 2.1.0
5
5
  - 2.0.0
6
- - jruby-19mode
6
+ - 1.9.3
7
+ - 1.9.2
7
8
  - ruby-head
9
+ - jruby-19mode
8
10
 
9
11
  env:
10
- - RUBYGEMS_VERSION=2.0.3
12
+ - RUBYGEMS_VERSION=2.1.11
13
+ - RUBYGEMS_VERSION=2.0.14
11
14
  - RUBYGEMS_VERSION=1.8.25
12
15
 
13
16
  before_install:
@@ -23,3 +26,5 @@ matrix:
23
26
  exclude:
24
27
  - rvm: 2.0.0
25
28
  env: RUBYGEMS_VERSION=1.8.25
29
+ - rvm: 2.1.0-preview2
30
+ env: RUBYGEMS_VERSION=1.8.25
data/CHANGELOG.md CHANGED
@@ -1,3 +1,39 @@
1
+ ## 1.2.0 / 2014-02-11
2
+
3
+ ### Upstream changes
4
+
5
+ * Pull request [#288][]: Update omnibus URL to getchef.com. ([@juliandunn][])
6
+
7
+ ### Bug fixes
8
+
9
+ * Pull request [#353][]: Ensure that a chef-client failure returns non-zero exit code for chef-client-zero.rb shim script. ([@kamalim][])
10
+ * Pull request [#318][]: Upload chef clients data. ([@jtimberman][])
11
+ * Issue [#282][], issue [#316][]: [CLI] Match a specific instance before trying to find with a regexp. ([@fnichol][])
12
+ * Issue [#305][]: Ensure that `kitchen help` exits non-zero on failure. ([@fnichol][])
13
+ * Pull request [#296][]: Fixing error when using more than one helper. ([@jschneiderhan][])
14
+ * Pull request [#313][]: Allow files in subdirectories in "helpers" directory. ([@mthssdrbrg][])
15
+ * Pull request [#309][]: Add `/opt/local/bin` to instance path when installing Chef Omnibus package. Smartmachines need this otherwise curl can't find certificates. ([@someara][])
16
+ * Pull request [#283][], pull request [#287][], pull request [#310][]: Fix failing minitest test on Windows. ([@rarenerd][])
17
+ * Fix testing regressions for Ruby 1.9.2 around YAML parsing. ([@fnichol][])
18
+
19
+ ### New features
20
+
21
+ * Pull request [#286][]: **Experimental** Basic shell provisioner, first non-Chef addition! Still considered experimental, that is subject to change between releases until APIs stabilize. ([@ChrisLundquist][])
22
+ * Pull request [#293][], pull request [#277][], issue [#176][]: Add `--concurrency` option to specify number of multiple actions to perform at a time. ([@ryotarai][], [@bkw][])
23
+ * Support `--concurrency` without value, defaulting to all instances and begin to deprecate `--parallel` flag. ([@fnichol][])
24
+ * Pull request [#306][], issue [#304][]: Add local & global file locations with environment variables (`KITCHEN_LOCAL_YAML` and `KITCHEN_GLOBAL_YAML`). ([@fnichol][])
25
+ * Pull request [#298][]: Base provisioner refactoring to start accommodating other provisioners. For more details, see [#298][]. ([@fnichol][])
26
+
27
+ ### Improvements
28
+
29
+ * Pull request [#280][]: Add `json_attributes: true` config option to ChefZero provisioner. This option allows a user to invoke chef-client without passing the generated JSON file in the `--json-attributes` option. ([@fnichol][])
30
+ * Make `kitchen login` work without args if there is only one instance (thank goodness). ([@fnichol][])
31
+ * Issue [#285][]: Greatly improved error recovery & reporting in Kitchen::Loader::YAML. ([@fnichol][])
32
+ * Pull request [#303][]: Use SafeYAML.load to avoid YAML monkeypatch in safe_yaml. This will leave YAML loading in Test Kitchen as implementation detail and avoid polluting other Ruby objects. ([@fnichol][])
33
+ * Pull request [#302][]: CLI refactoring to remove logic from cli.rb. ([@fnichol][])
34
+ * Add Ruby 2.1.0 to TravisCI testing matrix. ([@fnichol][])
35
+
36
+
1
37
  ## 1.1.1 / 2013-12-08
2
38
 
3
39
  ### Bug fixes
@@ -372,6 +408,7 @@ The initial release.
372
408
  [#170]: https://github.com/opscode/test-kitchen/issues/170
373
409
  [#171]: https://github.com/opscode/test-kitchen/issues/171
374
410
  [#172]: https://github.com/opscode/test-kitchen/issues/172
411
+ [#176]: https://github.com/opscode/test-kitchen/issues/176
375
412
  [#178]: https://github.com/opscode/test-kitchen/issues/178
376
413
  [#179]: https://github.com/opscode/test-kitchen/issues/179
377
414
  [#187]: https://github.com/opscode/test-kitchen/issues/187
@@ -399,11 +436,34 @@ The initial release.
399
436
  [#272]: https://github.com/opscode/test-kitchen/issues/272
400
437
  [#275]: https://github.com/opscode/test-kitchen/issues/275
401
438
  [#276]: https://github.com/opscode/test-kitchen/issues/276
439
+ [#277]: https://github.com/opscode/test-kitchen/issues/277
402
440
  [#278]: https://github.com/opscode/test-kitchen/issues/278
441
+ [#280]: https://github.com/opscode/test-kitchen/issues/280
442
+ [#282]: https://github.com/opscode/test-kitchen/issues/282
443
+ [#283]: https://github.com/opscode/test-kitchen/issues/283
444
+ [#285]: https://github.com/opscode/test-kitchen/issues/285
445
+ [#286]: https://github.com/opscode/test-kitchen/issues/286
446
+ [#287]: https://github.com/opscode/test-kitchen/issues/287
447
+ [#288]: https://github.com/opscode/test-kitchen/issues/288
448
+ [#293]: https://github.com/opscode/test-kitchen/issues/293
449
+ [#296]: https://github.com/opscode/test-kitchen/issues/296
450
+ [#298]: https://github.com/opscode/test-kitchen/issues/298
451
+ [#302]: https://github.com/opscode/test-kitchen/issues/302
452
+ [#303]: https://github.com/opscode/test-kitchen/issues/303
453
+ [#304]: https://github.com/opscode/test-kitchen/issues/304
454
+ [#305]: https://github.com/opscode/test-kitchen/issues/305
455
+ [#306]: https://github.com/opscode/test-kitchen/issues/306
456
+ [#309]: https://github.com/opscode/test-kitchen/issues/309
457
+ [#310]: https://github.com/opscode/test-kitchen/issues/310
458
+ [#313]: https://github.com/opscode/test-kitchen/issues/313
459
+ [#316]: https://github.com/opscode/test-kitchen/issues/316
460
+ [#318]: https://github.com/opscode/test-kitchen/issues/318
461
+ [#353]: https://github.com/opscode/test-kitchen/issues/353
403
462
  [@ChrisLundquist]: https://github.com/ChrisLundquist
404
463
  [@adamhjk]: https://github.com/adamhjk
405
464
  [@arangamani]: https://github.com/arangamani
406
465
  [@arunthampi]: https://github.com/arunthampi
466
+ [@bkw]: https://github.com/bkw
407
467
  [@bryanwb]: https://github.com/bryanwb
408
468
  [@calavera]: https://github.com/calavera
409
469
  [@ekrupnik]: https://github.com/ekrupnik
@@ -417,24 +477,30 @@ The initial release.
417
477
  [@jonsmorrow]: https://github.com/jonsmorrow
418
478
  [@josephholsten]: https://github.com/josephholsten
419
479
  [@jrwesolo]: https://github.com/jrwesolo
480
+ [@jschneiderhan]: https://github.com/jschneiderhan
420
481
  [@jtimberman]: https://github.com/jtimberman
421
482
  [@juliandunn]: https://github.com/juliandunn
483
+ [@kamalim]: https://github.com/kamalim
422
484
  [@kisoku]: https://github.com/kisoku
423
485
  [@manul]: https://github.com/manul
424
486
  [@mattray]: https://github.com/mattray
425
487
  [@mconigliaro]: https://github.com/mconigliaro
488
+ [@mthssdrbrg]: https://github.com/mthssdrbrg
426
489
  [@oferrigni]: https://github.com/oferrigni
427
490
  [@patcon]: https://github.com/patcon
428
491
  [@portertech]: https://github.com/portertech
492
+ [@rarenerd]: https://github.com/rarenerd
429
493
  [@reset]: https://github.com/reset
430
494
  [@rteabeault]: https://github.com/rteabeault
431
495
  [@ryansouza]: https://github.com/ryansouza
496
+ [@ryotarai]: https://github.com/ryotarai
432
497
  [@saketoba]: https://github.com/saketoba
433
498
  [@scarolan]: https://github.com/scarolan
434
499
  [@schisamo]: https://github.com/schisamo
435
500
  [@scotthain]: https://github.com/scotthain
436
501
  [@sethvargo]: https://github.com/sethvargo
437
502
  [@smith]: https://github.com/smith
503
+ [@someara]: https://github.com/someara
438
504
  [@stevendanna]: https://github.com/stevendanna
439
505
  [@thommay]: https://github.com/thommay
440
506
  [@zts]: https://github.com/zts
data/Guardfile CHANGED
@@ -4,7 +4,9 @@ guard 'minitest' do
4
4
  watch(%r|^spec/spec_helper\.rb|) { "spec" }
5
5
  end
6
6
 
7
- guard 'cucumber' do
7
+ cucumber_cli = '--no-profile --color --format progress --strict'
8
+ cucumber_cli += ' --tags ~@spawn' if RUBY_PLATFORM =~ /mswin|mingw|windows/
9
+ guard 'cucumber', cli: cucumber_cli do
8
10
  watch(%r{^features/.+\.feature$})
9
11
  watch(%r{^features/support/.+$}) { 'features' }
10
12
  watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  |-------------|-----------------------------------------------|
10
10
  | Website | http://kitchen.ci |
11
11
  | Source Code | http://kitchen.ci/docs/getting-started/ |
12
- | IRC | [#kitchenci][irc] channel on Freenode |
12
+ | IRC | [#kitchenci][irc] channel on Freenode, [transcript][irc_log] thanks to [BotBot.me][botbotme] |
13
13
  | Twitter | [@kitchenci][twitter] |
14
14
 
15
15
  > **Test Kitchen is an integration tool for developing and testing
@@ -111,10 +111,12 @@ a growing community of [contributors][contributors].
111
111
 
112
112
  Apache License, Version 2.0 (see [LICENSE][license])
113
113
 
114
+ [botbotme]: https://botbot.me/
114
115
  [contributors]: https://github.com/test-kitchen/test-kitchen/graphs/contributors
115
116
  [fnichol]: https://github.com/fnichol
116
117
  [guide]: http://kitchen.ci/docs/getting-started/
117
118
  [irc]: http://webchat.freenode.net/?channels=kitchenci
119
+ [irc_log]: https://botbot.me/freenode/kitchenci/
118
120
  [issues]: https://github.com/test-kitchen/test-kitchen/issues
119
121
  [license]: https://github.com/test-kitchen/test-kitchen/blob/master/LICENSE
120
122
  [repo]: https://github.com/test-kitchen/test-kitchen
data/Rakefile CHANGED
@@ -30,22 +30,7 @@ unless RUBY_ENGINE == 'jruby'
30
30
 
31
31
  desc "Run cane to check quality metrics"
32
32
  Cane::RakeTask.new do |cane|
33
- cane.abc_max = 20
34
- cane.abc_exclude = %w(
35
- Kitchen::RakeTasks#define
36
- Kitchen::ThorTasks#define
37
- Kitchen::CLI#pry_prompts
38
- Kitchen::CLI#debug_instance
39
- Kitchen::Instance#synchronize_or_call
40
- Kitchen::Driver::SSHBase#converge
41
- )
42
- cane.style_exclude = %w(
43
- lib/vendor/hash_recursive_merge.rb
44
- )
45
- cane.doc_exclude = %w(
46
- lib/vendor/hash_recursive_merge.rb
47
- )
48
- cane.style_measure = 160
33
+ cane.canefile = './.cane'
49
34
  end
50
35
 
51
36
  Tailor::RakeTask.new do |task|
@@ -3,6 +3,7 @@ Feature: A command line interface for Test Kitchen
3
3
  As a Test Kitchen user
4
4
  I want a command line interface that has sane defaults and built in help
5
5
 
6
+ @spawn
6
7
  Scenario: Displaying help
7
8
  When I run `kitchen help`
8
9
  Then the exit status should be 0
@@ -3,6 +3,7 @@ Feature: Create a new Test Kitchen Driver project
3
3
  As a user of Test Kitchen
4
4
  I want a command to run that will give me a driver gem project scaffold
5
5
 
6
+ @spawn
6
7
  Scenario: Displaying help
7
8
  When I run `kitchen help driver create`
8
9
  Then the output should contain:
@@ -3,6 +3,7 @@ Feature: Search RubyGems to discover new Test Kitchen Driver gems
3
3
  As a Test Kitchen user
4
4
  I want to run a command which returns candidate Kitchen drivers
5
5
 
6
+ @spawn
6
7
  Scenario: Displaying help
7
8
  When I run `kitchen help driver discover`
8
9
  Then the output should contain:
@@ -0,0 +1,16 @@
1
+ Feature: Using Test Kitchen CLI help
2
+ In order to access the self describing documentation
3
+ As a user of Test Kitchen
4
+ I want to run a command help help for kitchen commands
5
+
6
+ @spawn
7
+ Scenario: Printing help
8
+ When I run `kitchen help`
9
+ Then the exit status should be 0
10
+ And the output should contain "kitchen help [COMMAND]"
11
+
12
+ @spawn
13
+ Scenario: Bad arugments should exit nonzero
14
+ When I run `kitchen help -d always -c`
15
+ Then the exit status should not be 0
16
+ And the output should contain "Usage: "
@@ -3,6 +3,7 @@ Feature: Add Test Kitchen support to an existing project
3
3
  As an operator
4
4
  I want to run a command to initialize my project
5
5
 
6
+ @spawn
6
7
  Scenario: Displaying help
7
8
  When I run `kitchen help init`
8
9
  Then the output should contain:
@@ -12,6 +13,7 @@ Feature: Add Test Kitchen support to an existing project
12
13
  """
13
14
  And the exit status should be 0
14
15
 
16
+ @spawn
15
17
  Scenario: Running init with default values
16
18
  Given a sandboxed GEM_HOME directory named "kitchen-init"
17
19
  And I have a git repository
@@ -13,6 +13,7 @@ Feature: Listing Test Kitchen instances
13
13
  platforms:
14
14
  - name: ubuntu-13.04
15
15
  - name: centos-6.4
16
+ - name: centos-6.4-with-small-mem
16
17
 
17
18
  suites:
18
19
  - name: foobar
@@ -31,6 +32,7 @@ Feature: Listing Test Kitchen instances
31
32
  """
32
33
  foobar-ubuntu-1304
33
34
  foobar-centos-64
35
+ foobar-centos-64-with-small-mem
34
36
 
35
37
  """
36
38
 
@@ -51,12 +53,52 @@ Feature: Listing Test Kitchen instances
51
53
 
52
54
  """
53
55
 
56
+ @spawn
54
57
  Scenario: Listing instances with a regular expression yielding no results
55
58
  When I run `kitchen list freebsd --bare`
56
59
  Then the exit status should not be 0
57
60
  And the output should contain "No instances for regex `freebsd', try running `kitchen list'"
58
61
 
62
+ @spawn
59
63
  Scenario: Listing instances with a bad regular expression
60
64
  When I run `kitchen list *centos* --bare`
61
65
  Then the exit status should not be 0
62
66
  And the output should contain "Invalid Ruby regular expression"
67
+
68
+ Scenario: Listing a full instance name returns an exact match, not fuzzy matches
69
+ When I successfully run `kitchen list foobar-centos-64 --bare`
70
+ Then the output should contain exactly:
71
+ """
72
+ foobar-centos-64
73
+
74
+ """
75
+
76
+ Scenario: Listing a full instance name returns an exact match, not fuzzy matches at start
77
+ Given a file named ".kitchen.yml" with:
78
+ """
79
+ ---
80
+ driver: dummy
81
+ provisioner: chef_solo
82
+
83
+ platforms:
84
+ - name: ubuntu-12.04
85
+
86
+ suites:
87
+ - name: gdb01-master
88
+ - name: logdb01-master
89
+ """
90
+ When I successfully run `kitchen list gdb01-master-ubuntu-1204 --bare`
91
+ Then the output should contain exactly:
92
+ """
93
+ gdb01-master-ubuntu-1204
94
+
95
+ """
96
+
97
+ Scenario: Listing a full instance with regex returns all regex matches
98
+ When I successfully run `kitchen list 'foobar-centos-64.*' --bare`
99
+ Then the output should contain exactly:
100
+ """
101
+ foobar-centos-64
102
+ foobar-centos-64-with-small-mem
103
+
104
+ """
@@ -1,10 +1,35 @@
1
1
  # Set up the environment for testing
2
2
  require 'aruba/cucumber'
3
+ require 'aruba/in_process'
4
+ require 'aruba/spawn_process'
3
5
  require 'kitchen'
6
+ require 'kitchen/cli'
7
+
8
+ class ArubaHelper
9
+ def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
10
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
11
+ end
12
+
13
+ def execute!
14
+ $stdout = @stdout
15
+ $stdin = @stdin
16
+
17
+ kitchen_cli = Kitchen::CLI
18
+ kitchen_cli.start(@argv)
19
+ @kernel.exit(0)
20
+ end
21
+ end
4
22
 
5
23
  Before do
6
24
  @aruba_timeout_seconds = 15
7
25
  @cleanup_dirs = []
26
+
27
+ Aruba::InProcess.main_class = ArubaHelper
28
+ Aruba.process = Aruba::InProcess
29
+ end
30
+
31
+ Before('@spawn') do
32
+ Aruba.process = Aruba::SpawnProcess
8
33
  end
9
34
 
10
35
  After do |s|
data/lib/kitchen.rb CHANGED
@@ -32,7 +32,6 @@ require 'kitchen/color'
32
32
  require 'kitchen/collection'
33
33
  require 'kitchen/config'
34
34
  require 'kitchen/data_munger'
35
- require 'kitchen/diagnostic'
36
35
  require 'kitchen/driver'
37
36
  require 'kitchen/driver/base'
38
37
  require 'kitchen/driver/proxy'
@@ -128,7 +128,7 @@ module Kitchen
128
128
  sync_cmd << busser_setup_env
129
129
  sync_cmd << "#{sudo}#{config[:busser_bin]} suite cleanup"
130
130
  sync_cmd << "#{local_suite_files.map { |f| stream_file(f, remote_file(f, config[:suite_name])) }.join("; ")}"
131
- sync_cmd << "#{helper_files.map { |f| stream_file(f, remote_file(f, "helpers")) }.join}"
131
+ sync_cmd << "#{helper_files.map { |f| stream_file(f, remote_file(f, "helpers")) }.join("; ")}"
132
132
 
133
133
  # use Bourne (/bin/sh) as Bash does not exist on all Unix flavors
134
134
  "sh -c '#{sync_cmd.join('; ')}'"
@@ -193,7 +193,7 @@ module Kitchen
193
193
  end
194
194
 
195
195
  def helper_files
196
- Dir.glob(File.join(config[:test_base_path], "helpers", "*/**/*"))
196
+ Dir.glob(File.join(config[:test_base_path], "helpers", "*/**/*")).reject { |f| File.directory?(f) }
197
197
  end
198
198
 
199
199
  def remote_file(file, dir)
data/lib/kitchen/cli.rb CHANGED
@@ -16,11 +16,7 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require 'benchmark'
20
- require 'erb'
21
- require 'ostruct'
22
19
  require 'thor'
23
- require 'thread'
24
20
 
25
21
  require 'kitchen'
26
22
  require 'kitchen/generator/driver_create'
@@ -33,22 +29,47 @@ module Kitchen
33
29
  # @author Fletcher Nichol <fnichol@nichol.ca>
34
30
  class CLI < Thor
35
31
 
36
- include Thor::Actions
32
+ # Common module to load and invoke a CLI-implementation agnostic command.
33
+ module PerformCommand
34
+
35
+ def perform(task, command, args = nil, additional_options = {})
36
+ require "kitchen/command/#{command}"
37
+
38
+ command_options = {
39
+ :action => task,
40
+ :help => lambda { help(task) },
41
+ :config => @config,
42
+ :shell => shell
43
+ }.merge(additional_options)
44
+
45
+ str_const = Thor::Util.camel_case(command)
46
+ klass = ::Kitchen::Command.const_get(str_const)
47
+ klass.new(args, options, command_options).call
48
+ end
49
+ end
50
+
37
51
  include Logging
52
+ include PerformCommand
53
+
54
+ MAX_CONCURRENCY = 9999
38
55
 
39
56
  # Constructs a new instance.
40
57
  def initialize(*args)
41
58
  super
42
59
  $stdout.sync = true
43
60
  Kitchen.logger = Kitchen.default_file_logger
44
- @loader = Kitchen::Loader::YAML.new(ENV['KITCHEN_YAML'])
61
+ @loader = Kitchen::Loader::YAML.new(
62
+ :project_config => ENV['KITCHEN_YAML'],
63
+ :local_config => ENV['KITCHEN_LOCAL_YAML'],
64
+ :global_config => ENV['KITCHEN_GLOBAL_YAML']
65
+ )
45
66
  @config = Kitchen::Config.new(
46
67
  :loader => @loader,
47
68
  :log_level => ENV.fetch('KITCHEN_LOG', "info").downcase.to_sym
48
69
  )
49
70
  end
50
71
 
51
- desc "list [(all|<REGEX>)]", "List all instances"
72
+ desc "list [INSTANCE|REGEXP|all]", "Lists one or more instances"
52
73
  method_option :bare, :aliases => "-b", :type => :boolean,
53
74
  :desc => "List the name of each instance only, one per line"
54
75
  method_option :debug, :aliases => "-d", :type => :boolean,
@@ -57,18 +78,10 @@ module Kitchen
57
78
  :desc => "Set the log level (debug, info, warn, error, fatal)"
58
79
  def list(*args)
59
80
  update_config!
60
- result = parse_subcommand(args.first)
61
- if options[:debug]
62
- die task, "The --debug flag on the list subcommand is deprecated, " +
63
- "please use `kitchen diagnose'."
64
- elsif options[:bare]
65
- say Array(result).map { |i| i.name }.join("\n")
66
- else
67
- list_table(result)
68
- end
81
+ perform("list", "list", args)
69
82
  end
70
83
 
71
- desc "diagnose [(all|<REGEX>)]", "Show computed diagnostic configuration"
84
+ desc "diagnose [INSTANCE|REGEXP|all]", "Show computed diagnostic configuration"
72
85
  method_option :log_level, :aliases => "-l",
73
86
  :desc => "Set the log level (debug, info, warn, error, fatal)"
74
87
  method_option :loader, :type => :boolean,
@@ -79,36 +92,34 @@ module Kitchen
79
92
  :desc => "Include all diagnostics"
80
93
  def diagnose(*args)
81
94
  update_config!
82
-
83
- loader = if options[:all] || options[:loader]
84
- @loader
85
- else
86
- nil
87
- end
88
- instances = if options[:all] || options[:instances]
89
- parse_subcommand(args.first)
90
- else
91
- []
92
- end
93
-
94
- require 'yaml'
95
- Kitchen::Diagnostic.new(:loader => loader, :instances => instances).
96
- read.to_yaml.each_line { |line| say(line) }
95
+ perform("diagnose", "diagnose", args, :loader => @loader)
97
96
  end
98
97
 
99
98
  [:create, :converge, :setup, :verify, :destroy].each do |action|
100
99
  desc(
101
- "#{action} [(all|<REGEX>)] [opts]",
100
+ "#{action} [INSTANCE|REGEXP|all]",
102
101
  "#{action.capitalize} one or more instances"
103
102
  )
103
+ method_option :concurrency, :aliases => "-c",
104
+ :type => :numeric, :lazy_default => MAX_CONCURRENCY,
105
+ :desc => <<-DESC.gsub(/^\s+/, '').gsub(/\n/, ' ')
106
+ Run a #{action} against all matching instances concurrently. Only N
107
+ instances will run at the same time if a number is given.
108
+ DESC
104
109
  method_option :parallel, :aliases => "-p", :type => :boolean,
105
- :desc => "Perform action against all matching instances in parallel"
110
+ :desc => <<-DESC.gsub(/^\s+/, '').gsub(/\n/, ' ')
111
+ [Future DEPRECATION, use --concurrency]
112
+ Run a #{action} against all matching instances concurrently.
113
+ DESC
106
114
  method_option :log_level, :aliases => "-l",
107
115
  :desc => "Set the log level (debug, info, warn, error, fatal)"
108
- define_method(action) { |*args| exec_action(action) }
116
+ define_method(action) do |*args|
117
+ update_config!
118
+ perform(action, "action", args)
119
+ end
109
120
  end
110
121
 
111
- desc "test [all|<REGEX>)] [opts]", "Test one or more instances"
122
+ desc "test [INSTANCE|REGEXP|all]", "Test one or more instances"
112
123
  long_desc <<-DESC
113
124
  Test one or more instances
114
125
 
@@ -119,8 +130,17 @@ module Kitchen
119
130
  * always: instances will always be destroyed afterwards.\n
120
131
  * never: instances will never be destroyed afterwards.
121
132
  DESC
133
+ method_option :concurrency, :aliases => "-c",
134
+ :type => :numeric, :lazy_default => MAX_CONCURRENCY,
135
+ :desc => <<-DESC.gsub(/^\s+/, '').gsub(/\n/, ' ')
136
+ Run a test against all matching instances concurrently. Only N
137
+ instances will run at the same time if a number is given.
138
+ DESC
122
139
  method_option :parallel, :aliases => "-p", :type => :boolean,
123
- :desc => "Perform action against all matching instances in parallel"
140
+ :desc => <<-DESC.gsub(/^\s+/, '').gsub(/\n/, ' ')
141
+ [Future DEPRECATION, use --concurrency]
142
+ Run a test against all matching instances concurrently.
143
+ DESC
124
144
  method_option :log_level, :aliases => "-l",
125
145
  :desc => "Set the log level (debug, info, warn, error, fatal)"
126
146
  method_option :destroy, :aliases => "-d", :default => "passing",
@@ -128,79 +148,33 @@ module Kitchen
128
148
  method_option :auto_init, :type => :boolean, :default => false,
129
149
  :desc => "Invoke init command if .kitchen.yml is missing"
130
150
  def test(*args)
131
- if ! %w{passing always never}.include?(options[:destroy])
132
- raise ArgumentError, "Destroy mode must be passing, always, or never."
133
- end
134
-
135
151
  update_config!
136
- banner "Starting Kitchen (v#{Kitchen::VERSION})"
137
- elapsed = Benchmark.measure do
138
- ensure_initialized
139
- destroy_mode = options[:destroy].to_sym
140
- @task = :test
141
- results = parse_subcommand(args.join('|'))
142
-
143
- if options[:parallel]
144
- run_parallel(results, destroy_mode)
145
- else
146
- run_serial(results, destroy_mode)
147
- end
148
- end
149
- banner "Kitchen is finished. #{Util.duration(elapsed.real)}"
152
+ ensure_initialized
153
+ perform("test", "test", args)
150
154
  end
151
155
 
152
- desc "login (['REGEX']|[INSTANCE])", "Log in to one instance"
156
+ desc "login INSTANCE|REGEXP", "Log in to one instance"
153
157
  method_option :log_level, :aliases => "-l",
154
158
  :desc => "Set the log level (debug, info, warn, error, fatal)"
155
- def login(regexp)
159
+ def login(*args)
156
160
  update_config!
157
- results = get_filtered_instances(regexp)
158
- if results.size > 1
159
- die task, "Argument `#{regexp}' returned multiple results:\n" +
160
- results.map { |i| " * #{i.name}" }.join("\n")
161
- end
162
- instance = results.pop
163
-
164
- instance.login
161
+ perform("login", "login", args)
165
162
  end
166
163
 
167
164
  desc "version", "Print Kitchen's version information"
168
165
  def version
169
- say "Test Kitchen version #{Kitchen::VERSION}"
166
+ puts "Test Kitchen version #{Kitchen::VERSION}"
170
167
  end
171
168
  map %w(-v --version) => :version
172
169
 
173
170
  desc "sink", "Show the Kitchen sink!", :hide => true
174
171
  def sink
175
- say [
176
- "",
177
- " ___ ",
178
- " ' _ '. ",
179
- " / /` `\\ \\ ",
180
- " | | [__] ",
181
- " | | {{ ",
182
- " | | }} ",
183
- " _ | | _ {{ ",
184
- " ___________<_>_| |_<_>}}________ ",
185
- " .=======^=(___)=^={{====. ",
186
- " / .----------------}}---. \\ ",
187
- " / / {{ \\ \\ ",
188
- " / / }} \\ \\ ",
189
- " ( '=========================' ) ",
190
- " '-----------------------------' ",
191
- " ", # necessary newline
192
- ""
193
- ].map(&:rstrip).join("\n")
172
+ perform("sink", "sink")
194
173
  end
195
174
 
196
175
  desc "console", "Kitchen Console!"
197
176
  def console
198
- require 'pry'
199
- Pry.start(@config, :prompt => pry_prompts)
200
- rescue LoadError => e
201
- warn %{Make sure you have the pry gem installed. You can install it with:}
202
- warn %{`gem install pry` or including 'gem "pry"' in your Gemfile.}
203
- exit 1
177
+ perform("console", "console")
204
178
  end
205
179
 
206
180
  register Kitchen::Generator::Init, "init",
@@ -218,6 +192,8 @@ module Kitchen
218
192
  # @author Fletcher Nichol <fnichol@nichol.ca>
219
193
  class Driver < Thor
220
194
 
195
+ include PerformCommand
196
+
221
197
  register Kitchen::Generator::DriverCreate, "create",
222
198
  "create [NAME]", "Create a new Kitchen Driver gem project"
223
199
  long_desc <<-D, :for => "create"
@@ -238,43 +214,12 @@ module Kitchen
238
214
  relevant drivers will be returned.
239
215
  D
240
216
  def discover
241
- specs = fetch_gem_specs.sort { |x, y| x[0] <=> y[0] }
242
- specs = specs[0, 49].push(["...", "..."]) if specs.size > 49
243
- specs = specs.unshift(["Gem Name", "Latest Stable Release"])
244
- print_table(specs, :indent => 4)
217
+ perform("discover", "driver_discover", args)
245
218
  end
246
219
 
247
220
  def self.basename
248
221
  super + " driver"
249
222
  end
250
-
251
- private
252
-
253
- def fetch_gem_specs
254
- require 'rubygems/spec_fetcher'
255
- SafeYAML::OPTIONS[:suppress_warnings] = true
256
- req = Gem::Requirement.default
257
- dep = Gem::Deprecate.skip_during do
258
- Gem::Dependency.new(/kitchen-/i, req)
259
- end
260
- fetcher = Gem::SpecFetcher.fetcher
261
-
262
- specs = if fetcher.respond_to?(:find_matching)
263
- fetch_gem_specs_pre_rubygems_2(fetcher, dep)
264
- else
265
- fetch_gem_specs_post_rubygems_2(fetcher, dep)
266
- end
267
- end
268
-
269
- def fetch_gem_specs_pre_rubygems_2(fetcher, dep)
270
- specs = fetcher.find_matching(dep, false, false, false)
271
- specs.map { |t| t.first }.map { |t| t[0, 2] }
272
- end
273
-
274
- def fetch_gem_specs_post_rubygems_2(fetcher, dep)
275
- specs = fetcher.spec_for_dependency(dep, false)
276
- specs.first.map { |t| [t.first.name, t.first.version] }
277
- end
278
223
  end
279
224
 
280
225
  register Kitchen::CLI::Driver, "driver",
@@ -293,111 +238,30 @@ module Kitchen
293
238
 
294
239
  private
295
240
 
296
- attr_reader :task
241
+ def self.exit_on_failure?
242
+ true
243
+ end
297
244
 
298
245
  def logger
299
246
  Kitchen.logger
300
247
  end
301
248
 
302
- def exec_action(action)
303
- update_config!
304
- banner "Starting Kitchen (v#{Kitchen::VERSION})"
305
- elapsed = Benchmark.measure do
306
- @task = action
307
- results = parse_subcommand(args.first)
308
- options[:parallel] ? run_parallel(results) : run_serial(results)
309
- end
310
- banner "Kitchen is finished. #{Util.duration(elapsed.real)}"
311
- end
312
-
313
- def run_serial(instances, *args)
314
- Array(instances).map { |i| i.public_send(task, *args) }
315
- end
316
-
317
- def run_parallel(instances, *args)
318
- threads = Array(instances).map do |i|
319
- Thread.new do
320
- i.public_send(task, *args)
321
- end
322
- end
323
- threads.map { |i| i.join }
324
- end
325
-
326
- def parse_subcommand(arg = nil)
327
- arg == "all" ? get_all_instances : get_filtered_instances(arg)
328
- end
329
-
330
- def get_all_instances
331
- result = @config.instances
332
-
333
- if result.empty?
334
- die task, "No instances defined"
335
- else
336
- result
337
- end
338
- end
339
-
340
- def get_filtered_instances(regexp)
341
- result = begin
342
- @config.instances.get_all(/#{regexp}/)
343
- rescue RegexpError => e
344
- die task, "Invalid Ruby regular expression, " +
345
- "you may need to single quote the argument. " +
346
- "Please try again or consult http://rubular.com/ (#{e.message})"
347
- end
348
-
349
- if result.empty?
350
- die task, "No instances for regex `#{regexp}', try running `kitchen list'"
351
- else
352
- result
353
- end
354
- end
355
-
356
- def list_table(result)
357
- table = [
358
- [set_color("Instance", :green), set_color("Driver", :green),
359
- set_color("Provisioner", :green), set_color("Last Action", :green)]
360
- ]
361
- table += Array(result).map { |i| display_instance(i) }
362
- print_table(table)
363
- end
364
-
365
- def display_instance(instance)
366
- [
367
- color_pad(instance.name),
368
- color_pad(instance.driver.name),
369
- color_pad(instance.provisioner.name),
370
- format_last_action(instance.last_action)
371
- ]
372
- end
373
-
374
- def color_pad(string)
375
- string + set_color("", :white)
376
- end
377
-
378
- def format_last_action(last_action)
379
- case last_action
380
- when 'create' then set_color("Created", :cyan)
381
- when 'converge' then set_color("Converged", :magenta)
382
- when 'setup' then set_color("Set Up", :blue)
383
- when 'verify' then set_color("Verified", :yellow)
384
- when nil then set_color("<Not Created>", :red)
385
- else set_color("<Unknown>", :white)
386
- end
387
- end
388
-
389
249
  def update_config!
390
250
  if options[:log_level]
391
251
  level = options[:log_level].downcase.to_sym
392
252
  @config.log_level = level
393
253
  Kitchen.logger.level = Util.to_logger_level(level)
394
254
  end
395
- end
396
255
 
397
- def die(task, msg)
398
- error "\n#{msg}\n\n"
399
- help(task)
400
- exit 1
256
+ if options[:parallel]
257
+ # warn here in a future release when option is used
258
+ @options = Thor::CoreExt::HashWithIndifferentAccess.new(options.to_hash)
259
+ if options[:parallel] && !options[:concurrency]
260
+ options[:concurrency] = MAX_CONCURRENCY
261
+ end
262
+ options.delete(:parallel)
263
+ options.freeze
264
+ end
401
265
  end
402
266
 
403
267
  def ensure_initialized
@@ -408,22 +272,5 @@ module Kitchen
408
272
  invoke "init"
409
273
  end
410
274
  end
411
-
412
- def pry_prompts
413
- [
414
- proc { |target_self, nest_level, pry|
415
- ["[#{pry.input_array.size}] ",
416
- "kc(#{Pry.view_clip(target_self.class)})",
417
- "#{":#{nest_level}" unless nest_level.zero?}> "
418
- ].join
419
- },
420
- proc { |target_self, nest_level, pry|
421
- ["[#{pry.input_array.size}] ",
422
- "kc(#{Pry.view_clip(target_self.class)})",
423
- "#{":#{nest_level}" unless nest_level.zero?}* "
424
- ].join
425
- },
426
- ]
427
- end
428
275
  end
429
276
  end