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