sonic-screwdriver 2.0.0 → 2.2.2
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/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
|
|