xn.rb 0.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ea2329de7679cd2d4e1d11d8e744d27b874421e1
4
+ data.tar.gz: 23b61587ef31d5aca7da249871c88e314ff802da
5
+ SHA512:
6
+ metadata.gz: 28d2d444b53fdd371e7b169af8ab6d64dcd703a31cfc5c8b985da9950950c6694f564a4537fccff9939e6f5e843cdb559a5b775c5f92602addcc76a7a3fd9eb2
7
+ data.tar.gz: aff9f1d1aa456a3b296ed378252d3a91157c9fbc25b349f9099e55e8c078ca3c1fc80db5fe04ecd777476f6fd62347e2e3bd18bfb2970b27e51a780c7622ae77
@@ -0,0 +1,3 @@
1
+ .bundle
2
+ bin
3
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
@@ -0,0 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xn.rb (0.0.2)
5
+ highline
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ highline (1.6.15)
11
+
12
+ PLATFORMS
13
+ java
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ xn.rb!
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 XN LOGIC CORPORATION
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,62 @@
1
+ xn.rb
2
+ =====
3
+
4
+ Ruby API client for XN Logic based applications.
5
+
6
+ Source code available at https://github.com/xnlogic/xn.rb and Pull Requests are welcome!
7
+
8
+ Synopsis
9
+ --------
10
+
11
+ Set up and authentication:
12
+ ```
13
+ require 'rubygems'
14
+ require 'xn.rb'
15
+ api_session = Xn::XnApiSession.new 'https://app.lightmesh.com', 'my@email.com'
16
+ ```
17
+ This will prompt the terminal for your password. If you are not writing an interactive script, set the LMTOKEN environment variable first and the XnApiSession constructor will use that.
18
+ e.g.
19
+
20
+ ```
21
+ ~/my_project $ LMTOKEN='client_name XYZ1234' irb
22
+ 2.1.0p0 :001 > require 'rubygems'
23
+ 2.1.0p0 :002 > require 'xn.rb'
24
+ 2.1.0p0 :003 > api_session = Xn::XnApiSession.new 'https://app.lightmesh.com', 'my@email.com'
25
+ 2.1.0p0 :004 > Using token from env LMTOKEN
26
+ => #<Xn::XnApiSession:0x007feef5c65070.....>
27
+ 2.1.0p0 :005 >
28
+ ```
29
+
30
+ To query the API, there are a few helpers:
31
+ ```
32
+ # NB: These two queries return subtly different results. The first returns a Model record, with all Parts your user
33
+ # has access to, whereas the second returns just the Part data. For more on the difference between Models and Parts see:
34
+ # https://lightmesh.zendesk.com/entries/41374508-Concepts-Types-or-parts-and-Models for
35
+
36
+ response_hash = api_session.find_vertex_by_model 'ip', '/filter/name?name=10.0.0.2'
37
+ response_hash = api_session.find_vertex_by_part 'ip', '/filter/name?name=10.0.0.2'
38
+
39
+ # Create, Update, Find-or-Create:
40
+ new_ip_r_hash = api_session.create_vertex 'ip', name: '10.0.0.3', description: 'New reserved IP for projekt XYZ'
41
+ response_hash = api_session.update_vertex new_ip_r_hash, description: 'New reserved IP for project XYZ'
42
+ response_hash = api_session.find_or_create_by_model_and_name('ip', '10.0.0.3', nil, description: 'New reserved IP for project XYZ')
43
+
44
+ # Get all related vertices:
45
+ api_session.related_vertices(new_ip_r_hash)
46
+
47
+ # Execute an action:
48
+ api_session.exec_action( new_ip_r_hash, 'set_subnet_from_ip', {} ) # Some actions take paramters in the form of a hash
49
+
50
+ ```
51
+ We are always interested in hearing suggestions for more helpers (open an issue at https://github.com/xnlogic/xn.rb)
52
+
53
+ but you can also use the API http wrapper directly for more advanced use-cases:
54
+ ```
55
+ api_session.api.get "/v1/is/ip/filters/name/properties/name/?name[regex]=^(?!10)" do |response|
56
+ response.each do |id, ip|
57
+ puts "IP: #{ip}, xnid: /model/ip/#{id}"
58
+ end
59
+ end
60
+ ```
61
+
62
+
@@ -0,0 +1,3 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
@@ -0,0 +1,206 @@
1
+ if RUBY_VERSION == '1.8.7'
2
+ STDERR.puts <<WARNING
3
+ WARNING: XnApiSession is developed using Ruby in 1.9.3 mode.
4
+ We recommend you use ruby-1.9.3 or if using JRuby, start ruby in 1.9 mode,
5
+ either with the --1.9 flag, or by setting the environment variable
6
+ JRUBY_OPTS=--1.9
7
+ WARNING
8
+ end
9
+ require 'rubygems'
10
+ require 'bundler'
11
+ require 'bundler/setup'
12
+
13
+ require 'json'
14
+ require 'net/http'
15
+ raise "Missing Net::HTTP::Patch in #{RUBY_VERSION}" unless defined? Net::HTTP::Patch
16
+
17
+ require File.join(File.dirname(__FILE__), 'xn', 'api_requestor')
18
+
19
+ module Xn
20
+ class XnApiSession
21
+ if RUBY_PLATFORM == 'java'
22
+ require 'java'
23
+ include_class 'java.lang.System'
24
+ include_class 'java.io.Console'
25
+ else
26
+ require 'highline/import'
27
+ end
28
+
29
+ # Could make these configurable via xn.yml file in the future:
30
+ attr_reader :url, :login_path, :user, :api_suffix
31
+ attr_accessor :api
32
+
33
+ def initialize(server_url, user_email, api_suffix = "v1")
34
+ @url = URI(server_url)
35
+ @login_path = '/sessions.json'
36
+ @user = user_email
37
+ @api_suffix = api_suffix
38
+ @api = ApiRequestor.new url
39
+
40
+ if ENV['LMTOKEN']
41
+ @token = ENV['LMTOKEN']
42
+ puts "Using token from env LMTOKEN"
43
+ else
44
+ @token = token
45
+ end
46
+ @api.token = @token
47
+ end
48
+
49
+ # Get a user's password from the command line
50
+ # don't cache me...
51
+ def password
52
+ if RUBY_PLATFORM == 'java'
53
+ console = System.console
54
+ pass = console.read_password("Password: ")
55
+ java.lang.String.new(pass)
56
+ else
57
+ pass = ask("Password: ") { |q| q.echo = "*" }
58
+ end
59
+ end
60
+
61
+ # Login to the host and prompt for password.
62
+ # return the token
63
+ def login
64
+ request_hash = { user: { email: user, password: password } }
65
+ api.post login_path, request_hash do |response|
66
+ response['token']
67
+ end
68
+ end
69
+
70
+ def token
71
+ @token ||= login
72
+ end
73
+
74
+ # Fetch a single vertex of given model type from the server
75
+ # return a hash of the vertex or nil
76
+ def find_vertex_by_model(model, filter_string)
77
+ debug "find_vertex_by_model(#{model.downcase}, #{filter_string})"
78
+ raise "model is a required argument" if model.nil?
79
+ if filter_string and filter_string[/\?/]
80
+ filter_string = "#{filter_string}&limit=1"
81
+ else
82
+ filter_string = "#{filter_string}?limit=1"
83
+ end
84
+ request_path = "/#{api_suffix}/model/#{model.downcase}/#{filter_string}"
85
+ debug "GET #{request_path}"
86
+ api.get request_path do |response|
87
+ vertex = response.first
88
+ debug "found vertex [#{vertex}]"
89
+ vertex
90
+ end
91
+ end
92
+
93
+ # Fetch a single vertex of given part type from the server
94
+ # return a hash of the vertex or nil
95
+ def find_vertex_by_part(part, filter_string)
96
+ debug "find_vertex_by_part(#{part}, #{filter_string})"
97
+ raise "part is a required argument" if part.nil?
98
+ if filter_string and filter_string[/\?/]
99
+ filer_string = "#{filter_string}&limit=1"
100
+ else
101
+ filter_string = "#{filter_string}?limit=1"
102
+ end
103
+ request_path = "/#{api_suffix}/is/#{part.downcase}/#{filter_string}"
104
+ api.get request_path do |response|
105
+ vertex = response.first
106
+ debug "found vertex [#{vertex}]"
107
+ vertex
108
+ end
109
+ end
110
+
111
+ # Create a vertex of given model with given properties
112
+ # return a hash of the vertex or nil
113
+ def create_vertex(model, props)
114
+ debug "create_vertex(#{model}, #{props})"
115
+ raise "model and :name property are required" if model.nil? or props.nil? or props[:name].nil?
116
+
117
+ request_path = "/#{api_suffix}/model/#{model.downcase}"
118
+ api.put request_path, props do |response|
119
+ if response and response[0] != false
120
+ return vertex = response[2]
121
+ end
122
+ end
123
+ end
124
+
125
+ # update the given vertex with the given hash
126
+ # return a hash of the updates or nil
127
+ def update_vertex(vertex, update_hash)
128
+ debug "update_vertex(#{vertex}, #{update_hash})"
129
+ return nil if vertex.nil? or update_hash.nil?
130
+ if !vertex.is_a? Hash
131
+ vertex = find_vertex_by_part :record, vertex
132
+ end
133
+
134
+ request_path = "/#{api_suffix}/model/#{vertex['meta']['model_name']}/#{vertex['id']}"
135
+ debug "PATCH #{request_path} (#{update_hash.to_json})"
136
+ api.patch request_path, update_hash do |response|
137
+ debug "updated vertex [#{response}]"
138
+ response
139
+ end
140
+ end
141
+
142
+ # find or create a vertex by model and name and optionally provide
143
+ # a filter string to append to the model/xxx/ URL and properties to set
144
+ # if not found.
145
+ #
146
+ # (example filter_string: 'filter/name/?name[value]=myname')
147
+ def find_or_create_by_model_and_name(model, name, filter_string = nil, props = {})
148
+ debug "find_or_create_by_model_and_name(#{model}, #{name}, #{filter_string}, #{props})"
149
+ filter_string = "filter/name/?name[value]=#{name}" unless filter_string
150
+ props = props.merge name: name
151
+ obj = find_vertex_by_model(model, filter_string)
152
+ if obj.nil? or obj.empty?
153
+ debug "about to create a #{model} vertex with #{props} properties" if obj.nil? or obj.empty?
154
+ obj = create_vertex(model, props)
155
+ end
156
+ obj
157
+ end
158
+
159
+ # return all vertices related to the given one
160
+ def related_vertices(vertex)
161
+ debug "related_vertices(#{vertex})"
162
+ if vertex and vertex.is_a? Hash and vertex['meta']['model_name']
163
+ model = vertex['meta']['model_name']
164
+ request_path = "/#{api_suffix}/model/#{model}/#{vertex['id']}/rel"
165
+ api.get request_path do |parts|
166
+ debug " parts: #{parts}"
167
+ related = parts.map do |part|
168
+ find_vertex_by_model model, "#{vertex['id']}/rel/#{part}"
169
+ end.compact.reject do |response|
170
+ response[:status] == 404 # No need to tell us what doesn't exist!
171
+ end
172
+ debug "found related: [#{related}]"
173
+ return related if related.any?
174
+ end
175
+ end
176
+ end
177
+
178
+ # execute the named action with any properties as args
179
+ def exec_action(vertex, action_name, props = {})
180
+ debug "exec_action(#{vertex}, #{action_name}, #{props})"
181
+ request_path = "/#{api_suffix}/model/#{vertex['meta']['model_name']}/#{vertex['id']}/action/#{action_name}"
182
+ debug "POST #{request_path} (#{props.to_json})"
183
+ api.post request_path, props do |response|
184
+ debug "executed #{action_name} on vertex [#{response}]"
185
+ response
186
+ end
187
+ end
188
+
189
+ protected
190
+
191
+ def debug(message = "")
192
+ if ENV['XN_VERBOSITY'] and ENV['XN_VERBOSITY'] == 'debug'
193
+ message = yield if block_given?
194
+ STDERR.puts "DEBUG: #{message}"
195
+ end
196
+ end
197
+
198
+ def verbose(message)
199
+ if ENV['XN_VERBOSITY']
200
+ message = yield if block_given?
201
+ STDERR.puts "#{message}"
202
+ end
203
+ end
204
+ end
205
+ end
206
+
@@ -0,0 +1,98 @@
1
+ # Simple API request wrapper. Instantiate multiple times to hit the
2
+ # server in parallel.
3
+
4
+ module Xn
5
+ class AuthorizationError < StandardError; end
6
+
7
+ class ApiRequestor
8
+ attr_accessor :uri, :token
9
+
10
+ def initialize(server_uri, token = nil)
11
+ raise "#{self.class} Err: First arg must be an instance of URI" if !server_uri.is_a? URI
12
+ @uri = server_uri
13
+ @token = token
14
+ end
15
+
16
+ # Request the resource(s) and a renderable JSON response
17
+ def get(resource_url, &block)
18
+ call_http_server Net::HTTP::Get.new( URI.encode(resource_url) ), &block
19
+ end
20
+
21
+ # Calls a method resource and returns a renderable JSON response
22
+ def post(resource_url, body = nil, &block)
23
+ http_post = Net::HTTP::Post.new resource_url, {'Content-Type' =>'application/json'}
24
+ if body
25
+ body = body.to_json if body.is_a? Enumerable
26
+ http_post.body = body
27
+ end
28
+ call_http_server http_post, &block
29
+ end
30
+
31
+ # Calls a method resource to create a vertex and returns a renderable JSON response
32
+ def put(resource_url, body = nil, &block)
33
+ http_put = Net::HTTP::Put.new resource_url, {'Content-Type' =>'application/json'}
34
+ http_put.body = json_body(body) if body
35
+ call_http_server http_put, &block
36
+ end
37
+
38
+ # Calls a method resource to update a vertex and returns a renderable JSON response
39
+ def patch(resource_url, body = nil, &block)
40
+ http_patch = Net::HTTP::Patch.new resource_url, {'Content-Type' =>'application/json'}
41
+ http_patch.body = json_body(body) if body
42
+ call_http_server http_patch, &block
43
+ end
44
+
45
+ private
46
+
47
+ # Ensure the body is json
48
+ def json_body(body)
49
+ if body.is_a? Enumerable
50
+ body.to_json
51
+ else
52
+ body
53
+ end
54
+ end
55
+
56
+ def call_http_server(request, &block)
57
+ request['AUTHORIZATION'] = token if token
58
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https' ) do |http|
59
+ response = nil
60
+ http.read_timeout = 240
61
+ start_time = Time.now
62
+ t = Thread.new { response = http.request(request) }
63
+ i = 0
64
+ while response.nil? and ( Time.now - start_time < http.read_timeout )
65
+ i += 1
66
+ print '.'
67
+ if i % 20 == 0
68
+ print "\r"
69
+ end
70
+ sleep 0.01
71
+ end
72
+ print "\r"
73
+ t.join
74
+
75
+ begin
76
+ json = JSON.parse(response.body)
77
+ raise AuthorizationError, "#{json['message']} (#{json['parsed_url']})" if response.code.to_i == 401
78
+ rescue JSON::ParserError => e
79
+ # Probably not JSON, return entire body
80
+ return { status: response.code.to_i, body: response.body }
81
+ end
82
+ if block and response.code.to_i < 300
83
+ block.call json
84
+ else
85
+ { status: response.code.to_i, json: json }
86
+ end
87
+ end
88
+ rescue Exception => e
89
+ raise e if e.is_a? AuthorizationError
90
+ raise e if e.is_a? Errno::ECONNREFUSED or e.is_a? Net::HTTPBadRequest
91
+ puts "ERROR making request from: "
92
+ puts request
93
+ puts e.message
94
+ puts e.backtrace
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,3 @@
1
+ module Xn
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "xn/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "xn.rb"
7
+ s.version = Xn::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["David Colebatch", "Darrick Wiebe"]
10
+ s.email = ["dc@xnlogic", "dw@xnlogic"]
11
+ s.homepage = "http://www.xnlogic.com/"
12
+ s.summary = %q{XN Logic API client - first used by LightMesh utilities}
13
+ s.description = %q{Aims to provide a semantic wrapper for the XN Logic graph-db application framework's REST API.}
14
+
15
+ s.add_dependency 'highline'
16
+
17
+ s.rubyforge_project = "xn.rb"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.require_paths = ["lib"]
22
+ end
23
+
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xn.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - David Colebatch
8
+ - Darrick Wiebe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: highline
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ description: Aims to provide a semantic wrapper for the XN Logic graph-db application
29
+ framework's REST API.
30
+ email:
31
+ - dc@xnlogic
32
+ - dw@xnlogic
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - ".gitignore"
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - lib/xn.rb
44
+ - lib/xn/api_requestor.rb
45
+ - lib/xn/version.rb
46
+ - xn.rb.gemspec
47
+ homepage: http://www.xnlogic.com/
48
+ licenses: []
49
+ metadata: {}
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project: xn.rb
66
+ rubygems_version: 2.2.2
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: XN Logic API client - first used by LightMesh utilities
70
+ test_files: []