Skip to content

Commit

Permalink
Refactor SSH functionality. (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
askreet committed Sep 19, 2016
1 parent d5eb3ba commit 41f48f7
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 77 deletions.
4 changes: 2 additions & 2 deletions lib/moonshot/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ def controller # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplex
config.parameter_strategy = parameter_strategy_factory(parameter_strategy) \
unless parameter_strategy.nil?

config.ssh_user = options[:user]
config.ssh_identity_file = options[:identity_file]
config.ssh_config.ssh_user = options[:user]
config.ssh_config.ssh_identity_file = options[:identity_file]
config.ssh_instance = options[:instance]
config.ssh_command = options[:command]
config.ssh_auto_scaling_group_name = options[:auto_scaling_group]
Expand Down
17 changes: 10 additions & 7 deletions lib/moonshot/controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
require_relative 'ssh_target_selector'
require_relative 'ssh_command_builder'

module Moonshot
# The Controller coordinates and performs all Moonshot actions.
class Controller # rubocop:disable ClassLength
Expand Down Expand Up @@ -82,8 +85,13 @@ def doctor

def ssh
run_plugins(:pre_ssh)
stack.ssh
run_plugins(:post_ssh)
@config.ssh_instance ||= SSHTargetSelector.new(
stack, asg_name: @config.ssh_auto_scaling_group_name).choose!
cb = SSHCommandBuilder.new(@config.ssh_config, @config.ssh_instance)
result = cb.build(@config.ssh_command)

puts "Opening SSH connection to #{@config.ssh_instance} (#{result.ip})..."
exec(result.cmd)
end

def stack
Expand All @@ -94,11 +102,6 @@ def stack
config.parent_stacks = @config.parent_stacks
config.show_all_events = @config.show_all_stack_events
config.parameter_strategy = @config.parameter_strategy
config.ssh_user = @config.ssh_user
config.ssh_identity_file = @config.ssh_identity_file
config.ssh_instance = @config.ssh_instance
config.ssh_command = @config.ssh_command
config.ssh_auto_scaling_group_name = @config.ssh_auto_scaling_group_name
end
end

Expand Down
7 changes: 4 additions & 3 deletions lib/moonshot/controller_config.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require_relative 'default_strategy'
require_relative 'ssh_config'

module Moonshot
# Holds configuration for Moonshot::Controller
Expand All @@ -15,11 +16,10 @@ class ControllerConfig
attr_accessor :plugins
attr_accessor :show_all_stack_events
attr_accessor :parameter_strategy
attr_accessor :ssh_instance
attr_accessor :ssh_identity_file
attr_accessor :ssh_user
attr_accessor :ssh_config
attr_accessor :ssh_command
attr_accessor :ssh_auto_scaling_group_name
attr_accessor :ssh_instance

def initialize
@auto_prefix_stack = true
Expand All @@ -29,6 +29,7 @@ def initialize
@plugins = []
@show_all_stack_events = false
@parameter_strategy = Moonshot::ParameterStrategy::DefaultStrategy.new
@ssh_config = SSHConfig.new
end
end
end
32 changes: 32 additions & 0 deletions lib/moonshot/ssh_command_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'shellwords'

module Moonshot
# Create an ssh command from configuration.
class SSHCommandBuilder
Result = Struct.new(:cmd, :ip)

def initialize(ssh_config, instance_id)
@config = ssh_config
@instance_id = instance_id
end

def build(command = nil)
cmd = ['ssh', '-t']
cmd << "-i #{@config.ssh_identity_file}" if @config.ssh_identity_file
cmd << "-l #{@config.ssh_user}" if @config.ssh_user
cmd << instance_ip
cmd << command if command
Result.new(cmd.join(' '), instance_ip)
end

private

def instance_ip
@instance_ip ||= Aws::EC2::Client.new
.describe_instances(instance_ids: [@instance_id])
.reservations.first.instances.first.public_ip_address
rescue
raise "Failed to determine public IP address for instance #{@instance_id}!"
end
end
end
6 changes: 6 additions & 0 deletions lib/moonshot/ssh_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Moonshot
class SSHConfig # rubocop:disable Documentation
attr_accessor :ssh_identity_file
attr_accessor :ssh_user
end
end
30 changes: 30 additions & 0 deletions lib/moonshot/ssh_target_selector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Moonshot
# Choose a publically accessible instance to run commands on, given a Moonshot::Stack.
class SSHTargetSelector
def initialize(stack, asg_name: nil)
@asg_name = asg_name
@stack = stack
end

def choose! # rubocop:disable AbcSize
groups = @stack.resources_of_type('AWS::AutoScaling::AutoScalingGroup')

asg = if groups.count == 1
groups.first
elsif asgs.count > 1
unless @asg_name
raise 'Multiple Auto Scaling Groups found in the stack. Please specify which '\
'one to SSH into using the --auto-scaling-group (-g) option.'
end
groups.detect { |x| x.logical_resource_id == @config.ssh_auto_scaling_group_name }
end
raise 'Failed to find the Auto Scaling Group.' unless asg

Aws::AutoScaling::Client.new.describe_auto_scaling_groups(
auto_scaling_group_names: [asg.physical_resource_id]
).auto_scaling_groups.first.instances.map(&:instance_id).first
rescue
raise 'Failed to find instances in the Auto Scaling Group!'
end
end
end
43 changes: 0 additions & 43 deletions lib/moonshot/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,6 @@ def status
end
end

def ssh
box_id = @config.ssh_instance || instances.sort.first
box_ip = instance_ip(box_id)
cmd = ['ssh', '-t']
cmd << "-i #{@config.ssh_identity_file}" if @config.ssh_identity_file
cmd << "-l #{@config.ssh_user}" if @config.ssh_user
cmd << box_ip
cmd << @config.ssh_command if @config.ssh_command
puts "Opening SSH connection to #{box_id} (#{box_ip})..."
exec(cmd.join(' '))
end

def parameters
get_stack(@name)
.parameters
Expand Down Expand Up @@ -188,37 +176,6 @@ def add_parameter_overrides(hash)

private

def asgs
resources_of_type('AWS::AutoScaling::AutoScalingGroup')
end

def instance_ip(instance_id)
Aws::EC2::Client.new.describe_instances(instance_ids: [instance_id])
.reservations.first.instances.first.public_ip_address
rescue
raise "Failed to determine public IP address for instance #{instance_id}."
end

def instances # rubocop:disable Metrics/AbcSize
groups = asgs
asg = if groups.count == 1
groups.first
elsif asgs.count > 1
unless @config.ssh_auto_scaling_group_name
raise 'Multiple Auto Scaling Groups found in the stack. Please specify which '\
'one to SSH into using the --auto-scaling-group (-g) option.'
end
groups.detect { |x| x.logical_resource_id == @config.ssh_auto_scaling_group_name }
end
raise 'Failed to find the Auto Scaling Group.' unless asg

Aws::AutoScaling::Client.new.describe_auto_scaling_groups(
auto_scaling_group_names: [asg.physical_resource_id]
).auto_scaling_groups.first.instances.map(&:instance_id)
rescue
raise 'Failed to find instances in the Auto Scaling Group.'
end

def stack_name
"CloudFormation Stack #{@name.blue}"
end
Expand Down
5 changes: 0 additions & 5 deletions lib/moonshot/stack_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ class StackConfig
attr_accessor :parent_stacks
attr_accessor :show_all_events
attr_accessor :parameter_strategy
attr_accessor :ssh_instance
attr_accessor :ssh_identity_file
attr_accessor :ssh_user
attr_accessor :ssh_command
attr_accessor :ssh_auto_scaling_group_name

def initialize
@parent_stacks = []
Expand Down
6 changes: 4 additions & 2 deletions spec/moonshot/build_mechanism/travis_deploy_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# coding: utf-8
module Moonshot # rubocop:disable Metrics/ModuleLength
describe BuildMechanism::TravisDeploy do
let(:resources) do
Expand Down Expand Up @@ -125,10 +126,11 @@ module Moonshot # rubocop:disable Metrics/ModuleLength

it 'should fail if the job does not complete within the time limit' do
expect(resources.ilog).to receive(:start_threaded).and_yield(step)
expect(step).to receive(:continue)
expect(step).to receive(:continue).at_least(9)
expect(step).to receive(:failure)
expect(subject).to receive(:sleep).at_least(:once) { sleep 0.1 }

subject.instance_variable_set(:@timeout, 5)
subject.instance_variable_set(:@timeout, 1)
subject.send(:wait_for_job, job_number)
end
end
Expand Down
47 changes: 47 additions & 0 deletions spec/moonshot/ssh_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
describe 'Moonshot SSH features' do
subject do
Moonshot::Controller.new do |c|
c.app_name = 'MyApp'
c.environment_name = 'prod'
c.ssh_config.ssh_user = 'joeuser'
c.ssh_config.ssh_identity_file = '/Users/joeuser/.ssh/thegoods.key'
c.ssh_command = 'cat /etc/passwd'
end
end

describe 'Moonshot::Controller#ssh' do
context 'normally' do
it 'should execute an ssh command with proper parameters' do
ts = instance_double(Moonshot::SSHTargetSelector)
expect(Moonshot::SSHTargetSelector).to receive(:new).and_return(ts)
expect(ts).to receive(:choose!).and_return('i-04683a82f2dddcc04')

expect_any_instance_of(Moonshot::SSHCommandBuilder).to receive(:instance_ip).exactly(2)
.and_return('123.123.123.123')
expect(STDOUT).to receive(:puts)
.with('Opening SSH connection to i-04683a82f2dddcc04 (123.123.123.123)...')
expect(subject).to receive(:exec)
.with('ssh -t -i /Users/joeuser/.ssh/thegoods.key -l joeuser 123.123.123.123 cat /etc/passwd') # rubocop:disable LineLength
subject.ssh
end
end

context 'when an instance id is given' do
subject do
c = super()
c.config.ssh_instance = 'i-012012012012012'
c
end

it 'should execute an ssh command with proper parameters' do
expect_any_instance_of(Moonshot::SSHCommandBuilder).to receive(:instance_ip).exactly(2)
.and_return('123.123.123.123')
expect(STDOUT).to receive(:puts)
.with('Opening SSH connection to i-012012012012012 (123.123.123.123)...')
expect(subject).to receive(:exec)
.with('ssh -t -i /Users/joeuser/.ssh/thegoods.key -l joeuser 123.123.123.123 cat /etc/passwd') # rubocop:disable LineLength
subject.ssh
end
end
end
end
15 changes: 0 additions & 15 deletions spec/moonshot/stack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
subject do
described_class.new('test', app_name: 'rspec-app', log: log, ilog: ilog) do |c|
c.parent_stacks = parent_stacks
c.ssh_instance = 'i-04683a82f2dddcc04'
c.ssh_identity_file = '~/.ssh/whatever'
c.ssh_user = 'username'
c.ssh_command = 'uname -a'
end
end

Expand Down Expand Up @@ -143,15 +139,4 @@
expect(subject.parameters_file).to eq(path)
end
end

describe '#ssh' do
it 'should execute an ssh command with proper parameters' do
allow(subject).to receive(:instance_ip).and_return('123.123.123.123')
expect(STDOUT).to receive(:puts).with('Opening SSH connection to i-04683a82f2dddcc04 '\
'(123.123.123.123)...')
expect(subject).to receive('exec').with('ssh -t -i ~/.ssh/whatever -l username '\
'123.123.123.123 uname -a')
subject.ssh
end
end
end

0 comments on commit 41f48f7

Please sign in to comment.