matross 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YWRiYWQ4NDg4NGNlNDc0OWY5Y2M5NGU1NGViNTY3NWMxN2JmYzU1NQ==
4
+ ZDdiOTQ1YzRjMmRlOTRhZDk3YTdlNzc1NDhkMjhjMTJjNjY4YjYyOA==
5
5
  data.tar.gz: !binary |-
6
- NDlkYmU4ZjNhNTFmNGNlMWJmMzMyMDllNTcyODIyNGNkODI3ZjVkOQ==
6
+ ZDhlYmYxZjhhODE2MTM5ODkxODA0NmFlNzkwZTRhOWIxMGQyODNmZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YzJlMjY4MmE5ODkxYWI2YjE4N2I3MDU2NjA4NWY3YjliMzBmN2U5OTg0NjQx
10
- ZjM0ODEwYmFkZDkwNDM3NWU4MTQwMTU2MzNkNWVjMDllMjBlYWNhMGY0OGYx
11
- YTMxOTk2ZGU1ZjkxNzUzZjI4YTc3Zjg0YTE0OGJlNjZlNDQyOTE=
9
+ NTYzZTk0ZDAzZmU1ODhiYWJiYWNlOWZjMDBjN2FhYTk0ZmVmMzg0YTIwMmVh
10
+ ZDA2NTRiYzE0M2ZiOTExODNjNmNlM2NjNzFiN2RjZDUyMDBhYTIzNGEwNTVl
11
+ MjNjOTZkNzQzYzNjZjVhYjk5YzEyMTQ2ZjI2OTAzOWVmMTZkZjU=
12
12
  data.tar.gz: !binary |-
13
- NjlhNzdhY2M0NDRlMzQwMjJkNTFiMWU0NTJjM2NjMzRiNzUxMzRhYmU4OWM2
14
- M2I5ZTliOWFkNDJhYzE0N2Q1ZjZlODZmZmVkMmVhNDgzMmYxYjk3MzI1M2Zj
15
- MGZlMDhkZGFiNTYxODIzZWU4NmFmZWM0ZTUwMzMyOWYwYTIxZjE=
13
+ YTY5NmNhN2M0NTI2NTk4NzVhNTY1OTM5YjA2OWFhZGQzN2Q4YjY2YzMwNGEw
14
+ YTRkOTZlZWU5MWM0NTE3Y2FiYjBmN2ViYWZmYmU2NTAyODdjMzkzZjAwMzcy
15
+ OTZlZWUyYTJjZmRlMzhmYTMxYWQ1NjQ0M2VhMmVmZDk1OWQ2MTk=
data/README.md CHANGED
@@ -130,31 +130,41 @@ This recipes creates and configures the virtual_host for the application. [This
130
130
  | `nginx:lock` | Sets up the a basic http auth on the virtual host |
131
131
  | `nginx:unlock` | Removes the basic http auth |
132
132
 
133
-
134
133
  ### MySQL
135
134
 
136
135
  Requires having [`mysql2`](http://rubygems.org/gems/mysql2) available in the application. In our MySQL recipe we dynamically generate a `database.yml` based on the variables that should be set globally or per-stage.
137
136
 
137
+ The backup routine requires [`s3cmd`](http://s3tools.org/s3cmd) installed and properly configured. The key pair must have write access to the backup bucket on S3.
138
+
138
139
  Overwritable template: [`database.yml.erb`](lib/matross/templates/mysql/database.yml.erb)
139
140
 
140
141
  > Variables
141
142
 
142
- | Variable | Default value | Description |
143
- | --- | --- | --- |
144
- | `:database_config` | `"#{shared_path}/config/database.yml"` | Location of the configuration file |
145
- | `:mysql_host` | None | MySQL host address |
146
- | `:mysql_database` | None | MySQL database name. We automatically substitute dashes `-` for underscores `_` |
147
- | `:mysql_user` | None | MySQL user |
148
- | `:mysql_passwd` | None | MySQL password |
143
+ | Variable | Default value | Description |
144
+ | --- | --- | --- |
145
+ | `:database_config` | `"#{shared_path}/config/database.yml"` | Location of the configuration file |
146
+ | `:mysql_host` | None | MySQL host address |
147
+ | `:mysql_database` | None | MySQL database name. Dashes `-` are *gsubed* for underscores `_` |
148
+ | `:mysql_user` | None | MySQL user |
149
+ | `:mysql_passwd` | None | MySQL password |
150
+ | `:mysql_backup_script` | `"#{shared_path}/matross/mysql_backup.sh"` | MySQL backup script location |
151
+ | `:mysql_backup_cron_schedule` | `'30 3 * * *'` | Cron schedule for the backup script |
152
+ | `:mysql_backup_bucket` | None | Bucket used to store the dumps |
153
+ | `:mysql_backup_prefix` | None | Flat file prefix for the backup files |
149
154
 
150
155
  > Tasks
151
156
 
152
- | Task | Description |
153
- | --- | --- |
154
- | `mysql:setup` | Creates the `database.yml` in the `shared_path` |
155
- | `mysql:symlink` | Creates a symlink for the `database.yml` file in the `current_path` |
156
- | `mysql:create` | Creates the database if it hasn't been created |
157
- | `mysql:schema_load` | Loads the schema if there are no tables in the DB |
157
+ | Task | Description |
158
+ | --- | --- |
159
+ | `mysql:setup` | Creates the `database.yml` in the `shared_path` |
160
+ | `mysql:symlink` | Creates a symlink for the `database.yml` file in the `current_path` |
161
+ | `mysql:create` | Creates the database if it hasn't been created |
162
+ | `mysql:schema_load` | Loads the schema if there are no tables in the DB |
163
+ | `mysql:backup:setup` | Creates the backup script and configures the user's cron - *Not hooked to any other task* |
164
+ | `mysql:dump:do` | Dumps the application database |
165
+ | `mysql:dump:get` | Downloads a copy of the last generated database dump |
166
+ | `mysql:dump:apply` | Apply the latest dump generated stored in 'dumps' locally |
167
+
158
168
 
159
169
  ## Mongoid
160
170
 
@@ -198,7 +208,6 @@ Procfile task: `dj: bundle exec rake jobs:work` or `dj_<%= queue_name %>: bundle
198
208
  | --- | --- |
199
209
  | `delayed_job:procfile` | Defines how `delayed_job` should be run in a temporary `Procfile` |
200
210
 
201
-
202
211
  ### Fog (AWS)
203
212
 
204
213
  Requires having [`fog`](http://rubygems.org/gems/fog) available in the application. When we use `fog`, it is for interacting with Amazon services, once again very opinionated.
@@ -1,6 +1,8 @@
1
1
  dep_included? 'mysql2'
2
2
 
3
- _cset(:database_config) { "#{shared_path}/config/database.yml" }
3
+ _cset(:database_config) { "#{shared_path}/config/database.yml" }
4
+ _cset(:mysql_backup_script) { "#{shared_path}/matross/mysql_backup.sh" }
5
+ _cset :mysql_backup_cron_schedule, '30 3 * * *'
4
6
 
5
7
  namespace :mysql do
6
8
 
@@ -18,26 +20,112 @@ namespace :mysql do
18
20
  after "bundle:install", "mysql:symlink"
19
21
 
20
22
  desc "Creates the application database"
21
- task :create, :roles => [:db] do
23
+ task :create, :roles => :db do
22
24
  sql = <<-EOF.gsub(/^\s+/, '')
23
25
  CREATE DATABASE IF NOT EXISTS #{mysql_database.gsub("-", "_")};
24
26
  EOF
25
- run "mysql --user=#{mysql_user} --password=#{mysql_passwd} --host=#{mysql_host} --execute=\"#{sql}\""
27
+ run %W{mysql --user=#{mysql_user}
28
+ #{'--password=' + mysql_passwd unless mysql_passwd.empty?}
29
+ --host=#{mysql_host}
30
+ --execute="#{sql}"} * ' '
26
31
  end
27
32
  after "mysql:setup", "mysql:create"
28
33
 
29
34
  desc "Loads the application schema into the database"
30
- task :schema_load, :roles => [:db] do
35
+ task :schema_load, :roles => :db do
31
36
  sql = <<-EOF.gsub(/^\s+/, '')
32
37
  SELECT count(*) FROM information_schema.TABLES WHERE (TABLE_SCHEMA = '#{mysql_database.gsub("-", "_")}');
33
38
  EOF
34
- table_count = capture("mysql --batch --skip-column-names "\
35
- "--user=#{mysql_user} "\
36
- "--password=#{mysql_passwd} "\
37
- "--host=#{mysql_host} "\
38
- "--execute=\"#{sql}\"").to_i
39
- run "cd #{release_path} &&"\
40
- "RAILS_ENV=#{rails_env.to_s.shellescape} bundle exec rake db:schema:load" if table_count == 0
39
+ table_count = capture(%W{mysql --batch --skip-column-names
40
+ --user=#{mysql_user}
41
+ #{'--password=' + mysql_passwd unless mysql_passwd.empty?}
42
+ --host=#{mysql_host}
43
+ --execute="#{sql}"} * ' ').to_i
44
+ run %W{cd #{release_path} &&
45
+ RAILS_ENV=#{rails_env.to_s.shellescape} bundle exec rake db:schema:load
46
+ } * ' ' if table_count == 0
41
47
  end
42
48
  after "mysql:symlink", "mysql:schema_load"
49
+
50
+ namespace :backup do
51
+
52
+ # This routine is heavily inspired by whenever's approach
53
+ # https://github.com/javan/whenever
54
+ desc "Updates the crontab"
55
+ task :setup, :roles => :db do
56
+ template "mysql/backup.sh.erb", mysql_backup_script
57
+ run "chmod +x #{mysql_backup_script}"
58
+
59
+ comment_open = '# Begin Matross generated task for MySQL Backup'
60
+ comment_close = '# End Matross generated task for MySQL Backup'
61
+
62
+ cron_command = "#{mysql_backup_script} 2>&1 >> #{shared_path}/log/mysql_backup.log"
63
+ cron_entry = "#{mysql_backup_cron_schedule} #{cron_command}"
64
+ cron = [comment_open, cron_entry, comment_close].compact.join("\n")
65
+
66
+ current_crontab = ''
67
+ begin
68
+ # Some cron implementations require all non-comment lines to be
69
+ # newline-terminated. Strip all newlines and replace with the default
70
+ # platform record seperator ($/)
71
+ current_crontab = capture("crontab -l -u #{user} 2> /dev/null").gsub!(/\s+$/, $/)
72
+ rescue Capistrano::CommandError
73
+ logger.debug 'The user has no crontab'
74
+ end
75
+ contains_open_comment = current_crontab =~ /^#{comment_open}\s*$/
76
+ contains_close_comment = current_crontab =~ /^#{comment_close}\s*$/
77
+
78
+ # If an existing identier block is found, replace it with the new cron entries
79
+ if contains_open_comment && contains_close_comment
80
+ updated_crontab = current_crontab.gsub(/^#{comment_open}\s*$.+^#{comment_close}\s*$/m, cron.chomp)
81
+ else # Otherwise, append the new cron entries after any existing ones
82
+ updated_crontab = current_crontab.empty? ? cron : [current_crontab, cron].join("\n")
83
+ end.gsub(/\n{2,}/, "\n") # More than one newline becomes just one.
84
+
85
+ temp_crontab_file = "/tmp/matross_#{user}_crontab"
86
+ put updated_crontab, temp_crontab_file
87
+ run "crontab -u #{user} #{temp_crontab_file}"
88
+ run "rm #{temp_crontab_file}"
89
+ end
90
+ end
91
+
92
+ namespace :dump do
93
+
94
+ desc "Dumps the application database"
95
+ task :do, :roles => :db, :except => { :no_release => true } do
96
+ run "mkdir -p #{shared_path}/dumps"
97
+ run %W{cd #{shared_path}/dumps &&
98
+ mysqldump --quote-names --create-options
99
+ --user=#{mysql_user}
100
+ #{'--password=' + mysql_passwd unless mysql_passwd.empty?}
101
+ --host=#{mysql_host}
102
+ #{mysql_database} |
103
+ gzip > "$(date +'#{mysql_database}_\%Y\%m\%d\%H\%M.sql.gz')"} * ' '
104
+ end
105
+
106
+ desc "Downloads a copy of the last generated database dump"
107
+ task :get, :roles => :db, :except => { :no_release => true } do
108
+ run_locally "mkdir -p dumps"
109
+ most_recent_bkp = capture(%W{find #{shared_path} -type f -name
110
+ '#{mysql_database}_*.sql.gz'} * ' '
111
+ ).split.sort.last
112
+ abort "No dump found. Run mysql:dump:do." if most_recent_bkp.nil?
113
+
114
+ download most_recent_bkp, "dumps", :via => :scp
115
+ run_locally "gzip -d dumps/#{File.basename(most_recent_bkp)}"
116
+ end
117
+
118
+ desc "Apply the latest dump generated stored in 'dumps' locally"
119
+ task :apply, :roles => :db, :except => { :no_release => true } do
120
+ most_recent_bkp = %x[find dumps -type f -name\
121
+ '#{mysql_database}_*.sql'].split.sort.last
122
+ abort "No dump found. Run mysql:dump:get." if most_recent_bkp.nil?
123
+
124
+ db_config = YAML.load(File.read('config/database.yml'))['development']
125
+ run_locally %W{mysql --user=#{db_config['username']}
126
+ --host=#{db_config['host']}
127
+ #{'--password=' + db_config['password'] unless db_config['password'].nil?}
128
+ #{db_config['database']} < #{most_recent_bkp}} * ' '
129
+ end
130
+ end
43
131
  end
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+
3
+ SRCDIR='/tmp/<%= application %>-mysql-backup'
4
+ BUCKET=<%= mysql_backup_bucket %>
5
+ PREFIX=<%= File.join(mysql_backup_prefix, '') %>
6
+
7
+ # database access details
8
+ HOST=<%= mysql_host %>
9
+ USER=<%= mysql_user%>
10
+ PASS=<%= mysql_passwd%>
11
+ DB=<%= mysql_database%>
12
+
13
+ # backup name
14
+ BACKUP_NAME="$(date +'bkp%Y%m%d%H%M')"
15
+
16
+ # make the temp directory if it doesn't exist and cd into it
17
+ mkdir -p ${SRCDIR}
18
+ cd ${SRCDIR}
19
+
20
+ # mysql dump
21
+ mysqldump -h${HOST} -u${USER} --force ${DB} > ${BACKUP_NAME}.sql
22
+ tar --create --gzip --absolute-names --file ${BACKUP_NAME}.tar.gz ${BACKUP_NAME}.sql
23
+
24
+ # upload to s3
25
+ s3cmd put ${SRCDIR}/${BACKUP_NAME}.tar.gz s3://${BUCKET}/${PREFIX}
26
+
27
+ # clean old files
28
+ find ${SRCDIR}/* -mtime +5 -exec rm {} \;
@@ -1,3 +1,3 @@
1
1
  module Matross
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matross
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artur Rodrigues
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-18 00:00:00.000000000 Z
12
+ date: 2013-11-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -84,6 +84,7 @@ files:
84
84
  - lib/matross/templates/fog/fog_config.yml.erb
85
85
  - lib/matross/templates/foreman/process.conf.erb
86
86
  - lib/matross/templates/mongoid/mongoid.yml.erb
87
+ - lib/matross/templates/mysql/backup.sh.erb
87
88
  - lib/matross/templates/mysql/database.yml.erb
88
89
  - lib/matross/templates/nginx/nginx_virtual_host_conf.erb
89
90
  - lib/matross/templates/unicorn/unicorn.rb.erb