xn.rb 0.0.2

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