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.
- data/.gitignore +20 -0
- data/.travis.yml +11 -0
- data/.yardopts +3 -0
- data/Gemfile +13 -0
- data/Guardfile +11 -0
- data/LICENSE +15 -0
- data/README.md +131 -0
- data/Rakefile +69 -0
- data/bin/kitchen +9 -4
- data/features/cli.feature +17 -0
- data/features/cli_init.feature +156 -0
- data/features/support/env.rb +14 -0
- data/lib/kitchen/busser.rb +166 -0
- data/lib/kitchen/chef_data_uploader.rb +156 -0
- data/lib/kitchen/cli.rb +540 -0
- data/lib/kitchen/collection.rb +55 -0
- data/lib/kitchen/color.rb +46 -0
- data/lib/kitchen/config.rb +223 -0
- data/lib/kitchen/driver/base.rb +180 -0
- data/lib/kitchen/driver/dummy.rb +81 -0
- data/lib/kitchen/driver/ssh_base.rb +192 -0
- data/lib/kitchen/driver.rb +42 -0
- data/lib/kitchen/errors.rb +52 -0
- data/lib/kitchen/instance.rb +327 -0
- data/lib/kitchen/instance_actor.rb +42 -0
- data/lib/kitchen/loader/yaml.rb +105 -0
- data/lib/kitchen/logger.rb +145 -0
- data/{cookbooks/test-kitchen/libraries/helpers.rb → lib/kitchen/logging.rb} +13 -9
- data/lib/kitchen/manager.rb +45 -0
- data/lib/kitchen/metadata_chopper.rb +52 -0
- data/lib/kitchen/platform.rb +61 -0
- data/lib/kitchen/rake_tasks.rb +59 -0
- data/lib/kitchen/shell_out.rb +65 -0
- data/lib/kitchen/state_file.rb +88 -0
- data/lib/kitchen/suite.rb +76 -0
- data/lib/kitchen/thor_tasks.rb +62 -0
- data/lib/kitchen/util.rb +79 -0
- data/{cookbooks/test-kitchen/recipes/erlang.rb → lib/kitchen/version.rb} +9 -6
- data/lib/kitchen.rb +98 -0
- data/lib/vendor/hash_recursive_merge.rb +74 -0
- data/spec/kitchen/collection_spec.rb +80 -0
- data/spec/kitchen/color_spec.rb +54 -0
- data/spec/kitchen/config_spec.rb +201 -0
- data/spec/kitchen/driver/dummy_spec.rb +191 -0
- data/spec/kitchen/instance_spec.rb +162 -0
- data/spec/kitchen/loader/yaml_spec.rb +243 -0
- data/spec/kitchen/platform_spec.rb +48 -0
- data/spec/kitchen/state_file_spec.rb +122 -0
- data/spec/kitchen/suite_spec.rb +64 -0
- data/spec/spec_helper.rb +47 -0
- data/templates/plugin/driver.rb.erb +23 -0
- data/templates/plugin/license_apachev2.erb +15 -0
- data/templates/plugin/license_gplv2.erb +18 -0
- data/templates/plugin/license_gplv3.erb +16 -0
- data/templates/plugin/license_mit.erb +22 -0
- data/templates/plugin/license_reserved.erb +5 -0
- data/templates/plugin/version.rb.erb +12 -0
- data/test-kitchen.gemspec +44 -0
- metadata +290 -82
- data/config/Cheffile +0 -47
- data/config/Kitchenfile +0 -39
- data/config/Vagrantfile +0 -114
- data/cookbooks/test-kitchen/attributes/default.rb +0 -25
- data/cookbooks/test-kitchen/metadata.rb +0 -27
- data/cookbooks/test-kitchen/recipes/chef.rb +0 -19
- data/cookbooks/test-kitchen/recipes/compat.rb +0 -39
- data/cookbooks/test-kitchen/recipes/default.rb +0 -51
- data/cookbooks/test-kitchen/recipes/ruby.rb +0 -29
- data/lib/test-kitchen/cli/destroy.rb +0 -36
- data/lib/test-kitchen/cli/init.rb +0 -37
- data/lib/test-kitchen/cli/platform_list.rb +0 -37
- data/lib/test-kitchen/cli/project_info.rb +0 -44
- data/lib/test-kitchen/cli/ssh.rb +0 -36
- data/lib/test-kitchen/cli/status.rb +0 -36
- data/lib/test-kitchen/cli/test.rb +0 -68
- data/lib/test-kitchen/cli.rb +0 -282
- data/lib/test-kitchen/dsl.rb +0 -63
- data/lib/test-kitchen/environment.rb +0 -166
- data/lib/test-kitchen/platform.rb +0 -79
- data/lib/test-kitchen/project/base.rb +0 -159
- data/lib/test-kitchen/project/cookbook.rb +0 -97
- data/lib/test-kitchen/project/cookbook_copy.rb +0 -58
- data/lib/test-kitchen/project/ruby.rb +0 -37
- data/lib/test-kitchen/project/supported_platforms.rb +0 -75
- data/lib/test-kitchen/project.rb +0 -23
- data/lib/test-kitchen/runner/base.rb +0 -154
- data/lib/test-kitchen/runner/openstack/dsl.rb +0 -39
- data/lib/test-kitchen/runner/openstack/environment.rb +0 -141
- data/lib/test-kitchen/runner/openstack.rb +0 -147
- data/lib/test-kitchen/runner/vagrant.rb +0 -95
- data/lib/test-kitchen/runner.rb +0 -21
- data/lib/test-kitchen/scaffold.rb +0 -88
- data/lib/test-kitchen/ui.rb +0 -73
- data/lib/test-kitchen/version.rb +0 -21
- 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::
|
3
|
-
#
|
4
|
-
#
|
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
|
-
#
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|