fulmar 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CODE_OF_CONDUCT.md +13 -0
  4. data/Gemfile +13 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +23 -0
  7. data/bin/fulmar +3 -0
  8. data/fulmar.gemspec +28 -0
  9. data/lib/fulmar/domain/service/application_service.rb +47 -0
  10. data/lib/fulmar/domain/service/cache_service.rb +30 -0
  11. data/lib/fulmar/domain/service/config_rendering_service.rb +26 -0
  12. data/lib/fulmar/domain/service/configuration_service.rb +115 -0
  13. data/lib/fulmar/domain/service/file_sync_service.rb +23 -0
  14. data/lib/fulmar/domain/service/initialization_service.rb +7 -0
  15. data/lib/fulmar/domain/task/setup.rake +6 -0
  16. data/lib/fulmar/domain/task/versions.rake +54 -0
  17. data/lib/fulmar/infrastructure/service/cache/dummy_cache_service.rb +25 -0
  18. data/lib/fulmar/infrastructure/service/cache/neos_cache_service.rb +25 -0
  19. data/lib/fulmar/infrastructure/service/cache/symfony_cache_service.rb +28 -0
  20. data/lib/fulmar/infrastructure/service/composer_service.rb +16 -0
  21. data/lib/fulmar/infrastructure/service/database/database_service.rb +151 -0
  22. data/lib/fulmar/infrastructure/service/git_service.rb +67 -0
  23. data/lib/fulmar/infrastructure/service/shell_service.rb +10 -0
  24. data/lib/fulmar/infrastructure/service/transfer/base.rb +58 -0
  25. data/lib/fulmar/infrastructure/service/transfer/rsync.rb +58 -0
  26. data/lib/fulmar/infrastructure/service/transfer/rsync_with_versions.rb +194 -0
  27. data/lib/fulmar/infrastructure/service/tunnel_service.rb +49 -0
  28. data/lib/fulmar/service/bootstrap_service.rb +14 -0
  29. data/lib/fulmar/service/common_helper_service.rb +48 -0
  30. data/lib/fulmar/service/helper_service.rb +37 -0
  31. data/lib/fulmar/service/logger_service.rb +7 -0
  32. data/lib/fulmar/task_manager.rb +24 -0
  33. data/lib/fulmar/version.rb +4 -0
  34. data/test/rsync_with_versions.rb +20 -0
  35. 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,10 @@
1
+ require 'fulmar/shell'
2
+
3
+ module Fulmar
4
+ module Infrastructure
5
+ module Service
6
+ # This is only a wrapper to fit the shell into the naming scheme
7
+ class ShellService < Fulmar::Shell; end
8
+ end
9
+ end
10
+ 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