pe-razor-client 0.15.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +27 -0
  3. data/bin/razor +27 -2
  4. data/lib/razor/cli.rb +11 -2
  5. data/lib/razor/cli/command.rb +150 -0
  6. data/lib/razor/cli/document.rb +8 -4
  7. data/lib/razor/cli/format.rb +46 -22
  8. data/lib/razor/cli/navigate.rb +28 -156
  9. data/lib/razor/cli/parse.rb +49 -7
  10. data/lib/razor/cli/query.rb +69 -0
  11. data/lib/razor/cli/table_format.rb +41 -0
  12. data/lib/razor/cli/transforms.rb +25 -0
  13. data/lib/razor/cli/version.rb +1 -1
  14. data/lib/razor/cli/views.yaml +53 -2
  15. data/spec/cli/command_spec.rb +66 -0
  16. data/spec/cli/format_spec.rb +95 -5
  17. data/spec/cli/navigate_spec.rb +50 -6
  18. data/spec/cli/parse_spec.rb +42 -2
  19. data/spec/fixtures/vcr/Razor_CLI_Navigate/argument_formatting/should_allow_in_string.yml +233 -0
  20. data/spec/fixtures/vcr/Razor_CLI_Navigate/argument_formatting/should_allow_spaces.yml +281 -548
  21. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_--help_command_.yml +160 -37
  22. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_-h_command_.yml +160 -37
  23. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_--help_.yml +160 -37
  24. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_-h_.yml +160 -37
  25. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_help_.yml +160 -37
  26. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_help_command_.yml +160 -37
  27. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_authentication/should_preserve_that_across_navigation.yml +10 -10
  28. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_authentication/should_supply_that_to_the_API_service.yml +5 -5
  29. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_invalid_parameter/should_fail_with_bad_JSON.yml +71 -166
  30. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_invalid_parameter/should_fail_with_malformed_argument.yml +109 -59
  31. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_multiple_arguments_with_same_name/combining_as_an_array/should_merge_an_array_into_an_existing_array.yml +527 -1360
  32. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_multiple_arguments_with_same_name/combining_as_an_array/should_merge_the_arguments_as_an_array.yml +528 -1361
  33. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_multiple_arguments_with_same_name/combining_as_an_array/should_merge_the_arguments_into_an_existing_array.yml +528 -1361
  34. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_multiple_arguments_with_same_name/combining_as_an_object/should_construct_a_json_object.yml +80 -111
  35. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_multiple_arguments_with_same_name/combining_as_an_object/should_construct_a_json_object_with_unicode.yml +137 -123
  36. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_multiple_arguments_with_same_name/combining_as_an_object/should_fail_with_mixed_types_array_then_hash_.yml +71 -166
  37. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_multiple_arguments_with_same_name/combining_as_an_object/should_fail_with_mixed_types_hash_then_array_.yml +71 -102
  38. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_no_parameters/should_fail_with_bad_JSON.yml +98 -5
  39. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_append_limit.yml +69 -0
  40. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_append_start.yml +69 -0
  41. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_not_fail_when_query_returns_details_for_one_item.yml +313 -0
  42. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_store_query_without_query_parameters.yml +557 -0
  43. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_throw_an_error_if_the_query_parameter_is_not_in_the_API.yml +36 -0
  44. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_throw_an_error_if_the_query_parameter_is_not_in_the_API_from_a_single_item.yml +280 -0
  45. data/spec/fixtures/vcr/Razor_CLI_Parse/_new/_help/should_print_a_list_of_known_endpoints.yml +5 -5
  46. data/spec/spec_helper.rb +8 -4
  47. metadata +26 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 74733b4a8fe748bab977062365bd2d8ed264facc
4
- data.tar.gz: 52bdf2ad11d93cc78b64b55457fbc8726038a026
3
+ metadata.gz: e6c2760d424fcb90751a22b65217172feab26b6c
4
+ data.tar.gz: 51cca0cde398a43684ed9398eb709322874a817d
5
5
  SHA512:
6
- metadata.gz: a40b6adabd53ef8fbbdeb1403eb8374b9dd528cf8451ebc840bf768c79b457bd605ebab4959aacf08119f614bc39a39f133d9a553b6002dc6ad6e6e987d036ee
7
- data.tar.gz: 04f970bac6a6d7915869f6bf1f704072a86ddce68fe4042390ddc6cfe70192babbc4aff19aef6e8adfe89ed873586e41f7f69ea25e22d4c1b55981239a18078b
6
+ metadata.gz: 455603cb50328105b7fdf55e7e5454c01eab1d7f1043494f5619a98615cf9dd646c1d5ff3884a828e59a7ac569ad44af120e0e9e66332d37b6b4627471b18b5c
7
+ data.tar.gz: 26a4ba1316f72362e66501b9d7595b72886f4b1665dee5ef5a90d53235486f3574a209671e011e42fd88a027b3b9c76469fe3e409db2ba12b233f6865cc1b6dc
data/NEWS.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Razor Client Release Notes
2
2
 
3
+ ## Next - TBD
4
+
5
+ * NEW: RAZOR_CA_FILE environment variable allows TLS/SSL certificate
6
+ verification for requests.
7
+
8
+ ## 0.16.0 - 2015-01-05
9
+
10
+ * BUGFIX: Commands were not always including authentication
11
+ information in every request.
12
+ * IMPROVEMENT: Ruby version compatibility: Now works with Ruby < 1.9.2.
13
+ * NEW: Separate API and CLI help examples: There are now two formats for help
14
+ examples. The new CLI format shows help text as a standard razor-client
15
+ command. CLI is used by default. The API format is the same as before,
16
+ and will show examples in JSON format.
17
+ * NEW: The `events` collection is new and has a special client-side display.
18
+ * NEW: RAZOR_API and `razor -u $url` URLs need to be explicit about `http:`
19
+ and `https:`.
20
+ * NEW: Event queries can be limited (`razor events --limit 5`) and offset
21
+ (`razor events --start 5`). This also works for `razor nodes $name log`.
22
+ * IMPROVEMENT: Viewing all columns in a query is now possible via
23
+ `razor $collection_path --full` rather than `razor --full $collection_path`.
24
+ * NEW: razor-client now has an 'insecure' flag to ignore SSL verification
25
+ errors.
26
+ * IMPROVEMENT: Argument types were previously not very context-aware. Now,
27
+ for example, names can include the '=' character.
28
+ * BUGFIX: A reasonable error will be thrown if help is requested but does not exist.
29
+
3
30
  ## 0.15.1 - 2014-06-12
4
31
 
5
32
  Server version compatibility
data/bin/razor CHANGED
@@ -33,20 +33,45 @@ if parse.show_version?
33
33
  end
34
34
 
35
35
  if parse.show_help? and not parse.show_command_help?
36
- puts parse.help
37
- exit 0
36
+ output, exitcode = parse.help
37
+ puts output
38
+ exit exitcode
39
+ end
40
+
41
+ def unexpected_error(e)
42
+ die <<-ERROR
43
+ An unexpected error has occurred.
44
+
45
+ Backtrace:
46
+ #{e.backtrace.take(10).join('
47
+ ')}
48
+
49
+ Error: #{e}
50
+
51
+ Please inspect server logs for the cause, then report issue to:
52
+ https://tickets.puppetlabs.com/browse/RAZOR
53
+
54
+ ERROR
38
55
  end
39
56
 
40
57
  begin
41
58
  document = parse.navigate.get_document
42
59
  url = parse.navigate.last_url
43
60
  puts "From #{url}:\n\n#{format_document document, parse}\n\n"
61
+ rescue OptionParser::InvalidOption => e
62
+ # Occurs when invalid flags are passed in the navigation.
63
+ die e.message + "\nTry 'razor --help' for more information"
44
64
  rescue SocketError, Errno::ECONNREFUSED => e
45
65
  puts "Error: Could not connect to the server at #{parse.api_url}"
46
66
  puts " #{e}\n"
47
67
  die
68
+ rescue RestClient::SSLCertificateNotVerified
69
+ puts "Error: SSL certificate could not be verified against known CA certificates."
70
+ puts " To turn off verification, use the -k or --insecure option."
71
+ die
48
72
  rescue RestClient::Exception => e
49
73
  r = e.response
74
+ unexpected_error(e) if r.nil?
50
75
  puts "Error from doing #{r.args[:method].to_s.upcase} #{r.args[:url]}"
51
76
  puts e.message
52
77
  begin
data/lib/razor/cli.rb CHANGED
@@ -19,13 +19,19 @@ module Razor
19
19
  when :env
20
20
  super "URL '#{url}' in ENV variable RAZOR_API is not valid"
21
21
  when :opts
22
- super "URL '#{url}' provided by -U or --url is not valid"
22
+ super "URL '#{url}' provided by -u or --url is not valid"
23
23
  else
24
24
  super "URL '#{url}' is not valid"
25
25
  end
26
26
  end
27
27
  end
28
28
 
29
+ class InvalidCAFileError < Error
30
+ def initialize(path)
31
+ super "CA file '#{path}' in ENV variable RAZOR_CA_FILE does not exist"
32
+ end
33
+ end
34
+
29
35
  class VersionCompatibilityError < Error
30
36
  def initialize(reason)
31
37
  super "Server version is not compatible with client version: #{reason}"
@@ -39,6 +45,9 @@ require_relative 'cli/version'
39
45
  require_relative 'cli/navigate'
40
46
  require_relative 'cli/parse'
41
47
  require_relative 'cli/format'
48
+ require_relative 'cli/table_format'
42
49
  require_relative 'cli/document'
43
50
  require_relative 'cli/views'
44
- require_relative 'cli/transforms'
51
+ require_relative 'cli/transforms'
52
+ require_relative 'cli/query'
53
+ require_relative 'cli/command'
@@ -0,0 +1,150 @@
1
+ class Razor::CLI::Command
2
+ def initialize(parse, navigate, commands, segments)
3
+ @parse = parse
4
+ @navigate = navigate
5
+ @commands = commands
6
+ @segments = segments
7
+ end
8
+
9
+ def run
10
+ # @todo lutter 2013-08-16: None of this has any tests, and error
11
+ # handling is heinous at best
12
+ cmd, body = extract_command
13
+ # Ensure that we copy authentication data from our previous URL.
14
+ url = URI.parse(cmd["id"])
15
+ if @doc_resource
16
+ url = URI.parse(url.to_s)
17
+ end
18
+
19
+ if @parse.show_command_help?
20
+ @navigate.json_get(url)
21
+ else
22
+ if body.empty?
23
+ raise Razor::CLI::Error,
24
+ "No arguments for command (did you forget --json ?)"
25
+ end
26
+ result = @navigate.json_post(url, body)
27
+ # Get actual object from the id.
28
+ result = result.merge(@navigate.json_get(URI.parse(result['id']))) if result['id']
29
+ result
30
+ end
31
+ end
32
+
33
+ def command(name)
34
+ @command ||= @commands.find { |coll| coll["name"] == name }
35
+ end
36
+
37
+ def extract_command
38
+ cmd = command(@segments.shift)
39
+ @cmd_url = URI.parse(cmd['id'])
40
+ @cmd_schema = cmd_schema(@cmd_url)
41
+ body = {}
42
+ until @segments.empty?
43
+ argument = @segments.shift
44
+ if argument =~ /\A--([a-z-]+)(=(.+))?\Z/
45
+ # `--arg=value` or `--arg value`
46
+ arg, value = [$1, $3]
47
+ value = @segments.shift if value.nil? && @segments[0] !~ /^--/
48
+ arg = self.class.resolve_alias(arg, @cmd_schema)
49
+ body[arg] = self.class.convert_arg(arg, value, body[arg], @cmd_schema)
50
+ else
51
+ raise ArgumentError, "Unexpected argument #{argument}"
52
+ end
53
+ end
54
+
55
+ begin
56
+ body = MultiJson::load(File::read(body["json"])) if body["json"]
57
+ rescue MultiJson::LoadError
58
+ raise Razor::CLI::Error, "File #{body["json"]} is not valid JSON"
59
+ rescue Errno::ENOENT
60
+ raise Razor::CLI::Error, "File #{body["json"]} not found"
61
+ rescue Errno::EACCES
62
+ raise Razor::CLI::Error,
63
+ "Permission to read file #{body["json"]} denied"
64
+ end
65
+ [cmd, body]
66
+ end
67
+
68
+ def cmd_schema(cmd_url)
69
+ begin
70
+ @navigate.json_get(cmd_url)['schema']
71
+ rescue RestClient::ResourceNotFound => _
72
+ raise VersionCompatibilityError, 'Server must supply the expected datatypes for command arguments; use `--json` or upgrade razor-server'
73
+ end
74
+ end
75
+
76
+ def self.arg_type(arg_name, cmd_schema)
77
+ # Short-circuit to allow this as a work-around for backwards compatibility.
78
+ return nil if arg_name == 'json'
79
+ return nil unless cmd_schema.is_a?(Hash)
80
+ return cmd_schema[arg_name]['type'] if cmd_schema.has_key?(arg_name)
81
+ return nil
82
+ end
83
+
84
+ # `cmd_name`: The name of the command being executed.
85
+ # `arg_name`: The name of the argument being formatted.
86
+ # `value`: The original value provided by the user.
87
+ # `existing_value`: The value already assigned to this variable
88
+ # by previous calls to this method. The new `value` will be
89
+ # concatenated to an array or hash if an array/hash is
90
+ # accepted by the command for the given argument.
91
+ def self.convert_arg(arg_name, value, existing_value, cmd_schema)
92
+ value = nil if value == "null"
93
+
94
+ argument_type = arg_type(arg_name, cmd_schema)
95
+
96
+ # This might be helpful, since there's no other method for debug-level logging on the client.
97
+ puts "Formatting argument #{arg_name} with value #{value} as #{argument_type}\n" if @parse && @parse.dump_response?
98
+
99
+ case argument_type
100
+ when "array"
101
+ existing_value ||= []
102
+ begin
103
+ existing_value + Array(MultiJson::load(value))
104
+ rescue MultiJson::LoadError => _
105
+ existing_value + Array(value)
106
+ end
107
+ when "object"
108
+ existing_value ||= {}
109
+ begin
110
+ if value =~ /\A(.+?)=(.+)?\z/
111
+ # `--arg name=value`
112
+ existing_value.merge($1 => $2)
113
+ else
114
+ MultiJson::load(value).tap do |value|
115
+ value.is_a?(Hash) or raise ArgumentError, "Invalid object for argument '#{arg_name}'"
116
+ existing_value.merge(value)
117
+ end
118
+ end
119
+ rescue MultiJson::LoadError => error
120
+ raise ArgumentError, "Invalid object for argument '#{arg_name}': #{error.message}"
121
+ end
122
+ when "boolean"
123
+ ["true", nil].include?(value)
124
+ when "number"
125
+ begin
126
+ Integer(value)
127
+ rescue ArgumentError
128
+ raise ArgumentError, "Invalid integer for argument '#{arg_name}': #{value}"
129
+ end
130
+ when "null"
131
+ raise ArgumentError, "Expected nothing for argument '#{arg_name}', but was: '#{value}'" unless value.nil?
132
+ nil
133
+ when "string", nil # `nil` for 'might be an alias, send as-is'
134
+ value
135
+ else
136
+ raise Razor::CLI::Error, "Unexpected datatype '#{argument_type}' for argument #{arg_name}"
137
+ end
138
+ end
139
+
140
+ def self.resolve_alias(arg_name, cmd_schema)
141
+ return arg_name if cmd_schema[arg_name]
142
+ cmd_schema.find do |other_attr, metadata|
143
+ if metadata && metadata.has_key?('aliases')
144
+ return other_attr if metadata['aliases'].find {|aliaz| aliaz == arg_name}
145
+ end
146
+ end
147
+ # No results; return the same name to generate a reasonable error message.
148
+ arg_name
149
+ end
150
+ end
@@ -1,6 +1,7 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module Razor::CLI
4
+ class HideColumnError < RuntimeError; end
4
5
  class Document
5
6
  extend Forwardable
6
7
  attr_reader 'spec', 'items', 'format_view', 'original_items'
@@ -42,10 +43,13 @@ module Razor::CLI
42
43
  item_spec = (item_format_spec[1] or {})
43
44
  item_label = item_format_spec[0]
44
45
  item_column = (item_spec['+column'] or item_label)
45
- value = Razor::CLI::Views.transform(item[item_column], item_spec['+format'])
46
-
47
- [item_label, value]
48
- end
46
+ begin
47
+ value = Razor::CLI::Views.transform(item[item_column], item_spec['+format'])
48
+ [item_label, value]
49
+ rescue Razor::CLI::HideColumnError
50
+ nil
51
+ end
52
+ end.reject {|k| k.nil? }
49
53
  ].tap do |hash|
50
54
  # Re-add the special 'command' key and value if the key isn't already there.
51
55
  hash['command'] = @command if @command and not hash.has_key?('command')
@@ -1,5 +1,5 @@
1
1
  require 'forwardable'
2
- require 'terminal-table'
2
+ require 'command_line_reporter'
3
3
 
4
4
  module Razor::CLI
5
5
  module Format
@@ -20,11 +20,11 @@ module Razor::CLI
20
20
 
21
21
  def format_document(doc, parse = nil)
22
22
  format = parse && parse.format
23
- arguments = parse && parse.args
23
+ arguments = parse && parse.stripped_args
24
24
  doc = Razor::CLI::Document.new(doc, format)
25
25
 
26
26
  return "There are no items for this query." if doc.items.empty?
27
- return format_objects(doc.items).chomp if parse && parse.show_command_help?
27
+ return format_command_help(doc, parse.show_api_help?) if parse && parse.show_command_help?
28
28
 
29
29
  case (doc.format_view['+layout'] or 'list')
30
30
  when 'list'
@@ -43,25 +43,8 @@ module Razor::CLI
43
43
  private
44
44
  def get_table(doc, formatting)
45
45
  # Use the formatting if it exists, otherwise build from the data.
46
- headings = (formatting['+show'] and formatting['+show'].keys or get_headers(doc))
47
- Terminal::Table.new do |table|
48
- table.rows = doc.map do |page|
49
- headings.map do |heading|
50
- page[heading]
51
- end
52
- end
53
- table.headings = headings
54
- end.to_s
55
- end
56
-
57
- def get_headers(doc)
58
- [].tap do |headers|
59
- doc.map do |page|
60
- page.map do |item|
61
- headers << item[0] unless headers.include?(item[0])
62
- end
63
- end
64
- end
46
+ column_overrides = formatting['+show'] && formatting['+show'].keys
47
+ Razor::CLI::TableFormat.new.run(doc, column_overrides)
65
48
  end
66
49
 
67
50
  # We assume that all collections are homogenous
@@ -79,6 +62,47 @@ module Razor::CLI
79
62
  end
80
63
  end
81
64
 
65
+ def format_command_help(doc, show_api_help)
66
+ item = doc.items.first
67
+ raise Razor::CLI::Error, 'Could not find help for that entry' unless item.has_key?('help')
68
+ if show_api_help and (item['help'].has_key?('summary') or item['help'].has_key?('description'))
69
+ format_composed_help(item['help']).chomp
70
+ elsif item['help'].has_key?('summary') or item['help'].has_key?('description')
71
+ format_composed_help(item['help'], item['help']['examples']['cli']).chomp
72
+ else
73
+ format_full_help(item['help']).chomp
74
+ end
75
+ end
76
+
77
+ def format_composed_help(object, examples = object['examples']['api'])
78
+ ret = ''
79
+ ret = ret + <<-SYNOPSIS if object.has_key?('summary')
80
+ # SYNOPSIS
81
+ #{object['summary']}
82
+
83
+ SYNOPSIS
84
+ ret = ret + <<-DESCRIPTION if object.has_key?('description')
85
+ # DESCRIPTION
86
+ #{object['description']}
87
+
88
+ #{object['schema']}
89
+ DESCRIPTION
90
+ ret = ret + <<-RETURNS if object.has_key?('returns')
91
+ # RETURNS
92
+ #{object['returns'].gsub(/^/, ' ')}
93
+ RETURNS
94
+ ret = ret + <<-EXAMPLES if object.has_key?('examples') && object['examples'].has_key?('cli')
95
+ # EXAMPLES
96
+
97
+ #{examples.gsub(/^/, ' ')}
98
+ EXAMPLES
99
+ ret
100
+ end
101
+
102
+ def format_full_help(object)
103
+ object['full']
104
+ end
105
+
82
106
  def format_default_object(object, indent = 0 )
83
107
  fields = display_fields(object)
84
108
  key_indent = indent + fields.map {|f| f.length}.max
@@ -11,12 +11,14 @@ module Razor::CLI
11
11
  @parse = parse
12
12
  @segments = segments||[]
13
13
  @doc = entrypoint
14
- @doc_url = parse.api_url
15
- @userinfo = parse.api_url.userinfo
14
+ @username, @password = parse.api_url.userinfo.to_s.split(':')
15
+ @doc_resource = create_resource parse.api_url, {:accept => :json}
16
16
  end
17
17
 
18
+ attr_accessor :doc_resource
19
+
18
20
  def last_url
19
- @doc_url
21
+ @doc_resource
20
22
  end
21
23
 
22
24
  def entrypoint
@@ -53,95 +55,16 @@ module Razor::CLI
53
55
  if @segments.empty?
54
56
  entrypoint
55
57
  elsif query?
56
- @doc = collections
57
- while @segments.any?
58
- move_to @segments.shift
59
- end
60
-
61
- # Get the next level if it's a list of objects.
62
- if @doc.is_a?(Hash) and @doc['items'].is_a?(Array)
63
- @doc['items'] = @doc['items'].map do |item|
64
- item.is_a?(Hash) && item.has_key?('id') ? json_get(item['id']) : item
65
- end
66
- end
67
- @doc
58
+ Razor::CLI::Query.new(@parse, self, collections, @segments).run
68
59
  elsif command?
69
- # @todo lutter 2013-08-16: None of this has any tests, and error
70
- # handling is heinous at best
71
- cmd, body = extract_command
72
- # Ensure that we copy authentication data from our previous URL.
73
- url = cmd["id"]
74
- if @doc_url
75
- url = URI.parse(url.to_s)
76
- url.userinfo = @doc_url.userinfo
77
- end
78
-
79
- if show_command_help?
80
- json_get(url)
81
- else
82
- if body.empty?
83
- raise Razor::CLI::Error,
84
- "No arguments for command (did you forget --json ?)"
85
- end
86
- result = json_post(url, body)
87
- # Get actual object from the id.
88
- result = result.merge(json_get(result['id'])) if result['id']
89
- result
90
- end
60
+ Razor::CLI::Command.new(@parse, self, commands, @segments).run
91
61
  else
92
- raise NavigationError.new(@doc_url, @segments, @doc)
62
+ raise NavigationError.new(@doc_resource, @segments, @doc)
93
63
  end
94
64
  end
95
65
 
96
- def extract_command
97
- cmd = command(@segments.shift)
98
- @cmd_url = cmd['id']
99
- body = {}
100
- until @segments.empty?
101
- argument = @segments.shift
102
- if argument =~ /\A--([a-z-]+)(=(.+))?\Z/
103
- # `--arg=value` or `--arg value`
104
- arg, value = [$1, $3]
105
- value = @segments.shift if value.nil? && @segments[0] !~ /^--/
106
- if value =~ /\A(.+?)=(\S+)?\z/
107
- # `--arg name=value`
108
- unless body[arg].nil? or body[arg].is_a?(Hash)
109
- # Error: `--arg value --arg name=value`
110
- raise ArgumentError, "Cannot handle mixed types for argument #{arg}"
111
- end
112
- # Do not convert, assume the above is the conversion.
113
- body[arg] = (body[arg].nil? ? {} : body[arg]).merge($1 => $2)
114
- elsif body[arg].is_a?(Hash)
115
- # Error: `--arg name=value --arg value`
116
- raise ArgumentError, "Cannot handle mixed types for argument #{arg}"
117
- else
118
- value = convert_arg(cmd["name"], arg, value)
119
- if body[arg].nil?
120
- body[arg] = value
121
- else
122
- # Either/both `body[arg]` or/and `value` might be an array at this point.
123
- body[arg] = Array(body[arg]) + Array(value)
124
- end
125
- end
126
- else
127
- raise ArgumentError, "Unexpected argument #{argument}"
128
- end
129
- end
130
-
131
- begin
132
- body = MultiJson::load(File::read(body["json"])) if body["json"]
133
- rescue MultiJson::LoadError
134
- raise Razor::CLI::Error, "File #{body["json"]} is not valid JSON"
135
- rescue Errno::ENOENT
136
- raise Razor::CLI::Error, "File #{body["json"]} not found"
137
- rescue Errno::EACCES
138
- raise Razor::CLI::Error,
139
- "Permission to read file #{body["json"]} denied"
140
- end
141
- [cmd, body]
142
- end
143
-
144
- def move_to(key)
66
+ def move_to(key, doc = @doc, params = {})
67
+ @doc = doc
145
68
  if @doc.is_a? Array
146
69
  obj = @doc.find {|x| x.is_a?(Hash) and x["name"] == key }
147
70
  elsif @doc.is_a?(Hash) && @doc['items'].is_a?(Array)
@@ -150,17 +73,12 @@ module Razor::CLI
150
73
  obj = @doc[key]
151
74
  end
152
75
 
153
- raise NavigationError.new(@doc_url, key, @doc) unless obj
76
+ raise NavigationError.new(@doc_resource, key, @doc) unless obj
154
77
 
155
78
  if obj.is_a?(Hash) && obj["id"]
156
- url = obj["id"]
157
- if @doc_url
158
- url = URI.parse(url.to_s)
159
- url.userinfo = @doc_url.userinfo
160
- end
79
+ url = URI.parse(obj["id"])
161
80
 
162
- @doc = json_get(url)
163
- @doc_url = url
81
+ @doc = json_get(url, {}, params)
164
82
  elsif obj.is_a?(Hash) && obj['spec']
165
83
  @doc = obj
166
84
  elsif obj.is_a?(Hash)
@@ -187,16 +105,17 @@ module Razor::CLI
187
105
  end
188
106
 
189
107
  def get(url, headers={})
190
- url = URI.parse(url.to_s)
191
- url.userinfo = @userinfo
192
- response = RestClient.get url.to_s, headers
108
+ resource = create_resource(url, headers)
109
+ response = resource.get
193
110
  print "GET #{url.to_s}\n#{response.body}\n\n" if @parse.dump_response?
194
111
  response
195
112
  end
196
113
 
197
- def json_get(url, headers = {})
198
- url = URI.parse(url.to_s)
199
- url.userinfo = @userinfo
114
+ def json_get(url, headers = {}, params = {})
115
+ # Add extra parameters to URL.
116
+ url.query = URI.encode_www_form(params)
117
+ url.query = nil if url.query.empty? # Remove dangling '?' from URL.
118
+
200
119
  response = get(url,headers.merge(:accept => :json))
201
120
  unless response.headers[:content_type] =~ /application\/json/
202
121
  raise "Received content type #{response.headers[:content_type]}"
@@ -205,11 +124,10 @@ module Razor::CLI
205
124
  end
206
125
 
207
126
  def json_post(url, body)
208
- url = URI.parse(url.to_s)
209
- url.userinfo = @userinfo
210
127
  headers = { :accept=>:json, "Content-Type" => :json }
211
128
  begin
212
- response = RestClient.post url.to_s, MultiJson::dump(body), headers
129
+ resource = create_resource(url, headers)
130
+ response = resource.post MultiJson::dump(body)
213
131
  ensure
214
132
  if @parse.dump_response?
215
133
  print "POST #{url.to_s}\n#{body}\n-->\n"
@@ -220,59 +138,13 @@ module Razor::CLI
220
138
  end
221
139
 
222
140
  private
223
- def cmd_schema(cmd_name)
224
- begin
225
- json_get(@cmd_url)['schema']
226
- rescue RestClient::ResourceNotFound => _
227
- raise VersionCompatibilityError, 'Server must supply the expected datatypes for command arguments; use `--json` or upgrade razor-server'
228
- end
229
- end
230
141
 
231
- def arg_type(cmd_name, arg_name)
232
- # Short-circuit to allow this as a work-around for backwards compatibility.
233
- return nil if arg_name == 'json'
234
- cmd = cmd_schema(cmd_name)
235
- cmd && cmd[arg_name] && cmd[arg_name]['type'] or nil
236
- end
237
-
238
- def convert_arg(cmd_name, arg_name, value)
239
- value = nil if value == "null"
240
-
241
- argument_type = arg_type(cmd_name, arg_name)
242
-
243
- # This might be helpful, since there's no other method for debug-level logging on the client.
244
- puts "Formatting argument #{arg_name} with value #{value} as #{argument_type}\n" if @parse.dump_response?
245
-
246
- case argument_type
247
- when "array"
248
- # 'array' datatype arguments will never fail. At worst, they'll be wrapped in an array.
249
- begin
250
- MultiJson::load(value)
251
- rescue MultiJson::LoadError => _
252
- Array(value)
253
- end
254
- when "object"
255
- begin
256
- MultiJson::load(value)
257
- rescue MultiJson::LoadError => error
258
- raise ArgumentError, "Invalid JSON for argument '#{arg_name}': #{error.message}"
259
- end
260
- when "boolean"
261
- ["true", nil].include?(value)
262
- when "number"
263
- begin
264
- Integer(value)
265
- rescue ArgumentError
266
- raise ArgumentError, "Invalid integer for argument '#{arg_name}': #{value}"
267
- end
268
- when "null"
269
- raise ArgumentError, "Expected nothing for argument '#{arg_name}', but was: '#{value}'" unless value.nil?
270
- nil
271
- when "string", nil # `nil` for 'might be an alias, send as-is'
272
- value
273
- else
274
- raise Razor::CLI::Error, "Unexpected datatype '#{argument_type}' for argument #{arg_name}"
275
- end
142
+ def create_resource(url, headers)
143
+ @doc_resource = RestClient::Resource.new(url.to_s, :headers => headers,
144
+ :verify_ssl => @parse.verify_ssl?,
145
+ :ssl_ca_file => @parse.ssl_ca_file,
146
+ :user => @username,
147
+ :password => @password)
276
148
  end
277
149
  end
278
150
  end