Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for multiple step converge #564

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/kitchen/command/list.rb
Expand Up @@ -61,7 +61,7 @@ def display_instance(instance)
[
color_pad(instance.name),
color_pad(instance.driver.name),
color_pad(instance.provisioner.name),
color_pad(instance.provisioners.last.name),
format_last_action(instance.last_action)
]
end
Expand Down
31 changes: 28 additions & 3 deletions lib/kitchen/config.rb
Expand Up @@ -120,6 +120,13 @@ def suites

private

def each_step(suite_name)
name, step = * suite_name.split(/_step_/)
(1..step.to_i).map do |count|
yield suites.find { |suite| suite.name == "#{name}_step_#{count}" }
end
end

# Builds the filtered list of Instance objects.
#
# @return [Array<Instance] an array of Instances
Expand Down Expand Up @@ -241,7 +248,7 @@ def new_instance(suite, platform, index)
:logger => new_logger(suite, platform, index),
:suite => suite,
:platform => platform,
:provisioner => new_provisioner(suite, platform),
:provisioners => new_provisioners(suite, platform),
:state_file => new_state_file(suite, platform)
)
end
Expand All @@ -265,8 +272,26 @@ def new_logger(suite, platform, index)
)
end

# Builds a newly configured Provisioner object, for a given Suite and
# Platform.
# Builds an array of newly configured Provisioner objects,
# for a given Suite and Platform.
#
# @param suite [Suite,#name] a Suite
# @param platform [Platform,#name] a Platform
# @return Array[Provisioner] a new Provisioner object
# @api private
def new_provisioners(suite, platform)
# check if suite name otherwise is a suite step
if data.steps?(suite.name)
each_step(suite.name) do |step|
new_provisioner(step, platform)
end
else
[new_provisioner(suite, platform)]
end
end

# Builds a newly configured Provisioner object, for a given Suite
# and Platform.
#
# @param suite [Suite,#name] a Suite
# @param platform [Platform,#name] a Platform
Expand Down
21 changes: 18 additions & 3 deletions lib/kitchen/data_munger.rb
Expand Up @@ -104,7 +104,21 @@ def provisioner_data_for(suite, platform)
#
# @return [Array<Hash>] an Array of Hashes
def suite_data
data.fetch(:suites, [])
@suite_data ||= data.fetch(:suites, []).inject([]) do |suites, suite|
if suite[:steps]
suite[:steps].each_with_index do |sub_suite, index|
sub_suite[:name] = "#{suite[:name]}_step_#{index + 1}"
suites << sub_suite
end
else
suites << suite
end
suites
end
end

def steps?(suite_name)
!data.fetch(:suites, []).find { |suite| suite[:name] == suite_name }
end

private
Expand Down Expand Up @@ -448,7 +462,7 @@ def merged_data_for(key, suite, platform, default_key = :name)
#
# @api private
def move_chef_data_to_provisioner!
data.fetch(:suites, []).each do |suite|
suite_data.each do |suite|
move_chef_data_to_provisioner_at!(suite, :attributes)
move_chef_data_to_provisioner_at!(suite, :run_list)
end
Expand All @@ -473,6 +487,7 @@ def move_chef_data_to_provisioner_at!(root, key)
pdata = { :name => pdata } if pdata.is_a?(String)
if !root.fetch(key, nil).nil?
root[:provisioner] = pdata.rmerge(key => root.delete(key))
root[:provisioner]
end
end
end
Expand Down Expand Up @@ -721,7 +736,7 @@ def set_kitchen_config_at!(root, key)
# Hash if not found
# @api private
def suite_data_for(name)
data.fetch(:suites, Hash.new).find(-> { Hash.new }) do |suite|
suite_data.find(-> { Hash.new }) do |suite|
suite.fetch(:name, nil) == name
end
end
Expand Down
35 changes: 23 additions & 12 deletions lib/kitchen/driver/ssh_base.rb
Expand Up @@ -38,19 +38,16 @@ def create(state) # rubocop:disable Lint/UnusedMethodArgument

# (see Base#converge)
def converge(state)
provisioner = instance.provisioner
provisioner.create_sandbox
sandbox_dirs = Dir.glob("#{provisioner.sandbox_path}/*")

Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
run_remote(provisioner.install_command, conn)
run_remote(provisioner.init_command, conn)
transfer_path(sandbox_dirs, provisioner[:root_path], conn)
run_remote(provisioner.prepare_command, conn)
run_remote(provisioner.run_command, conn)
# allow multiple provisioning runs
step = 1
instance.provisioners.each do |provisioner|
begin
run_step(state, provisioner, step)
ensure
provisioner && provisioner.cleanup_sandbox
end
step += 1
end
ensure
provisioner && provisioner.cleanup_sandbox
end

# (see Base#setup)
Expand Down Expand Up @@ -103,6 +100,20 @@ def ssh(ssh_args, command)

private

def run_step(state, provisioner, step)
info("Running Step #{step}")
provisioner.create_sandbox
sandbox_dirs = Dir.glob("#{provisioner.sandbox_path}/*")
Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
conn.wait
run_remote(provisioner.install_command, conn)
run_remote(provisioner.init_command, conn)
transfer_path(sandbox_dirs, provisioner[:root_path], conn)
run_remote(provisioner.prepare_command, conn)
run_remote(provisioner.run_command, conn)
end
end

# Builds arguments for constructing a `Kitchen::SSH` instance.
#
# @param state [Hash] state hash
Expand Down
30 changes: 20 additions & 10 deletions lib/kitchen/instance.rb
Expand Up @@ -62,7 +62,7 @@ def name_for(suite, platform)
# @return [Provisioner::Base] provisioner object which will the setup
# and invocation instructions for configuration management and other
# automation tools
attr_reader :provisioner
attr_reader :provisioners

# @return [Busser] busser object for instance to manage the busser
# installation on this instance
Expand All @@ -77,7 +77,7 @@ def name_for(suite, platform)
# @option options [Suite] :suite the suite (**Required**)
# @option options [Platform] :platform the platform (**Required**)
# @option options [Driver::Base] :driver the driver (**Required**)
# @option options [Provisioner::Base] :provisioner the provisioner
# @option options Array[Provisioner::Base] :provisioner the provisioner
# (**Required**)
# @option options [Busser] :busser the busser logger (**Required**)
# @option options [Logger] :logger the instance logger
Expand All @@ -92,13 +92,13 @@ def initialize(options = {})
@platform = options.fetch(:platform)
@name = self.class.name_for(@suite, @platform)
@driver = options.fetch(:driver)
@provisioner = options.fetch(:provisioner)
@provisioners = options.fetch(:provisioners)
@busser = options.fetch(:busser)
@logger = options.fetch(:logger) { Kitchen.logger }
@state_file = options.fetch(:state_file)

setup_driver
setup_provisioner
setup_provisioners
end

# Returns a displayable representation of the instance.
Expand Down Expand Up @@ -221,10 +221,18 @@ def remote_exec(command)
# @return [Hash] a diagnostic hash
def diagnose
result = Hash.new
[:state_file, :driver, :provisioner, :busser].each do |sym|
[:state_file, :driver, :busser].each do |sym|
obj = send(sym)
result[sym] = obj.respond_to?(:diagnose) ? obj.diagnose : :unknown
end
result[:provisioners] = provisioners.inject([]) do |rslts, obj|
if obj.respond_to?(:diagnose)
rslts << obj.send(:diagnose)
else
rslts << :unknown
end
rslts
end
result
end

Expand All @@ -250,7 +258,7 @@ def last_action
# @api private
def validate_options(options)
[
:suite, :platform, :driver, :provisioner, :busser, :state_file
:suite, :platform, :driver, :provisioners, :busser, :state_file
].each do |k|
next if options.key?(k)

Expand All @@ -273,12 +281,14 @@ def setup_driver
end
end

# Perform any final configuration or preparation needed for the provisioner
# object carry out its duties.
# Perform any final configuration or preparation needed for the provisioners
# objects to carry out their duties.
#
# @api private
def setup_provisioner
@provisioner.finalize_config!(self)
def setup_provisioners
@provisioners.each do |provisioner|
provisioner.finalize_config!(self)
end
end

# Perform all actions in order from last state to desired state.
Expand Down
17 changes: 15 additions & 2 deletions spec/kitchen/config_spec.rb
Expand Up @@ -264,6 +264,7 @@ def read
Kitchen::DataMunger.stubs(:new).returns(munger)
config.stubs(:platforms).returns(platforms)
config.stubs(:suites).returns(suites)
munger.stubs(:steps?).returns(false)
end

it "constructs a Busser object" do
Expand All @@ -284,7 +285,7 @@ def read
config.instances
end

it "constructs a Provisioner object" do
it "constructs single Provisioner object" do
munger.expects(:provisioner_data_for).with("tiny", "unax").
returns(:name => "provey", :datum => "lots")
Kitchen::Provisioner.unstub(:for_plugin)
Expand All @@ -294,6 +295,18 @@ def read
config.instances
end

it "constructs Provisioner objects" do
munger.stubs(:steps?).returns(true)
config.stubs(:each_step).yields(stub(:name => "tiny_step_1"))
munger.expects(:provisioner_data_for).with("tiny_step_1", "unax").
returns(:name => "provey", :datum => "lots")
Kitchen::Provisioner.unstub(:for_plugin)
Kitchen::Provisioner.expects(:for_plugin).
with("provey", :name => "provey", :datum => "lots")

config.instances
end

it "constructs a Logger object" do
Kitchen::Logger.unstub(:new)
Kitchen::Logger.expects(:new).with(
Expand Down Expand Up @@ -322,7 +335,7 @@ def read
:logger => "logger",
:suite => suites.first,
:platform => platforms.first,
:provisioner => "provisioner",
:provisioners => ["provisioner"],
:state_file => "state_file"
)
config.instances
Expand Down
4 changes: 2 additions & 2 deletions spec/kitchen/driver/ssh_base_spec.rb
Expand Up @@ -52,7 +52,7 @@
:name => "coolbeans",
:logger => logger,
:busser => busser,
:provisioner => provisioner,
:provisioners => [provisioner],
:to_str => "instance"
)
end
Expand Down Expand Up @@ -286,7 +286,7 @@ def self.constructs_an_ssh_object
describe "#converge" do

let(:cmd) { driver.converge(state) }
let(:connection) { stub(:exec => true) }
let(:connection) { stub(:exec => true, :wait => true) }

before do
state[:hostname] = "fizzy"
Expand Down
34 changes: 20 additions & 14 deletions spec/kitchen/instance_spec.rb
Expand Up @@ -93,13 +93,13 @@ def destroy(state)
let(:logger_io) { StringIO.new }
let(:logger) { Kitchen::Logger.new(:logdev => logger_io) }
let(:instance) { Kitchen::Instance.new(opts) }
let(:provisioner) { Kitchen::Provisioner::Dummy.new({}) }
let(:provisioners) { [Kitchen::Provisioner::Dummy.new({})] }
let(:state_file) { DummyStateFile.new }
let(:busser) { Kitchen::Busser.new(suite.name, {}) }

let(:opts) do
{ :suite => suite, :platform => platform, :driver => driver,
:provisioner => provisioner, :busser => busser,
:provisioners => provisioners, :busser => busser,
:logger => logger, :state_file => state_file }
end

Expand Down Expand Up @@ -217,20 +217,22 @@ def platform(name = "platform")
end
end

describe "#provisioner" do
describe "#provisioners" do

it "returns its provisioner" do
instance.provisioner.must_equal provisioner
it "returns its provisioners" do
instance.provisioners.must_equal provisioners
end

it "raises an ArgumentError if missing" do
opts.delete(:provisioner)
opts.delete(:provisioners)
proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
end

it "sets Provisioner#instance to itself" do
# it's mind-bottling
instance.provisioner.instance.must_equal instance
instance.provisioners.each do |provisioner|
provisioner.instance.must_equal instance
end
end
end

Expand Down Expand Up @@ -297,18 +299,22 @@ def platform(name = "platform")
instance.diagnose[:state_file].must_equal :unknown
end

it "sets :provisioner key to provisioner's diganose info" do
it "sets :provisioners key to provisioner's diganose info" do
provisioner = provisioners.first
provisioner.stubs(:diagnose).returns(:a => "b")
provisioners.stubs(:[]).returns(provisioner)

instance.diagnose[:provisioner].must_equal(:a => "b")
instance.diagnose[:provisioners].first.must_equal(:a => "b")
end

it "sets :provisioner key to :unknown if obj can't respond to #diagnose" do
opts[:provisioner] = Class.new(provisioner.class) {
undef_method :diagnose
}.new
it "sets :provisioners key to :unknown if obj can't respond to #diagnose" do
opts[:provisioners] = [
Class.new(provisioners.first.class) {
undef_method :diagnose
}.new
]

instance.diagnose[:provisioner].must_equal :unknown
instance.diagnose[:provisioners].first.must_equal :unknown
end

it "sets :busser key to busser's diganose info" do
Expand Down