razor-client 0.14.0 → 0.15.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 (48) hide show
  1. data/NEWS.md +18 -0
  2. data/bin/razor +9 -4
  3. data/lib/razor/cli.rb +10 -0
  4. data/lib/razor/cli/document.rb +59 -0
  5. data/lib/razor/cli/format.rb +70 -20
  6. data/lib/razor/cli/navigate.rb +114 -25
  7. data/lib/razor/cli/parse.rb +79 -10
  8. data/lib/razor/cli/transforms.rb +39 -0
  9. data/lib/razor/cli/version.rb +46 -0
  10. data/lib/razor/cli/views.rb +25 -0
  11. data/lib/razor/cli/views.yaml +189 -0
  12. data/spec/cli/format_spec.rb +77 -0
  13. data/spec/cli/navigate_spec.rb +102 -2
  14. data/spec/cli/parse_spec.rb +20 -0
  15. data/spec/fixtures/vcr/Razor_CLI_Navigate/argument_formatting/should_allow_spaces.yml +966 -0
  16. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_--help_command_.yml +99 -0
  17. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_-h_command_.yml +99 -0
  18. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_--help_.yml +99 -0
  19. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_-h_.yml +99 -0
  20. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_command_help_.yml +99 -0
  21. data/spec/fixtures/vcr/Razor_CLI_Navigate/for_command_help/should_provide_command_help_for_razor_help_command_.yml +99 -0
  22. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_authentication/should_preserve_that_across_navigation.yml +9 -42
  23. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_authentication/should_supply_that_to_the_API_service.yml +5 -5
  24. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_invalid_parameter/should_fail_with_bad_JSON.yml +228 -0
  25. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_invalid_parameter/should_fail_with_malformed_argument.yml +120 -0
  26. 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 +2006 -0
  27. 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 +2006 -0
  28. 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 +2006 -0
  29. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_multiple_arguments_with_same_name/combining_as_an_object/should_construct_a_json_object.yml +234 -0
  30. 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 +228 -0
  31. 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 +164 -0
  32. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_no_parameters/should_fail_with_bad_JSON.yml +36 -0
  33. data/spec/fixtures/vcr/Razor_CLI_Parse/_new/_help/should_print_a_list_of_known_endpoints.yml +5 -5
  34. data/spec/spec_helper.rb +12 -1
  35. data/spec/testing.md +16 -0
  36. data/spec/version_spec.rb +8 -0
  37. metadata +74 -66
  38. data/.gitignore +0 -7
  39. data/.yardopts +0 -2
  40. data/Gemfile +0 -35
  41. data/Gemfile.lock +0 -53
  42. data/Rakefile +0 -37
  43. data/lib/razor/cli/navigate.yaml +0 -28
  44. data/razor-client.gemspec +0 -32
  45. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_a_single_item_path/.yml +0 -69
  46. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_an_invalid_path/.yml +0 -36
  47. data/spec/fixtures/vcr/Razor_CLI_Navigate/with_no_path/.yml +0 -36
  48. data/spec/fixtures/vcr/Razor_CLI_Parse/_new/_help/.yml +0 -36
@@ -14,6 +14,14 @@ module Razor::CLI
14
14
  @dump = true
15
15
  end
16
16
 
17
+ opts.on "-f", "--full", "Show full details when viewing entities" do
18
+ @format = 'full'
19
+ end
20
+
21
+ opts.on "-s", "--short", "Show shortened details when viewing entities" do
22
+ @format = 'short'
23
+ end
24
+
17
25
  opts.on "-u", "--url URL",
18
26
  "The full Razor API URL, can also be set\n" + " "*37 +
19
27
  "with the RAZOR_API environment variable\n" + " "*37 +
@@ -21,7 +29,12 @@ module Razor::CLI
21
29
  parse_and_set_api_url(url, :opts)
22
30
  end
23
31
 
32
+ opts.on "-v", "--version", "Show the version of Razor" do
33
+ @show_version = true
34
+ end
35
+
24
36
  opts.on "-h", "--help", "Show this screen" do
37
+ # If searching for a command's help, leave the argument for navigation.
25
38
  @option_help = true
26
39
  end
27
40
 
@@ -35,41 +48,97 @@ module Razor::CLI
35
48
  end.join("\n")
36
49
  end
37
50
 
51
+ def version
52
+ <<-VERSION
53
+ Razor Server version: #{navigate.server_version}
54
+ Razor Client version: #{Razor::CLI::VERSION}
55
+ VERSION
56
+ end
57
+
38
58
  def help
39
59
  output = get_optparse.to_s
40
60
  begin
41
- output << list_things("Collections", navigate.collections)
42
- output << "\n\n Navigate to entries of a collection using COLLECTION NAME, for example,\n 'nodes node15' for the details of a node or 'nodes node15 log' to see\n the log for node15\n"
43
- output << list_things("Commands", navigate.commands)
44
- output << "\n\n Pass arguments to commands either directly by name ('--name=NAME')\n or save the JSON body for the command in a file and pass it with\n '--json FILE'. Using --json is the only way to pass arguments in\n nested structures such as the configuration for a broker.\n"
61
+ output << <<-HELP
62
+ #{list_things("Collections", navigate.collections)}
63
+
64
+ Navigate to entries of a collection using COLLECTION NAME, for example,
65
+ 'nodes node15' for the details of a node or 'nodes node15 log' to see
66
+ the log for node15
67
+ #{list_things("Commands", navigate.commands)}
68
+
69
+ Pass arguments to commands either directly by name ('--name=NAME')
70
+ or save the JSON body for the command in a file and pass it with
71
+ '--json FILE'. Using --json is the only way to pass arguments in
72
+ nested structures such as the configuration for a broker.
73
+
74
+ HELP
75
+ rescue RestClient::Unauthorized
76
+ output << <<-UNAUTH
77
+ Error: Credentials are required to connect to the server at #{@api_url}"
78
+ UNAUTH
45
79
  rescue
46
- output << "\nCould not connect to the server at #{@api_url}. More help is available after "
47
- output << "pointing\nthe client to a Razor server"
80
+ output << <<-ERR
81
+ Error: Could not connect to the server at #{@api_url}. More help is available after pointing
82
+ the client to a Razor server
83
+ ERR
48
84
  end
49
85
  output
50
86
  end
51
87
 
88
+ def show_version?
89
+ !!@show_version
90
+ end
91
+
52
92
  def show_help?
53
93
  !!@option_help
54
94
  end
55
95
 
96
+ def show_command_help?
97
+ !!@command_help
98
+ end
99
+
56
100
  def dump_response?
57
101
  !!@dump
58
102
  end
59
103
 
60
- attr_reader :api_url
104
+ attr_reader :api_url, :format, :args
61
105
 
62
106
  def initialize(args)
63
107
  parse_and_set_api_url(ENV["RAZOR_API"] || DEFAULT_RAZOR_API, :env)
64
108
  @args = args.dup
65
- rest = get_optparse.order(args)
66
- if rest.any?
67
- @navigation = rest
109
+ @format = 'short'
110
+ @args = get_optparse.order(@args)
111
+ @args = set_help_vars(@args)
112
+ if @args == ['version'] or @show_version
113
+ @show_version = true
114
+ elsif @args.any?
115
+ @navigation = @args.dup
68
116
  else
117
+ # Called with no remaining arguments to parse.
69
118
  @option_help = true
70
119
  end
71
120
  end
72
121
 
122
+ # This method sets the appropriate help flags `@command_help` and `@option_help`,
123
+ # then returns a new set of arguments.
124
+ def set_help_vars(rest)
125
+ # Find and remove 'help' variations anywhere in the command.
126
+ if rest.any? { |arg| ['-h', '--help'].include? arg } or
127
+ rest.first == 'help' or rest.drop(1).first == 'help'
128
+ rest = rest.reject { |arg| ['-h', '--help', 'help'].include? arg }
129
+ # If anything is left, assume it is a command.
130
+ if rest.any?
131
+ @command_help = true
132
+ else
133
+ @option_help = true
134
+ end
135
+ end
136
+ if @option_help && rest.any?
137
+ @command_help = true
138
+ end
139
+ rest
140
+ end
141
+
73
142
  def navigate
74
143
  @navigate ||=Navigate.new(self, @navigation)
75
144
  end
@@ -0,0 +1,39 @@
1
+ module Razor::CLI
2
+ module Transforms
3
+ module_function
4
+ def identity(any)
5
+ any
6
+ end
7
+ def if_present(obj)
8
+ obj.nil? ? "---" : obj
9
+ end
10
+ def join_names(arr)
11
+ (arr.nil? or arr.empty?) ? '(none)' : arr.map { |item| item['name'] }.join(", ")
12
+ end
13
+ def nested(nested_obj)
14
+ (nested_obj.nil? or nested_obj.empty?) ? '(none)' : nested_obj.to_s
15
+ end
16
+ def shallow_hash(hash)
17
+ (hash.nil? or hash.empty?) ? '(none)' :
18
+ hash.map {|key, val| "#{key}: #{val}"}.join(', ')
19
+ end
20
+ def select_name(item)
21
+ item and item['name'] or "---"
22
+ end
23
+ def mac(mac)
24
+ mac ? mac.gsub(/-/, ":") : "---"
25
+ end
26
+ def name(obj)
27
+ obj ? obj['name'] : "---"
28
+ end
29
+ def name_if_present(obj)
30
+ obj ? obj['name'] : "---"
31
+ end
32
+ def count_column(hash)
33
+ hash['count']
34
+ end
35
+ def count(arr)
36
+ arr.size
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,46 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Razor
3
+ module CLI
4
+ # Define the Razor version, and stash it in a constant. When we build a
5
+ # package for shipping we burn the version directly into this file, by
6
+ # modifying the text on the fly during package building.
7
+ #
8
+ # That "burns in" the value, but if that hasn't happened we do our best to
9
+ # work out a reasonable version number: If we are running from a git
10
+ # checkout, and we have git installed, determine this with `git describe`,
11
+ #
12
+ # If we don't have git, or it fails, but have the metadata, parse out some
13
+ # useful information directly from the checkout; this isn't great, but does
14
+ # give some guidance as to where the user was working.
15
+ #
16
+ # Finally, fall back to a default version placeholder.
17
+ #
18
+ #
19
+ # The next line is the one that our packaging tools modify, so please make
20
+ # sure that any change to it is discussed and agreed first.
21
+ version = '0.15.0'
22
+
23
+ if version == "DEVELOPMENT"
24
+ root = File.expand_path("../../..", File.dirname(__FILE__))
25
+ if File.directory? File.join(root, ".git")
26
+ git_version = %x{cd '#{root}' && git describe --tags --dirty --always 2>&1}
27
+ if $?.success?
28
+ version = 'v' + git_version
29
+ else # try to read manually...
30
+ head = File.read(File.join(root, ".git", "HEAD")) rescue nil
31
+ if head and match = %r{^ref: (refs/heads/(.[^\n]+))$}.match(head.lines.first)
32
+ version = 'git-' + match[2]
33
+ if sha = File.read(File.join(root, ".git", match[1]))[0,8] rescue nil
34
+ version += '-' + sha
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # The running version of Razor. Razor follows the tenets of [semantic
42
+ # versioning](http://semver.org), and this version number reflects the rules
43
+ # as of SemVer 2.0.0
44
+ VERSION = version.chomp
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ module Razor::CLI
2
+ module Views
3
+ module_function
4
+ def views
5
+ @views ||= YAML::load_file(File::join(File::dirname(__FILE__), "views.yaml"))
6
+ end
7
+
8
+ def transform(item, transform_name)
9
+ Razor::CLI::Transforms.send(transform_name || 'identity', item)
10
+ end
11
+
12
+ def find_formatting(spec, format, remaining_navigation)
13
+ remaining_navigation ||= ''
14
+ # Scope will narrow by traversing the spec.
15
+ scope = views
16
+ spec = spec ? spec.split('/').drop_while { |i| i != 'collections'} : []
17
+ spec = spec + remaining_navigation.split(' ')
18
+ while spec.any?
19
+ val = spec.shift
20
+ scope = (scope[val] or {})
21
+ end
22
+ scope["+#{format}"] or {}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,189 @@
1
+ ---
2
+
3
+ # This file contains instructions for how the client should format various
4
+ # objects based on the object's 'spec' path. The structure uses a `+` prefix
5
+ # to denote metadata. `member` is used to describe a member of a group.
6
+ #
7
+ # Accepted metadata annotations are:
8
+ # `+short`: This section configures what the output for the object looks like in short format.
9
+ # `+show`: This section configures which attributes are shown in the output.
10
+ # `+layout`: This configures whether to use 'list' or 'table' output.
11
+ # `+column`: This changes which column is used in the field formatting.
12
+ collections:
13
+ brokers:
14
+ +short:
15
+ +layout: table
16
+ +show:
17
+ name:
18
+ broker-type:
19
+ configuration:
20
+ +format: nested
21
+ policies:
22
+ +format: count_column
23
+ member:
24
+ +short:
25
+ +layout: list
26
+ +show:
27
+ name:
28
+ broker-type:
29
+ configuration:
30
+ policies:
31
+ +format: count_column
32
+ nodes:
33
+ +short:
34
+ +layout: table
35
+ +show:
36
+ name:
37
+ dhcp_mac:
38
+ +format: mac
39
+ tags:
40
+ +format: join_names
41
+ policy:
42
+ +format: select_name
43
+ member:
44
+ +short:
45
+ +layout: list
46
+ +show:
47
+ name:
48
+ dhcp_mac:
49
+ +format: mac
50
+ state:
51
+ last_checkin:
52
+ tags:
53
+ +format: join_names
54
+ tags:
55
+ +short:
56
+ +layout: table
57
+ +show:
58
+ name:
59
+ rule:
60
+ +format: nested
61
+ nodes:
62
+ +format: count_column
63
+ policies:
64
+ +format: count_column
65
+ hw_info:
66
+ +short:
67
+ +layout: list
68
+ log:
69
+ +short:
70
+ +layout: table
71
+ # +show:
72
+ # event:
73
+ # task:
74
+ # timestamp:
75
+ policies:
76
+ +short:
77
+ +layout: table
78
+ +show:
79
+ name:
80
+ repo:
81
+ +format: name
82
+ task:
83
+ +format: name
84
+ broker:
85
+ +format: name
86
+ enabled:
87
+ max_count:
88
+ tags:
89
+ +format: join_names
90
+ nodes:
91
+ +format: count_column
92
+ member:
93
+ +short:
94
+ +show:
95
+ name:
96
+ repo:
97
+ +format: name
98
+ task:
99
+ +format: name
100
+ broker:
101
+ +format: name
102
+ enabled:
103
+ max_count:
104
+ tags:
105
+ +format: join_names
106
+ nodes:
107
+ +format: count_column
108
+ tasks:
109
+ +short:
110
+ +layout: table
111
+ +show:
112
+ name:
113
+ description:
114
+ base:
115
+ +format: name_if_present
116
+ boot_seq:
117
+ +format: shallow_hash
118
+ member:
119
+ +short:
120
+ +show:
121
+ name:
122
+ description:
123
+ os:
124
+ boot_seq:
125
+ repos:
126
+ +short:
127
+ +layout: table
128
+ +show:
129
+ name:
130
+ iso_url:
131
+ +format: if_present
132
+ url:
133
+ +format: if_present
134
+ task:
135
+ +format: name
136
+ member:
137
+ +short:
138
+ +layout: list
139
+ +show:
140
+ name:
141
+ iso_url:
142
+ +format: if_present
143
+ url:
144
+ +format: if_present
145
+ task:
146
+ +format: name
147
+ tags:
148
+ +short:
149
+ +layout: table
150
+ +show:
151
+ name:
152
+ rule:
153
+ +format: nested
154
+ nodes:
155
+ +format: count_column
156
+ policies:
157
+ +format: count_column
158
+ member:
159
+ +short:
160
+ +layout: list
161
+ +show:
162
+ name:
163
+ rule:
164
+ +format: nested
165
+ nodes:
166
+ +format: count_column
167
+ policies:
168
+ +format: count_column
169
+ commands:
170
+ +short:
171
+ +layout: table
172
+ +show:
173
+ name:
174
+ command:
175
+ name parameter:
176
+ +format: name
177
+ errors:
178
+ +format: count
179
+ status:
180
+ member:
181
+ +short:
182
+ +show:
183
+ name:
184
+ command:
185
+ params:
186
+ errors:
187
+ +format: count
188
+ status:
189
+ submitted_at:
@@ -0,0 +1,77 @@
1
+ # Needed to make the client work on Ruby 1.8.7
2
+ unless Kernel.respond_to?(:require_relative)
3
+ module Kernel
4
+ def require_relative(path)
5
+ require File.join(File.dirname(caller[0]), path.to_str)
6
+ end
7
+ end
8
+ end
9
+
10
+ require "rspec/expectations"
11
+ require_relative '../spec_helper'
12
+
13
+ describe Razor::CLI::Format do
14
+ include described_class
15
+
16
+ def format(doc, args = {})
17
+ args = {:format => 'short', :args => ['something', 'else'], :show_command_help? => false}.merge(args)
18
+ parse = double(args)
19
+ format_document doc, parse
20
+ end
21
+
22
+ context 'additional details' do
23
+ it "tells additional details for a hash" do
24
+ doc = {'abc' => {'def' => 'ghi'}}
25
+ result = format doc
26
+ result.should =~ /Query additional details via: `razor something else \[abc\]`\z/
27
+ end
28
+ it "tells additional details for an array" do
29
+ doc = {'abc' => ['def']}
30
+ result = format doc
31
+ result.should =~ /Query additional details via: `razor something else \[abc\]`\z/
32
+ end
33
+ it "tells multiple additional details" do
34
+ doc = {'abc' => ['def'], 'ghi' => {'jkl' => 'mno'}}
35
+ result = format doc
36
+ result.should =~ /Query additional details via: `razor something else \[abc, ghi\]`\z/
37
+ end
38
+ it "tells no additional details for a string" do
39
+ doc = {'abc' => 'def'}
40
+ result = format doc
41
+ result.should_not =~ /Query additional details/
42
+ end
43
+ it "hides array spec array from additional details" do
44
+ doc = {'abc' => [], 'spec' => ['def', 'jkl']}
45
+ result = format doc
46
+ result.should =~ /Query additional details via: `razor something else \[abc\]`\z/
47
+ end
48
+ it "hides array +spec array from additional details" do
49
+ doc = {'abc' => [], '+spec' => ['def', 'jkl']}
50
+ result = format doc
51
+ result.should =~ /Query additional details via: `razor something else \[abc\]`\z/
52
+ end
53
+ it "only shows additional details for nested objects" do
54
+ doc = {'one-prop' => 'val', 'abc' => [], 'prop' => 'jkl'}
55
+ result = format doc
56
+ result.should =~ /Query additional details via: `razor something else \[abc\]`\z/
57
+ end
58
+ it "tells how to query by name" do
59
+ doc = {'items' => [{'name' => 'entirely'}, {'name' => 'bar'} ]}
60
+ result = format doc
61
+ result.should =~ /Query an entry by including its name, e.g. `razor something else entirely`\z/
62
+ end
63
+ end
64
+
65
+ context 'empty display' do
66
+ it "works right when it has nothing to display as a table" do
67
+ doc = {"spec"=>"http://api.puppetlabs.com/razor/v1/collections/policies", "items"=>[]}
68
+ result = format doc
69
+ result.should == "There are no items for this query."
70
+ end
71
+ it "works right when it has nothing to display as a list" do
72
+ doc = {"spec"=>"http://api.puppetlabs.com/razor/v1/collections/policies/member", "items"=>[]}
73
+ result = format doc
74
+ result.should == "There are no items for this query."
75
+ end
76
+ end
77
+ end