fulmar 1.5.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 94c412b27a13af7d2c5954307361b2afedb2a065
4
- data.tar.gz: b1630fd62123dfaeab9167648a07e3af90f8f840
3
+ metadata.gz: 86d6596b35b141aa77a6e85f919123ba13f2524e
4
+ data.tar.gz: ed72cbe277ea44d85c0487427d7a6c5abf24be20
5
5
  SHA512:
6
- metadata.gz: 30f6993150e391316a3a706b45237f271adc249fee20d94fc5fbe44e43edd6f473b9c813dcbd4d9fb651467fe8a49231fd3836e19100c515d208cad89b6e72b7
7
- data.tar.gz: 7a94a81d220d6c3536d98291bfa3cdd15af8c0aff7ea3a7db79c612509ce5c6cbfc60717d8dd7b387f46c21ce6ee5648b8ac5de7100ee2496752823a44508a43
6
+ metadata.gz: 6226c3b6326906ef3167475aa68b5e96603b681d6a8003d602489b57c711555b127fc61b00174243230308f785f8b4b0438f0942ade12aa98bf7d24c504d23e5
7
+ data.tar.gz: 6023777a09ddd21d853aa61a2fed8724376e48c67448a71d8a3f37c6fc45ac058e7bfc2090620049d39fba34a9cb3f5f4d88c8b59feb08e71650e1367c0d9d0f
data/README.md CHANGED
@@ -1,9 +1,28 @@
1
1
  # Fulmar
2
2
 
3
- Fulmar is a task manager for deployments.
3
+ Fulmar is a task manager for deployments. It is build on top of rake to use its powerful
4
+ task system and adds host and environment configuration. It provides methods to easily
5
+ access the current deployment configuration and simplifies especially file transfers and
6
+ remote shell execution.
4
7
 
5
- This project is still under heavy development for the next few weeks. You might want to check back in May 2015. We hope
6
- to get this somehow stable until then.
8
+ A deployment can create a new version folder on the remote system in which you can warm up
9
+ the cache and the publish via a symlink. This avoids an inconsistent state on the production
10
+ machine and allows a quick revert to the old version, as long as other dependencies are
11
+ compatible (i.e. database).
12
+
13
+ It has (yet limited) support for MySQL / MariaDB and git. Remote databases can be accessed
14
+ through an ssh tunnel.
15
+
16
+ ## Prerequisites
17
+
18
+ Fulmar currently requires the [mysql2](https://github.com/brianmario/mysql2) gem which
19
+ requires the mysql header files. So on a linux system, you want to install
20
+ libmariadbclient-dev/libmysqlclient-dev or similar.
21
+
22
+ You also need cmake to build the dependencies.
23
+
24
+ - OSX: brew install mariadb cmake
25
+ - Ubuntu: apt-get install libmariadbclient-dev build-essential cmake
7
26
 
8
27
  ## Installation
9
28
 
@@ -23,4 +42,288 @@ Or install it yourself as:
23
42
 
24
43
  ## Usage
25
44
 
26
- TODO: Write usage instructions here
45
+ Fulmar works like Rake. In fact it is just a collection of services on top of Rake.
46
+
47
+ ## A simple start
48
+
49
+ Start by creating a file named "Fulmarfile" in your project root. Then add a Folder "Fulmar" with a project.config.yml in it. The name of the config file doesn't matter, it just needs to end with .config.yml. All \*.config.yml in the Fulmar directory are merged so you can split you configuration into
50
+ several files.
51
+
52
+ $ touch Fulmarfile
53
+ $ mkdir Fulmar
54
+ $ touch Fulmar/project.config.yml
55
+
56
+ You can now now test your configuration.
57
+
58
+ $ fulmar test:config
59
+
60
+ Everything should be fine. With `fulmar -T` you can get a quick overview on the available tasks. That is probably just one, the fulmar console. We'll come back to this later.
61
+
62
+ Now let's start with a first configuration. You might want to have a server ready which you can access via ssh and public key authentication. Add this to you configuration file (Fulmar/project.config.yml):
63
+
64
+ ```yaml
65
+ environments:
66
+ all:
67
+ local_path: .
68
+ my_server:
69
+ files:
70
+ hostname: my-ssh-server
71
+ remote_path: /tmp
72
+ type: rsync
73
+ rsync:
74
+ delete: false
75
+ ```
76
+
77
+ Now you can run a second test: `fulmar test:hosts`. This will test the ssh connection to your remote machine. You can add a username to the ssh configuration in the files section (below the hostname). However, if you need a finer grained host configuration, you should use your [ssh config file](http://www.cyberciti.biz/faq/create-ssh-config-file-on-linux-unix/).
78
+
79
+ Most fulmar tasks need to run within a configured environment. Within the configuration, environments are split into targets. So actually, a task works within a target. In our example, we set up an environment named 'my_server' with a target named 'files'. You might want to use one environment for your development server, one for a preview server and one for the production machine. Within each environment you might have different hosts for the file and the database, so each environment can be split up into multiple targets (e.g. 'files' and 'database').
80
+
81
+ There is one special environment 'all' which contains settings you want to use in all targets of all environements. These globally configured settings are overwritten with the specific ones from a target if they exist. Useful examples for this are 'local_path' and 'debug'.
82
+
83
+ ### Tasks
84
+
85
+ As you can see (`fulmar -T`), you cannot do anything more useful now. So it is time to write a first task. If you are familiar with rake, this should not be a problem.
86
+
87
+ ```ruby
88
+ namespace :deploy do
89
+ task :test do
90
+ configuration.environment = :my_server
91
+ configuration.target = :files
92
+ file_sync.transfer
93
+ end
94
+ end
95
+ ```
96
+
97
+ This creates a task 'test' within the namespace 'deploy', which means you can access the task by running `fulmar deploy:test`. The task will load the configuration for 'myserver/files' and start a file sync (via rsync) to your remote host.
98
+
99
+ If you log in to your remote machine, you should now see the Fulmar directory and the Fulmarfile in /tmp.
100
+ ```bash
101
+ ssh my-ssh-server "ls -l /tmp | grep Fulmar"
102
+ ```
103
+
104
+ This task can be even shorter. Fulmar creates some helper tasks automatically from your configuration. Just like in rake, you can list them via `fulmar -T -A`:
105
+ ```
106
+ fulmar console # Open an interactive terminal within fulmar
107
+ fulmar deploy:test # Deploy to test system
108
+ fulmar environment:my_server #
109
+ fulmar environment:my_server:files #
110
+ fulmar test:config #
111
+ fulmar test:hosts #
112
+ ```
113
+
114
+ Fulmar creates task for every environment/target with this scheme. So you can just set a dependency to these tasks to load the configuration. Your task now only needs one line:
115
+
116
+ ```ruby
117
+ namespace :deploy do
118
+ task :test => 'environment:my_server:files' do
119
+ file_sync.transfer
120
+ end
121
+ end
122
+ ```
123
+
124
+ Task can have multiple dependencies, just put them into an array:
125
+
126
+ ```ruby
127
+ task :test => ['environment:my_server:files', 'prepare_files', 'do_some_more'] do
128
+ end
129
+ ```
130
+
131
+ If you call one task from another, your configuration will be given to the subtask. If you change the target in the subtask it will be reverted when returning to your main task unless you did not set any configuration in the main task. This means the following:
132
+ - You can use the dependency to a 'environment:' task to set the target initially (as it will be the first subtask to run)
133
+ - You can call a subtask and and you don't have to worry it might change your target.
134
+
135
+ *The configuration object is a singleton. So if you change the actual configuration of a target, it will affect other tasks*
136
+
137
+ ## Features
138
+
139
+ So now you can deploy the Fulmarfiles itself which is nice. So what else can you do within a task?
140
+
141
+ ### Shell
142
+
143
+ Very often you need to call other programs or script from a shell to compile your assets or clear the cache. Within a task, you can access the local and remote shell:
144
+
145
+ ```ruby
146
+ task :tutorial => 'environment:my_server:files' do
147
+ local_shell.run 'echo Wheeeeee'
148
+ remote_shell.run 'ls -l | grep Fulmar'
149
+ puts remote_shell.last_output
150
+ end
151
+ ```
152
+
153
+ You can run multiple commands. If one fails, everything stops immediately. This is intended to stop the deployment of broken files.
154
+
155
+ ```ruby
156
+ task :tutorial => 'environment:my_server:files' do
157
+ local_shell.run [
158
+ 'echo Wheeeee',
159
+ 'false',
160
+ 'echo Does not get executed'
161
+ ]
162
+ info "This does not get executed, either"
163
+ end
164
+ ```
165
+
166
+ ### Output
167
+
168
+ If you want your tasks to output some status messages, you can call `info`, `warning` or `error`.
169
+
170
+ ```ruby
171
+ task :tutorial => 'environment:my_server:files' do
172
+ info "Everything is fine."
173
+ warning "Or probably not."
174
+ error "Yepp, it's borken"
175
+ end
176
+ ```
177
+
178
+ ### File synchronisation
179
+
180
+ At the moment, there are two ways to synchronize your files to a remote host. The basic rsync and "rsync with versions". You already know how to set up the simple file sync. You can add a few options, if you like:
181
+
182
+ ```yaml
183
+ environments:
184
+ all:
185
+ local_path: .
186
+ my_server:
187
+ files:
188
+ hostname: my-ssh-server
189
+ remote_path: /tmp
190
+ type: rsync
191
+ rsync:
192
+ # Default values are:
193
+ delete: true
194
+ exclude: nil,
195
+ exclude_file: .rsyncignore,
196
+ chown: nil,
197
+ chmod: nil,
198
+ direction: up
199
+ ```
200
+
201
+ These are:
202
+ - **delete**: true/false, Should additional files be deleted from the remote host? The default is "true", as you don't want to have legacy files opening security holes on your server.
203
+ - **exclude**: regexp, which works with "--exclude" from rsync. Usually, you should prefer the next option
204
+ - **exclude_file**: Filename of the exclude file relative to "local_path". If you have a file called .rsyncignore in "local_path" this will be used automatically.
205
+ - **chown**: chown files on remote machine to this user
206
+ - **chmod**: change mode of files on remote machines
207
+ - **direction**: up/down, Should the files be uploaded or downloaded? The latter is useful if you want to download images created by a cms on the remote machine.
208
+
209
+ ### rsync with versions
210
+
211
+ When deploying to a production machine, you want your minimize the downtime for the website. Fulmar can help by syncing different versions which are symlinked after they are set up.
212
+
213
+ ```yaml
214
+ environments:
215
+ all:
216
+ local_path: .
217
+ my_server:
218
+ files:
219
+ hostname: my-ssh-server
220
+ remote_path: /tmp
221
+ type: rsync_with_versions
222
+ shared:
223
+ - data
224
+ limit_releases: 10
225
+ #temp_dir: temp
226
+ #releases_dir: releases
227
+ #shared_dir: shared
228
+ ```
229
+
230
+ You do no necessarily need to set "limit_releases" or "shared". "temp_dir", "releases_dir" and "shared_dir" are relative to the remote_path and usually do not need to be changed.
231
+
232
+ So what does this all mean?
233
+
234
+ If you still have your deploy:test task you can now deploy a first version of your files. Probably create the "data" dir first and put a file in to see what happens. Fulmar will sync you project into a temporary directory ("temp_dir") and then copy it the the releases directory with the current timestamp as a directory name. So in this case, a directory like "/tmp/releases/2015-04-27_123456" will be created. Fulmar will then look of shared/data exists and if not, copy it from the releases directory. Shared directories in releases will be deleted an symlinked to shared. So you want to list all directories which are filled by you web application (images uploaded from the cms, etc).
235
+
236
+ Then you can build up the cache or whatever needs to be done within that directory before putting it live. This last step is not yet covered in our little deployment task. So:
237
+
238
+ ```ruby
239
+ namespace :deploy do
240
+ task :test => 'environment:my_server:files' do
241
+ releases_dir = file_sync.transfer
242
+ puts release_dir # Something like 'releases/2015-04-27_123456'
243
+ remote_shell [
244
+ "cd #{release_dir}",
245
+ 'php app/console cache:clear --env=dev'
246
+ ]
247
+ file_sync.publish # create
248
+ end
249
+ end
250
+ ```
251
+
252
+ The transfer() method of the file_sync service returns the created release dir and you can do what needs to be done before calling file_sync.publish to create the symlink (current => releases/2015-04-27_123456).
253
+
254
+ As you probably figured out, your webserver uses the "current" symlink to get the base directory of your web application. Here, you would point it to /tmp/current/public or something like that.
255
+
256
+ ### Database access
257
+
258
+ Within a task you can access and update databases (mariadb/mysql at the time of writing). Remote databases do not need to be accessible directly since fulmar uses an ssh tunnel to connect to the host first. So the database host is often just "localhost" (the default value). You can specify a different host if you database server is on another host, which is the case on most simple web spaces.
259
+
260
+ The field "maria:database" is required for type "maria". The other fields are optional, though you probably need the fields "user" and "password". Below you can see the default values for the optional fields.
261
+
262
+ ```yaml
263
+ environments:
264
+ staging:
265
+ database:
266
+ host: project-staging
267
+ type: maria
268
+ maria:
269
+ database: db_name
270
+ user: root
271
+ password:
272
+ port: 3306
273
+ host: localhost
274
+ encoding: utf-8
275
+ ```
276
+
277
+ ```ruby
278
+ require 'pp'
279
+
280
+ task :database => 'environment:staging:database' do
281
+ database.create 'test_db'
282
+ database.clear # deletes all tables within the database
283
+ remote_file_name = database.dump # dumps the database to the returned file
284
+ database.load_dump(remote_file_name) # loads an sql dump
285
+ local_filename = database.download_dump # downloads an sql dump to your machine
286
+ end
287
+ ```
288
+
289
+ You can query the database like this:
290
+
291
+ ```ruby
292
+ results = database.query 'SELECT name, email FROM users'
293
+ results.each do |row|
294
+ puts "#{row['name']} <#{row['email']}>"
295
+ end
296
+ ```
297
+
298
+ You can use all features of the mysql2 gem via `database.client`.
299
+
300
+ If you configured more than one database on different environments, fulmar will
301
+ create task to sync these databases via mysql_dump. This allows you to update a
302
+ staging or preview database with the data from the production system.
303
+
304
+ ```
305
+ fulmar database:update:preview:from_live
306
+ ```
307
+
308
+ The task to copy data *to* the live database is hidden (it has no description).
309
+
310
+ ### Configuration templates
311
+
312
+ Sometimes you need different versions of files on your different environments. An exmaple might be the .htaccess file. You can use the [Ruby ERB templating engine](http://www.stuartellis.eu/articles/erb/) to generate the different versions. The configuration of your environment are available via the `@config` variable.
313
+
314
+ Templates need to be named with the schema `<original_filename>.erb`. All files you want to render need to be listed in the configuration:
315
+
316
+ ```yaml
317
+ environments:
318
+ staging:
319
+ files:
320
+ config_templates:
321
+ - .htaccess.erb
322
+ application_environment: Production/Live
323
+ ```
324
+
325
+ Then add the variable to your template:
326
+
327
+ ```
328
+ SetEnv APPLICATION_ENVIRONMENT "<%= @config['application_environment'] %>"
329
+ ```
data/bin/fulmar CHANGED
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- if ARGV[0] == 'init'
4
- system('touch Fulmarfile')
5
- end
3
+ system('touch Fulmarfile') if ARGV[0] == 'init'
6
4
 
7
5
  require 'fulmar/task_manager'
@@ -50,12 +50,14 @@ module Fulmar
50
50
  def standard_rake_options
51
51
  options = super
52
52
  options.reject { |option| option[0] == '--version' }
53
- options << ['--version', '-V',
54
- 'Display the program version.',
55
- lambda { |_value|
53
+ options << [
54
+ '--version',
55
+ '-V',
56
+ 'Display the program version.',
57
+ lambda do |_value|
56
58
  puts "fulmar #{Fulmar::VERSION} (using rake, version #{RAKEVERSION})"
57
59
  exit
58
- }
60
+ end
59
61
  ]
60
62
  end
61
63
  end
@@ -12,15 +12,20 @@ module Fulmar
12
12
  def render
13
13
  return unless @config[:config_templates]
14
14
  @config[:config_templates].each do |template_file|
15
- template = "#{@config[:local_path]}/#{template_file}"
16
- fail "Template filenames must end in .erb - '#{template}' does not" unless template[-4, 4] == '.erb'
17
- fail "Cannot render missing config file '#{template}'" unless File.exist? template
15
+ template = template_path(template_file)
18
16
 
19
17
  renderer = ERB.new(File.read(template))
20
18
  result_path = File.dirname(template) + '/' + File.basename(template, '.erb')
21
19
  File.open(result_path, 'w') { |config_file| config_file.write(renderer.result(binding)) }
22
20
  end
23
21
  end
22
+
23
+ def template_path(template_file)
24
+ template = "#{@config[:local_path]}/#{template_file}"
25
+ fail "Template filenames must end in .erb - '#{template}' does not" unless template[-4, 4] == '.erb'
26
+ fail "Cannot render missing config file '#{template}'" unless File.exist? template
27
+ template
28
+ end
24
29
  end
25
30
  end
26
31
  end
@@ -0,0 +1,143 @@
1
+ require 'erb'
2
+
3
+ module Fulmar
4
+ module Domain
5
+ module Service
6
+ # Tests the configuration
7
+ class ConfigTestService
8
+ def initialize(config)
9
+ @config = config
10
+ @config.load_user_config = false
11
+ @config.reset
12
+ end
13
+
14
+ # Runs all methods beginning with test_ and returns the report
15
+ def run
16
+ @report = []
17
+ tests = methods.select { |name| name.to_s[0, 5] == 'test_' }
18
+ tests.each do |test|
19
+ send(test)
20
+ end
21
+ @report
22
+ end
23
+
24
+ def test_hostnames_exist
25
+ @config.each do |env, target, data|
26
+ if data[:hostname].blank? && !data[:host].blank?
27
+ @report << {
28
+ message: "#{env}:#{target} has a host (#{data[:host]}) but is missing a hostname",
29
+ severity: :warning
30
+ }
31
+ end
32
+ end
33
+ end
34
+
35
+ def test_project_name_exists
36
+ if @config.configuration[:project][:name].blank?
37
+ add_report 'Project is missing a name', :warning
38
+ end
39
+ end
40
+
41
+ def test_hostnames_in_ssh_config
42
+ hostnames = ssh_hostnames
43
+
44
+ @config.each do |env, target, data|
45
+ next if data[:hostname].blank?
46
+
47
+ unless hostnames.include? data[:hostname]
48
+ @report << {
49
+ message: "#{env}:#{target} has a hostname (#{data[:hostname]}) which is not found in your ssh config",
50
+ severity: :info
51
+ }
52
+ end
53
+ end
54
+ end
55
+
56
+ def test_required_hostnames
57
+ types = %i(rsync rsync_with_version maria)
58
+ @config.each do |env, target, data|
59
+ if types.include?(data[:type]) && data[:hostname].blank?
60
+ @report << {
61
+ message: "#{env}:#{target} requires a hostname (#{data[:hostname]})",
62
+ severity: :error
63
+ }
64
+ end
65
+ end
66
+ end
67
+
68
+ def test_vhost_template_is_set
69
+ vhost_template = false
70
+
71
+ @config.each do |_env, _target, data|
72
+ vhost_template = true unless data[:vhost_template].blank?
73
+ end
74
+
75
+ add_report(
76
+ 'The configuration uses the vhost feature but misses a valid configuration for it (missing :vhost_template)',
77
+ :warning
78
+ ) if @config.feature?(:vhost) && !vhost_template
79
+ end
80
+
81
+ # Run simple test which only require one configuration
82
+ def test_simple_tests
83
+ @config.each do |env, target, data|
84
+ tests = methods.select { |name| name.to_s[0, 12] == 'simple_test_' }
85
+ tests.each do |test|
86
+ send(test, env, target, data)
87
+ end
88
+ end
89
+ end
90
+
91
+ def simple_test_local_path_exists(env, target, data)
92
+ unless File.exist? data[:local_path]
93
+ add_report("#{env}:#{target} has no valid local_path (#{data[:local_path]})", :warning)
94
+ end
95
+ end
96
+
97
+ def simple_test_mariadb_feature_is_set(env, target, data)
98
+ if data[:type] == :maria && !@config.feature?(:database)
99
+ @report << {
100
+ message: "#{env}:#{target} uses mysql/mariadb but your config is missing the database feature",
101
+ severity: :notice
102
+ }
103
+ end
104
+ end
105
+
106
+ def simple_test_vhost_feature_is_set(env, target, data)
107
+ if data[:vhost_template] && !@config.feature?(:vhost)
108
+ @report << {
109
+ message: "#{env}:#{target} refers to a vhost_template but your config is missing the vhost feature",
110
+ severity: :warning
111
+ }
112
+ end
113
+ end
114
+
115
+ def simple_test_maria_db_config_exists(env, target, data)
116
+ if data[:type] == :maria && data[:maria][:database].blank?
117
+ add_report "#{env}:#{target} is missing a database name in maria:database", :error
118
+ end
119
+ end
120
+
121
+ def simple_test_remote_path_exists_for_rsync(env, target, data)
122
+ types = [:rsync, :rsync_with_version]
123
+ if types.include?(data[:type]) && data[:remote_path].blank?
124
+ add_report "#{env}:#{target} is missing a remote path", :error
125
+ end
126
+ end
127
+
128
+ protected
129
+
130
+ def add_report(message, severity)
131
+ @report << {
132
+ message: message,
133
+ severity: severity
134
+ }
135
+ end
136
+
137
+ def ssh_hostnames
138
+ `grep -E '^Host [^ *]+$' ~/.ssh/config | sort | uniq | cut -d ' ' -f 2`.split("\n")
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -13,6 +13,7 @@ module Fulmar
13
13
  DEPLOYMENT_CONFIG_FILE = 'deployment.yml'
14
14
 
15
15
  BLANK_CONFIG = {
16
+ project: {},
16
17
  environments: {},
17
18
  features: {},
18
19
  hosts: {},
@@ -22,10 +23,12 @@ module Fulmar
22
23
  include Singleton
23
24
 
24
25
  attr_reader :environment, :target
26
+ attr_accessor :load_user_config
25
27
 
26
28
  def initialize
27
29
  @environment = nil
28
30
  @target = nil
31
+ @load_user_config = true
29
32
  end
30
33
 
31
34
  # Allow access of configuration via array/hash access methods (read access)
@@ -120,10 +123,17 @@ module Fulmar
120
123
 
121
124
  def config_files
122
125
  files = Dir.glob(File.join(base_path, FULMAR_CONFIGURATION_DIR, '*.config.yml')).sort
123
- files << "#{ENV['HOME']}/.fulmar.yml" if File.exist? "#{ENV['HOME']}/.fulmar.yml"
126
+ files << "#{ENV['HOME']}/.fulmar.yml" if File.exist?("#{ENV['HOME']}/.fulmar.yml") && @load_user_config
124
127
  files
125
128
  end
126
129
 
130
+ # Reset the loaded configuration, forcing a reload
131
+ # this is currently used for reloading the config without the user config file
132
+ # to test the project configuration
133
+ def reset
134
+ @config = nil
135
+ end
136
+
127
137
  protected
128
138
 
129
139
  def lookup_base_path
@@ -145,12 +155,9 @@ module Fulmar
145
155
  return if @config[:environments][env][target][:host].blank?
146
156
 
147
157
  host = @config[:environments][env][target][:host].to_sym
148
- if @config[:hosts] && @config[:hosts][host]
149
- @config[:hosts][host].each do
150
- @config[:environments][env][target] = @config[:hosts][host].deep_merge(@config[:environments][env][target])
151
- end
152
- else
153
- fail "Host #{host} is not configured."
158
+ return unless @config[:hosts] && @config[:hosts][host]
159
+ @config[:hosts][host].each do
160
+ @config[:environments][env][target] = @config[:hosts][host].deep_merge(@config[:environments][env][target])
154
161
  end
155
162
  end
156
163
 
@@ -26,7 +26,7 @@ module Fulmar
26
26
  if git.branches.select { |b| b.name.split('/').last == data[:ref] }.any?
27
27
  checkout_branch(git, data[:ref])
28
28
  elsif git.tags.map(&:name).include?(data[:ref])
29
- git.checkout('refs/tags/'+data[:ref])
29
+ git.checkout("refs/tags/#{data[:ref]}")
30
30
  elsif data[:ref].match(/^[a-zA-Z0-9]{40}$/) && git.exists?(data[:ref])
31
31
  git.checkout(data[:ref])
32
32
  else
@@ -34,7 +34,7 @@ module Fulmar
34
34
  end
35
35
 
36
36
  # Pull
37
- shell = Fulmar::Infrastructure::Service::ShellService.new @config[:local_path]+'/'+data[:path]
37
+ shell = Fulmar::Infrastructure::Service::ShellService.new "#{@config[:local_path]}/#{data[:path]}"
38
38
  unless shell.run 'git pull --rebase -q'
39
39
  fail "Cannot update repository #{data[:path]}. Please update manually."
40
40
  end
@@ -17,6 +17,10 @@ module Fulmar
17
17
  (@_config_service ||= Fulmar::Domain::Service::ConfigurationService.instance)
18
18
  end
19
19
 
20
+ def project
21
+ full_configuration[:project]
22
+ end
23
+
20
24
  def composer(command, arguments = Fulmar::Infrastructure::Service::ComposerService::DEFAULT_PARAMS)
21
25
  (storage['composer'] ||= Fulmar::Infrastructure::Service::ComposerService.new(local_shell)).execute(command, arguments)
22
26
  end
@@ -64,15 +68,15 @@ module Fulmar
64
68
  end
65
69
 
66
70
  def info(text)
67
- puts (ENV['TERM'] == 'xterm-256color' ? text.blue : "* Info: #{text}") if verbose
71
+ puts(ENV['TERM'] == 'xterm-256color' ? text.blue : "* Info: #{text}") if verbose
68
72
  end
69
73
 
70
- def warn(text)
71
- STDERR.puts (ENV['TERM'] == 'xterm-256color' ? text.magenta : "* Warning: #{text}")
74
+ def warning(text)
75
+ STDERR.puts(ENV['TERM'] == 'xterm-256color' ? text.magenta : "* Warning: #{text}")
72
76
  end
73
77
 
74
78
  def error(text)
75
- STDERR.puts (ENV['TERM'] == 'xterm-256color' ? text.light_red : "* Error: #{text}")
79
+ STDERR.puts(ENV['TERM'] == 'xterm-256color' ? text.light_red : "* Error: #{text}")
76
80
  end
77
81
  end
78
82
  end
@@ -18,4 +18,4 @@ end
18
18
 
19
19
  if configuration.feature?(:vhost) && configuration.any? { |data| !data[:vhost_template].blank? }
20
20
  require 'fulmar/domain/task/optional/vhost'
21
- end
21
+ end
@@ -0,0 +1,43 @@
1
+ require 'pp'
2
+
3
+ namespace :test do
4
+ task :config do
5
+ require 'fulmar/domain/service/config_test_service'
6
+ test_service = Fulmar::Domain::Service::ConfigTestService.new(configuration)
7
+ results = test_service.run
8
+
9
+ results.each do |report|
10
+ case report[:severity]
11
+ when :warning
12
+ warning "Warning: #{report[:message]}"
13
+ when :error
14
+ error "Error: #{report[:message]}"
15
+ else
16
+ info "Notice: #{report[:message]}"
17
+ end
18
+ end
19
+
20
+ info "Feelin' fine." if results.empty?
21
+ end
22
+
23
+ task :hosts do
24
+ error_count = 0
25
+ configuration.each do |env, target, _data|
26
+ configuration.environment = env
27
+ configuration.target = target
28
+
29
+ next if configuration[:hostname].blank?
30
+ remote_shell.quiet = true
31
+ remote_shell.strict = false
32
+
33
+ message = "Cannot open remote shell to host '#{configuration[:hostname]}' (#{env}:#{target})"
34
+
35
+ begin
36
+ remote_shell.run 'true' || error(message)
37
+ rescue
38
+ error(message)
39
+ end
40
+ end
41
+ info "Feelin' fine." if error_count == 0
42
+ end
43
+ end
@@ -1,7 +1,12 @@
1
1
  desc 'Open an interactive terminal within fulmar'
2
- task :console do
2
+ task :console, [:environment] do |t, args|
3
3
  require 'irb'
4
4
  require 'irb/completion'
5
5
  ARGV.clear
6
+ if args[:environment] && args[:environment].include?(':')
7
+ environment, target = args[:environment].split(':')
8
+ configuration.environment = environment
9
+ configuration.target = target
10
+ end
6
11
  IRB.start
7
- end
12
+ end
@@ -2,7 +2,6 @@
2
2
  # so that these tasks might be a dependency for other tasks
3
3
 
4
4
  namespace :environment do
5
-
6
5
  full_configuration[:environments].each_key do |env|
7
6
  # Sets the environment to #{env}
8
7
  task env do
@@ -3,8 +3,7 @@ include Fulmar::Domain::Service::Helper::VhostHelper
3
3
 
4
4
  VHOST_DEFAULT_CONFIG = {
5
5
  webserver: 'nginx',
6
- sites_enabled_dir: '../sites-enabled',
7
-
6
+ sites_enabled_dir: '../sites-enabled'
8
7
  }
9
8
 
10
9
  vhost_count = 0
@@ -14,8 +13,10 @@ namespace :vhost do
14
13
  configuration.each do |env, target, data|
15
14
  next if data[:vhost_template].blank?
16
15
 
16
+ task_environment = vhost_count > 1 ? ":#{env}" : ''
17
+
17
18
  desc "Create a vhost for #{env}"
18
- task (vhost_count > 1 ? "create:#{env}" : 'create') do
19
+ task "create#{task_environment}" do
19
20
  configuration.environment = env
20
21
  configuration.target = target
21
22
  configuration.merge(VHOST_DEFAULT_CONFIG)
@@ -36,17 +37,19 @@ namespace :vhost do
36
37
  upload config_file_name
37
38
  config_remote_path = configuration[:sites_available_dir] + '/' + File.basename(config_file_name)
38
39
  remote_shell.run [
39
- "rm -f #{configuration[:sites_enabled_dir]}/#{File.basename(config_file_name)}", # remove any existing link
40
- "ln -s #{config_remote_path} #{configuration[:sites_enabled_dir]}/#{File.basename(config_file_name)}",
41
- "service #{configuration[:webserver]} reload"
42
- ]
40
+ "rm -f #{configuration[:sites_enabled_dir]}/#{File.basename(config_file_name)}", # remove any existing link
41
+ "ln -s #{config_remote_path} #{configuration[:sites_enabled_dir]}/#{File.basename(config_file_name)}",
42
+ "service #{configuration[:webserver]} reload"
43
+ ]
44
+
45
+ FileUtils.rm config_file_name
43
46
 
44
47
  # recover remote path
45
48
  configuration[:remote_path] = remote_path
46
49
  end
47
50
 
48
51
  desc "List existing vhosts for #{env}"
49
- task (vhost_count > 1 ? "list:#{env}" : 'list') do
52
+ task "list#{task_environment}" do
50
53
  configuration.environment = env
51
54
  configuration.target = target
52
55
  configuration.merge(VHOST_DEFAULT_CONFIG)
@@ -62,16 +65,16 @@ namespace :vhost do
62
65
  end
63
66
 
64
67
  desc "Delete a vhost for #{env}"
65
- task (vhost_count > 1 ? "delete:#{env}" : 'delete'), [:name] do |_t, argv|
68
+ task "delete#{task_environment}", [:name] do |_t, argv|
66
69
  configuration.environment = env
67
70
  configuration.target = target
68
71
  configuration.merge(VHOST_DEFAULT_CONFIG)
69
72
 
70
73
  remote_shell.run [
71
- "rm auto_vhost_#{argv[:name]}.conf",
72
- "rm #{configuration[:sites_enabled_dir]}/auto_vhost_#{argv[:name]}.conf",
73
- "service #{configuration[:webserver] || 'nginx'} reload"
74
- ]
74
+ "rm auto_vhost_#{argv[:name]}.conf",
75
+ "rm #{configuration[:sites_enabled_dir]}/auto_vhost_#{argv[:name]}.conf",
76
+ "service #{configuration[:webserver] || 'nginx'} reload"
77
+ ]
75
78
  end
76
79
  end
77
80
  end
@@ -14,31 +14,40 @@ namespace :versions do
14
14
  unless @versioned_servers.empty?
15
15
 
16
16
  @versioned_servers.each_key do |env|
17
- target_count = @versioned_servers.keys.reduce(0) { |sum, target| target.split(':').first == env.split(':').first ? sum + 1 : sum }
18
- namespace :list do
17
+ target_count = @versioned_servers.keys.reduce(0) { |a, e| e.split(':').first == env.split(':').first ? a + 1 : a }
18
+
19
+ task_environment = (target_count > 1 ? env : env.split(':').first)
19
20
 
21
+ namespace :list do
20
22
  # Count of there are multiple targets within the environment
21
23
  # if not, we can omit the target name in the task and shorten it a bit
22
- # This should work in most cases.
24
+ # This should apply for almost all cases.
23
25
 
24
26
  desc "List available versions for environment/target \"#{env}\""
25
- task (target_count > 1 ? env : env.split(':').first) do
27
+ task task_environment do
26
28
  configuration.environment = env.split(':').first
27
29
  configuration.target = env.split(':').last
28
30
  file_sync.list_releases(false).each { |item| puts item }
29
31
  end
30
32
  end
31
33
 
32
-
33
34
  namespace :clean do
34
35
  desc "Delete obsolete versions for target \"#{env}\""
35
- task (target_count > 1 ? env : env.split(':').first) do
36
+ task task_environment do
36
37
  configuration.environment = env.split(':').first
37
38
  configuration.target = env.split(':').last
38
39
  file_sync.cleanup
39
40
  end
40
41
  end
41
- end
42
42
 
43
+ namespace :revert do
44
+ desc "Revert to the previous version for \"#{env}\""
45
+ task task_environment do
46
+ configuration.environment = env.split(':').first
47
+ configuration.target = env.split(':').last
48
+ error 'Cannot revert to previous version.' unless file_sync.revert
49
+ end
50
+ end
51
+ end
43
52
  end
44
53
  end
@@ -73,12 +73,14 @@ module Fulmar
73
73
  end
74
74
 
75
75
  def dump(filename = backup_filename)
76
+ filename = "#{@config[:remote_path]}/#{filename}" unless filename[0, 1] == '/'
77
+
76
78
  diffable = @config[:maria][:diffable_dump] ? '--skip-comments --skip-extended-insert ' : ''
77
79
 
78
80
  @shell.run "mysqldump -h #{@config[:maria][:host]} -u #{@config[:maria][:user]} --password='#{@config[:maria][:password]}' " \
79
81
  "#{@config[:maria][:database]} --single-transaction #{diffable}-r \"#{filename}\""
80
82
 
81
- @config[:remote_path] + '/' + filename
83
+ filename
82
84
  end
83
85
 
84
86
  def load_dump(dump_file, database = @config[:maria][:database])
@@ -98,6 +100,25 @@ module Fulmar
98
100
  end
99
101
  end
100
102
 
103
+ def clear
104
+ clear_statements = <<-EOD.gsub(/^\s+\|/, '')
105
+ |SET FOREIGN_KEY_CHECKS = 0;
106
+ |SET GROUP_CONCAT_MAX_LEN=32768;
107
+ |SET @tables = NULL;
108
+ |SELECT GROUP_CONCAT('`', table_name, '`') INTO @tables
109
+ |FROM information_schema.tables
110
+ |WHERE table_schema = (SELECT DATABASE());
111
+ |SELECT IFNULL(@tables,'dummy') INTO @tables;
112
+ |
113
+ |SET @tables = CONCAT('DROP TABLE IF EXISTS ', @tables);
114
+ |PREPARE stmt FROM @tables;
115
+ |EXECUTE stmt;
116
+ |DEALLOCATE PREPARE stmt;
117
+ |SET FOREIGN_KEY_CHECKS = 1;
118
+ EOD
119
+ @client.query clear_statements
120
+ end
121
+
101
122
  protected
102
123
 
103
124
  def try_connect(options, i)
@@ -115,9 +115,11 @@ module Fulmar
115
115
  #
116
116
  # @params release [String] the release folder or time string (which is found in the output list)
117
117
  # @return [true, false] success
118
- def revert(release)
118
+ def revert(release = last_release)
119
119
  prepare unless @prepared
120
120
 
121
+ return false if release.nil?
122
+
121
123
  # Convenience: Allow more readable version string from output
122
124
  if release.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)
123
125
  release = Time.strptime(item, TIME_READABLE).strftime(TIME_FOLDER)
@@ -128,6 +130,13 @@ module Fulmar
128
130
 
129
131
  protected
130
132
 
133
+ def last_release
134
+ list = list_releases
135
+ current = current_release
136
+ current_index = list.index(current)
137
+ (current_index.nil? || current_index == 0) ? nil : list[current_index - 1]
138
+ end
139
+
131
140
  # Creates all necessary paths on the remote machine
132
141
  # @return [true, false] success
133
142
  def create_paths
@@ -1,4 +1,4 @@
1
1
  # Provides a global version number
2
2
  module Fulmar
3
- VERSION = '1.5.2'
3
+ VERSION = '1.6.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fulmar
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonas Siegl
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-04-20 00:00:00.000000000 Z
12
+ date: 2015-05-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -139,6 +139,7 @@ files:
139
139
  - fulmar.gemspec
140
140
  - lib/fulmar/domain/service/application_service.rb
141
141
  - lib/fulmar/domain/service/config_rendering_service.rb
142
+ - lib/fulmar/domain/service/config_test_service.rb
142
143
  - lib/fulmar/domain/service/configuration_service.rb
143
144
  - lib/fulmar/domain/service/dependency_service.rb
144
145
  - lib/fulmar/domain/service/file_sync_service.rb
@@ -149,6 +150,7 @@ files:
149
150
  - lib/fulmar/domain/service/helper/vhost_helper.rb
150
151
  - lib/fulmar/domain/service/initialization_service.rb
151
152
  - lib/fulmar/domain/task/base.rake
153
+ - lib/fulmar/domain/task/configuration.rake
152
154
  - lib/fulmar/domain/task/console.rake
153
155
  - lib/fulmar/domain/task/database_sync.rake
154
156
  - lib/fulmar/domain/task/environment.rake