capistrano-patch 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ .bundle
3
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.8.7
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ capistrano-patch (0.0.1)
5
+ capistrano (>= 2.5.5)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ capistrano (2.9.0)
11
+ highline
12
+ net-scp (>= 1.0.0)
13
+ net-sftp (>= 2.0.0)
14
+ net-ssh (>= 2.0.14)
15
+ net-ssh-gateway (>= 1.1.0)
16
+ highline (1.6.2)
17
+ net-scp (1.0.4)
18
+ net-ssh (>= 1.99.1)
19
+ net-sftp (2.0.5)
20
+ net-ssh (>= 2.0.9)
21
+ net-ssh (2.2.1)
22
+ net-ssh-gateway (1.1.0)
23
+ net-ssh (>= 1.99.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ capistrano-patch!
@@ -0,0 +1,162 @@
1
+ # Capistrano patch recipes
2
+
3
+ ## Concept
4
+
5
+ Usual deployment procedure is pretty good.
6
+ But real project are big and offen have several megabytes of code. So standard deployment is not so fast.
7
+ You need chekout new code, prepare all symplinks, etc ... It takes the time.
8
+
9
+ Sometimes you need to *quickly* apply hostfix that fixes critical bug.
10
+ Usually such fix is small and may contain only several lines of code.
11
+ Doing it manual it's not good from any point of view.
12
+
13
+ So that's why *capistrano patch recipes* appears. They are extracted from real big project.
14
+
15
+ It's automated solution for safely code patching on any stage.
16
+
17
+ Depending on patch strategy `cap patch` :
18
+
19
+ * creates patch file
20
+ * delivers patch file to all application servers
21
+ * checks patch
22
+ * applies patch
23
+ * updates REVISION
24
+
25
+ If something went wrong your also able to quickly revert patch with `cap patch:revert`. See recipes below.
26
+
27
+
28
+ ## Installation
29
+
30
+ gem install capistrano-patch
31
+
32
+ Add to `Capfile`
33
+
34
+ # load standard capistrano recipes
35
+ load 'deploy'
36
+
37
+ # load patch recipes
38
+ require 'capistrano/patch'
39
+
40
+ ## Configuration
41
+
42
+ * *:patch_via* - patch strategy (default `:git`)
43
+ * *:patch_repository* - SCM repository that will be using for generating diffs (default `.git`)
44
+ * *:patch_directory* - directory for storing generated patches (default `.`)
45
+ * *:patch_base_url* - base url for patch download ( default `nil`)
46
+ * *:patch_server_host* - server host for patch generating ( default `nil`)
47
+ * *:patch_server_options* -server host options ( default `{}`)
48
+
49
+ ## Patch strategies
50
+
51
+ Patch strategy is about how to process code patching on stage. It also depends on patch source (or scm).
52
+
53
+ Currently implemented patch strategies:
54
+
55
+ * git
56
+ * git_server
57
+
58
+ ## Git strategy
59
+
60
+ It's simple strategy that is suitable for almost all git projects.
61
+
62
+ ### Integrated deployment
63
+
64
+ When you use capistrano directly into your application you need zero configuration.
65
+
66
+ ### Separated deployment
67
+
68
+ When you have separated deployment repository you need to specify patch repository and directory.
69
+
70
+ Example:
71
+
72
+ set(:patch_repository) { "../#{application}.git" }
73
+ set(:patch_directory) { "/tmp/patches/#{application}" }
74
+
75
+ ## GitServer strategy
76
+
77
+ For big projects offen is required to generate patch using some certain or origin repository and store generated patch in some directory there. Thus patch can be accessible from directory server via http and delivered to application servers from the same host where repository is located.
78
+
79
+ Example:
80
+
81
+ set :patch_via, :git_server
82
+
83
+ set(:patch_repository) { "/var/git/repositories/#{application}.git" }
84
+ set(:patch_directory) { "/var/www/patches/#{application}" }
85
+ set(:patch_base_url) { "http://scm.example.com/patches/#{application}" }
86
+
87
+ set :patch_server_host, "scm.example.com"
88
+
89
+ ### Extra options
90
+
91
+ Patch server is not in global capistrano servers list.
92
+ So any other task will not affect your server. And this is good.
93
+ But offen you need to specify different options to patch server instead of global values.
94
+
95
+ When you want to specify just another `user` or `port` you can do it in `patch_server_host` variable:
96
+
97
+ set :patch_server_host, "USER@scm.example.com:PORT"
98
+
99
+ or using `patch_server_options` variable:
100
+
101
+ set :patch_server_options, {
102
+ :user => 'patcher',
103
+ :port => 2222
104
+ }
105
+
106
+ Also you are able to specify another ssh keys:
107
+
108
+ set :patch_server_options, {
109
+ :ssh_options => { :auth_methods => %w(publickey), :keys => %w(keys/patcher.pem) }
110
+ }
111
+
112
+ ## Recipes
113
+
114
+ ### Create deliver and apply patch
115
+
116
+ $ cap patch FROM=v0.0.1 TO=v0.0.2
117
+
118
+ or
119
+
120
+ $ cap patch PATCH=v0.0.1-v0.0.2
121
+
122
+ or
123
+
124
+ $ cap patch FROM=development TO=master
125
+
126
+
127
+ * It will create patch file like `sha1-sha2.patch`
128
+ * It will deliver patch file to all application servers
129
+ * It will check patch before applying
130
+ * It will apply check
131
+ * It will update REVISION after applying
132
+
133
+
134
+ ### Create patch
135
+
136
+ $ cap patch:create FROM=v0.0.1 TO=v0.0.2
137
+
138
+ Create file like: `6c5923863a288b089a6a2bc11e560f0d28dabfb6-92e018dbda8a91c442e40257b81097ceeb150876.patch`
139
+
140
+ ### Deliver patch
141
+
142
+ If patch file is missing on some server you can manualy deliver it with
143
+
144
+ $ cap patch:deliver FROM=v0.0.1 TO=v0.0.2 HOSTFILTER='app1'
145
+
146
+ ### Apply patch
147
+
148
+ If may manualy apply patch on some server:
149
+
150
+ $ cap patch:apply FROM=v0.0.1 TO=v0.0.2 HOSTFILTER='app1'
151
+
152
+ ### Revert patch
153
+
154
+
155
+ If something went bad you may revert patch
156
+
157
+ $ cap patch:revert FROM=v0.0.1 TO=v0.0.2
158
+ # or
159
+ $ cap patch:revert PATCH=v0.0.1-v0.0.2
160
+ # or
161
+ $ cap patch:revert PATCH=6c5923863a288b089a6a2bc11e560f0d28dabfb6-92e018dbda8a91c442e40257b81097ceeb150876.patch
162
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "capistrano/patch/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "capistrano-patch"
8
+ s.version = Capistrano::Patch::VERSION
9
+ s.authors = ["Andriy Yanko"]
10
+ s.email = ["andriy.yanko@gmail.com"]
11
+ s.homepage = "https://github.com/railsware/capistrano-patch"
12
+ s.summary = %q{Capistrano patch recipes}
13
+
14
+ s.rubyforge_project = "capistrano-patch"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency "capistrano", ">=2.5.5"
22
+ end
@@ -0,0 +1,44 @@
1
+ require 'capistrano/patch/strategy'
2
+
3
+ Capistrano::Configuration.instance.load do
4
+
5
+ set(:patch_strategy) { Capistrano::Patch::Strategy.new(fetch(:patch_via, scm), self) }
6
+
7
+ namespace :patch do
8
+
9
+ desc "Create, deliver and apply patch"
10
+ task :default, :except => { :no_release => true } do
11
+ top.patch.create
12
+ top.patch.deliver
13
+ top.patch.apply
14
+ end
15
+
16
+ desc "Create patch"
17
+ task :create, :except => { :no_release => true } do
18
+ patch_strategy.create
19
+ Capistrano::CLI.ui.say "Patch location: #{patch_strategy.patch_location}"
20
+ abort unless Capistrano::CLI.ui.ask("Is created patch ok? (y/n)") == 'y'
21
+ end
22
+
23
+ desc "Deliver patch"
24
+ task :deliver, :except => { :no_release => true } do
25
+ patch_strategy.deliver
26
+ end
27
+
28
+ desc "Apply patch"
29
+ task :apply, :except => { :no_release => true } do
30
+ abort unless Capistrano::CLI.ui.ask("Apply #{patch_strategy.patch_file} ? (y/n)") == 'y'
31
+ patch_strategy.check_apply
32
+ patch_strategy.apply
33
+ patch_strategy.mark_apply
34
+ end
35
+
36
+ desc "Revert patch"
37
+ task :revert, :except => { :no_release => true } do
38
+ abort unless Capistrano::CLI.ui.ask("Revert #{patch_strategy.patch_file} ? (y/n)") == 'y'
39
+ patch_strategy.check_revert
40
+ patch_strategy.revert
41
+ patch_strategy.mark_revert
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ module Capistrano
2
+ module Patch
3
+ module Strategy
4
+
5
+ def self.new(strategy, config = {})
6
+ require "capistrano/patch/strategy/#{strategy}"
7
+
8
+ strategy_const = strategy.to_s.capitalize.gsub(/_(.)/) { $1.upcase }
9
+
10
+ const_get(strategy_const).new(config)
11
+
12
+ rescue LoadError
13
+ raise Capistrano::Error, "could not find any strategy named `#{strategy}'"
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,94 @@
1
+ require 'capistrano/recipes/deploy/strategy/base'
2
+
3
+ module Capistrano
4
+ module Patch
5
+ module Strategy
6
+ class Base < Deploy::Strategy::Base
7
+
8
+ def initialize(*)
9
+ super
10
+
11
+ if ENV['PATCH']
12
+ ENV['FROM'], ENV['TO'] = File.basename(ENV['PATCH'], '.patch').split('-', 2)
13
+ end
14
+
15
+ ENV['FROM'] or abort "Please specify FROM revision environment variable"
16
+ ENV['TO'] or abort "Please specify TO revision environment variable"
17
+
18
+ @revision_from = normalize_revision(ENV['FROM'])
19
+ @revision_to = normalize_revision(ENV['TO'])
20
+ end
21
+
22
+ #
23
+ # Configuration
24
+ #
25
+
26
+ attr_reader :revision_from, :revision_to
27
+
28
+ def patch_file
29
+ @patch_file ||= "#{revision_from}-#{revision_to}.patch"
30
+ end
31
+
32
+ def patch_directory
33
+ raise NotImplementedError, "`patch_directory' is not implemented by #{self.class.name}"
34
+ end
35
+
36
+ def patch_path
37
+ File.join patch_directory, patch_file
38
+ end
39
+
40
+ def patch_location
41
+ raise NotImplementedError, "`patch_location' is not implemented by #{self.class.name}"
42
+ end
43
+
44
+ #
45
+ # Actions
46
+ #
47
+
48
+ def create
49
+ raise NotImplementedError, "`create' is not implemented by #{self.class.name}"
50
+ end
51
+
52
+ def deliver
53
+ raise NotImplementedError, "`deliver' is not implemented by #{self.class.name}"
54
+ end
55
+
56
+ def check_apply
57
+ raise NotImplementedError, "`check_apply' is not implemented by #{self.class.name}"
58
+ end
59
+
60
+ def check_revert
61
+ raise NotImplementedError, "`check_revert' is not implemented by #{self.class.name}"
62
+ end
63
+
64
+ def apply
65
+ raise NotImplementedError, "`apply' is not implemented by #{self.class.name}"
66
+ end
67
+
68
+ def revert
69
+ raise NotImplementedError, "`revert' is not implemented by #{self.class.name}"
70
+ end
71
+
72
+ def mark_apply
73
+ mark(revision_to)
74
+ end
75
+
76
+ def mark_revert
77
+ mark(revision_from)
78
+ end
79
+
80
+ protected
81
+
82
+ def normalize_revision(revision)
83
+ source.local.query_revision(revision) { |cmd| run_locally(cmd) }
84
+ end
85
+
86
+ def mark(revision)
87
+ command = "echo #{revision} > #{configuration.current_path}/REVISION"
88
+ run(command)
89
+ end
90
+
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,57 @@
1
+ require 'capistrano/patch/strategy/base'
2
+
3
+ module Capistrano
4
+ module Patch
5
+ module Strategy
6
+ class Git < Base
7
+
8
+ def patch_repository
9
+ configuration.fetch(:patch_repository, '.git')
10
+ end
11
+
12
+ def patch_directory
13
+ configuration.fetch(:patch_directory, '.')
14
+ end
15
+
16
+ def patch_location
17
+ patch_path
18
+ end
19
+
20
+ def create
21
+ command = "env GIT_DIR=#{patch_repository} "
22
+ command << source.local.scm('diff-tree', "--binary #{revision_from}..#{revision_to} > #{patch_path}")
23
+ system(command)
24
+ end
25
+
26
+ def deliver
27
+ top.upload patch_path, "#{configuration.current_path}/#{patch_file}"
28
+ end
29
+
30
+ def check_apply
31
+ command = "cd #{configuration.current_path} && "
32
+ command << source.scm('apply', "--binary --check #{patch_file}")
33
+ run(command)
34
+ end
35
+
36
+ def check_revert
37
+ command = "cd #{configuration.current_path} && "
38
+ command << source.scm('apply', "-R --binary --check #{patch_file}")
39
+ run(command)
40
+ end
41
+
42
+ def apply
43
+ command = "cd #{configuration.current_path} && "
44
+ command << source.scm('apply', "--binary #{patch_file}")
45
+ run(command)
46
+ end
47
+
48
+ def revert
49
+ command = "cd #{configuration.current_path} && "
50
+ command << source.scm('apply', "-R --binary #{patch_file}")
51
+ run(command)
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ require 'capistrano/patch/strategy/git'
2
+
3
+ module Capistrano
4
+ module Patch
5
+ module Strategy
6
+ class GitServer < Git
7
+
8
+ def patch_location
9
+ File.join configuration.fetch(:patch_base_url), patch_file
10
+ end
11
+
12
+ def create
13
+ command = "mkdir -p #{patch_directory} && "
14
+ command << source.scm('diff-tree', "--binary #{revision_from}..#{revision_to} > #{patch_path}")
15
+
16
+ run(command, {
17
+ :hosts => [server],
18
+ :env => { 'GIT_DIR' => patch_repository }
19
+ })
20
+ end
21
+
22
+ def deliver
23
+ command = "cd #{current_path} && "
24
+ command << "wget -N -q #{patch_location}"
25
+ run(command)
26
+ end
27
+
28
+ protected
29
+
30
+ def server
31
+ ServerDefinition.new(
32
+ configuration.fetch(:patch_server_host),
33
+ configuration.fetch(:patch_server_options, {})
34
+ )
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ module Capistrano
2
+ module Patch
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capistrano-patch
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Andriy Yanko
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-10-22 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: capistrano
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 17
29
+ segments:
30
+ - 2
31
+ - 5
32
+ - 5
33
+ version: 2.5.5
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ description:
37
+ email:
38
+ - andriy.yanko@gmail.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - .gitignore
47
+ - .rvmrc
48
+ - Gemfile
49
+ - Gemfile.lock
50
+ - README.md
51
+ - Rakefile
52
+ - capistrano-patch.gemspec
53
+ - lib/capistrano/patch.rb
54
+ - lib/capistrano/patch/strategy.rb
55
+ - lib/capistrano/patch/strategy/base.rb
56
+ - lib/capistrano/patch/strategy/git.rb
57
+ - lib/capistrano/patch/strategy/git_server.rb
58
+ - lib/capistrano/patch/version.rb
59
+ homepage: https://github.com/railsware/capistrano-patch
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options: []
64
+
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements: []
86
+
87
+ rubyforge_project: capistrano-patch
88
+ rubygems_version: 1.8.6
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Capistrano patch recipes
92
+ test_files: []
93
+