pe-razor-client 0.14.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 56d3c2b6b7151fc0b1e41bc7b6761b9e7bc46b04
4
+ data.tar.gz: 28c812344e73fefc6a6634c9688e302afefaa716
5
+ SHA512:
6
+ metadata.gz: 8261b52410c1685770bb406d68698d541c1e986c6b249b21d9cbda8255763a130de0808f21e021228388978482cf9b3c1da6fdf6c226edaf5ce2b4697e49ab34
7
+ data.tar.gz: 37f1d1535ddcb8d12a405548912dbfcec7a1cdea73b39d39e1df0310ee181ef706e4b08420f4159c1d5f7fa516514d54514c92bc753b7c2d72fd1aff2558b20c
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ config.yaml
2
+ log/*
3
+ pkg/
4
+ /.yardoc
5
+ /Gemfile.local*
6
+ /coverage
7
+ /.project
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
data/Gemfile ADDED
@@ -0,0 +1,35 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # mime-types is a dependency of rest-client. We need to explicitly depend
4
+ # on it and pin its version to make sure this works with Ruby 1.8.7
5
+ gem 'mime-types', '< 2.0'
6
+ gem 'rest-client'
7
+ gem 'terminal-table'
8
+
9
+ group :doc do
10
+ gem 'yard'
11
+ gem 'kramdown'
12
+ end
13
+
14
+ # This group will be excluded by default in `torquebox archive`
15
+ group :test do
16
+ gem 'rack-test'
17
+ gem 'rspec', '~> 2.13.0'
18
+ gem 'rspec-core', '~> 2.13.1'
19
+ gem 'rspec-expectations', '~> 2.13.0'
20
+ gem 'rspec-mocks', '~> 2.13.1'
21
+ gem 'simplecov'
22
+ gem 'webmock'
23
+ gem 'vcr'
24
+ end
25
+
26
+ group :development do
27
+ gem 'rake'
28
+ end
29
+
30
+ # This allows you to create `Gemfile.local` and have it loaded automatically;
31
+ # the purpose of this is to allow you to put additional development gems
32
+ # somewhere convenient without having to constantly mess with this file.
33
+ #
34
+ # Gemfile.local is in the .gitignore file; do not check one in!
35
+ eval(File.read(File.dirname(__FILE__) + '/Gemfile.local'), binding) rescue nil
data/Gemfile.lock ADDED
@@ -0,0 +1,53 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ addressable (2.3.3)
5
+ crack (0.3.2)
6
+ diff-lcs (1.2.4)
7
+ kramdown (1.1.0)
8
+ mime-types (1.24)
9
+ multi_json (1.7.9)
10
+ rack (1.5.2)
11
+ rack-test (0.6.2)
12
+ rack (>= 1.0)
13
+ rake (10.1.0)
14
+ rest-client (1.6.7)
15
+ mime-types (>= 1.16)
16
+ rspec (2.13.0)
17
+ rspec-core (~> 2.13.0)
18
+ rspec-expectations (~> 2.13.0)
19
+ rspec-mocks (~> 2.13.0)
20
+ rspec-core (2.13.1)
21
+ rspec-expectations (2.13.0)
22
+ diff-lcs (>= 1.1.3, < 2.0)
23
+ rspec-mocks (2.13.1)
24
+ simplecov (0.7.1)
25
+ multi_json (~> 1.0)
26
+ simplecov-html (~> 0.7.1)
27
+ simplecov-html (0.7.1)
28
+ terminal-table (1.4.5)
29
+ vcr (2.5.0)
30
+ webmock (1.9.3)
31
+ addressable (>= 2.2.7)
32
+ crack (>= 0.3.2)
33
+ yard (0.8.7)
34
+
35
+ PLATFORMS
36
+ java
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ kramdown
41
+ mime-types (< 2.0)
42
+ rack-test
43
+ rake
44
+ rest-client
45
+ rspec (~> 2.13.0)
46
+ rspec-core (~> 2.13.1)
47
+ rspec-expectations (~> 2.13.0)
48
+ rspec-mocks (~> 2.13.1)
49
+ simplecov
50
+ terminal-table
51
+ vcr
52
+ webmock
53
+ yard
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Razor
2
+
3
+ Copyright 2013, 2014 Puppet Labs Inc
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # Razor client
2
+
3
+ This code is still in development mode; that means that we might make
4
+ backwards incompatible changes, especially to the database schema which
5
+ would force you to rebuild all the machines that Razor is managing. Razor
6
+ will become stable RSN.
7
+
8
+ This is the command line client for the [Razor
9
+ server](https://github.com/puppetlabs/razor-server) Please have a look at
10
+ the Wiki over there for details, in particular, the [Getting
11
+ Started](https://github.com/puppetlabs/razor-server/wiki/Getting-started)
12
+ page contains instructions about installation and setup of the client.
13
+
14
+ ## Getting in touch
15
+
16
+ * bug/issue tracker: [RAZOR project in JIRA](https://tickets.puppetlabs.com/browse/RAZOR)
17
+ * on IRC: `#puppet-razor` on [freenode](http://freenode.net/)
18
+ * mailing list: [puppet-razor@googlegroups.com](http://groups.google.com/group/puppet-razor)
19
+
20
+ ## License
21
+
22
+ Razor is distributed under the Apache 2.0 license.
23
+ See [the LICENSE file](LICENSE) for full details.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rake'
2
+ require "bundler/gem_tasks"
3
+
4
+ # Needed to make the client work on Ruby 1.8.7
5
+ unless Kernel.respond_to?(:require_relative)
6
+ module Kernel
7
+ def require_relative(path)
8
+ require File.join(File.dirname(caller[0]), path.to_str)
9
+ end
10
+ end
11
+ end
12
+
13
+ require_relative 'spec/vcr_library'
14
+
15
+ namespace :bundler do
16
+ task :setup do
17
+ require 'bundler/setup'
18
+ end
19
+ end
20
+
21
+ namespace :spec do
22
+ require 'rspec/core'
23
+ require 'rspec/core/rake_task'
24
+
25
+ desc <<EOS
26
+ Run all specs. Set VCR_RECORD to 'all' to rerecord and to 'new_episodes'
27
+ to record new tests. Tapes are in #{VCR_LIBRARY}
28
+ EOS
29
+ RSpec::Core::RakeTask.new(:all => :"bundler:setup") do |t|
30
+ t.pattern = 'spec/**/*_spec.rb'
31
+ end
32
+ end
33
+
34
+ desc "Erase all VCR recordings"
35
+ task :"vcr:erase" do
36
+ erase_vcr_library
37
+ end
data/bin/razor ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Needed to make the client work on Ruby 1.8.7
4
+ unless Kernel.respond_to?(:require_relative)
5
+ module Kernel
6
+ def require_relative(path)
7
+ require File.join(File.dirname(caller[0]), path.to_str)
8
+ end
9
+ end
10
+ end
11
+
12
+ require 'rubygems'
13
+ require_relative "../lib/razor/cli"
14
+
15
+ include Razor::CLI::Format
16
+
17
+ def die(message = nil)
18
+ puts "Error: #{message}" if message
19
+ exit 1
20
+ end
21
+
22
+ begin
23
+ parse = Razor::CLI::Parse.new(ARGV)
24
+ rescue Razor::CLI::InvalidURIError => e
25
+ die e.message
26
+ rescue OptionParser::InvalidOption => e
27
+ die e.message + "\nTry 'razor --help' for more information"
28
+ end
29
+
30
+ if parse.show_help?
31
+ puts parse.help
32
+ exit 0
33
+ end
34
+
35
+ begin
36
+ document = parse.navigate.get_document
37
+ url = parse.navigate.last_url
38
+ puts "From #{url}:\n\n#{format_document document}\n\n"
39
+ rescue Razor::CLI::Error => e
40
+ die "#{e}\n#{parse.help}\n\n"
41
+ rescue SocketError, Errno::ECONNREFUSED => e
42
+ puts "Error: Could not connect to the server at #{parse.api_url}"
43
+ puts " #{e}\n"
44
+ die
45
+ rescue RestClient::Exception => e
46
+ r = e.response
47
+ puts "Error from doing #{r.args[:method].to_s.upcase} #{r.args[:url]}"
48
+ puts e.message
49
+ begin
50
+ body = MultiJson::load(r.body)
51
+ puts body.delete("details") if body["details"]
52
+ unless body.empty?
53
+ puts format_document(body)
54
+ end
55
+ rescue => e
56
+ # Ignore errors here; our best efforts at telling the user more about
57
+ # what happened has failed. Just dump the response
58
+ puts r.body
59
+ end
60
+ die
61
+ end
data/lib/razor.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'razor/cli'
data/lib/razor/cli.rb ADDED
@@ -0,0 +1,34 @@
1
+ module Razor
2
+ module CLI
3
+ class Error < RuntimeError; end
4
+
5
+ class NavigationError < Error
6
+ def initialize(url, key, doc)
7
+ @key = key; @doc = doc
8
+ if key.is_a?(Array)
9
+ super "Could not navigate to '#{key.join(" ")}' from #{url}"
10
+ else
11
+ super "Could not find entry '#{key}' in document at #{url}"
12
+ end
13
+ end
14
+ end
15
+
16
+ class InvalidURIError < Error
17
+ def initialize(url, type)
18
+ case type
19
+ when :env
20
+ super "URL '#{url}' in ENV variable RAZOR_API is not valid"
21
+ when :opts
22
+ super "URL '#{url}' provided by -U or --url is not valid"
23
+ else
24
+ super "URL '#{url}' is not valid"
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ require_relative 'cli/navigate'
33
+ require_relative 'cli/parse'
34
+ require_relative 'cli/format'
@@ -0,0 +1,76 @@
1
+ require 'terminal-table'
2
+
3
+ module Razor::CLI
4
+ module Format
5
+ PriorityKeys = %w[ id name ]
6
+ SpecNames = {
7
+ "/spec/object/policy" => "Policy",
8
+ "/spec/object/tag" => "Tag",
9
+ "/spec/object/reference" => "reference"
10
+ }
11
+
12
+ def self.spec_name(spec)
13
+ path = spec && URI.parse(spec).path
14
+ SpecNames[path] || path
15
+ rescue => e
16
+ spec
17
+ end
18
+
19
+ def format_document(doc)
20
+ case doc
21
+ when Array then format_objects(doc)
22
+ when Hash then format_object(doc)
23
+ else doc.to_s
24
+ end.chomp
25
+ end
26
+
27
+ # We assume that all collections are homogenous
28
+ def format_objects(objects, indent = 0)
29
+ objects.map do |obj|
30
+ obj.is_a?(Hash) ? format_object(obj, indent) : ' '*indent + obj.inspect
31
+ end.join "\n\n"
32
+ end
33
+
34
+ def format_reference_object(ref, indent = 0)
35
+ output = ' '* indent + "#{ref['name']} => #{ref['id'].to_s.ljust 4}"
36
+ end
37
+
38
+
39
+ def format_object(object, indent = 0)
40
+ if object.keys == ["id", "name"]
41
+ format_reference_object(object, indent)
42
+ else
43
+ format_default_object(object, indent)
44
+ end
45
+ end
46
+
47
+ def format_default_object(object, indent = 0 )
48
+ fields = (PriorityKeys & object.keys) + (object.keys - PriorityKeys)
49
+ key_indent = indent + fields.map {|f| f.length}.max
50
+ output = ""
51
+ fields.map do |f|
52
+ value = object[f]
53
+ output = "#{f.rjust key_indent + 2}: "
54
+ output << case value
55
+ when Hash
56
+ if value.empty?
57
+ "{}"
58
+ else
59
+ "\n" + format_object(value, key_indent + 4).rstrip
60
+ end
61
+ when Array
62
+ if value.all? { |v| v.is_a?(String) }
63
+ "[" + value.map(&:inspect).join(",") + "]"
64
+ else
65
+ "[\n" + format_objects(value, key_indent + 6) + ("\n"+' '*(key_indent+4)+"]")
66
+ end
67
+ else
68
+ case f
69
+ when "spec" then "\"#{Format.spec_name(value)}\""
70
+ else value.inspect
71
+ end
72
+ end
73
+ end.join "\n"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,177 @@
1
+ require 'rest-client'
2
+ require 'multi_json'
3
+ require 'yaml'
4
+
5
+ module Razor::CLI
6
+ class Navigate
7
+
8
+ def initialize(parse, segments)
9
+ @parse = parse
10
+ @segments = segments||[]
11
+ @doc = entrypoint
12
+ @doc_url = parse.api_url
13
+ end
14
+
15
+ def last_url
16
+ @doc_url
17
+ end
18
+
19
+ def entrypoint
20
+ @entrypoint ||= json_get(@parse.api_url)
21
+ end
22
+
23
+ def collections
24
+ entrypoint["collections"]
25
+ end
26
+
27
+ def commands
28
+ entrypoint["commands"]
29
+ end
30
+
31
+ def query?
32
+ collections.any? { |coll| coll["name"] == @segments.first }
33
+ end
34
+
35
+ def command(name)
36
+ commands.find { |coll| coll["name"] == name }
37
+ end
38
+
39
+ def command?
40
+ !! command(@segments.first)
41
+ end
42
+
43
+ def get_document
44
+ if @segments.empty?
45
+ entrypoint
46
+ elsif query?
47
+ @doc = collections
48
+ while @segments.any?
49
+ move_to @segments.shift
50
+ end
51
+ @doc
52
+ elsif command?
53
+ # @todo lutter 2013-08-16: None of this has any tests, and error
54
+ # handling is heinous at best
55
+ cmd, body = extract_command
56
+ if body.empty?
57
+ raise Razor::CLI::Error,
58
+ "No arguments for command (did you forget --json ?)"
59
+ end
60
+ # Ensure that we copy authentication data from our previous URL.
61
+ url = cmd["id"]
62
+ if @doc_url
63
+ url = URI.parse(url.to_s)
64
+ url.userinfo = @doc_url.userinfo
65
+ end
66
+
67
+ json_post(url, body)
68
+ else
69
+ raise NavigationError.new(@doc_url, @segments, @doc)
70
+ end
71
+ end
72
+
73
+ def extract_command
74
+ cmd = command(@segments.shift)
75
+ body = {}
76
+ until @segments.empty?
77
+ if @segments.shift =~ /\A--([a-z-]+)(=(\S+))?\Z/
78
+ arg, value = [$1, $3]
79
+ value = @segments.shift if value.nil? && @segments[0] !~ /^--/
80
+ body[arg] = convert_arg(cmd["name"], arg, value)
81
+ end
82
+ end
83
+
84
+ begin
85
+ body = MultiJson::load(File::read(body["json"])) if body["json"]
86
+ rescue MultiJson::LoadError
87
+ raise Razor::CLI::Error, "File #{body["json"]} is not valid JSON"
88
+ rescue Errno::ENOENT
89
+ raise Razor::CLI::Error, "File #{body["json"]} not found"
90
+ rescue Errno::EACCES
91
+ raise Razor::CLI::Error,
92
+ "Permission to read file #{body["json"]} denied"
93
+ end
94
+ [cmd, body]
95
+ end
96
+
97
+ def move_to(key)
98
+ key = key.to_i if key.to_i.to_s == key
99
+ if @doc.is_a? Array
100
+ obj = @doc.find {|x| x.is_a?(Hash) and x["name"] == key }
101
+ elsif @doc.is_a? Hash
102
+ obj = @doc[key]
103
+ end
104
+
105
+ raise NavigationError.new(@doc_url, key, @doc) unless obj
106
+
107
+ if obj.is_a?(Hash) && obj["id"]
108
+ url = obj["id"]
109
+ if @doc_url
110
+ url = URI.parse(url.to_s)
111
+ url.userinfo = @doc_url.userinfo
112
+ end
113
+
114
+ @doc = json_get(url)
115
+ # strip the wrapper around collections
116
+ if @doc.is_a? Hash and @doc["items"].is_a? Array
117
+ @doc = @doc["items"]
118
+ end
119
+ @doc_url = url
120
+ elsif obj.is_a? Hash
121
+ @doc = obj
122
+ else
123
+ @doc = nil
124
+ end
125
+ end
126
+
127
+ def get(url, headers={})
128
+ response = RestClient.get url.to_s, headers
129
+ print "GET #{url.to_s}\n#{response.body}\n\n" if @parse.dump_response?
130
+ response
131
+ end
132
+
133
+ def json_get(url, headers = {})
134
+ response = get(url,headers.merge(:accept => :json))
135
+ unless response.headers[:content_type] =~ /application\/json/
136
+ raise "Received content type #{response.headers[:content_type]}"
137
+ end
138
+ MultiJson.load(response.body)
139
+ end
140
+
141
+ def json_post(url, body)
142
+ headers = { :accept=>:json, "Content-Type" => :json }
143
+ begin
144
+ response = RestClient.post url.to_s, MultiJson::dump(body), headers
145
+ ensure
146
+ if @parse.dump_response?
147
+ print "POST #{url.to_s}\n#{body}\n-->\n"
148
+ puts (response ? response.body : "ERROR")
149
+ end
150
+ end
151
+ MultiJson::load(response.body)
152
+ end
153
+
154
+ private
155
+ def self.annotations
156
+ @@annotations ||=
157
+ YAML::load_file(File::join(File::dirname(__FILE__), "navigate.yaml"))
158
+ end
159
+
160
+ def self.arg_type(cmd_name, arg_name)
161
+ cmd = annotations["commands"][cmd_name]
162
+ cmd && cmd["args"][arg_name]
163
+ end
164
+
165
+ def convert_arg(cmd_name, arg_name, value)
166
+ value = nil if value == "null"
167
+ case self.class.arg_type(cmd_name, arg_name)
168
+ when "json"
169
+ MultiJson::load(value)
170
+ when "boolean"
171
+ ["true", nil].include?(value)
172
+ else
173
+ value
174
+ end
175
+ end
176
+ end
177
+ end