capistrano-chocopoche 0.0.2 → 0.2.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,7 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: 1bd1489c00876b7c9ab12807d47716466f3040c6
4
- data.tar.gz: cfd8a4a6c773f096d2e8e32ab4aba5e3703dbcb6
5
- !binary "U0hBNTEy":
6
- metadata.gz: fc676969d37f7523d4ed8b775bd4fb4700a23dfe0409e35456f0ae8eb16b8af022d5b43b5f2762c3689cff4770eeb95bacfd0028da89a2b682aad4522f4131fa
7
- data.tar.gz: 9d643175726549773759b31b9c259c42f5ffb7af7594031552a172664e6fee202f049e38f58c1df7ef9c72f18a3d040f4e18ed8e0248f2317f1ef0a37d1b5c97
2
+ SHA1:
3
+ metadata.gz: 26cfccb5e750bc7083dfdd42dfc6320d40622ef9
4
+ data.tar.gz: b56877aa569af52f82ee19958ee0502bb3e78abe
5
+ SHA512:
6
+ metadata.gz: bda1db522b9277ea660c14953985a1783c79f7b8551ab2659c7975466e43f9724eb46042beee6ddd06779b8957b6770d688f55d10b7013deccd11e5541b948b2
7
+ data.tar.gz: db3d2a905b958f5def5288136a99faf175dcfb80a423335709aa8c93243934ceb1fc3ec0e9000642731951d1be65a31ed126bf0ea07b4cea25ad5e6fd51530db
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in pm.gemspec
3
+ # Specify your gem's dependencies in capistrano-chocopoche.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -2,145 +2,178 @@
2
2
 
3
3
  My capistrano poche contains:
4
4
 
5
- - another railsless-deploy recipe
6
- - a files utility to rsync directories
5
+ - a php composer recipe
6
+ - a files utility to rsync directories up and down
7
+ - a mysql utility to dump/import, download/upload databases
8
+ - a bin to help synchronizing multiple stage together: `csync`
9
+ - and a railsless-deploy recipe (which uses the default rails recipes -
10
+ it may not be a good idea to reimplement...)
7
11
 
8
12
  ## Installation
9
13
 
10
14
  $ gem install capistrano-chocopoche
11
15
 
12
- ## Capfile example
16
+ ## Tasks
17
+
18
+ ```shell
19
+ cap composer # Runs an arbitrary composer command
20
+ cap composer:update # Runs the composer update command
21
+ cap connect # Connects via SSH to the first app server, and executes a `bash --login` to stay connected
22
+ cap deploy # Deploys your project.
23
+ cap deploy:check # Test deployment dependencies.
24
+ cap deploy:cleanup # Clean up old releases.
25
+ cap deploy:create_symlink # Updates the symlink to the most recently deployed version.
26
+ cap deploy:pending # Displays the commits since your last deploy.
27
+ cap deploy:pending:diff # Displays the `diff' since your last deploy.
28
+ cap deploy:restart # Blank task exists as a hook into which to install your own environment specific behaviour.
29
+ cap deploy:rollback # Rolls back to a previous version and restarts.
30
+ cap deploy:rollback:code # Rolls back to the previously deployed version.
31
+ cap deploy:setup # Prepares one or more servers for deployment.
32
+ cap deploy:start # Blank task exists as a hook into which to install your own environment specific behaviour.
33
+ cap deploy:stop # Blank task exists as a hook into which to install your own environment specific behaviour.
34
+ cap deploy:symlink # Deprecated API.
35
+ cap deploy:update # Copies your project and updates the symlink.
36
+ cap deploy:update_code # Copies your project to the remote servers.
37
+ cap deploy:upload # Copy files to the currently deployed version.
38
+ cap files:create_symlinks # Creates :files_symlinks from the shared folder to the current one on the app servers.
39
+ cap files:download # Sync files from the first web server to the local temp directory.
40
+ cap files:upload # Sync files from the local temp directory to the first web server.
41
+ cap files:upload_files_from_templates # Creates files from templates and upload them to the app server.
42
+ cap invoke # Invoke a single command on the remote servers.
43
+ cap mysql:download # Download last remote dumps of each databases.
44
+ cap mysql:dump # Dump databases to remote backup folder.
45
+ cap mysql:import # Import last remote dumps to databases.
46
+ cap mysql:upload # Upload last local dump of each databases.
47
+ cap shell # Begin an interactive Capistrano session.
48
+ ```
49
+
50
+ ## Capfile example with multistage
51
+
52
+ The [short_url](https://github.com/chocopoche/short_url) project uses that
53
+ library, it's a good working example. See the `Capfile` and stages config under
54
+ `config/deploy`.
55
+
56
+ The Capfile:
13
57
 
14
58
  ```ruby
15
59
  # Capistrao defaults
16
60
  load 'deploy'
17
61
 
18
- # Multistage - to be loaded before the railsless-deploy
19
62
  require 'capistrano/ext/multistage'
20
-
21
- # Rails inhibition
22
- require 'capistrano-chocopoche/railsless-deploy'
23
-
24
- # Rsync tasks
25
- require 'capistrano-chocopoche/files'
63
+ require 'capistrano/chocopoche'
26
64
 
27
65
  # Base configuration
28
66
  set :application, "my-project"
29
- set :repository, "git@localhost:#{application}.git"
67
+ set :repository, "git@example.com:my-project.git"
30
68
  set :use_sudo, false
31
69
  ssh_options[:forward_agent] = true
32
70
 
33
- # Rsync + symlinks configuration
34
- set :files_directories, [ 'public/upload' ]
35
- set :files_symlinks, [ 'public/upload' ]
36
-
37
- # # Server config, won't be here in case of multistage
38
- # server 'localhost', :app, :web, :db, :primary => true
39
-
40
- # # default settings
41
- # set :files_tmp_dir, 'tmp/capistrano-chocopoche/files'
42
- # set :deploy_to, '/home/#{user}/apps/#{application}[.#{stage}]'
71
+ # Folders to rsync with files:download
72
+ set :files_rsync, files_rsync + %w(web/qr)
73
+
74
+ # Symlinks to create after deploy:update_code
75
+ set :files_symlinks, files_symlinks + %w(web/qr)
76
+
77
+ # # Won't work with the cli command `csync` because the default stage task
78
+ # # will be invoked, but it should not
79
+ # set :default_stage, 'vm'
80
+
81
+ # Files to be generated on setup
82
+ set :files_tpl, [
83
+ {
84
+ :template => "config/deploy/templates/nginx.conf.erb",
85
+ :dest => "config/nginx.conf"
86
+ },
87
+ {
88
+ :template => "config/deploy/templates/parameters.yml.erb",
89
+ :dest => "config/parameters.yml"
90
+ }
91
+ ]
43
92
  ```
44
93
 
45
- ## Railsless-deploy
46
-
47
- This script requires the default capistrano tasks to be loaded, then it will:
48
-
49
- - delete rails tasks: `migrate`, `migrations`, `cold`
50
- - empty rails tasks (but keep it for hooks): `finalize_update`
51
- - delete rails vars: `rails_env`
52
- - empty rails vars: `shared_children`
53
- - override the `deploy_to` var to:
54
-
55
- - `/home/#{user}/apps/#{application}.#{stage}`
56
- - or `/home/#{user}/apps/#{application}` if the multistage ext is not
57
- loaded.
58
-
59
- Therefore the multistage ext must be required before the railsless-deploy.
60
-
61
- - override symlinks related tasks to use relative paths: `create_symlink`,
62
- `rollback:revision`
63
-
64
- Also the last one implements the atomic symlink as suggested in
65
- [issue #346](https://github.com/capistrano/capistrano/issues/346).
66
-
67
- ## Files
68
-
69
- The `download` and `upload` tasks use a temporary directory as pivot, so you are
70
- able to sync from a stage to another.
71
-
72
- The following scenario assumes that all commands are launched from the dev stage,
73
- that you have 3 environements and that `:files_directories` and `:files_symlinks`
74
- are set as in the example:
75
-
76
- - dev: may only contains the cap recipe
77
- - staging: a stage without files and symlinks
78
- - prod: a stage with files and symlinks. Prod looks like:
79
-
80
- my-project.prod/
81
- ├── current/
82
- │ └── public/
83
- │ └── upload/ => symlink to my-project/shared/public/upload/
84
- └── shared/
85
- └── public/
86
- └── upload/ => git ignores that folder
87
- ├── file1.png
88
- ├── file2.png
89
- ...
90
-
91
- ### Download
92
-
93
- Download `prod:my-project/shared/public/upload` to `dev:my-project/tmp/capistrano-chocopoche/files/public/upload`:
94
-
95
- $ cap prod files:download
96
-
97
- Gives:
98
-
99
- my-project.dev/
100
- ├── current/
101
- ├── shared/
102
- └── tmp/
103
- └── capistrano-chocopoche/
104
- └── files/
105
- └── public/
106
- └── upload/
107
- ├── file1.png
108
- ├── file2.png
109
- ...
94
+ A stage file in `config/deploy/[stage].rb:
95
+ ```
96
+ server 'example.com', :app, :web, :db, :primary => true
97
+
98
+ def set_files_tpl_params
99
+ set :files_tpl_params, {
100
+ :server => {
101
+ :hostname => "#{stage}.example.com"
102
+ },
103
+ :database => {
104
+ :driver => "pdo_mysql",
105
+ :dbname => "dbname",
106
+ :user => "user",
107
+ :password => "password",
108
+ :host => "localhost"
109
+ },
110
+ }
111
+ end
112
+ ```
110
113
 
111
- ### Upload
114
+ ## Capfile example without multistage
112
115
 
113
- Upload `dev:my-project/tmp/capistrano-chocopoche/files/public/upload` to `staging:my-project/shared/public/upload`:
116
+ ```ruby
117
+ # Capistrao defaults
118
+ load 'deploy'
114
119
 
115
- $cap staging files:upload
120
+ require 'capistrano/chocopoche'
116
121
 
117
- Gives:
122
+ # Base configuration
123
+ set :application, "my-project"
124
+ set :repository, "git@example.com:my-project.git"
125
+ set :use_sudo, false
126
+ ssh_options[:forward_agent] = true
118
127
 
119
- my-project.staging/
120
- ├── current/
121
- └── shared/
122
- └── public/
123
- └── upload/
124
- ├── file1.png
125
- ├── file2.png
126
- ...
128
+ # Folders to rsync with files:download
129
+ set :files_rsync, files_rsync + %w(web/qr)
130
+
131
+ # Symlinks to create after deploy:update_code
132
+ set :files_symlinks, files_symlinks + %w(web/qr)
133
+
134
+ # Files to be generated on setup
135
+ set :files_tpl, [
136
+ {
137
+ :template => "config/deploy/templates/nginx.conf.erb",
138
+ :dest => "config/nginx.conf"
139
+ },
140
+ {
141
+ :template => "config/deploy/templates/parameters.yml.erb",
142
+ :dest => "config/parameters.yml"
143
+ }
144
+ ]
145
+
146
+ server 'localhost', :app, :web, :db, :primary => true
147
+
148
+ def set_files_tpl_params
149
+ set :files_tpl_params, {
150
+ :server => {
151
+ :hostname => "#{stage}.example.com"
152
+ },
153
+ :database => {
154
+ :driver => "pdo_mysql",
155
+ :dbname => "dbname",
156
+ :user => "user",
157
+ :password => "password",
158
+ :host => "localhost"
159
+ },
160
+ }
161
+ end
162
+ ```
127
163
 
128
- ### Symlink
164
+ ## csync
129
165
 
130
- Create a symlink from `staging:my-project/shared/public/upload/` to `staging:my-project/current/public/upload/`:
166
+ The csync will chain capistrano commands in order to synchronise two stages.
167
+ Example:
131
168
 
132
- $ cap staging files:create_symlinks
169
+ $ csync mysql prod dev
133
170
 
134
- Gives:
171
+ will dump and download databases from *prod*, then upload and import them to
172
+ *dev*.
135
173
 
136
- my-project.staging/
137
- ├── current/
138
- │ └── public/
139
- │ └── upload/ => symlink to my-project/shared/public/upload/
140
- └── shared/
141
- └── public/
142
- └── upload/ => git ignores that folder
174
+ $ csync files prod dev
143
175
 
176
+ will rsync files from *prod* to *dev*.
144
177
 
145
178
  ## License
146
179
 
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'capistrano/chocopoche/sync-cli'
3
+
4
+ cli = Capistrano::Chocopoche::SyncCLI.new(ARGV)
5
+ cli.execute
@@ -1,19 +1,25 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'capistrano/chocopoche/version'
4
5
 
5
6
  Gem::Specification.new do |spec|
6
7
  spec.name = "capistrano-chocopoche"
7
- spec.version = "0.0.2"
8
+ spec.version = Capistrano::Chocopoche::VERSION
8
9
  spec.authors = ["Corentin Merot"]
9
10
  spec.email = ["cmerot@themarqueeblink.com"]
10
- spec.description = %q{Capistrano recipes, with another railsless-deploy and a files utility.}
11
+ spec.description = %q{Capistrano recipes and bin for mysql, rsync, php composer.}
11
12
  spec.summary = %q{Chocopoche's Capistrano recipes}
12
13
  spec.homepage = "https://github.com/chocopoche/capistrano-chocopoche"
13
14
  spec.license = "MIT"
15
+
14
16
  spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
19
  spec.require_paths = ["lib"]
16
20
 
21
+ spec.add_runtime_dependency "capistrano", "~> 2.14"
22
+
17
23
  spec.add_development_dependency "bundler", "~> 1.3"
18
24
  end
19
25
 
@@ -0,0 +1,5 @@
1
+ require "capistrano/chocopoche/version"
2
+ require 'capistrano/chocopoche/railsless-deploy'
3
+ require 'capistrano/chocopoche/files'
4
+ require 'capistrano/chocopoche/mysql'
5
+ require 'capistrano/chocopoche/composer'
@@ -0,0 +1,36 @@
1
+ configuration = Capistrano::Configuration.respond_to?(:instance) ?
2
+ Capistrano::Configuration.instance(:must_exist) :
3
+ Capistrano.configuration(:must_exist)
4
+
5
+
6
+ configuration.load do
7
+
8
+ _cset :user, Etc.getlogin
9
+
10
+ # Used to create relative symlinks in deploy.create_symlink
11
+ def relative_path(from_str, to_str)
12
+ require 'pathname'
13
+ Pathname.new(to_str).relative_path_from(Pathname.new(from_str)).to_s
14
+ end
15
+
16
+ # https://github.com/everzet/capifony/blob/c32d2ae118584d37e9051b6eeda0674ea420f824/lib/capifony.rb
17
+ def prompt_with_default(var, default, &block)
18
+ set(var) do
19
+ Capistrano::CLI.ui.ask("#{var} [#{default}] : ", &block)
20
+ end
21
+ set var, default if eval("#{var.to_s}.empty?")
22
+ end
23
+
24
+ desc <<-DESC
25
+ Connects via SSH to the first app server, and executes a `bash --login` \
26
+ to stay connected
27
+ DESC
28
+ task :connect, :roles => :app, :primary => true do
29
+ set :host, roles[:app].servers.first.host
30
+ set :port, ssh_options[:port] || roles[:web].servers.first.port || 22
31
+ set :user, Etc.getlogin unless exists?(:user)
32
+
33
+ cmd = "ssh -A #{user}@#{host} -p #{port} -t \"cd #{current_path};bash --login\""
34
+ exec(cmd)
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ require 'capistrano/chocopoche/files'
2
+
3
+ configuration = Capistrano::Configuration.respond_to?(:instance) ?
4
+ Capistrano::Configuration.instance(:must_exist) :
5
+ Capistrano.configuration(:must_exist)
6
+
7
+
8
+ configuration.load do
9
+
10
+ set :shared_children, shared_children + %w(vendor)
11
+ set :files_symlinks, files_symlinks + %w(vendor)
12
+
13
+ _cset :composer_cli_options, '--dev --quiet'
14
+
15
+ namespace :composer do
16
+
17
+ desc "Runs an arbitrary composer command"
18
+ task :default, :roles => :app do
19
+ prompt_with_default(:composer_task, "update")
20
+ run "cd #{latest_release} && php composer.phar #{composer_cli_options} " + composer_task
21
+ end
22
+
23
+ desc "Runs the composer update command"
24
+ task :update, :roles => :app do
25
+ install_composer
26
+ run "cd #{latest_release} && php composer.phar #{composer_cli_options} update"
27
+ end
28
+
29
+ desc "[internal] Installs composer"
30
+ task :install_composer, :roles => :app do
31
+ run "curl -s http://getcomposer.org/installer | php -- --install-dir=#{current_release}"
32
+ end
33
+ end
34
+
35
+ # before 'deploy:finalize_update', 'composer:update'
36
+ end
@@ -0,0 +1,102 @@
1
+ # see https://gist.github.com/111597
2
+ require 'etc'
3
+ require 'fileutils'
4
+ require 'capistrano/chocopoche/common'
5
+
6
+ configuration = Capistrano::Configuration.respond_to?(:instance) ?
7
+ Capistrano::Configuration.instance(:must_exist) :
8
+ Capistrano.configuration(:must_exist)
9
+
10
+ configuration.load do
11
+
12
+ _cset :user, Etc.getlogin
13
+ _cset :files_rsync, []
14
+ _cset :files_symlinks, []
15
+ _cset :files_tmp_dir, 'tmp/capistrano-chocopoche/files'
16
+ _cset :files_tpl, []
17
+ _cset :files_tpl_params, {}
18
+
19
+ namespace :files do
20
+
21
+ task :set_tpl_symlinks do
22
+ files_tpl.each do |t|
23
+ set :files_symlinks, files_symlinks + [t[:dest]]
24
+ end
25
+ end
26
+
27
+ desc "Creates files from templates and upload them to the app server."
28
+ task :upload_files_from_templates, :roles => :app do
29
+
30
+ server 'localhost', :app, :web, :db, :primary => true
31
+
32
+ set_files_tpl_params
33
+
34
+ files_tpl.each do |t|
35
+ params = fetch(:files_tpl_params)
36
+ template = File.read(t[:template])
37
+ buffer = ERB.new(template).result(binding)
38
+ parent = File.dirname("#{shared_path}/#{t[:dest]}")
39
+ run "mkdir -p #{parent}"
40
+ put buffer, "#{shared_path}/#{t[:dest]}", :mode => 0664
41
+ end
42
+ end
43
+
44
+ desc <<-DESC
45
+ Sync files from the first web server to the local temp directory. \
46
+ Files on the remote server must be somewhere in the shared directory.
47
+ DESC
48
+ task :download, :roles => :web, :only => { :primary => true }, :once => true do
49
+
50
+ host, port = host_and_port
51
+
52
+ # Sync each directory
53
+ files_rsync.each do |file_dir|
54
+ source = "#{user}@#{host}:#{shared_path}/#{file_dir}"
55
+ dest = File.dirname("#{files_tmp_dir}/#{file_dir}")
56
+ FileUtils.mkdir_p(dest)
57
+ system "rsync --archive --compress --copy-links --delete --stats --rsh='ssh -p #{port}' #{source} #{dest}"
58
+ logger.info "sync files from #{host}:#{source} to #{dest} finished"
59
+ end
60
+
61
+ end
62
+
63
+ desc <<-DESC
64
+ Sync files from the local temp directory to the first web server. \
65
+ Files on the remote server will be copied in the shared directory.
66
+ DESC
67
+ task :upload, :roles => :web, :only => { :primary => true }, :once => true do
68
+ host, port = host_and_port
69
+ files_rsync.each do |file_dir|
70
+ source = "#{files_tmp_dir}/#{file_dir}"
71
+ dest = "#{user}@#{host}:" + File.dirname("#{shared_path}/#{file_dir}")
72
+ system "rsync --archive --compress --keep-dirlinks --delete --stats --rsh='ssh -p #{port}' #{source} #{dest}"
73
+ logger.info "sync files from #{source} to #{host}:#{dest} finished"
74
+ end
75
+ end
76
+
77
+ desc <<-DESC
78
+ Creates :files_symlinks from the shared folder to the current one on the \
79
+ app servers. If a file or directory exists as the linkname, it will be \
80
+ removed.
81
+ DESC
82
+ task :create_symlinks, :roles => :app do
83
+ cmds = files_symlinks.collect do | l |
84
+ parent = File.dirname("#{latest_release}/#{l}")
85
+ "mkdir -p #{parent} && rm -rf #{latest_release}/#{l} && ln -nfs #{shared_path}/#{l} #{latest_release}/#{l}"
86
+ end
87
+ run cmds.join(' && ')
88
+ end
89
+
90
+ #
91
+ # Returns the actual host name to sync and port
92
+ #
93
+ def host_and_port
94
+ return roles[:web].servers.first.host, ssh_options[:port] || roles[:web].servers.first.port || 22
95
+ end
96
+
97
+ end
98
+
99
+ before 'deploy:finalize_update', 'files:create_symlinks'
100
+ before 'files:create_symlinks', 'files:set_tpl_symlinks'
101
+ after 'deploy:setup', 'files:upload_files_from_templates'
102
+ end
@@ -0,0 +1,190 @@
1
+ # see https://gist.github.com/111597
2
+ require 'fileutils'
3
+ require 'yaml'
4
+
5
+ # Converts deeply keys of a hash|array into symbols when they are strings
6
+ # @see http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash#answer-15815545
7
+ class Object
8
+ def deep_symbolize_keys
9
+ return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
10
+ return self.inject([]){|memo,v | memo << v.deep_symbolize_keys; memo} if self.is_a? Array
11
+ return self
12
+ end
13
+ end
14
+
15
+ configuration = Capistrano::Configuration.respond_to?(:instance) ?
16
+ Capistrano::Configuration.instance(:must_exist) :
17
+ Capistrano.configuration(:must_exist)
18
+
19
+ configuration.load do
20
+
21
+ _cset :mysql_backup_dir, "backup/mysql"
22
+ _cset :mysql_config_file, "config/parameters.yml"
23
+ _cset :mysql_yaml_key, :databases
24
+
25
+ namespace :mysql do
26
+
27
+ desc "Dump databases to remote backup folder."
28
+ task :dump, :roles => :db, :only => { :primary => true }, :once => true do
29
+ fetch_databases.each do |db,config|
30
+ db_backup_dir = "#{deploy_to}/#{mysql_backup_dir}/#{db}"
31
+ run "mkdir -p #{db_backup_dir}"
32
+
33
+ if exists? :stages
34
+ dump_filename = "%s/%s-%s-%d.sql" % [
35
+ db_backup_dir,
36
+ fetch(:stage),
37
+ config[:dbname],
38
+ Time.now.to_i
39
+ ]
40
+ else
41
+ dump_filename = "%s/%s-%d.sql" % [
42
+ db_backup_dir,
43
+ config[:dbname],
44
+ Time.now.to_i
45
+ ]
46
+ end
47
+
48
+ # Dumps the schema
49
+ cmd_schema = "mysqldump -h %s --default-character-set=utf8 --no-data -u%s -p %s > %s" % [
50
+ config[:host],
51
+ config[:user],
52
+ config[:dbname],
53
+ dump_filename
54
+ ]
55
+ begin
56
+ run cmd_schema do |ch, stream, out|
57
+ ch.send_data "#{config[:password]}\n" if out =~ /^Enter password:/
58
+ end
59
+ rescue Capistrano::CommandError => e
60
+ abort("Connection error, you may want to check database config.")
61
+ end
62
+
63
+ # Tables to ignore (cache, indexes, ...)
64
+ ignore_tables = ""
65
+ if config.has_key?(:capistrano) && config[:capistrano].has_key?(:ignore_tables)
66
+ # Retrieve tables to ignore and generate the mysqldump arguments
67
+ cmd_ignore_tables = "mysql -h %s --default-character-set=utf8 -u%s -p %s" % [
68
+ config[:host],
69
+ config[:user],
70
+ config[:dbname]
71
+ ]
72
+ cmd_ignore_tables += " -BNe 'show tables;'"
73
+ cmd_ignore_tables += " | grep -E '^#{config[:capistrano][:ignore_tables]}$'"
74
+ cmd_ignore_tables += " | xargs -I {} echo --ignore-table #{config[:dbname]}.{} "
75
+
76
+ run cmd_ignore_tables do |ch, stream, out|
77
+ if out =~ /^Enter password:/
78
+ ch.send_data "#{config[:password]}\n"
79
+ else
80
+ ignore_tables += " " + out.chop if out.match(/ignore-table/)
81
+ end
82
+ end
83
+ end
84
+
85
+ # Dumps the data
86
+ cmd_data = "mysqldump -h %s --default-character-set=utf8 -u%s -p %s %s >> %s" % [
87
+ config[:host],
88
+ config[:user],
89
+ ignore_tables,
90
+ config[:dbname],
91
+ dump_filename
92
+ ]
93
+
94
+ # Executes sql
95
+ run cmd_data do |ch, stream, out|
96
+ ch.send_data "#{config[:password]}\n" if out =~ /^Enter password:/
97
+ end
98
+
99
+ # Compress dump
100
+ run "bzip2 #{dump_filename}"
101
+ end
102
+ end
103
+
104
+ desc "Import last remote dumps to databases."
105
+ task :import, :roles => :db, :only => { :primary => true }, :once => true do
106
+ fetch_databases.each do |db,config|
107
+ remote_db_backup_dir = "#{deploy_to}/#{mysql_backup_dir}/#{db}"
108
+ last_dump = capture("ls -xt #{remote_db_backup_dir}").split.reverse.last
109
+
110
+ # cmd = "mysql -u%s -p\"%s\" %s < %s/%s" % [
111
+ # config[:user],
112
+ # config[:password],
113
+ # config[:dbname],
114
+ # remote_db_backup_dir,
115
+ # last_dump
116
+ # ]
117
+ # run cmd do |ch, stream, out|
118
+ # ch.send_data "#{config[:password]}\n" if out =~ /^Enter password:/
119
+ # end
120
+
121
+ run "bzcat #{remote_db_backup_dir}/#{last_dump} | mysql -u%s -p\"%s\" %s" % [
122
+ config[:user],
123
+ config[:password],
124
+ config[:dbname]
125
+ ]
126
+ end
127
+ end
128
+
129
+ desc "Download last remote dumps of each databases."
130
+ task :download, :roles => :db, :only => { :primary => true }, :once => true do
131
+ fetch_databases.each do |db,config|
132
+ local_db_backup_dir = "#{mysql_backup_dir}/#{db}"
133
+ remote_db_backup_dir = "#{deploy_to}/#{mysql_backup_dir}/#{db}"
134
+
135
+ # Don't know how to avoid the command error if the remote_db_backup_dir
136
+ # does not exist, other than this way
137
+ begin
138
+ dumps = capture("test -d #{remote_db_backup_dir} && ls -xt #{remote_db_backup_dir}").split.reverse
139
+ rescue Capistrano::CommandError
140
+ logger.important "No dump found!"
141
+ end
142
+
143
+ if dumps
144
+ FileUtils.mkdir_p(local_db_backup_dir)
145
+ hostname = capture("hostname -f").chop
146
+ file_name = "#{local_db_backup_dir}/#{hostname}-#{dumps.last}"
147
+ top.get("#{remote_db_backup_dir}/#{dumps.last}", file_name)
148
+ end
149
+ end
150
+ end
151
+
152
+ desc "Upload last local dump of each databases."
153
+ task :upload, :roles => :db, :only => { :primary => true }, :once => true do
154
+
155
+ databases = fetch_databases
156
+
157
+ databases.each do |db,config|
158
+
159
+ local_db_backup_dir = "#{mysql_backup_dir}/#{db}"
160
+ remote_db_backup_dir = "#{deploy_to}/#{mysql_backup_dir}/#{db}"
161
+
162
+ if File.directory?(local_db_backup_dir) && last_dump = `ls -xt #{local_db_backup_dir}`.split.reverse.last
163
+ run "mkdir -p #{remote_db_backup_dir}"
164
+ top.upload("#{local_db_backup_dir}/#{last_dump}", "#{remote_db_backup_dir}/#{last_dump}")
165
+ logger.info "#{last_dump} uploaded."
166
+ else
167
+ logger.important 'No dump found!'
168
+ end
169
+ end
170
+ end
171
+
172
+ desc "[internal] Parses the config/databases.yml and returns compatible ones."
173
+ task :fetch_databases, :roles => :db, :only => { :primary => true }, :once => true do
174
+ location = "#{latest_release}/#{mysql_config_file}"
175
+
176
+ FileUtils.mkdir_p("config/deploy/tmp")
177
+ top.get(location, "config/deploy/tmp/parameters.yml")
178
+ config = YAML.load_file('config/deploy/tmp/parameters.yml').deep_symbolize_keys
179
+ FileUtils.rm("config/deploy/tmp/parameters.yml")
180
+
181
+ databases = config[mysql_yaml_key]
182
+ abort("No database found in #{mysql_config_file}") if databases.nil? || databases.length < 1
183
+ databases.delete_if { |key,value| value[:driver] != 'pdo_mysql' }
184
+ abort("No compatible database found in #{mysql_config_file}") if databases.length < 1
185
+
186
+ databases
187
+ end
188
+ end
189
+
190
+ end
@@ -1,5 +1,4 @@
1
- require 'etc'
2
- require 'capistrano-chocopoche/common'
1
+ require 'capistrano/chocopoche/common'
3
2
 
4
3
  configuration = Capistrano::Configuration.respond_to?(:instance) ?
5
4
  Capistrano::Configuration.instance(:must_exist) :
@@ -9,9 +8,9 @@ configuration = Capistrano::Configuration.respond_to?(:instance) ?
9
8
  configuration.load do
10
9
 
11
10
  # Remove rails specific tasks
11
+ deploy.tasks.delete(:cold)
12
12
  deploy.tasks.delete(:migrate)
13
13
  deploy.tasks.delete(:migrations)
14
- deploy.tasks.delete(:cold)
15
14
 
16
15
  # Remove rails specific vars
17
16
  unset :rails_env
@@ -26,9 +25,6 @@ configuration.load do
26
25
  set(:deploy_to) { "/home/#{user}/apps/#{application}" }
27
26
  end
28
27
 
29
- # Remove rails specific shared children
30
- # set :shared_children, []
31
-
32
28
  namespace :deploy do
33
29
 
34
30
  desc <<-DESC
@@ -44,34 +40,21 @@ configuration.load do
44
40
  on_rollback do
45
41
  if previous_release
46
42
  previous_release_relative = relative_path(deploy_to, previous_release)
47
- run "ln -s #{previous_release_relative} #{current_path}.tmp && mv -f #{current_path}.tmp #{current_path}"
43
+ run "ln -nfs #{previous_release_relative} #{current_path}"
48
44
  else
49
45
  logger.important "no previous release to rollback to, rollback of symlink skipped"
50
46
  end
51
47
  end
52
48
  latest_release_relative = relative_path(deploy_to,latest_release)
53
- run "ln -s #{latest_release_relative} #{current_path}.tmp && mv -f #{current_path}.tmp #{current_path}"
49
+ run "ln -nfs #{latest_release_relative} #{current_path}"
54
50
  end
55
51
 
56
52
  desc <<-DESC
57
- [internal] Touches up the released code. This is called by update_code \
58
- after the basic deploy finishes. It assumes a Rails project was deployed, \
59
- so if you are deploying something else, you may want to override this \
60
- task with your own environment's requirements.
61
-
62
- This task will make the release group-writable (if the :group_writable \
63
- variable is set to true, which is the default). It will then set up \
64
- symlinks to the shared directory for the log, system, and tmp/pids \
65
- directories, and will lastly touch all assets in public/images, \
66
- public/stylesheets, and public/javascripts so that the times are \
67
- consistent (so that asset timestamping works). This touch process \
68
- is only carried out if the :normalize_asset_timestamps variable is \
69
- set to true, which is the default The asset directories can be overridden \
70
- using the :public_children variable.
53
+ [internal] This is called by update_code after the basic deploy \
54
+ finishes.
71
55
  DESC
72
56
  task :finalize_update, :except => { :no_release => true } do
73
57
  end
74
-
75
58
  end
76
59
 
77
60
  namespace :rollback do
@@ -83,7 +66,7 @@ configuration.load do
83
66
  task :revision, :except => { :no_release => true } do
84
67
  if previous_release
85
68
  previous_release_relative = relative_path(deploy_to, previous_release)
86
- run "ln -s #{previous_release_relative} #{current_path}.tmp && mv -f #{current_path}.tmp #{current_path}"
69
+ run "ln -nfs #{previous_release_relative} #{current_path}"
87
70
  else
88
71
  abort "could not rollback the code because there is no prior release"
89
72
  end
@@ -0,0 +1,67 @@
1
+ require 'capistrano/cli'
2
+
3
+ module Capistrano
4
+ module Chocopoche
5
+ class SyncCLI
6
+ def prompt(default, *args)
7
+ print(*args)
8
+ result = STDIN.gets.strip
9
+ return result.empty? ? default : result
10
+ end
11
+
12
+ def ensure_stages
13
+ show_error("The DATATYPE is missing.") if @args[0].nil?
14
+ show_error("The DATATYPE is incorrect.") unless @args[0] =~ /^files|mysql$/
15
+ show_error("The SOURCE is missing.") if @args[1].nil?
16
+ show_error("The DEST is missing.") if @args[2].nil?
17
+ @data = @args[0]
18
+ @source = @args[1]
19
+ @dest = @args[2]
20
+ end
21
+
22
+ def ensure_confirmation
23
+ unless @options[:no_confirmation] == false
24
+ confirm = prompt('y', "Sync from #{@source} to #{@dest}? [Y/n] ").downcase
25
+ unless confirm == "y"
26
+ abort("Cancelled")
27
+ end
28
+ end
29
+ end
30
+
31
+ def show_error(msg)
32
+ puts @parser.help
33
+ abort("\n[ERROR] #{msg}")
34
+ end
35
+
36
+ def initialize(args)
37
+ @args = args
38
+ @options = {}
39
+ @parser = OptionParser.new do |opts|
40
+ opts.banner = "Usage: files-sync [options] DATATYPE SOURCE DEST \n"
41
+ opts.banner += "Sync DATATYPE from the SOURCE stage to the DEST stage.\n"
42
+ opts.on("-y", "--no-confirmation", "Don't prompt for confirmation") do |v|
43
+ @options[:no_confirmation] = v
44
+ end
45
+
46
+ opts.on("--no-backup", "Dont backup databases before importing") do |v|
47
+ @options[:no_backup] = v
48
+ end
49
+ end
50
+ end
51
+
52
+ def execute
53
+ @parser.parse!
54
+ ensure_stages
55
+ ensure_confirmation
56
+ if @data == 'mysql'
57
+ Capistrano::CLI.parse([@dest, "mysql:dump"]).execute! unless @options[:no_backup]
58
+ Capistrano::CLI.parse([@source, "mysql:dump", "mysql:download"]).execute!
59
+ Capistrano::CLI.parse([@dest, "mysql:upload", "mysql:import"]).execute!
60
+ elsif @data == 'files'
61
+ Capistrano::CLI.parse([@source, "files:download"]).execute!
62
+ Capistrano::CLI.parse([@dest, "files:upload"]).execute!
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ module Capistrano
2
+ module Chocopoche
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano-chocopoche
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Corentin Merot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-09 00:00:00.000000000 Z
11
+ date: 2013-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capistrano
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.14'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.14'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,10 +38,11 @@ dependencies:
24
38
  - - ~>
25
39
  - !ruby/object:Gem::Version
26
40
  version: '1.3'
27
- description: Capistrano recipes, with another railsless-deploy and a files utility.
41
+ description: Capistrano recipes and bin for mysql, rsync, php composer.
28
42
  email:
29
43
  - cmerot@themarqueeblink.com
30
- executables: []
44
+ executables:
45
+ - csync
31
46
  extensions: []
32
47
  extra_rdoc_files: []
33
48
  files:
@@ -35,10 +50,16 @@ files:
35
50
  - Gemfile
36
51
  - LICENSE.txt
37
52
  - README.md
53
+ - bin/csync
38
54
  - capistrano-chocopoche.gemspec
39
- - lib/capistrano-chocopoche/common.rb
40
- - lib/capistrano-chocopoche/files.rb
41
- - lib/capistrano-chocopoche/railsless-deploy.rb
55
+ - lib/capistrano/chocopoche.rb
56
+ - lib/capistrano/chocopoche/common.rb
57
+ - lib/capistrano/chocopoche/composer.rb
58
+ - lib/capistrano/chocopoche/files.rb
59
+ - lib/capistrano/chocopoche/mysql.rb
60
+ - lib/capistrano/chocopoche/railsless-deploy.rb
61
+ - lib/capistrano/chocopoche/sync-cli.rb
62
+ - lib/capistrano/chocopoche/version.rb
42
63
  homepage: https://github.com/chocopoche/capistrano-chocopoche
43
64
  licenses:
44
65
  - MIT
@@ -49,12 +70,12 @@ require_paths:
49
70
  - lib
50
71
  required_ruby_version: !ruby/object:Gem::Requirement
51
72
  requirements:
52
- - - ! '>='
73
+ - - '>='
53
74
  - !ruby/object:Gem::Version
54
75
  version: '0'
55
76
  required_rubygems_version: !ruby/object:Gem::Requirement
56
77
  requirements:
57
- - - ! '>='
78
+ - - '>='
58
79
  - !ruby/object:Gem::Version
59
80
  version: '0'
60
81
  requirements: []
@@ -64,4 +85,3 @@ signing_key:
64
85
  specification_version: 4
65
86
  summary: Chocopoche's Capistrano recipes
66
87
  test_files: []
67
- has_rdoc:
@@ -1,16 +0,0 @@
1
- configuration = Capistrano::Configuration.respond_to?(:instance) ?
2
- Capistrano::Configuration.instance(:must_exist) :
3
- Capistrano.configuration(:must_exist)
4
-
5
-
6
- configuration.load do
7
-
8
- _cset :user, Etc.getlogin
9
-
10
- # Used to create relative symlinks in deploy.create_symlink
11
- def relative_path(from_str, to_str)
12
- require 'pathname'
13
- Pathname.new(to_str).relative_path_from(Pathname.new(from_str)).to_s
14
- end
15
-
16
- end
@@ -1,83 +0,0 @@
1
- # Capistrano recipe to rsync files up and down.
2
- #
3
- # author: Corentin Merot
4
- # real author: Michael Kessler aka netzpirat, see https://gist.github.com/111597
5
-
6
- require 'fileutils'
7
- require 'capistrano-chocopoche/common'
8
-
9
- configuration = Capistrano::Configuration.respond_to?(:instance) ?
10
- Capistrano::Configuration.instance(:must_exist) :
11
- Capistrano.configuration(:must_exist)
12
-
13
- configuration.load do
14
-
15
- _cset :user, Etc.getlogin
16
- _cset :files_directories, []
17
- _cset :files_tmp_dir, 'tmp/capistrano-chocopoche/files'
18
-
19
- namespace :files do
20
-
21
- desc <<-DESC
22
- Sync files from the first web server to the local temp directory. \
23
- Files on the remote server must be somewhere in the shared directory.
24
- DESC
25
- task :download, :roles => :web, :only => { :primary => true }, :once => true do
26
-
27
- host, port = host_and_port
28
-
29
- Array(fetch(:files_directories, [])).each do |file_dir|
30
- unless File.directory? "#{files_tmp_dir}/#{file_dir}"
31
- logger.info "create temporary '#{files_tmp_dir}/#{file_dir}' folder"
32
- FileUtils.mkdir_p "#{files_tmp_dir}/#{file_dir}"
33
- end
34
-
35
- source = "#{shared_path}/#{file_dir}"
36
- dest = File.dirname("#{files_tmp_dir}/#{file_dir}")
37
-
38
- # Sync directory down
39
- system "rsync --verbose --archive --compress --copy-links --delete --stats --rsh='ssh -p #{port}' #{user}@#{host}:#{source} #{dest}"
40
- logger.info "sync files from #{host}:#{source} to #{dest} finished"
41
- end
42
-
43
- end
44
-
45
- desc <<-DESC
46
- Sync files from the local temp directory to the first web server. \
47
- Files on the remote server will be copied in the shared directory.
48
- DESC
49
- task :upload, :roles => :web, :only => { :primary => true }, :once => true do
50
-
51
- host, port = host_and_port
52
- Array(fetch(:files_directories, [])).each do |file_dir|
53
- source = "#{files_tmp_dir}/#{file_dir}"
54
- dest = File.dirname("#{shared_path}/#{file_dir}")
55
-
56
- # Sync directory up
57
- system "rsync --verbose --archive --compress --keep-dirlinks --delete --stats --rsh='ssh -p #{port}' #{source} #{user}@#{host}:#{dest}"
58
- logger.info "sync files from #{source} to #{host}:#{dest} finished"
59
- end
60
- end
61
-
62
- desc <<-DESC
63
- Creates :files_symlinks from the shared folder to the current one on the \
64
- web servers
65
- DESC
66
- task :create_symlinks, :roles => [:web] do
67
- symlinks = fetch(:files_symlinks)
68
- cmds = symlinks.collect do | l |
69
- parent = File.dirname("#{current_path}/#{l}")
70
- "mkdir -p #{parent} && ln -fs #{shared_path}/#{l} #{current_path}/#{l}"
71
- end
72
- run cmds.join(' && ')
73
- end
74
-
75
- #
76
- # Returns the actual host name to sync and port
77
- #
78
- def host_and_port
79
- return roles[:web].servers.first.host, ssh_options[:port] || roles[:web].servers.first.port || 22
80
- end
81
-
82
- end
83
- end