sonic-screwdriver 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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