skytap-yf 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/data/.DS_Store ADDED
Binary file
@@ -0,0 +1,11 @@
1
+ module Skytap
2
+ module ApiSchema
3
+ SCHEMA_FILE = File.join(File.dirname(__FILE__), '..', '..', 'api_schema.yaml')
4
+ def get
5
+ return @info unless @info.nil?
6
+
7
+ @info = YAML.load_file(SCHEMA_FILE)
8
+ end
9
+ module_function :get
10
+ end
11
+ end
@@ -0,0 +1,145 @@
1
+ require 'skytap/skytaprc'
2
+
3
+ module Skytap
4
+ class CommandLine
5
+ ALLOWED_GLOBAL_OPTIONS = {
6
+ # Flags
7
+ :'base-url' => { :default => 'https://cloud.skytap.com' },
8
+ :'username' => { :desc => 'Skytap username' },
9
+ :'api-token' => { :desc => 'Skytap API token, found on "My Account" page', :hide_default_value => true },
10
+ :'http-format' => { :default => 'json', :in => ['json', 'xml'], :desc => 'HTTP request and response format' },
11
+ :'log-level' => { :default => 'info', :in => ['quiet', 'info', 'verbose'], :desc => 'Output verbosity' },
12
+ :'params-file' => { :desc => 'File whose contents to send as request params, read according to http-format' },
13
+ :'param' => { :desc => 'Request parameters in form --param=key1:value1 --param=key2:value2', :default => [], :multiple => true },
14
+
15
+ # Switches
16
+ :'help' => { :desc => 'Show help message for command', :switch => true },
17
+ :'version' => { :desc => 'Show CLI version', :switch => true },
18
+ :'colorize' => { :default => true, :desc => 'Colorize output?', :switch => true, :negatable => true },
19
+ :'verify-certs' => { :default => true, :desc => 'Verify HTTPS certificates?', :switch => true, :negatable => true },
20
+ :'ask' => { :default => true, :desc => 'Ask for params interactively?', :switch => true, :negatable => true }
21
+ }
22
+
23
+
24
+ def self.parse(argv=ARGV)
25
+ global = read_global_options
26
+ command_opts = {}
27
+ args = []
28
+ argv.each do |arg|
29
+ if arg =~ /^[^-]/
30
+ args << arg
31
+ # Switches
32
+ elsif arg =~ /^\-\-(no-)?([^=]+)$/
33
+ negated, name = $1, $2.to_sym
34
+ val = !negated
35
+ if global.has_key?(name)
36
+ global[name] = val
37
+ else
38
+ command_opts[name] = val
39
+ end
40
+ # Flags
41
+ elsif arg =~ /^\-\-(.+?)=(.+)$/
42
+ name, val = $1.to_sym, $2
43
+
44
+ if global.has_key?(name)
45
+ multiple = ALLOWED_GLOBAL_OPTIONS[name].try(:[], :multiple)
46
+ if multiple
47
+ if global[name].present?
48
+ global[name] << val
49
+ else
50
+ global[name] = [val]
51
+ end
52
+ else
53
+ global[name] = val
54
+ end
55
+ else
56
+ command_opts[name] = val
57
+ end
58
+ elsif arg =~ /^\-([A-Za-z0-9]+)$/
59
+ raise Skytap::Error.new 'Single-dash flags not supported. Use --flag form.'
60
+ else
61
+ raise "Unrecognized command-line arg: #{arg.inspect}"
62
+ end
63
+ end
64
+
65
+ # To simplify client handling, treat a first arg of "help" as --help.
66
+ if args.first == 'help'
67
+ args.shift
68
+ global[:help] = true
69
+ end
70
+
71
+ [args, global, command_opts]
72
+ end
73
+
74
+ def self.read_global_options
75
+ global_options.inject({}) do |acc, (k, option)|
76
+ acc[k] = option.val
77
+ acc
78
+ end
79
+ end
80
+
81
+ def self.global_options
82
+ return @global_options if @global_options
83
+
84
+ rc = SkytapRC.load
85
+ @global_options = ALLOWED_GLOBAL_OPTIONS.inject({}) do |acc, (k, opts)|
86
+ opts[:default] = rc[k] if rc.has_key?(k)
87
+ acc[k] = Option.new(k, opts)
88
+ acc
89
+ end
90
+ end
91
+ end
92
+
93
+ class Option
94
+ attr_reader :name, :desc, :default
95
+
96
+ def initialize(name, options = {})
97
+ @name = name.to_s
98
+ @options = options.symbolize_keys
99
+ @desc = @options[:desc]
100
+ @default = @options[:default]
101
+ end
102
+
103
+ def show_default?
104
+ !@options[:hide_default_value]
105
+ end
106
+
107
+ def switch?
108
+ @options[:switch]
109
+ end
110
+
111
+ def negatable?
112
+ switch? && @options[:negatable]
113
+ end
114
+
115
+ def val
116
+ if @set
117
+ @val
118
+ else
119
+ default
120
+ end
121
+ end
122
+
123
+ def val=(v)
124
+ @set = true
125
+ if switch?
126
+ @val = !!v
127
+ else
128
+ @val = v
129
+ end
130
+ end
131
+
132
+ def signature
133
+ if switch?
134
+ negation = '[no-]' if negatable?
135
+ "--#{negation}#{name}"
136
+ else
137
+ "--#{name}=#{name.upcase}"
138
+ end
139
+ end
140
+
141
+ def choices
142
+ @options[:in]
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,294 @@
1
+ require 'skytap/commands/help'
2
+
3
+ module Skytap
4
+ module Commands
5
+ class Base
6
+ include Help
7
+
8
+ class_attribute :subcommands, :parent, :ask_interactively
9
+ self.subcommands = []
10
+
11
+ attr_accessor :error
12
+ attr_reader :args, :global_options, :command_options, :logger, :invoker
13
+
14
+ def initialize(logger, args, global_options, command_options, programmatic_context=false, &invoker)
15
+ @logger = logger
16
+ @args = args
17
+ @global_options = global_options
18
+ @command_options = command_options || {}
19
+ @programmatic_context = programmatic_context
20
+ @invoker = invoker
21
+ end
22
+
23
+ def programmatic?
24
+ !!@programmatic_context
25
+ end
26
+
27
+ class << self
28
+ # A factory that makes a command class from the given resource
29
+ def make_from(resource, spec={})
30
+ spec ||= {}
31
+ Class.new(Base).tap do |klass|
32
+ klass.instance_eval do
33
+ self.container = true
34
+ self.subcommands = [Index, Show, Create, Update, Destroy].reject do |sub|
35
+ spec['skip_actions'].try(:include?, sub.command_name)
36
+ end.collect do |sub|
37
+ sub_spec = spec['actions'].try(:[], sub.command_name)
38
+ sub.make_for(self, sub_spec)
39
+ end
40
+ self.spec = spec
41
+ alias_method :run!, :help!
42
+ end
43
+
44
+ Skytap::Commands.const_set(resource.classify, klass)
45
+ end
46
+ end
47
+
48
+ def command_name
49
+ name.split('::').last.underscore
50
+ end
51
+ end
52
+
53
+ def invoke
54
+ if matching_subcommand
55
+ matching_subcommand.new(logger, args[1..-1], global_options, command_options, @programmatic_context, &invoker).invoke
56
+ elsif help?
57
+ help!
58
+ elsif version?
59
+ logger.puts "skytap version #{Skytap::VERSION}"
60
+ exit
61
+ elsif container && args.present?
62
+ self.error = "Subcommand '#{args.first}' not found"
63
+ help!
64
+ exit(false)
65
+ else
66
+ validate_args
67
+ validate_command_options
68
+ run!
69
+ end
70
+ end
71
+
72
+ def expected_args
73
+ ActiveSupport::OrderedHash.new
74
+ end
75
+
76
+ # Expected command-specific options (not global options)
77
+ def expected_options
78
+ {}
79
+ end
80
+
81
+ # Returns an ID string from an URL, path or ID
82
+ def find_id(arg)
83
+ arg.to_s =~ /(.*\/)?(.+)$/ && $2
84
+ end
85
+
86
+ def solicit_user_input?
87
+ global_options[:ask] && !programmatic? && $stdin.tty?
88
+ end
89
+
90
+
91
+ def ask_param
92
+ name = ask('Name: '.bright)
93
+ value = ask('Value: '.bright)
94
+ [name, value]
95
+ end
96
+
97
+ def ask(question, choices={})
98
+ return unless solicit_user_input?
99
+ default = choices.delete(:default)
100
+ raise "Default choice must be in the choices hash" if default && !choices.has_key?(default)
101
+ if choices.present?
102
+ letters = " [#{choices.collect{|l, _| l == default ? l.upcase : l.downcase}.join}]"
103
+ end
104
+
105
+ loop do
106
+ line = "#{question}#{letters} ".color(:yellow)
107
+ $stdout.print line
108
+ $stdout.flush
109
+
110
+ answer = $stdin.gets.try(&:strip)
111
+ if choices.blank?
112
+ break answer
113
+ elsif answer.blank? && default
114
+ break choices[default]
115
+ elsif choices.has_key?(answer.downcase)
116
+ break choices[answer.downcase]
117
+ end
118
+ end
119
+ end
120
+
121
+ def composed_params
122
+ noninteractive_params.merge(interactive_params)
123
+ end
124
+
125
+ def file_params
126
+ file = global_options[:'params-file'] or return {}
127
+ file = File.expand_path(file)
128
+ raise 'Params file not found' unless File.exist?(file)
129
+ case File.basename(file)
130
+ when /\.json$/i
131
+ format = 'json'
132
+ when /\.xml$/i
133
+ format = 'xml'
134
+ else
135
+ format = global_options[:'http-format']
136
+ end
137
+
138
+ body = File.open(file, &:read)
139
+ deserialized = case format
140
+ when 'json'
141
+ JSON.load(body)
142
+ when 'xml'
143
+ parsed = Hash.from_xml(body)
144
+ # Strip out the root name.
145
+ if parsed.is_a?(Hash)
146
+ parsed.values.first
147
+ else
148
+ parsed
149
+ end
150
+ else
151
+ raise Skytap::Error.new("Unknown format: #{format.inspect}")
152
+ end
153
+ end
154
+
155
+ def command_line_params
156
+ param = global_options[:param]
157
+ return {} if param.blank?
158
+ case param
159
+ when Hash
160
+ param
161
+ when String
162
+ #TODO:NLA This will blow up if param has a weird form.
163
+ Hash[*param.split(':', 2)]
164
+ when Array
165
+ split = param.collect {|v| v.split(':', 2)}.flatten
166
+ Hash[*split]
167
+ else
168
+ param
169
+ end
170
+ end
171
+
172
+ def noninteractive_params
173
+ @noninteractive_params ||= file_params.merge(command_line_params)
174
+ end
175
+
176
+ def interactive_params
177
+ if !solicit_user_input? ||
178
+ !ask_interactively ||
179
+ parameters.blank? ||
180
+ (required_parameters.empty? && noninteractive_params.present?) ||
181
+ (required_parameters.present? && (required_parameters - noninteractive_params.keys).empty?)
182
+
183
+ return {}
184
+ end
185
+
186
+ param_info = Templates::Help.new('command' => self).parameters_table
187
+ logger.info param_info << "\n"
188
+
189
+ params = {}
190
+ loop do
191
+ answer = ask('include a param?', ActiveSupport::OrderedHash['y', 'y', 'n', 'n', 'q', 'q', '?', '?', :default, 'n'])
192
+ case answer.downcase
193
+ when 'y'
194
+ k, v = ask_param
195
+ params[k] = v
196
+ when 'n'
197
+ break
198
+ when 'q'
199
+ logger.info 'Bye.'
200
+ exit
201
+ else
202
+ logger.puts <<-"EOF"
203
+ y - include an HTTP request parameter
204
+ n - stop adding parameters (default answer)
205
+ q - quit
206
+ ? - show this message
207
+
208
+ EOF
209
+ end
210
+ end
211
+
212
+ params
213
+ end
214
+
215
+ def username
216
+ return @_username if @_username
217
+ username = global_options[:username]
218
+ if username.blank?
219
+ if solicit_user_input?
220
+ username = ask('Skytap username:') while username.blank?
221
+ else
222
+ raise Skytap::Error.new('Must provide --username')
223
+ end
224
+ end
225
+ @_username = username
226
+ end
227
+
228
+ def api_token
229
+ return @_api_token if @_api_token
230
+ api_token = global_options[:'api-token']
231
+ if api_token.blank?
232
+ if solicit_user_input?
233
+ api_token = ask('API token:') while api_token.blank?
234
+ else
235
+ raise Skytap::Error.new('Must provide --api-token')
236
+ end
237
+ end
238
+ @_api_token = api_token
239
+ end
240
+
241
+ private
242
+
243
+
244
+ def run!
245
+ raise NotImplementedError.new('Must override #run!')
246
+ end
247
+
248
+ def required_parameters
249
+ @required_parameters ||= parameters.collect{|p| p.keys.first if p.values.first['required']}.compact
250
+ end
251
+
252
+ #TODO:NLA Need to track somewhere that only the last expected arg may end with *
253
+ def validate_args
254
+ unlimited = expected_args.keys.last.try(:end_with?, '*')
255
+ if unlimited
256
+ min = expected_args.keys.length
257
+ if args.length < expected_args.keys.length
258
+ self.error = "Expected at least #{min} command line #{'argument'.pluralize(min)} but got #{args.length}."
259
+ help!
260
+ exit(false)
261
+ end
262
+ else
263
+ expected = expected_args.keys.length
264
+ unless args.length == expected
265
+ self.error = "Expected #{expected} command line #{'argument'.pluralize(expected)} but got #{args.length}."
266
+ help!
267
+ exit(false)
268
+ end
269
+ end
270
+ end
271
+
272
+ def validate_command_options
273
+ bad = command_options.inject([]) do |acc, (name, value)|
274
+ if !expected_options[name]
275
+ acc << "Unknown option '#{name}'."
276
+ elsif !expected_options[name][:flag_arg] && (value != true && value != false)
277
+ acc << "'#{name}' option is a switch and cannot be set to a value."
278
+ end
279
+ acc
280
+ end
281
+ if bad.present?
282
+ self.error = bad.join(' ')
283
+ help!
284
+ exit(false)
285
+ end
286
+ end
287
+
288
+ def matching_subcommand
289
+ next_command = args.first
290
+ subcommands.detect {|k| k.command_name == next_command}
291
+ end
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,82 @@
1
+ require 'skytap/templates'
2
+
3
+ module Skytap
4
+ module Commands
5
+ module Help
6
+
7
+ module ClassMethods
8
+ def banner_prefix
9
+ parent_prefix = parent.try(:banner_prefix)
10
+ "#{parent_prefix.try(:+, ' ')}#{self.command_name}"
11
+ end
12
+
13
+ def banner
14
+ b = "#{banner_prefix} - #{short_desc}"
15
+ if plugin
16
+ b.color(:cyan)
17
+ else
18
+ b
19
+ end
20
+ end
21
+
22
+ def description
23
+ spec['description'] || default_description
24
+ end
25
+
26
+ def default_description
27
+ nil
28
+ end
29
+
30
+ def short_desc
31
+ description.split("\n\n").first.split("\n").join(' ') if description
32
+ end
33
+
34
+ def subcommand_banners
35
+ subcommands.inject([]) do |acc, klass|
36
+ acc << klass.banner unless klass.container
37
+ acc.concat(klass.subcommand_banners)
38
+ acc
39
+ end
40
+ end
41
+ end
42
+
43
+ def synopsis
44
+ if self.container
45
+ command_name = self.class.command_name
46
+ "skytap #{command_name + ' ' if command_name}<subcommand> <options>"
47
+ else
48
+ "#{self.class.banner_prefix} #{expected_args.keys.collect(&:upcase).join(' ') << ' '}<options> - #{self.class.short_desc}"
49
+ end
50
+ end
51
+
52
+ def help?
53
+ !!global_options[:help]
54
+ end
55
+
56
+ def help!
57
+ puts Skytap::Templates::Help.render(self)
58
+ end
59
+
60
+ def version?
61
+ !!global_options[:version]
62
+ end
63
+
64
+ def parameters
65
+ spec['params']
66
+ end
67
+
68
+ def description
69
+ self.class.description
70
+ end
71
+
72
+ def self.included(base)
73
+ base.extend(ClassMethods)
74
+ # Indicates whether this class is only a container for subcommands.
75
+ base.class_attribute :container, :spec, :plugin
76
+ base.container = false
77
+ base.plugin = false
78
+ base.spec = {}
79
+ end
80
+ end
81
+ end
82
+ end