sentofu 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32c5cc4788ad456cd8121cb497ec1edd2646414cc2006466d18cef63c14d3d12
4
- data.tar.gz: d681e4a0b6c3d95a3ad2bf6e3a50da249c3876503fb1cd057bd5e94fe94f2e58
3
+ metadata.gz: ec10af5cec1acf4c593f766637358a1cc323665bf65cde68c5c727c637582f7f
4
+ data.tar.gz: e75a6edec73c0c0faa5cc7e9a8657f9d348a25bbcc023d3422b4f62dd9e89a5e
5
5
  SHA512:
6
- metadata.gz: 67f28338c633693c0d2bff8157beab313be3d9a07c6b9ecacd7652710ff3282d3fac42a11f3e6e1973374486f1fb6fa13d4ce1472ca958ae2e2fb3cb3a683823
7
- data.tar.gz: e895b60701e76e2f2a50791b3727caa611d6da06bc84e05ef7be7c222531994b25ad3ae48c9c94d38eddfcf2d2a00124ab734166f9ad1bc49d3998c42d05b006
6
+ metadata.gz: 7e8145180ed891775ef9cf4358778091d18fe5f4199cf2c87a899de9ec14e5ab3a60d18cfa93314a3417660a3a2cb34757bedcaefc0d6b51f5da862659f9836a
7
+ data.tar.gz: 2f3a1bf05a54b680bdf6247ea2c9b72cac64a590586341476d9b90fe2c2b27986bbcd32d55f94e190fda85d13ffca4daa5a005930abc50e3f2855ea642b44ee0
data/CHANGELOG.md CHANGED
@@ -2,7 +2,12 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## sentofu 0.1.0 released 2019-05-23
6
+
7
+ * Initial implementation
8
+
9
+
5
10
  ## sentofu 0.0.1 released 2019-05-19
6
11
 
7
- * initial empty release
12
+ * Initial empty release
8
13
 
data/Makefile CHANGED
@@ -40,5 +40,15 @@ test: spec
40
40
 
41
41
  ## specific to project ##
42
42
 
43
- .PHONY: count_lines scan gemspec_validate name cw build push spec
43
+ detail_apis:
44
+ time bundle exec ruby -I lib -r yaml -r sentofu -e "puts YAML.dump(Sentofu.detail_apis)"
45
+ list_apis:
46
+ time bundle exec ruby -I lib -r yaml -r sentofu -e "puts YAML.dump(Sentofu.list_apis)"
47
+ dump_apis:
48
+ time bundle exec ruby -I lib -r sentofu -e "Sentofu.dump_apis"
49
+
50
+
51
+ ## done ##
52
+
53
+ .PHONY: count_lines scan gemspec_validate name cw build push spec detail_apis list_apis dump_apis
44
54
 
data/lib/sentofu.rb CHANGED
@@ -1,21 +1,112 @@
1
1
 
2
2
  require 'json'
3
3
  require 'yaml'
4
+ require 'time'
4
5
  require 'base64'
5
6
  require 'ostruct'
6
7
  require 'net/http'
7
8
 
8
9
  require 'sentofu/http'
9
10
  require 'sentofu/api'
11
+ require 'sentofu/explo'
10
12
 
11
13
 
12
14
  module Sentofu
13
15
 
14
- VERSION = '0.0.1'
16
+ VERSION = '0.1.0'
15
17
 
16
- Dir[File.join(__dir__, 'sentofu/sentifi-*.yaml')].each do |fpath|
18
+ USER_AGENT =
19
+ "Sentofu #{Sentofu::VERSION} - " +
20
+ [ 'Ruby', RUBY_VERSION, RUBY_RELEASE_DATE, RUBY_PLATFORM ].join(' ')
21
+ @user_agent =
22
+ USER_AGENT
17
23
 
18
- Sentofu::Api.make(YAML.load(File.read(fpath)))
24
+ @auth_uri = nil
25
+ @apis = {}
26
+
27
+ class << self
28
+
29
+ attr_reader :auth_uri, :apis
30
+ attr_accessor :user_agent
31
+
32
+ def init(versions=%w[ common:1.0.0 company:1.0.0 markets:1.0.0 ])
33
+
34
+ vers = split_versions(versions)
35
+
36
+ vers << %w[ auth * ] unless vers.find { |n, _| n == 'auth' }
37
+
38
+ vers.each do |api_name, ver_pattern|
39
+
40
+ doc_uri =
41
+ 'https://api.swaggerhub.com/apis/sentifi-api-docs' +
42
+ (api_name == 'auth' ?
43
+ '/sentifi-api_o_auth_2_authentication_and_authorization/' :
44
+ "/sentifi-intelligence_#{api_name}_api/")
45
+
46
+ metas = Sentofu::Http.get_and_parse(doc_uri)
47
+
48
+ v, u, meta = metas['apis']
49
+ .collect { |m|
50
+ prs = m['properties']
51
+ [ prs.find { |pr| pr['type'] == 'X-Version' }['value'],
52
+ prs.find { |pr| pr['type'] == 'Swagger' }['url'],
53
+ m ] }
54
+ .select { |v, _, _| version_match(v, ver_pattern) }
55
+ .sort_by(&:first)
56
+ .first
57
+
58
+ spec = Sentofu::Http.get_and_parse(u)
59
+ spec[:meta] = meta
60
+
61
+ if api_name == 'auth'
62
+ @auth_uri = spec['servers'][0]['url'] + spec['paths'].keys.first
63
+ else
64
+ api = Sentofu::Api.new(api_name, spec)
65
+ Sentofu.define_singleton_method(api_name) { api }
66
+ @apis[api_name] = api
67
+ end
68
+ end
69
+ end
70
+
71
+ def credentials=(cs)
72
+
73
+ apis.each { |_, api| api.credentials = cs }
74
+ end
75
+
76
+ protected
77
+
78
+ def split_versions(vs)
79
+
80
+ case vs
81
+ when Array
82
+ vs
83
+ .select { |v| v.strip.length > 0 }
84
+ .collect { |v| split_version(v) }
85
+ when String
86
+ vs.split(/[,;]/)
87
+ .select { |v| v.strip.length > 0 }
88
+ .collect { |v| split_version(v) }
89
+ else
90
+ vs
91
+ end
92
+ end
93
+
94
+ def split_version(v)
95
+
96
+ v.is_a?(Array) ? v : v.split(':').collect(&:strip)
97
+ end
98
+
99
+ def version_match(version, pattern)
100
+
101
+ ves = version.split('.')
102
+ pattern.split('.').each do |pa|
103
+ ve = ves.shift
104
+ next if pa == 'x' || pa == '*'
105
+ return false if ve != pa
106
+ end
107
+
108
+ true
109
+ end
19
110
  end
20
111
  end
21
112
 
@@ -31,7 +122,13 @@ if $0 == __FILE__
31
122
  #pp y['paths'].keys
32
123
 
33
124
  #p Sentofu.company.class
34
- p Sentofu.company.topic_search
125
+ #p Sentofu.company.topic_search
35
126
  #pp Sentofu.markets
127
+
128
+ #puts Sentofu.company.paths.keys
129
+
130
+ t0 = Time.now
131
+ Sentofu.init
132
+ puts "took #{Time.now - t0}s..."
36
133
  end
37
134
 
data/lib/sentofu/api.rb CHANGED
@@ -3,6 +3,8 @@ module Sentofu
3
3
 
4
4
  class Resource
5
5
 
6
+ TVP = '_sentofu_' # THREAD VARIABLE PREFIX
7
+
6
8
  attr_reader :parent, :segment
7
9
 
8
10
  def initialize(parent, segment)
@@ -14,20 +16,22 @@ module Sentofu
14
16
 
15
17
  def add_segment(segment)
16
18
 
17
- m = segment.match(/\A\{([^}]+)\}\z/)
19
+ m = segment.match(/\A\{[^}]+\}\z/)
18
20
  mth = m ? :[] : segment
19
21
 
20
22
  return @children[mth] if @children[mth]
21
23
 
24
+ res = @children[mth] = Sentofu::Resource.new(self, segment)
25
+
22
26
  if mth == :[]
23
- @children[:[]] = res = Sentofu::Resource.new(self, m[1])
24
- define_singleton_method(:[]) { |i| res.index = i; res }
25
- res
27
+ define_singleton_method(:[]) { |i|
28
+ Thread.current.thread_variable_set(TVP + segment, i); res }
26
29
  else
27
- @children[mth] = res = Sentofu::Resource.new(self, segment)
28
- define_singleton_method(mth) { res }
29
- res
30
+ define_singleton_method(mth) {
31
+ res }
30
32
  end
33
+
34
+ res
31
35
  end
32
36
 
33
37
  def add_leaf_segment(segment, point)
@@ -45,49 +49,105 @@ module Sentofu
45
49
 
46
50
  def fetch(segment, point, index, query)
47
51
 
48
- p [ :fetch, segment, '(point)', index, query ]
49
- p path(segment)
52
+ #p [ :fetch, segment, '(point)', index, query ]
53
+ #p path(segment)
50
54
  #pp point
51
- self.index = index if index
55
+ Thread.current.thread_variable_set(TVP + segment, index) if index
56
+
57
+ q = rectify_query_parameters(point, query)
58
+
59
+ pa = File.join(path, segment)
60
+ pa = pa.gsub(/_/, '-')
61
+
62
+ pa = pa + '?' + URI.encode_www_form(q) if q.any?
52
63
 
53
- validate_query_parameters(point, query)
64
+ return query.merge(path: pa) if query[:debug]
54
65
 
55
- nil
66
+ JSON.parse(Sentofu::Http.get(pa, api.token).body)
56
67
  end
57
68
 
58
- def path(segment=nil)
69
+ def path
59
70
 
60
- segment ||= self.segment
61
- segment = segment.gsub(/_/, '-')
71
+ seg =
72
+ segment[0, 1] == '{' ?
73
+ Thread.current.thread_variable_get(TVP + segment).to_s :
74
+ segment
62
75
 
63
76
  if parent
64
- File.join(parent.send(:path), segment)
77
+ File.join(parent.send(:path), seg)
65
78
  else
66
- '/' + segment
79
+ seg
67
80
  end
68
81
  end
69
82
 
70
- def validate_query_parameters(point, query)
83
+ def rectify_query_parameters(point, query)
84
+
85
+ q = query
86
+ .inject({}) { |h, (k, v)|
87
+ next h if k == :debug
88
+ h[k.to_s.gsub(/_/, '-')] =
89
+ case v
90
+ when Symbol then v.to_s
91
+ when Array then v.collect(&:to_s).join(',')
92
+ #when Time, Date then v.utc.strftime('%F')
93
+ when Time, Date then v.strftime('%F')
94
+ else v
95
+ end
96
+ h }
97
+
98
+ point['get']['parameters']
99
+ .each { |par|
100
+
101
+ next if par['in'] != 'query'
102
+
103
+ nam = par['name']
104
+ key = nam.gsub(/-/, '_')
105
+
106
+ fail ArgumentError.new(
107
+ "missing query parameter :#{key}"
108
+ ) if par['required'] == true && !q.has_key?(nam)
109
+
110
+ v = q[nam]
111
+ typ = par['schema']['type']
112
+
113
+ fail ArgumentError.new(
114
+ "argument to :#{key} not an integer"
115
+ ) if v && typ == 'integer' && ! v.is_a?(Integer)
116
+ fail ArgumentError.new(
117
+ "argument to :#{key} not a string (or a symbol)"
118
+ ) if v && typ == 'string' && ! v.is_a?(String)
119
+
120
+ enu = par['schema']['enum']
121
+
122
+ fail ArgumentError.new(
123
+ "value #{v.inspect} for :#{key} not present in #{enu.inspect}"
124
+ ) if v && enu && ! enu.include?(v) }
125
+
126
+ q
127
+ end
128
+
129
+ def api
71
130
 
72
- point['get']['parameters'].select { |pa| pa['in'] == 'query' }
73
- .each { |pa|
74
- k = (pa[:key] ||= pa['name'].gsub(/-/, '_').to_sym)
75
- fail ArgumentError.new("missing query parameter #{k.inspect}") \
76
- if pa['required'] == true && !query.has_key?(k) }
77
- pp params
131
+ parent ? parent.api : self
78
132
  end
79
133
  end
80
134
 
81
135
  class Api < Resource
82
136
 
83
137
  attr_reader :spec
138
+ attr_accessor :credentials
84
139
 
85
- def initialize(spec, name)
140
+ def initialize(name, spec)
86
141
 
87
- super(nil, nil)
142
+ super(nil, spec['servers'].first['url'])
88
143
 
89
- @spec = spec
90
144
  @name = name
145
+ @spec = spec
146
+
147
+ @credentials = nil
148
+ @token = nil
149
+
150
+ inflate_parameters
91
151
 
92
152
  #puts "================== #{name}"
93
153
  spec['paths'].each do |path, point|
@@ -103,17 +163,71 @@ pp params
103
163
  end
104
164
  end
105
165
 
106
- class << self
166
+ def token
107
167
 
108
- def make(api_spec)
168
+ if @token && @token.not_expired?
169
+ @token
170
+ else
171
+ @token = Sentofu::Http.fetch_token(@credentials)
172
+ end
173
+ end
174
+
175
+ def paths
176
+
177
+ @spec['paths']
178
+ end
179
+
180
+ def query(path, params={})
181
+
182
+ pa = server + path
183
+ pa = pa + '?' + URI.encode_www_form(params) if params.any?
184
+
185
+ return params.merge(path: pa) if params[:debug]
186
+
187
+ JSON.parse(Sentofu::Http.get(pa, token).body)
188
+ end
109
189
 
110
- name =
111
- api_spec['info']['title'].split(' - ').last[0..-4].strip
112
- .gsub(/([a-z])([A-Z])/) { |_| $1 + '_' + $2.downcase }
113
- .gsub(/([A-Z])/) { |c| c.downcase }
190
+ def modified
114
191
 
115
- api = Sentofu::Api.new(api_spec, name)
116
- (class << Sentofu; self; end).define_method(name) { api }
192
+ Time.parse(
193
+ @spec[:meta]['properties']
194
+ .find { |pr| pr['type'] == 'X-Modified' }['value'])
195
+ end
196
+
197
+ def version
198
+
199
+ @spec[:meta]['properties']
200
+ .find { |pr| pr['type'] == 'X-Version' }['value']
201
+ end
202
+
203
+ protected
204
+
205
+ def server
206
+
207
+ @spec['servers'].first['url']
208
+ end
209
+
210
+ def inflate_parameters
211
+
212
+ pars = @spec['components']['parameters']
213
+ return unless pars
214
+
215
+ refs = pars
216
+ .inject({}) { |h, (k, v)|
217
+ h["#/components/parameters/#{k}"] = v
218
+ h }
219
+
220
+ @spec['paths'].each do |_, pa|
221
+ pa.each do |_, me|
222
+ next unless me['parameters']
223
+ me['parameters'] = me['parameters']
224
+ .collect { |pm|
225
+ if ref = pm['$ref']
226
+ refs[ref] || fail("found no $ref #{ref.inspect} in spec")
227
+ else
228
+ pm
229
+ end }
230
+ end
117
231
  end
118
232
  end
119
233
  end
@@ -0,0 +1,47 @@
1
+
2
+ module Sentofu
3
+
4
+ class << self
5
+
6
+ def list_apis
7
+
8
+ detail_apis['apis']
9
+ .collect { |meta|
10
+
11
+ ps = meta['properties']
12
+
13
+ u = ps.find { |p| p['type'] == 'Swagger' }['url']
14
+ v = ps.find { |p| p['type'] == 'X-Version' }['value']
15
+
16
+ m = u.match(/intelligence_([^_]+)_api/)
17
+ n = m ? m[1] : meta['name']
18
+
19
+ d = meta['description']
20
+
21
+ { 'n' => n, 'v' => v, 'd' => d, 'u' => u } }
22
+ end
23
+
24
+ def detail_apis
25
+
26
+ Sentofu::Http.get_and_parse(
27
+ 'https://api.swaggerhub.com/apis/sentifi-api-docs/')
28
+ end
29
+
30
+ #- curl https://api.swaggerhub.com/apis/sentifi-api-docs/sentifi-intelligence_company_api/1.0.0/swagger.yaml > api_company.yaml
31
+ def dump_apis
32
+
33
+ puts
34
+
35
+ list_apis.each do |h|
36
+ n = h['n']; n = "auth" if n.match(/auth/i)
37
+ fn = "api_#{n}_#{h['v']}.yaml"
38
+ res = Sentofu::Http.get(h['u'] + '/swagger.yaml')
39
+ File.open(fn, 'wb') { |f| f.write(res.body) }
40
+ puts " wrote #{fn}"
41
+ end
42
+ end
43
+
44
+ #protected
45
+ end
46
+ end
47
+
data/lib/sentofu/http.rb CHANGED
@@ -3,39 +3,71 @@ module Sentofu
3
3
 
4
4
  module Http
5
5
 
6
- HOST = 'apis.sentifi.com'
7
-
8
6
  class << self
9
7
 
10
8
  def fetch_token(credentials=nil)
11
9
 
10
+ u = URI(Sentofu.auth_uri)
11
+
12
12
  cs = narrow_credentials(credentials)
13
13
 
14
14
  a = Base64.encode64("#{cs.id}:#{cs.secret}").strip
15
15
  #p a
16
16
 
17
- req = Net::HTTP::Post.new('/v1/oauth/token')
17
+ req = Net::HTTP::Post.new(u.path)
18
18
  req.add_field('Content-Type', 'application/json')
19
19
  req.add_field('Authorization', a)
20
20
 
21
21
  req.body = JSON.dump(
22
22
  grant_type: 'password', username: cs.user, password: cs.pass)
23
23
 
24
- res = request(req)
25
- #pp res
24
+ res = make_http(u).request(req)
25
+
26
+ Sentofu::Token.new(res)
27
+ end
28
+
29
+ def get(uri, token=nil)
30
+
31
+ u = URI(uri)
32
+
33
+ #t0 = Time.now
34
+ res = make_http(u).request(make_get_req(u, token))
35
+ #def res.headers; r = {}; each_header { |k, v| r[k] = v }; r; end
36
+ #puts "*** GET #{uri} took #{Time.now - t0}s"
26
37
 
27
- OpenStruct.new(JSON.parse(res.body))
38
+ res
39
+ end
40
+
41
+ def get_and_parse(uri, token=nil)
42
+
43
+ JSON.parse(get(uri, token).body)
28
44
  end
29
45
 
30
46
  protected
31
47
 
32
- def request(req)
48
+ def make_http(uri)
33
49
 
34
- t = Net::HTTP.new(HOST, 443)
35
- t.use_ssl = true
36
- #t.verify_mode = OpenSSL::SSL::VERIFY_NONE # avoid
50
+ #p uri.to_s
51
+ t = Net::HTTP.new(uri.host, uri.port)
52
+ t.use_ssl = (uri.scheme == 'https')
53
+ #t.set_debug_output($stdout) if uri.to_s.match(/search/)
37
54
 
38
- t.request(req)
55
+ t
56
+ end
57
+
58
+ def make_get_req(uri, token)
59
+
60
+ req = Net::HTTP::Get.new(uri.to_s)
61
+ req.instance_eval { @header.clear }
62
+ def req.set_header(k, v); @header[k] = [ v ]; end
63
+
64
+ req.set_header('User-Agent', Sentofu.user_agent)
65
+ req.set_header('Accept', 'application/json')
66
+
67
+ req.set_header('Authorization', token.header_value) if token
68
+ #pp req.instance_variable_get(:@header)
69
+
70
+ req
39
71
  end
40
72
 
41
73
  def narrow_credentials(o)
@@ -55,5 +87,24 @@ module Sentofu
55
87
  end
56
88
  end
57
89
  end
90
+
91
+ class Token
92
+
93
+ def initialize(res)
94
+
95
+ @h = JSON.parse(res.body)
96
+ @expires_at = Time.now + @h['expires_in']
97
+ end
98
+
99
+ def not_expired?
100
+
101
+ Time.now < @expires_at
102
+ end
103
+
104
+ def header_value
105
+
106
+ 'Bearer ' + @h['access_token']
107
+ end
108
+ end
58
109
  end
59
110
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentofu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Mettraux
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-18 00:00:00.000000000 Z
11
+ date: 2019-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -37,6 +37,7 @@ files:
37
37
  - README.md
38
38
  - lib/sentofu.rb
39
39
  - lib/sentofu/api.rb
40
+ - lib/sentofu/explo.rb
40
41
  - lib/sentofu/http.rb
41
42
  - sentofu.gemspec
42
43
  homepage: http://github.com/jmettraux/sentofu