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 +4 -4
- data/README.md +307 -4
- data/bin/fulmar +1 -3
- data/lib/fulmar/domain/service/application_service.rb +6 -4
- data/lib/fulmar/domain/service/config_rendering_service.rb +8 -3
- data/lib/fulmar/domain/service/config_test_service.rb +143 -0
- data/lib/fulmar/domain/service/configuration_service.rb +14 -7
- data/lib/fulmar/domain/service/dependency_service.rb +2 -2
- data/lib/fulmar/domain/service/helper/common_helper.rb +8 -4
- data/lib/fulmar/domain/task/base.rake +1 -1
- data/lib/fulmar/domain/task/configuration.rake +43 -0
- data/lib/fulmar/domain/task/console.rake +7 -2
- data/lib/fulmar/domain/task/environment.rake +0 -1
- data/lib/fulmar/domain/task/optional/vhost.rb +16 -13
- data/lib/fulmar/domain/task/versions.rake +16 -7
- data/lib/fulmar/infrastructure/service/database/database_service.rb +22 -1
- data/lib/fulmar/infrastructure/service/transfer/rsync_with_versions.rb +10 -1
- data/lib/fulmar/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86d6596b35b141aa77a6e85f919123ba13f2524e
|
4
|
+
data.tar.gz: ed72cbe277ea44d85c0487427d7a6c5abf24be20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
6
|
-
|
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
|
-
|
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
@@ -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 << [
|
54
|
-
|
55
|
-
|
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 =
|
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?
|
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
|
-
|
149
|
-
|
150
|
-
|
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(
|
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]
|
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
|
71
|
+
puts(ENV['TERM'] == 'xterm-256color' ? text.blue : "* Info: #{text}") if verbose
|
68
72
|
end
|
69
73
|
|
70
|
-
def
|
71
|
-
STDERR.puts
|
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
|
79
|
+
STDERR.puts(ENV['TERM'] == 'xterm-256color' ? text.light_red : "* Error: #{text}")
|
76
80
|
end
|
77
81
|
end
|
78
82
|
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
|
@@ -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
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
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
|
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
|
-
|
72
|
-
|
73
|
-
|
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) { |
|
18
|
-
|
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
|
24
|
+
# This should apply for almost all cases.
|
23
25
|
|
24
26
|
desc "List available versions for environment/target \"#{env}\""
|
25
|
-
task
|
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
|
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
|
-
|
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
|
data/lib/fulmar/version.rb
CHANGED
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.
|
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-
|
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
|