test-kitchen 0.7.0 → 1.0.0.alpha.0

Sign up to get free protection for your applications and to get access to all the features.
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