skull_island 1.2.0 → 1.2.2

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: 67a8f014f5c284c8fe9a8201fa1296130abb88a08b52e8f512e935eb178bb337
4
- data.tar.gz: ed28d0b9011b978e0939ae7ba73b8934d147f77df0ebee8f6a38464f6ed12440
3
+ metadata.gz: 782471de2e1ce1e974ac22f0420d33ce0903f909b4286094537c7169caa872f5
4
+ data.tar.gz: 7edb2bcdb5094f27932c2a3e13f6865e969ba6f43e78f34d6a26c7416cb01bc9
5
5
  SHA512:
6
- metadata.gz: e63e8b8e203f408df32846e4aec20cbbcea5ac7688ef2ec06db386c9e1f7f88a13094706dcb2b12dba98639c25d9488209ff6b6ce6b9ce60af3c2237bd39f1b0
7
- data.tar.gz: e150792100e0b64286c7dee6338bf723aa0f2e88487bc9f5846485e410e57207504c974b7e861dc366082e849bea1b845a20d00cf9250951db46da62cc05ce5d
6
+ metadata.gz: 6a83f0d13f17f858332882836b22be450c9974283c9e1a8fe0db20b007f13b080a73d4c9fe43bc3516a2a2a5768821f1a4b7494bf9262874774a63c9feb78068
7
+ data.tar.gz: 7dc758ebad1c39cbaa8b64789ac0ff7db061349cd0a086a2d50cfedf2cf822e6d8745d92a1a976d63f7398b810f7ca634bf46b7f2408709c33b8cbf2a18131d0
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  *.gem
13
+ .DS_Store
@@ -8,7 +8,7 @@ Metrics/LineLength:
8
8
  Max: 100
9
9
 
10
10
  Metrics/ClassLength:
11
- Max: 175
11
+ Max: 190
12
12
 
13
13
  Metrics/ModuleLength:
14
14
  Max: 175
@@ -23,7 +23,7 @@ Metrics/PerceivedComplexity:
23
23
  - 'lib/skull_island/cli.rb'
24
24
 
25
25
  Metrics/AbcSize:
26
- Max: 27
26
+ Max: 28
27
27
 
28
28
  Metrics/BlockLength:
29
29
  Max: 35
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- skull_island (1.2.0)
4
+ skull_island (1.2.2)
5
5
  deepsort (~> 0.4)
6
6
  erubi (~> 1.8)
7
7
  json (~> 2.1)
@@ -15,11 +15,11 @@ GEM
15
15
  specs:
16
16
  addressable (2.4.0)
17
17
  ast (2.4.0)
18
- backports (3.12.0)
19
- deepsort (0.4.1)
18
+ backports (3.15.0)
19
+ deepsort (0.4.2)
20
20
  diff-lcs (1.3)
21
- docile (1.3.1)
22
- domain_name (0.5.20180417)
21
+ docile (1.3.2)
22
+ domain_name (0.5.20190701)
23
23
  unf (>= 0.0.5, < 1.0.0)
24
24
  erubi (1.8.0)
25
25
  ethon (0.12.0)
@@ -28,7 +28,7 @@ GEM
28
28
  multipart-post (>= 1.2, < 3)
29
29
  faraday_middleware (0.13.1)
30
30
  faraday (>= 0.7.4, < 1.0)
31
- ffi (1.10.0)
31
+ ffi (1.11.1)
32
32
  gh (0.15.1)
33
33
  addressable (~> 2.4.0)
34
34
  backports
@@ -39,7 +39,7 @@ GEM
39
39
  highline (1.7.10)
40
40
  http-cookie (1.0.3)
41
41
  domain_name (~> 0.5)
42
- jaro_winkler (1.5.2)
42
+ jaro_winkler (1.5.3)
43
43
  json (2.2.0)
44
44
  launchy (2.4.3)
45
45
  addressable (~> 2.3)
@@ -50,15 +50,13 @@ GEM
50
50
  mime-types-data (~> 3.2015)
51
51
  mime-types-data (3.2019.0331)
52
52
  multi_json (1.13.1)
53
- multipart-post (2.0.0)
53
+ multipart-post (2.1.1)
54
54
  net-http-persistent (2.9.4)
55
55
  net-http-pipeline (1.0.1)
56
56
  netrc (0.11.0)
57
- parallel (1.14.0)
58
- parser (2.6.0.0)
57
+ parallel (1.17.0)
58
+ parser (2.6.3.0)
59
59
  ast (~> 2.4.0)
60
- powerpack (0.1.2)
61
- psych (3.1.0)
62
60
  pusher-client (0.6.2)
63
61
  json
64
62
  websocket (~> 1.0)
@@ -72,32 +70,30 @@ GEM
72
70
  rspec-core (~> 3.8.0)
73
71
  rspec-expectations (~> 3.8.0)
74
72
  rspec-mocks (~> 3.8.0)
75
- rspec-core (3.8.0)
73
+ rspec-core (3.8.2)
76
74
  rspec-support (~> 3.8.0)
77
- rspec-expectations (3.8.2)
75
+ rspec-expectations (3.8.4)
78
76
  diff-lcs (>= 1.2.0, < 2.0)
79
77
  rspec-support (~> 3.8.0)
80
- rspec-mocks (3.8.0)
78
+ rspec-mocks (3.8.1)
81
79
  diff-lcs (>= 1.2.0, < 2.0)
82
80
  rspec-support (~> 3.8.0)
83
- rspec-support (3.8.0)
84
- rubocop (0.65.0)
81
+ rspec-support (3.8.2)
82
+ rubocop (0.74.0)
85
83
  jaro_winkler (~> 1.5.1)
86
84
  parallel (~> 1.10)
87
- parser (>= 2.5, != 2.5.1.1)
88
- powerpack (~> 0.1)
89
- psych (>= 3.1.0)
85
+ parser (>= 2.6)
90
86
  rainbow (>= 2.2.2, < 4.0)
91
87
  ruby-progressbar (~> 1.7)
92
- unicode-display_width (~> 1.4.0)
93
- ruby-progressbar (1.10.0)
94
- simplecov (0.16.1)
88
+ unicode-display_width (>= 1.4.0, < 1.7)
89
+ ruby-progressbar (1.10.1)
90
+ simplecov (0.17.0)
95
91
  docile (~> 1.1)
96
92
  json (>= 1.8, < 3)
97
93
  simplecov-html (~> 0.10.0)
98
94
  simplecov-html (0.10.2)
99
95
  thor (0.20.3)
100
- travis (1.8.9)
96
+ travis (1.8.10)
101
97
  backports
102
98
  faraday (~> 0.9)
103
99
  faraday_middleware (~> 0.9, >= 0.9.1)
@@ -110,11 +106,11 @@ GEM
110
106
  ethon (>= 0.8.0)
111
107
  unf (0.1.4)
112
108
  unf_ext
113
- unf_ext (0.0.7.5)
114
- unicode-display_width (1.4.1)
109
+ unf_ext (0.0.7.6)
110
+ unicode-display_width (1.6.0)
115
111
  websocket (1.2.8)
116
112
  will_paginate (3.1.7)
117
- yard (0.9.18)
113
+ yard (0.9.20)
118
114
 
119
115
  PLATFORMS
120
116
  ruby
@@ -127,7 +123,7 @@ DEPENDENCIES
127
123
  simplecov (~> 0.15)
128
124
  skull_island!
129
125
  travis (~> 1.8)
130
- yard (~> 0.9)
126
+ yard (~> 0.9.20)
131
127
 
132
128
  BUNDLED WITH
133
129
  2.0.1
data/README.md CHANGED
@@ -81,6 +81,8 @@ KONG_ADMIN_URL='https://api-admin.mydomain.com' \
81
81
  skull_island export --verbose /path/to/export.yml
82
82
  ```
83
83
 
84
+ Exporting, by default, exports the entire configuration of a Kong gateway, but will strip out special meta-data tags added by Skull Island to track projects. If, instead, you'd like to export **only** the configuration for a specific project, you can add `--project foo` (where `foo` is the name of your project) to export only those resources associated with it and maintain the special key in the exported YAML.
85
+
84
86
  ### Importing
85
87
 
86
88
  Skull Island also supports importing configurations (both partial and full) from a YAML + ERB document:
@@ -112,6 +114,16 @@ KONG_ADMIN_URL='https://api-admin.mydomain.com' \
112
114
  skull_island import --verbose --test /path/to/export.yml
113
115
  ```
114
116
 
117
+ Note that `--test` has a high likelihood of generating errors with a complicated import if required/dependent resources do not exist.
118
+
119
+ #### Importing with Projects
120
+
121
+ Skull Island 1.2.1 introduces the ability to use a special top-level key in the configuration called `project` that uses meta-data to track which resources belong to a project. This meta-data can safely be added at another time as this tool will "adopt" otherwise matching resources into a project.
122
+
123
+ To use this functionality, either add the `project` key to your configuration file (usually directly below the `version` key) with some value that will be unique on your gateway, or use `--project foo` (where `foo` is the name of your project) as a CLI flag.
124
+
125
+ When using the `project` feature of Skull Island, the CLI tool will automatically clean up old resources no longer found in your config file. This is, in fact, the _only_ circumstance under which this tool actually removes resources. Use this feature with care, as it can delete large swaths of your configuration if used incorrectly. It is **critical** that this value is unique since this project functionality is used to delete resources.
126
+
115
127
  ### Migrating
116
128
 
117
129
  With Skull Island, it is possible to migrate a configuration from a 0.14.x gateway to one compatible with a 1.2.x gateway. If you have a previous export, you can just run `skull_island migrate /path/to/export.yml` and you'll receive a 1.2 compatible config on standard out. If you'd prefer, you can have that config written to a file as well (just like the export command) like so:
@@ -133,6 +145,7 @@ The import/export/migrate CLI functions produce YAML with support for embedded R
133
145
  ```yaml
134
146
  ---
135
147
  version: '1.2'
148
+ project: FooV2
136
149
  certificates: []
137
150
  consumers:
138
151
  - username: foo
@@ -190,7 +203,7 @@ plugins:
190
203
  service: "<%= lookup :service, 'search_api' %>"
191
204
  ```
192
205
 
193
- All top-level keys (other than `version`) require an Array as a parameter, either by providing a list of entries or an empty Array (`[]`). The above shows how to use the `lookup()` function to refer to another resource. This "looks up" the resource type (`service` in this case) by `name` (`search_api` in this case) and resolves its `id`. This function can also be used to lookup a `route` or `upstream` by its `name`, or a `consumer` by its `username`. Note that Kong itself doesn't _require_ `route` resources to have unique names, so you'll need to enforce that practice yourself for `lookup` to be useful for Routes.
206
+ All top-level keys (other than `version` and `project`) require an Array as a parameter, either by providing a list of entries or an empty Array (`[]`). The above shows how to use the `lookup()` function to refer to another resource. This "looks up" the resource type (`service` in this case) by `name` (`search_api` in this case) and resolves its `id`. This function can also be used to lookup a `route` or `upstream` by its `name`, or a `consumer` by its `username`. Note that Kong itself doesn't _require_ `route` resources to have unique names, so you'll need to enforce that practice yourself for `lookup` to be useful for Routes.
194
207
 
195
208
  While technically _any_ Ruby is valid, the following are pretty helpful for templating your YAML files:
196
209
 
@@ -39,6 +39,7 @@ require 'skull_island/api_client_base'
39
39
  require 'skull_island/api_client'
40
40
  require 'skull_island/simple_api_client'
41
41
  require 'skull_island/resource_collection'
42
+ require 'skull_island/helpers/meta'
42
43
  require 'skull_island/helpers/resource'
43
44
  require 'skull_island/helpers/resource_class'
44
45
  require 'skull_island/helpers/migration'
@@ -67,6 +67,12 @@ module SkullIsland
67
67
  end
68
68
  end
69
69
 
70
+ def delete(uri)
71
+ client_action do |client|
72
+ client[uri].delete(json_headers)
73
+ end
74
+ end
75
+
70
76
  private
71
77
 
72
78
  def client_action
@@ -15,9 +15,10 @@ module SkullIsland
15
15
  class_option :verbose, type: :boolean
16
16
 
17
17
  desc 'export [OPTIONS] [OUTPUT|-]', 'Export the current configuration to OUTPUT'
18
+ option :project, desc: 'Project identifier for metadata'
18
19
  def export(output_file = '-')
19
20
  if output_file == '-'
20
- STDERR.puts '[INFO] Outputting to STDOUT' if options['verbose']
21
+ warn '[INFO] Outputting to STDOUT' if options['verbose']
21
22
  else
22
23
  full_filename = File.expand_path(output_file)
23
24
  dirname = File.dirname(full_filename)
@@ -29,6 +30,7 @@ module SkullIsland
29
30
  validate_server_version
30
31
 
31
32
  output = { 'version' => '1.2' }
33
+ output['project'] = options['project'] if options['project']
32
34
 
33
35
  [
34
36
  Resources::Certificate,
@@ -46,6 +48,7 @@ module SkullIsland
46
48
  end
47
49
 
48
50
  desc 'import [OPTIONS] [INPUT|-]', 'Import a configuration from INPUT'
51
+ option :project, desc: 'Project identifier for metadata'
49
52
  option :test, type: :boolean, desc: "Don't do anything, just show what would happen"
50
53
  def import(input_file = '-')
51
54
  raw ||= acquire_input(input_file, options['verbose'])
@@ -56,19 +59,23 @@ module SkullIsland
56
59
 
57
60
  validate_config_version input['version']
58
61
 
62
+ import_time = Time.now.utc.to_i
63
+ input['project'] = options['project'] if options['project']
64
+
59
65
  [
60
66
  Resources::Certificate,
61
67
  Resources::Consumer,
62
68
  Resources::Upstream,
63
69
  Resources::Service,
64
70
  Resources::Plugin
65
- ].each { |clname| import_class(clname, input) }
71
+ ].each { |clname| import_class(clname, input, import_time) }
66
72
  end
67
73
 
68
74
  desc(
69
75
  'migrate [OPTIONS] [INPUT|-] [OUTPUT|-]',
70
76
  'Migrate an older config from INPUT to OUTPUT'
71
77
  )
78
+ option :project, desc: 'Project identifier for metadata'
72
79
  def migrate(input_file = '-', output_file = '-')
73
80
  raw ||= acquire_input(input_file, options['verbose'])
74
81
 
@@ -79,9 +86,10 @@ module SkullIsland
79
86
  validate_migrate_version input['version']
80
87
 
81
88
  output = migrate_config(input)
89
+ output['project'] = options['project'] if options['project']
82
90
 
83
91
  if output_file == '-'
84
- STDERR.puts '[INFO] Outputting to STDOUT' if options['verbose']
92
+ warn '[INFO] Outputting to STDOUT' if options['verbose']
85
93
  STDOUT.puts output.to_yaml
86
94
  else
87
95
  full_filename = File.expand_path(output_file)
@@ -97,23 +105,30 @@ module SkullIsland
97
105
  private
98
106
 
99
107
  def export_class(class_name, output_data)
100
- STDERR.puts "[INFO] Processing #{class_name.route_key}" if options['verbose']
101
- output_data[class_name.route_key] = class_name.all.collect(&:export)
108
+ warn "[INFO] Processing #{class_name.route_key}" if options['verbose']
109
+ output_data[class_name.route_key] = if options['project']
110
+ class_name.where(:project, options['project'])
111
+ .collect(&:export)
112
+ else
113
+ class_name.all.collect(&:export)
114
+ end
102
115
  end
103
116
 
104
- def import_class(class_name, import_data)
105
- STDERR.puts "[INFO] Processing #{class_name.route_key}" if options['verbose']
117
+ def import_class(class_name, import_data, import_time)
118
+ warn "[INFO] Processing #{class_name.route_key}" if options['verbose']
106
119
  class_name.batch_import(
107
120
  import_data[class_name.route_key],
108
121
  verbose: options['verbose'],
109
- test: options['test']
122
+ test: options['test'],
123
+ time: import_time,
124
+ project: import_data['project']
110
125
  )
111
126
  end
112
127
 
113
128
  # Used to pull input from either STDIN or the specified file
114
129
  def acquire_input(input_file, verbose = false)
115
130
  if input_file == '-'
116
- STDERR.puts '[INFO] Reading from STDIN' if verbose
131
+ warn '[INFO] Reading from STDIN' if verbose
117
132
  STDIN.read
118
133
  else
119
134
  full_filename = File.expand_path(input_file)
@@ -133,10 +148,10 @@ module SkullIsland
133
148
  if version && ['1.1', '1.2'].include?(version)
134
149
  validate_server_version
135
150
  elsif version && ['0.14', '1.0'].include?(version)
136
- STDERR.puts '[CRITICAL] Config version is too old. Try `migrate` instead of `import`.'
151
+ warn '[CRITICAL] Config version is too old. Try `migrate` instead of `import`.'
137
152
  exit 2
138
153
  else
139
- STDERR.puts '[CRITICAL] Config version is unknown or not supported.'
154
+ warn '[CRITICAL] Config version is unknown or not supported.'
140
155
  exit 3
141
156
  end
142
157
  end
@@ -145,7 +160,7 @@ module SkullIsland
145
160
  if version && version == '0.14'
146
161
  true
147
162
  else
148
- STDERR.puts '[CRITICAL] Config version must be 0.14 for migration.'
163
+ warn '[CRITICAL] Config version must be 0.14 for migration.'
149
164
  exit 4
150
165
  end
151
166
  end
@@ -155,7 +170,7 @@ module SkullIsland
155
170
  if server_version.match?(/^1.[12]/)
156
171
  true
157
172
  else
158
- STDERR.puts '[CRITICAL] Server version mismatch!'
173
+ warn '[CRITICAL] Server version mismatch!'
159
174
  exit 1
160
175
  end
161
176
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SkullIsland
4
+ module Helpers
5
+ # Useful for embedding meta-data into special tags
6
+ module Meta
7
+ def add_meta(key, value)
8
+ metatag = "_meta~#{key}~#{value}"
9
+
10
+ # filter out any existing duplicate metatags
11
+ existing_tags = raw_tags.reject { |tag| tag.start_with?("_meta~#{key}~") }
12
+
13
+ # Add the new tag directly, bypassing preprocessing
14
+ raw_set('tags', existing_tags + [metatag])
15
+ end
16
+
17
+ def import_time
18
+ metatags['import_time']
19
+ end
20
+
21
+ def import_time=(time)
22
+ add_meta('import_time', time)
23
+ end
24
+
25
+ def remove_meta(key)
26
+ # filter out an existing metatags
27
+ filtered_tags = raw_tags.reject { |tag| tag.start_with?("_meta~#{key}~") }
28
+
29
+ # Bypassing preprocessing
30
+ raw_set('tags', filtered_tags)
31
+ end
32
+
33
+ def metatags
34
+ metadata = {}
35
+ raw_tags.select { |tag| tag.start_with?('_meta~') }.each do |tag|
36
+ key, value = tag.gsub('_meta~', '').split('~', 2)
37
+ metadata[key] = value
38
+ end
39
+ metadata
40
+ end
41
+
42
+ def project
43
+ metatags['project']
44
+ end
45
+
46
+ def project=(project_id)
47
+ unless project_id.is_a?(String) && project_id.match?(/^[\w_\-\.~]+$/)
48
+ raise Exceptions::InvalidArguments, 'project'
49
+ end
50
+
51
+ add_meta('project', project_id)
52
+ end
53
+
54
+ def raw_tags
55
+ reload if @lazy && !@entity.key?('tags')
56
+ @entity['tags'] || []
57
+ end
58
+
59
+ def preprocess_tags(input)
60
+ input.uniq + metatags.map { |k, v| "_meta~#{k}~#{v}" }
61
+ end
62
+
63
+ def postprocess_tags(value)
64
+ (value || []).reject { |tag| tag.start_with?('_meta~') }
65
+ end
66
+
67
+ def supports_meta?
68
+ true
69
+ end
70
+ end
71
+ end
72
+ end
@@ -30,12 +30,13 @@ module SkullIsland
30
30
 
31
31
  def digest
32
32
  Digest::MD5.hexdigest(
33
- digest_properties.sort.map { |prop| "#{prop}=#{send(prop.to_sym)}" }.compact.join(':')
33
+ digest_properties.sort.map { |prp| "#{prp}=#{send(prp.to_sym) || ''}" }.compact.join(':')
34
34
  )
35
35
  end
36
36
 
37
37
  def digest_properties
38
- properties.keys.reject { |k| %i[created_at updated_at].include? k }
38
+ props = properties.keys.reject { |k| %i[created_at updated_at].include? k }
39
+ supports_meta? ? props + [:project] : props
39
40
  end
40
41
 
41
42
  # Tests for an existing version of this resource based on its properties rather than its `id`
@@ -45,8 +46,8 @@ module SkullIsland
45
46
  entity_data = @api_client.cache(result.first.relative_uri.to_s) do |client|
46
47
  client.get(result.first.relative_uri.to_s)
47
48
  end
48
- @entity = entity_data
49
- @lazy = false
49
+ @entity = entity_data
50
+ @lazy = false
50
51
  @tainted = false
51
52
  true
52
53
  else
@@ -172,8 +173,8 @@ module SkullIsland
172
173
  entity_data = @api_client.cache(relative_uri.to_s) do |client|
173
174
  client.get(relative_uri.to_s)
174
175
  end
175
- @entity = entity_data
176
- @lazy = false
176
+ @entity = entity_data
177
+ @lazy = false
177
178
  @tainted = false
178
179
  true
179
180
  end
@@ -199,6 +200,10 @@ module SkullIsland
199
200
  self.class.relative_uri
200
201
  end
201
202
 
203
+ def supports_meta?
204
+ false
205
+ end
206
+
202
207
  # ActiveRecord ActiveModel compatibility method
203
208
  def update(params)
204
209
  new_params = {}
@@ -179,6 +179,13 @@ module SkullIsland
179
179
  )
180
180
  end
181
181
 
182
+ def self.cleanup_except(project, keep_these)
183
+ where(:project, project).reject { |res| keep_these.include?(res.id) }.map do |res|
184
+ puts "[WARN] ! Removing #{name} (#{res.id})"
185
+ res.destroy
186
+ end
187
+ end
188
+
182
189
  def initialize(options = {})
183
190
  # TODO: better options validations
184
191
  raise Exceptions::InvalidOptions unless options.is_a?(Hash)
@@ -207,5 +214,17 @@ module SkullIsland
207
214
  def relative_uri
208
215
  "#{self.class.relative_uri}/#{id}"
209
216
  end
217
+
218
+ private
219
+
220
+ # A way to add things _without_ preprocessing them
221
+ def raw_set(key, value)
222
+ raise Exceptions::ImmutableModification if immutable?
223
+
224
+ @entity[key.to_s] = value
225
+
226
+ @modified_properties << key.to_sym
227
+ @tainted = true
228
+ end
210
229
  end
211
230
  end
@@ -18,13 +18,18 @@ module SkullIsland
18
18
  def self.batch_import(data, verbose: false, test: false)
19
19
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
20
20
 
21
+ known_ids = []
22
+
21
23
  data.each_with_index do |resource_data, index|
22
24
  resource = new
23
- resource.username = resource_data['username']
24
- resource.password = resource_data['password'] if resource_data['password']
25
+ resource.delayed_set(:username, resource_data, 'username')
26
+ resource.delayed_set(:password, resource_data, 'password') if resource_data['password']
25
27
  resource.delayed_set(:consumer, resource_data, 'consumer')
26
28
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
29
+ known_ids << resource.id
27
30
  end
31
+
32
+ known_ids
28
33
  end
29
34
 
30
35
  def self.relative_uri
@@ -56,6 +61,10 @@ module SkullIsland
56
61
  false
57
62
  end
58
63
 
64
+ def project
65
+ consumer ? consumer.project : nil
66
+ end
67
+
59
68
  private
60
69
 
61
70
  def postprocess_consumer(value)
@@ -7,29 +7,42 @@ module SkullIsland
7
7
  #
8
8
  # @see https://docs.konghq.com/1.1.x/admin-api/#certificate-object Certificate API definition
9
9
  class Certificate < Resource
10
+ include Helpers::Meta
11
+
10
12
  property :cert, required: true, validate: true
11
13
  property :key, required: true, validate: true
12
14
  property :snis, validate: true
13
15
  property :created_at, read_only: true, postprocess: true
14
- property :tags, validate: true
16
+ property :tags, validate: true, preprocess: true, postprocess: true
15
17
 
16
- def self.batch_import(data, verbose: false, test: false)
18
+ # rubocop:disable Metrics/CyclomaticComplexity
19
+ # rubocop:disable Metrics/PerceivedComplexity
20
+ def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
17
21
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
18
22
 
23
+ known_ids = []
24
+
19
25
  data.each_with_index do |resource_data, index|
20
26
  resource = new
21
- resource.cert = resource_data['cert']
22
- resource.key = resource_data['key']
27
+ resource.delayed_set(:cert, resource_data, 'cert')
28
+ resource.delayed_set(:key, resource_data, 'key')
23
29
  resource.snis = resource_data['snis'] if resource_data['snis']
24
30
  resource.tags = resource_data['tags'] if resource_data['tags']
31
+ resource.project = project if project
32
+ resource.import_time = (time || Time.now.utc.to_i) if project
25
33
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
34
+ known_ids << resource.id
26
35
  end
36
+
37
+ cleanup_except(project, known_ids) if project
27
38
  end
39
+ # rubocop:enable Metrics/CyclomaticComplexity
40
+ # rubocop:enable Metrics/PerceivedComplexity
28
41
 
29
42
  def export(options = {})
30
43
  hash = { 'cert' => cert, 'key' => key }
31
44
  hash['snis'] = snis if snis && !snis.empty?
32
- hash['tags'] = tags if tags
45
+ hash['tags'] = tags unless tags.empty?
33
46
  [*options[:exclude]].each do |exclude|
34
47
  hash.delete(exclude.to_s)
35
48
  end
@@ -7,22 +7,33 @@ module SkullIsland
7
7
  #
8
8
  # @see https://docs.konghq.com/1.1.x/admin-api/#consumer-object Consumer API definition
9
9
  class Consumer < Resource
10
+ include Helpers::Meta
11
+
10
12
  property :username
11
13
  property :custom_id
12
14
  property :created_at, read_only: true, postprocess: true
13
- property :tags, validate: true
15
+ property :tags, validate: true, preprocess: true, postprocess: true
14
16
 
15
- def self.batch_import(data, verbose: false, test: false)
17
+ # rubocop:disable Metrics/CyclomaticComplexity
18
+ # rubocop:disable Metrics/PerceivedComplexity
19
+ # rubocop:disable Metrics/AbcSize
20
+ def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
16
21
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
17
22
 
23
+ known_ids = []
24
+
25
+ # rubocop:disable Metrics/BlockLength
18
26
  data.each_with_index do |resource_data, index|
19
27
  resource = new
20
28
  resource.username = resource_data['username']
21
29
  resource.custom_id = resource_data['custom_id']
22
30
  resource.tags = resource_data['tags'] if resource_data['tags']
31
+ resource.project = project if project
32
+ resource.import_time = (time || Time.now.utc.to_i) if project
23
33
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
34
+ known_ids << resource.id
24
35
 
25
- BasicauthCredential.batch_import(
36
+ known_basic_auths = BasicauthCredential.batch_import(
26
37
  (
27
38
  resource_data.dig('credentials', 'basic-auth') || []
28
39
  ).map { |t| t.merge('consumer' => { 'id' => resource.id }) },
@@ -30,7 +41,7 @@ module SkullIsland
30
41
  test: test
31
42
  )
32
43
 
33
- JWTCredential.batch_import(
44
+ known_jwt_auths = JWTCredential.batch_import(
34
45
  (
35
46
  resource_data.dig('credentials', 'jwt') || []
36
47
  ).map { |t| t.merge('consumer' => { 'id' => resource.id }) },
@@ -38,20 +49,36 @@ module SkullIsland
38
49
  test: test
39
50
  )
40
51
 
41
- KeyauthCredential.batch_import(
52
+ known_key_auths = KeyauthCredential.batch_import(
42
53
  (
43
54
  resource_data.dig('credentials', 'key-auth') || []
44
55
  ).map { |t| t.merge('consumer' => { 'id' => resource.id }) },
45
56
  verbose: verbose,
46
57
  test: test
47
58
  )
59
+
60
+ next unless project
61
+
62
+ BasicauthCredential.all.reject { |res| known_basic_auths.include?(res.id) }.map do |res|
63
+ puts "[WARN] ! Removing #{res.class.name} (#{res.id})"
64
+ res.destroy
65
+ end
66
+
67
+ JWTCredential.all.reject { |res| known_jwt_auths.include?(res.id) }.map do |res|
68
+ puts "[WARN] ! Removing #{res.class.name} (#{res.id})"
69
+ res.destroy
70
+ end
71
+
72
+ KeyauthCredential.all.reject { |res| known_key_auths.include?(res.id) }.map do |res|
73
+ puts "[WARN] ! Removing #{res.class.name} (#{res.id})"
74
+ res.destroy
75
+ end
48
76
  end
77
+ # rubocop:enable Metrics/BlockLength
78
+
79
+ cleanup_except(project, known_ids) if project
49
80
  end
50
81
 
51
- # Convenience method to add upstream targets
52
- # rubocop:disable Metrics/AbcSize
53
- # rubocop:disable Metrics/CyclomaticComplexity
54
- # rubocop:disable Metrics/PerceivedComplexity
55
82
  def add_credential!(details)
56
83
  r = if [BasicauthCredential, JWTCredential, KeyauthCredential].include? details.class
57
84
  details
@@ -100,7 +127,7 @@ module SkullIsland
100
127
  hash = { 'username' => username, 'custom_id' => custom_id }
101
128
  creds = credentials_for_export
102
129
  hash['credentials'] = creds unless creds.empty?
103
- hash['tags'] = tags if tags
130
+ hash['tags'] = tags unless tags.empty?
104
131
  [*options[:exclude]].each do |exclude|
105
132
  hash.delete(exclude.to_s)
106
133
  end
@@ -20,17 +20,22 @@ module SkullIsland
20
20
  def self.batch_import(data, verbose: false, test: false)
21
21
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
22
22
 
23
+ known_ids = []
24
+
23
25
  data.each_with_index do |resource_data, index|
24
26
  resource = new
25
27
  resource.algorithm = resource_data['algorithm']
26
- resource.key = resource_data['key'] if resource_data['key']
27
- resource.secret = resource_data['secret'] if resource_data['secret']
28
+ resource.delayed_set(:key, resource_data, 'key') if resource_data['key']
29
+ resource.delayed_set(:secret, resource_data, 'secret') if resource_data['secret']
28
30
  if resource_data['rsa_public_key']
29
- resource.rsa_public_key = resource_data['rsa_public_key']
31
+ resource.delayed_set(:rsa_public_key, resource_data, 'rsa_public_key')
30
32
  end
31
33
  resource.delayed_set(:consumer, resource_data, 'consumer')
32
34
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
35
+ known_ids << resource.id
33
36
  end
37
+
38
+ known_ids
34
39
  end
35
40
 
36
41
  def self.relative_uri
@@ -65,6 +70,10 @@ module SkullIsland
65
70
  false
66
71
  end
67
72
 
73
+ def project
74
+ consumer ? consumer.project : nil
75
+ end
76
+
68
77
  private
69
78
 
70
79
  def postprocess_consumer(value)
@@ -19,7 +19,7 @@ module SkullIsland
19
19
 
20
20
  data.each_with_index do |resource_data, index|
21
21
  resource = new
22
- resource.key = resource_data['key']
22
+ resource.delayed_set(:key, resource_data, 'key')
23
23
  resource.delayed_set(:consumer, resource_data, 'consumer')
24
24
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
25
25
  end
@@ -7,6 +7,8 @@ module SkullIsland
7
7
  #
8
8
  # @see https://docs.konghq.com/1.1.x/admin-api/#plugin-object Plugin API definition
9
9
  class Plugin < Resource
10
+ include Helpers::Meta
11
+
10
12
  property :name
11
13
  property :enabled, type: :boolean
12
14
  property :run_on, validate: true
@@ -15,11 +17,15 @@ module SkullIsland
15
17
  property :route, validate: true, preprocess: true, postprocess: true
16
18
  property :service, validate: true, preprocess: true, postprocess: true
17
19
  property :created_at, read_only: true, postprocess: true
18
- property :tags, validate: true
20
+ property :tags, validate: true, preprocess: true, postprocess: true
19
21
 
20
- def self.batch_import(data, verbose: false, test: false)
22
+ # rubocop:disable Metrics/CyclomaticComplexity
23
+ # rubocop:disable Metrics/PerceivedComplexity
24
+ def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
21
25
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
22
26
 
27
+ known_ids = []
28
+
23
29
  data.each_with_index do |resource_data, index|
24
30
  resource = new
25
31
  resource.name = resource_data['name']
@@ -27,12 +33,19 @@ module SkullIsland
27
33
  resource.run_on = resource_data['run_on'] if resource_data['run_on']
28
34
  resource.config = resource_data['config'].deep_sort if resource_data['config']
29
35
  resource.tags = resource_data['tags'] if resource_data['tags']
36
+ resource.project = project if project
37
+ resource.import_time = (time || Time.now.utc.to_i) if project
30
38
  resource.delayed_set(:consumer, resource_data, 'consumer')
31
39
  resource.delayed_set(:route, resource_data, 'route')
32
40
  resource.delayed_set(:service, resource_data, 'service')
33
41
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
42
+ known_ids << resource.id
34
43
  end
44
+
45
+ cleanup_except(project, known_ids) if project
35
46
  end
47
+ # rubocop:enable Metrics/CyclomaticComplexity
48
+ # rubocop:enable Metrics/PerceivedComplexity
36
49
 
37
50
  def self.enabled_names(api_client: APIClient.instance)
38
51
  api_client.get("#{relative_uri}/enabled")['enabled_plugins']
@@ -55,7 +68,7 @@ module SkullIsland
55
68
  hash['consumer'] = "<%= lookup :consumer, '#{consumer.username}' %>" if consumer
56
69
  hash['route'] = "<%= lookup :route, '#{route.name}' %>" if route
57
70
  hash['service'] = "<%= lookup :service, '#{service.name}' %>" if service
58
- hash['tags'] = tags if tags
71
+ hash['tags'] = tags unless tags.empty?
59
72
  [*options[:exclude]].each do |exclude|
60
73
  hash.delete(exclude.to_s)
61
74
  end
@@ -7,6 +7,8 @@ module SkullIsland
7
7
  #
8
8
  # @see https://docs.konghq.com/1.1.x/admin-api/#route-object Route API definition
9
9
  class Route < Resource
10
+ include Helpers::Meta
11
+
10
12
  property :name
11
13
  property :methods
12
14
  property :paths
@@ -21,14 +23,16 @@ module SkullIsland
21
23
  property :service, validate: true, preprocess: true, postprocess: true
22
24
  property :created_at, read_only: true, postprocess: true
23
25
  property :updated_at, read_only: true, postprocess: true
24
- property :tags, validate: true
26
+ property :tags, validate: true, preprocess: true, postprocess: true
25
27
 
26
28
  # rubocop:disable Metrics/CyclomaticComplexity
27
29
  # rubocop:disable Metrics/PerceivedComplexity
28
30
  # rubocop:disable Metrics/AbcSize
29
- def self.batch_import(data, verbose: false, test: false)
31
+ def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
30
32
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
31
33
 
34
+ known_ids = []
35
+
32
36
  data.each_with_index do |rdata, index|
33
37
  resource = new
34
38
  resource.name = rdata['name']
@@ -41,9 +45,14 @@ module SkullIsland
41
45
  resource.preserve_host = rdata['preserve_host'] unless rdata['preserve_host'].nil?
42
46
  resource.snis = rdata['snis'] if rdata['snis']
43
47
  resource.tags = rdata['tags'] if rdata['tags']
48
+ resource.project = project if project
49
+ resource.import_time = (time || Time.now.utc.to_i) if project
44
50
  resource.delayed_set(:service, rdata, 'service')
45
51
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
52
+ known_ids << resource.id
46
53
  end
54
+
55
+ cleanup_except(project, known_ids) if project
47
56
  end
48
57
  # rubocop:enable Metrics/CyclomaticComplexity
49
58
  # rubocop:enable Metrics/PerceivedComplexity
@@ -68,7 +77,7 @@ module SkullIsland
68
77
  }
69
78
  hash['service'] = "<%= lookup :service, '#{service.name}' %>" if service
70
79
  hash['snis'] = snis if snis && !snis.empty?
71
- hash['tags'] = tags if tags
80
+ hash['tags'] = tags unless tags.empty?
72
81
  [*options[:exclude]].each do |exclude|
73
82
  hash.delete(exclude.to_s)
74
83
  end
@@ -7,6 +7,8 @@ module SkullIsland
7
7
  #
8
8
  # @see https://docs.konghq.com/1.1.x/admin-api/#service-object Service API definition
9
9
  class Service < Resource
10
+ include Helpers::Meta
11
+
10
12
  property :name
11
13
  property :retries
12
14
  property :protocol, validate: true, required: true
@@ -18,27 +20,32 @@ module SkullIsland
18
20
  property :read_timeout, validate: true
19
21
  property :created_at, read_only: true, postprocess: true
20
22
  property :updated_at, read_only: true, postprocess: true
21
- property :tags, validate: true
23
+ property :tags, validate: true, preprocess: true, postprocess: true
22
24
 
23
25
  # rubocop:disable Metrics/CyclomaticComplexity
24
26
  # rubocop:disable Metrics/PerceivedComplexity
25
27
  # rubocop:disable Metrics/AbcSize
26
- def self.batch_import(data, verbose: false, test: false)
28
+ def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
27
29
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
28
30
 
31
+ known_ids = []
32
+
29
33
  data.each_with_index do |rdata, index|
30
34
  resource = new
31
35
  resource.name = rdata['name']
32
36
  resource.retries = rdata['retries'] if rdata['retries']
33
37
  resource.protocol = rdata['protocol']
34
- resource.host = rdata['host']
35
- resource.port = rdata['port']
38
+ resource.delayed_set(:host, rdata, 'host')
39
+ resource.delayed_set(:port, rdata, 'port')
36
40
  resource.path = rdata['path'] if rdata['path']
37
41
  resource.connect_timeout = rdata['connect_timeout'] if rdata['connect_timeout']
38
42
  resource.write_timeout = rdata['write_timeout'] if rdata['write_timeout']
39
43
  resource.read_timeout = rdata['read_timeout'] if rdata['read_timeout']
40
44
  resource.tags = rdata['tags'] if rdata['tags']
45
+ resource.project = project if project
46
+ resource.import_time = (time || Time.now.utc.to_i) if project
41
47
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
48
+ known_ids << resource.id
42
49
 
43
50
  Route.batch_import(
44
51
  (rdata['routes'] || []).map { |r| r.merge('service' => { 'id' => resource.id }) },
@@ -46,6 +53,8 @@ module SkullIsland
46
53
  test: test
47
54
  )
48
55
  end
56
+
57
+ cleanup_except(project, known_ids) if project
49
58
  end
50
59
  # rubocop:enable Metrics/CyclomaticComplexity
51
60
  # rubocop:enable Metrics/PerceivedComplexity
@@ -59,6 +68,11 @@ module SkullIsland
59
68
  r.save
60
69
  end
61
70
 
71
+ def destroy
72
+ routes.each(&:destroy)
73
+ super
74
+ end
75
+
62
76
  # Provides a collection of related {Route} instances
63
77
  def routes
64
78
  Route.where(:service, self, api_client: api_client)
@@ -82,7 +96,7 @@ module SkullIsland
82
96
  'read_timeout' => read_timeout
83
97
  }
84
98
  hash['routes'] = routes.collect { |route| route.export(exclude: 'service') }
85
- hash['tags'] = tags if tags
99
+ hash['tags'] = tags unless tags.empty?
86
100
  [*options[:exclude]].each do |exclude|
87
101
  hash.delete(exclude.to_s)
88
102
  end
@@ -7,6 +7,8 @@ module SkullIsland
7
7
  #
8
8
  # @see https://docs.konghq.com/1.1.x/admin-api/#upstream-objects Upstream API definition
9
9
  class Upstream < Resource
10
+ include Helpers::Meta
11
+
10
12
  property :name, required: true, validate: true
11
13
  property :slots, validate: true
12
14
  property :hash_on, validate: true
@@ -17,14 +19,16 @@ module SkullIsland
17
19
  property :hash_on_cookie_path, validate: true
18
20
  property :healthchecks, validate: true
19
21
  property :created_at, read_only: true, postprocess: true
20
- property :tags, validate: true
22
+ property :tags, validate: true, preprocess: true, postprocess: true
21
23
 
22
24
  # rubocop:disable Metrics/CyclomaticComplexity
23
25
  # rubocop:disable Metrics/PerceivedComplexity
24
26
  # rubocop:disable Metrics/AbcSize
25
- def self.batch_import(data, verbose: false, test: false)
27
+ def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
26
28
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
27
29
 
30
+ known_ids = []
31
+
28
32
  data.each_with_index do |rdata, index|
29
33
  resource = new
30
34
  resource.name = rdata['name']
@@ -41,15 +45,22 @@ module SkullIsland
41
45
  end
42
46
  resource.healthchecks = rdata['healthchecks'] if rdata['healthchecks']
43
47
  resource.tags = rdata['tags'] if rdata['tags']
48
+ resource.project = project if project
49
+ resource.import_time = (time || Time.now.utc.to_i) if project
44
50
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
51
+ known_ids << resource.id
45
52
  puts '[INFO] Processing UpstreamTarget entries...' if verbose
46
53
 
47
54
  UpstreamTarget.batch_import(
48
55
  (rdata['targets'] || []).map { |t| t.merge('upstream' => { 'id' => resource.id }) },
49
56
  verbose: verbose,
50
- test: test
57
+ test: test,
58
+ project: project,
59
+ time: time
51
60
  )
52
61
  end
62
+
63
+ cleanup_except(project, known_ids) if project
53
64
  end
54
65
  # rubocop:enable Metrics/CyclomaticComplexity
55
66
  # rubocop:enable Metrics/PerceivedComplexity
@@ -113,7 +124,7 @@ module SkullIsland
113
124
  'healthchecks' => healthchecks
114
125
  }
115
126
  hash['targets'] = targets.collect { |target| target.export(exclude: 'upstream') }
116
- hash['tags'] = tags if tags
127
+ hash['tags'] = tags unless tags.empty?
117
128
  [*options[:exclude]].each do |exclude|
118
129
  hash.delete(exclude.to_s)
119
130
  end
@@ -7,6 +7,8 @@ module SkullIsland
7
7
  #
8
8
  # @see https://docs.konghq.com/1.1.x/admin-api/#target-object Target API definition
9
9
  class UpstreamTarget < Resource
10
+ include Helpers::Meta
11
+
10
12
  property :target, required: true, validate: true, preprocess: true
11
13
  property(
12
14
  :upstream,
@@ -14,20 +16,31 @@ module SkullIsland
14
16
  )
15
17
  property :weight, validate: true
16
18
  property :created_at, read_only: true, postprocess: true
17
- property :tags, validate: true
19
+ property :tags, validate: true, preprocess: true, postprocess: true
18
20
 
19
- def self.batch_import(data, verbose: false, test: false)
21
+ # rubocop:disable Metrics/CyclomaticComplexity
22
+ # rubocop:disable Metrics/PerceivedComplexity
23
+ def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
20
24
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
21
25
 
26
+ known_ids = []
27
+
22
28
  data.each_with_index do |resource_data, index|
23
29
  resource = new
24
- resource.target = resource_data['target']
30
+ resource.delayed_set(:target, resource_data, 'target')
25
31
  resource.delayed_set(:upstream, resource_data, 'upstream')
26
32
  resource.weight = resource_data['weight'] if resource_data['weight']
27
33
  resource.tags = resource_data['tags'] if resource_data['tags']
34
+ resource.project = project if project
35
+ resource.import_time = (time || Time.now.utc.to_i) if project
28
36
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
37
+ known_ids << resource.id
29
38
  end
39
+
40
+ cleanup_except(project, known_ids) if project
30
41
  end
42
+ # rubocop:enable Metrics/CyclomaticComplexity
43
+ # rubocop:enable Metrics/PerceivedComplexity
31
44
 
32
45
  def self.all(options = {})
33
46
  api_client = options[:api_client] || APIClient.instance
@@ -68,7 +81,7 @@ module SkullIsland
68
81
  def export(options = {})
69
82
  hash = { 'target' => target, 'weight' => weight }
70
83
  hash['upstream'] = "<%= lookup :upstream, '#{upstream.name}' %>" if upstream
71
- hash['tags'] = tags if tags
84
+ hash['tags'] = tags unless tags.empty?
72
85
  [*options[:exclude]].each do |exclude|
73
86
  hash.delete(exclude.to_s)
74
87
  end
@@ -79,19 +92,7 @@ module SkullIsland
79
92
  end
80
93
 
81
94
  def modified_existing?
82
- return false unless new?
83
-
84
- # Find routes of the same name
85
- same_target_and_upstream = self.class.where(:target, target).and(:upstream, upstream)
86
-
87
- existing = same_target_and_upstream.size == 1 ? same_target_and_upstream.first : nil
88
-
89
- if existing
90
- @entity['id'] = existing.id
91
- save
92
- else
93
- false
94
- end
95
+ false # Upstream Targets can not be PATCHed, so can not be modified
95
96
  end
96
97
 
97
98
  private
@@ -24,7 +24,7 @@ module SkullIsland
24
24
  def validate_tags(value)
25
25
  # allow only valid hostnames
26
26
  value.each do |tag|
27
- return false unless tag.is_a?(String) && tag.match?(/\w_-\.~/)
27
+ return false unless tag.is_a?(String) && tag.match?(/^[\w_\-\.~]+$/)
28
28
  end
29
29
  true
30
30
  end
@@ -4,6 +4,6 @@ module SkullIsland
4
4
  VERSION = [
5
5
  1, # Major
6
6
  2, # Minor
7
- 0 # Patch
7
+ 2 # Patch
8
8
  ].join('.')
9
9
  end
@@ -40,5 +40,5 @@ Gem::Specification.new do |spec|
40
40
  spec.add_development_dependency 'rubocop', '~> 0.50'
41
41
  spec.add_development_dependency 'simplecov', '~> 0.15'
42
42
  spec.add_development_dependency 'travis', '~> 1.8'
43
- spec.add_development_dependency 'yard', '~> 0.9'
43
+ spec.add_development_dependency 'yard', '~> 0.9.20'
44
44
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skull_island
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Gnagy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-26 00:00:00.000000000 Z
11
+ date: 2019-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deepsort
@@ -198,14 +198,14 @@ dependencies:
198
198
  requirements:
199
199
  - - "~>"
200
200
  - !ruby/object:Gem::Version
201
- version: '0.9'
201
+ version: 0.9.20
202
202
  type: :development
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
- version: '0.9'
208
+ version: 0.9.20
209
209
  description: A Ruby SDK for Kong 0.14.x
210
210
  email:
211
211
  - jonathan.gnagy@gmail.com
@@ -242,6 +242,7 @@ files:
242
242
  - lib/skull_island/exceptions/invalid_where_query.rb
243
243
  - lib/skull_island/exceptions/new_instance_with_id.rb
244
244
  - lib/skull_island/helpers/api_client.rb
245
+ - lib/skull_island/helpers/meta.rb
245
246
  - lib/skull_island/helpers/migration.rb
246
247
  - lib/skull_island/helpers/resource.rb
247
248
  - lib/skull_island/helpers/resource_class.rb
@@ -284,7 +285,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
284
285
  - !ruby/object:Gem::Version
285
286
  version: '0'
286
287
  requirements: []
287
- rubygems_version: 3.0.4
288
+ rubyforge_project:
289
+ rubygems_version: 2.7.7
288
290
  signing_key:
289
291
  specification_version: 4
290
292
  summary: Ruby SDK for Kong