pe-razor-client 0.14.0

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