fulmar 0.6.3
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 +7 -0
- data/.gitignore +16 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +23 -0
- data/bin/fulmar +3 -0
- data/fulmar.gemspec +28 -0
- data/lib/fulmar/domain/service/application_service.rb +47 -0
- data/lib/fulmar/domain/service/cache_service.rb +30 -0
- data/lib/fulmar/domain/service/config_rendering_service.rb +26 -0
- data/lib/fulmar/domain/service/configuration_service.rb +115 -0
- data/lib/fulmar/domain/service/file_sync_service.rb +23 -0
- data/lib/fulmar/domain/service/initialization_service.rb +7 -0
- data/lib/fulmar/domain/task/setup.rake +6 -0
- data/lib/fulmar/domain/task/versions.rake +54 -0
- data/lib/fulmar/infrastructure/service/cache/dummy_cache_service.rb +25 -0
- data/lib/fulmar/infrastructure/service/cache/neos_cache_service.rb +25 -0
- data/lib/fulmar/infrastructure/service/cache/symfony_cache_service.rb +28 -0
- data/lib/fulmar/infrastructure/service/composer_service.rb +16 -0
- data/lib/fulmar/infrastructure/service/database/database_service.rb +151 -0
- data/lib/fulmar/infrastructure/service/git_service.rb +67 -0
- data/lib/fulmar/infrastructure/service/shell_service.rb +10 -0
- data/lib/fulmar/infrastructure/service/transfer/base.rb +58 -0
- data/lib/fulmar/infrastructure/service/transfer/rsync.rb +58 -0
- data/lib/fulmar/infrastructure/service/transfer/rsync_with_versions.rb +194 -0
- data/lib/fulmar/infrastructure/service/tunnel_service.rb +49 -0
- data/lib/fulmar/service/bootstrap_service.rb +14 -0
- data/lib/fulmar/service/common_helper_service.rb +48 -0
- data/lib/fulmar/service/helper_service.rb +37 -0
- data/lib/fulmar/service/logger_service.rb +7 -0
- data/lib/fulmar/task_manager.rb +24 -0
- data/lib/fulmar/version.rb +4 -0
- data/test/rsync_with_versions.rb +20 -0
- metadata +177 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
require 'fulmar/infrastructure/service/tunnel_service'
|
3
|
+
|
4
|
+
module Fulmar
|
5
|
+
module Infrastructure
|
6
|
+
module Service
|
7
|
+
module Database
|
8
|
+
# Provides basic methods common to all database services
|
9
|
+
class DatabaseService
|
10
|
+
attr_accessor :client
|
11
|
+
attr_reader :shell
|
12
|
+
|
13
|
+
DEFAULT_CONFIG = {
|
14
|
+
maria: {
|
15
|
+
hostname: '127.0.0.1',
|
16
|
+
port: 3306,
|
17
|
+
user: 'root',
|
18
|
+
password: '',
|
19
|
+
encoding: 'utf8',
|
20
|
+
backup_path: '/tmp'
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
def initialize(config)
|
25
|
+
@config = config
|
26
|
+
@config.merge DEFAULT_CONFIG
|
27
|
+
@shell = Fulmar::Infrastructure::Service::ShellService.new(config[:local_path], config[:hostname])
|
28
|
+
@tunnel = nil
|
29
|
+
@client = nil
|
30
|
+
config_test
|
31
|
+
end
|
32
|
+
|
33
|
+
def connect
|
34
|
+
options = compile_options
|
35
|
+
|
36
|
+
if @config[:maria][:hostname] != 'localhost' && @config[:maria][:hostname] != '127.0.0.1'
|
37
|
+
tunnel.open
|
38
|
+
options[:port] = tunnel.local_port
|
39
|
+
end
|
40
|
+
|
41
|
+
# Wait max 3 seconds for the tunnel to establish
|
42
|
+
4.times do |i|
|
43
|
+
begin
|
44
|
+
@client = Mysql2::Client.new options
|
45
|
+
break
|
46
|
+
rescue Mysql2::Error => e
|
47
|
+
sleep 1 if i < 3
|
48
|
+
fail e.message if i == 3
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@connected = true
|
53
|
+
end
|
54
|
+
|
55
|
+
def disconnect
|
56
|
+
@connected = false
|
57
|
+
@client.close
|
58
|
+
@tunnel.close if @tunnel # using the variable directly avoids creating a tunnel instance when closing the database connection
|
59
|
+
end
|
60
|
+
|
61
|
+
def connected?
|
62
|
+
@connected
|
63
|
+
end
|
64
|
+
|
65
|
+
def tunnel
|
66
|
+
@tunnel ||= Fulmar::Infrastructure::Service::TunnelService.new(@config[:maria][:hostname], @config[:maria][:port])
|
67
|
+
end
|
68
|
+
|
69
|
+
# shortcut for DatabaseService.client.query
|
70
|
+
def query(*arguments)
|
71
|
+
@client.query(arguments)
|
72
|
+
end
|
73
|
+
|
74
|
+
def create(name)
|
75
|
+
state_before = connected?
|
76
|
+
connect unless connected?
|
77
|
+
@client.query "CREATE DATABASE IF NOT EXISTS `#{name}`"
|
78
|
+
disconnect unless state_before
|
79
|
+
end
|
80
|
+
|
81
|
+
def dump(filename = nil)
|
82
|
+
|
83
|
+
if filename
|
84
|
+
# Ensure path is absolute
|
85
|
+
path = filename[0, 1] == '/' ? filename : @config[:maria][:backup_path] + '/' + filename
|
86
|
+
else
|
87
|
+
path = @config[:maria][:backup_path] + '/' + backup_filename
|
88
|
+
end
|
89
|
+
|
90
|
+
diffable = @config[:maria][:diffable_dump] ? '--skip-comments --skip-extended-insert ' : ''
|
91
|
+
|
92
|
+
@shell.run "mysqldump -u #{@config[:maria][:user]} --password='#{@config[:maria][:password]}' " \
|
93
|
+
"#{@config[:maria][:database]} --single-transaction #{diffable}-r \"#{path}\""
|
94
|
+
|
95
|
+
path
|
96
|
+
end
|
97
|
+
|
98
|
+
def load_dump(dump_file, database = @config[:maria][:database])
|
99
|
+
@shell.run "mysql -u #{@config[:maria][:user]} --password='#{@config[:maria][:password]}' " \
|
100
|
+
"-D #{database} < #{dump_file}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def download_dump(filename = backup_filename)
|
104
|
+
local_path = filename[0, 1] == '/' ? filename : @config[:local_path] + '/' + filename
|
105
|
+
remote_path = dump
|
106
|
+
copy = system("scp -q #{@config[:maria][:hostname]}:#{remote_path} #{local_path}")
|
107
|
+
system("ssh #{@config[:maria][:hostname]} 'rm -f #{remote_path}'") # delete temporary file
|
108
|
+
if copy
|
109
|
+
local_path
|
110
|
+
else
|
111
|
+
''
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
# Test configuration
|
118
|
+
def config_test
|
119
|
+
fail 'Configuration option "database" missing.' unless @config[:maria][:database]
|
120
|
+
@shell.run "test -d '#{@config[:maria][:backup_path]}'"
|
121
|
+
end
|
122
|
+
|
123
|
+
# Builds the filename for a new database backup file
|
124
|
+
# NOTE: The file might already exist, for example if this is run at the same
|
125
|
+
# time from to different clients. I won't handle this as it is unlikely and
|
126
|
+
# would result in more I/O
|
127
|
+
def backup_filename
|
128
|
+
"#{@config[:maria][:database]}_#{Time.now.strftime('%Y-%m-%dT%H%M%S')}.sql"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Compiles a mysql config hash from valid options of the fulmar config
|
132
|
+
def compile_options
|
133
|
+
possible_options = [:host, :username, :password, :port, :encoding, :socket, :read_timeout, :write_timeout,
|
134
|
+
:connect_timeout, :reconnect, :local_infile, :secure_auth, :default_file, :default_group,
|
135
|
+
:init_command
|
136
|
+
]
|
137
|
+
options = {}
|
138
|
+
options[:host] = '127.0.0.1'
|
139
|
+
options[:username] = @config[:maria][:user]
|
140
|
+
possible_options.each do |option|
|
141
|
+
options[option] = @config[:maria][option] unless @config[:maria][option].nil?
|
142
|
+
end
|
143
|
+
|
144
|
+
options
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module Fulmar
|
4
|
+
module Infrastructure
|
5
|
+
module Service
|
6
|
+
# Provides access to to the local git repository
|
7
|
+
class GitService
|
8
|
+
attr_accessor :git
|
9
|
+
|
10
|
+
DEFAULT_CONFIG = {
|
11
|
+
local_path: '.',
|
12
|
+
git: {
|
13
|
+
branch: nil
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(config)
|
18
|
+
@config = config
|
19
|
+
@config.merge(DEFAULT_CONFIG)
|
20
|
+
|
21
|
+
unless @config[:git][:branch]
|
22
|
+
@config[:git][:branch] = case config.environment
|
23
|
+
when :preview
|
24
|
+
'preview'
|
25
|
+
when :live
|
26
|
+
'release'
|
27
|
+
else
|
28
|
+
'master'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@git = Git.open(@config[:local_path]) # :log => Logger.new(STDOUT)
|
33
|
+
end
|
34
|
+
|
35
|
+
def branches
|
36
|
+
@git.branches
|
37
|
+
end
|
38
|
+
|
39
|
+
def feature_branches
|
40
|
+
@git.branches.collect { |b| b.full }.select { |name| name.match(/^feature_/) }.sort
|
41
|
+
end
|
42
|
+
|
43
|
+
def preview_branches
|
44
|
+
@git.branches.collect { |b| b.full }.select { |name| name.match(/^preview_/) }.sort
|
45
|
+
end
|
46
|
+
|
47
|
+
def checkout(branch_name = derive_branch_name)
|
48
|
+
branches = @git.branches.local.find(branch_name)
|
49
|
+
if branches.any?
|
50
|
+
@git.checkout(branches.first)
|
51
|
+
else
|
52
|
+
branches = @git.branches.remote.find(branch_name)
|
53
|
+
fail "Cannot find a valid branch, last search was for #{branch_name}" unless branches.any?
|
54
|
+
@git.branch(branches.first)
|
55
|
+
@git.checkout(branches.first)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# The current preview branch is the alphabetically last preview branch
|
60
|
+
def derive_branch_name
|
61
|
+
@config[:git][:branch] == 'preview' ? preview_branches.last : @config[:git][:branch]
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
|
2
|
+
require 'fulmar/shell'
|
3
|
+
|
4
|
+
module Fulmar
|
5
|
+
module Infrastructure
|
6
|
+
module Service
|
7
|
+
module Transfer
|
8
|
+
# Abstract class for alle transfers, provides common methods
|
9
|
+
class Base
|
10
|
+
DEFAULT_CONFIG = {
|
11
|
+
debug: false,
|
12
|
+
user: '',
|
13
|
+
password: '',
|
14
|
+
remote_path: nil,
|
15
|
+
local_path: '.',
|
16
|
+
type: :rsync_with_versions
|
17
|
+
}
|
18
|
+
|
19
|
+
attr_accessor :config
|
20
|
+
|
21
|
+
def initialize(config)
|
22
|
+
@config = DEFAULT_CONFIG.merge(config)
|
23
|
+
|
24
|
+
# Remove trailing slashes
|
25
|
+
@config[:local_path] = @config[:local_path].chomp('/') if @config[:local_path]
|
26
|
+
@config[:remote_path] = @config[:remote_path].chomp('/') if @config[:remote_path]
|
27
|
+
|
28
|
+
@prepared = false
|
29
|
+
end
|
30
|
+
|
31
|
+
# Test the supplied config for required parameters
|
32
|
+
def test_config
|
33
|
+
required = [:host, :remote_path, :local_path]
|
34
|
+
required.each { |key| fail "Configuration is missing required setting '#{key}'." if @config.blank? }
|
35
|
+
fail ':remote_path must be absolute' if @config[:remote_path][0, 1] != '/'
|
36
|
+
end
|
37
|
+
|
38
|
+
def prepare
|
39
|
+
@local_shell = Fulmar::Infrastructure::Service::ShellService.new @config[:local_path]
|
40
|
+
@local_shell.debug = @config[:debug]
|
41
|
+
@prepared = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def publish
|
45
|
+
# Placeholder for consistent api, currently only implemented in rsync_with_versions
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def ssh_user_and_host
|
52
|
+
@config[:user].blank? ? @config[:hostname] : @config[:user] + '@' + @config[:hostname]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
|
2
|
+
require 'fulmar/infrastructure/service/transfer/base'
|
3
|
+
|
4
|
+
module Fulmar
|
5
|
+
module Infrastructure
|
6
|
+
module Service
|
7
|
+
module Transfer
|
8
|
+
# Implements the rsync transfer
|
9
|
+
class Rsync < Base
|
10
|
+
DEFAULT_CONFIG = {
|
11
|
+
rsync: {
|
12
|
+
exclude: nil,
|
13
|
+
exclude_file: nil,
|
14
|
+
chown: nil,
|
15
|
+
chmod: nil,
|
16
|
+
delete: true,
|
17
|
+
direction: 'up'
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
def initialize(config)
|
22
|
+
@config = DEFAULT_CONFIG.deep_merge(config)
|
23
|
+
|
24
|
+
if @config[:rsync][:exclude_file].blank? && File.exist?(@config[:local_path] + '/.rsyncignore')
|
25
|
+
@config[:rsync][:exclude_file] = @config[:local_path] + '/.rsyncignore'
|
26
|
+
end
|
27
|
+
|
28
|
+
super(@config)
|
29
|
+
end
|
30
|
+
|
31
|
+
def transfer
|
32
|
+
prepare unless @prepared
|
33
|
+
@local_shell.run rsync_command
|
34
|
+
end
|
35
|
+
|
36
|
+
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
|
+
if @config[:rsync][:direction] == 'up'
|
45
|
+
from = @config[:local_path]
|
46
|
+
to = ssh_user_and_host + ':' + @config[:remote_path]
|
47
|
+
else
|
48
|
+
from = ssh_user_and_host + ':' + @config[:remote_path]
|
49
|
+
to = @config[:local_path]
|
50
|
+
end
|
51
|
+
|
52
|
+
"rsync #{options.join(' ')} '#{from}/' '#{to}'"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
|
2
|
+
require 'fulmar/infrastructure/service/transfer/base'
|
3
|
+
require 'time'
|
4
|
+
require 'ruby_wings'
|
5
|
+
|
6
|
+
module Fulmar
|
7
|
+
module Infrastructure
|
8
|
+
module Service
|
9
|
+
module Transfer
|
10
|
+
# Provides syncing with versioning on the server
|
11
|
+
#
|
12
|
+
# Every deployment results in a new directory in the releases directory with the deployment time as
|
13
|
+
# the folder name. A symlink 'current' points to this new directory after publish() is called.
|
14
|
+
class RsyncWithVersions < Base
|
15
|
+
TIME_FOLDER = '%Y-%m-%d_%H%M%S'
|
16
|
+
TIME_READABLE = '%Y-%m-%d %H:%M:%S'
|
17
|
+
|
18
|
+
DEFAULT_CONFIG = {
|
19
|
+
temp_dir: 'temp',
|
20
|
+
releases_dir: 'releases',
|
21
|
+
shared_dir: 'shared',
|
22
|
+
rsync: {
|
23
|
+
exclude: nil,
|
24
|
+
exclude_file: nil,
|
25
|
+
chown: nil,
|
26
|
+
chmod: nil,
|
27
|
+
delete: true
|
28
|
+
},
|
29
|
+
symlinks: {},
|
30
|
+
limit_releases: 10,
|
31
|
+
shared: []
|
32
|
+
}
|
33
|
+
|
34
|
+
def initialize(config)
|
35
|
+
@config = DEFAULT_CONFIG.deep_merge(config)
|
36
|
+
super(@config)
|
37
|
+
|
38
|
+
if @config[:rsync][:exclude_file].blank? && File.exist?(@config[:local_path] + '/.rsyncignore')
|
39
|
+
@config[:rsync][:exclude_file] = @config[:local_path] + '/.rsyncignore'
|
40
|
+
end
|
41
|
+
|
42
|
+
@release_time = Time.now
|
43
|
+
end
|
44
|
+
|
45
|
+
# Ensures all needed services are set up
|
46
|
+
def prepare
|
47
|
+
super
|
48
|
+
@remote_shell = Fulmar::Infrastructure::Service::ShellService.new @config[:remote_path], ssh_user_and_host
|
49
|
+
@remote_shell.debug = @config[:debug]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Copy the files via rsync to the release_path on the remote machine
|
53
|
+
# @return [true, false] success
|
54
|
+
def transfer
|
55
|
+
prepare unless @prepared
|
56
|
+
|
57
|
+
create_paths && @local_shell.run(rsync_command) && copy_temp_to_release && add_shared
|
58
|
+
end
|
59
|
+
|
60
|
+
# Publishes the current release (i.e. sets the 'current' symlink)
|
61
|
+
# @return [true, false] success
|
62
|
+
def publish
|
63
|
+
prepare unless @prepared
|
64
|
+
create_symlink
|
65
|
+
end
|
66
|
+
|
67
|
+
# Gets the currently generated absolute release path
|
68
|
+
# @return [String] the release directory
|
69
|
+
def release_path
|
70
|
+
@config[:remote_path] + '/' + release_dir
|
71
|
+
end
|
72
|
+
|
73
|
+
# Gets the currently generated release directory
|
74
|
+
# @return [String] the release directory
|
75
|
+
def release_dir
|
76
|
+
@config[:releases_dir] + '/' + @release_time.strftime('%Y-%m-%d_%H%M%S')
|
77
|
+
end
|
78
|
+
|
79
|
+
# Lists the existing releases on the remote machine
|
80
|
+
# @param plain [boolean] if the list should be plain directory names or more readable time strings with a star for the current release
|
81
|
+
# @return [Array] list of dirs or dates/times
|
82
|
+
def list_releases(plain = true)
|
83
|
+
prepare unless @prepared
|
84
|
+
@remote_shell.run "ls -1 '#{@config[:releases_dir]}'"
|
85
|
+
list = @remote_shell.last_output.select { |dir| dir.match(/^\d{4}-\d{2}-\d{2}_\d{6}/) }
|
86
|
+
return list if plain
|
87
|
+
|
88
|
+
current = current_release
|
89
|
+
list.collect do |item|
|
90
|
+
Time.strptime(item, TIME_FOLDER).strftime(TIME_READABLE) + (item == current ? ' *' : '')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Cleans up old releases limited by :limit_releases
|
95
|
+
# @return [true, false] success
|
96
|
+
def cleanup
|
97
|
+
limit = @config[:limit_releases].to_i
|
98
|
+
return true unless limit > 0
|
99
|
+
releases = list_releases.sort
|
100
|
+
return true if releases.length <= limit
|
101
|
+
obsolete_dirs = releases[0, releases.length - limit].collect { |dir| "'#{@config[:releases_dir]}/#{dir}'" }
|
102
|
+
@remote_shell.run "rm -fr #{obsolete_dirs.join(' ')}"
|
103
|
+
end
|
104
|
+
|
105
|
+
# Return the release at which the "current" symlinks points at
|
106
|
+
def current_release
|
107
|
+
prepare unless @prepared
|
108
|
+
@remote_shell.run 'readlink -f current'
|
109
|
+
@remote_shell.last_output.first.split('/').last
|
110
|
+
end
|
111
|
+
|
112
|
+
# Reverts to a given release
|
113
|
+
#
|
114
|
+
# @params release [String] the release folder or time string (which is found in the output list)
|
115
|
+
# @return [true, false] success
|
116
|
+
def revert(release)
|
117
|
+
prepare unless @prepared
|
118
|
+
|
119
|
+
# Convenience: Allow more readable version string from output
|
120
|
+
if release.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)
|
121
|
+
release = Time.strptime(item, TIME_READABLE).strftime(TIME_FOLDER)
|
122
|
+
end
|
123
|
+
|
124
|
+
create_symlink release
|
125
|
+
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
|
129
|
+
# Creates all necessary paths on the remote machine
|
130
|
+
# @return [true, false] success
|
131
|
+
def create_paths
|
132
|
+
paths = [@config[:releases_dir], @config[:temp_dir], @config[:shared_dir]]
|
133
|
+
@remote_shell.run paths.collect { |path| "mkdir -p '#{@config[:remote_path]}/#{path}'" }
|
134
|
+
end
|
135
|
+
|
136
|
+
# Builds the rsync command
|
137
|
+
# @return [String] the command
|
138
|
+
def rsync_command
|
139
|
+
options = ['-rl']
|
140
|
+
options << "--exclude='#{@config[:rsync][:exclude]}'" if @config[:rsync][:exclude]
|
141
|
+
options << "--exclude-from='#{@config[:rsync][:exclude_file]}'" if @config[:rsync][:exclude_file]
|
142
|
+
options << "--chown='#{@config[:rsync][:chown]}'" if @config[:rsync][:chown]
|
143
|
+
options << "--chmod='#{@config[:rsync][:chmod]}'" if @config[:rsync][:chmod]
|
144
|
+
options << '--delete' if @config[:rsync][:delete]
|
145
|
+
|
146
|
+
"rsync #{options.join(' ')} '#{@config[:local_path]}/' '#{ssh_user_and_host}:#{@config[:remote_path]}/#{@config[:temp_dir]}'"
|
147
|
+
end
|
148
|
+
|
149
|
+
# Copies the data from the sync temp to the actual release directory
|
150
|
+
# @return [true, false] success
|
151
|
+
def copy_temp_to_release
|
152
|
+
@remote_shell.run "cp -r #{@config[:temp_dir]} #{release_dir}"
|
153
|
+
end
|
154
|
+
|
155
|
+
# Set the symlink to the given release or the return value of release_dir() otherwise
|
156
|
+
#
|
157
|
+
# @params release [String] the release folder
|
158
|
+
# @return [true, false] success
|
159
|
+
def create_symlink(release = nil)
|
160
|
+
@remote_shell.run [
|
161
|
+
"rm -f #{@config[:remote_path]}/current",
|
162
|
+
"ln -s #{release ? @config[:releases_dir] + '/' + release : release_dir} current"
|
163
|
+
]
|
164
|
+
end
|
165
|
+
|
166
|
+
# Creates symlinks into the shared folder
|
167
|
+
#
|
168
|
+
# An entry in :shared might be "data/resources/images", so we first need to create
|
169
|
+
# the directory "data/resources" in the release dir and afterwards create the link
|
170
|
+
# @return [true, false] success
|
171
|
+
def add_shared
|
172
|
+
commands = [] # Collect all remote commands first, then execute them in one step to avoid reconnecting very often
|
173
|
+
@config[:shared].each do |dir|
|
174
|
+
if remote_dir_exists?("#{release_dir}/#{dir}") && !remote_dir_exists?("#{@config[:shared_dir]}/#{dir}")
|
175
|
+
commands << "mkdir -p \"#{@config[:shared_dir]}/#{File.dirname(dir)}\""
|
176
|
+
commands << "cp -r \"#{release_dir}/#{dir}\" \"#{@config[:shared_dir]}/#{File.dirname(dir)}\""
|
177
|
+
end
|
178
|
+
|
179
|
+
commands << "rm -fr \"#{release_dir}/#{dir}\""
|
180
|
+
commands << "mkdir -p \"#{release_dir}/#{File.dirname(dir)}\""
|
181
|
+
commands << "ln -s \"#{@config[:remote_path]}/#{@config[:shared_dir]}/#{dir}\" \"#{release_dir}/#{dir}\""
|
182
|
+
end
|
183
|
+
|
184
|
+
@remote_shell.run commands if commands.length > 0
|
185
|
+
end
|
186
|
+
|
187
|
+
def remote_dir_exists?(dir)
|
188
|
+
@remote_shell.run "test -d \"#{dir}\""
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Fulmar
|
4
|
+
module Infrastructure
|
5
|
+
module Service
|
6
|
+
class TunnelService
|
7
|
+
attr_reader :host, :remote_port, :local_port
|
8
|
+
|
9
|
+
def initialize(host, port)
|
10
|
+
@host = host
|
11
|
+
@remote_port = port
|
12
|
+
@local_port = 0
|
13
|
+
@tunnel_pid = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def open
|
17
|
+
@local_port = free_port
|
18
|
+
@tunnel_pid = Process.spawn "ssh #{@host} -L #{@local_port}:localhost:#{@remote_port} -N"
|
19
|
+
sleep 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
Process.kill 'TERM', @tunnel_pid if @tunnel_pid > 0
|
24
|
+
@local_port = 0
|
25
|
+
@tunnel_pid = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def open?
|
29
|
+
@tunnel_pid > 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def free_port
|
33
|
+
port = 60000
|
34
|
+
begin
|
35
|
+
1000.times do
|
36
|
+
socket = TCPSocket.new('localhost', port)
|
37
|
+
socket.close
|
38
|
+
port += 1
|
39
|
+
end
|
40
|
+
rescue Errno::ECONNREFUSED
|
41
|
+
return port
|
42
|
+
end
|
43
|
+
fail 'Cannot find an open local port'
|
44
|
+
0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Fulmar
|
2
|
+
module Service
|
3
|
+
# Initializes the rake service and starts it
|
4
|
+
class BootstrapService
|
5
|
+
def initialize
|
6
|
+
$logger = Fulmar::Service::LoggerService.new(STDOUT)
|
7
|
+
end
|
8
|
+
|
9
|
+
def fly
|
10
|
+
Fulmar::Domain::Service::ApplicationService.new.run
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'fulmar/domain/service/configuration_service'
|
2
|
+
|
3
|
+
module Fulmar
|
4
|
+
module Domain
|
5
|
+
module Service
|
6
|
+
# Provides the helper methods used in the tasks
|
7
|
+
module CommonHelperService
|
8
|
+
attr_accessor :environment
|
9
|
+
|
10
|
+
def full_configuration
|
11
|
+
configuration.configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def configuration
|
15
|
+
(@_config_service ||= Fulmar::Domain::Service::ConfigurationService.instance)
|
16
|
+
end
|
17
|
+
|
18
|
+
def composer(command, arguments = [])
|
19
|
+
(@_composer ||= Fulmar::Infrastructure::Service::ComposerService.new).execute(command, arguments)
|
20
|
+
end
|
21
|
+
|
22
|
+
def local_shell
|
23
|
+
@_local_shell ||= Fulmar::Infrastructure::Service::ShellService.new configuration[:local_path]
|
24
|
+
end
|
25
|
+
|
26
|
+
def remote_shell
|
27
|
+
@_remote_shell ||= Fulmar::Infrastructure::Service::ShellService.new(configuration[:remote_path], configuration[:hostname])
|
28
|
+
end
|
29
|
+
|
30
|
+
def file_sync
|
31
|
+
@_file_sync ||= Fulmar::FileSync.create_transfer configuration
|
32
|
+
end
|
33
|
+
|
34
|
+
def database
|
35
|
+
@_database ||= Fulmar::Infrastructure::Service::Database::DatabaseService.new configuration
|
36
|
+
end
|
37
|
+
|
38
|
+
def render_templates
|
39
|
+
(Fulmar::Domain::Service::ConfigRenderingService.new configuration).render
|
40
|
+
end
|
41
|
+
|
42
|
+
def git
|
43
|
+
@_git ||= Fulmar::Infrastructure::Service::GitService.new configuration
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Fulmar
|
2
|
+
module Service
|
3
|
+
# Provides internal helper methods
|
4
|
+
class HelperService
|
5
|
+
class << self
|
6
|
+
##
|
7
|
+
# Reverse file lookup in path
|
8
|
+
# @param path [String]
|
9
|
+
def reverse_file_lookup(path, filename)
|
10
|
+
paths = get_parent_directory_paths(path)
|
11
|
+
|
12
|
+
paths.each do |directory|
|
13
|
+
file_path = directory + '/' + filename
|
14
|
+
return file_path if File.exist? file_path
|
15
|
+
end
|
16
|
+
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
##
|
23
|
+
# Get paths of each parent directory
|
24
|
+
# @param path [String]
|
25
|
+
# @param paths [Array]
|
26
|
+
# @return [Array] A list of paths
|
27
|
+
def get_parent_directory_paths(path, paths = [])
|
28
|
+
paths << path
|
29
|
+
|
30
|
+
parent_dir_path = File.expand_path('..', path)
|
31
|
+
|
32
|
+
parent_dir_path == '/' ? paths : get_parent_directory_paths(parent_dir_path, paths)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|