skytap-yf 0.2.3

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.
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