razor-client 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
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