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