sonic-screwdriver 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/bin/commit_docs.sh +26 -0
- data/.circleci/config.yml +70 -0
- data/.gitignore +1 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +13 -2
- data/Gemfile +3 -3
- data/Gemfile.lock +43 -14
- data/Guardfile +17 -10
- data/LICENSE.txt +2 -2
- data/README.md +10 -10
- data/Rakefile +9 -2
- data/docs/_config.yml +3 -0
- data/docs/_docs/help.md +1 -1
- data/docs/_docs/install-bastion.md +5 -15
- data/docs/_docs/install.md +3 -3
- data/docs/_docs/settings.md +40 -56
- data/docs/_docs/tutorial-ecs-exec.md +16 -20
- data/docs/_docs/tutorial-ecs-sh.md +73 -0
- data/docs/_docs/tutorial-execute.md +93 -17
- data/docs/_docs/tutorial-ssh.md +13 -18
- data/docs/_docs/why-ec2-run-command.md +1 -1
- data/docs/_includes/commands.html +5 -5
- data/docs/_includes/content.html +5 -0
- data/docs/_includes/css/main.css +15 -9
- data/docs/_includes/css/sonic.css +7 -5
- data/docs/_includes/example.html +4 -4
- data/docs/_includes/reference.md +1 -0
- data/docs/_includes/subnav.html +2 -1
- data/docs/_reference/sonic-completion.md +44 -0
- data/docs/_reference/sonic-completion_script.md +25 -0
- data/docs/_reference/sonic-ecs-exec.md +30 -0
- data/docs/_reference/sonic-ecs-help.md +21 -0
- data/docs/_reference/sonic-ecs-sh.md +35 -0
- data/docs/_reference/sonic-ecs.md +25 -0
- data/docs/_reference/sonic-execute.md +84 -0
- data/docs/_reference/sonic-list.md +40 -0
- data/docs/_reference/sonic-ssh.md +86 -0
- data/docs/_reference/sonic-version.md +21 -0
- data/docs/img/tutorials/ec2-console-run-command.png +0 -0
- data/docs/quick-start.md +9 -10
- data/docs/reference.md +12 -0
- data/{bin → exe}/sonic +3 -3
- data/lib/bash_scripts/docker-exec.sh +1 -0
- data/lib/bash_scripts/docker-run.sh +8 -1
- data/lib/sonic.rb +10 -2
- data/lib/sonic/{aws_services.rb → aws_service.rb} +6 -1
- data/lib/sonic/base_command.rb +82 -0
- data/lib/sonic/cli.rb +37 -27
- data/lib/sonic/command.rb +8 -22
- data/lib/sonic/completer.rb +161 -0
- data/lib/sonic/completer/script.rb +6 -0
- data/lib/sonic/completer/script.sh +10 -0
- data/lib/sonic/core.rb +15 -0
- data/lib/sonic/default/settings.yml +6 -16
- data/lib/sonic/docker.rb +29 -1
- data/lib/sonic/ecs.rb +22 -0
- data/lib/sonic/execute.rb +153 -18
- data/lib/sonic/help.rb +9 -0
- data/lib/sonic/help/command/send.md +10 -0
- data/lib/sonic/help/completion.md +22 -0
- data/lib/sonic/help/completion_script.md +3 -0
- data/lib/sonic/help/ecs/exec.md +8 -0
- data/lib/sonic/help/ecs/sh.md +13 -0
- data/lib/sonic/help/execute.md +60 -0
- data/lib/sonic/help/list.md +17 -0
- data/lib/sonic/help/ssh.md +60 -0
- data/lib/sonic/list.rb +4 -1
- data/lib/sonic/setting.rb +47 -0
- data/lib/sonic/ssh.rb +41 -20
- data/lib/sonic/ssh/identifier_detector.rb +6 -2
- data/lib/sonic/version.rb +1 -1
- data/sonic.gemspec +14 -9
- data/spec/lib/cli_spec.rb +5 -10
- data/spec/lib/sonic/execute_spec.rb +0 -1
- data/spec/spec_helper.rb +18 -10
- metadata +115 -16
- data/docs/_docs/tutorial-ecs-run.md +0 -100
- data/lib/sonic/cli/help.rb +0 -152
- data/lib/sonic/settings.rb +0 -115
data/lib/sonic/cli.rb
CHANGED
@@ -1,49 +1,59 @@
|
|
1
1
|
require 'thor'
|
2
|
-
require 'sonic/cli/help'
|
3
2
|
|
4
3
|
module Sonic
|
5
|
-
class CLI <
|
4
|
+
class CLI < BaseCommand
|
5
|
+
desc "ecs SUBCOMMAND", "ecs subcommands"
|
6
|
+
long_desc Help.text(:ecs)
|
7
|
+
subcommand "ecs", Ecs
|
8
|
+
|
9
|
+
# desc "command SUBCOMMAND", "command subcommands"
|
10
|
+
# long_desc Help.text(:command)
|
11
|
+
# subcommand "command", Command
|
12
|
+
|
6
13
|
class_option :verbose, type: :boolean
|
7
14
|
class_option :noop, type: :boolean
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
method_option :retry, :aliases => '-r', :type => :boolean, :desc => "keep retrying the server login until successful. Useful when on newly launched instances."
|
15
|
+
|
16
|
+
desc "ssh [IDENTIFER]", "Ssh into a instance using identifier. identifer can be several things: instance id, ec2 tag, ECS service name, etc."
|
17
|
+
long_desc Help.text(:ssh)
|
18
|
+
option :keys, :aliases => '-i', :desc => "comma separated list of ssh private key paths"
|
19
|
+
option :retry, :aliases => '-r', :type => :boolean, :desc => "keep retrying the server login until successful. Useful when on newly launched instances."
|
20
|
+
option :bastion, desc: "Bastion jump host to use. Defaults to no bastion server."
|
21
|
+
option :cluster, desc: "ECS Cluster to use. Default cluster is default"
|
16
22
|
def ssh(identifier, *command)
|
17
23
|
Ssh.new(identifier, options.merge(command: command)).run
|
18
24
|
end
|
19
25
|
|
20
|
-
desc "
|
21
|
-
long_desc Help.
|
22
|
-
def ecs_exec(service, *command)
|
23
|
-
Docker.new(service, options.merge(command: command)).exec
|
24
|
-
end
|
25
|
-
|
26
|
-
# Cannot name the command run because that is a reserved Thor keyword :(
|
27
|
-
desc "ecs-run [ECS_SERVICE]", "docker run with the service on a container instance"
|
28
|
-
long_desc Help.ecs_run
|
29
|
-
def ecs_run(service, *command)
|
30
|
-
Docker.new(service, options.merge(command: command)).run
|
31
|
-
end
|
32
|
-
|
33
|
-
desc "execute [FILTER] [COMMAND]", "runs command across fleet of servers via AWS Run Command"
|
34
|
-
long_desc Help.execute
|
26
|
+
desc "execute [FILTER] [COMMAND]", "Runs command across fleet of servers via AWS Run Command."
|
27
|
+
long_desc Help.text("execute")
|
35
28
|
option :zero_warn, type: :boolean, default: true, desc: "Warns user when no instances found"
|
36
29
|
# filter - Filter ec2 instances by tag name or instance_ids separated by commas
|
37
30
|
def execute(filter, *command)
|
38
31
|
Execute.new(command, options.merge(filter: filter)).execute
|
39
32
|
end
|
40
33
|
|
41
|
-
desc "list [FILTER]", "
|
42
|
-
long_desc Help.list
|
34
|
+
desc "list [FILTER]", "Lists ec2 instances."
|
35
|
+
long_desc Help.text(:list)
|
43
36
|
option :header, type: :boolean, desc: "Displays header"
|
44
37
|
# filter - Filter ec2 instances by tag name or instance_ids separated by commas
|
45
38
|
def list(filter)
|
46
39
|
List.new(options.merge(filter: filter)).run
|
47
40
|
end
|
41
|
+
|
42
|
+
desc "completion *PARAMS", "Prints words for auto-completion."
|
43
|
+
long_desc Help.text("completion")
|
44
|
+
def completion(*params)
|
45
|
+
Completer.new(CLI, *params).run
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "completion_script", "Generates a script that can be eval to setup auto-completion."
|
49
|
+
long_desc Help.text("completion_script")
|
50
|
+
def completion_script
|
51
|
+
Completer::Script.generate
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "version", "prints version"
|
55
|
+
def version
|
56
|
+
puts VERSION
|
57
|
+
end
|
48
58
|
end
|
49
59
|
end
|
data/lib/sonic/command.rb
CHANGED
@@ -1,25 +1,11 @@
|
|
1
|
-
require 'thor'
|
2
|
-
|
3
1
|
module Sonic
|
4
|
-
class Command <
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# sonic command -D
|
12
|
-
#
|
13
|
-
# as well thor's normal way:
|
14
|
-
#
|
15
|
-
# sonic help command
|
16
|
-
help_flags = Thor::HELP_MAPPINGS + ["help"]
|
17
|
-
if args.length > 1 && !(args & help_flags).empty?
|
18
|
-
args -= help_flags
|
19
|
-
args.insert(-2, "help")
|
20
|
-
end
|
21
|
-
super
|
22
|
-
end
|
2
|
+
class Command < BaseCommand
|
3
|
+
desc "send [FILTER] [COMMAND]", "runs command across fleet of servers via AWS Run Command"
|
4
|
+
long_desc Help.text("command/send")
|
5
|
+
option :zero_warn, type: :boolean, default: true, desc: "Warns user when no instances found"
|
6
|
+
# filter - Filter ec2 instances by tag name or instance_ids separated by commas
|
7
|
+
def send(filter, *command)
|
8
|
+
Commander.new(command, options.merge(filter: filter)).execute
|
23
9
|
end
|
24
10
|
end
|
25
|
-
end
|
11
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
=begin
|
2
|
+
Code Explanation:
|
3
|
+
|
4
|
+
There are 3 types of things to auto-complete:
|
5
|
+
|
6
|
+
1. command: the command itself
|
7
|
+
2. parameters: command parameters.
|
8
|
+
3. options: command options
|
9
|
+
|
10
|
+
Here's an example:
|
11
|
+
|
12
|
+
mycli hello name --from me
|
13
|
+
|
14
|
+
* command: hello
|
15
|
+
* parameters: name
|
16
|
+
* option: --from
|
17
|
+
|
18
|
+
When command parameters are done processing, the remaining completion words will be options. We can tell that the command params are completed based on the method arity.
|
19
|
+
|
20
|
+
## Arity
|
21
|
+
|
22
|
+
For example, say you had a method for a CLI command with the following form:
|
23
|
+
|
24
|
+
ufo scale service count --cluster development
|
25
|
+
|
26
|
+
It's equivalent ruby method:
|
27
|
+
|
28
|
+
scale(service, count) = has an arity of 2
|
29
|
+
|
30
|
+
So typing:
|
31
|
+
|
32
|
+
ufo scale service count [TAB] # there are 3 parameters including the "scale" command according to Thor's CLI processing.
|
33
|
+
|
34
|
+
So the completion should only show options, something like this:
|
35
|
+
|
36
|
+
--noop --verbose --cluster
|
37
|
+
|
38
|
+
## Splat Arguments
|
39
|
+
|
40
|
+
When the ruby method has a splat argument, it's arity is negative. Here are some example methods and their arities.
|
41
|
+
|
42
|
+
ship(service) = 1
|
43
|
+
scale(service, count) = 2
|
44
|
+
ships(*services) = -1
|
45
|
+
foo(example, *rest) = -2
|
46
|
+
|
47
|
+
Fortunately, negative and positive arity values are processed the same way. So we take simply take the absolute value of the arity and process it the same.
|
48
|
+
|
49
|
+
Here are some test cases, hit TAB after typing the command:
|
50
|
+
|
51
|
+
sonic completion
|
52
|
+
sonic completion hello
|
53
|
+
sonic completion hello name
|
54
|
+
sonic completion hello name --
|
55
|
+
sonic completion hello name --noop
|
56
|
+
|
57
|
+
sonic completion
|
58
|
+
sonic completion sub:goodbye
|
59
|
+
sonic completion sub:goodbye name
|
60
|
+
|
61
|
+
## Subcommands and Thor::Group Registered Commands
|
62
|
+
|
63
|
+
Sometimes the commands are not simple thor commands but are subcommands or Thor::Group commands. A good specific example is the ufo tool.
|
64
|
+
|
65
|
+
* regular command: ufo ship
|
66
|
+
* subcommand: ufo docker
|
67
|
+
* Thor::Group command: ufo init
|
68
|
+
|
69
|
+
Auto-completion accounts for each of these type of commands.
|
70
|
+
=end
|
71
|
+
module Sonic
|
72
|
+
class Completer
|
73
|
+
autoload :Script, 'sonic/completer/script'
|
74
|
+
|
75
|
+
def initialize(command_class, *params)
|
76
|
+
@params = params
|
77
|
+
@current_command = @params[0]
|
78
|
+
@command_class = command_class # CLI initiall
|
79
|
+
end
|
80
|
+
|
81
|
+
def run
|
82
|
+
if subcommand?(@current_command)
|
83
|
+
subcommand_class = @command_class.subcommand_classes[@current_command]
|
84
|
+
@params.shift # destructive
|
85
|
+
Completer.new(subcommand_class, *@params).run # recursively use subcommand
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
# full command has been found!
|
90
|
+
unless found?(@current_command)
|
91
|
+
puts all_commands
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
# will only get to here if command aws found (above)
|
96
|
+
arity = @command_class.instance_method(@current_command).arity.abs
|
97
|
+
if @params.size > arity or thor_group_command?
|
98
|
+
puts options_completion
|
99
|
+
else
|
100
|
+
puts params_completion
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def subcommand?(command)
|
105
|
+
@command_class.subcommands.include?(command)
|
106
|
+
end
|
107
|
+
|
108
|
+
# hacky way to detect that command is a registered Thor::Group command
|
109
|
+
def thor_group_command?
|
110
|
+
command_params(raw=true) == [[:rest, :args]]
|
111
|
+
end
|
112
|
+
|
113
|
+
def found?(command)
|
114
|
+
public_methods = @command_class.public_instance_methods(false)
|
115
|
+
command && public_methods.include?(command.to_sym)
|
116
|
+
end
|
117
|
+
|
118
|
+
# all top-level commands
|
119
|
+
def all_commands
|
120
|
+
commands = @command_class.all_commands.reject do |k,v|
|
121
|
+
v.is_a?(Thor::HiddenCommand)
|
122
|
+
end
|
123
|
+
commands.keys
|
124
|
+
end
|
125
|
+
|
126
|
+
def command_params(raw=false)
|
127
|
+
params = @command_class.instance_method(@current_command).parameters
|
128
|
+
# Example:
|
129
|
+
# >> Sub.instance_method(:goodbye).parameters
|
130
|
+
# => [[:req, :name]]
|
131
|
+
# >>
|
132
|
+
raw ? params : params.map!(&:last)
|
133
|
+
end
|
134
|
+
|
135
|
+
def params_completion
|
136
|
+
offset = @params.size - 1
|
137
|
+
offset_params = command_params[offset..-1]
|
138
|
+
command_params[offset..-1].first
|
139
|
+
end
|
140
|
+
|
141
|
+
def options_completion
|
142
|
+
used = ARGV.select { |a| a.include?('--') } # so we can remove used options
|
143
|
+
|
144
|
+
method_options = @command_class.all_commands[@current_command].options.keys
|
145
|
+
class_options = @command_class.class_options.keys
|
146
|
+
|
147
|
+
all_options = method_options + class_options + ['help']
|
148
|
+
|
149
|
+
all_options.map! { |o| "--#{o.to_s.gsub('_','-')}" }
|
150
|
+
filtered_options = all_options - used
|
151
|
+
filtered_options.uniq
|
152
|
+
end
|
153
|
+
|
154
|
+
# Useful for debugging. Using puts messes up completion.
|
155
|
+
def log(msg)
|
156
|
+
File.open("/tmp/complete.log", "a") do |file|
|
157
|
+
file.puts(msg)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/sonic/core.rb
ADDED
@@ -1,16 +1,6 @@
|
|
1
|
-
bastion:
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
service_cluster:
|
8
|
-
default: # defaults to nil
|
9
|
-
# Examples:
|
10
|
-
# hi-web-prod: prod
|
11
|
-
# hi-clock-prod: prod
|
12
|
-
# hi-worker-prod: prod
|
13
|
-
# hi-web-stag: stag
|
14
|
-
# hi-clock-stag: stag
|
15
|
-
# hi-worker-stag: stag
|
16
|
-
user: ec2-user
|
1
|
+
bastion:
|
2
|
+
host_key_check: false
|
3
|
+
user: ec2-user
|
4
|
+
|
5
|
+
ecs_service_cluster_map:
|
6
|
+
default: default
|
data/lib/sonic/docker.rb
CHANGED
@@ -19,6 +19,7 @@ module Sonic
|
|
19
19
|
|
20
20
|
def setup
|
21
21
|
validate!
|
22
|
+
confirm_ssh_access
|
22
23
|
copy_over_container_data
|
23
24
|
end
|
24
25
|
|
@@ -46,10 +47,37 @@ module Sonic
|
|
46
47
|
kernel_exec(*args)
|
47
48
|
end
|
48
49
|
|
50
|
+
def build_host
|
51
|
+
host = @bastion ? bastion_host : ssh_host
|
52
|
+
host = "#{@user}@#{host}" unless host.include?('@')
|
53
|
+
host
|
54
|
+
end
|
55
|
+
|
56
|
+
def confirm_ssh_access
|
57
|
+
host = build_host
|
58
|
+
puts "Checking access to instance #{detector.instance_id}"
|
59
|
+
|
60
|
+
ssh = ["ssh", ssh_options, "-At", host, "uptime", "2>&1"]
|
61
|
+
command = ssh.join(' ')
|
62
|
+
puts "=> #{command}".colorize(:green)
|
63
|
+
output = `#{command}`
|
64
|
+
if output.include?("Permission denied")
|
65
|
+
puts output
|
66
|
+
UI.error("Access to the instance denied. Maybe check your ssh keys.")
|
67
|
+
exit 1
|
68
|
+
elsif output.include?(" up ")
|
69
|
+
puts "Access OK!"
|
70
|
+
else
|
71
|
+
puts output
|
72
|
+
UI.error("There was an error trying to access the instnace.")
|
73
|
+
exit 1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
49
77
|
def copy_over_container_data
|
50
78
|
create_container_data
|
51
79
|
|
52
|
-
host =
|
80
|
+
host = build_host
|
53
81
|
|
54
82
|
# LEVEL 1
|
55
83
|
# Always clean up remote /tmp/sonic in case of previous interrupted run.
|
data/lib/sonic/ecs.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sonic
|
2
|
+
autoload :Docker, 'sonic/docker'
|
3
|
+
|
4
|
+
class Ecs < BaseCommand
|
5
|
+
|
6
|
+
class_option :bastion, desc: "Bastion jump host to use. Defaults to no bastion server."
|
7
|
+
class_option :cluster, desc: "ECS Cluster to use. Default cluster is default"
|
8
|
+
|
9
|
+
desc "exec [ECS_SERVICE]", "docker exec into running docker container associated with the service on a container instance"
|
10
|
+
long_desc Help.text("ecs/exec")
|
11
|
+
def exec(service, *command)
|
12
|
+
Docker.new(service, options.merge(command: command)).exec
|
13
|
+
end
|
14
|
+
|
15
|
+
# Cannot name the command run because that is a reserved Thor keyword :(
|
16
|
+
desc "sh [ECS_SERVICE]", "docker run with the service on a container instance"
|
17
|
+
long_desc Help.text("ecs/sh")
|
18
|
+
def sh(service, *command)
|
19
|
+
Docker.new(service, options.merge(command: command)).run
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/sonic/execute.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'yaml'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
|
1
5
|
module Sonic
|
2
6
|
class Execute
|
3
|
-
include
|
7
|
+
include AwsService
|
4
8
|
|
5
9
|
def initialize(command, options)
|
6
10
|
@command = command
|
@@ -18,37 +22,163 @@ module Sonic
|
|
18
22
|
ssm_options = build_ssm_options
|
19
23
|
if @options[:noop]
|
20
24
|
UI.noop = true
|
21
|
-
command_id = "fake command id"
|
25
|
+
command_id = "fake command id for noop mode"
|
22
26
|
success = true # fake it for specs
|
23
27
|
else
|
24
28
|
instances_count = check_instances
|
25
29
|
return unless instances_count > 0
|
26
30
|
|
27
31
|
success = nil
|
32
|
+
puts "Sending command to SSM with options:"
|
33
|
+
puts YAML.dump(ssm_options.deep_stringify_keys)
|
34
|
+
puts
|
28
35
|
begin
|
29
|
-
resp =
|
36
|
+
resp = send_command(ssm_options)
|
30
37
|
command_id = resp.command.command_id
|
31
38
|
success = true
|
32
39
|
rescue Aws::SSM::Errors::InvalidInstanceId => e
|
33
40
|
ssm_invalid_instance_error_message(e)
|
34
41
|
end
|
35
42
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
43
|
+
|
44
|
+
return unless success
|
45
|
+
|
46
|
+
# IF COMMAND IS ONLY ON A SINGLE INSTANCE THEN WILL DISPLAY A BUNCH OF
|
47
|
+
# INFO ON THE INSTANCE. IF ITS A LOT OF INSTANCES, THEN SHOW A SUMMARY
|
48
|
+
# OF COMMANDS THAT WILL LEAD TO THE OUTPUT OF EACH INSTANCE.
|
49
|
+
UI.say "Command sent to AWS SSM. To check the details of the command:"
|
50
|
+
display_ssm_commands(command_id, ssm_options)
|
51
|
+
puts
|
52
|
+
return if @options[:noop]
|
53
|
+
wait(command_id)
|
54
|
+
display_ssm_output(command_id, ssm_options)
|
55
|
+
display_console_url(command_id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def wait(command_id)
|
59
|
+
ongoing_states = ["Pending", "InProgress", "Delayed"]
|
60
|
+
|
61
|
+
print "Waiting for ssm command to finish..."
|
62
|
+
resp = ssm.list_commands(command_id: command_id)
|
63
|
+
status = resp["commands"].first["status"]
|
64
|
+
while ongoing_states.include?(status)
|
65
|
+
resp = ssm.list_commands(command_id: command_id)
|
66
|
+
status = resp["commands"].first["status"]
|
67
|
+
sleep 1
|
68
|
+
print '.'
|
69
|
+
end
|
70
|
+
puts "\nCommand finished."
|
71
|
+
puts
|
72
|
+
end
|
73
|
+
|
74
|
+
def display_ssm_output(command_id, ssm_options)
|
75
|
+
instance_ids = ssm_options[:instance_ids]
|
76
|
+
return unless instance_ids && instance_ids.size > 0
|
77
|
+
|
78
|
+
instance_id = instance_ids.first
|
79
|
+
if ssm_options[:instance_ids].size > 1
|
80
|
+
puts "Multiple instance targets. Only displaying output for #{instance_id}."
|
81
|
+
else
|
82
|
+
puts "Displaying output for #{instance_id}."
|
83
|
+
end
|
84
|
+
|
85
|
+
resp = ssm.get_command_invocation(
|
86
|
+
command_id: command_id, instance_id: instance_id
|
87
|
+
)
|
88
|
+
puts "Command status: #{colorized_status(resp["status"])}"
|
89
|
+
ssm_output(resp, "output")
|
90
|
+
ssm_output(resp, "error")
|
91
|
+
puts
|
92
|
+
end
|
93
|
+
|
94
|
+
def display_console_url(command_id)
|
95
|
+
region = `aws configure get region`.strip rescue 'us-east-1'
|
96
|
+
console_url = "https://#{region}.console.aws.amazon.com/systems-manager/run-command/#{command_id}"
|
97
|
+
puts "To see the more output details visit:"
|
98
|
+
puts " #{console_url}"
|
99
|
+
puts
|
100
|
+
copy_paste_clipboard(console_url)
|
101
|
+
UI.say "Pro tip: the console url is already in your copy/paste clipboard."
|
102
|
+
end
|
103
|
+
|
104
|
+
def colorized_status(status)
|
105
|
+
case status
|
106
|
+
when "Success"
|
107
|
+
status.colorize(:green)
|
108
|
+
when "Failed"
|
109
|
+
status.colorize(:red)
|
110
|
+
else
|
111
|
+
status
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# type: output or error
|
116
|
+
def ssm_output(resp, type)
|
117
|
+
content_key = "standard_#{type}_content"
|
118
|
+
s3_key = "standard_#{type}_url"
|
119
|
+
|
120
|
+
content = resp[content_key]
|
121
|
+
return if content.empty?
|
122
|
+
|
123
|
+
puts "Command standard #{type}:"
|
124
|
+
# "https://s3.amazonaws.com/lr-infrastructure-prod/ssm/commands/sonic/0a4f4bef-8f63-4235-8b30-ae296477261a/i-0b2e6e187a3f9ada9/awsrunPowerShellScript/0.awsrunPowerShellScript/stderr">
|
125
|
+
if content.include?("--output truncated--") && !resp[s3_key].empty?
|
126
|
+
s3_url = resp[s3_key]
|
127
|
+
info = s3_url.sub('https://s3.amazonaws.com/', '').split('/')
|
128
|
+
bucket = info[0]
|
129
|
+
key = info[1..-1].join('/')
|
130
|
+
resp = s3.get_object(bucket: bucket, key: key)
|
131
|
+
data = resp.body.read
|
132
|
+
puts data
|
133
|
+
|
134
|
+
path = "/tmp/sonic-output.txt"
|
135
|
+
puts "------"
|
136
|
+
puts "Output also written to #{path}"
|
137
|
+
IO.write(path, data)
|
138
|
+
else
|
139
|
+
puts content
|
39
140
|
end
|
141
|
+
|
142
|
+
# puts "#{s3_key}: #{resp[s3_key]}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def send_command(options)
|
146
|
+
retries = 0
|
147
|
+
|
148
|
+
begin
|
149
|
+
resp = ssm.send_command(options)
|
150
|
+
# puts "NOOP FOR NOW"
|
151
|
+
rescue Aws::SSM::Errors::UnsupportedPlatformType
|
152
|
+
retries += 1
|
153
|
+
# toggle AWS-RunShellScript / AWS-RunPowerShellScript
|
154
|
+
options[:document_name] =
|
155
|
+
options[:document_name] == "AWS-RunShellScript" ?
|
156
|
+
"AWS-RunPowerShellScript" : "AWS-RunShellScript"
|
157
|
+
|
158
|
+
puts "#{$!}"
|
159
|
+
puts "Retrying with document_name #{options[:document_name]}"
|
160
|
+
puts "Retries: #{retries}"
|
161
|
+
|
162
|
+
retries <= 1 ? retry : raise
|
163
|
+
end
|
164
|
+
|
165
|
+
resp
|
40
166
|
end
|
41
167
|
|
42
168
|
def build_ssm_options
|
43
169
|
criteria = transform_filter(@filter)
|
44
170
|
command = build_command(@command)
|
45
|
-
criteria.merge(
|
46
|
-
document_name: "AWS-RunShellScript",
|
47
|
-
comment: "sonic #{ARGV.join(' ')}",
|
48
|
-
parameters: {
|
49
|
-
"commands" => command
|
50
|
-
}
|
171
|
+
options = criteria.merge(
|
172
|
+
document_name: "AWS-RunShellScript", # default
|
173
|
+
comment: "sonic #{ARGV.join(' ')}"[0..99], # comment has a max of 100 chars
|
174
|
+
parameters: { "commands" => command }
|
51
175
|
)
|
176
|
+
settings_options = settings["send_command"] || {}
|
177
|
+
options.merge(settings_options.deep_symbolize_keys)
|
178
|
+
end
|
179
|
+
|
180
|
+
def settings
|
181
|
+
@settings ||= Setting.new.data
|
52
182
|
end
|
53
183
|
|
54
184
|
#
|
@@ -125,6 +255,7 @@ You can use the following command to check registered instances to SSM.
|
|
125
255
|
EOS
|
126
256
|
UI.warn(message)
|
127
257
|
copy_paste_clipboard(ssm_describe_command)
|
258
|
+
UI.say "Pro tip: ssm describe-instance-information already in your copy/paste clipboard."
|
128
259
|
end
|
129
260
|
|
130
261
|
def file_path?(command)
|
@@ -136,7 +267,7 @@ You can use the following command to check registered instances to SSM.
|
|
136
267
|
def file_path(command)
|
137
268
|
path = command.first
|
138
269
|
path = path.sub('file://', '')
|
139
|
-
path = "#{
|
270
|
+
path = "#{Sonic.root}/#{path}"
|
140
271
|
path
|
141
272
|
end
|
142
273
|
|
@@ -150,7 +281,7 @@ You can use the following command to check registered instances to SSM.
|
|
150
281
|
instances = List.new(@options).instances
|
151
282
|
if instances.count == 0
|
152
283
|
message = <<-EOL
|
153
|
-
|
284
|
+
Unable to find any instances with filter #{@filter.join(',')}.
|
154
285
|
Are you sure you specify the filter with either a EC2 tag or list instance ids?
|
155
286
|
If you are using ECS identifiers, they are not supported with this command.
|
156
287
|
EOL
|
@@ -170,16 +301,20 @@ EOL
|
|
170
301
|
text =~ /i-.{17}/ || text =~ /i-.{8}/
|
171
302
|
end
|
172
303
|
|
173
|
-
def
|
174
|
-
list_command = "aws ssm list-commands --command-id #{command_id}"
|
304
|
+
def display_ssm_commands(command_id, ssm_options)
|
305
|
+
list_command = " aws ssm list-commands --command-id #{command_id}"
|
175
306
|
UI.say list_command
|
176
|
-
|
307
|
+
|
308
|
+
return unless ssm_options[:instance_ids]
|
309
|
+
ssm_options[:instance_ids].each do |instance_id|
|
310
|
+
get_command = " aws ssm get-command-invocation --command-id #{command_id} --instance-id #{instance_id}"
|
311
|
+
UI.say get_command
|
312
|
+
end
|
177
313
|
end
|
178
314
|
|
179
315
|
def copy_paste_clipboard(command)
|
180
316
|
return unless RUBY_PLATFORM =~ /darwin/
|
181
317
|
system("echo '#{command}' | pbcopy")
|
182
|
-
UI.say "Pro tip: the aws ssm command is already in your copy/paste clipboard."
|
183
318
|
end
|
184
319
|
end
|
185
320
|
end
|