sonic-screwdriver 2.0.0 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +3 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +19 -1
- data/README.md +25 -28
- data/docs/_docs/install.md +0 -10
- data/docs/_docs/next-steps.md +1 -1
- data/docs/_docs/settings.md +4 -2
- data/docs/_docs/tutorial-execute.md +48 -56
- data/docs/_docs/tutorial-ssh.md +2 -1
- data/docs/_includes/footer.html +6 -4
- data/docs/_reference/sonic-execute.md +39 -38
- data/docs/_reference/sonic-list.md +3 -3
- data/docs/bin/web +1 -1
- data/docs/quick-start.md +17 -21
- data/lib/sonic.rb +1 -1
- data/lib/sonic/checks.rb +2 -2
- data/lib/sonic/cli.rb +4 -2
- data/lib/sonic/default/settings.yml +3 -0
- data/lib/sonic/docker.rb +2 -2
- data/lib/sonic/execute.rb +81 -50
- data/lib/sonic/help/execute.md +37 -38
- data/lib/sonic/help/list.md +3 -3
- data/lib/sonic/list.rb +1 -1
- data/lib/sonic/ssh.rb +2 -4
- data/lib/sonic/ssh/identifier_detector.rb +1 -1
- data/lib/sonic/ui.rb +2 -2
- data/lib/sonic/version.rb +1 -1
- data/sonic.gemspec +1 -1
- data/spec/lib/cli_spec.rb +7 -2
- data/spec/lib/sonic/execute_spec.rb +1 -1
- metadata +6 -9
- data/.ruby-version +0 -1
- data/Gemfile.lock +0 -163
@@ -11,16 +11,16 @@ reference: true
|
|
11
11
|
|
12
12
|
Lists ec2 instances.
|
13
13
|
|
14
|
-
|
14
|
+
A filter must be provided. The filter can be a mix of instance ids and ec2 tags. sonic list will auto-detect the what type of filter it is. The filter is optional.
|
15
15
|
|
16
|
-
Examples
|
16
|
+
## Examples
|
17
17
|
|
18
18
|
$ sonic list
|
19
19
|
$ sonic list hi-web-prod
|
20
20
|
$ sonic list hi-web-prod,hi-clock-prod
|
21
21
|
$ sonic list i-09482b1a6e330fbf7
|
22
22
|
|
23
|
-
Example Output
|
23
|
+
## Example Output
|
24
24
|
|
25
25
|
$ sonic list --header i-09482b1a6e330fbf7
|
26
26
|
Instance Id Public IP Private IP Type
|
data/docs/bin/web
CHANGED
data/docs/quick-start.md
CHANGED
@@ -6,35 +6,31 @@ In a hurry? No sweat! Here's a quick overview of how to use sonic.
|
|
6
6
|
|
7
7
|
### Install
|
8
8
|
|
9
|
-
|
10
|
-
gem install sonic-screwdriver
|
11
|
-
```
|
9
|
+
gem install sonic-screwdriver
|
12
10
|
|
13
11
|
### Usage
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
sonic ssh
|
18
|
-
sonic ssh
|
19
|
-
sonic ssh
|
20
|
-
sonic ssh
|
21
|
-
sonic ssh 1ed12abd-645c-4a05-9acf-739b9d790170 --cluster staging # ECS task id
|
13
|
+
# ssh into an instance
|
14
|
+
sonic ssh i-0f7f833131a51ce35
|
15
|
+
sonic ssh demo-web # ec2 tag
|
16
|
+
sonic ssh demo-web --cluster staging # ecs service name
|
17
|
+
sonic ssh 7fbc8c75-4675-4d39-a5a4-0395ff8cd474 --cluster staging # ECS container id
|
18
|
+
sonic ssh 1ed12abd-645c-4a05-9acf-739b9d790170 --cluster staging # ECS task id
|
22
19
|
|
23
|
-
# docker exec to a running ECS docker container
|
24
|
-
sonic ecs exec
|
20
|
+
# docker exec to a running ECS docker container
|
21
|
+
sonic ecs exec demo-web
|
25
22
|
|
26
|
-
# docker run with same environment as the ECS docker running containers
|
27
|
-
sonic ecs sh
|
23
|
+
# docker run with same environment as the ECS docker running containers
|
24
|
+
sonic ecs sh demo-web
|
28
25
|
|
29
|
-
# run command on 1 instance
|
30
|
-
sonic execute i-0f7f833131a51ce35 uptime
|
26
|
+
# run command on 1 instance
|
27
|
+
sonic execute --instance-ids i-0f7f833131a51ce35 uptime
|
31
28
|
|
32
|
-
# run command on all instances tagged with
|
33
|
-
sonic execute
|
29
|
+
# run command on all instances tagged with demo-web and worker
|
30
|
+
sonic execute --tags Name=demo-web,demo-worker uptime
|
34
31
|
|
35
|
-
# list ec2 instances
|
36
|
-
sonic list
|
37
|
-
```
|
32
|
+
# list ec2 instances
|
33
|
+
sonic list demo-web
|
38
34
|
|
39
35
|
Congratulations! You now know the basic sonic screwdriver commands now.
|
40
36
|
|
data/lib/sonic.rb
CHANGED
data/lib/sonic/checks.rb
CHANGED
@@ -3,7 +3,7 @@ module Sonic
|
|
3
3
|
def check_cluster_exists!
|
4
4
|
cluster = ecs.describe_clusters(clusters: [@cluster]).clusters.first
|
5
5
|
unless cluster
|
6
|
-
UI.error "The #{@cluster.green} cluster does not exist. Are you sure you specified the right cluster?"
|
6
|
+
UI.error "The #{@cluster.color(:green)} cluster does not exist. Are you sure you specified the right cluster?"
|
7
7
|
exit 1
|
8
8
|
end
|
9
9
|
end
|
@@ -20,7 +20,7 @@ module Sonic
|
|
20
20
|
|
21
21
|
service = resp.services.first
|
22
22
|
unless service
|
23
|
-
UI.error "The #{@service.green} service does not exist in #{@cluster.green} cluster. Are you sure you specified the right service and cluster?"
|
23
|
+
UI.error "The #{@service.color(:green)} service does not exist in #{@cluster.color(:green)} cluster. Are you sure you specified the right service and cluster?"
|
24
24
|
exit 1
|
25
25
|
end
|
26
26
|
end
|
data/lib/sonic/cli.rb
CHANGED
@@ -27,8 +27,10 @@ module Sonic
|
|
27
27
|
long_desc Help.text("execute")
|
28
28
|
option :zero_warn, type: :boolean, default: true, desc: "Warns user when no instances found"
|
29
29
|
# filter - Filter ec2 instances by tag name or instance_ids separated by commas
|
30
|
-
|
31
|
-
|
30
|
+
option :instance_ids, desc: %Q|Instance ids to execute command on. Format: --instance-ids "i-111,i-222"|
|
31
|
+
option :tags, desc: %Q|Tags used to determine what instances to execute command on. Format: --tags "Key1=v1,v2;Key2=v3"|
|
32
|
+
def execute(*command)
|
33
|
+
Execute.new(command, options).execute
|
32
34
|
end
|
33
35
|
|
34
36
|
desc "list [FILTER]", "Lists ec2 instances."
|
data/lib/sonic/docker.rb
CHANGED
@@ -30,7 +30,7 @@ module Sonic
|
|
30
30
|
|
31
31
|
# command is an Array
|
32
32
|
def execute(*command)
|
33
|
-
UI.say "=> #{command.join(' ')}".
|
33
|
+
UI.say "=> #{command.join(' ')}".color(:green)
|
34
34
|
success = system(*command)
|
35
35
|
unless success
|
36
36
|
UI.error(command.join(' '))
|
@@ -59,7 +59,7 @@ module Sonic
|
|
59
59
|
|
60
60
|
ssh = ["ssh", ssh_options, "-At", host, "uptime", "2>&1"]
|
61
61
|
command = ssh.join(' ')
|
62
|
-
puts "=> #{command}".
|
62
|
+
puts "=> #{command}".color(:green)
|
63
63
|
output = `#{command}`
|
64
64
|
if output.include?("Permission denied")
|
65
65
|
puts output
|
data/lib/sonic/execute.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'colorize'
|
2
1
|
require 'yaml'
|
3
2
|
require 'active_support/core_ext/hash'
|
4
3
|
|
@@ -9,7 +8,8 @@ module Sonic
|
|
9
8
|
def initialize(command, options)
|
10
9
|
@command = command
|
11
10
|
@options = options
|
12
|
-
@
|
11
|
+
@tags = @options[:tags]
|
12
|
+
@instance_ids = @options[:instance_ids]
|
13
13
|
end
|
14
14
|
|
15
15
|
# aws ssm send-command \
|
@@ -19,6 +19,7 @@ module Sonic
|
|
19
19
|
# --parameters '{"commands":["#!/usr/bin/python","print \"Hello world from python\""]}' \
|
20
20
|
# --query "Command.CommandId"
|
21
21
|
def execute
|
22
|
+
check_filter_options!
|
22
23
|
ssm_options = build_ssm_options
|
23
24
|
if @options[:noop]
|
24
25
|
UI.noop = true
|
@@ -34,6 +35,7 @@ module Sonic
|
|
34
35
|
puts
|
35
36
|
begin
|
36
37
|
resp = send_command(ssm_options)
|
38
|
+
|
37
39
|
command_id = resp.command.command_id
|
38
40
|
success = true
|
39
41
|
rescue Aws::SSM::Errors::InvalidInstanceId => e
|
@@ -50,9 +52,31 @@ module Sonic
|
|
50
52
|
display_ssm_commands(command_id, ssm_options)
|
51
53
|
puts
|
52
54
|
return if @options[:noop]
|
53
|
-
wait(command_id)
|
54
|
-
display_ssm_output(command_id
|
55
|
+
status = wait(command_id)
|
56
|
+
instances_found = display_ssm_output(command_id)
|
55
57
|
display_console_url(command_id)
|
58
|
+
|
59
|
+
if status == "Success"
|
60
|
+
puts "Command successful: #{status}".color(:green)
|
61
|
+
exit_status(0)
|
62
|
+
else
|
63
|
+
puts "Command unsuccessful: #{status}".color(:red)
|
64
|
+
exit_status(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def exit_status(code)
|
69
|
+
exit(code) unless cli?
|
70
|
+
|
71
|
+
if code == 0
|
72
|
+
true
|
73
|
+
else
|
74
|
+
raise "Error running command"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def cli?
|
79
|
+
$0.include?('sonic')
|
56
80
|
end
|
57
81
|
|
58
82
|
def wait(command_id)
|
@@ -69,15 +93,21 @@ module Sonic
|
|
69
93
|
end
|
70
94
|
puts "\nCommand finished."
|
71
95
|
puts
|
96
|
+
status
|
72
97
|
end
|
73
98
|
|
74
|
-
def display_ssm_output(command_id
|
75
|
-
|
76
|
-
|
99
|
+
def display_ssm_output(command_id)
|
100
|
+
resp = ssm.list_command_invocations(command_id: command_id)
|
101
|
+
command_invocations = resp.command_invocations
|
102
|
+
command_invocation = command_invocations.first
|
103
|
+
unless command_invocation
|
104
|
+
puts "WARN: No instances found that matches the --tags or --instance-ids option".color(:yellow)
|
105
|
+
return false # instances_found
|
106
|
+
end
|
107
|
+
instance_id = command_invocation.instance_id
|
77
108
|
|
78
|
-
|
79
|
-
|
80
|
-
puts "Multiple instance targets. Only displaying output for #{instance_id}."
|
109
|
+
if command_invocations.size > 1
|
110
|
+
puts "Multiple instance targets. Total targets: #{command_invocations.size}. Only displaying output for #{instance_id}."
|
81
111
|
else
|
82
112
|
puts "Displaying output for #{instance_id}."
|
83
113
|
end
|
@@ -89,6 +119,7 @@ module Sonic
|
|
89
119
|
ssm_output(resp, "output")
|
90
120
|
ssm_output(resp, "error")
|
91
121
|
puts
|
122
|
+
true # instances_found
|
92
123
|
end
|
93
124
|
|
94
125
|
def display_console_url(command_id)
|
@@ -97,16 +128,15 @@ module Sonic
|
|
97
128
|
puts "To see the more output details visit:"
|
98
129
|
puts " #{console_url}"
|
99
130
|
puts
|
100
|
-
copy_paste_clipboard(console_url)
|
101
|
-
UI.say "Pro tip: the console url is already in your copy/paste clipboard."
|
131
|
+
copy_paste_clipboard(console_url, "Pro tip: the console url is already in your copy/paste clipboard.")
|
102
132
|
end
|
103
133
|
|
104
134
|
def colorized_status(status)
|
105
135
|
case status
|
106
136
|
when "Success"
|
107
|
-
status.
|
137
|
+
status.color(:green)
|
108
138
|
when "Failed"
|
109
|
-
status.
|
139
|
+
status.color(:red)
|
110
140
|
else
|
111
141
|
status
|
112
142
|
end
|
@@ -121,7 +151,7 @@ module Sonic
|
|
121
151
|
return if content.empty?
|
122
152
|
|
123
153
|
puts "Command standard #{type}:"
|
124
|
-
# "https://s3.amazonaws.com/
|
154
|
+
# "https://s3.amazonaws.com/infra-prod/ssm/commands/sonic/0a4f4bef-8f63-4235-8b30-ae296477261a/i-0b2e6e187a3f9ada9/awsrunPowerShellScript/0.awsrunPowerShellScript/stderr">
|
125
155
|
if content.include?("--output truncated--") && !resp[s3_key].empty?
|
126
156
|
s3_url = resp[s3_key]
|
127
157
|
info = s3_url.sub('https://s3.amazonaws.com/', '').split('/')
|
@@ -147,7 +177,6 @@ module Sonic
|
|
147
177
|
|
148
178
|
begin
|
149
179
|
resp = ssm.send_command(options)
|
150
|
-
# puts "NOOP FOR NOW"
|
151
180
|
rescue Aws::SSM::Errors::UnsupportedPlatformType
|
152
181
|
retries += 1
|
153
182
|
# toggle AWS-RunShellScript / AWS-RunPowerShellScript
|
@@ -166,12 +195,18 @@ module Sonic
|
|
166
195
|
end
|
167
196
|
|
168
197
|
def build_ssm_options
|
169
|
-
criteria =
|
198
|
+
criteria = transform_filter_option
|
170
199
|
command = build_command(@command)
|
171
200
|
options = criteria.merge(
|
172
201
|
document_name: "AWS-RunShellScript", # default
|
173
202
|
comment: "sonic #{ARGV.join(' ')}"[0..99], # comment has a max of 100 chars
|
174
|
-
parameters: { "commands" => command }
|
203
|
+
parameters: { "commands" => command },
|
204
|
+
# Default CloudWatchLog settings. Can be overwritten with settings.yml send_command
|
205
|
+
# IMPORTANT: make sure the EC2 instance the command runs on has access to write to CloudWatch Logs.
|
206
|
+
cloud_watch_output_config: {
|
207
|
+
# cloud_watch_log_group_name: "ssm", # Defaults to /aws/ssm/AWS-RunShellScript (aws/ssm/SystemsManagerDocumentName https://amzn.to/38TKVse)
|
208
|
+
cloud_watch_output_enabled: true,
|
209
|
+
},
|
175
210
|
)
|
176
211
|
settings_options = settings["send_command"] || {}
|
177
212
|
options.merge(settings_options.deep_symbolize_keys)
|
@@ -181,6 +216,12 @@ module Sonic
|
|
181
216
|
@settings ||= Setting.new.data
|
182
217
|
end
|
183
218
|
|
219
|
+
def check_filter_options!
|
220
|
+
return if @tags || @instance_ids
|
221
|
+
puts "ERROR: Please provide --tags or --instance-ids option".color(:red)
|
222
|
+
exit 1
|
223
|
+
end
|
224
|
+
|
184
225
|
#
|
185
226
|
# Public: Transform the filter to the ssm send_command equivalent options
|
186
227
|
#
|
@@ -188,41 +229,31 @@ module Sonic
|
|
188
229
|
#
|
189
230
|
# Examples
|
190
231
|
#
|
191
|
-
#
|
232
|
+
# transform_filter_option
|
192
233
|
# # => {
|
193
234
|
# instance_ids: ["i-006a097bb10643e20"],
|
194
235
|
# targets: [{key: "Name", values: "hi-web-prod,hi-worker-prod"}]
|
195
236
|
# }
|
196
237
|
#
|
197
238
|
# Returns the duplicated String.
|
198
|
-
def
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
key: "tag:#{tag_name}",
|
213
|
-
values: tags
|
214
|
-
}]
|
239
|
+
def transform_filter_option
|
240
|
+
if @tags
|
241
|
+
list = @tags.split(';')
|
242
|
+
targets = list.inject([]) do |final,item|
|
243
|
+
tag_name,value_list = item.split('=')
|
244
|
+
values = value_list.split(',').map(&:strip)
|
245
|
+
# structure expected by ssm send_command
|
246
|
+
option = {
|
247
|
+
key: "tag:#{tag_name}",
|
248
|
+
values: values
|
249
|
+
}
|
250
|
+
final << option
|
251
|
+
final
|
252
|
+
end
|
215
253
|
{targets: targets}
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
# Either all instance ids are no instance ids is a valid filter
|
220
|
-
def validate_filter(filter)
|
221
|
-
if filter.detect { |i| instance_id?(i) }
|
222
|
-
instance_ids = filter.select { |i| instance_id?(i) }
|
223
|
-
instance_ids.size == filter.size
|
224
|
-
else
|
225
|
-
true
|
254
|
+
else # @instance_ids
|
255
|
+
instance_ids = @instance_ids.split(',')
|
256
|
+
{instance_ids: instance_ids}
|
226
257
|
end
|
227
258
|
end
|
228
259
|
|
@@ -239,7 +270,7 @@ module Sonic
|
|
239
270
|
# The script is being feed inline so just join the command together into one script.
|
240
271
|
# Still keep in an array form because that's how ssn.send_command works with AWS-RunShellScript
|
241
272
|
# usually reads the command.
|
242
|
-
|
273
|
+
command.is_a?(Array) ? command : [command]
|
243
274
|
end
|
244
275
|
end
|
245
276
|
|
@@ -254,8 +285,7 @@ You can use the following command to check registered instances to SSM.
|
|
254
285
|
#{ssm_describe_command}
|
255
286
|
EOS
|
256
287
|
UI.warn(message)
|
257
|
-
copy_paste_clipboard(ssm_describe_command)
|
258
|
-
UI.say "Pro tip: ssm describe-instance-information already in your copy/paste clipboard."
|
288
|
+
copy_paste_clipboard(ssm_describe_command, "Pro tip: ssm describe-instance-information already in your copy/paste clipboard.")
|
259
289
|
end
|
260
290
|
|
261
291
|
def file_path?(command)
|
@@ -312,9 +342,10 @@ EOL
|
|
312
342
|
end
|
313
343
|
end
|
314
344
|
|
315
|
-
def copy_paste_clipboard(command)
|
345
|
+
def copy_paste_clipboard(command, text)
|
316
346
|
return unless RUBY_PLATFORM =~ /darwin/
|
317
347
|
system("echo '#{command}' | pbcopy")
|
348
|
+
UI.say text
|
318
349
|
end
|
319
350
|
end
|
320
351
|
end
|
data/lib/sonic/help/execute.md
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
|
1
|
+
* A filter must be provided. The filter can be a mix of instance ids and ec2 tags.
|
2
|
+
* The command can be provided inline or as a file. To a file use `file://` at the beginning of your file.
|
2
3
|
|
3
4
|
## Examples Summary
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
sonic execute --tags Name=demo-web uptime
|
7
|
+
sonic execute --tags Name=demo-web,demo-worker uptime # multiple values
|
8
|
+
sonic execute --instance-ids i-030033c20c54bf149,i-030033c20c54bf150 uname -a
|
9
|
+
sonic execute --instance-ids i-030033c20c54bf149 file://hello.sh # script from file
|
9
10
|
|
10
11
|
You cannot mix instance ids and tag names in the filter.
|
11
12
|
|
@@ -13,39 +14,37 @@ You cannot mix instance ids and tag names in the filter.
|
|
13
14
|
|
14
15
|
Here's a command example output in detailed:
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
Command
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
$
|
48
|
-
```
|
17
|
+
$ sonic execute --instance-ids i-0bf51a000ab4e73a8 uptime
|
18
|
+
Sending command to SSM with options:
|
19
|
+
---
|
20
|
+
instance_ids:
|
21
|
+
- i-0bf51a000ab4e73a8
|
22
|
+
document_name: AWS-RunShellScript
|
23
|
+
comment: sonic execute --instance-ids i-0bf51a000ab4e73a8 uptime
|
24
|
+
parameters:
|
25
|
+
commands:
|
26
|
+
- uptime
|
27
|
+
output_s3_region: us-east-1
|
28
|
+
output_s3_bucket_name: [reacted]
|
29
|
+
output_s3_key_prefix: ssm/commands/sonic
|
30
|
+
|
31
|
+
Command sent to AWS SSM. To check the details of the command:
|
32
|
+
aws ssm list-commands --command-id 0bb18d58-6436-49fd-9bfd-0c4b6c51c7a2
|
33
|
+
aws ssm get-command-invocation --command-id 0bb18d58-6436-49fd-9bfd-0c4b6c51c7a2 --instance-id i-0bf51a000ab4e73a8
|
34
|
+
|
35
|
+
Waiting for ssm command to finish.....
|
36
|
+
Command finished.
|
37
|
+
|
38
|
+
Displaying output for i-0bf51a000ab4e73a8.
|
39
|
+
Command status: Success
|
40
|
+
Command standard output:
|
41
|
+
01:08:10 up 8 days, 6:41, 0 users, load average: 0.00, 0.00, 0.00
|
42
|
+
|
43
|
+
To see the more output details visit:
|
44
|
+
https://us-west-2.console.aws.amazon.com/systems-manager/run-command/0bb18d58-6436-49fd-9bfd-0c4b6c51c7a2
|
45
|
+
|
46
|
+
Pro tip: the console url is already in your copy/paste clipboard.
|
47
|
+
$
|
49
48
|
|
50
49
|
Notice the conveniences of `sonic execute`, it:
|
51
50
|
|