geoserver_migrations 0.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 92b8423112ac07d6f582f135ff9dddc19ad21735
4
+ data.tar.gz: 9de2a13435f58793d77d197dc64995032bbb5570
5
+ SHA512:
6
+ metadata.gz: b3a41c386c5f07fa7472b8c3eb1d2be7c89e6872fbba86f0d6f1efc71e5bdd5a121570a0304e30be3baa1788b69d70c130066b01953de3582a5df38be0ac9868
7
+ data.tar.gz: d2c96d02f7eacca6fc46925c852abb6755a89050675f0df11c9fa7e98906d1c9d0bccf416b799206c0f7cc6a7892a3ae5a287b47dab7db9f2873593d661cd01d
@@ -0,0 +1,31 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'GeoserverMigrations'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+ require 'rake-version'
19
+ RakeVersion::Tasks.new do |v|
20
+ v.copy 'lib/geoserver_migrations/version.rb'
21
+ end
22
+
23
+
24
+ Bundler::GemHelper.install_tasks
25
+
26
+ require 'rspec/core'
27
+ require 'rspec/core/rake_task'
28
+
29
+ RSpec::Core::RakeTask.new(:spec)
30
+
31
+ task default: :spec
@@ -0,0 +1,21 @@
1
+ class GeoserverMigration < ActiveRecord::Base
2
+
3
+ self.primary_key = 'version'
4
+
5
+ def self.all_versions
6
+ order(:version).pluck(:version)
7
+ end
8
+
9
+ def self.set_migrated(migration)
10
+ self.create!(version: migration.version.to_s)
11
+ end
12
+
13
+ def self.set_reverted(migration)
14
+ self.where(version: migration.version.to_s).destroy_all
15
+ end
16
+
17
+ def version
18
+ super.to_i
19
+ end
20
+
21
+ end
@@ -0,0 +1,8 @@
1
+ class CreateGeoserverMigrations < ActiveRecord::Migration
2
+ def change
3
+ create_table :geoserver_migrations, id: false do |t|
4
+ t.string :version
5
+ end
6
+ add_index :geoserver_migrations, :version, unique: true
7
+ end
8
+ end
@@ -0,0 +1,26 @@
1
+ module GeoserverMigrations
2
+ module Generators
3
+ class AddGenerator < ::Rails::Generators::NamedBase
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ desc "Adds an empty named migration"
7
+
8
+ def add_migration
9
+ migration_name = file_name.underscore
10
+ class_name = file_name.camelize
11
+
12
+ create_file File.join(Rails.root, GeoserverMigrations.migrations_rootpath, "#{Time.now.strftime('%Y%m%d%H%M%S')}_#{migration_name}.rb"), <<-MIGRATION_FILE
13
+ class #{class_name} < GeoserverMigrations::Migration
14
+
15
+ def run
16
+ # add migration code here
17
+ end
18
+
19
+ end
20
+ MIGRATION_FILE
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ module GeoserverMigrations
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+ desc "This generator installs the default config file, and glue code into application-controller; which you can edit"
6
+
7
+
8
+ def copy_config_file
9
+ copy_file "geoserver_migrations_config.yml", "config/geoserver_migrations_config.yml"
10
+ end
11
+
12
+ def create_migrations_folder
13
+ # empty_directory "geoserver", "migrate"
14
+ empty_directory "geoserver/migrate"
15
+ empty_directory "geoserver/migrate/assets"
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ development: &common
2
+ # define migrations_path if you want to keep your migrations in a different folder
3
+ # migrations_path: 'geoserver/migrate'
4
+ geoserver_base: "http://localhost:8080/geoserver"
5
+ api:
6
+ user: admin
7
+ password: geoserver
8
+ workspace: your-workspace
9
+ datastore: your-datastore
10
+
11
+ test:
12
+ <<: *common # merges key:value pairs defined in development anchor
13
+
14
+ production:
15
+ <<: *common # merges key:value pairs defined in development anchor
@@ -0,0 +1,15 @@
1
+ module GeoserverMigrations
2
+ module Generators
3
+ class MigrateGenerator < ::Rails::Generators::Base
4
+ desc "Check and run the needed geoserver migrations"
5
+
6
+ def run_migrations
7
+ migrator = GeoserverMigrations::Migrator.new
8
+ migrator.migrations_paths = GeoserverMigrations.migrations_rootpath
9
+ migrator.migrations_paths = GeoserverMigrations.migrations_rootpath
10
+ migrator.migrate
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module GeoserverMigrations
2
+ module Generators
3
+ class RedoGenerator < ::Rails::Generators::Base
4
+ desc "Redo the last geoserver migrations"
5
+
6
+ def rollback_migrations
7
+ migrator = GeoserverMigrations::Migrator.new
8
+ migrator.migrations_paths = GeoserverMigrations.migrations_rootpath
9
+ # there should not be any pending migrations!
10
+ if migrator.needs_migration?
11
+ puts "There are pending migrations! We can only redo the last migration if there are no pending migrations."
12
+ else
13
+ migrator.rollback
14
+ migrator.migrate
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module GeoserverMigrations
2
+ module Generators
3
+ class RollbackGenerator < ::Rails::Generators::Base
4
+ desc "Rollback the last geoserver migrations"
5
+
6
+ def rollback_migrations
7
+ migrator = GeoserverMigrations::Migrator.new
8
+ migrator.migrations_paths = GeoserverMigrations.migrations_rootpath
9
+ migrator.rollback
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,53 @@
1
+ module GeoserverMigrations
2
+ module Generators
3
+ class StatusGenerator < ::Rails::Generators::Base
4
+ desc "Verify geoserver-migrations is setup correctly"
5
+
6
+ def check_status
7
+ puts "Checking configuration ... "
8
+ errors = []
9
+ errors << "geoserver_base is not set" unless GeoserverMigrations.config[:geoserver_base].present?
10
+ errors << "api-user is not set" unless GeoserverMigrations.config[:api][:user].present?
11
+ errors << "api-password is not set" unless GeoserverMigrations.config[:api][:password].present?
12
+
13
+ errors << "Geoserver workspace is not set" unless GeoserverMigrations.config[:workspace].present?
14
+ errors << "Geoserver datastore is not set" unless GeoserverMigrations.config[:datastore].present?
15
+
16
+ unless errors.count > 0
17
+ puts " --> Configuration ok"
18
+ else
19
+ puts "Problems with the configuration-file:\n #{errors.join("\n")}"
20
+ puts "Check the documentation how to fix them"
21
+ puts "Did you forget to edit the default config-file or to run rails g geoserver_migrations:install ?"
22
+ end
23
+
24
+ puts "Checking API reachability"
25
+ begin
26
+ layers = GeoserverMigrations::Base.connection.test
27
+ puts " --> Connected to API correctly and retrieved #{layers.count} layers"
28
+ rescue => e
29
+ puts " FAILED to reach Geoserver REST API."
30
+ puts " Error: #{e.message}"
31
+ end
32
+
33
+ puts "Checking database setup"
34
+ begin
35
+ count = GeoserverMigration.all_versions.count
36
+ puts " --> Database is correctly setup"
37
+ rescue => e
38
+ puts " Database is not setup correctly. Did you run rake db:migrate ?"
39
+ puts " The returned error is #{e.message}"
40
+ end
41
+
42
+ migrator = GeoserverMigrations::Migrator.new
43
+ if migrator.needs_migration?
44
+ puts "There are pending migrations."
45
+ else
46
+ puts "There are no pending migrations. Geoserver is up to date."
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,89 @@
1
+ require 'geoserver_migrations/migration_proxy'
2
+ require 'geoserver_migrations/migration'
3
+ require 'geoserver_migrations/migrator'
4
+ require 'geoserver_migrations/base'
5
+ require 'geoserver_migrations/test_connector'
6
+ require 'geoserver_migrations/api_connector'
7
+ require 'geoserver_migrations/layer_config'
8
+ require 'geoserver_migrations/layergroup_config'
9
+
10
+
11
+ module GeoserverMigrations
12
+
13
+ #define exceptions
14
+
15
+ module ExceptionsMixin
16
+ def initialize(str)
17
+ super("GeoserverMigrations: " + str)
18
+ end
19
+ end
20
+ class ArgumentError < ::ArgumentError #:nodoc:
21
+ include ExceptionsMixin
22
+ end
23
+
24
+ class IllegalMigrationNameError < StandardError #:nodoc:
25
+ include ExceptionsMixin
26
+ def initialize(name = nil)
27
+ if name
28
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).")
29
+ else
30
+ super("Illegal name for migration.")
31
+ end
32
+ end
33
+ end
34
+
35
+
36
+ def self.migrations_rootpath
37
+ (!GeoserverMigrations.config.nil? && GeoserverMigrations.config[:migrations_path]) || 'geoserver/migrate'
38
+ end
39
+
40
+ def self.assets_path
41
+ "#{self.migrations_rootpath}/assets"
42
+ end
43
+
44
+ def self.config
45
+ ::GEOSERVER_MIGRATIONS_CONFIG
46
+ end
47
+
48
+ # class AccessDenied < ::StandardError #:nodoc:
49
+ # include ExceptionsMixin
50
+ # def initialize(str='Access denied: you do not have the right permissions for the requested action.')
51
+ # super(str)
52
+ # end
53
+ # end
54
+
55
+
56
+ class Engine < ::Rails::Engine
57
+
58
+ # configure our plugin on boot
59
+ initializer "geoserver_migrations.initialize" do |app|
60
+
61
+ # mixin our code ?
62
+ # ActionController::Base.send(:include, Vigilante::ControllerExtension)
63
+ # ActiveRecord::Base.send(:include, Vigilante::ActiveRecordExtensions)
64
+
65
+ #load configuration files if they are available
66
+ geoserver_config_file = File.join("#{Rails.root.to_s}", 'config', 'geoserver_migrations_config.yml')
67
+
68
+ if File.exist?(geoserver_config_file)
69
+ Rails.logger.debug "define GEOSERVER_MIGRATIONS_CONFIG"
70
+ raw_config = File.read(geoserver_config_file)
71
+ ::GEOSERVER_MIGRATIONS_CONFIG = YAML.load(raw_config)["#{Rails.env}"].with_indifferent_access
72
+ # else
73
+ # raise GeoserverMigrations::ArgumentError.new("The geoserver_migrations_config.yml is missing. Path=#{geoserver_config_file} Did you run the generator geoserver_migrations:install? ")
74
+ end
75
+ end
76
+
77
+ initializer :append_migrations do |app|
78
+ unless app.root.to_s == root.to_s
79
+ config.paths["db/migrate"].expanded.each do |expanded_path|
80
+ app.config.paths["db/migrate"] << expanded_path
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+
89
+ end
@@ -0,0 +1,110 @@
1
+ module GeoserverMigrations
2
+ class APIConnector
3
+
4
+ def initialize
5
+ # initialise geoserver-client
6
+ GeoserverClient.api_root = ::GEOSERVER_MIGRATIONS_CONFIG[:geoserver_base]
7
+ GeoserverClient.api_user = ::GEOSERVER_MIGRATIONS_CONFIG[:api][:user]
8
+ GeoserverClient.api_password = ::GEOSERVER_MIGRATIONS_CONFIG[:api][:password]
9
+
10
+ GeoserverClient.workspace = GEOSERVER_MIGRATIONS_CONFIG[:workspace]
11
+ GeoserverClient.datastore = GEOSERVER_MIGRATIONS_CONFIG[:datastore]
12
+ # if Rails.present?
13
+ # GeoserverClient.logger = Rails.logger
14
+ # end
15
+ GeoserverClient.logger = :stdout
16
+ end
17
+
18
+ def execute(ordered_actions, direction = :up, options={})
19
+ if direction != :up
20
+ ordered_actions = ordered_actions.reverse
21
+ end
22
+
23
+ ordered_actions.each do |action_to_complete|
24
+ case action_to_complete[:action]
25
+ when :add_resource
26
+ file_name = action_to_complete[:params][:name]
27
+ if direction == :up
28
+ GeoserverClient.create_resource file_name
29
+ else
30
+ GeoserverClient.delete_resource file_name
31
+ end
32
+ when :create_layer
33
+ layer_name = action_to_complete[:params][:name]
34
+ layer_config = action_to_complete[:params][:layer_config]
35
+ if direction == :up
36
+ # explicitly create style if sld given
37
+ if !layer_config.sld.nil?
38
+ puts " -- Create style #{layer_config.style_name}"
39
+
40
+ GeoserverClient.create_style layer_config.style_name, sld: layer_config.sld
41
+ end
42
+
43
+ puts " -- Create layer #{layer_config.layer_name} [native_name = #{layer_config.feature_name}]"
44
+ GeoserverClient.create_featuretype layer_name, native_name: layer_config.feature_name
45
+ GeoserverClient.set_layer_style layer_name, layer_config.style_name
46
+ else
47
+ puts " -- delete layer #{layer_config.layer_name}"
48
+ GeoserverClient.delete_featuretype layer_name
49
+
50
+ if !layer_config.sld.nil?
51
+ puts " -- delete style #{layer_config.style_name}"
52
+
53
+ GeoserverClient.delete_style layer_config.style_name
54
+ end
55
+
56
+ end
57
+ when :delete_layer
58
+ if direction == :up
59
+ layer_name = action_to_complete[:params][:name]
60
+ puts " -- Delete layer #{layer_name}"
61
+ GeoserverClient.delete_featuretype layer_name
62
+ else
63
+ # do nothing??
64
+ # we should save the layer-definition in the :up direction so we can
65
+ # restore it if needed?
66
+ end
67
+ when :delete_style
68
+ if direction == :up
69
+ style_name = action_to_complete[:params][:name]
70
+ puts " -- Delete style #{style_name}"
71
+ GeoserverClient.delete_style style_name
72
+ end
73
+ when :update_style
74
+ if direction == :up
75
+ style_name = action_to_complete[:params][:name]
76
+ layer_config = action_to_complete[:params][:layer_config]
77
+ puts " -- Updating style #{style_name}"
78
+ GeoserverClient.update_style style_name, sld: layer_config.sld
79
+ end
80
+ when :create_layergroup
81
+ layer_name = action_to_complete[:params][:name]
82
+ layer_config = action_to_complete[:params][:layer_config]
83
+ if direction == :up
84
+ GeoserverClient.create_layergroup layer_name, layer_config.layers
85
+ else
86
+ puts " -- delete layergroup #{layer_config.layer_name}"
87
+ GeoserverClient.delete_layergroup layer_name
88
+ end
89
+ when :delete_layergroup
90
+ if direction == :up
91
+ layer_name = action_to_complete[:params][:name]
92
+ puts " -- Delete layergroup #{layer_name}"
93
+ GeoserverClient.delete_layergroup layer_name
94
+ else
95
+ # do nothing??
96
+ # we should save the layer-definition in the :up direction so we can
97
+ # restore it if needed?
98
+ end
99
+
100
+ end
101
+ end
102
+ end
103
+
104
+ def test
105
+ layers = GeoserverClient.layers
106
+ layers["featureTypes"]["featureType"].map{|l| l["name"]}
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,13 @@
1
+ module GeoserverMigrations
2
+ module Base
3
+
4
+ def self.connection=(connector)
5
+ @connection = connector
6
+ end
7
+
8
+ def self.connection
9
+ @connection ||= GeoserverMigrations::APIConnector.new
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ require 'geoserver_migrations/sld_helpers'
2
+
3
+ module GeoserverMigrations
4
+ class LayerConfig
5
+
6
+ # attr_writer :sld, :layer_name, :feature_name, :style_name
7
+ attr_accessor :options
8
+
9
+ def initialize(layer_name, is_update_style=false)
10
+ @options = {
11
+ layer_name: layer_name,
12
+ is_update_style: is_update_style
13
+ }
14
+
15
+ end
16
+
17
+ include GeoserverMigrations::SldHelpers
18
+
19
+ def valid?
20
+ # at least feature-name should be present?
21
+ options[:feature_name].present? || (options[:is_update_style] && options[:sld].present?)
22
+ end
23
+
24
+ def style_name(style_name=nil)
25
+ if style_name.nil?
26
+ @options[:style_name] || @options[:layer_name]
27
+ else
28
+ @options[:style_name] = style_name
29
+ end
30
+ end
31
+
32
+ def method_missing(method,*args,&block)
33
+ # puts "LayerConfig method-missing: #{method.inspect}, #{args.inspect}"
34
+ if [:sld, :layer_name, :feature_name, :style_name].include? method.to_sym
35
+ if args.size > 0
36
+ value = args.size == 1 ? args.first : args
37
+ value = value.strip if value.is_a? String
38
+ @options[method.to_sym] = value
39
+ else
40
+ @options[method.to_sym]
41
+ end
42
+ else
43
+ super
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ module GeoserverMigrations
2
+ class LayergroupConfig
3
+
4
+ attr_accessor :options
5
+
6
+ def initialize(layer_name, is_update_style=false)
7
+ @options = {
8
+ layer_name: layer_name,
9
+ is_update_style: is_update_style,
10
+ layers: []
11
+ }
12
+ end
13
+
14
+ def valid?
15
+ # at least one layer to be grouped should be present?
16
+ options[:layers].size > 0
17
+ end
18
+
19
+ def layers(*received_layers)
20
+ if received_layers.nil?
21
+ @options[:layers]
22
+ else
23
+ if received_layers.is_a? String
24
+ @options[:layers] = received_layers.split(/[,; ]/).select{|x| !x.empty?}
25
+ elsif received_layers.is_a? Array
26
+ received_layers.each do |received_layer|
27
+ @options[:layers] += received_layer.split(/[,; ]/).select{|x| !x.empty?}
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def method_missing(method,*args,&block)
34
+ # puts "LayerConfig method-missing: #{method.inspect}, #{args.inspect}"
35
+ if [:layer_name].include? method.to_sym
36
+ if args.size > 0
37
+ value = args.size == 1 ? args.first : args
38
+ value = value.strip if value.is_a? String
39
+ @options[method.to_sym] = value
40
+ else
41
+ @options[method.to_sym]
42
+ end
43
+ else
44
+ super
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,116 @@
1
+ module GeoserverMigrations
2
+ class Migration
3
+
4
+ attr_accessor :name, :version, :assets_path
5
+
6
+ def initialize(name = self.class.name, version = nil)
7
+ @name = name
8
+ @version = version
9
+ @connection = nil
10
+ @layers_to_create = {}
11
+ @layers_to_delete = []
12
+ @styles_to_delete = []
13
+ @ordered_actions_to_take = []
14
+ @assets_path = nil
15
+ end
16
+
17
+ def migrate(direction = :up)
18
+ return unless [:up, :down].include?(direction)
19
+
20
+ case direction
21
+ when :up then announce "migrating"
22
+ when :down then announce "reverting"
23
+ end
24
+
25
+ time = Benchmark.measure do
26
+ # collect configuration
27
+ run
28
+
29
+ # puts @layers_to_create.inspect
30
+
31
+ # create configured layers
32
+ GeoserverMigrations::Base.connection.execute(@ordered_actions_to_take, direction)
33
+ end
34
+
35
+ operation = direction == :up ? 'migrated' : 'reverted'
36
+ announce "#{operation} (%.4fs)" % time.real; write
37
+ end
38
+
39
+
40
+ def add_resource(resource_name)
41
+ # translate to full file-name and raise error if not exists?
42
+
43
+ full_resource_name = File.join(File.expand_path(self.assets_path), resource_name )
44
+
45
+ raise StandardError.new("File #{full_resource_name} not found!") unless File.exists?(full_resource_name)
46
+
47
+ @ordered_actions_to_take << {action: :add_resource, params: {name: full_resource_name }}
48
+ end
49
+
50
+ def create_layer(layer_name, &block)
51
+ layer_config = GeoserverMigrations::LayerConfig.new(layer_name)
52
+ layer_config.instance_eval(&block) if block_given?
53
+ if layer_config.valid?
54
+ @layers_to_create[layer_name] = layer_config
55
+ @ordered_actions_to_take << {action: :create_layer, params: {name: layer_name, layer_config: layer_config}}
56
+ end
57
+ end
58
+
59
+ def create_layergroup(layer_name, &block)
60
+ layergroup_config = GeoserverMigrations::LayergroupConfig.new(layer_name)
61
+ layergroup_config.instance_eval(&block) if block_given?
62
+ if layergroup_config.valid?
63
+ @layers_to_create[layer_name] = layergroup_config
64
+ @ordered_actions_to_take << {action: :create_layergroup, params: {name: layer_name, layer_config: layergroup_config}}
65
+ end
66
+ end
67
+
68
+ def update_style(layer_name, &block)
69
+ layer_config = GeoserverMigrations::LayerConfig.new(layer_name, true)
70
+ layer_config.instance_eval(&block) if block_given?
71
+ if layer_config.valid?
72
+ @ordered_actions_to_take << {action: :update_style, params: {name: layer_name, layer_config: layer_config}}
73
+ end
74
+ end
75
+
76
+ def delete_layer(layer_name)
77
+ @layers_to_delete << layer_name
78
+ @ordered_actions_to_take << {action: :delete_layer, params: {name: layer_name }}
79
+ end
80
+
81
+ def delete_layergroup(layer_name)
82
+ @layers_to_delete << layer_name
83
+ @ordered_actions_to_take << {action: :delete_layergroup, params: {name: layer_name }}
84
+ end
85
+
86
+ def delete_style(style_name)
87
+ @styles_to_delete << style_name
88
+ @ordered_actions_to_take << {action: :delete_style, params: {name: style_name }}
89
+ end
90
+
91
+ def write(text = "")
92
+ puts(text) unless Rails.env.test?
93
+ end
94
+
95
+ def announce(message)
96
+ text = "#{version} #{name}: #{message}"
97
+ length = [0, 75 - text.length].max
98
+ write "== %s %s" % [text, "=" * length]
99
+ end
100
+
101
+ def say(message, subitem = false)
102
+ write "#{subitem ? " ->" : "--"} #{message}"
103
+ end
104
+
105
+ def say_with_time(message)
106
+ say(message)
107
+ result = nil
108
+ time = Benchmark.measure { result = yield }
109
+ say "%.4fs" % time.real, :subitem
110
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
111
+ result
112
+ end
113
+
114
+
115
+ end
116
+ end
@@ -0,0 +1,34 @@
1
+ module GeoserverMigrations
2
+
3
+ # MigrationProxy is used to defer loading of the actual migration classes
4
+ # until they are needed
5
+ MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
6
+ def initialize(name, version, filename, scope)
7
+ super
8
+ @migration = nil
9
+ end
10
+
11
+ def basename
12
+ File.basename(filename)
13
+ end
14
+
15
+ def mtime
16
+ File.mtime filename
17
+ end
18
+
19
+ delegate :migrate, to: :migration
20
+ delegate :assets_path=, to: :migration
21
+
22
+ private
23
+
24
+ def migration
25
+ @migration ||= load_migration
26
+ end
27
+
28
+ def load_migration
29
+ require(File.expand_path(filename))
30
+ name.constantize.new(name, version)
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,109 @@
1
+ module GeoserverMigrations
2
+
3
+ class Migrator
4
+
5
+ MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/
6
+
7
+ def migrate
8
+ if !any_migrations?
9
+ puts "No migration files found!"
10
+ else
11
+ if !needs_migration?
12
+ puts "There are no pending migrations."
13
+ else
14
+ pending_migrations.each do |migration|
15
+ migration.assets_path = GeoserverMigrations.assets_path
16
+ migration.migrate
17
+
18
+ GeoserverMigration.set_migrated(migration)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ def rollback(steps=1)
26
+ if steps < 1
27
+ puts "The nr of steps to rollback has to be at least 1"
28
+ else
29
+ rollback_migrations(steps).each do |migration|
30
+ migration.assets_path = GeoserverMigrations.assets_path
31
+ migration.migrate(:down)
32
+
33
+ GeoserverMigration.set_reverted(migration)
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+
40
+ def get_all_versions
41
+ begin
42
+ GeoserverMigration.all_versions.map(&:to_i)
43
+ rescue
44
+ []
45
+ end
46
+ end
47
+
48
+ def current_version
49
+ get_all_versions.max || 0
50
+ end
51
+
52
+ def needs_migration?
53
+ (migrations(migrations_paths).collect(&:version) - get_all_versions).size > 0
54
+ end
55
+
56
+ def any_migrations?
57
+ migrations(migrations_paths).any?
58
+ end
59
+
60
+
61
+ def rollback_migrations(steps=1)
62
+ to_rollback = get_all_versions[0 - steps .. -1]
63
+ migrations = migrations(migrations_paths)
64
+ migrations.select { |m| to_rollback.include?(m.version) }
65
+ end
66
+
67
+ def pending_migrations
68
+ already_migrated = get_all_versions
69
+ migrations = migrations(migrations_paths)
70
+ migrations.reject { |m| already_migrated.include?(m.version) }
71
+ end
72
+
73
+
74
+ def migrations_paths=(paths)
75
+ @migrations_paths = paths
76
+ end
77
+
78
+ def migrations_paths
79
+ @migrations_paths ||= ["geoserver/migrate"]
80
+ # just to not break things if someone uses: migrations_path = some_string
81
+ Array(@migrations_paths)
82
+ end
83
+
84
+ def parse_migration_filename(filename) # :nodoc:
85
+ File.basename(filename).scan(MigrationFilenameRegexp).first
86
+ end
87
+
88
+ def migrations(paths)
89
+ paths = Array(paths)
90
+
91
+ migrations = migration_files(paths).map do |file|
92
+ version, name, scope = parse_migration_filename(file)
93
+ raise GeoserverMigrations::IllegalMigrationNameError.new(file) unless version
94
+ version = version.to_i
95
+ name = name.camelize
96
+
97
+ MigrationProxy.new(name, version, file, scope)
98
+ end
99
+
100
+ migrations.sort_by(&:version)
101
+ end
102
+
103
+ def migration_files(paths)
104
+ Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
105
+ end
106
+
107
+
108
+ end
109
+ end
@@ -0,0 +1,338 @@
1
+ module GeoserverMigrations
2
+ module SldHelpers
3
+
4
+
5
+ def build_sld_filter(filter_hash)
6
+ filter_text = ""
7
+ filters_count = filter_hash.keys.count
8
+ if filters_count > 0
9
+ if filters_count == 1
10
+ filter_text = <<-FILTER
11
+ <ogc:Filter>
12
+ <ogc:PropertyIsEqualTo>
13
+ <ogc:PropertyName>#{filter_hash.keys[0]}</ogc:PropertyName>
14
+ <ogc:Literal>#{filter_hash.values[0]}</ogc:Literal>
15
+ </ogc:PropertyIsEqualTo>
16
+ </ogc:Filter>
17
+ FILTER
18
+ elsif filters_count == 2
19
+ filter_text = <<-FILTER
20
+ <ogc:Filter>
21
+ <ogc:And>
22
+ <ogc:PropertyIsEqualTo>
23
+ <ogc:PropertyName>#{filter_hash.keys[0]}</ogc:PropertyName>
24
+ <ogc:Literal>#{filter_hash.values[0]}</ogc:Literal>
25
+ </ogc:PropertyIsEqualTo>
26
+ <ogc:PropertyIsEqualTo>
27
+ <ogc:PropertyName>#{filter_hash.keys[1]}</ogc:PropertyName>
28
+ <ogc:Literal>#{filter_hash.values[1]}</ogc:Literal>
29
+ </ogc:PropertyIsEqualTo>
30
+ </ogc:And>
31
+ </ogc:Filter>
32
+ FILTER
33
+ else
34
+ raise StandardError.new("Unsupported filter! For now we only support 1 or 2 filter-values :( :(")
35
+ end
36
+ end
37
+ filter_text
38
+ end
39
+
40
+
41
+ def icon_style_with_label(title, icon_name, icon_style_options={})
42
+ display_label = icon_style_options[:display_label] || title
43
+ abstract = icon_style_options[:abstract] || title
44
+
45
+ filter_text = ""
46
+ if icon_style_options[:filter].present? && icon_style_options[:filter].is_a?(Hash)
47
+ filter_text = build_sld_filter(icon_style_options[:filter])
48
+ end
49
+
50
+ max_scale_text = ""
51
+ if icon_style_options[:max_scale_denominator].present?
52
+ max_scale_text = "<MaxScaleDenominator>#{icon_style_options[:max_scale_denominator]}</MaxScaleDenominator>"
53
+ end
54
+
55
+ @options[:sld] = <<-SLD.strip_heredoc
56
+ <?xml version="1.0" encoding="UTF-8"?>
57
+ <StyledLayerDescriptor version="1.0.0" xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc"
58
+ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
59
+ xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd">
60
+ <NamedLayer>
61
+ <Name>#{title}</Name>
62
+ <UserStyle>
63
+ <Name>#{title}</Name>
64
+ <Title>#{title}</Title>
65
+ <Abstract>#{abstract}</Abstract>
66
+ <FeatureTypeStyle>
67
+ <Rule>
68
+ <Title>#{display_label}</Title>
69
+ #{filter_text}
70
+ #{max_scale_text}
71
+ <PointSymbolizer>
72
+ <Graphic>
73
+ <ExternalGraphic>
74
+ <OnlineResource xlink:type="simple" xlink:href="#{icon_name}" />
75
+ <Format>image/png</Format>
76
+ </ExternalGraphic>
77
+ </Graphic>
78
+ <VendorOption name="labelObstacle">true</VendorOption>
79
+ </PointSymbolizer>
80
+ <TextSymbolizer>
81
+ <Label>
82
+ <ogc:PropertyName>name</ogc:PropertyName>
83
+ </Label>
84
+ <Font>
85
+ <CssParameter name="font-family">Arial</CssParameter>
86
+ <CssParameter name="font-size">11</CssParameter>
87
+ <CssParameter name="font-style">normal</CssParameter>
88
+ <CssParameter name="font-weight">bold</CssParameter>
89
+ </Font>
90
+ <LabelPlacement>
91
+ <PointPlacement>
92
+ <AnchorPoint>
93
+ <AnchorPointX>0</AnchorPointX>
94
+ <AnchorPointY>0</AnchorPointY>
95
+ </AnchorPoint>
96
+ </PointPlacement>
97
+ </LabelPlacement>
98
+ <Halo>
99
+ <Radius>
100
+ <ogc:Literal>1</ogc:Literal>
101
+ </Radius>
102
+ <Fill>
103
+ <CssParameter name="fill">#ffffff</CssParameter>
104
+ </Fill>
105
+ </Halo>
106
+ <Fill>
107
+ <CssParameter name="fill">#2f3133</CssParameter>
108
+ </Fill>
109
+ <VendorOption name="maxDisplacement">40</VendorOption>
110
+ <!--VendorOption name="displacementMode">S, SW, W, NW, N</VendorOption-->
111
+ <VendorOption name="conflictResolution">true</VendorOption>
112
+ <VendorOption name="partials">true</VendorOption>
113
+ </TextSymbolizer>
114
+ </Rule>
115
+ </FeatureTypeStyle>
116
+ </UserStyle>
117
+ </NamedLayer>
118
+ </StyledLayerDescriptor>
119
+ SLD
120
+
121
+ end
122
+
123
+
124
+ def polygon_style(title, style_options={})
125
+ display_label = style_options[:display_label] || title
126
+ abstract = style_options[:abstract] || title
127
+
128
+ fill_colour = style_options[:fill_colour] || "#aaaaaa"
129
+ fill_opacity = style_options[:fill_opacity] || "1"
130
+ stroke_colour = style_options[:stroke_colour] || "#000000"
131
+ stroke_width = style_options[:stroke_width] || "1"
132
+
133
+ filter_text = ""
134
+ if style_options[:filter].present? && style_options[:filter].is_a?(Hash)
135
+ filter_text = build_sld_filter(style_options[:filter])
136
+ end
137
+
138
+ fill_style = ""
139
+ unless style_options[:no_fill] == true
140
+ fill_styles = ["<Fill>"]
141
+ fill_styles << " <CssParameter name=\"fill\">#{fill_colour}</CssParameter>"
142
+ fill_styles << " <CssParameter name=\"opacity\">#{fill_opacity}</CssParameter>" if style_options[:fill_opacity].present?
143
+ fill_styles << " </Fill>"
144
+ fill_style = fill_styles.join("\n")
145
+ end
146
+
147
+
148
+ stroke_styles = []
149
+ stroke_styles << "<Stroke>"
150
+ stroke_styles << " <CssParameter name=\"stroke\">#{stroke_colour}</CssParameter>"
151
+ stroke_styles << " <CssParameter name=\"stroke-width\">#{stroke_width}</CssParameter>"
152
+
153
+ if style_options[:stroke_dasharray].present?
154
+ stroke_styles << " <CssParameter name=\"stroke-dasharray\">"
155
+ stroke_styles << " <ogc:Literal>#{style_options[:stroke_dasharray]}</ogc:Literal>"
156
+ stroke_styles << " </CssParameter>"
157
+ end
158
+ stroke_styles << " </Stroke>"
159
+ stroke_style = stroke_styles.join("\n")
160
+
161
+ max_scale_text = ""
162
+ if style_options[:max_scale_denominator].present?
163
+ max_scale_text = "<MaxScaleDenominator>#{style_options[:max_scale_denominator]}</MaxScaleDenominator>"
164
+ end
165
+
166
+ @options[:sld] = <<-SLD.strip_heredoc
167
+ <?xml version="1.0" encoding="UTF-8"?>
168
+ <StyledLayerDescriptor version="1.0.0"
169
+ xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
170
+ xmlns="http://www.opengis.net/sld"
171
+ xmlns:ogc="http://www.opengis.net/ogc"
172
+ xmlns:xlink="http://www.w3.org/1999/xlink"
173
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
174
+ <NamedLayer>
175
+ <Name>#{title}</Name>
176
+ <UserStyle>
177
+ <Name>#{title}</Name>
178
+ <Title>#{title}</Title>
179
+ <Abstract>#{abstract}</Abstract>
180
+ <FeatureTypeStyle>
181
+ <Rule>
182
+ <Title>#{display_label}</Title>
183
+ #{filter_text}
184
+ #{max_scale_text}
185
+ <PolygonSymbolizer>
186
+ #{fill_style}
187
+ #{stroke_style}
188
+ </PolygonSymbolizer>
189
+ </Rule>
190
+ </FeatureTypeStyle>
191
+ </UserStyle>
192
+ </NamedLayer>
193
+ </StyledLayerDescriptor>
194
+ SLD
195
+
196
+ end
197
+
198
+
199
+ def line_style(title, style_options={})
200
+ display_label = style_options[:display_label] || title
201
+ abstract = style_options[:abstract] || title
202
+
203
+ stroke_colour = style_options[:stroke_colour] || "#000000"
204
+ stroke_width = style_options[:stroke_width] || "1"
205
+
206
+
207
+ filter_text = ""
208
+ if style_options[:filter].present? && style_options[:filter].is_a?(Hash)
209
+ filter_text = build_sld_filter(style_options[:filter])
210
+ end
211
+
212
+ max_scale_text = ""
213
+ if style_options[:max_scale_denominator].present?
214
+ max_scale_text = "<MaxScaleDenominator>#{style_options[:max_scale_denominator]}</MaxScaleDenominator>"
215
+ end
216
+
217
+ @options[:sld] = <<-SLD.strip_heredoc
218
+ <?xml version="1.0" encoding="UTF-8"?>
219
+ <StyledLayerDescriptor version="1.0.0"
220
+ xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
221
+ xmlns="http://www.opengis.net/sld"
222
+ xmlns:ogc="http://www.opengis.net/ogc"
223
+ xmlns:xlink="http://www.w3.org/1999/xlink"
224
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
225
+ <NamedLayer>
226
+ <Name>#{title}</Name>
227
+ <UserStyle>
228
+ <Name>#{title}</Name>
229
+ <Title>#{title}</Title>
230
+ <Abstract>#{abstract}</Abstract>
231
+ <FeatureTypeStyle>
232
+ <Rule>
233
+ <Title>#{display_label}</Title>
234
+ #{filter_text}
235
+ #{max_scale_text}
236
+ <LineSymbolizer>
237
+ <Stroke>
238
+ <CssParameter name="stroke">#{stroke_colour}</CssParameter>
239
+ <CssParameter name="stroke-width">#{stroke_width}</CssParameter>
240
+ </Stroke>
241
+ </LineSymbolizer>
242
+ </Rule>
243
+ </FeatureTypeStyle>
244
+ </UserStyle>
245
+ </NamedLayer>
246
+ </StyledLayerDescriptor>
247
+ SLD
248
+
249
+ end
250
+
251
+ # def polygon_style_with_label(title, style_options={})
252
+ # display_label = icon_style_options[:display_label] || title
253
+ # abstract = icon_style_options[:abstract] || title
254
+ #
255
+ #
256
+ #
257
+ #
258
+ # @options[:sld] = <<-SLD
259
+ # <?xml version="1.0" encoding="ISO-8859-1"?>
260
+ # <StyledLayerDescriptor version="1.0.0"
261
+ # xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
262
+ # xmlns="http://www.opengis.net/sld"
263
+ # xmlns:ogc="http://www.opengis.net/ogc"
264
+ # xmlns:xlink="http://www.w3.org/1999/xlink"
265
+ # xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
266
+ # <NamedLayer>
267
+ # <Name>#{title}</Name>
268
+ # <UserStyle>
269
+ # <Title>#{title}</Title>
270
+ # <Abstract>#{abstract}</Abstract>
271
+ # <FeatureTypeStyle>
272
+ # <Rule>
273
+ # <Title>#{display_label}</Title>
274
+ # <MinScaleDenominator>10000</MinScaleDenominator>
275
+ # <MaxScaleDenominator>200000</MaxScaleDenominator>
276
+ # <PolygonSymbolizer>
277
+ # <Fill>
278
+ # <CssParameter name="fill">#AAAAAA</CssParameter>
279
+ # <CssParameter name="fill-opacity">0.3</CssParameter>
280
+ # </Fill>
281
+ # <Stroke>
282
+ # <CssParameter name="stroke">#FFFFFF</CssParameter>
283
+ # <CssParameter name="stroke-width">3</CssParameter>
284
+ # </Stroke>
285
+ # </PolygonSymbolizer>
286
+ # </Rule>
287
+ # <Rule>
288
+ # <Name>rule2</Name>
289
+ # <Title>Beheerkaart ingezoomed</Title>
290
+ # <Abstract>A polygon with a gray fill and a 3 pixel while outline and a label indicating the name</Abstract>
291
+ # <MaxScaleDenominator>10000</MaxScaleDenominator>
292
+ # <PolygonSymbolizer>
293
+ # <Fill>
294
+ # <CssParameter name="fill">#AAAAAA</CssParameter>
295
+ # <CssParameter name="fill-opacity">0.3</CssParameter>
296
+ # </Fill>
297
+ # <Stroke>
298
+ # <CssParameter name="stroke">#FFFFFF</CssParameter>
299
+ # <CssParameter name="stroke-width">3</CssParameter>
300
+ # </Stroke>
301
+ # </PolygonSymbolizer>
302
+ # <TextSymbolizer>
303
+ # <Geometry>
304
+ # <ogc:Function name="centroid">
305
+ # <ogc:PropertyName>geom</ogc:PropertyName>
306
+ # </ogc:Function>
307
+ # </Geometry>
308
+ # <Label>
309
+ # <ogc:PropertyName>name</ogc:PropertyName>
310
+ # </Label>
311
+ # <Font>
312
+ # <CssParameter name="font-family">Arial</CssParameter>
313
+ # <CssParameter name="font-size">18</CssParameter>
314
+ # <CssParameter name="font-style">normal</CssParameter>
315
+ # <CssParameter name="font-weight">bold</CssParameter>
316
+ # </Font>
317
+ # <Halo>
318
+ # <Radius>2</Radius>
319
+ # <Fill>
320
+ # <CssParameter name="fill">#FFFFFF</CssParameter>
321
+ # </Fill>
322
+ # </Halo>
323
+ # <Fill>
324
+ # <CssParameter name="fill">#000</CssParameter>
325
+ # </Fill>
326
+ # </TextSymbolizer>
327
+ # </Rule>
328
+ # </FeatureTypeStyle>
329
+ # </UserStyle>
330
+ # </NamedLayer>
331
+ # </StyledLayerDescriptor>
332
+ #
333
+ # SLD
334
+ #
335
+ # end
336
+
337
+ end
338
+ end
@@ -0,0 +1,15 @@
1
+ module GeoserverMigrations
2
+ class TestConnector
3
+
4
+ attr_accessor :collected_actions
5
+
6
+ def initialize
7
+ @collected_actions = {}
8
+ end
9
+
10
+ def execute(layer_configs, direction=:up, options={})
11
+ collected_actions.merge!(direction => layer_configs)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module GeoserverMigrations
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :geoserver_migrations do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geoserver_migrations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - "'nathanvda'"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.7
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.2.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: 'Manage your geoserver configuration like you manage your database: with
84
+ migrations'
85
+ email:
86
+ - "'nathan@dixis.com'"
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - Rakefile
92
+ - app/models/geoserver_migration.rb
93
+ - db/migrate/20180104170500_create_geoserver_migrations.rb
94
+ - lib/generators/geoserver_migrations/add/add_generator.rb
95
+ - lib/generators/geoserver_migrations/add/templates/migration_template.rb
96
+ - lib/generators/geoserver_migrations/install/install_generator.rb
97
+ - lib/generators/geoserver_migrations/install/templates/geoserver_migrations_config.yml
98
+ - lib/generators/geoserver_migrations/migrate/migrate_generator.rb
99
+ - lib/generators/geoserver_migrations/redo/redo_generator.rb
100
+ - lib/generators/geoserver_migrations/rollback/rollback_generator.rb
101
+ - lib/generators/geoserver_migrations/status/status_generator.rb
102
+ - lib/geoserver_migrations.rb
103
+ - lib/geoserver_migrations/api_connector.rb
104
+ - lib/geoserver_migrations/base.rb
105
+ - lib/geoserver_migrations/layer_config.rb
106
+ - lib/geoserver_migrations/layergroup_config.rb
107
+ - lib/geoserver_migrations/migration.rb
108
+ - lib/geoserver_migrations/migration_proxy.rb
109
+ - lib/geoserver_migrations/migrator.rb
110
+ - lib/geoserver_migrations/sld_helpers.rb
111
+ - lib/geoserver_migrations/test_connector.rb
112
+ - lib/geoserver_migrations/version.rb
113
+ - lib/tasks/geoserver_migrations_tasks.rake
114
+ homepage: https://github.com/nathanvda/geoserver_migrations
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.2.2
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: manage your geoserver configuration using migrations
138
+ test_files: []