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.
@@ -11,16 +11,16 @@ reference: true
11
11
 
12
12
  Lists ec2 instances.
13
13
 
14
- List ec2 servers. 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 filter appropriately. The filter for listing is optional.
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
@@ -1,4 +1,4 @@
1
1
  #!/bin/bash -ex
2
2
 
3
3
  bundle exec jekyll clean
4
- exec bundle exec jekyll serve
4
+ exec bundle exec jekyll serve --host 0.0.0.0 "$@"
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
- ```sh
10
- gem install sonic-screwdriver
11
- ```
9
+ gem install sonic-screwdriver
12
10
 
13
11
  ### Usage
14
12
 
15
- ```sh
16
- # ssh into an instance
17
- sonic ssh i-0f7f833131a51ce35
18
- sonic ssh hi-web # ec2 tag
19
- sonic ssh hi-web --cluster staging # ecs service name
20
- sonic ssh 7fbc8c75-4675-4d39-a5a4-0395ff8cd474 --cluster staging # ECS container id
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 hi-web
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 hi-web
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 hi-web and worker
33
- sonic execute hi-web,hi-worker uptime
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 hi-web
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
@@ -1,6 +1,6 @@
1
1
  $:.unshift(File.expand_path("../", __FILE__))
2
+ require "rainbow/ext/string"
2
3
  require "sonic/version"
3
- require "colorize"
4
4
 
5
5
  module Sonic
6
6
  autoload :Core, 'sonic/core'
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
- def execute(filter, *command)
31
- Execute.new(command, options.merge(filter: filter)).execute
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."
@@ -1,5 +1,8 @@
1
1
  bastion:
2
2
  host_key_check: false
3
+ # host:
4
+
5
+ ssh:
3
6
  user: ec2-user
4
7
 
5
8
  ecs_service_cluster_map:
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(' ')}".colorize(:green)
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}".colorize(:green)
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
- @filter = @options[:filter].split(',').map{|s| s.strip}
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, ssm_options)
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, ssm_options)
75
- instance_ids = ssm_options[:instance_ids]
76
- return unless instance_ids && instance_ids.size > 0
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
- instance_id = instance_ids.first
79
- if ssm_options[:instance_ids].size > 1
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.colorize(:green)
137
+ status.color(:green)
108
138
  when "Failed"
109
- status.colorize(:red)
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/lr-infrastructure-prod/ssm/commands/sonic/0a4f4bef-8f63-4235-8b30-ae296477261a/i-0b2e6e187a3f9ada9/awsrunPowerShellScript/0.awsrunPowerShellScript/stderr">
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 = transform_filter(@filter)
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
- # transform_filter(["hi-web-prod", "hi-worker-prod", "i-006a097bb10643e20"])
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 transform_filter(filter)
199
- valid = validate_filter(filter)
200
- unless valid
201
- UI.error("The filter you provided '#{filter.join(',')}' is not valid.")
202
- UI.say("The filter must either be all instance ids or just a list of tag names.")
203
- exit 1
204
- end
205
-
206
- if filter.detect { |i| instance_id?(i) }
207
- instance_ids = filter
208
- {instance_ids: instance_ids}
209
- else
210
- tags = filter
211
- targets = [{
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
- end
217
- end
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
- [command.join(" ")]
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
@@ -1,11 +1,12 @@
1
- Run as a command across a list of servers. A filter must be provided. The filter can be a mix of instance ids and ec2 tags. This command can also take a path to a file. To specify a path to a file use file:// at the beginning of your file.
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
- $ sonic execute hi-web-prod uptime
6
- $ sonic execute hi-web-prod,hi-worker-prod,hi-clock-prod uptime
7
- $ sonic execute i-030033c20c54bf149,i-030033c20c54bf150 uname -a
8
- $ sonic execute i-030033c20c54bf149 file://hello.sh
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
- ```sh
17
- $ sonic execute 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 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
- $
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