test-kitchen 1.1.1 → 1.2.0

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