kong-client 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.hound.yml +2 -0
  4. data/.rubocop.yml +360 -0
  5. data/.travis.yml +11 -0
  6. data/CHANGELOG.md +37 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE +191 -0
  9. data/README.md +295 -0
  10. data/Rakefile +5 -0
  11. data/kong.gemspec +24 -0
  12. data/lib/kong/acl.rb +8 -0
  13. data/lib/kong/api.rb +19 -0
  14. data/lib/kong/base.rb +180 -0
  15. data/lib/kong/basic_auth.rb +8 -0
  16. data/lib/kong/belongs_to_api.rb +30 -0
  17. data/lib/kong/belongs_to_consumer.rb +30 -0
  18. data/lib/kong/client.rb +232 -0
  19. data/lib/kong/consumer.rb +103 -0
  20. data/lib/kong/error.rb +10 -0
  21. data/lib/kong/hmac_auth.rb +9 -0
  22. data/lib/kong/jwt.rb +8 -0
  23. data/lib/kong/key_auth.rb +8 -0
  24. data/lib/kong/oauth2_token.rb +14 -0
  25. data/lib/kong/oauth_app.rb +8 -0
  26. data/lib/kong/plugin.rb +31 -0
  27. data/lib/kong/server.rb +23 -0
  28. data/lib/kong/target.rb +61 -0
  29. data/lib/kong/upstream.rb +24 -0
  30. data/lib/kong/util.rb +16 -0
  31. data/lib/kong/version.rb +3 -0
  32. data/lib/kong.rb +20 -0
  33. data/spec/kong/acl_spec.rb +19 -0
  34. data/spec/kong/api_spec.rb +32 -0
  35. data/spec/kong/base_spec.rb +169 -0
  36. data/spec/kong/basic_auth_spec.rb +26 -0
  37. data/spec/kong/client_spec.rb +297 -0
  38. data/spec/kong/consumer_spec.rb +72 -0
  39. data/spec/kong/error_spec.rb +23 -0
  40. data/spec/kong/hmac_auth_spec.rb +26 -0
  41. data/spec/kong/key_auth_spec.rb +26 -0
  42. data/spec/kong/oauth2_token_spec.rb +19 -0
  43. data/spec/kong/oauth_app_spec.rb +19 -0
  44. data/spec/kong/plugin_spec.rb +62 -0
  45. data/spec/kong/server_spec.rb +39 -0
  46. data/spec/kong/target_spec.rb +53 -0
  47. data/spec/kong/upstream_spec.rb +37 -0
  48. data/spec/kong/util_spec.rb +29 -0
  49. data/spec/spec_helper.rb +19 -0
  50. data/tasks/rspec.rake +5 -0
  51. metadata +153 -0
@@ -0,0 +1,232 @@
1
+ require 'singleton'
2
+ require 'json'
3
+ require 'excon'
4
+ require_relative './error'
5
+
6
+ module Kong
7
+ class Client
8
+ class << self
9
+ attr_accessor :http_client
10
+ end
11
+
12
+ include Singleton
13
+
14
+ attr_accessor :default_headers
15
+
16
+ # Initialize api client
17
+ #
18
+ def initialize
19
+ Excon.defaults[:ssl_verify_peer] = false if ignore_ssl_errors?
20
+ @api_url = api_url
21
+ self.class.http_client = Excon.new(@api_url, omit_default_port: true)
22
+ @default_headers = { 'Accept' => 'application/json' }
23
+ end
24
+
25
+ def self.api_url
26
+ @api_url || ENV['KONG_URI'] || 'http://localhost:8001'
27
+ end
28
+
29
+ def self.api_url=(url)
30
+ @api_url = url
31
+ @http_client = Excon.new(self.api_url, omit_default_port: true)
32
+ end
33
+
34
+ def http_client
35
+ self.class.http_client
36
+ end
37
+
38
+ # Kong Admin API URL
39
+ #
40
+ # @return [String]
41
+ def api_url
42
+ self.class.api_url
43
+ end
44
+
45
+ def api_url=(url)
46
+ @api_url = url
47
+ end
48
+
49
+ # Get request
50
+ #
51
+ # @param [String] path
52
+ # @param [Hash,NilClass] params
53
+ # @param [Hash] headers
54
+ # @return [Hash]
55
+ def get(path, params = nil, headers = {})
56
+ response = http_client.get(
57
+ path: path,
58
+ query: encode_params(params),
59
+ headers: request_headers(headers)
60
+ )
61
+ if response.status == 200
62
+ parse_response(response)
63
+ else
64
+ handle_error_response(response)
65
+ end
66
+ end
67
+
68
+ # Post request
69
+ #
70
+ # @param [String] path
71
+ # @param [Object] obj
72
+ # @param [Hash] params
73
+ # @param [Hash] headers
74
+ # @return [Hash]
75
+ def post(path, obj, params = {}, headers = {})
76
+ request_headers = request_headers(headers)
77
+ request_options = {
78
+ path: path,
79
+ headers: request_headers,
80
+ body: encode_body(obj, request_headers['Content-Type']),
81
+ query: encode_params(params)
82
+ }
83
+ response = http_client.post(request_options)
84
+ if [200, 201].include?(response.status)
85
+ parse_response(response)
86
+ else
87
+ handle_error_response(response)
88
+ end
89
+ end
90
+
91
+ # Patch request
92
+ #
93
+ # @param [String] path
94
+ # @param [Object] obj
95
+ # @param [Hash] params
96
+ # @param [Hash] headers
97
+ # @return [Hash]
98
+ def patch(path, obj, params = {}, headers = {})
99
+ request_headers = request_headers(headers)
100
+ request_options = {
101
+ path: path,
102
+ headers: request_headers,
103
+ body: encode_body(obj, request_headers['Content-Type']),
104
+ query: encode_params(params)
105
+ }
106
+
107
+ response = http_client.patch(request_options)
108
+ if [200, 201].include?(response.status)
109
+ parse_response(response)
110
+ else
111
+ handle_error_response(response)
112
+ end
113
+ end
114
+
115
+ # Put request
116
+ #
117
+ # @param [String] path
118
+ # @param [Object] obj
119
+ # @param [Hash] params
120
+ # @param [Hash] headers
121
+ # @return [Hash]
122
+ def put(path, obj, params = {}, headers = {})
123
+ request_headers = request_headers(headers)
124
+ request_options = {
125
+ path: path,
126
+ headers: request_headers,
127
+ body: encode_body(obj, request_headers['Content-Type']),
128
+ query: encode_params(params)
129
+ }
130
+
131
+ response = http_client.put(request_options)
132
+ if [200, 201].include?(response.status)
133
+ parse_response(response)
134
+ else
135
+ handle_error_response(response)
136
+ end
137
+ end
138
+
139
+ # Delete request
140
+ #
141
+ # @param [String] path
142
+ # @param [Hash,String] body
143
+ # @param [Hash] params
144
+ # @param [Hash] headers
145
+ # @return [Hash]
146
+ def delete(path, body = nil, params = {}, headers = {})
147
+ request_headers = request_headers(headers)
148
+ request_options = {
149
+ path: path,
150
+ headers: request_headers,
151
+ body: encode_body(body, request_headers['Content-Type']),
152
+ query: encode_params(params)
153
+ }
154
+ response = http_client.delete(request_options)
155
+ unless response.status == 204
156
+ handle_error_response(response)
157
+ end
158
+ end
159
+
160
+ private
161
+
162
+ ##
163
+ # Get request headers
164
+ #
165
+ # @param [Hash] headers
166
+ # @return [Hash]
167
+ def request_headers(headers = {})
168
+ @default_headers.merge(headers)
169
+ end
170
+
171
+ ##
172
+ # Encode body based on content type
173
+ #
174
+ # @param [Object] body
175
+ # @param [String] content_type
176
+ def encode_body(body, content_type)
177
+ if content_type == 'application/json'
178
+ dump_json(body)
179
+ else
180
+ body
181
+ end
182
+ end
183
+
184
+ def encode_params(params)
185
+ return nil if params.nil?
186
+ URI.encode_www_form(params)
187
+ end
188
+
189
+ ##
190
+ # Parse response
191
+ #
192
+ # @param [HTTP::Message]
193
+ # @return [Object]
194
+ def parse_response(response)
195
+ if response.headers['Content-Type'].include?('application/json')
196
+ parse_json(response.body)
197
+ else
198
+ response.body
199
+ end
200
+ end
201
+
202
+ ##
203
+ # Parse json
204
+ #
205
+ # @param [String] json
206
+ # @return [Hash,Object,NilClass]
207
+ def parse_json(json)
208
+ JSON.parse(json) rescue nil
209
+ end
210
+
211
+ ##
212
+ # Dump json
213
+ #
214
+ # @param [Object] obj
215
+ # @return [String]
216
+ def dump_json(obj)
217
+ JSON.dump(obj)
218
+ end
219
+
220
+ def ignore_ssl_errors?
221
+ ENV['SSL_IGNORE_ERRORS'] == 'true'
222
+ end
223
+
224
+ def handle_error_response(response)
225
+ message = response.body
226
+ if response.status == 404 && message == ''
227
+ message = 'Not found'
228
+ end
229
+ raise Error.new(response.status, message)
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,103 @@
1
+ module Kong
2
+ class Consumer
3
+ include Base
4
+
5
+ ATTRIBUTE_NAMES = %w(id custom_id username created_at).freeze
6
+ API_END_POINT = '/consumers/'.freeze
7
+
8
+ def delete
9
+ self.oauth2_tokens.each do |token|
10
+ token.delete
11
+ end
12
+ super
13
+ end
14
+
15
+ # List plugins
16
+ #
17
+ # @return [Array<Kong::Plugin>]
18
+ def plugins
19
+ Plugin.list({ consumer_id: self.id })
20
+ end
21
+
22
+ # List OAuth applications
23
+ #
24
+ # @return [Array<Kong::OAuthApp>]
25
+ def oauth_apps
26
+ apps = []
27
+ response = client.get("#{@api_end_point}#{self.id}/oauth2") rescue nil
28
+ if response
29
+ response['data'].each do |attributes|
30
+ apps << Kong::OAuthApp.new(attributes)
31
+ end
32
+ end
33
+ apps
34
+ end
35
+
36
+ # List KeyAuth credentials
37
+ #
38
+ # @return [Array<Kong::KeyAuth]
39
+ def basic_auths
40
+ apps = []
41
+ response = client.get("#{@api_end_point}#{self.id}/basic-auth") rescue nil
42
+ if response
43
+ response['data'].each do |attributes|
44
+ apps << Kong::BasicAuth.new(attributes)
45
+ end
46
+ end
47
+ apps
48
+ end
49
+
50
+ # List KeyAuth credentials
51
+ #
52
+ # @return [Array<Kong::KeyAuth]
53
+ def key_auths
54
+ apps = []
55
+ response = client.get("#{@api_end_point}#{self.id}/key-auth") rescue nil
56
+ if response
57
+ response['data'].each do |attributes|
58
+ apps << Kong::KeyAuth.new(attributes)
59
+ end
60
+ end
61
+ apps
62
+ end
63
+
64
+ # List OAuth2Tokens
65
+ #
66
+ # @return [Array<Kong::OAuth2Token>]
67
+ def oauth2_tokens
68
+ if self.custom_id
69
+ OAuth2Token.list({ authenticated_userid: self.custom_id })
70
+ else
71
+ []
72
+ end
73
+ end
74
+
75
+ # List Acls
76
+ #
77
+ # @return [Array<Kong::Acl>]
78
+ def acls
79
+ acls = []
80
+ response = client.get("#{@api_end_point}#{self.id}/acls") rescue nil
81
+ if response
82
+ response['data'].each do |attributes|
83
+ acls << Kong::Acl.new(attributes)
84
+ end
85
+ end
86
+ acls
87
+ end
88
+
89
+ # List JWTs
90
+ #
91
+ # @return [Array<Kong::JWT>]
92
+ def jwts
93
+ apps = []
94
+ response = client.get("#{@api_end_point}#{self.id}/jwt") rescue nil
95
+ if response
96
+ response['data'].each do |attributes|
97
+ apps << Kong::JWT.new(attributes)
98
+ end
99
+ end
100
+ apps
101
+ end
102
+ end
103
+ end
data/lib/kong/error.rb ADDED
@@ -0,0 +1,10 @@
1
+ module Kong
2
+ class Error < StandardError
3
+ attr_reader :status
4
+
5
+ def initialize(status, message)
6
+ @status = status
7
+ super(message)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Kong
2
+ class HmacAuth
3
+ include Base
4
+ include BelongsToConsumer
5
+
6
+ ATTRIBUTE_NAMES = %w(id username consumer_id).freeze
7
+ API_END_POINT = '/hmac-auth/'
8
+ end
9
+ end
data/lib/kong/jwt.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Kong
2
+ class JWT
3
+ include Base
4
+ include BelongsToConsumer
5
+ ATTRIBUTE_NAMES = %w(id key secret consumer_id).freeze
6
+ API_END_POINT = '/jwt/'.freeze
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Kong
2
+ class KeyAuth
3
+ include Base
4
+ include BelongsToConsumer
5
+ ATTRIBUTE_NAMES = %w(id key consumer_id).freeze
6
+ API_END_POINT = "/key-auth/".freeze
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ require_relative './base'
2
+ module Kong
3
+ class OAuth2Token
4
+ include Base
5
+ ATTRIBUTE_NAMES = %w(id credential_id expires_in created_at token_type access_token refresh_token scope authenticated_userid).freeze
6
+ API_END_POINT = "/oauth2_tokens/".freeze
7
+
8
+ # Get OAuthApp resource
9
+ # @return [Kong::OAuthApp]
10
+ def oauth_app
11
+ Kong::OAuthApp.find_by_id(self.credential_id)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ module Kong
2
+ class OAuthApp
3
+ include Base
4
+ include BelongsToConsumer
5
+ ATTRIBUTE_NAMES = %w(id name client_id client_secret redirect_uri consumer_id).freeze
6
+ API_END_POINT = "/oauth2/".freeze
7
+ end
8
+ end
@@ -0,0 +1,31 @@
1
+ require_relative './util'
2
+
3
+ module Kong
4
+ class Plugin
5
+ include Base
6
+ include BelongsToApi
7
+
8
+ ATTRIBUTE_NAMES = %w(id api_id name config enabled consumer_id).freeze
9
+ API_END_POINT = '/plugins/'.freeze
10
+
11
+ # Create resource
12
+ def create
13
+ flatten_config
14
+ super
15
+ end
16
+
17
+ # update resource
18
+ def update
19
+ flatten_config
20
+ super
21
+ end
22
+
23
+ private
24
+
25
+ def flatten_config
26
+ if attributes['config']
27
+ attributes.merge!(Util.flatten(attributes.delete('config'), 'config'))
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module Kong
2
+ class Server
3
+ def self.version
4
+ self.info['version'] rescue nil
5
+ end
6
+
7
+ def self.info
8
+ Client.instance.get('/')
9
+ end
10
+
11
+ def self.status
12
+ Client.instance.get('/status')
13
+ end
14
+
15
+ def self.cluster
16
+ Client.instance.get('/cluster')
17
+ end
18
+
19
+ def self.remove_node(name)
20
+ Client.instance.delete("/cluster/nodes/#{name}")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,61 @@
1
+ module Kong
2
+ class Target
3
+ include Base
4
+
5
+ ATTRIBUTE_NAMES = %w(id upstream_id target weight).freeze
6
+ API_END_POINT = '/targets/'.freeze
7
+
8
+ def self.find(id)
9
+ raise NotImplementedError, 'Kong does not support direct access to targets, you must go via an upstream'
10
+ end
11
+
12
+ def self.list(params = {})
13
+ raise NotImplementedError, 'Kong does not support direct access to targets, you must go via an upstream'
14
+ end
15
+
16
+ def initialize(attributes = {})
17
+ super(attributes)
18
+ raise ArgumentError, 'You must specify an upstream_id' unless self.upstream_id
19
+ end
20
+
21
+ def active?
22
+ self.weight > 0
23
+ end
24
+
25
+ def save
26
+ create
27
+ end
28
+
29
+ def create_or_update
30
+ raise NotImplementedError, 'Kong does not support updating targets, you must delete and re-create'
31
+ end
32
+
33
+ def update
34
+ raise NotImplementedError, 'Kong does not support updating targets, you must delete and re-create'
35
+ end
36
+
37
+ def use_upstream_end_point
38
+ self.api_end_point = "/upstreams/#{self.upstream_id}#{self.class::API_END_POINT}" if self.upstream_id
39
+ end
40
+
41
+ # Get Upstream resource
42
+ # @return [Kong::Upstream]
43
+ def upstream
44
+ @upstream ||= Upstream.find(self.upstream_id)
45
+ end
46
+
47
+ # Set Upstream resource
48
+ # @param [Kong::Upstream] upstream
49
+ def upstream=(upstream)
50
+ @upstream = upstream
51
+ self.upstream_id = upstream.id
52
+ end
53
+
54
+ # Set Upstream id
55
+ # @param [String] id
56
+ def upstream_id=(id)
57
+ super(id)
58
+ use_upstream_end_point
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,24 @@
1
+ module Kong
2
+ class Upstream
3
+ include Base
4
+
5
+ ATTRIBUTE_NAMES = %w(id name slots orderlist).freeze
6
+ API_END_POINT = '/upstreams/'.freeze
7
+
8
+ ##
9
+ # @return [Array<Kong::Target>]
10
+ def targets
11
+ targets = []
12
+ json_data = Client.instance.get("#{API_END_POINT}#{self.id}/targets")
13
+
14
+ if json_data['data']
15
+ json_data['data'].each do |target_data|
16
+ target = Target.new(target_data)
17
+ targets << target if target.active?
18
+ end
19
+ end
20
+
21
+ targets
22
+ end
23
+ end
24
+ end
data/lib/kong/util.rb ADDED
@@ -0,0 +1,16 @@
1
+ module Kong
2
+ module Util
3
+ def self.flatten(cursor, parent_key = nil, memo = {})
4
+ memo.tap do
5
+ case cursor
6
+ when Hash
7
+ cursor.keys.each do |key|
8
+ flatten(cursor[key], [parent_key, key].compact.join('.'), memo)
9
+ end
10
+ else
11
+ memo["#{parent_key}"] = cursor
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Kong
2
+ VERSION = '0.4.0'.freeze
3
+ end
data/lib/kong.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'kong/version'
2
+ require_relative 'kong/base'
3
+ require_relative 'kong/api'
4
+ require_relative 'kong/belongs_to_api'
5
+ require_relative 'kong/client'
6
+ require_relative 'kong/consumer'
7
+ require_relative 'kong/belongs_to_consumer'
8
+ require_relative 'kong/plugin'
9
+ require_relative 'kong/error'
10
+ require_relative 'kong/oauth_app'
11
+ require_relative 'kong/oauth2_token'
12
+ require_relative 'kong/basic_auth'
13
+ require_relative 'kong/hmac_auth'
14
+ require_relative 'kong/key_auth'
15
+ require_relative 'kong/jwt'
16
+ require_relative 'kong/acl'
17
+ require_relative 'kong/target'
18
+ require_relative 'kong/upstream'
19
+ require_relative 'kong/server'
20
+ require_relative 'kong/util'
@@ -0,0 +1,19 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe Kong::Acl do
4
+ let(:valid_attribute_names) do
5
+ %w(id group tags consumer_id)
6
+ end
7
+
8
+ describe '::ATTRIBUTE_NAMES' do
9
+ it 'contains valid names' do
10
+ expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names)
11
+ end
12
+ end
13
+
14
+ describe '::API_END_POINT' do
15
+ it 'contains valid end point' do
16
+ expect(subject.class::API_END_POINT).to eq('/acls/')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe Kong::Api do
4
+ let(:valid_attribute_names) do
5
+ %w(
6
+ id name request_host request_path strip_request_path
7
+ hosts uris strip_uri preserve_host upstream_url retries
8
+ upstream_connect_timeout upstream_send_timeout upstream_read_timeout
9
+ https_only http_if_terminated methods
10
+ )
11
+ end
12
+
13
+ describe 'ATTRIBUTE_NAMES' do
14
+ it 'contains valid names' do
15
+ expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names)
16
+ end
17
+ end
18
+
19
+ describe 'API_END_POINT' do
20
+ it 'contains valid end point' do
21
+ expect(subject.class::API_END_POINT).to eq('/apis/')
22
+ end
23
+ end
24
+
25
+ describe '.plugins' do
26
+ it 'requests plugins attached to Api' do
27
+ subject.id = '12345'
28
+ expect(Kong::Plugin).to receive(:list).with({ api_id: subject.id })
29
+ subject.plugins
30
+ end
31
+ end
32
+ end