skull_island 0.14.1 → 1.1.1

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: d2a3355226814eb0d485dbac28624003b323eb62b4a1116493ef5da234108930
4
- data.tar.gz: 8fe0388fb91633ea0bd1eba39e98671bb3fe88bc691700573ed1a6daa7814e8e
3
+ metadata.gz: 2599c9fbc76abcc4be21923b95477c784515165c3891aa5155d7a9d88c5c887b
4
+ data.tar.gz: df6f3715f49a4a6cb09118bd446f1c7097fa7d0228dfce497e7396e63921e13c
5
5
  SHA512:
6
- metadata.gz: 9e429af9e6768ad18c00e2b6e0f60844f2194eb32c627c7bb9cacfa4a0c282cb3069ed70c54a5be686c9c36a7b5f3882b0c6dfe34d5566a7c48a92b648894632
7
- data.tar.gz: 320f1a3e376ac977d050460d978e6e838914932d759f82542bba0c797ae37c1ef2bb1f830f2c810f2843d0db91238074ac9c9f3a371a1e13eaa05beeb2d9c07e
6
+ metadata.gz: 70be200f4e3721d42c820176d16350e705c40c38254876322181a1509538b0e7612156fbb2ddb29b9a8bdfab522ec8452046b346a2fe059a3bd838f4f2c578bc
7
+ data.tar.gz: 7c426dfbf53dbb080ed5211c14ce22b0d5ef78aa06a8e27c1fd1d91b1534065f77554ffc93edc56728fec870aa8bdd591ed10d1fcadf454c4372f897e16f03bf
data/.rubocop.yml CHANGED
@@ -8,10 +8,10 @@ Metrics/LineLength:
8
8
  Max: 100
9
9
 
10
10
  Metrics/ClassLength:
11
- Max: 165
11
+ Max: 170
12
12
 
13
13
  Metrics/ModuleLength:
14
- Max: 165
14
+ Max: 170
15
15
  Exclude:
16
16
  - 'lib/skull_island/helpers/resource.rb'
17
17
 
@@ -23,7 +23,7 @@ Metrics/PerceivedComplexity:
23
23
  - 'lib/skull_island/cli.rb'
24
24
 
25
25
  Metrics/AbcSize:
26
- Max: 25
26
+ Max: 27
27
27
 
28
28
  Metrics/BlockLength:
29
29
  Max: 35
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- skull_island (0.14.1)
4
+ skull_island (1.1.1)
5
5
  deepsort (~> 0.4)
6
6
  erubi (~> 1.8)
7
7
  json (~> 2.1)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Skull Island
2
2
 
3
- Work In Progress for a full-featured SDK for Kong 0.14.x (with 1.0.x details added for future development).
3
+ A full-featured SDK for [Kong](https://konghq.com/kong/) 1.1.x (with support for migrating from 0.14.x). Note that this is unofficial (meaning this project is in no way officially endorsed, recommended, or related to Kong [as a company](https://konghq.com/) or an [open-source project](https://github.com/Kong/kong)). It is also in no way related to the [pet toy company](https://www.kongcompany.com/) by the same name (but hopefully that was obvious).
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,7 +13,7 @@ gem install skull_island
13
13
  Or add this to your Gemfile:
14
14
 
15
15
  ```ruby
16
- gem 'skull_island', '~>0.2'
16
+ gem 'skull_island', '~> 1.1'
17
17
  ```
18
18
 
19
19
  Or add this to your .gemspec:
@@ -21,7 +21,7 @@ Or add this to your .gemspec:
21
21
  ```ruby
22
22
  Gem::Specification.new do |spec|
23
23
  # ...
24
- spec.add_runtime_dependency 'skull_island', '~> 0.2'
24
+ spec.add_runtime_dependency 'skull_island', '~> 1.1'
25
25
  # ...
26
26
  end
27
27
  ```
@@ -33,9 +33,10 @@ Skull Island comes with an executable called `skull_island` that leverages the S
33
33
  ```
34
34
  $ skull_island help
35
35
  Commands:
36
- skull_island export [OPTIONS] OUTPUT_FILE # Export the current configuration to OUTPUT_FILE
37
- skull_island help [COMMAND] # Describe available commands or one specific command
38
- skull_island import [OPTIONS] INPUT_FILE # Import a configuration from INPUT_FILE
36
+ skull_island export [OPTIONS] [OUTPUT|-] # Export the current configuration to OUTPUT
37
+ skull_island help [COMMAND] # Describe available commands or one specific command
38
+ skull_island import [OPTIONS] [INPUT|-] # Import a configuration from INPUT
39
+ skull_island migrate [OPTIONS] [INPUT|-] [OUTPUT|-] # Migrate an older config from INPUT to OUTPUT
39
40
 
40
41
  Options:
41
42
  [--verbose], [--no-verbose]
@@ -111,13 +112,27 @@ KONG_ADMIN_URL='https://api-admin.mydomain.com' \
111
112
  skull_island import --verbose --test /path/to/export.yml
112
113
  ```
113
114
 
115
+ ### Migrating
116
+
117
+ With Skull Island, it is possible to migrate a configuration from a 0.14.x gateway to one compatible with a 1.1.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.1 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:
118
+
119
+ ```
120
+ skull_island migrate /path/to/export.yml /output/location/migrated.yml
121
+ ```
122
+
123
+ While this hasn't been heavily tested for all possible use-cases, any configuration generated or usable by the `'~> 0.14'` version of this gem should safely convert using the migration command. This tool also makes no guarantees about plugin functionality, configuration compatibility across versions, or that the same plugins are installed and available in your newer gateway. It should go without saying that you should **test and confirm** that all of your functionality was successfully migrated.
124
+
125
+ If you don't have a previous export, you'll need to install an older version of this gem using `gem install --version '~> 0.14' skull_island`, then perform an `export`, then you can switch back to the latest version of the gem for migrating and importing.
126
+
127
+ While it would be possible to make migration _automatic_ for the `import` command, `skull_island` intentionally doesn't do this to avoid the appearance that the config is losslessly compatible across versions. In reality, the newer config version has additional features (like tagging) that will likely be used heavily. It makes sense to this author to maintain the migration component and the normal functionality as distinct features to encourage the use of the newer capabilities in 1.1+.
128
+
114
129
  ### File Format
115
130
 
116
- The import/export CLI functions produce YAML with support for embedded Ruby ([ERB](https://ruby-doc.org/stdlib-2.5.3/libdoc/erb/rdoc/ERB.html)). The file is structured like this (as an example):
131
+ The import/export/migrate CLI functions produce YAML with support for embedded Ruby ([ERB](https://ruby-doc.org/stdlib-2.5.3/libdoc/erb/rdoc/ERB.html)). The file is structured like this (as an example):
117
132
 
118
133
  ```yaml
119
134
  ---
120
- version: '0.14'
135
+ version: '1.1'
121
136
  certificates: []
122
137
  consumers:
123
138
  - username: foo
@@ -172,7 +187,7 @@ plugins:
172
187
  key_names:
173
188
  - x-api-key
174
189
  run_on_preflight: true
175
- service_id: "<%= lookup :service, 'search_api' %>"
190
+ service: "<%= lookup :service, 'search_api' %>"
176
191
  ```
177
192
 
178
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.
@@ -295,7 +310,7 @@ service.routes.size
295
310
  # => 4
296
311
  ```
297
312
 
298
- From here, the SDK mostly wraps the attributes described in the [Kong API Docs](https://docs.konghq.com/0.14.x/admin-api/). For simplicity, I'll go over the resource types and attributes this SDK supports manipulating. Rely on the API documentation to determine which attributes are required and under which conditions.
313
+ From here, the SDK mostly wraps the attributes described in the [Kong API Docs](https://docs.konghq.com/1.1.x/admin-api/). For simplicity, I'll go over the resource types and attributes this SDK supports manipulating. Rely on the API documentation to determine which attributes are required and under which conditions.
299
314
 
300
315
  #### Certificates
301
316
 
@@ -365,7 +380,7 @@ resource.enabled = true # A Boolean
365
380
  resource.config = { 'minute' => 50, 'hour' => 1000 } # A Hash of config keys and values
366
381
 
367
382
  # Either reference related resources by ID
368
- resource.service = '5fd1z584-1adb-40a5-c042-63b19db49x21'
383
+ resource.service = { 'id' => '5fd1z584-1adb-40a5-c042-63b19db49x21' }
369
384
  resource.service
370
385
  # => #<SkullIsland::Resources::Services:0x00007f9f201f6f44...
371
386
 
data/lib/skull_island.rb CHANGED
@@ -41,6 +41,7 @@ require 'skull_island/simple_api_client'
41
41
  require 'skull_island/resource_collection'
42
42
  require 'skull_island/helpers/resource'
43
43
  require 'skull_island/helpers/resource_class'
44
+ require 'skull_island/helpers/migration'
44
45
  require 'skull_island/validations/resource'
45
46
  require 'skull_island/resource'
46
47
  require 'skull_island/resources/certificate'
@@ -31,6 +31,7 @@ module SkullIsland
31
31
 
32
32
  def get(uri, data = nil)
33
33
  client_action do |client|
34
+ # TODO: Support the API's pagination through the "next" top-level key
34
35
  if data
35
36
  JSON.parse client[uri].get(json_headers.merge(params: data))
36
37
  else
@@ -10,9 +10,11 @@ require 'thor'
10
10
  module SkullIsland
11
11
  # Base CLI for SkullIsland
12
12
  class CLI < Thor
13
+ include Helpers::Migration
14
+
13
15
  class_option :verbose, type: :boolean
14
16
 
15
- desc 'export [OPTIONS] OUTPUT_FILE', 'Export the current configuration to OUTPUT_FILE'
17
+ desc 'export [OPTIONS] [OUTPUT|-]', 'Export the current configuration to OUTPUT'
16
18
  def export(output_file = '-')
17
19
  if output_file == '-'
18
20
  STDERR.puts '[INFO] Outputting to STDOUT' if options['verbose']
@@ -24,7 +26,9 @@ module SkullIsland
24
26
  end
25
27
  end
26
28
 
27
- output = { 'version' => '0.14' }
29
+ validate_server_version
30
+
31
+ output = { 'version' => '1.1' }
28
32
 
29
33
  [
30
34
  Resources::Certificate,
@@ -41,29 +45,17 @@ module SkullIsland
41
45
  end
42
46
  end
43
47
 
44
- desc 'import [OPTIONS] INPUT_FILE', 'Import a configuration from INPUT_FILE'
48
+ desc 'import [OPTIONS] [INPUT|-]', 'Import a configuration from INPUT'
45
49
  option :test, type: :boolean, desc: "Don't do anything, just show what would happen"
46
50
  def import(input_file = '-')
47
- raw ||= if input_file == '-'
48
- STDERR.puts '[INFO] Reading from STDOUT' if options['verbose']
49
- STDIN.read
50
- else
51
- full_filename = File.expand_path(input_file)
52
- unless File.exist?(full_filename) && File.ftype(full_filename) == 'file'
53
- raise Exceptions::InvalidArguments, "#{full_filename} is invalid"
54
- end
55
-
56
- begin
57
- File.read(full_filename)
58
- rescue StandardError => e
59
- raise "Unable to process #{relative_path}: #{e.message}"
60
- end
61
- end
51
+ raw ||= acquire_input(input_file, options['verbose'])
62
52
 
63
53
  # rubocop:disable Security/YAMLLoad
64
54
  input = YAML.load(raw)
65
55
  # rubocop:enable Security/YAMLLoad
66
56
 
57
+ validate_config_version input['version']
58
+
67
59
  [
68
60
  Resources::Certificate,
69
61
  Resources::Consumer,
@@ -73,6 +65,35 @@ module SkullIsland
73
65
  ].each { |clname| import_class(clname, input) }
74
66
  end
75
67
 
68
+ desc(
69
+ 'migrate [OPTIONS] [INPUT|-] [OUTPUT|-]',
70
+ 'Migrate an older config from INPUT to OUTPUT'
71
+ )
72
+ def migrate(input_file = '-', output_file = '-')
73
+ raw ||= acquire_input(input_file, options['verbose'])
74
+
75
+ # rubocop:disable Security/YAMLLoad
76
+ input = YAML.load(raw)
77
+ # rubocop:enable Security/YAMLLoad
78
+
79
+ validate_migrate_version input['version']
80
+
81
+ output = migrate_config(input)
82
+
83
+ if output_file == '-'
84
+ STDERR.puts '[INFO] Outputting to STDOUT' if options['verbose']
85
+ STDOUT.puts output.to_yaml
86
+ else
87
+ full_filename = File.expand_path(output_file)
88
+ dirname = File.dirname(full_filename)
89
+ unless File.exist?(dirname) && File.ftype(dirname) == 'directory'
90
+ raise Exceptions::InvalidArguments, "#{full_filename} is invalid"
91
+ end
92
+
93
+ File.write(full_filename, output.to_yaml)
94
+ end
95
+ end
96
+
76
97
  private
77
98
 
78
99
  def export_class(class_name, output_data)
@@ -88,5 +109,54 @@ module SkullIsland
88
109
  test: options['test']
89
110
  )
90
111
  end
112
+
113
+ # Used to pull input from either STDIN or the specified file
114
+ def acquire_input(input_file, verbose = false)
115
+ if input_file == '-'
116
+ STDERR.puts '[INFO] Reading from STDIN' if verbose
117
+ STDIN.read
118
+ else
119
+ full_filename = File.expand_path(input_file)
120
+ unless File.exist?(full_filename) && File.ftype(full_filename) == 'file'
121
+ raise Exceptions::InvalidArguments, "#{full_filename} is invalid"
122
+ end
123
+
124
+ begin
125
+ File.read(full_filename)
126
+ rescue StandardError => e
127
+ raise "Unable to process #{relative_path}: #{e.message}"
128
+ end
129
+ end
130
+ end
131
+
132
+ def validate_config_version(version)
133
+ if version && version == '1.1'
134
+ validate_server_version
135
+ elsif version && ['0.14', '1.0'].include?(version)
136
+ STDERR.puts '[CRITICAL] Config version is too old. Try `migrate` instead of `import`.'
137
+ exit 2
138
+ else
139
+ STDERR.puts '[CRITICAL] Config version is unknown or not supported.'
140
+ exit 3
141
+ end
142
+ end
143
+
144
+ def validate_migrate_version(version)
145
+ if version && version == '0.14'
146
+ true
147
+ else
148
+ STDERR.puts '[CRITICAL] Config version must be 0.14 for migration.'
149
+ exit 4
150
+ end
151
+ end
152
+
153
+ def validate_server_version
154
+ if SkullIsland::APIClient.about_service['version'].start_with? '1.1'
155
+ true
156
+ else
157
+ STDERR.puts '[CRITICAL] Server version mismatch!'
158
+ exit 1
159
+ end
160
+ end
91
161
  end
92
162
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SkullIsland
4
+ module Helpers
5
+ # Simple helper methods for migrating old configs
6
+ module Migration
7
+ def migrate_config(config)
8
+ if config['version'] == '0.14'
9
+ migrate_0_14_to_1_1(config)
10
+ else
11
+ false # Just return false if it can't be migrated
12
+ end
13
+ end
14
+
15
+ def migrate_0_14_to_1_1(config)
16
+ new_config = config.dup
17
+ config['plugins']&.each_with_index do |plugin, plugin_index|
18
+ %w[consumer route service].each do |rtype|
19
+ if plugin["#{rtype}_id"]&.start_with?('<%=')
20
+ new_config['plugins'][plugin_index][rtype] = plugin["#{rtype}_id"].dup
21
+ new_config['plugins'][plugin_index].delete("#{rtype}_id")
22
+ elsif plugin["#{rtype}_id"]
23
+ new_config['plugins'][plugin_index][rtype] = {
24
+ 'id' => plugin["#{rtype}_id"].dup
25
+ }
26
+ end
27
+ end
28
+ end
29
+ new_config['version'] = '1.1'
30
+ new_config
31
+ end
32
+ end
33
+ end
34
+ end
@@ -18,9 +18,10 @@ module SkullIsland
18
18
  def delayed_set(property, data, key)
19
19
  # rubocop:disable Security/Eval
20
20
  if data[key]
21
+ value = data[key].is_a?(String) ? eval(Erubi::Engine.new(data[key]).src) : data[key]
21
22
  send(
22
23
  "#{property}=".to_sym,
23
- data[key].is_a?(String) ? eval(Erubi::Engine.new(data[key]).src) : data[key]
24
+ value.is_a?(String) && value.start_with?('{"') ? eval(value) : value
24
25
  )
25
26
  end
26
27
  # rubocop:enable Security/Eval
@@ -94,13 +95,13 @@ module SkullIsland
94
95
  def lookup(type, value)
95
96
  case type
96
97
  when :consumer
97
- Resources::Consumer.find(:username, value).id
98
+ { 'id' => Resources::Consumer.find(:username, value).id }
98
99
  when :route
99
- Resources::Route.find(:name, value).id
100
+ { 'id' => Resources::Route.find(:name, value).id }
100
101
  when :service
101
- Resources::Service.find(:name, value).id
102
+ { 'id' => Resources::Service.find(:name, value).id }
102
103
  when :upstream
103
- Resources::Upstream.find(:name, value).id
104
+ { 'id' => Resources::Upstream.find(:name, value).id }
104
105
  else
105
106
  raise Exceptions::InvalidArguments, "#{type} is not a valid lookup type"
106
107
  end
@@ -10,8 +10,8 @@ module SkullIsland
10
10
  property :username, required: true, validate: true
11
11
  property :password, validated: true
12
12
  property(
13
- :consumer_id,
14
- required: true, validate: true, preprocess: true, postprocess: true, as: :consumer
13
+ :consumer,
14
+ required: true, validate: true, preprocess: true, postprocess: true
15
15
  )
16
16
  property :created_at, read_only: true, postprocess: true
17
17
 
@@ -22,7 +22,7 @@ module SkullIsland
22
22
  resource = new
23
23
  resource.username = resource_data['username']
24
24
  resource.password = resource_data['password'] if resource_data['password']
25
- resource.delayed_set(:consumer, resource_data, 'consumer_id')
25
+ resource.delayed_set(:consumer, resource_data, 'consumer')
26
26
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
27
27
  end
28
28
  end
@@ -41,7 +41,7 @@ module SkullIsland
41
41
 
42
42
  def export(options = {})
43
43
  hash = { 'username' => username, 'password' => password }
44
- hash['consumer_id'] = "<%= lookup :consumer, '#{consumer.username}' %>" if consumer
44
+ hash['consumer'] = "<%= lookup :consumer, '#{consumer.username}' %>" if consumer
45
45
  [*options[:exclude]].each do |exclude|
46
46
  hash.delete(exclude.to_s)
47
47
  end
@@ -58,10 +58,10 @@ module SkullIsland
58
58
 
59
59
  private
60
60
 
61
- def postprocess_consumer_id(value)
62
- if value.is_a?(String)
61
+ def postprocess_consumer(value)
62
+ if value.is_a?(Hash)
63
63
  Consumer.new(
64
- entity: { 'id' => value },
64
+ entity: value,
65
65
  lazy: true,
66
66
  tainted: false
67
67
  )
@@ -70,18 +70,18 @@ module SkullIsland
70
70
  end
71
71
  end
72
72
 
73
- def preprocess_consumer_id(input)
74
- if input.is_a?(String)
73
+ def preprocess_consumer(input)
74
+ if input.is_a?(Hash)
75
75
  input
76
76
  else
77
- input.id
77
+ { 'id' => input.id }
78
78
  end
79
79
  end
80
80
 
81
81
  # Used to validate {#consumer} on set
82
- def validate_consumer_id(value)
83
- # allow either a Consumer object or a String
84
- value.is_a?(Consumer) || value.is_a?(String)
82
+ def validate_consumer(value)
83
+ # allow either a Consumer object or a Hash
84
+ value.is_a?(Consumer) || value.is_a?(Hash)
85
85
  end
86
86
 
87
87
  # Used to validate {#password} on set
@@ -5,12 +5,13 @@ module SkullIsland
5
5
  module Resources
6
6
  # The Certificate resource class
7
7
  #
8
- # @see https://docs.konghq.com/0.14.x/admin-api/#certificate-object Certificate API definition
8
+ # @see https://docs.konghq.com/1.1.x/admin-api/#certificate-object Certificate API definition
9
9
  class Certificate < Resource
10
10
  property :cert, required: true, validate: true
11
11
  property :key, required: true, validate: true
12
12
  property :snis, validate: true
13
13
  property :created_at, read_only: true, postprocess: true
14
+ property :tags, validate: true
14
15
 
15
16
  def self.batch_import(data, verbose: false, test: false)
16
17
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
@@ -20,12 +21,15 @@ module SkullIsland
20
21
  resource.cert = resource_data['cert']
21
22
  resource.key = resource_data['key']
22
23
  resource.snis = resource_data['snis'] if resource_data['snis']
24
+ resource.tags = resource_data['tags'] if resource_data['tags']
23
25
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
24
26
  end
25
27
  end
26
28
 
27
29
  def export(options = {})
28
- hash = { 'cert' => cert, 'key' => key, 'snis' => snis }
30
+ hash = { 'cert' => cert, 'key' => key }
31
+ hash['snis'] = snis if snis && !snis.empty?
32
+ hash['tags'] = tags if tags
29
33
  [*options[:exclude]].each do |exclude|
30
34
  hash.delete(exclude.to_s)
31
35
  end
@@ -67,8 +71,13 @@ module SkullIsland
67
71
 
68
72
  # Used to validate {#snis} on set
69
73
  def validate_snis(value)
70
- # only Array is allowed
71
- value.is_a?(Array)
74
+ return false unless value.is_a?(Array)
75
+
76
+ # allow only valid hostnames
77
+ value.each do |sni|
78
+ return false unless sni.match?(host_regex) && !sni.match?(/_/)
79
+ end
80
+ true
72
81
  end
73
82
  end
74
83
  end
@@ -5,11 +5,12 @@ module SkullIsland
5
5
  module Resources
6
6
  # The Consumer resource class
7
7
  #
8
- # @see https://docs.konghq.com/0.14.x/admin-api/#consumer-object Consumer API definition
8
+ # @see https://docs.konghq.com/1.1.x/admin-api/#consumer-object Consumer API definition
9
9
  class Consumer < Resource
10
10
  property :username
11
11
  property :custom_id
12
12
  property :created_at, read_only: true, postprocess: true
13
+ property :tags, validate: true
13
14
 
14
15
  def self.batch_import(data, verbose: false, test: false)
15
16
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
@@ -18,12 +19,13 @@ module SkullIsland
18
19
  resource = new
19
20
  resource.username = resource_data['username']
20
21
  resource.custom_id = resource_data['custom_id']
22
+ resource.tags = resource_data['tags'] if resource_data['tags']
21
23
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
22
24
 
23
25
  BasicauthCredential.batch_import(
24
26
  (
25
27
  resource_data.dig('credentials', 'basic-auth') || []
26
- ).map { |t| t.merge('consumer_id' => resource.id) },
28
+ ).map { |t| t.merge('consumer' => { 'id' => resource.id }) },
27
29
  verbose: verbose,
28
30
  test: test
29
31
  )
@@ -31,7 +33,7 @@ module SkullIsland
31
33
  KeyauthCredential.batch_import(
32
34
  (
33
35
  resource_data.dig('credentials', 'key-auth') || []
34
- ).map { |t| t.merge('consumer_id' => resource.id) },
36
+ ).map { |t| t.merge('consumer' => { 'id' => resource.id }) },
35
37
  verbose: verbose,
36
38
  test: test
37
39
  )
@@ -75,6 +77,7 @@ module SkullIsland
75
77
  hash = { 'username' => username, 'custom_id' => custom_id }
76
78
  creds = credentials_for_export
77
79
  hash['credentials'] = creds unless creds.empty?
80
+ hash['tags'] = tags if tags
78
81
  [*options[:exclude]].each do |exclude|
79
82
  hash.delete(exclude.to_s)
80
83
  end
@@ -106,12 +109,12 @@ module SkullIsland
106
109
  creds = {}
107
110
  unless credentials['key-auth'].empty?
108
111
  creds['key-auth'] = credentials['key-auth'].collect do |cred|
109
- cred.export(exclude: 'consumer_id')
112
+ cred.export(exclude: 'consumer')
110
113
  end
111
114
  end
112
115
  unless credentials['basic-auth'].empty?
113
116
  creds['basic-auth'] = credentials['basic-auth'].collect do |cred|
114
- cred.export(exclude: 'consumer_id')
117
+ cred.export(exclude: 'consumer')
115
118
  end
116
119
  end
117
120
  creds
@@ -9,8 +9,8 @@ module SkullIsland
9
9
  class KeyauthCredential < Resource
10
10
  property :key, validate: true
11
11
  property(
12
- :consumer_id,
13
- required: true, validate: true, preprocess: true, postprocess: true, as: :consumer
12
+ :consumer,
13
+ required: true, validate: true, preprocess: true, postprocess: true
14
14
  )
15
15
  property :created_at, read_only: true, postprocess: true
16
16
 
@@ -20,7 +20,7 @@ module SkullIsland
20
20
  data.each_with_index do |resource_data, index|
21
21
  resource = new
22
22
  resource.key = resource_data['key']
23
- resource.delayed_set(:consumer, resource_data, 'consumer_id')
23
+ resource.delayed_set(:consumer, resource_data, 'consumer')
24
24
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
25
25
  end
26
26
  end
@@ -39,7 +39,7 @@ module SkullIsland
39
39
 
40
40
  def export(options = {})
41
41
  hash = { 'key' => key }
42
- hash['consumer_id'] = "<%= lookup :consumer, '#{consumer.username}' %>" if consumer
42
+ hash['consumer'] = "<%= lookup :consumer, '#{consumer.username}' %>" if consumer
43
43
  [*options[:exclude]].each do |exclude|
44
44
  hash.delete(exclude.to_s)
45
45
  end
@@ -56,10 +56,10 @@ module SkullIsland
56
56
 
57
57
  private
58
58
 
59
- def postprocess_consumer_id(value)
60
- if value.is_a?(String)
59
+ def postprocess_consumer(value)
60
+ if value.is_a?(Hash)
61
61
  Consumer.new(
62
- entity: { 'id' => value },
62
+ entity: value,
63
63
  lazy: true,
64
64
  tainted: false
65
65
  )
@@ -68,18 +68,18 @@ module SkullIsland
68
68
  end
69
69
  end
70
70
 
71
- def preprocess_consumer_id(input)
72
- if input.is_a?(String)
71
+ def preprocess_consumer(input)
72
+ if input.is_a?(Hash)
73
73
  input
74
74
  else
75
- input.id
75
+ { 'id' => input.id }
76
76
  end
77
77
  end
78
78
 
79
79
  # Used to validate {#consumer} on set
80
- def validate_consumer_id(value)
81
- # allow either a Consumer object or a String
82
- value.is_a?(Consumer) || value.is_a?(String)
80
+ def validate_consumer(value)
81
+ # allow either a Consumer object or a Hash
82
+ value.is_a?(Consumer) || value.is_a?(Hash)
83
83
  end
84
84
 
85
85
  # Used to validate {#key} on set
@@ -5,16 +5,17 @@ module SkullIsland
5
5
  module Resources
6
6
  # The Plugin resource class
7
7
  #
8
- # @see https://docs.konghq.com/0.14.x/admin-api/#plugin-object Plugin API definition
8
+ # @see https://docs.konghq.com/1.1.x/admin-api/#plugin-object Plugin API definition
9
9
  class Plugin < Resource
10
10
  property :name
11
11
  property :enabled, type: :boolean
12
- # property :run_on # 1.0.x only
12
+ property :run_on, validate: true
13
13
  property :config, validate: true, preprocess: true, postprocess: true
14
- property :consumer_id, validate: true, preprocess: true, postprocess: true, as: :consumer
15
- property :route_id, validate: true, preprocess: true, postprocess: true, as: :route
16
- property :service_id, validate: true, preprocess: true, postprocess: true, as: :service
14
+ property :consumer, validate: true, preprocess: true, postprocess: true
15
+ property :route, validate: true, preprocess: true, postprocess: true
16
+ property :service, validate: true, preprocess: true, postprocess: true
17
17
  property :created_at, read_only: true, postprocess: true
18
+ property :tags, validate: true
18
19
 
19
20
  def self.batch_import(data, verbose: false, test: false)
20
21
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
@@ -23,10 +24,12 @@ module SkullIsland
23
24
  resource = new
24
25
  resource.name = resource_data['name']
25
26
  resource.enabled = resource_data['enabled']
27
+ resource.run_on = resource_data['run_on'] if resource_data['run_on']
26
28
  resource.config = resource_data['config'].deep_sort if resource_data['config']
27
- resource.delayed_set(:consumer, resource_data, 'consumer_id')
28
- resource.delayed_set(:route, resource_data, 'route_id')
29
- resource.delayed_set(:service, resource_data, 'service_id')
29
+ resource.tags = resource_data['tags'] if resource_data['tags']
30
+ resource.delayed_set(:consumer, resource_data, 'consumer')
31
+ resource.delayed_set(:route, resource_data, 'route')
32
+ resource.delayed_set(:service, resource_data, 'service')
30
33
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
31
34
  end
32
35
  end
@@ -45,9 +48,10 @@ module SkullIsland
45
48
  'enabled' => enabled?,
46
49
  'config' => config.deep_sort
47
50
  }
48
- hash['consumer_id'] = "<%= lookup :consumer, '#{consumer.username}' %>" if consumer
49
- hash['route_id'] = "<%= lookup :route, '#{route.name}' %>" if route
50
- hash['service_id'] = "<%= lookup :service, '#{service.name}' %>" if service
51
+ hash['consumer'] = "<%= lookup :consumer, '#{consumer.username}' %>" if consumer
52
+ hash['route'] = "<%= lookup :route, '#{route.name}' %>" if route
53
+ hash['service'] = "<%= lookup :service, '#{service.name}' %>" if service
54
+ hash['tags'] = tags if tags
51
55
  [*options[:exclude]].each do |exclude|
52
56
  hash.delete(exclude.to_s)
53
57
  end
@@ -94,8 +98,7 @@ module SkullIsland
94
98
  value.deep_sort
95
99
  end
96
100
 
97
- # TODO: 1.0.x requires refactoring as `consumer_id` becomes `consumer`
98
- def postprocess_consumer_id(value)
101
+ def postprocess_consumer(value)
99
102
  if value.is_a?(Hash)
100
103
  Consumer.new(
101
104
  entity: value,
@@ -113,19 +116,17 @@ module SkullIsland
113
116
  end
114
117
  end
115
118
 
116
- # TODO: 1.0.x requires refactoring as `consumer_id` becomes `consumer`
117
- def preprocess_consumer_id(input)
119
+ def preprocess_consumer(input)
118
120
  if input.is_a?(Hash)
119
- input['id']
121
+ input
120
122
  elsif input.is_a?(Consumer)
121
- input.id
123
+ { 'id' => input.id }
122
124
  else
123
125
  input
124
126
  end
125
127
  end
126
128
 
127
- # TODO: 1.0.x requires refactoring as `route_id` becomes `route`
128
- def postprocess_route_id(value)
129
+ def postprocess_route(value)
129
130
  if value.is_a?(Hash)
130
131
  Route.new(
131
132
  entity: value,
@@ -143,19 +144,17 @@ module SkullIsland
143
144
  end
144
145
  end
145
146
 
146
- # TODO: 1.0.x requires refactoring as `route_id` becomes `route`
147
- def preprocess_route_id(input)
147
+ def preprocess_route(input)
148
148
  if input.is_a?(Hash)
149
- input['id']
149
+ input
150
150
  elsif input.is_a?(Route)
151
- input.id
151
+ { 'id' => input.id }
152
152
  else
153
153
  input
154
154
  end
155
155
  end
156
156
 
157
- # TODO: 1.0.x requires refactoring as `service_id` becomes `service`
158
- def postprocess_service_id(value)
157
+ def postprocess_service(value)
159
158
  if value.is_a?(Hash)
160
159
  Service.new(
161
160
  entity: value,
@@ -173,12 +172,11 @@ module SkullIsland
173
172
  end
174
173
  end
175
174
 
176
- # TODO: 1.0.x requires refactoring as `service_id` becomes `service`
177
- def preprocess_service_id(input)
175
+ def preprocess_service(input)
178
176
  if input.is_a?(Hash)
179
- input['id']
177
+ input
180
178
  elsif input.is_a?(Service)
181
- input.id
179
+ { 'id' => input.id }
182
180
  else
183
181
  input
184
182
  end
@@ -191,21 +189,27 @@ module SkullIsland
191
189
  end
192
190
 
193
191
  # Used to validate {#consumer} on set
194
- def validate_consumer_id(value)
192
+ def validate_consumer(value)
195
193
  # allow either a Consumer object or a Hash of a specific structure
196
- value.is_a?(Consumer) || value.is_a?(String)
194
+ value.is_a?(Consumer) || value.is_a?(Hash)
197
195
  end
198
196
 
199
197
  # Used to validate {#route} on set
200
- def validate_route_id(value)
198
+ def validate_route(value)
199
+ # allow either a Route object or a Hash of a specific structure
200
+ value.is_a?(Route) || value.is_a?(Hash)
201
+ end
202
+
203
+ # Used to validate {#run_on} on set
204
+ def validate_run_on(value)
201
205
  # allow either a Route object or a Hash of a specific structure
202
- value.is_a?(Route) || value.is_a?(String)
206
+ %w[first second all].include?(value)
203
207
  end
204
208
 
205
209
  # Used to validate {#service} on set
206
- def validate_service_id(value)
210
+ def validate_service(value)
207
211
  # allow either a Service object or a Hash of a specific structure
208
- value.is_a?(Service) || value.is_a?(String)
212
+ value.is_a?(Service) || value.is_a?(Hash)
209
213
  end
210
214
  end
211
215
  end
@@ -5,8 +5,9 @@ module SkullIsland
5
5
  module Resources
6
6
  # The Route resource class
7
7
  #
8
- # @see https://docs.konghq.com/0.14.x/admin-api/#route-object Route API definition
8
+ # @see https://docs.konghq.com/1.1.x/admin-api/#route-object Route API definition
9
9
  class Route < Resource
10
+ property :name
10
11
  property :methods
11
12
  property :paths
12
13
  property :protocols, validate: true
@@ -14,23 +15,23 @@ module SkullIsland
14
15
  property :regex_priority, validate: true
15
16
  property :strip_path, type: :boolean
16
17
  property :preserve_host, type: :boolean
17
- # The following are 1.0+ only
18
- # property :name
19
- # property :snis
20
- # property :sources
21
- # property :destinations
18
+ property :snis, validate: true
19
+ property :sources
20
+ property :destinations
22
21
  property :service, validate: true, preprocess: true, postprocess: true
23
22
  property :created_at, read_only: true, postprocess: true
24
23
  property :updated_at, read_only: true, postprocess: true
24
+ property :tags, validate: true
25
25
 
26
26
  # rubocop:disable Metrics/CyclomaticComplexity
27
27
  # rubocop:disable Metrics/PerceivedComplexity
28
+ # rubocop:disable Metrics/AbcSize
28
29
  def self.batch_import(data, verbose: false, test: false)
29
30
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
30
31
 
31
32
  data.each_with_index do |rdata, index|
32
33
  resource = new
33
- # resource.name = rdata['name'] # 1.0+ only
34
+ resource.name = rdata['name']
34
35
  resource.methods = rdata['methods'] if rdata['methods']
35
36
  resource.paths = rdata['paths'] if rdata['paths']
36
37
  resource.protocols = rdata['protocols'] if rdata['protocols']
@@ -38,21 +39,25 @@ module SkullIsland
38
39
  resource.regex_priority = rdata['regex_priority'] if rdata['regex_priority']
39
40
  resource.strip_path = rdata['strip_path'] unless rdata['strip_path'].nil?
40
41
  resource.preserve_host = rdata['preserve_host'] unless rdata['preserve_host'].nil?
42
+ resource.snis = rdata['snis'] if rdata['snis']
43
+ resource.tags = rdata['tags'] if rdata['tags']
41
44
  resource.delayed_set(:service, rdata, 'service')
42
45
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
43
46
  end
44
47
  end
45
48
  # rubocop:enable Metrics/CyclomaticComplexity
46
49
  # rubocop:enable Metrics/PerceivedComplexity
50
+ # rubocop:enable Metrics/AbcSize
47
51
 
48
52
  # Provides a collection of related {Plugin} instances
49
53
  def plugins
50
54
  Plugin.where(:route, self, api_client: api_client)
51
55
  end
52
56
 
57
+ # rubocop:disable Metrics/AbcSize
53
58
  def export(options = {})
54
59
  hash = {
55
- # 'name' => name, # 1.0+ only
60
+ 'name' => name,
56
61
  'methods' => methods,
57
62
  'paths' => paths,
58
63
  'protocols' => protocols,
@@ -61,7 +66,9 @@ module SkullIsland
61
66
  'strip_path' => strip_path?,
62
67
  'preserve_host' => preserve_host?
63
68
  }
64
- hash['service'] = { 'id' => "<%= lookup :service, '#{service.name}' %>" } if service
69
+ hash['service'] = "<%= lookup :service, '#{service.name}' %>" if service
70
+ hash['snis'] = snis if snis && !snis.empty?
71
+ hash['tags'] = tags if tags
65
72
  [*options[:exclude]].each do |exclude|
66
73
  hash.delete(exclude.to_s)
67
74
  end
@@ -70,15 +77,13 @@ module SkullIsland
70
77
  end
71
78
  hash.reject { |_, value| value.nil? }
72
79
  end
80
+ # rubocop:enable Metrics/AbcSize
73
81
 
74
82
  def modified_existing?
75
83
  return false unless new?
76
84
 
77
- # Find routes of the same host, path, protocol, and service
78
- same_name_and_service = self.class.where(:protocols, protocols)
79
- .and(:hosts, hosts)
80
- .and(:paths, paths)
81
- .and(:service, service)
85
+ # Find routes of the same name and service
86
+ same_name_and_service = self.class.where(:name, name).and(:service, service)
82
87
 
83
88
  existing = same_name_and_service.size == 1 ? same_name_and_service.first : nil
84
89
 
@@ -140,6 +145,17 @@ module SkullIsland
140
145
  # allow either a Service object or a Hash of a specific structure
141
146
  value.is_a?(Service) || (value.is_a?(Hash) && value['id'].is_a?(String))
142
147
  end
148
+
149
+ # Used to validate {#snis} on set
150
+ def validate_snis(value)
151
+ return false unless value.is_a?(Array)
152
+
153
+ # allow only valid hostnames
154
+ value.each do |sni|
155
+ return false unless sni.match?(host_regex) && !sni.match?(/_/)
156
+ end
157
+ true
158
+ end
143
159
  end
144
160
  end
145
161
  end
@@ -5,7 +5,7 @@ module SkullIsland
5
5
  module Resources
6
6
  # The Service resource class
7
7
  #
8
- # @see https://docs.konghq.com/0.14.x/admin-api/#service-object Service API definition
8
+ # @see https://docs.konghq.com/1.1.x/admin-api/#service-object Service API definition
9
9
  class Service < Resource
10
10
  property :name
11
11
  property :retries
@@ -18,6 +18,7 @@ module SkullIsland
18
18
  property :read_timeout, validate: true
19
19
  property :created_at, read_only: true, postprocess: true
20
20
  property :updated_at, read_only: true, postprocess: true
21
+ property :tags, validate: true
21
22
 
22
23
  # rubocop:disable Metrics/CyclomaticComplexity
23
24
  # rubocop:disable Metrics/PerceivedComplexity
@@ -36,6 +37,7 @@ module SkullIsland
36
37
  resource.connect_timeout = rdata['connect_timeout'] if rdata['connect_timeout']
37
38
  resource.write_timeout = rdata['write_timeout'] if rdata['write_timeout']
38
39
  resource.read_timeout = rdata['read_timeout'] if rdata['read_timeout']
40
+ resource.tags = rdata['tags'] if rdata['tags']
39
41
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
40
42
 
41
43
  Route.batch_import(
@@ -80,6 +82,7 @@ module SkullIsland
80
82
  'read_timeout' => read_timeout
81
83
  }
82
84
  hash['routes'] = routes.collect { |route| route.export(exclude: 'service') }
85
+ hash['tags'] = tags if tags
83
86
  [*options[:exclude]].each do |exclude|
84
87
  hash.delete(exclude.to_s)
85
88
  end
@@ -5,7 +5,7 @@ module SkullIsland
5
5
  module Resources
6
6
  # The Upstream resource class
7
7
  #
8
- # @see https://docs.konghq.com/0.14.x/admin-api/#upstream-objects Upstream API definition
8
+ # @see https://docs.konghq.com/1.1.x/admin-api/#upstream-objects Upstream API definition
9
9
  class Upstream < Resource
10
10
  property :name, required: true, validate: true
11
11
  property :slots, validate: true
@@ -17,6 +17,7 @@ module SkullIsland
17
17
  property :hash_on_cookie_path, validate: true
18
18
  property :healthchecks, validate: true
19
19
  property :created_at, read_only: true, postprocess: true
20
+ property :tags, validate: true
20
21
 
21
22
  # rubocop:disable Metrics/CyclomaticComplexity
22
23
  # rubocop:disable Metrics/PerceivedComplexity
@@ -39,11 +40,12 @@ module SkullIsland
39
40
  resource.hash_on_cookie_path = rdata['hash_on_cookie_path']
40
41
  end
41
42
  resource.healthchecks = rdata['healthchecks'] if rdata['healthchecks']
43
+ resource.tags = rdata['tags'] if rdata['tags']
42
44
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
43
45
  puts '[INFO] Processing UpstreamTarget entries...' if verbose
44
46
 
45
47
  UpstreamTarget.batch_import(
46
- (rdata['targets'] || []).map { |t| t.merge('upstream_id' => resource.id) },
48
+ (rdata['targets'] || []).map { |t| t.merge('upstream' => { 'id' => resource.id }) },
47
49
  verbose: verbose,
48
50
  test: test
49
51
  )
@@ -77,7 +79,7 @@ module SkullIsland
77
79
 
78
80
  def target(target_id)
79
81
  UpstreamTarget.new(
80
- entity: { 'id' => target_id, 'upstream_id' => id },
82
+ entity: { 'id' => target_id, 'upstream' => { 'id' => id } },
81
83
  lazy: true,
82
84
  tainted: false,
83
85
  api_client: api_client
@@ -116,6 +118,7 @@ module SkullIsland
116
118
  'healthchecks' => healthchecks
117
119
  }
118
120
  hash['targets'] = targets.collect { |route| route.export(exclude: 'upstream_id') }
121
+ hash['tags'] = tags if tags
119
122
  [*options[:exclude]].each do |exclude|
120
123
  hash.delete(exclude.to_s)
121
124
  end
@@ -5,15 +5,16 @@ module SkullIsland
5
5
  module Resources
6
6
  # The Upstream Target resource class
7
7
  #
8
- # @see https://docs.konghq.com/0.14.x/admin-api/#target-object Target API definition
8
+ # @see https://docs.konghq.com/1.1.x/admin-api/#target-object Target API definition
9
9
  class UpstreamTarget < Resource
10
10
  property :target, required: true, validate: true, preprocess: true
11
11
  property(
12
- :upstream_id,
13
- required: true, validate: true, preprocess: true, postprocess: true, as: :upstream
12
+ :upstream,
13
+ required: true, validate: true, preprocess: true, postprocess: true
14
14
  )
15
15
  property :weight, validate: true
16
16
  property :created_at, read_only: true, postprocess: true
17
+ property :tags, validate: true
17
18
 
18
19
  def self.batch_import(data, verbose: false, test: false)
19
20
  raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
@@ -21,8 +22,9 @@ module SkullIsland
21
22
  data.each_with_index do |resource_data, index|
22
23
  resource = new
23
24
  resource.target = resource_data['target']
24
- resource.delayed_set(:upstream, resource_data, 'upstream_id')
25
+ resource.delayed_set(:upstream, resource_data, 'upstream')
25
26
  resource.weight = resource_data['weight'] if resource_data['weight']
27
+ resource.tags = resource_data['tags'] if resource_data['tags']
26
28
  resource.import_update_or_skip(index: index, verbose: verbose, test: test)
27
29
  end
28
30
  end
@@ -46,7 +48,8 @@ module SkullIsland
46
48
 
47
49
  def export(options = {})
48
50
  hash = { 'target' => target, 'weight' => weight }
49
- hash['upstream_id'] = "<%= lookup :upstream, '#{upstream.name}' %>" if upstream
51
+ hash['upstream'] = "<%= lookup :upstream, '#{upstream.name}' %>" if upstream
52
+ hash['tags'] = tags if tags
50
53
  [*options[:exclude]].each do |exclude|
51
54
  hash.delete(exclude.to_s)
52
55
  end
@@ -82,20 +85,20 @@ module SkullIsland
82
85
  end
83
86
  end
84
87
 
85
- def preprocess_upstream_id(input)
88
+ def preprocess_upstream(input)
86
89
  if input.is_a?(Hash)
87
- input['id']
88
- elsif input.is_a?(String)
89
90
  input
91
+ elsif input.is_a?(String)
92
+ { 'id' => input }
90
93
  else
91
- input.id
94
+ { 'id' => input.id }
92
95
  end
93
96
  end
94
97
 
95
- def postprocess_upstream_id(value)
96
- if value.is_a?(String)
98
+ def postprocess_upstream(value)
99
+ if value.is_a?(Hash)
97
100
  Upstream.new(
98
- entity: { 'id' => value },
101
+ entity: value,
99
102
  lazy: true,
100
103
  tainted: false
101
104
  )
@@ -113,9 +116,9 @@ module SkullIsland
113
116
  end
114
117
 
115
118
  # Used to validate #upstream on set
116
- def validate_upstream_id(value)
117
- # allow either a Upstream object or a String
118
- value.is_a?(Upstream) || value.is_a?(String)
119
+ def validate_upstream(value)
120
+ # allow either a Upstream object or a Hash
121
+ value.is_a?(Upstream) || value.is_a?(Hash) || value.is_a?(String)
119
122
  end
120
123
 
121
124
  # Used to validate {#weight} on set
@@ -19,6 +19,15 @@ module SkullIsland
19
19
  raise Exceptions::InvalidArguments if data[name.to_s].nil?
20
20
  end
21
21
  end
22
+
23
+ # Used to validate #tags on set
24
+ def validate_tags(value)
25
+ # allow only valid hostnames
26
+ value.each do |tag|
27
+ return false unless tag.is_a?(String) && tag.match?(/\w_-\.~/)
28
+ end
29
+ true
30
+ end
22
31
  end
23
32
  end
24
33
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module SkullIsland
4
4
  VERSION = [
5
- 0, # Major
6
- 14, # Minor
7
- 1 # Patch
5
+ 1, # Major
6
+ 1, # Minor
7
+ 1 # Patch
8
8
  ].join('.')
9
9
  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: 0.14.1
4
+ version: 1.1.1
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-05-01 00:00:00.000000000 Z
11
+ date: 2019-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deepsort
@@ -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/migration.rb
245
246
  - lib/skull_island/helpers/resource.rb
246
247
  - lib/skull_island/helpers/resource_class.rb
247
248
  - lib/skull_island/lru_cache.rb