test-kitchen 0.7.0 → 1.0.0.alpha.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 (95) hide show
  1. data/.gitignore +20 -0
  2. data/.travis.yml +11 -0
  3. data/.yardopts +3 -0
  4. data/Gemfile +13 -0
  5. data/Guardfile +11 -0
  6. data/LICENSE +15 -0
  7. data/README.md +131 -0
  8. data/Rakefile +69 -0
  9. data/bin/kitchen +9 -4
  10. data/features/cli.feature +17 -0
  11. data/features/cli_init.feature +156 -0
  12. data/features/support/env.rb +14 -0
  13. data/lib/kitchen/busser.rb +166 -0
  14. data/lib/kitchen/chef_data_uploader.rb +156 -0
  15. data/lib/kitchen/cli.rb +540 -0
  16. data/lib/kitchen/collection.rb +55 -0
  17. data/lib/kitchen/color.rb +46 -0
  18. data/lib/kitchen/config.rb +223 -0
  19. data/lib/kitchen/driver/base.rb +180 -0
  20. data/lib/kitchen/driver/dummy.rb +81 -0
  21. data/lib/kitchen/driver/ssh_base.rb +192 -0
  22. data/lib/kitchen/driver.rb +42 -0
  23. data/lib/kitchen/errors.rb +52 -0
  24. data/lib/kitchen/instance.rb +327 -0
  25. data/lib/kitchen/instance_actor.rb +42 -0
  26. data/lib/kitchen/loader/yaml.rb +105 -0
  27. data/lib/kitchen/logger.rb +145 -0
  28. data/{cookbooks/test-kitchen/libraries/helpers.rb → lib/kitchen/logging.rb} +13 -9
  29. data/lib/kitchen/manager.rb +45 -0
  30. data/lib/kitchen/metadata_chopper.rb +52 -0
  31. data/lib/kitchen/platform.rb +61 -0
  32. data/lib/kitchen/rake_tasks.rb +59 -0
  33. data/lib/kitchen/shell_out.rb +65 -0
  34. data/lib/kitchen/state_file.rb +88 -0
  35. data/lib/kitchen/suite.rb +76 -0
  36. data/lib/kitchen/thor_tasks.rb +62 -0
  37. data/lib/kitchen/util.rb +79 -0
  38. data/{cookbooks/test-kitchen/recipes/erlang.rb → lib/kitchen/version.rb} +9 -6
  39. data/lib/kitchen.rb +98 -0
  40. data/lib/vendor/hash_recursive_merge.rb +74 -0
  41. data/spec/kitchen/collection_spec.rb +80 -0
  42. data/spec/kitchen/color_spec.rb +54 -0
  43. data/spec/kitchen/config_spec.rb +201 -0
  44. data/spec/kitchen/driver/dummy_spec.rb +191 -0
  45. data/spec/kitchen/instance_spec.rb +162 -0
  46. data/spec/kitchen/loader/yaml_spec.rb +243 -0
  47. data/spec/kitchen/platform_spec.rb +48 -0
  48. data/spec/kitchen/state_file_spec.rb +122 -0
  49. data/spec/kitchen/suite_spec.rb +64 -0
  50. data/spec/spec_helper.rb +47 -0
  51. data/templates/plugin/driver.rb.erb +23 -0
  52. data/templates/plugin/license_apachev2.erb +15 -0
  53. data/templates/plugin/license_gplv2.erb +18 -0
  54. data/templates/plugin/license_gplv3.erb +16 -0
  55. data/templates/plugin/license_mit.erb +22 -0
  56. data/templates/plugin/license_reserved.erb +5 -0
  57. data/templates/plugin/version.rb.erb +12 -0
  58. data/test-kitchen.gemspec +44 -0
  59. metadata +290 -82
  60. data/config/Cheffile +0 -47
  61. data/config/Kitchenfile +0 -39
  62. data/config/Vagrantfile +0 -114
  63. data/cookbooks/test-kitchen/attributes/default.rb +0 -25
  64. data/cookbooks/test-kitchen/metadata.rb +0 -27
  65. data/cookbooks/test-kitchen/recipes/chef.rb +0 -19
  66. data/cookbooks/test-kitchen/recipes/compat.rb +0 -39
  67. data/cookbooks/test-kitchen/recipes/default.rb +0 -51
  68. data/cookbooks/test-kitchen/recipes/ruby.rb +0 -29
  69. data/lib/test-kitchen/cli/destroy.rb +0 -36
  70. data/lib/test-kitchen/cli/init.rb +0 -37
  71. data/lib/test-kitchen/cli/platform_list.rb +0 -37
  72. data/lib/test-kitchen/cli/project_info.rb +0 -44
  73. data/lib/test-kitchen/cli/ssh.rb +0 -36
  74. data/lib/test-kitchen/cli/status.rb +0 -36
  75. data/lib/test-kitchen/cli/test.rb +0 -68
  76. data/lib/test-kitchen/cli.rb +0 -282
  77. data/lib/test-kitchen/dsl.rb +0 -63
  78. data/lib/test-kitchen/environment.rb +0 -166
  79. data/lib/test-kitchen/platform.rb +0 -79
  80. data/lib/test-kitchen/project/base.rb +0 -159
  81. data/lib/test-kitchen/project/cookbook.rb +0 -97
  82. data/lib/test-kitchen/project/cookbook_copy.rb +0 -58
  83. data/lib/test-kitchen/project/ruby.rb +0 -37
  84. data/lib/test-kitchen/project/supported_platforms.rb +0 -75
  85. data/lib/test-kitchen/project.rb +0 -23
  86. data/lib/test-kitchen/runner/base.rb +0 -154
  87. data/lib/test-kitchen/runner/openstack/dsl.rb +0 -39
  88. data/lib/test-kitchen/runner/openstack/environment.rb +0 -141
  89. data/lib/test-kitchen/runner/openstack.rb +0 -147
  90. data/lib/test-kitchen/runner/vagrant.rb +0 -95
  91. data/lib/test-kitchen/runner.rb +0 -21
  92. data/lib/test-kitchen/scaffold.rb +0 -88
  93. data/lib/test-kitchen/ui.rb +0 -73
  94. data/lib/test-kitchen/version.rb +0 -21
  95. data/lib/test-kitchen.rb +0 -34
@@ -0,0 +1,105 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'erb'
20
+ require 'vendor/hash_recursive_merge'
21
+ require 'safe_yaml'
22
+
23
+ module Kitchen
24
+
25
+ module Loader
26
+
27
+ # YAML file loader for Test Kitchen configuration. This class is
28
+ # responisble for parsing the main YAML file and the local YAML if it
29
+ # exists. Local file configuration will win over the default configuration.
30
+ # The client of this class should not require any YAML loading or parsing
31
+ # logic.
32
+ #
33
+ # @author Fletcher Nichol <fnichol@nichol.ca>
34
+ class YAML
35
+
36
+ attr_reader :config_file
37
+
38
+ # Creates a new loader that can parse and load YAML files.
39
+ #
40
+ # @param config_file [String] path to Kitchen config YAML file
41
+ # @param options [Hash] configuration for a new loader
42
+ # @option options [String] :process_erb whether or not to process YAML
43
+ # through an ERB processor (default: `true`)
44
+ # @option options [String] :process_local whether or not to process a
45
+ # local kitchen YAML file, if it exists (default: `true`)
46
+ def initialize(config_file = nil, options = {})
47
+ @config_file = File.expand_path(config_file || default_config_file)
48
+ @process_erb = options.fetch(:process_erb, true)
49
+ @process_local = options.fetch(:process_local, true)
50
+ end
51
+
52
+ # Reads, parses, and merges YAML configuration files and returns a Hash
53
+ # of tne merged data.
54
+ #
55
+ # @return [Hash] merged configuration data
56
+ def read
57
+ if ! File.exists?(config_file)
58
+ raise UserError, "Kitchen YAML file #{config_file} does not exist."
59
+ end
60
+
61
+ Util.symbolized_hash(combined_hash)
62
+ end
63
+
64
+ protected
65
+
66
+ def default_config_file
67
+ File.join(Dir.pwd, '.kitchen.yml')
68
+ end
69
+
70
+ def combined_hash
71
+ @process_local ? yaml.rmerge(local_yaml) : yaml
72
+ end
73
+
74
+ def yaml
75
+ parse_yaml_string(yaml_string(config_file), config_file)
76
+ end
77
+
78
+ def local_yaml
79
+ parse_yaml_string(yaml_string(local_config_file), local_config_file)
80
+ end
81
+
82
+ def yaml_string(file)
83
+ string = read_file(file)
84
+
85
+ @process_erb ? ERB.new(string).result : string
86
+ end
87
+
88
+ def read_file(file)
89
+ File.exists?(file.to_s) ? IO.read(file) : ""
90
+ end
91
+
92
+ def local_config_file
93
+ config_file.sub(/(#{File.extname(config_file)})$/, '.local\1')
94
+ end
95
+
96
+ def parse_yaml_string(string, file_name)
97
+ return Hash.new if string.nil? || string.empty?
98
+
99
+ ::YAML.safe_load(string)
100
+ rescue SyntaxError => ex
101
+ raise UserError, "Error parsing #{file_name} (#{ex.message})"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,145 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'fileutils'
20
+ require 'logger'
21
+
22
+ module Kitchen
23
+
24
+ # Logging implementation for Kitchen. By default the console/stdout output
25
+ # will be displayed differently than the file log output. Therefor, this
26
+ # class wraps multiple loggers that conform to the stdlib `Logger` class
27
+ # behavior.
28
+ #
29
+ # @author Fletcher Nichol <fnichol@nichol.ca>
30
+ class Logger
31
+
32
+ include ::Logger::Severity
33
+
34
+ attr_reader :logdev
35
+
36
+ def initialize(options = {})
37
+ color = options[:color] || :bright_white
38
+
39
+ @loggers = []
40
+ @loggers << @logdev = logdev_logger(options[:logdev]) if options[:logdev]
41
+ @loggers << stdout_logger(options[:stdout], color) if options[:stdout]
42
+ @loggers << stdout_logger(STDOUT, color) if @loggers.empty?
43
+
44
+ self.progname = options[:progname] || "Kitchen"
45
+ self.level = options[:level] || default_log_level
46
+ end
47
+
48
+ %w{ level progname datetime_format debug? info? error? warn? fatal?
49
+ }.each do |meth|
50
+ define_method(meth) do |*args|
51
+ @loggers.first.public_send(meth, *args)
52
+ end
53
+ end
54
+
55
+ %w{ level= progname= datetime_format= add <<
56
+ banner debug info error warn fatal unknown close
57
+ }.map(&:to_sym).each do |meth|
58
+ define_method(meth) do |*args|
59
+ result = nil
60
+ @loggers.each { |l| result = l.public_send(meth, *args) }
61
+ result
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def default_log_level
68
+ Util.to_logger_level(Kitchen::DEFAULT_LOG_LEVEL)
69
+ end
70
+
71
+ def stdout_logger(stdout, color)
72
+ logger = StdoutLogger.new(stdout)
73
+ logger.formatter = proc do |severity, datetime, progname, msg|
74
+ Color.colorize("#{msg}\n", color)
75
+ end
76
+ logger
77
+ end
78
+
79
+ def logdev_logger(filepath_or_logdev)
80
+ LogdevLogger.new(resolve_logdev(filepath_or_logdev))
81
+ end
82
+
83
+ def resolve_logdev(filepath_or_logdev)
84
+ if filepath_or_logdev.is_a? String
85
+ FileUtils.mkdir_p(File.dirname(filepath_or_logdev))
86
+ file = File.open(File.expand_path(filepath_or_logdev), "ab")
87
+ file.sync = true
88
+ file
89
+ else
90
+ filepath_or_logdev
91
+ end
92
+ end
93
+
94
+ # Internal class which adds a #banner method call that displays the
95
+ # message with a callout arrow.
96
+ class LogdevLogger < ::Logger
97
+
98
+ alias_method :super_info, :info
99
+
100
+ def <<(msg)
101
+ msg =~ /\n/ ? msg.split("\n").each { |l| format_line(l) } : super
102
+ end
103
+
104
+ def banner(msg = nil, &block)
105
+ super_info("-----> #{msg}", &block)
106
+ end
107
+
108
+ private
109
+
110
+ def format_line(line)
111
+ case line
112
+ when %r{^-----> } then banner(line.gsub(%r{^[ >-]{6} }, ''))
113
+ when %r{^>>>>>> } then error(line.gsub(%r{^[ >-]{6} }, ''))
114
+ when %r{^ } then info(line.gsub(%r{^[ >-]{6} }, ''))
115
+ else info(line)
116
+ end
117
+ end
118
+ end
119
+
120
+ # Internal class which reformats logging methods for display as console
121
+ # output.
122
+ class StdoutLogger < LogdevLogger
123
+
124
+ def debug(msg = nil, &block)
125
+ super("D #{msg}", &block)
126
+ end
127
+
128
+ def info(msg = nil, &block)
129
+ super(" #{msg}", &block)
130
+ end
131
+
132
+ def warn(msg = nil, &block)
133
+ super("$$$$$$ #{msg}", &block)
134
+ end
135
+
136
+ def error(msg = nil, &block)
137
+ super(">>>>>> #{msg}", &block)
138
+ end
139
+
140
+ def fatal(msg = nil, &block)
141
+ super("!!!!!! #{msg}", &block)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -1,25 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  #
2
- # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
- # Copyright:: Copyright (c) 2012 Opscode, Inc.
4
- # License:: Apache License, Version 2.0
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
5
6
  #
6
7
  # Licensed under the Apache License, Version 2.0 (the "License");
7
8
  # you may not use this file except in compliance with the License.
8
9
  # You may obtain a copy of the License at
9
10
  #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
+ # http://www.apache.org/licenses/LICENSE-2.0
11
12
  #
12
13
  # Unless required by applicable law or agreed to in writing, software
13
14
  # distributed under the License is distributed on an "AS IS" BASIS,
14
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
16
  # See the License for the specific language governing permissions and
16
17
  # limitations under the License.
17
- #
18
18
 
19
- class Chef::Recipe
20
- def recipe_for_project?(project_name)
21
- run_context.cookbook_collection['test-kitchen'].recipe_files.any? do |rf|
22
- rf =~ Regexp.new(project_name)
19
+ module Kitchen
20
+
21
+ module Logging
22
+
23
+ %w{banner debug info warn error fatal}.map(&:to_sym).each do |meth|
24
+ define_method(meth) do |*args|
25
+ logger.public_send(meth, *args)
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -0,0 +1,45 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module Kitchen
20
+
21
+ # A class to manage actors.
22
+ #
23
+ # @author Fletcher Nichol <fnichol@nichol.ca>
24
+ class Manager
25
+
26
+ include Celluloid
27
+
28
+ trap_exit :actor_died
29
+
30
+ # Terminate all actors that are linked.
31
+ def stop
32
+ Array(links.to_a).map { |actor| actor.terminate if actor.alive? }
33
+ end
34
+
35
+ private
36
+
37
+ def actor_died(actor, reason)
38
+ if reason.nil?
39
+ Kitchen.logger.debug("Actor terminated cleanly")
40
+ else
41
+ Kitchen.logger.debug("An actor crashed due to #{reason.inspect}")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,52 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2012, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module Kitchen
20
+
21
+ # A rather insane and questionable class to quickly consume a metadata.rb
22
+ # file and return the cookbook name and version attributes.
23
+ #
24
+ # @see https://twitter.com/fnichol/status/281650077901144064
25
+ # @see https://gist.github.com/4343327
26
+ class MetadataChopper < Hash
27
+
28
+ # Return an Array containing the cookbook name and version attributes,
29
+ # or nil values if they could not be parsed.
30
+ #
31
+ # @param metadata_file [String] path to a metadata.rb file
32
+ # @return [Array<String>] array containing the cookbook name and version
33
+ # attributes or nil values if they could not be determined
34
+ def self.extract(metadata_file)
35
+ mc = new(File.expand_path(metadata_file))
36
+ [mc[:name], mc[:version]]
37
+ end
38
+
39
+ # Creates a new instances and loads in the contents of the metdata.rb
40
+ # file. If you value your life, you may want to avoid reading the
41
+ # implementation.
42
+ #
43
+ # @param metadata_file [String] path to a metadata.rb file
44
+ def initialize(metadata_file)
45
+ eval(IO.read(metadata_file), nil, metadata_file)
46
+ end
47
+
48
+ def method_missing(meth, *args, &block)
49
+ self[meth] = args.first
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,61 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2012, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module Kitchen
20
+
21
+ # A target operating system environment in which convergence integration
22
+ # will take place. This may represent a specific operating system, version,
23
+ # and machine architecture.
24
+ #
25
+ # @author Fletcher Nichol <fnichol@nichol.ca>
26
+ class Platform
27
+
28
+ # @return [String] logical name of this platform
29
+ attr_reader :name
30
+
31
+ # @return [Array] Array of Chef run_list items
32
+ attr_reader :run_list
33
+
34
+ # @return [Hash] Hash of Chef node attributes
35
+ attr_reader :attributes
36
+
37
+ # Constructs a new platform.
38
+ #
39
+ # @param [Hash] options configuration for a new platform
40
+ # @option options [String] :name logical name of this platform
41
+ # (**Required**)
42
+ # @option options [Array<String>] :run_list Array of Chef run_list
43
+ # items
44
+ # @option options [Hash] :attributes Hash of Chef node attributes
45
+ def initialize(options = {})
46
+ validate_options(options)
47
+
48
+ @name = options[:name]
49
+ @run_list = Array(options[:run_list])
50
+ @attributes = options[:attributes] || Hash.new
51
+ end
52
+
53
+ private
54
+
55
+ def validate_options(opts)
56
+ [:name].each do |k|
57
+ raise ClientError, "Platform#new requires option :#{k}" if opts[k].nil?
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,59 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2012, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'rake/tasklib'
20
+
21
+ require 'kitchen'
22
+
23
+ module Kitchen
24
+
25
+ # Kitchen Rake task generator.
26
+ #
27
+ # @author Fletcher Nichol <fnichol@nichol.ca>
28
+ class RakeTasks < ::Rake::TaskLib
29
+
30
+ # Creates Kitchen Rake tasks and allows the callee to configure it.
31
+ #
32
+ # @yield [self] gives itself to the block
33
+ def initialize
34
+ @config = Kitchen::Config.new
35
+ @config.supervised = false
36
+ Kitchen.logger = Kitchen.default_file_logger
37
+ yield self if block_given?
38
+ define
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :config
44
+
45
+ def define
46
+ namespace "kitchen" do
47
+ config.instances.each do |instance|
48
+ desc "Run #{instance.name} test instance"
49
+ task instance.name do
50
+ instance.test(:always)
51
+ end
52
+ end
53
+
54
+ desc "Run all test instances"
55
+ task "all" => config.instances.map { |i| i.name }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2012, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'mixlib/shellout'
20
+
21
+ module Kitchen
22
+
23
+ # Mixin that wraps a command shell out invocation, providing a #run_command
24
+ # method.
25
+ #
26
+ # @author Fletcher Nichol <fnichol@nichol.ca>
27
+ module ShellOut
28
+
29
+ # Wrapped exception for any interally raised shell out commands.
30
+ class ShellCommandFailed < TransientFailure ; end
31
+
32
+ # Executes a command in a subshell on the local running system.
33
+ #
34
+ # @param cmd [String] command to be executed locally
35
+ # @param use_sudo [TrueClass, FalseClass] whether or not to use sudo
36
+ # @param log_subject [String] used in the output or log header for clarity
37
+ # and context
38
+ # @raise [ShellCommandFailed] if the command fails
39
+ # @raise [Error] for all other unexpected exceptions
40
+ def run_command(cmd, use_sudo = false, log_subject = "local")
41
+ cmd = "sudo #{cmd}" if use_sudo
42
+ subject = "[#{log_subject} command]"
43
+
44
+ info("#{subject} BEGIN (#{display_cmd(cmd)})")
45
+ sh = Mixlib::ShellOut.new(cmd, :live_stream => logger, :timeout => 60000)
46
+ sh.run_command
47
+ info("#{subject} END #{Util.duration(sh.execution_time)}")
48
+ sh.error!
49
+ rescue Mixlib::ShellOut::ShellCommandFailed => ex
50
+ raise ShellCommandFailed, ex.message
51
+ rescue Exception => error
52
+ error.extend(Kitchen::Error)
53
+ raise
54
+ end
55
+
56
+ private
57
+
58
+ def display_cmd(cmd)
59
+ first_line, newline, rest = cmd.partition("\n")
60
+ last_char = cmd[cmd.size - 1]
61
+
62
+ newline == "\n" ? "#{first_line}\\n...#{last_char}" : cmd
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,88 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'safe_yaml'
20
+
21
+ module Kitchen
22
+
23
+ # Exception class for any exceptions raised when reading and parsing a state
24
+ # file from disk
25
+ class StateFileLoadError < StandardError ; end
26
+
27
+ # State persistence manager for instances between actions and invocations.
28
+ class StateFile
29
+
30
+ # Constructs an new instance taking the kitchen root and instance name.
31
+ #
32
+ # @param kitchen_root [String] path to the Kitchen project's root directory
33
+ # @param name [String] name of the instance representing this state
34
+ def initialize(kitchen_root, name)
35
+ @file_name = File.expand_path(
36
+ File.join(kitchen_root, ".kitchen", "#{name}.yml")
37
+ )
38
+ end
39
+
40
+ # Reads and loads an instance's state into a Hash data structure which is
41
+ # returned.
42
+ #
43
+ # @return [Hash] a hash representation of an instance's state
44
+ # @raise [StateFileLoadError] if there is a problem loading the state file
45
+ # from disk and loading it into a Hash
46
+ def read
47
+ if File.exists?(file_name)
48
+ Util.symbolized_hash(deserialize_string(read_file))
49
+ else
50
+ Hash.new
51
+ end
52
+ end
53
+
54
+ # Serializes the state hash and writes a state file to disk.
55
+ #
56
+ # @param state [Hash] the current state of the instance
57
+ def write(state)
58
+ dir = File.dirname(file_name)
59
+ serialized_string = serialize_hash(Util.stringified_hash(state))
60
+
61
+ FileUtils.mkdir_p(dir) if ! File.directory?(dir)
62
+ File.open(file_name, "wb") { |f| f.write(serialized_string) }
63
+ end
64
+
65
+ # Destroys a state file on disk if it exists.
66
+ def destroy
67
+ FileUtils.rm_f(file_name) if File.exists?(file_name)
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :file_name
73
+
74
+ def read_file
75
+ IO.read(file_name)
76
+ end
77
+
78
+ def deserialize_string(string)
79
+ ::YAML.safe_load(string)
80
+ rescue SyntaxError => ex
81
+ raise StateFileLoadError, "Error parsing #{file_name} (#{ex.message})"
82
+ end
83
+
84
+ def serialize_hash(hash)
85
+ ::YAML.dump(hash)
86
+ end
87
+ end
88
+ end