fulmar 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|