Skip to content

Commit

Permalink
[Potentially Breaking] Transports responsible for login action.
Browse files Browse the repository at this point in the history
Potentially Breaking Notes
==========================

This is potentially breaking to Driver authors if all of the following
are true:

* Your Driver currently inherits from `Kitchen::Driver::Base`
* Your Driver implements/overrides the `#login_command` method

Put another way, your Driver may have issues if it looks like the
following:

    module Kitchen
      module Driver
        class MyDriver < Kitchen::Driver::Base
          def login_command(state)
            # custom converge work
          end
        end
      end
    end

For the vast majority (well over 90%) of OSS Drivers in the wild,
current behavio is maintained as they all inherit from
`Kitchen::Driver::SSHBase`. This class has been cemented to preserve its
current behavior, and Test Kitchen will invoke the `#login_command`
method for these Drivers.

**Note:** upgrade path and instructions for Driver authors will be
written, but backwards compatibility is being taken seriously.

A future deprecation process may remove the `SSHBase` backwards
compatibility, but not without plent of lead time and warning. Due to
the constraints of SemVer, by definition, this wouldn't occur before a
2.x codebase release.
  • Loading branch information
fnichol committed Mar 11, 2015
1 parent cd5d784 commit ae360a1
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 23 deletions.
2 changes: 1 addition & 1 deletion features/kitchen_login_command.feature
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Feature: Logging into a Kitchen instance
When I run `kitchen login default-flebian`
Then the output should contain:
"""
Remote login is not supported in this driver.
Remote login not supported in Kitchen::Transport::Dummy::Connection.
"""
And the exit status should not be 0

Expand Down
10 changes: 0 additions & 10 deletions lib/kitchen/driver/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,6 @@ def verify(state) # rubocop:disable Lint/UnusedMethodArgument
def destroy(state) # rubocop:disable Lint/UnusedMethodArgument
end

# Returns the shell command that will log into an instance.
#
# @param state [Hash] mutable instance and driver state
# @return [LoginCommand] an object containing the array of command line
# tokens and exec options to be used in a fork/exec
# @raise [ActionFailed] if the action could not be completed
def login_command(state) # rubocop:disable Lint/UnusedMethodArgument
raise ActionFailed, "Remote login is not supported in this driver."
end

class << self
# @return [Array<Symbol>] an array of action method names that cannot
# be run concurrently and must be run in serial via a shared mutex
Expand Down
30 changes: 24 additions & 6 deletions lib/kitchen/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,23 +195,27 @@ def test(destroy_mode = :passing)
end

# Logs in to this instance by invoking a system command, provided by the
# instance's driver. This could be an SSH command, telnet, or serial
# instance's transport. This could be an SSH command, telnet, or serial
# console session.
#
# **Note** This method calls exec and will not return.
#
# @see Driver::LoginCommand
# @see Driver::Base#login_command
# @see Kitchen::LoginCommand
# @see Transport::Base::Connection#login_command
def login
state = state_file.read
if state[:last_action].nil?
raise UserError, "Instance #{to_str} has not yet been created"
end

lc = driver.login_command(state)
lc = if legacy_ssh_base_driver?
legacy_ssh_base_login(state)
else
transport.connection(state).login_command
end

debug(%{Login command: #{lc.command} #{lc.arguments.join(" ")} " \
"(Options: #{lc.options})})
debug(%{Login command: #{lc.command} #{lc.arguments.join(" ")} } \
"(Options: #{lc.options})")
Kernel.exec(*lc.exec_args)
end

Expand Down Expand Up @@ -495,6 +499,20 @@ def legacy_ssh_base_converge(state)
driver.converge(state)
end

# Invokes `Driver#login_command` on a legacy Driver, which inherits from
# `Kitchen::Driver::SSHBase`.
#
# @param state [Hash] mutable instance state
# @deprecated When legacy Driver::SSHBase support is removed, the
# `#login_command` method will no longer be called on the Driver.
# @api private
def legacy_ssh_base_login(state)
warn("Running legacy login for '#{driver.name}' Driver")
# TODO: Document upgrade path and provide link
# warn("Driver authors: please read http://example.com for more details.")
driver.login_command(state)
end

# @return [TrueClass,FalseClass] whether or not the Driver inherits from
# `Kitchen::Driver::SSHBase`
# @deprecated When legacy Driver::SSHBase support is removed, the
Expand Down
4 changes: 0 additions & 4 deletions spec/kitchen/driver/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,6 @@ class Slow < Base
end
end

it "has a login command that raises ActionFailed by default" do
proc { driver.login_command(Hash.new) }.must_raise Kitchen::ActionFailed
end

describe ".no_parallel_for" do

it "registers no serial actions when none are declared" do
Expand Down
16 changes: 14 additions & 2 deletions spec/kitchen/instance_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,12 @@ def platform(name = "platform")
instance.to_str.must_equal "<suite-platform>"
end

it "#login executes the driver's login_command" do
it "#login executes the transport's login_command" do
conn = stub("connection")
state_file.write(:last_action => "create")
driver.stubs(:login_command).with(:last_action => "create").
transport.stubs(:connection).with(:last_action => "create").
returns(conn)
conn.stubs(:login_command).
returns(Kitchen::LoginCommand.new("echo", ["hello"], :purple => true))
Kernel.expects(:exec).with("echo", "hello", :purple => true)

Expand Down Expand Up @@ -1163,6 +1166,15 @@ def platform(name = "platform")
end
end
end

it "#login executes the driver's login_command" do
state_file.write(:last_action => "create")
driver.stubs(:login_command).with(:last_action => "create").
returns(Kitchen::LoginCommand.new("echo", ["hello"], :purple => true))
Kernel.expects(:exec).with("echo", "hello", :purple => true)

instance.login
end
end
end

Expand Down

0 comments on commit ae360a1

Please sign in to comment.