aws-ec2 0.9.0 → 1.0.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +11 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +18 -10
  7. data/LICENSE.txt +1 -1
  8. data/README.md +74 -7
  9. data/Rakefile +1 -1
  10. data/aws-ec2.gemspec +7 -5
  11. data/lib/aws-ec2.rb +5 -2
  12. data/lib/aws_ec2/ami.rb +1 -1
  13. data/lib/aws_ec2/base.rb +34 -1
  14. data/lib/aws_ec2/cli.rb +20 -1
  15. data/lib/aws_ec2/command.rb +34 -5
  16. data/lib/aws_ec2/completer.rb +161 -0
  17. data/lib/aws_ec2/completer/script.rb +6 -0
  18. data/lib/aws_ec2/completer/script.sh +10 -0
  19. data/lib/aws_ec2/config.rb +4 -2
  20. data/lib/aws_ec2/core.rb +5 -1
  21. data/lib/aws_ec2/create.rb +11 -8
  22. data/lib/aws_ec2/create/error_messages.rb +1 -1
  23. data/lib/aws_ec2/create/params.rb +2 -6
  24. data/lib/aws_ec2/help/completion.md +22 -0
  25. data/lib/aws_ec2/help/completion_script.md +3 -0
  26. data/lib/aws_ec2/profile.rb +26 -19
  27. data/lib/aws_ec2/script.rb +1 -0
  28. data/lib/aws_ec2/script/compile.rb +15 -6
  29. data/lib/aws_ec2/script/compress.rb +62 -0
  30. data/lib/aws_ec2/script/upload.rb +75 -9
  31. data/lib/aws_ec2/setting.rb +41 -0
  32. data/lib/aws_ec2/template.rb +13 -0
  33. data/lib/aws_ec2/template/context.rb +32 -0
  34. data/lib/aws_ec2/template/helper.rb +17 -0
  35. data/lib/aws_ec2/{template_helper → template/helper}/ami_helper.rb +8 -3
  36. data/lib/aws_ec2/template/helper/core_helper.rb +88 -0
  37. data/lib/aws_ec2/{template_helper → template/helper}/partial_helper.rb +2 -2
  38. data/lib/aws_ec2/template/helper/script_helper.rb +53 -0
  39. data/lib/aws_ec2/template/helper/ssh_key_helper.rb +21 -0
  40. data/lib/aws_ec2/version.rb +1 -1
  41. data/spec/lib/cli_spec.rb +14 -0
  42. data/spec/spec_helper.rb +16 -6
  43. metadata +54 -14
  44. data/lib/aws_ec2/template_helper.rb +0 -18
  45. data/lib/aws_ec2/template_helper/core_helper.rb +0 -98
@@ -18,21 +18,50 @@ module AwsEc2
18
18
  class << self
19
19
  def dispatch(m, args, options, config)
20
20
  # Allow calling for help via:
21
- # aws_ec2 command help
22
- # aws_ec2 command -h
23
- # aws_ec2 command --help
24
- # aws_ec2 command -D
21
+ # aws-ec2 command help
22
+ # aws-ec2 command -h
23
+ # aws-ec2 command --help
24
+ # aws-ec2 command -D
25
25
  #
26
26
  # as well thor's normal way:
27
27
  #
28
- # aws_ec2 help command
28
+ # aws-ec2 help command
29
29
  help_flags = Thor::HELP_MAPPINGS + ["help"]
30
30
  if args.length > 1 && !(args & help_flags).empty?
31
31
  args -= help_flags
32
32
  args.insert(-2, "help")
33
33
  end
34
+
35
+ # aws-ec2 version
36
+ # aws-ec2 --version
37
+ # aws-ec2 -v
38
+ version_flags = ["--version", "-v"]
39
+ if args.length == 1 && !(args & version_flags).empty?
40
+ args = ["version"]
41
+ end
42
+
43
+ super
44
+ end
45
+
46
+ # Override command_help to include the description at the top of the
47
+ # long_description.
48
+ def command_help(shell, command_name)
49
+ meth = normalize_command_name(command_name)
50
+ command = all_commands[meth]
51
+ alter_command_description(command)
34
52
  super
35
53
  end
54
+
55
+ def alter_command_description(command)
56
+ return unless command
57
+ long_desc = if command.long_description
58
+ "#{command.description}\n\n#{command.long_description}"
59
+ else
60
+ command.description
61
+ end
62
+ command.long_description = long_desc
63
+ end
64
+ private :alter_command_description
36
65
  end
37
66
  end
38
67
  end
@@ -0,0 +1,161 @@
1
+ =begin
2
+ Code Explanation:
3
+
4
+ There are 3 types of things to auto-complete:
5
+
6
+ 1. command: the command itself
7
+ 2. parameters: command parameters.
8
+ 3. options: command options
9
+
10
+ Here's an example:
11
+
12
+ mycli hello name --from me
13
+
14
+ * command: hello
15
+ * parameters: name
16
+ * option: --from
17
+
18
+ When command parameters are done processing, the remaining completion words will be options. We can tell that the command params are completed based on the method arity.
19
+
20
+ ## Arity
21
+
22
+ For example, say you had a method for a CLI command with the following form:
23
+
24
+ ufo scale service count --cluster development
25
+
26
+ It's equivalent ruby method:
27
+
28
+ scale(service, count) = has an arity of 2
29
+
30
+ So typing:
31
+
32
+ ufo scale service count [TAB] # there are 3 parameters including the "scale" command according to Thor's CLI processing.
33
+
34
+ So the completion should only show options, something like this:
35
+
36
+ --noop --verbose --cluster
37
+
38
+ ## Splat Arguments
39
+
40
+ When the ruby method has a splat argument, it's arity is negative. Here are some example methods and their arities.
41
+
42
+ ship(service) = 1
43
+ scale(service, count) = 2
44
+ ships(*services) = -1
45
+ foo(example, *rest) = -2
46
+
47
+ Fortunately, negative and positive arity values are processed the same way. So we take simply take the absolute value of the arity and process it the same.
48
+
49
+ Here are some test cases, hit TAB after typing the command:
50
+
51
+ aws-ec2 completion
52
+ aws-ec2 completion hello
53
+ aws-ec2 completion hello name
54
+ aws-ec2 completion hello name --
55
+ aws-ec2 completion hello name --noop
56
+
57
+ aws-ec2 completion
58
+ aws-ec2 completion sub:goodbye
59
+ aws-ec2 completion sub:goodbye name
60
+
61
+ ## Subcommands and Thor::Group Registered Commands
62
+
63
+ Sometimes the commands are not simple thor commands but are subcommands or Thor::Group commands. A good specific example is the ufo tool.
64
+
65
+ * regular command: ufo ship
66
+ * subcommand: ufo docker
67
+ * Thor::Group command: ufo init
68
+
69
+ Auto-completion accounts for each of these type of commands.
70
+ =end
71
+ module AwsEc2
72
+ class Completer
73
+ autoload :Script, 'aws_ec2/completer/script'
74
+
75
+ def initialize(command_class, *params)
76
+ @params = params
77
+ @current_command = @params[0]
78
+ @command_class = command_class # CLI initiall
79
+ end
80
+
81
+ def run
82
+ if subcommand?(@current_command)
83
+ subcommand_class = @command_class.subcommand_classes[@current_command]
84
+ @params.shift # destructive
85
+ Completer.new(subcommand_class, *@params).run # recursively use subcommand
86
+ return
87
+ end
88
+
89
+ # full command has been found!
90
+ unless found?(@current_command)
91
+ puts all_commands
92
+ return
93
+ end
94
+
95
+ # will only get to here if command aws found (above)
96
+ arity = @command_class.instance_method(@current_command).arity.abs
97
+ if @params.size > arity or thor_group_command?
98
+ puts options_completion
99
+ else
100
+ puts params_completion
101
+ end
102
+ end
103
+
104
+ def subcommand?(command)
105
+ @command_class.subcommands.include?(command)
106
+ end
107
+
108
+ # hacky way to detect that command is a registered Thor::Group command
109
+ def thor_group_command?
110
+ command_params(raw=true) == [[:rest, :args]]
111
+ end
112
+
113
+ def found?(command)
114
+ public_methods = @command_class.public_instance_methods(false)
115
+ command && public_methods.include?(command.to_sym)
116
+ end
117
+
118
+ # all top-level commands
119
+ def all_commands
120
+ commands = @command_class.all_commands.reject do |k,v|
121
+ v.is_a?(Thor::HiddenCommand)
122
+ end
123
+ commands.keys
124
+ end
125
+
126
+ def command_params(raw=false)
127
+ params = @command_class.instance_method(@current_command).parameters
128
+ # Example:
129
+ # >> Sub.instance_method(:goodbye).parameters
130
+ # => [[:req, :name]]
131
+ # >>
132
+ raw ? params : params.map!(&:last)
133
+ end
134
+
135
+ def params_completion
136
+ offset = @params.size - 1
137
+ offset_params = command_params[offset..-1]
138
+ command_params[offset..-1].first
139
+ end
140
+
141
+ def options_completion
142
+ used = ARGV.select { |a| a.include?('--') } # so we can remove used options
143
+
144
+ method_options = @command_class.all_commands[@current_command].options.keys
145
+ class_options = @command_class.class_options.keys
146
+
147
+ all_options = method_options + class_options + ['help']
148
+
149
+ all_options.map! { |o| "--#{o.to_s.gsub('_','-')}" }
150
+ filtered_options = all_options - used
151
+ filtered_options.uniq
152
+ end
153
+
154
+ # Useful for debugging. Using puts messes up completion.
155
+ def log(msg)
156
+ File.open("/tmp/complete.log", "a") do |file|
157
+ file.puts(msg)
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,6 @@
1
+ class AwsEc2::Completer::Script
2
+ def self.generate
3
+ bash_script = File.expand_path("script.sh", File.dirname(__FILE__))
4
+ puts "source #{bash_script}"
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ _aws-ec2() {
2
+ COMPREPLY=()
3
+ local word="${COMP_WORDS[COMP_CWORD]}"
4
+ local words=("${COMP_WORDS[@]}")
5
+ unset words[0]
6
+ local completion=$(aws-ec2 completion ${words[@]})
7
+ COMPREPLY=( $(compgen -W "$completion" -- "$word") )
8
+ }
9
+
10
+ complete -F _aws-ec2 aws-ec2
@@ -7,8 +7,10 @@ module AwsEc2
7
7
  @path = options[:path] || "#{AwsEc2.root}/config/#{AwsEc2.env}.yml"
8
8
  end
9
9
 
10
- def settings
11
- YAML.load_file(@path)
10
+ @@data = nil
11
+ def data
12
+ return @@data if @@data
13
+ @@data = YAML.load_file(@path)
12
14
  rescue Errno::ENOENT => e
13
15
  puts e.message
14
16
  puts "The #{@path} does not exist. Please double check that it exists."
data/lib/aws_ec2/core.rb CHANGED
@@ -4,7 +4,11 @@ module AwsEc2
4
4
  module Core
5
5
  @@config = nil
6
6
  def config
7
- @@config ||= Config.new.settings
7
+ @@config ||= Config.new.data
8
+ end
9
+
10
+ def settings
11
+ Setting.new.data
8
12
  end
9
13
 
10
14
  def env
@@ -10,22 +10,25 @@ module AwsEc2
10
10
  include ErrorMessages
11
11
 
12
12
  def run
13
- puts "Creating EC2 instance #{@options[:name]}..."
13
+ Profile.new(@options).check!
14
+
15
+ puts "Creating EC2 instance #{@name}..."
14
16
  display_info
17
+
18
+ Hook.run(:before_run_instances, @options)
19
+ sync_scripts_to_s3
20
+
15
21
  if @options[:noop]
16
22
  puts "NOOP mode enabled. EC2 instance not created."
17
23
  return
18
24
  end
19
-
20
- Hook.run(:before_run_instances, @options)
21
- sync_scripts_to_s3
22
25
  run_instances(params)
23
- puts "EC2 instance #{@options[:name]} created! 🎉"
26
+ puts "EC2 instance #{@name} created! 🎉"
24
27
  puts "Visit https://console.aws.amazon.com/ec2/home to check on the status"
25
28
  end
26
29
 
27
30
  def run_instances(params)
28
- resp = ec2.run_instances(params)
31
+ ec2.run_instances(params)
29
32
  rescue Aws::EC2::Errors::ServiceError => e
30
33
  handle_ec2_service_error!(e)
31
34
  end
@@ -35,8 +38,8 @@ module AwsEc2
35
38
  #
36
39
  # scripts_s3_bucket: my-bucket
37
40
  def sync_scripts_to_s3
38
- if AwsEc2.config["scripts_s3_bucket"]
39
- Script::Upload.new(@options).upload
41
+ if AwsEc2.settings["s3_folder"]
42
+ Script::Upload.new(@options).run
40
43
  end
41
44
  end
42
45
 
@@ -20,7 +20,7 @@ EOL
20
20
  # Aws::EC2::Errors::InvalidParameterCombination => invalid_parameter_combination!
21
21
  def map_exception_to_method(exception)
22
22
  class_name = File.basename(exception.class.to_s).sub(/.*::/,'')
23
- method_name = class_name.underscore
23
+ class_name.underscore # method_name
24
24
  end
25
25
 
26
26
  def print_error_message(exception, message)
@@ -1,9 +1,5 @@
1
1
  class AwsEc2::Create
2
- class Params
3
- def initialize(options)
4
- @options = options
5
- end
6
-
2
+ class Params < AwsEc2::Base
7
3
  # deep_symbolize_keys is ran at the very end only.
8
4
  # up until that point we're dealing with String keys.
9
5
  def generate
@@ -52,7 +48,7 @@ class AwsEc2::Create
52
48
  tags = spec["tags"] || []
53
49
 
54
50
  unless tags.map { |t| t["key"] }.include?("Name")
55
- tags << { "key" => "Name", "value" => @options[:name] }
51
+ tags << { "key" => "Name", "value" => @name }
56
52
  end
57
53
 
58
54
  specs = specs.map do |s|
@@ -0,0 +1,22 @@
1
+ Example:
2
+
3
+ aws-ec2 completion
4
+
5
+ Prints words for TAB auto-completion.
6
+
7
+ Examples:
8
+
9
+ aws-ec2 completion
10
+ aws-ec2 completion hello
11
+ aws-ec2 completion hello name
12
+
13
+ To enable, TAB auto-completion add the following to your profile:
14
+
15
+ eval $(aws-ec2 completion_script)
16
+
17
+ Auto-completion example usage:
18
+
19
+ aws-ec2 [TAB]
20
+ aws-ec2 hello [TAB]
21
+ aws-ec2 hello name [TAB]
22
+ aws-ec2 hello name --[TAB]
@@ -0,0 +1,3 @@
1
+ To use, add the following to your `~/.bashrc` or `~/.profile`
2
+
3
+ eval $(aws-ec2 completion_script)
@@ -1,48 +1,55 @@
1
1
  module AwsEc2
2
- class Profile
3
- include TemplateHelper
4
-
5
- def initialize(options)
6
- @options = options
7
- end
2
+ class Profile < Base
3
+ include AwsEc2::Template
8
4
 
9
5
  def load
10
6
  return @profile_params if @profile_params
11
7
 
12
8
  check!
13
9
 
14
- @profile_params = load_profile(profile_file)
10
+ file = profile_file(profile_name)
11
+ @profile_params = load_profile(file)
15
12
  end
16
13
 
17
14
  def check!
18
- return if File.exist?(profile_file)
15
+ file = profile_file(profile_name)
16
+ return if File.exist?(file)
19
17
 
20
- puts "Unable to find a #{profile_file.colorize(:green)} profile file."
21
- puts "Please double check that it exists or that you specified the right profile."
22
- exit # EXIT HERE
18
+ puts "Unable to find a #{file.colorize(:green)} profile file."
19
+ puts "Please double check that it exists or that you specified the right profile.".colorize(:red)
20
+ exit 1
23
21
  end
24
22
 
25
23
  def load_profile(file)
26
24
  return {} unless File.exist?(file)
27
25
 
28
26
  puts "Using profile: #{file}".colorize(:green)
29
- data = YAML.load(erb_result(file))
27
+ text = RenderMePretty.result(file, context: context)
28
+ data = YAML.load(text)
30
29
  data ? data : {} # in case the file is empty
31
30
  data.has_key?("run_instances") ? data["run_instances"] : data
32
31
  end
33
32
 
34
- def profile_file
35
- "#{AwsEc2.root}/profiles/#{profile_name}.yml"
36
- end
37
-
33
+ # Determines a valid profile_name. Falls back to default
38
34
  def profile_name
39
35
  # allow user to specify the path also
40
36
  if @options[:profile] && File.exist?(@options[:profile])
41
- profile = File.basename(@options[:profile], '.yml')
37
+ filename_profile = File.basename(@options[:profile], '.yml')
42
38
  end
43
39
 
44
- # conventional profile is the name of the ec2 instance
45
- profile || @options[:profile] || "default"
40
+ name = derandomize(@name)
41
+ if File.exist?(profile_file(name))
42
+ name_profile = name
43
+ end
44
+
45
+ filename_profile ||
46
+ @options[:profile] ||
47
+ name_profile || # conventional profile is the name of the ec2 instance
48
+ "default"
49
+ end
50
+
51
+ def profile_file(name)
52
+ "#{AwsEc2.root}/profiles/#{name}.yml"
46
53
  end
47
54
  end
48
55
  end
@@ -1,6 +1,7 @@
1
1
  module AwsEc2
2
2
  class Script
3
3
  autoload :Compile, "aws_ec2/script/compile"
4
+ autoload :Compress, "aws_ec2/script/compress"
4
5
  autoload :Upload, "aws_ec2/script/upload"
5
6
 
6
7
  def initialize(options={})