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.
- checksums.yaml +4 -4
- data/NEWS.md +27 -0
- data/bin/razor +27 -2
- data/lib/razor/cli.rb +11 -2
- data/lib/razor/cli/command.rb +150 -0
- data/lib/razor/cli/document.rb +8 -4
- data/lib/razor/cli/format.rb +46 -22
- data/lib/razor/cli/navigate.rb +28 -156
- data/lib/razor/cli/parse.rb +49 -7
- data/lib/razor/cli/query.rb +69 -0
- data/lib/razor/cli/table_format.rb +41 -0
- data/lib/razor/cli/transforms.rb +25 -0
- data/lib/razor/cli/version.rb +1 -1
- data/lib/razor/cli/views.yaml +53 -2
- data/spec/cli/command_spec.rb +66 -0
- data/spec/cli/format_spec.rb +95 -5
- data/spec/cli/navigate_spec.rb +50 -6
- data/spec/cli/parse_spec.rb +42 -2
- data/spec/fixtures/vcr/Razor_CLI_Navigate/argument_formatting/should_allow_in_string.yml +233 -0
- data/spec/fixtures/vcr/Razor_CLI_Navigate/argument_formatting/should_allow_spaces.yml +281 -548
- data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_--help_command_.yml +160 -37
- data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_-h_command_.yml +160 -37
- data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_--help_.yml +160 -37
- data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_-h_.yml +160 -37
- data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_help_.yml +160 -37
- data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_help_command_.yml +160 -37
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_authentication/should_preserve_that_across_navigation.yml +10 -10
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_authentication/should_supply_that_to_the_API_service.yml +5 -5
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_invalid_parameter/should_fail_with_bad_JSON.yml +71 -166
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_invalid_parameter/should_fail_with_malformed_argument.yml +109 -59
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_no_parameters/should_fail_with_bad_JSON.yml +98 -5
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_append_limit.yml +69 -0
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_append_start.yml +69 -0
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_not_fail_when_query_returns_details_for_one_item.yml +313 -0
- data/spec/fixtures/vcr/Razor_CLI_Navigate/with_query_parameters/should_store_query_without_query_parameters.yml +557 -0
- 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
- 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
- data/spec/fixtures/vcr/Razor_CLI_Parse/_new/_help/should_print_a_list_of_known_endpoints.yml +5 -5
- data/spec/spec_helper.rb +8 -4
- metadata +26 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6c2760d424fcb90751a22b65217172feab26b6c
|
4
|
+
data.tar.gz: 51cca0cde398a43684ed9398eb709322874a817d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
37
|
-
|
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 -
|
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
|
data/lib/razor/cli/document.rb
CHANGED
@@ -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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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')
|
data/lib/razor/cli/format.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'forwardable'
|
2
|
-
require '
|
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.
|
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
|
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
|
-
|
47
|
-
|
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
|
data/lib/razor/cli/navigate.rb
CHANGED
@@ -11,12 +11,14 @@ module Razor::CLI
|
|
11
11
|
@parse = parse
|
12
12
|
@segments = segments||[]
|
13
13
|
@doc = entrypoint
|
14
|
-
@
|
15
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
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(@
|
62
|
+
raise NavigationError.new(@doc_resource, @segments, @doc)
|
93
63
|
end
|
94
64
|
end
|
95
65
|
|
96
|
-
def
|
97
|
-
|
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(@
|
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
|
-
|
191
|
-
|
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
|
-
|
199
|
-
url.
|
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
|
-
|
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
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|