pe-razor-client 0.15.2.2 → 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 (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