geoserver_migrations 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []