fulmar 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/fulmar.gemspec +2 -1
- data/lib/fulmar/domain/service/application_service.rb +1 -1
- data/lib/fulmar/domain/service/config_rendering_service.rb +2 -1
- data/lib/fulmar/domain/service/configuration_service.rb +23 -22
- data/lib/fulmar/domain/task/base.rake +3 -3
- data/lib/fulmar/domain/task/database_sync.rake +1 -3
- data/lib/fulmar/domain/task/versions.rake +1 -8
- data/lib/fulmar/infrastructure/service/composer_service.rb +3 -2
- data/lib/fulmar/infrastructure/service/database/database_service.rb +16 -20
- data/lib/fulmar/infrastructure/service/git_service.rb +7 -10
- data/lib/fulmar/infrastructure/service/transfer/rsync.rb +15 -8
- data/lib/fulmar/infrastructure/service/tunnel_service.rb +7 -8
- data/lib/fulmar/service/helper/common_helper.rb +15 -2
- data/lib/fulmar/service/helper/database_helper.rb +1 -0
- data/lib/fulmar/service/helper/flow_helper.rb +1 -0
- data/lib/fulmar/version.rb +1 -1
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec0fe71a95742d097b3d61053620fee7453fcd7f
|
4
|
+
data.tar.gz: d92d58a45c299f073afb02ea32e26fe2344b3d68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f8ebdc7e5247a90f35db2c30bfbef6630679f05b25b689325e2d984f3d23032df58e1e69d37e9981dac0461b28e2ed972badecc351965d11bdb8c35cb4a590e
|
7
|
+
data.tar.gz: 777be47a8bb21a2343fd5d8934464f3000133f8b287206a6afa3eee9d4cd8de90c99fbfca80524d189f038304c5030937c63322b9f6f538b93982bf63cfff347
|
data/fulmar.gemspec
CHANGED
@@ -21,8 +21,9 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
22
|
|
23
23
|
spec.add_runtime_dependency 'rake', '~>10'
|
24
|
-
spec.add_runtime_dependency '
|
24
|
+
spec.add_runtime_dependency 'rugged', '~>0'
|
25
25
|
spec.add_runtime_dependency 'mysql2', '~>0.3'
|
26
26
|
spec.add_runtime_dependency 'fulmar-shell', '~>1', '>=1.4.0'
|
27
27
|
spec.add_runtime_dependency 'ruby_wings', '~>0.1', '>=0.1.0'
|
28
|
+
spec.add_runtime_dependency 'colorize', '~>0'
|
28
29
|
end
|
@@ -11,7 +11,8 @@ module Fulmar
|
|
11
11
|
|
12
12
|
def render
|
13
13
|
return unless @config[:config_templates]
|
14
|
-
@config[:config_templates].each do |
|
14
|
+
@config[:config_templates].each do |template_file|
|
15
|
+
template = "#{@config[:local_path]}/#{template_file}"
|
15
16
|
fail "Template filenames must end in .erb - '#{template}' does not" unless template[-4, 4] == '.erb'
|
16
17
|
fail "Cannot render missing config file '#{template}'" unless File.exist? template
|
17
18
|
|
@@ -33,7 +33,7 @@ module Fulmar
|
|
33
33
|
else
|
34
34
|
fail 'Environment or target not set. Please set both variables via configuration.environment = \'xxx\' / '\
|
35
35
|
'configuration.target = \'yyy\''
|
36
|
-
|
36
|
+
end
|
37
37
|
end
|
38
38
|
|
39
39
|
def to_hash
|
@@ -61,10 +61,13 @@ module Fulmar
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def ready?
|
64
|
-
|
64
|
+
return false if @environment.nil? || @target.nil?
|
65
|
+
fail 'Environment is invalid' if configuration[:environments][@environment].nil?
|
66
|
+
fail 'Target is invalid' if configuration[:environments][@environment][@target].nil?
|
67
|
+
true
|
65
68
|
end
|
66
69
|
|
67
|
-
def
|
70
|
+
def feature?(feature)
|
68
71
|
return configuration[:features][feature] unless configuration[:features][feature].nil?
|
69
72
|
case feature
|
70
73
|
when :database
|
@@ -84,7 +87,7 @@ module Fulmar
|
|
84
87
|
|
85
88
|
def any?
|
86
89
|
if block_given?
|
87
|
-
each{ |_env, _target, data| return true if yield(data) }
|
90
|
+
each { |_env, _target, data| return true if yield(data) }
|
88
91
|
false
|
89
92
|
else
|
90
93
|
configuration[:environments].any?
|
@@ -96,9 +99,8 @@ module Fulmar
|
|
96
99
|
# Hashes are merged.
|
97
100
|
# @param [Hash] other
|
98
101
|
def merge(other)
|
99
|
-
|
100
|
-
|
101
|
-
end
|
102
|
+
return unless @environment && @target
|
103
|
+
configuration[:environments][@environment][@target] = other.deep_merge(configuration[:environments][@environment][@target])
|
102
104
|
end
|
103
105
|
|
104
106
|
def config_files
|
@@ -121,25 +123,25 @@ module Fulmar
|
|
121
123
|
# Fills a target with all globally set variables so all necessary information
|
122
124
|
# is found within each target
|
123
125
|
def fill_target(env, target)
|
124
|
-
@config[:environments][env][target] = @config[:environments][:all].deep_merge(@config[:environments][env][target])
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
else
|
133
|
-
fail "Host #{host} is not configured."
|
126
|
+
@config[:environments][env][target] = @config[:environments][:all].deep_merge(@config[:environments][env][target]) if @config[:environments][:all]
|
127
|
+
|
128
|
+
return if @config[:environments][env][target][:host].blank?
|
129
|
+
|
130
|
+
host = @config[:environments][env][target][:host].to_sym
|
131
|
+
if @config[:hosts] && @config[:hosts][host]
|
132
|
+
@config[:hosts][host].each do
|
133
|
+
@config[:environments][env][target] = @config[:hosts][host].deep_merge(@config[:environments][env][target])
|
134
134
|
end
|
135
|
+
else
|
136
|
+
fail "Host #{host} is not configured."
|
135
137
|
end
|
136
138
|
end
|
137
139
|
|
138
140
|
# Loads the configuration from the YAML file and populates all targets
|
139
141
|
def load_configuration
|
140
|
-
@config = { environments: {}, features: {} }
|
142
|
+
@config = { environments: {}, features: {}, hosts: {} }
|
141
143
|
config_files.each do |config_file|
|
142
|
-
@config = @config.deep_merge(YAML.load_file(config_file).symbolize)
|
144
|
+
@config = @config.deep_merge((YAML.load_file(config_file) || {}).symbolize)
|
143
145
|
end
|
144
146
|
|
145
147
|
# Iterate over all environments and targets to prepare them
|
@@ -155,9 +157,8 @@ module Fulmar
|
|
155
157
|
end
|
156
158
|
|
157
159
|
def check_path(env, target)
|
158
|
-
|
159
|
-
|
160
|
-
end
|
160
|
+
return if @config[:environments][env][target][:local_path].blank?
|
161
|
+
@config[:environments][env][target][:local_path] = File.expand_path(@config[:environments][env][target][:local_path])
|
161
162
|
end
|
162
163
|
end
|
163
164
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
include Fulmar::Domain::Service::Helper::CommonHelper
|
2
2
|
|
3
|
-
if configuration.
|
3
|
+
if configuration.feature? :database
|
4
4
|
require 'fulmar/service/helper/database_helper'
|
5
5
|
include Fulmar::Domain::Service::Helper::DatabaseHelper
|
6
6
|
end
|
7
7
|
|
8
|
-
if configuration.
|
8
|
+
if configuration.feature? :flow
|
9
9
|
require 'fulmar/service/helper/flow_helper'
|
10
10
|
include Fulmar::Domain::Service::Helper::FlowHelper
|
11
|
-
end
|
11
|
+
end
|
@@ -1,12 +1,7 @@
|
|
1
1
|
include Fulmar::Domain::Service::Helper::CommonHelper
|
2
2
|
|
3
3
|
namespace :versions do
|
4
|
-
|
5
4
|
@versioned_servers = {}
|
6
|
-
configuration.each do |env, target, data|
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
5
|
|
11
6
|
full_configuration[:environments].each_pair do |env, targets|
|
12
7
|
next if env == :all
|
@@ -23,7 +18,7 @@ namespace :versions do
|
|
23
18
|
task env do
|
24
19
|
configuration.environment = env.split(':').first
|
25
20
|
configuration.target = env.split(':').last
|
26
|
-
file_sync.list_releases(false).each{|item| puts item}
|
21
|
+
file_sync.list_releases(false).each { |item| puts item }
|
27
22
|
end
|
28
23
|
end
|
29
24
|
end
|
@@ -38,7 +33,5 @@ namespace :versions do
|
|
38
33
|
end
|
39
34
|
end
|
40
35
|
end
|
41
|
-
|
42
|
-
|
43
36
|
end
|
44
37
|
end
|
@@ -3,12 +3,13 @@ module Fulmar
|
|
3
3
|
module Service
|
4
4
|
# Provides access to composer
|
5
5
|
class ComposerService
|
6
|
-
def initialize(custom_path = '/usr/bin/env composer')
|
6
|
+
def initialize(shell, custom_path = '/usr/bin/env composer')
|
7
|
+
@local_shell = shell
|
7
8
|
@path = custom_path
|
8
9
|
end
|
9
10
|
|
10
11
|
def execute(command, arguments = [])
|
11
|
-
|
12
|
+
@local_shell.run "#{@path} #{command} #{arguments.join(' ')} > /dev/null"
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -8,7 +8,8 @@ module Fulmar
|
|
8
8
|
# Provides basic methods common to all database services
|
9
9
|
class DatabaseService
|
10
10
|
attr_accessor :client
|
11
|
-
attr_reader :shell
|
11
|
+
attr_reader :shell, :connected
|
12
|
+
alias_method :connected?, :connected
|
12
13
|
|
13
14
|
DEFAULT_CONFIG = {
|
14
15
|
maria: {
|
@@ -16,7 +17,7 @@ module Fulmar
|
|
16
17
|
port: 3306,
|
17
18
|
user: 'root',
|
18
19
|
password: '',
|
19
|
-
encoding: 'utf8'
|
20
|
+
encoding: 'utf8'
|
20
21
|
}
|
21
22
|
}
|
22
23
|
|
@@ -39,13 +40,7 @@ module Fulmar
|
|
39
40
|
|
40
41
|
# Wait max 3 seconds for the tunnel to establish
|
41
42
|
4.times do |i|
|
42
|
-
|
43
|
-
@client = Mysql2::Client.new options
|
44
|
-
break
|
45
|
-
rescue Mysql2::Error => e
|
46
|
-
sleep 1 if i < 3
|
47
|
-
fail e.message if i == 3
|
48
|
-
end
|
43
|
+
break if try_connect(options, i)
|
49
44
|
end
|
50
45
|
|
51
46
|
@connected = true
|
@@ -57,10 +52,6 @@ module Fulmar
|
|
57
52
|
@tunnel.close if @tunnel # using the variable directly avoids creating a tunnel instance when closing the database connection
|
58
53
|
end
|
59
54
|
|
60
|
-
def connected?
|
61
|
-
@connected
|
62
|
-
end
|
63
|
-
|
64
55
|
def local?
|
65
56
|
@config[:hostname] == 'localhost'
|
66
57
|
end
|
@@ -109,6 +100,13 @@ module Fulmar
|
|
109
100
|
|
110
101
|
protected
|
111
102
|
|
103
|
+
def try_connect(options, i)
|
104
|
+
@client = Mysql2::Client.new options
|
105
|
+
rescue Mysql2::Error => e
|
106
|
+
sleep 1 if i < 3
|
107
|
+
raise e.message if i == 3
|
108
|
+
end
|
109
|
+
|
112
110
|
# Test configuration
|
113
111
|
def config_test
|
114
112
|
fail 'Configuration option "database" missing.' unless @config[:maria][:database]
|
@@ -132,19 +130,17 @@ module Fulmar
|
|
132
130
|
# Compiles a mysql config hash from valid options of the fulmar config
|
133
131
|
def compile_options
|
134
132
|
possible_options = [:host, :username, :password, :port, :encoding, :socket, :read_timeout, :write_timeout,
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
options = {}
|
139
|
-
|
140
|
-
options[:username] = @config[:maria][:user]
|
133
|
+
:connect_timeout, :reconnect, :local_infile, :secure_auth, :default_file, :default_group,
|
134
|
+
:init_command
|
135
|
+
]
|
136
|
+
options = { host: '127.0.0.1', username: @config[:maria][:user] }
|
137
|
+
|
141
138
|
possible_options.each do |option|
|
142
139
|
options[option] = @config[:maria][option] unless @config[:maria][option].nil?
|
143
140
|
end
|
144
141
|
|
145
142
|
options
|
146
143
|
end
|
147
|
-
|
148
144
|
end
|
149
145
|
end
|
150
146
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'rugged'
|
2
2
|
|
3
3
|
module Fulmar
|
4
4
|
module Infrastructure
|
@@ -29,29 +29,27 @@ module Fulmar
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
@git =
|
32
|
+
@git = Rugged::Repository.new(@config[:local_path]) # :log => Logger.new(STDOUT)
|
33
33
|
end
|
34
34
|
|
35
35
|
def branches
|
36
|
-
@git.branches
|
36
|
+
@git.branches.collect(:name)
|
37
37
|
end
|
38
38
|
|
39
39
|
def feature_branches
|
40
|
-
|
40
|
+
branches.select { |name| name.match(/^feature_/) }.sort
|
41
41
|
end
|
42
42
|
|
43
43
|
def preview_branches
|
44
|
-
|
44
|
+
branches.select { |name| name.match(/^preview_/) }.sort
|
45
45
|
end
|
46
46
|
|
47
47
|
def checkout(branch_name = derive_branch_name)
|
48
|
-
|
49
|
-
if branches.any?
|
48
|
+
if branches.include?(branch_name)
|
50
49
|
@git.checkout(branches.first)
|
51
50
|
else
|
52
|
-
branches = @git.branches.
|
51
|
+
branches = @git.branches.select { |b| b.name.match(/\/#{branch_name}$/) }
|
53
52
|
fail "Cannot find a valid branch, last search was for #{branch_name}" unless branches.any?
|
54
|
-
@git.branch(branches.first)
|
55
53
|
@git.checkout(branches.first)
|
56
54
|
end
|
57
55
|
end
|
@@ -60,7 +58,6 @@ module Fulmar
|
|
60
58
|
def derive_branch_name
|
61
59
|
@config[:git][:branch] == 'preview' ? preview_branches.last : @config[:git][:branch]
|
62
60
|
end
|
63
|
-
|
64
61
|
end
|
65
62
|
end
|
66
63
|
end
|
@@ -33,14 +33,8 @@ module Fulmar
|
|
33
33
|
@local_shell.run rsync_command
|
34
34
|
end
|
35
35
|
|
36
|
+
# Build the rsync command from the given options
|
36
37
|
def rsync_command
|
37
|
-
options = ['-rl']
|
38
|
-
options << "--exclude='#{@config[:rsync][:exclude]}'" if @config[:rsync][:exclude]
|
39
|
-
options << "--exclude-from='#{@config[:rsync][:exclude_file]}'" if @config[:rsync][:exclude_file]
|
40
|
-
options << "--chown='#{@config[:rsync][:chown]}'" if @config[:rsync][:chown]
|
41
|
-
options << "--chmod='#{@config[:rsync][:chmod]}'" if @config[:rsync][:chmod]
|
42
|
-
options << '--delete' if @config[:rsync][:delete]
|
43
|
-
|
44
38
|
if @config[:rsync][:direction] == 'up'
|
45
39
|
from = @config[:local_path]
|
46
40
|
to = ssh_user_and_host + ':' + @config[:remote_path]
|
@@ -49,7 +43,20 @@ module Fulmar
|
|
49
43
|
to = @config[:local_path]
|
50
44
|
end
|
51
45
|
|
52
|
-
"rsync #{
|
46
|
+
"rsync #{rsync_command_options.join(' ')} '#{from}/' '#{to}'"
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
# Assembles all rsync command line parameters from the configuration options
|
52
|
+
def rsync_command_options
|
53
|
+
options = ['-rl']
|
54
|
+
options << "--exclude='#{@config[:rsync][:exclude]}'" if @config[:rsync][:exclude]
|
55
|
+
options << "--exclude-from='#{@config[:rsync][:exclude_file]}'" if @config[:rsync][:exclude_file]
|
56
|
+
options << "--chown='#{@config[:rsync][:chown]}'" if @config[:rsync][:chown]
|
57
|
+
options << "--chmod='#{@config[:rsync][:chmod]}'" if @config[:rsync][:chmod]
|
58
|
+
options << '--delete' if @config[:rsync][:delete]
|
59
|
+
options
|
53
60
|
end
|
54
61
|
end
|
55
62
|
end
|
@@ -3,6 +3,7 @@ require 'socket'
|
|
3
3
|
module Fulmar
|
4
4
|
module Infrastructure
|
5
5
|
module Service
|
6
|
+
# Opens an ssh tunnel to a remote host so other services can access mysql for example
|
6
7
|
class TunnelService
|
7
8
|
attr_reader :host, :remote_port, :local_port
|
8
9
|
|
@@ -31,20 +32,18 @@ module Fulmar
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def free_port
|
34
|
-
|
35
|
-
|
36
|
-
1000.times do
|
35
|
+
(60_000..61_000).each do |port|
|
36
|
+
begin
|
37
37
|
socket = TCPSocket.new('localhost', port)
|
38
38
|
socket.close
|
39
|
-
|
39
|
+
rescue Errno::ECONNREFUSED
|
40
|
+
return port
|
40
41
|
end
|
41
|
-
rescue Errno::ECONNREFUSED
|
42
|
-
return port
|
43
42
|
end
|
43
|
+
|
44
44
|
fail 'Cannot find an open local port'
|
45
|
-
0
|
46
45
|
end
|
47
46
|
end
|
48
47
|
end
|
49
48
|
end
|
50
|
-
end
|
49
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fulmar/domain/service/configuration_service'
|
2
|
+
require 'colorize'
|
2
3
|
|
3
4
|
module Fulmar
|
4
5
|
module Domain
|
@@ -17,7 +18,7 @@ module Fulmar
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def composer(command, arguments = [])
|
20
|
-
(storage['composer'] ||= Fulmar::Infrastructure::Service::ComposerService.new).execute(command, arguments)
|
21
|
+
(storage['composer'] ||= Fulmar::Infrastructure::Service::ComposerService.new(local_shell)).execute(command, arguments)
|
21
22
|
end
|
22
23
|
|
23
24
|
def local_shell
|
@@ -25,7 +26,7 @@ module Fulmar
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def remote_shell
|
28
|
-
storage['remote_shell'] ||= new_shell(configuration[:remote_path], configuration[:hostname])
|
29
|
+
storage['remote_shell'] ||= new_shell(configuration[:remote_path], (configuration[:user] ? configuration[:user]+'@' : '')+configuration[:hostname])
|
29
30
|
end
|
30
31
|
|
31
32
|
def file_sync
|
@@ -61,6 +62,18 @@ module Fulmar
|
|
61
62
|
@storage[configuration.environment] ||= {}
|
62
63
|
@storage[configuration.environment][configuration.target] ||= {}
|
63
64
|
end
|
65
|
+
|
66
|
+
def info(text)
|
67
|
+
puts (ENV['TERM'] == 'xterm-256color' ? text.blue : "* Info: #{text}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def warn(text)
|
71
|
+
puts (ENV['TERM'] == 'xterm-256color' ? text.magenta : "* Warning: #{text}")
|
72
|
+
end
|
73
|
+
|
74
|
+
def error(text)
|
75
|
+
puts (ENV['TERM'] == 'xterm-256color' ? text.light_red : "* Error: #{text}")
|
76
|
+
end
|
64
77
|
end
|
65
78
|
end
|
66
79
|
end
|
@@ -2,6 +2,7 @@ module Fulmar
|
|
2
2
|
module Domain
|
3
3
|
module Service
|
4
4
|
module Helper
|
5
|
+
# Provides access helper to the database service from within a task
|
5
6
|
module DatabaseHelper
|
6
7
|
def database
|
7
8
|
storage['database'] ||= Fulmar::Infrastructure::Service::Database::DatabaseService.new configuration
|
data/lib/fulmar/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fulmar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonas Siegl
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-03-
|
12
|
+
date: 2015-03-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -40,19 +40,19 @@ dependencies:
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '10'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: rugged
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
48
|
+
version: '0'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
55
|
+
version: '0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: mysql2
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,6 +107,20 @@ dependencies:
|
|
107
107
|
- - ">="
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: 0.1.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: colorize
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
type: :runtime
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
110
124
|
description: Fulmar is a task manager for deployments.
|
111
125
|
email:
|
112
126
|
- j.siegl@core4.de
|