kong-client 0.4.0

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.
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