Skip to content

Commit

Permalink
Merge pull request #110 from janost/ssh
Browse files Browse the repository at this point in the history
SSH functionality
  • Loading branch information
askreet committed Jul 11, 2016
2 parents 3a54e86 + 3b12145 commit 6f7a013
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 0 deletions.
6 changes: 6 additions & 0 deletions docs/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,9 @@ Tear down your stack by running the following command:
```shell
bundle exec bin/environment delete
```

SSH into the first instance in your stack by running the following command:

```shell
bundle exec bin/environment ssh
```
2 changes: 2 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ methods:
- post_status
- pre_doctor
- post_doctor
- pre_ssh
- post_ssh

The method will be handed a single argument, which is an instance of the
`Moonshot::Resources` class. This instance gives the plugin access to three
Expand Down
29 changes: 29 additions & 0 deletions docs/user-guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,32 @@ CodeDeploy
✓ CodeDeployRole exists.
✓ Resource 'AutoScalingGroup' exists in the CloudFormation template.
```

## SSH

SSH into the first or specified instance on the stack.

|Description|Long Form|Short Form|Type|Example|Default|
|---|---|---|---|---|---|
|SSH user name|user|l|string|someuser|Environment variable: MOONSHOT_SSH_USER or USER|
|Private key file for SSH|identity-file|i|string|~/.ssh/whatever|Environment variable: MOONSHOT_SSH_KEY_FILE|
|Instance ID|instance|s|string|i-04683a82f2dddcc04|(first)|
|Command to execute|command|c|string|uname -a|open a shell|
|Auto Scaling Group|auto-scaling-group|g|string|ExampleAppAsg||
|Environment Name|name|n|string|moonshot-sample-app|None|
|Interactive Logger|interactive_logger||boolean||true|
|Verbose|verbose|v|boolean||false|

Example:

```shell
./bin/environment ssh --name my-service-staging -i ~/.ssh/whatever -c "cat /etc/redhat-release"
```

Output:

```shell
Opening SSH connection to i-04683a82f2dddcc04 (123.123.123.123)...
CentOS Linux release 7.2.1511 (Core)
Connection to 123.123.123.123 closed.
```
36 changes: 36 additions & 0 deletions lib/moonshot/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ def inherited(base)
base.include(Moonshot::BuildMechanism)
base.include(Moonshot::DeploymentMechanism)
end

def ssh_options!
option :user, default: ENV['MOONSHOT_SSH_USER'] || ENV['USER'], type: :string,
aliases: '-l',
desc: 'Specifies the user to log in as on the remote machine.'
option :identity_file, default: ENV['MOONSHOT_SSH_KEY_FILE'], type: :string,
aliases: '-i',
desc: 'Selects a file from which the identity (private key) for '\
'public key authentication is read.'
option :instance, default: nil, type: :string, aliases: '-s',
desc: 'Connect to specified instance ID instead of the first one.'
option :command, default: nil, type: :string, aliases: '-c',
desc: 'Execute the specified command instead of opening a shell.'
option :auto_scaling_group, default: nil, type: :string, aliases: '-g',
desc: 'The logical ID of the auto scaling group to SSH into. '\
'Mandatory if the stack has multiple ASGs, '\
'ignored if you have only one.'
end
end

def initialize(*args)
Expand Down Expand Up @@ -91,6 +109,12 @@ def controller # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplex
parameter_strategy = options[:parameter_strategy] || self.class.default_parameter_strategy
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_instance = options[:instance]
config.ssh_command = options[:command]
config.ssh_auto_scaling_group_name = options[:auto_scaling_group]
end
rescue => e
raise Thor::Error, e.message
Expand All @@ -106,6 +130,12 @@ def parameter_strategy_factory(value)
raise Thor::Error, "Unknown parameter strategy: #{value}"
end
end

def ssh_command(command)
arguments = ARGV.drop(1)
arguments += ['--command', command]
invoke :ssh, [], arguments
end
end

desc :list, 'List stacks for this application.'
Expand Down Expand Up @@ -180,5 +210,11 @@ def doctor
success = controller.doctor
raise Thor::Error, 'One or more checks failed.' unless success
end

desc :ssh, 'SSH into the first or specified instance on the stack.'
ssh_options!
def ssh
controller.ssh
end
end
end
11 changes: 11 additions & 0 deletions lib/moonshot/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def doctor
success
end

def ssh
run_plugins(:pre_ssh)
stack.ssh
run_plugins(:post_ssh)
end

def stack
@stack ||= Stack.new(stack_name,
app_name: @config.app_name,
Expand All @@ -88,6 +94,11 @@ 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
5 changes: 5 additions & 0 deletions lib/moonshot/controller_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ 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_command
attr_accessor :ssh_auto_scaling_group_name

def initialize
@auto_prefix_stack = true
Expand Down
43 changes: 43 additions & 0 deletions lib/moonshot/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ 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 @@ -176,6 +188,37 @@ 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: 5 additions & 0 deletions lib/moonshot/stack_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ 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
15 changes: 15 additions & 0 deletions spec/moonshot/stack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
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 @@ -139,4 +143,15 @@
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 6f7a013

Please sign in to comment.