matross 0.3.0 → 0.4.0

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