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.
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