sonic-screwdriver 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/bin/commit_docs.sh +26 -0
  3. data/.circleci/config.yml +70 -0
  4. data/.gitignore +1 -1
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +13 -2
  7. data/Gemfile +3 -3
  8. data/Gemfile.lock +43 -14
  9. data/Guardfile +17 -10
  10. data/LICENSE.txt +2 -2
  11. data/README.md +10 -10
  12. data/Rakefile +9 -2
  13. data/docs/_config.yml +3 -0
  14. data/docs/_docs/help.md +1 -1
  15. data/docs/_docs/install-bastion.md +5 -15
  16. data/docs/_docs/install.md +3 -3
  17. data/docs/_docs/settings.md +40 -56
  18. data/docs/_docs/tutorial-ecs-exec.md +16 -20
  19. data/docs/_docs/tutorial-ecs-sh.md +73 -0
  20. data/docs/_docs/tutorial-execute.md +93 -17
  21. data/docs/_docs/tutorial-ssh.md +13 -18
  22. data/docs/_docs/why-ec2-run-command.md +1 -1
  23. data/docs/_includes/commands.html +5 -5
  24. data/docs/_includes/content.html +5 -0
  25. data/docs/_includes/css/main.css +15 -9
  26. data/docs/_includes/css/sonic.css +7 -5
  27. data/docs/_includes/example.html +4 -4
  28. data/docs/_includes/reference.md +1 -0
  29. data/docs/_includes/subnav.html +2 -1
  30. data/docs/_reference/sonic-completion.md +44 -0
  31. data/docs/_reference/sonic-completion_script.md +25 -0
  32. data/docs/_reference/sonic-ecs-exec.md +30 -0
  33. data/docs/_reference/sonic-ecs-help.md +21 -0
  34. data/docs/_reference/sonic-ecs-sh.md +35 -0
  35. data/docs/_reference/sonic-ecs.md +25 -0
  36. data/docs/_reference/sonic-execute.md +84 -0
  37. data/docs/_reference/sonic-list.md +40 -0
  38. data/docs/_reference/sonic-ssh.md +86 -0
  39. data/docs/_reference/sonic-version.md +21 -0
  40. data/docs/img/tutorials/ec2-console-run-command.png +0 -0
  41. data/docs/quick-start.md +9 -10
  42. data/docs/reference.md +12 -0
  43. data/{bin → exe}/sonic +3 -3
  44. data/lib/bash_scripts/docker-exec.sh +1 -0
  45. data/lib/bash_scripts/docker-run.sh +8 -1
  46. data/lib/sonic.rb +10 -2
  47. data/lib/sonic/{aws_services.rb → aws_service.rb} +6 -1
  48. data/lib/sonic/base_command.rb +82 -0
  49. data/lib/sonic/cli.rb +37 -27
  50. data/lib/sonic/command.rb +8 -22
  51. data/lib/sonic/completer.rb +161 -0
  52. data/lib/sonic/completer/script.rb +6 -0
  53. data/lib/sonic/completer/script.sh +10 -0
  54. data/lib/sonic/core.rb +15 -0
  55. data/lib/sonic/default/settings.yml +6 -16
  56. data/lib/sonic/docker.rb +29 -1
  57. data/lib/sonic/ecs.rb +22 -0
  58. data/lib/sonic/execute.rb +153 -18
  59. data/lib/sonic/help.rb +9 -0
  60. data/lib/sonic/help/command/send.md +10 -0
  61. data/lib/sonic/help/completion.md +22 -0
  62. data/lib/sonic/help/completion_script.md +3 -0
  63. data/lib/sonic/help/ecs/exec.md +8 -0
  64. data/lib/sonic/help/ecs/sh.md +13 -0
  65. data/lib/sonic/help/execute.md +60 -0
  66. data/lib/sonic/help/list.md +17 -0
  67. data/lib/sonic/help/ssh.md +60 -0
  68. data/lib/sonic/list.rb +4 -1
  69. data/lib/sonic/setting.rb +47 -0
  70. data/lib/sonic/ssh.rb +41 -20
  71. data/lib/sonic/ssh/identifier_detector.rb +6 -2
  72. data/lib/sonic/version.rb +1 -1
  73. data/sonic.gemspec +14 -9
  74. data/spec/lib/cli_spec.rb +5 -10
  75. data/spec/lib/sonic/execute_spec.rb +0 -1
  76. data/spec/spec_helper.rb +18 -10
  77. metadata +115 -16
  78. data/docs/_docs/tutorial-ecs-run.md +0 -100
  79. data/lib/sonic/cli/help.rb +0 -152
  80. data/lib/sonic/settings.rb +0 -115
data/lib/sonic/help.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Sonic::Help
2
+ class << self
3
+ def text(namespaced_command)
4
+ path = namespaced_command.to_s.gsub(':','/')
5
+ path = File.expand_path("../help/#{path}.md", __FILE__)
6
+ IO.read(path) if File.exist?(path)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
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.
2
+
3
+ Examples:
4
+
5
+ $ sonic command send hi-web-prod uptime
6
+ $ sonic command send hi-web-prod,hi-worker-prod,hi-clock-prod uptime
7
+ $ sonic command send i-030033c20c54bf149,i-030033c20c54bf150 uname -a
8
+ $ sonic command send i-030033c20c54bf149 file://hello.sh
9
+
10
+ You cannot mix instance ids and tag names in the filter.
@@ -0,0 +1,22 @@
1
+ Example:
2
+
3
+ sonic completion
4
+
5
+ Prints words for TAB auto-completion.
6
+
7
+ Examples:
8
+
9
+ sonic completion
10
+ sonic completion execute
11
+ sonic completion list
12
+
13
+ To enable, TAB auto-completion add the following to your profile:
14
+
15
+ eval $(sonic completion_script)
16
+
17
+ Auto-completion example usage:
18
+
19
+ sonic [TAB]
20
+ sonic exe[TAB]
21
+ sonic execute [TAB]
22
+ sonic list [TAB]
@@ -0,0 +1,3 @@
1
+ To use, add the following to your `~/.bashrc` or `~/.profile`
2
+
3
+ eval $(sonic completion_script)
@@ -0,0 +1,8 @@
1
+ Ssh into an ECS container instance, finds a running docker container associated
2
+ with the service and docker exec's into it.
3
+
4
+ Examples:
5
+
6
+ $ sonic ecs exec my-service --cluster my-cluster
7
+
8
+ You can use a variety of identifiers. These include the ECS service name and task id.
@@ -0,0 +1,13 @@
1
+ Ssh into an ECS container instance and runs a docker container using the same
2
+ environment and image as the specified running service.
3
+
4
+ Examples:
5
+
6
+ $ sonic ecs sh --cluster my-cluster my-service
7
+ $ sonic ecs sh --cluster my-cluster my-service
8
+
9
+ # If there are flags in the command that you want to pass down to the docker
10
+ run command you will need to put the command in single quotes. This is due to
11
+ the way Thor (what this tool uses) parses options.
12
+
13
+ $ sonic ecs sh --cluster production-hi hi-web 'rake -T'
@@ -0,0 +1,60 @@
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.
2
+
3
+ ## Examples Summary
4
+
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
9
+
10
+ You cannot mix instance ids and tag names in the filter.
11
+
12
+ ## Example Detailed
13
+
14
+ Here's a command example output in detailed:
15
+
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
+ ```
49
+
50
+ Notice the conveniences of `sonic execute`, it:
51
+
52
+ 1. Showed the parameters that will be sent as part of the send_command call to SSM.
53
+ 2. Sent the command to SSM.
54
+ 3. Waited for the command to finish.
55
+ 4. Displayed the output of the command.
56
+ 5. Provided the console url that visit to view more details about the SSM command.
57
+
58
+ The AWS SSM console looks like this:
59
+
60
+ <img src="/img/tutorials/ec2-console-run-command.png" class="doc-photo" />
@@ -0,0 +1,17 @@
1
+ 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.
2
+
3
+ Examples:
4
+
5
+ $ sonic list
6
+ $ sonic list hi-web-prod
7
+ $ sonic list hi-web-prod,hi-clock-prod
8
+ $ sonic list i-09482b1a6e330fbf7
9
+
10
+ Example Output:
11
+
12
+ $ sonic list --header i-09482b1a6e330fbf7
13
+ Instance Id Public IP Private IP Type
14
+ i-09482b1a6e330fbf7 54.202.152.168 172.31.21.108 t2.small
15
+ $
16
+
17
+ You cannot mix instance ids and tag names in the filter.
@@ -0,0 +1,60 @@
1
+ * EC2 instance id. Example: i-067c5e3f026c1e801
2
+ * EC2 tag value. Any tag value is search, the tag key does not matter only the tag value matters. Example: hi-web
3
+ * ECS service. Example: my-ecs-service
4
+ * ECS container instance id. Example: 7fbc8c75-4675-4d39-a5a4-0395ff8cd474
5
+ * ECS task id. Example: 1ed12abd-645c-4a05-9acf-739b9d790170
6
+
7
+ When using ecs identifiers the `--cluster` option is required or can be set in ~/.sonic/settings.yml.
8
+
9
+ Examples:
10
+
11
+ $ sonic ssh i-067c5e3f026c1e801
12
+ $ sonic ssh hi-web
13
+ $ sonic ssh --cluster my-cluster my-ecs-service
14
+ $ sonic ssh 7fbc8c75-4675-4d39-a5a4-0395ff8cd474
15
+ $ sonic ssh 1ed12abd-645c-4a05-9acf-739b9d790170
16
+
17
+ Sonic ssh builds up the ssh command and shells out to it. For example, the following commands:
18
+
19
+ sonic ssh i-027363802c6ff314f
20
+
21
+ Translates to:
22
+
23
+ ssh ec2-user@ec2-52-24-216-170.us-west-2.compute.amazonaws.com
24
+
25
+ You can also tack on any command to be run at the end of the command. Example:
26
+
27
+ $ sonic ssh i-027363802c6ff314f uptime
28
+ => ssh ec2-user@ec2-52-24-216-170.us-west-2.compute.amazonaws.com uptime
29
+ 15:57:02 up 18:21, 0 users, load average: 0.00, 0.01, 0.00
30
+
31
+ ## Specifying pem keys
32
+
33
+ The recommended way to specify custom private keys is to use ssh-agent as covered here: https://blog.boltops.com/2017/09/21/3-ssh-tips-ssh-agent-tunnel-and-escaping-from-the-dead
34
+
35
+ But you can also specify the pem key to use with the -i option. Example:
36
+
37
+ $ sonic ssh -i ~/.ssh/id_rsa-custom ec2-user@ec2-52-24-216-170.us-west-2.compute.amazonaws.com
38
+
39
+ ## Retry option
40
+
41
+ For newly launched instances, the instance's ssh access might not be quite ready. Typically, you must press up enter repeatedly until the instance is ready. Sonic ssh has a retry option that automates this. Example:
42
+
43
+ $ sonic ssh -r i-027363802c6ff314f
44
+
45
+ Bastion Host Support
46
+
47
+ Sonic ssh also supports a bastion host.
48
+
49
+ $ sonic ssh --bastion bastion.domain.com i-027363802c6ff314f
50
+ $ sonic ssh --bastion user@bastion.domain.com i-027363802c6ff314f
51
+
52
+ Here's what the output looks like:
53
+
54
+ $ sonic ssh --bastion 34.211.223.3 i-0f7f833131a51ce35 uptime
55
+ => ssh -At ec2-user@34.211.223.3 ssh ec2-user@10.10.110.135 uptime
56
+ 17:57:59 up 37 min, 0 users, load average: 0.00, 0.02, 0.00
57
+ Connection to 34.211.223.3 closed.
58
+ $
59
+
60
+ You can also set the bastion host and other options with a [settings file](http://sonic-screwdriver.cloud/docs/settings/).
data/lib/sonic/list.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Sonic
2
2
  class List
3
- include AwsServices
3
+ include AwsService
4
4
 
5
5
  def initialize(options)
6
6
  @options = options
@@ -22,6 +22,9 @@ module Sonic
22
22
  # ERROR: The instance ID 'i-066b140d9479e9682' does not exist
23
23
  UI.error(e.message)
24
24
  exit 1
25
+ rescue Aws::EC2::Errors::InvalidInstanceIDMalformed => e
26
+ UI.error(e.message)
27
+ exit 1
25
28
  end
26
29
  instances
27
30
  end
@@ -0,0 +1,47 @@
1
+ require 'yaml'
2
+ require 'memoist'
3
+ require 'active_support/core_ext/hash'
4
+
5
+ module Sonic
6
+ class Setting
7
+ extend Memoist
8
+
9
+ def data
10
+ settings_file = Sonic.profile || 'default'
11
+ settings_file += ".yml"
12
+
13
+ project = yaml_file("#{Sonic.root}/.sonic/#{settings_file}")
14
+ user = yaml_file("#{home}/.sonic/#{settings_file}")
15
+ default_file = File.expand_path("../default/settings.yml", __FILE__)
16
+ default = yaml_file(default_file)
17
+
18
+ data = merge(default, user, project)
19
+
20
+ if ENV['DEBUG_SETTINGS']
21
+ puts "settings data:"
22
+ pp data
23
+ end
24
+ data
25
+ end
26
+ memoize :data
27
+
28
+ def merge(*hashes)
29
+ hashes.inject({}) do |result, hash|
30
+ # note: important to compact for keys with nil value
31
+ result.deep_merge(hash.compact)
32
+ end
33
+ end
34
+
35
+ # Any empty file will result in "false". Lets ensure that an empty file
36
+ # loads an empty hash instead.
37
+ def yaml_file(path)
38
+ return {} unless File.exist?(path)
39
+ YAML.load_file(path) || {}
40
+ end
41
+
42
+ def home
43
+ # hack but fast
44
+ ENV['TEST'] ? "spec/fixtures/home" : ENV['HOME']
45
+ end
46
+ end
47
+ end
data/lib/sonic/ssh.rb CHANGED
@@ -5,7 +5,7 @@ module Sonic
5
5
  autoload :IdentifierDetector, 'sonic/ssh/identifier_detector'
6
6
  autoload :CliOptions, 'sonic/ssh/cli_options'
7
7
 
8
- include AwsServices
8
+ include AwsService
9
9
  include CliOptions
10
10
 
11
11
  def initialize(identifier, options)
@@ -14,11 +14,12 @@ module Sonic
14
14
  @user, @identifier = extract_user!(identifier) # extracts/strips user from identifier
15
15
  # While --user option is supported at the class level, don't expose at the CLI level
16
16
  # to encourage users to use user@host notation.
17
- @user ||= options[:user] || settings.data["user"]
17
+ @user ||= options[:user] || settings["bastion"]["user"]
18
18
 
19
19
  @service = @identifier # always set service even though it's not always used as the identifier
20
- @cluster = options[:cluster] || settings.default_cluster(@service)
21
- @bastion = options[:bastion] || settings.default_bastion(@bastion)
20
+ map = settings["ecs_service_cluster_map"]
21
+ @cluster = options[:cluster] || map[@service] || map["default"] || "default"
22
+ @bastion = options[:bastion] || settings["bastion"]["host"]
22
23
  end
23
24
 
24
25
  def run
@@ -47,28 +48,34 @@ module Sonic
47
48
  def build_ssh_host
48
49
  return @identifier if ENV['TEST']
49
50
 
50
- detector = Ssh::IdentifierDetector.new(@cluster, @service, @identifier, @options)
51
51
  instance_id = detector.detect!
52
52
  instance_hostname(instance_id)
53
53
  end
54
54
 
55
+ def detector
56
+ @detector ||= Ssh::IdentifierDetector.new(@cluster, @service, @identifier, @options)
57
+ end
58
+
59
+
55
60
  def instance_hostname(ec2_instance_id)
56
61
  begin
57
62
  resp = ec2.describe_instances(instance_ids: [ec2_instance_id])
58
63
  rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
59
64
  # e.message: The instance ID 'i-027363802c6ff3141' does not exist
60
- UI.error(e.message)
65
+ UI.error e.message
66
+ exit 1
67
+ rescue Aws::Errors::NoSuchEndpointError, SocketError
68
+ UI.error "It doesnt look like you have an internet connection. Please double check that you have an internet connection."
61
69
  exit 1
62
70
  end
63
71
  instance = resp.reservations[0].instances[0]
64
72
  # struct Aws::EC2::Types::Instance
65
73
  # http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Types/Instance.html
66
- host = if @bastion
67
- instance.private_ip_address
68
- else
69
- instance.public_ip_address
70
- end
71
- "#{@user}@#{host}"
74
+ if @bastion
75
+ instance.private_ip_address
76
+ else
77
+ instance.public_ip_address
78
+ end
72
79
  end
73
80
 
74
81
  # Will use Kernel.exec so that the ssh process takes over this ruby process.
@@ -82,18 +89,30 @@ module Sonic
82
89
  end
83
90
 
84
91
  private
92
+ # direct access to settings data
85
93
  def settings
86
- @settings ||= Settings.new(@options[:project_root])
94
+ @settings ||= Setting.new.data
87
95
  end
88
96
 
89
97
  # Returns Array of flags.
90
98
  # Example:
91
99
  # ["-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"]
92
100
  def ssh_options
93
- host_key_check_options = settings.host_key_check_options
94
101
  keys_option + host_key_check_options
95
102
  end
96
103
 
104
+ # By default bypass strict host key checking for convenience.
105
+ # But user can overrride this.
106
+ def host_key_check_options
107
+ if settings["bastion"]["host_key_check"] == true
108
+ []
109
+ else
110
+ # settings["bastion"]["host_key_check"] nil will disable checking also
111
+ # disables host key checking
112
+ %w[-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null]
113
+ end
114
+ end
115
+
97
116
  # Will prepend the bastion host if required
98
117
  # When bastion set
99
118
  # ssh [options] -At [bastion_host] ssh -At [ssh_host]
@@ -111,12 +130,14 @@ private
111
130
  # -A = Enables forwarding of the authentication agent connection
112
131
  # -t = Force pseudo-terminal allocati
113
132
  def build_ssh_command
114
- ssh = ["ssh"] + ssh_options
115
- ssh += ["-At", bastion_host, "ssh"] + ssh_options if @bastion
116
- # ssh_host is internal ip when bastion is set
117
- # ssh_host is public ip when bastion is not set
118
- ssh += ["-At", ssh_host]
119
- ssh
133
+ command = ["ssh", "-t"] + ssh_options
134
+ if @bastion
135
+ # https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts
136
+ # -J xxx is -o ProxyJump=xxx
137
+ proxy_jump = ["-J", bastion_host]
138
+ command += proxy_jump
139
+ end
140
+ command += ["-t", "#{@user}@#{ssh_host}"]
120
141
  end
121
142
 
122
143
  # Private: Extracts and strips the user from the identifier.
@@ -4,7 +4,7 @@ class Ssh
4
4
  class IdentifierDetector
5
5
 
6
6
  include Ec2Tag
7
- include AwsServices
7
+ include AwsService
8
8
  include Checks
9
9
 
10
10
  def initialize(cluster, service, identifier, options)
@@ -17,7 +17,7 @@ class Ssh
17
17
  # Returns exactly 1 instance_id or exits the program.
18
18
  # The bang check_* methods can exit early.
19
19
  def detect!
20
- instance_id = case detected_type
20
+ @instance_id ||= case detected_type
21
21
  when :ecs_container_instance_or_task_arn
22
22
  check_cluster_exists! unless @options[:noop]
23
23
 
@@ -36,6 +36,10 @@ class Ssh
36
36
  end
37
37
  end
38
38
 
39
+ def instance_id
40
+ detect!
41
+ end
42
+
39
43
  # Any of these methods either returns exactly 1 instance_id or exit the program.
40
44
  #
41
45
  # Returns detected_type
data/lib/sonic/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Sonic
2
- VERSION = "1.4.0"
2
+ VERSION = "2.0.0"
3
3
  end
data/sonic.gemspec CHANGED
@@ -1,34 +1,39 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'sonic/version'
4
+ require "sonic/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "sonic-screwdriver"
8
8
  spec.version = Sonic::VERSION
9
9
  spec.authors = ["Tung Nguyen"]
10
10
  spec.email = ["tung@boltops.com"]
11
- spec.description = %q{Multi-functional tool to manage AWS infrastructure}
12
- spec.summary = %q{Multi-functional tool to manage AWS infrastructure}
11
+ spec.summary = "Multi-functional tool to manage AWS infrastructure"
13
12
  spec.homepage = "http://sonic-screwdriver.cloud/"
14
13
  spec.license = "MIT"
15
14
 
16
15
  spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "thor"
22
- spec.add_dependency "hashie"
23
- spec.add_dependency "colorize"
21
+ spec.add_dependency "activesupport"
24
22
  spec.add_dependency "aws-sdk-ec2"
25
23
  spec.add_dependency "aws-sdk-ecs"
24
+ spec.add_dependency "aws-sdk-s3"
26
25
  spec.add_dependency "aws-sdk-ssm"
26
+ spec.add_dependency "colorize"
27
+ spec.add_dependency "hashie"
28
+ spec.add_dependency "memoist"
29
+ spec.add_dependency "thor"
27
30
  spec.add_dependency "tty-prompt"
28
31
 
29
32
  spec.add_development_dependency "bundler"
30
- spec.add_development_dependency "rake"
33
+ spec.add_development_dependency "byebug"
31
34
  spec.add_development_dependency "guard"
32
35
  spec.add_development_dependency "guard-bundler"
33
36
  spec.add_development_dependency "guard-rspec"
37
+ spec.add_development_dependency "rake"
38
+ spec.add_development_dependency "cli_markdown"
34
39
  end