sonic-screwdriver 1.2.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bab148a2ed5638be1af0462d0e8d8e594bb17b8b
4
- data.tar.gz: ab33bc905251629ebfe0afb313b1467652ec14f5
3
+ metadata.gz: dd37ca876dccfeb67a1540e2af0f312fafcde6a3
4
+ data.tar.gz: c728fd9cf1da7019cc2738912d372f937e36704d
5
5
  SHA512:
6
- metadata.gz: 448ee390aeaf1d33395515f30d0a7aafd1347a6c8cf07230325441cd4a38027f7d3eb0a7dd361d0698b96a4a36088a0eadc8631050c56e303bac017dd6efc7f1
7
- data.tar.gz: 5f346a3756cd24f8025eafd1ac227fc075d3f9948164ba076544f7bafb7905a5a35e01d3cad32d3a5a991605f456da72dec052ad11b93ce6f4acaeb6801b337a
6
+ metadata.gz: 04f2e8e50729ca19e70a5fdd720cdcef5bdbe1bef20f14c93287811ba44ca8864760715ab55af8a93ce609d3b4759cbb1c304640e981d765b72143f49deec99d
7
+ data.tar.gz: 67357d39582de07b37672ae14a101515afe8eb9be1569199a2eaa0b9f59226577c444a25530f8067427973351f04309fc0ec5ca7a56ca2d478462f988a19df24
@@ -3,6 +3,9 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
5
 
6
+ ## [1.3.0]
7
+ - support for different bastion cluster host mapping in settings
8
+
6
9
  ## [1.2.0]
7
10
  - add old instance id support
8
11
  - update docs and help
@@ -11,7 +11,11 @@ You can adjust the behavior of sonic and set some handy default values with `set
11
11
  Here is an `settings.yml` example:
12
12
 
13
13
  ```yaml
14
- bastion: bastion.mydomain.com
14
+ bastion: # cluster_host mapping
15
+ default: # default is nil - which means a bastion host wont be used
16
+ # Examples:
17
+ # prod: bastion.mydomain.com
18
+ # stag: ubuntu@bastion-stag.mydomain.com
15
19
  host_key_check: false
16
20
  service_cluster:
17
21
  default: # defaults to nil
@@ -28,13 +32,32 @@ The following table covers the different setting options:
28
32
 
29
33
  Setting | Description | Default
30
34
  ------------- | ------------- | -------------
31
- bastion | Bastion host to use as the jump host. Examples: bastion.mydomain.com, myuser@bastion.myuser.com or 123.123.123.123. | (no value)
35
+ bastion | Bastion mapping allows you to set a bastion host on a per ECS cluster basis. The bastion host is used as the jump host. Examples: bastion.mydomain.com, myuser@bastion.myuser.com or 123.123.123.123. | (no value)
32
36
  host_key_check | Controls whether or not use the strict host checking ssh option. Since EC2 server host changes often the default value is false. | false
33
37
  service_cluster | Service to cluster mapping. This is a Hash structure that maps the service name to cluster names. | (no value)
34
38
  user | User to ssh into the server with. This can be overriden at the CLI with the user@host notation but can be set in the settings.yml file also. | ec2-user
35
39
 
36
40
  The default settings are located tool source code at [lib/sonic/default/settings.yml](https://github.com/boltopslabs/sonic/blob/master/lib/sonic/default/settings.yml).
37
41
 
42
+ ### Bastion cluster to host mapping
43
+
44
+ Provided this example:
45
+
46
+ ```yaml
47
+ bastion: # cluster_host mapping
48
+ default: ec2-user@bastion.mydomain.com
49
+ prod: ec2-user@bastion.mydomain.com
50
+ stag: ubuntu@bastion-stag.mydomain.com
51
+ ```
52
+
53
+ This results in
54
+
55
+ ```sh
56
+ sonic ssh --cluster prod [IDENTIFER] # ec2-user@bastion.mydomain.com used as the bastion host
57
+ sonic ssh --cluster stag [IDENTIFER] # ubuntu@bastion-stag.mydomain.com used as the bastion host
58
+ sonic ssh --cluster whatever [IDENTIFER] # ec2-user@bastion.mydomain.com used as the bastion host
59
+ ```
60
+
38
61
  ### Service to Cluster Mapping
39
62
 
40
63
  One of the useful options is the `service_cluster`. This option maps service names to cluster names. This saves you from typing the `--cluster` option over and over. Here is an example `~/.sonic/settings.yml`:
@@ -68,6 +91,6 @@ sonic ssh hi-worker-stag --cluster stag
68
91
  ```
69
92
 
70
93
 
71
- <a id="prev" class="btn btn-basic" href="{% link _docs/tutorial-execute.md %}">Back</a>
94
+ <a id="prev" class="btn btn-basic" href="{% link _docs/tutorial-list.md %}">Back</a>
72
95
  <a id="next" class="btn btn-primary" href="{% link _docs/help.md %}">Next Step</a>
73
96
  <p class="keyboard-tip">Pro tip: Use the <- and -> arrow keys to move back and forward.</p>
@@ -15,7 +15,6 @@ Examples:
15
15
  ```sh
16
16
  sonic execute hi-web-stag uptime
17
17
  sonic execute hi-web-prod uptime
18
- sonic execute hi-web-prod,hi-worker-prod,hi-clock-prod uptime
19
18
  sonic execute i-030033c20c54bf149,i-030033c20c54bf150 uname -a
20
19
  sonic execute i-030033c20c54bf149 file://hello.sh
21
20
  ```
@@ -42,12 +41,12 @@ The output of the commands ran are also showed in the EC2 Run Command Console.
42
41
 
43
42
  ### Polymorphic Filter
44
43
 
45
- The `sonic execute` command can understand a variety of different filters. The filters can be a list of instances ids or a list of EC2 tag names. Note, ECS service names are *not* supported for the filter.
44
+ The `sonic execute` command can understand a variety of different filters. The filters can be a list of instances ids or one EC2 tag value. Note, ECS service names are *not* supported for the filter.
46
45
 
47
- Here is an example, where the uptime command will run on any server with a tag value of hi-web-stag,hi-clock-stag, or hi-worker-stag.
46
+ Here is an example, where the uptime command will run on both i-030033c20c54bf149 and i-030033c20c54bf150 instances.
48
47
 
49
48
  ```sh
50
- sonic execute hi-web-stag,hi-clock-stag,hi-worker-stag uptime
49
+ sonic execute i-066b140d9479e9681,i-09482b1a6e330fbf7 uptime
51
50
  ```
52
51
 
53
52
  ### Run Scripts
@@ -75,5 +74,5 @@ The `sonic execute` command relies on EC2 Run Manager. So you will need to have
75
74
  * You can read on [Why EC2 Run Manager]({% link _docs/why-ec2-run-command.md %}) is used also.
76
75
 
77
76
  <a id="prev" class="btn btn-basic" href="{% link _docs/tutorial-ecs-run.md %}">Back</a>
78
- <a id="next" class="btn btn-primary" href="{% link _docs/settings.md %}">Next Step</a>
77
+ <a id="next" class="btn btn-primary" href="{% link _docs/tutorial-list.md %}">Next Step</a>
79
78
  <p class="keyboard-tip">Pro tip: Use the <- and -> arrow keys to move back and forward.</p>
@@ -0,0 +1,50 @@
1
+ ---
2
+ title: Sonic List
3
+ ---
4
+
5
+ Sonic provides a quick way to list your instances.
6
+
7
+ ```sh
8
+ sonic list [FILTER]
9
+ ```
10
+
11
+ Example:
12
+
13
+ ```sh
14
+ sonic list i-066b140d9479e9681,i-09482b1a6e330fbf7
15
+ sonic list ec2-tag-1,ec2-tag-2
16
+ ```
17
+
18
+ You should see something like this:
19
+
20
+ ```sh
21
+ $ sonic list i-066b140d9479e9681,i-09482b1a6e330fbf7
22
+ i-09482b1a6e330fbf7 prod-20170416110240 54.202.152.168 172.31.21.108 t2.small
23
+ i-066b140d9479e9681 docker-20170428071833 34.211.144.113 172.31.11.250 m3.medium
24
+ $
25
+ ```
26
+
27
+ You can include a header with the `--header` option:
28
+
29
+ ```sh
30
+ $ sonic list i-066b140d9479e9681,i-09482b1a6e330fbf7 --header
31
+ Instance Id Name Public IP Private IP Type
32
+ i-09482b1a6e330fbf7 prod-20170416110240 54.202.152.168 172.31.21.108 t2.small
33
+ i-066b140d9479e9681 docker-20170428071833 34.211.144.113 172.31.11.250 m3.medium
34
+ $
35
+ ```
36
+
37
+ The list command can be helpful if you want to list out the instances and pipe it back into other tools. Here's a simple example:
38
+
39
+ ```sh
40
+ $ for i in $(sonic list i-066b140d9479e9681,i-09482b1a6e330fbf7 | awk '{print $3}') ; do echo $i ; ssh ec2-user@$i uptime ; done
41
+ 54.202.152.168
42
+ 17:39:14 up 6 days, 1:24, 0 users, load average: 0.00, 0.00, 0.00
43
+ 34.211.144.113
44
+ 17:39:14 up 3 days, 12:03, 0 users, load average: 0.00, 0.00, 0.00
45
+ $
46
+ ```
47
+
48
+ <a id="prev" class="btn btn-basic" href="{% link _docs/tutorial-execute.md %}">Back</a>
49
+ <a id="next" class="btn btn-primary" href="{% link _docs/settings.md %}">Next Step</a>
50
+ <p class="keyboard-tip">Pro tip: Use the <- and -> arrow keys to move back and forward.</p>
@@ -13,6 +13,7 @@
13
13
  <li><a href="{% link _docs/tutorial-ecs-exec.md %}">ECS Exec</a></li>
14
14
  <li><a href="{% link _docs/tutorial-ecs-run.md %}">ECS Run</a></li>
15
15
  <li><a href="{% link _docs/tutorial-execute.md %}">Execute</a></li>
16
+ <li><a href="{% link _docs/tutorial-list.md %}">List</a></li>
16
17
  </ul>
17
18
  </li>
18
19
  <li><a href="{% link _docs/settings.md %}">Settings</a></li>
@@ -30,6 +30,7 @@ module Sonic
30
30
 
31
31
  desc "execute [FILTER] [COMMAND]", "runs command across fleet of servers via AWS Run Command"
32
32
  long_desc Help.execute
33
+ option :zero_warn, type: :boolean, default: true, desc: "Warns user when no instances found"
33
34
  # filter - Filter ec2 instances by tag name or instance_ids separated by commas
34
35
  def execute(filter, *command)
35
36
  Execute.new(command, options.merge(filter: filter)).execute
@@ -1,4 +1,8 @@
1
- bastion: # defaults to nil
1
+ bastion: # cluster_host mapping
2
+ default: # default is nil - which means a bastion host wont be used
3
+ # Examples:
4
+ # prod: bastion.mydomain.com
5
+ # stag: ubuntu@bastion-stag.mydomain.com
2
6
  host_key_check: false
3
7
  service_cluster:
4
8
  default: # defaults to nil
@@ -1,3 +1,4 @@
1
+ require "byebug"
1
2
  module Sonic
2
3
  class Execute
3
4
  include AwsServices
@@ -19,16 +20,23 @@ module Sonic
19
20
  if @options[:noop]
20
21
  UI.noop = true
21
22
  command_id = "fake command id"
23
+ success = true # fake it for specs
22
24
  else
23
- resp = ssm.send_command(ssm_options)
24
- command_id = resp.command.command_id
25
+ instances_count = check_instances
26
+ return unless instances_count > 0
27
+
28
+ success = nil
29
+ begin
30
+ resp = ssm.send_command(ssm_options)
31
+ command_id = resp.command.command_id
32
+ success = true
33
+ rescue Aws::SSM::Errors::InvalidInstanceId => e
34
+ ssm_invalid_instance_error_message(e)
35
+ end
25
36
  end
26
- UI.say "Command sent to AWS SSM. To check the details of the command:"
27
- list_command = "aws ssm list-commands --command-id #{command_id}"
28
- UI.say list_command
29
- if RUBY_PLATFORM =~ /darwin/
30
- system("echo '#{list_command}' | pbcopy")
31
- UI.say "Pro tip: the aws ssm command is already in your copy/paste clipboard."
37
+ if success
38
+ UI.say "Command sent to AWS SSM. To check the details of the command:"
39
+ display_list_command(command_id)
32
40
  end
33
41
  end
34
42
 
@@ -44,36 +52,6 @@ module Sonic
44
52
  )
45
53
  end
46
54
 
47
- def build_command(command)
48
- if file_path?(command)
49
- path = file_path(command)
50
- if File.exist?(path)
51
- IO.readlines(path).map {|s| s.strip}
52
- else
53
- UI.error("File #{path} could not be found. Are you sure it exist?")
54
- exit 1
55
- end
56
- else
57
- # The script is being feed inline so just join the command together into one script.
58
- # Still keep in an array form because that's how ssn.send_command with AWS-RunShellScript
59
- # usually reads the command.
60
- [command.join(" ")]
61
- end
62
- end
63
-
64
- def file_path?(command)
65
- return false unless command.size == 1
66
- possible_path = command.first
67
- possible_path.include?("file://")
68
- end
69
-
70
- def file_path(command)
71
- path = command.first
72
- path = path.sub('file://', '')
73
- path = "#{@options[:project_root]}/#{path}" if @options[:project_root]
74
- path
75
- end
76
-
77
55
  #
78
56
  # Public: Transform the filter to the ssm send_command equivalent options
79
57
  #
@@ -119,6 +97,69 @@ module Sonic
119
97
  end
120
98
  end
121
99
 
100
+ def build_command(command)
101
+ if file_path?(command)
102
+ path = file_path(command)
103
+ if File.exist?(path)
104
+ IO.readlines(path).map {|s| s.strip}
105
+ else
106
+ UI.error("File #{path} could not be found. Are you sure it exist?")
107
+ exit 1
108
+ end
109
+ else
110
+ # The script is being feed inline so just join the command together into one script.
111
+ # Still keep in an array form because that's how ssn.send_command works with AWS-RunShellScript
112
+ # usually reads the command.
113
+ [command.join(" ")]
114
+ end
115
+ end
116
+
117
+ # e = Aws::SSM::Errors::InvalidInstanceId
118
+ def ssm_invalid_instance_error_message(e)
119
+ # e.message is an empty string so not very helpful
120
+ ssm_describe_command = 'aws ssm describe-instance-information --output text --query "InstanceInformationList[*]"'
121
+ message = <<-EOS
122
+ One of the instance ids: #{@filter.join(",")} is invalid according to SSM.
123
+ This might be because the SSM agent on the instance has not yet checked in.
124
+ You can use the following command to check registered instances to SSM.
125
+ #{ssm_describe_command}
126
+ EOS
127
+ UI.warn(message)
128
+ copy_paste_clipboard(ssm_describe_command)
129
+ end
130
+
131
+ def file_path?(command)
132
+ return false unless command.size == 1
133
+ possible_path = command.first
134
+ possible_path.include?("file://")
135
+ end
136
+
137
+ def file_path(command)
138
+ path = command.first
139
+ path = path.sub('file://', '')
140
+ path = "#{@options[:project_root]}/#{path}" if @options[:project_root]
141
+ path
142
+ end
143
+
144
+ # Counts the number of instances found using the filter and displays a helpful
145
+ # message to the user if 0 found.
146
+ def check_instances
147
+ return if @options[:zero_warn] == false
148
+
149
+ # The list options is a superset of the execute options so we can pass
150
+ # it right through
151
+ instances = List.new(@options).instances
152
+ if instances.count == 0
153
+ message = <<~EOL
154
+ Unable to find any instances with filter #{@filter.join(',')}.
155
+ Are you sure you specify the filter with either a EC2 tag or list instance ids?
156
+ If you are using ECS identifiers, they are not supported with this command.
157
+ EOL
158
+ UI.warn(message)
159
+ end
160
+ instances.count
161
+ end
162
+
122
163
  # TODO: make configurable
123
164
  def tag_name
124
165
  "Name"
@@ -129,5 +170,17 @@ module Sonic
129
170
  # old format is 8 characters long after i-
130
171
  text =~ /i-.{17}/ || text =~ /i-.{8}/
131
172
  end
173
+
174
+ def display_list_command(command_id)
175
+ list_command = "aws ssm list-commands --command-id #{command_id}"
176
+ UI.say list_command
177
+ copy_paste_clipboard(list_command)
178
+ end
179
+
180
+ def copy_paste_clipboard(command)
181
+ return unless RUBY_PLATFORM =~ /darwin/
182
+ system("echo '#{command}' | pbcopy")
183
+ UI.say "Pro tip: the aws ssm command is already in your copy/paste clipboard."
184
+ end
132
185
  end
133
186
  end
@@ -8,20 +8,22 @@ module Sonic
8
8
  end
9
9
 
10
10
  def run
11
+ display(instances)
12
+ end
13
+
14
+ def instances
15
+ return [] if @options[:noop]
16
+
11
17
  filter_options = transform_filter_option(@filter)
12
- if @options[:noop]
13
- instances = []
14
- else
15
- begin
16
- instances = ec2_resource.instances(filter_options)
17
- instances.count # force eager loading
18
- rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
19
- # ERROR: The instance ID 'i-066b140d9479e9682' does not exist
20
- UI.error(e.message)
21
- exit 1
22
- end
18
+ begin
19
+ instances = ec2_resource.instances(filter_options)
20
+ instances.count # force eager loading
21
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
22
+ # ERROR: The instance ID 'i-066b140d9479e9682' does not exist
23
+ UI.error(e.message)
24
+ exit 1
23
25
  end
24
- display(instances)
26
+ instances
25
27
  end
26
28
 
27
29
  def display(instances)
@@ -19,7 +19,8 @@ module Sonic
19
19
  default = YAML.load_file(default_file)
20
20
 
21
21
  @data = default.merge(user.merge(project))
22
- ensure_default_cluster(@data)
22
+ ensure_default_cluster!(@data)
23
+ ensure_default_bastion!(data)
23
24
  @data
24
25
  end
25
26
 
@@ -55,13 +56,46 @@ module Sonic
55
56
  # When user's .sonic/settings.yml lack the default cluster, we add it on.
56
57
  # Otherwise the user get confusing and scary aws-sdk-core/param_validator errors:
57
58
  # Example: https://gist.github.com/sonic/67b9a68a77363b908d1c36047bc2709a
58
- def ensure_default_cluster(data)
59
+ def ensure_default_cluster!(data)
59
60
  unless data["service_cluster"]["default"]
60
61
  data["service_cluster"]["default"] = "default"
61
62
  end
62
63
  data
63
64
  end
64
65
 
66
+ # Public: Returns default bastion host.
67
+ #
68
+ # cluster - cluster provided by user
69
+ #
70
+ # The settings.yml format:
71
+ #
72
+ # bastion:
73
+ # default: bastion.mydomain.com
74
+ # prod: bastion.mydomain.com
75
+ # stag: bastion-stag.mydomain.com
76
+ #
77
+ # Examples
78
+ #
79
+ # default_bastion('stag')
80
+ # # => 'bastion-stag.mydomain.com'
81
+ # default_bastion('whatever')
82
+ # # => 'bastion.mydomain.com'
83
+ #
84
+ # Returns the bastion host that is mapped to the cluster
85
+ def default_bastion(cluster)
86
+ bastion = data["bastion"]
87
+ bastion[cluster] || bastion["default"]
88
+ end
89
+
90
+ # When user's .sonic/settings.yml lack the default cluster, we add it on.
91
+ def ensure_default_bastion!(data)
92
+ unless data["bastion"] && data["bastion"].has_key?("default")
93
+ data["bastion"] ||= {}
94
+ data["bastion"]["default"] = nil
95
+ end
96
+ data
97
+ end
98
+
65
99
  def host_key_check_options
66
100
  if data["host_key_check"]
67
101
  # no options by default enables strict host key checking
@@ -16,7 +16,7 @@ module Sonic
16
16
 
17
17
  @service = @identifier # always set service even though it's not always used as the identifier
18
18
  @cluster = options[:cluster] || settings.default_cluster(@service)
19
- @bastion = options[:bastion] || settings.data["bastion"]
19
+ @bastion = options[:bastion] || settings.default_bastion(@bastion)
20
20
  end
21
21
 
22
22
  def run
@@ -21,6 +21,9 @@ module Sonic
21
21
  def error(msg='')
22
22
  say "ERROR: #{msg}".colorize(:red)
23
23
  end
24
+ def warn(msg='')
25
+ say "WARN: #{msg}".colorize(:yellow)
26
+ end
24
27
  end
25
28
  end
26
29
  end
@@ -1,3 +1,3 @@
1
1
  module Sonic
2
- VERSION = "1.2.0"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -4,7 +4,7 @@ describe Sonic::Execute do
4
4
  before(:each) do
5
5
  @options = {
6
6
  project_root: "spec/fixtures/project",
7
- filter: "hi-web-stag,hi-clock-stag"
7
+ filter: "i-066b140d9479e9681,i-09482b1a6e330fbf7"
8
8
  }
9
9
  end
10
10
 
@@ -12,8 +12,7 @@ describe Sonic::Execute do
12
12
  it "should build up options for ssm send command with inline command" do
13
13
  execute = Sonic::Execute.new(["uname", "-a"], @options)
14
14
  options = execute.build_ssm_options
15
- expect(options[:instance_ids]).to eq nil
16
- expect(options[:targets]).to eq [{:key=>"tag:Name", :values=>%w[hi-web-stag hi-clock-stag]}]
15
+ expect(options[:instance_ids]).to eq %w[i-066b140d9479e9681 i-09482b1a6e330fbf7]
17
16
  expect(options[:document_name]).to eq "AWS-RunShellScript"
18
17
  expect(options[:comment]).to include "sonic "
19
18
  expect(options[:parameters]["commands"]).to eq ["uname -a"]
@@ -22,8 +21,7 @@ describe Sonic::Execute do
22
21
  it "should build up options for ssm send command with file" do
23
22
  execute = Sonic::Execute.new(["file://command.txt"], @options)
24
23
  options = execute.build_ssm_options
25
- expect(options[:instance_ids]).to eq nil
26
- expect(options[:targets]).to eq [{:key=>"tag:Name", :values=>%w[hi-web-stag hi-clock-stag]}]
24
+ expect(options[:instance_ids]).to eq %w[i-066b140d9479e9681 i-09482b1a6e330fbf7]
27
25
  expect(options[:document_name]).to eq "AWS-RunShellScript"
28
26
  expect(options[:comment]).to include "sonic "
29
27
  expect(options[:parameters]["commands"]).to eq([
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sonic-screwdriver
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
@@ -183,6 +183,7 @@ files:
183
183
  - docs/_docs/tutorial-ecs-exec.md
184
184
  - docs/_docs/tutorial-ecs-run.md
185
185
  - docs/_docs/tutorial-execute.md
186
+ - docs/_docs/tutorial-list.md
186
187
  - docs/_docs/tutorial-ssh.md
187
188
  - docs/_docs/tutorial.md
188
189
  - docs/_docs/why-ec2-run-command.md