capistrano-campout 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +33 -0
- data/README.md +98 -0
- data/Rakefile +2 -0
- data/capistrano-campout.gemspec +25 -0
- data/lib/capistrano-campout/cap_tasks.rb +50 -0
- data/lib/capistrano-campout/core.rb +202 -0
- data/lib/capistrano-campout/deep_merge.rb +216 -0
- data/lib/capistrano-campout/defaults.yml +6 -0
- data/lib/capistrano-campout/git_utils.rb +74 -0
- data/lib/capistrano-campout/logger.rb +127 -0
- data/lib/capistrano-campout/options.rb +149 -0
- data/lib/capistrano-campout/recipes/campout.rb +49 -0
- data/lib/capistrano-campout/recipes/deploy.rb +20 -0
- data/lib/capistrano-campout/version.rb +5 -0
- data/lib/capistrano-campout.rb +39 -0
- metadata +100 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Copyright (c) 2012 Jason Adam Young
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
Additional Copyrights
|
25
|
+
=====================
|
26
|
+
|
27
|
+
This code also contains code from the following people:
|
28
|
+
|
29
|
+
rails_config : Copyright (c) 2010 Jacques Crocker
|
30
|
+
deep_merge : Copyright (c) 2008 Steve Midgley, now mainted by Daniel DeLeo
|
31
|
+
eycap : Copyright (c) 2008-2011 Engine Yard
|
32
|
+
capfire : Copyright (c) 2010 Piet Jaspers 10to1
|
33
|
+
capistrano-mountaintop : Copyright (c) 2010 Joshua Nichols.
|
data/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Capistrano::Campout
|
2
|
+
|
3
|
+
Capistrano::Campout is a gem extension to capistrano to post/speak messages
|
4
|
+
and paste logs from a capistrano deployment to a campfire room. Settings are
|
5
|
+
configurable in a "config/campout.yml" and/or "config/campout.local.yml"
|
6
|
+
file. ERB templates are supported for messages and can use all of the
|
7
|
+
capistrano settings.
|
8
|
+
|
9
|
+
Capistrano::Campout will a speak messages at pre-deployment and
|
10
|
+
post-deployment success or failure. Event sounds are also supported.
|
11
|
+
|
12
|
+
## Design goals
|
13
|
+
|
14
|
+
Capistrano::Campout is insipred by and borrows concepts (and in some cases code)
|
15
|
+
from two projects: [capistrano-mountaintop](https://github.com/technicalpickles/capistrano-mountaintop) and [capfire](https://github.com/pjaspers/capfire).
|
16
|
+
|
17
|
+
I created my own instead of forking either for the following reasons
|
18
|
+
|
19
|
+
* I wanted to use Tinder (Capfire uses broach)
|
20
|
+
* I wanted the necessary functionality built in instead of using additional gems (mountaintop depends on capistrano-campfire and capistrano-log_with_awesome)
|
21
|
+
* I wanted configuration-file based settings that I can separate into shared (checked into git) and local (git ignored) files
|
22
|
+
* I wanted room to expand for my team's needs for utilities for git and github inspection
|
23
|
+
* Because I could.
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
Add this line to your application's Gemfile:
|
28
|
+
|
29
|
+
gem 'capistrano-campout'
|
30
|
+
|
31
|
+
And then execute:
|
32
|
+
|
33
|
+
$ bundle
|
34
|
+
|
35
|
+
Or install it yourself as:
|
36
|
+
|
37
|
+
$ gem install capistrano-campout
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
Add a:
|
42
|
+
|
43
|
+
require "capistrano-campout"
|
44
|
+
|
45
|
+
to your capistrano deploy.rb and create a config/campout.yml and/or a config/campout.local.yml with your campfire settings (required)
|
46
|
+
|
47
|
+
campfire:
|
48
|
+
domain: 'your_campfire_domain'
|
49
|
+
room: room_id_to_post_to
|
50
|
+
token: your_campfire_api_token
|
51
|
+
|
52
|
+
"config/campout.yml" is meant to be a pre-project file and can contain global settings for everyone. Don't put the api token in a campout.yml file that's part of a public git repository
|
53
|
+
|
54
|
+
"config/campout.local.yml" is meant as a local/private configuration file - I'd recommend adding the file to the .gitignore
|
55
|
+
|
56
|
+
## Additional Settings
|
57
|
+
|
58
|
+
I'll write those up one day. Until that day, use the source, Luke.
|
59
|
+
|
60
|
+
## Known Limitations
|
61
|
+
|
62
|
+
### Deploy:Cleanup task
|
63
|
+
|
64
|
+
Capistrano::Campout requires [Tinder](https://github.com/collectiveidea/tinder) - which requires [ActiveSupport](https://github.com/rails/rails/tree/master/activesupport)
|
65
|
+
|
66
|
+
Apparently, Capistrano's "deploy:cleanup" task breaks due to conflicts between Capistrano and ActiveSupport
|
67
|
+
|
68
|
+
See:
|
69
|
+
|
70
|
+
* [Issue #169](https://github.com/capistrano/capistrano/issues/169)
|
71
|
+
* [Issue #170](https://github.com/capistrano/capistrano/issues/170)
|
72
|
+
* [Pull Request #175](https://github.com/capistrano/capistrano/pull/175)
|
73
|
+
|
74
|
+
Until that's fixed, you'll have to comment out any "deploy:cleanup" invocations
|
75
|
+
|
76
|
+
### Error checking
|
77
|
+
|
78
|
+
There isn't much. And there's no tests. The latter might be a feature.
|
79
|
+
|
80
|
+
## Contributing
|
81
|
+
|
82
|
+
1. Fork it
|
83
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
84
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
85
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
86
|
+
5. Create new Pull Request
|
87
|
+
|
88
|
+
## Sources
|
89
|
+
|
90
|
+
Capistrano::Campout includes code and ideas from the following projects:
|
91
|
+
|
92
|
+
* [rails_config](https://github.com/railsjedi/rails_config) Copyright © 2010 Jacques Crocker
|
93
|
+
* [deep_merge](https://github.com/danielsdeleo/deep_merge) Copyright © 2008 Steve Midgley, now mainted by Daniel DeLeo
|
94
|
+
* [eycap](https://github.com/engineyard/eycap) Copyright © 2008-2011 Engine Yard
|
95
|
+
* [capfire](https://github.com/pjaspers/capfire) Copyright © 2010 Piet Jaspers 10to1
|
96
|
+
* [capistrano-mountaintop](https://github.com/technicalpickles/capistrano-mountaintop) Copyright © 2010 Joshua Nichols.
|
97
|
+
|
98
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/capistrano-campout/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Jason Adam Young"]
|
6
|
+
gem.email = ["jay@outfielding.net"]
|
7
|
+
gem.description = <<-EOF
|
8
|
+
Capistrano::Campout is a gem extension to capistrano to post/speak messages and paste logs from a capistrano deployment
|
9
|
+
to a campfire room. Settings are configurable using ERB in a "config/campout.yml" or "config/campout.local.yml" file.
|
10
|
+
Capistrano::Campout will a speak a pre-deployment message, and a post-deployment success or failure message. Event sounds
|
11
|
+
are also supported.
|
12
|
+
EOF
|
13
|
+
gem.summary = %q{Post messages and paste logs from a capistrano deployment to a campfire room}
|
14
|
+
gem.homepage = %q{https://github.com/jasonadamyoung/capistrano-campout}
|
15
|
+
gem.license = 'MIT'
|
16
|
+
gem.files = `git ls-files`.split($\)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.name = "capistrano-campout"
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
gem.version = Capistrano::Campout::VERSION
|
22
|
+
gem.add_dependency('capistrano', '>= 2.11')
|
23
|
+
gem.add_dependency('tinder', '>= 1.8')
|
24
|
+
gem.add_dependency('grit', '>= 2.4')
|
25
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# === COPYRIGHT:
|
2
|
+
# Copyright (c) 2012 Jason Adam Young
|
3
|
+
# === LICENSE:
|
4
|
+
# see LICENSE file
|
5
|
+
|
6
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
7
|
+
# Don't bother users who have capfire installed but don't have a ~/.campfire file
|
8
|
+
|
9
|
+
if Capfire.config_file_exists?
|
10
|
+
if Capfire.valid_config?
|
11
|
+
before "deploy:update_code", "capfire:check_for_push"
|
12
|
+
after "deploy:update_code", "capfire:post_to_campfire"
|
13
|
+
else
|
14
|
+
logger.info "Not all required keys found in your .campfire file. Please regenerate."
|
15
|
+
end
|
16
|
+
else
|
17
|
+
logger.info "Couldn't find a .campfire in your home directory."
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
desc <<-DESC
|
23
|
+
This will post to the campfire room as specified in your ~/.campfire. \
|
24
|
+
The message posted will contain a link to Github's excellent compare view, \
|
25
|
+
the commiters name, the project name and the arguments supplied to cap.
|
26
|
+
DESC
|
27
|
+
task :post_to_campfire do
|
28
|
+
begin
|
29
|
+
source_repo_url = repository
|
30
|
+
deployed_version = previous_revision[0,7] rescue "000000"
|
31
|
+
local_version = `git rev-parse HEAD`[0,7]
|
32
|
+
|
33
|
+
compare_url = Capfire.github_compare_url source_repo_url, deployed_version, local_version
|
34
|
+
message = Capfire.deploy_message(ARGV.join(' '), compare_url, application)
|
35
|
+
message = `cowsay "#{message}"` if Capfire.cowsay?
|
36
|
+
|
37
|
+
if dry_run
|
38
|
+
logger.info "Capfire would have posted:\n#{message}"
|
39
|
+
else
|
40
|
+
Capfire.speak message
|
41
|
+
end
|
42
|
+
logger.info "Posting to Campfire"
|
43
|
+
rescue => e
|
44
|
+
# Making sure we don't make capistrano fail.
|
45
|
+
# Cause nothing sucks donkeyballs like not being able to deploy
|
46
|
+
logger.important e.message
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# === COPYRIGHT:
|
2
|
+
# Copyright (c) 2012 Jason Adam Young
|
3
|
+
# === LICENSE:
|
4
|
+
# see LICENSE file
|
5
|
+
module Capistrano
|
6
|
+
module Campout
|
7
|
+
class Core
|
8
|
+
attr_accessor :settings, :campfire, :room
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@settings = Options.new
|
12
|
+
@settings.load!
|
13
|
+
end
|
14
|
+
|
15
|
+
def local_deployer
|
16
|
+
ENV['USER']
|
17
|
+
end
|
18
|
+
|
19
|
+
def git_deployer
|
20
|
+
if(gitutils)
|
21
|
+
gitutils.user_name
|
22
|
+
else
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def gitutils
|
28
|
+
@gitutils ||= GitUtils.new('.')
|
29
|
+
end
|
30
|
+
|
31
|
+
def campout_deployer
|
32
|
+
if(!(deployer = git_deployer))
|
33
|
+
deployer = local_deployer
|
34
|
+
end
|
35
|
+
deployer
|
36
|
+
end
|
37
|
+
|
38
|
+
def speak(msg)
|
39
|
+
room.speak(msg)
|
40
|
+
end
|
41
|
+
|
42
|
+
def play(sound)
|
43
|
+
room.play(sound)
|
44
|
+
end
|
45
|
+
|
46
|
+
def paste(text)
|
47
|
+
room.paste(text)
|
48
|
+
end
|
49
|
+
|
50
|
+
def campfire
|
51
|
+
@campfire ||= connect_to_campfire
|
52
|
+
end
|
53
|
+
|
54
|
+
def connect_to_campfire
|
55
|
+
options = {token: settings.campfire.token}
|
56
|
+
if(!settings.campfire.ssl.nil?)
|
57
|
+
options[:ssl] = settings.campfire.ssl
|
58
|
+
end
|
59
|
+
|
60
|
+
if(!settings.campfire.ssl_verify.nil?)
|
61
|
+
options[:ssl_verify] = settings.campfire.ssl_verify
|
62
|
+
end
|
63
|
+
Tinder::Campfire.new(settings.campfire.domain,options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def pre_announce(options = {})
|
67
|
+
self.speak(ERB.new(settings.pre_deploy.message).result(options[:binding]))
|
68
|
+
if(!settings.suppress_sounds and settings.pre_deploy.play)
|
69
|
+
self.play(settings.pre_deploy.play)
|
70
|
+
end
|
71
|
+
@pre_announce_time = Time.now
|
72
|
+
end
|
73
|
+
|
74
|
+
def post_announce_success(options = {})
|
75
|
+
message = ERB.new(settings.post_deploy_success.message).result(options[:binding])
|
76
|
+
if(!settings.suppress_deploy_time and @pre_announce_time)
|
77
|
+
message += " (#{time_period_to_s(Time.now - @pre_announce_time)})"
|
78
|
+
end
|
79
|
+
|
80
|
+
if(!settings.suppress_github_compare and gitutils)
|
81
|
+
github_compare = true
|
82
|
+
repository = options[:repository] || github_compare = false
|
83
|
+
gitutils.repository = repository
|
84
|
+
previous = options[:previous_revision] || github_compare = false
|
85
|
+
latest = options[:latest_revision] || github_compare = false
|
86
|
+
else
|
87
|
+
github_compare = false
|
88
|
+
end
|
89
|
+
|
90
|
+
if(gitutils)
|
91
|
+
gitutils.user_name
|
92
|
+
else
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
if(github_compare and gitutils.repository_is_github?)
|
97
|
+
message += " #{gitutils.github_compare_url(previous,latest)}"
|
98
|
+
end
|
99
|
+
|
100
|
+
self.speak(message)
|
101
|
+
if(!settings.suppress_sounds and settings.post_deploy_success.play)
|
102
|
+
self.play(settings.post_deploy_success.play)
|
103
|
+
end
|
104
|
+
|
105
|
+
if(!settings.suppress_deploy_log_paste)
|
106
|
+
logger = Capistrano::CampoutLogger
|
107
|
+
log_output = File.open(logger.log_file_path).read
|
108
|
+
self.paste(log_output)
|
109
|
+
end
|
110
|
+
|
111
|
+
@pre_announce_time = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def post_announce_failure(options = {})
|
115
|
+
message = ERB.new(settings.post_deploy_failure.message).result(options[:binding])
|
116
|
+
self.speak(message)
|
117
|
+
if(!settings.suppress_sounds and settings.post_deploy_failure.play)
|
118
|
+
self.play(settings.post_deploy_failure.play)
|
119
|
+
end
|
120
|
+
|
121
|
+
if(!settings.suppress_deploy_log_paste)
|
122
|
+
logger = Capistrano::CampoutLogger
|
123
|
+
log_output = File.open(logger.log_file_path).read
|
124
|
+
self.paste(log_output)
|
125
|
+
end
|
126
|
+
@pre_announce_time = nil
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def room
|
131
|
+
@room ||= campfire.find_room_by_id(settings.campfire.room)
|
132
|
+
end
|
133
|
+
|
134
|
+
def will_do(options = {})
|
135
|
+
puts "Before Deployment:"
|
136
|
+
puts "Message: #{ERB.new(settings.pre_deploy.message).result(options[:binding])}"
|
137
|
+
if(!settings.suppress_sounds and settings.pre_deploy.play)
|
138
|
+
puts "Will play sound: #{settings.pre_deploy.play}"
|
139
|
+
else
|
140
|
+
puts "Will not play sound"
|
141
|
+
end
|
142
|
+
|
143
|
+
puts "\n"
|
144
|
+
puts "After Successful Deployment:"
|
145
|
+
puts "Message: #{ERB.new(settings.post_deploy_success.message).result(options[:binding])}"
|
146
|
+
if(!settings.suppress_sounds and settings.post_deploy_success.play)
|
147
|
+
puts "Will play sound: #{settings.post_deploy_success.play}"
|
148
|
+
else
|
149
|
+
puts "Will not play sound"
|
150
|
+
end
|
151
|
+
if(!settings.suppress_deploy_log_paste)
|
152
|
+
puts "Will paste deployment log"
|
153
|
+
else
|
154
|
+
puts "Will not paste deployment log"
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
puts "\n"
|
159
|
+
puts "After Failed Deployment:"
|
160
|
+
puts "Message: #{ERB.new(settings.post_deploy_failure.message).result(options[:binding])}"
|
161
|
+
if(!settings.suppress_sounds and settings.post_deploy_failure.play)
|
162
|
+
puts "Will play sound: #{settings.post_deploy_failure.play}"
|
163
|
+
else
|
164
|
+
puts "Will not play sound"
|
165
|
+
end
|
166
|
+
if(!settings.suppress_deploy_log_paste)
|
167
|
+
puts "Will paste deployment log"
|
168
|
+
else
|
169
|
+
puts "Will not paste deployment log"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Takes a period of time in seconds and returns it in human-readable form (down to minutes)
|
174
|
+
# code from http://www.postal-code.com/binarycode/2007/04/04/english-friendly-timespan/
|
175
|
+
def time_period_to_s(time_period,abbreviated=false,defaultstring = '')
|
176
|
+
out_str = ''
|
177
|
+
if(time_period.blank?)
|
178
|
+
return defaultstring
|
179
|
+
end
|
180
|
+
interval_array = [ [:weeks, 604800], [:days, 86400], [:hours, 3600], [:minutes, 60], [:seconds, 1] ]
|
181
|
+
interval_array.each do |sub|
|
182
|
+
if time_period >= sub[1] then
|
183
|
+
time_val, time_period = time_period.divmod( sub[1] )
|
184
|
+
if(abbreviated)
|
185
|
+
name = sub[0].to_s.first
|
186
|
+
( sub[0] != :seconds ? out_str += ", " : out_str += " " ) if out_str != ''
|
187
|
+
else
|
188
|
+
time_val == 1 ? name = sub[0].to_s.chop : name = sub[0].to_s
|
189
|
+
( sub[0] != :seconds ? out_str += ", " : out_str += " and " ) if out_str != ''
|
190
|
+
end
|
191
|
+
out_str += time_val.to_s + " #{name}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
if(out_str.blank?)
|
195
|
+
return defaultstring
|
196
|
+
else
|
197
|
+
return out_str
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# deep_merge was written by Steve Midgley, and is now maintained by Daniel DeLeo.
|
2
|
+
# The official home of deep_merge on the internet is now
|
3
|
+
# https://github.com/danielsdeleo/deep_merge
|
4
|
+
#
|
5
|
+
# Copyright (c) 2008 Steve Midgley, released under the MIT license
|
6
|
+
|
7
|
+
module DeepMerge
|
8
|
+
|
9
|
+
class InvalidParameter < StandardError; end
|
10
|
+
|
11
|
+
DEFAULT_FIELD_KNOCKOUT_PREFIX = '--'
|
12
|
+
|
13
|
+
# Deep Merge core documentation.
|
14
|
+
# deep_merge! method permits merging of arbitrary child elements. The two top level
|
15
|
+
# elements must be hashes. These hashes can contain unlimited (to stack limit) levels
|
16
|
+
# of child elements. These child elements to not have to be of the same types.
|
17
|
+
# Where child elements are of the same type, deep_merge will attempt to merge them together.
|
18
|
+
# Where child elements are not of the same type, deep_merge will skip or optionally overwrite
|
19
|
+
# the destination element with the contents of the source element at that level.
|
20
|
+
# So if you have two hashes like this:
|
21
|
+
# source = {:x => [1,2,3], :y => 2}
|
22
|
+
# dest = {:x => [4,5,'6'], :y => [7,8,9]}
|
23
|
+
# dest.deep_merge!(source)
|
24
|
+
# Results: {:x => [1,2,3,4,5,'6'], :y => 2}
|
25
|
+
# By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
|
26
|
+
# To avoid this, use "deep_merge" (no bang/exclamation mark)
|
27
|
+
#
|
28
|
+
# Options:
|
29
|
+
# Options are specified in the last parameter passed, which should be in hash format:
|
30
|
+
# hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '--'})
|
31
|
+
# :preserve_unmergeables DEFAULT: false
|
32
|
+
# Set to true to skip any unmergeable elements from source
|
33
|
+
# :knockout_prefix DEFAULT: nil
|
34
|
+
# Set to string value to signify prefix which deletes elements from existing element
|
35
|
+
# :sort_merged_arrays DEFAULT: false
|
36
|
+
# Set to true to sort all arrays that are merged together
|
37
|
+
# :unpack_arrays DEFAULT: nil
|
38
|
+
# Set to string value to run "Array::join" then "String::split" against all arrays
|
39
|
+
# :merge_hash_arrays DEFAULT: false
|
40
|
+
# Set to true to merge hashes within arrays
|
41
|
+
# :merge_debug DEFAULT: false
|
42
|
+
# Set to true to get console output of merge process for debugging
|
43
|
+
#
|
44
|
+
# Selected Options Details:
|
45
|
+
# :knockout_prefix => The purpose of this is to provide a way to remove elements
|
46
|
+
# from existing Hash by specifying them in a special way in incoming hash
|
47
|
+
# source = {:x => ['--1', '2']}
|
48
|
+
# dest = {:x => ['1', '3']}
|
49
|
+
# dest.ko_deep_merge!(source)
|
50
|
+
# Results: {:x => ['2','3']}
|
51
|
+
# Additionally, if the knockout_prefix is passed alone as a string, it will cause
|
52
|
+
# the entire element to be removed:
|
53
|
+
# source = {:x => '--'}
|
54
|
+
# dest = {:x => [1,2,3]}
|
55
|
+
# dest.ko_deep_merge!(source)
|
56
|
+
# Results: {:x => ""}
|
57
|
+
# :unpack_arrays => The purpose of this is to permit compound elements to be passed
|
58
|
+
# in as strings and to be converted into discrete array elements
|
59
|
+
# irsource = {:x => ['1,2,3', '4']}
|
60
|
+
# dest = {:x => ['5','6','7,8']}
|
61
|
+
# dest.deep_merge!(source, {:unpack_arrays => ','})
|
62
|
+
# Results: {:x => ['1','2','3','4','5','6','7','8'}
|
63
|
+
# Why: If receiving data from an HTML form, this makes it easy for a checkbox
|
64
|
+
# to pass multiple values from within a single HTML element
|
65
|
+
#
|
66
|
+
# :merge_hash_arrays => merge hashes within arrays
|
67
|
+
# source = {:x => [{:y => 1}]}
|
68
|
+
# dest = {:x => [{:z => 2}]}
|
69
|
+
# dest.deep_merge!(source, {:merge_hash_arrays => true})
|
70
|
+
# Results: {:x => [{:y => 1, :z => 2}]}
|
71
|
+
#
|
72
|
+
# There are many tests for this library - and you can learn more about the features
|
73
|
+
# and usages of deep_merge! by just browsing the test examples
|
74
|
+
def self.deep_merge!(source, dest, options = {})
|
75
|
+
# turn on this line for stdout debugging text
|
76
|
+
merge_debug = options[:merge_debug] || false
|
77
|
+
overwrite_unmergeable = !options[:preserve_unmergeables]
|
78
|
+
knockout_prefix = options[:knockout_prefix] || nil
|
79
|
+
raise InvalidParameter, "knockout_prefix cannot be an empty string in deep_merge!" if knockout_prefix == ""
|
80
|
+
raise InvalidParameter, "overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!" if knockout_prefix && !overwrite_unmergeable
|
81
|
+
# if present: we will split and join arrays on this char before merging
|
82
|
+
array_split_char = options[:unpack_arrays] || false
|
83
|
+
# request that we sort together any arrays when they are merged
|
84
|
+
sort_merged_arrays = options[:sort_merged_arrays] || false
|
85
|
+
# request that arrays of hashes are merged together
|
86
|
+
merge_hash_arrays = options[:merge_hash_arrays] || false
|
87
|
+
di = options[:debug_indent] || ''
|
88
|
+
# do nothing if source is nil
|
89
|
+
return dest if source.nil?
|
90
|
+
# if dest doesn't exist, then simply copy source to it
|
91
|
+
if !(dest) && overwrite_unmergeable
|
92
|
+
dest = source; return dest
|
93
|
+
end
|
94
|
+
|
95
|
+
puts "#{di}Source class: #{source.class.inspect} :: Dest class: #{dest.class.inspect}" if merge_debug
|
96
|
+
if source.kind_of?(Hash)
|
97
|
+
puts "#{di}Hashes: #{source.inspect} :: #{dest.inspect}" if merge_debug
|
98
|
+
source.each do |src_key, src_value|
|
99
|
+
if dest.kind_of?(Hash)
|
100
|
+
puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug
|
101
|
+
if dest[src_key]
|
102
|
+
puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug
|
103
|
+
dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(:debug_indent => di + ' '))
|
104
|
+
else # dest[src_key] doesn't exist so we want to create and overwrite it (but we do this via deep_merge!)
|
105
|
+
puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug
|
106
|
+
# note: we rescue here b/c some classes respond to "dup" but don't implement it (Numeric, TrueClass, FalseClass, NilClass among maybe others)
|
107
|
+
begin
|
108
|
+
src_dup = src_value.dup # we dup src_value if possible because we're going to merge into it (since dest is empty)
|
109
|
+
rescue TypeError
|
110
|
+
src_dup = src_value
|
111
|
+
end
|
112
|
+
dest[src_key] = deep_merge!(src_value, src_dup, options.merge(:debug_indent => di + ' '))
|
113
|
+
end
|
114
|
+
else # dest isn't a hash, so we overwrite it completely (if permitted)
|
115
|
+
if overwrite_unmergeable
|
116
|
+
puts "#{di} overwriting dest: #{src_key.inspect} => #{src_value.inspect} -over-> #{dest.inspect}" if merge_debug
|
117
|
+
dest = overwrite_unmergeables(source, dest, options)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
elsif source.kind_of?(Array)
|
122
|
+
puts "#{di}Arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
|
123
|
+
# if we are instructed, join/split any source arrays before processing
|
124
|
+
if array_split_char
|
125
|
+
puts "#{di} split/join on source: #{source.inspect}" if merge_debug
|
126
|
+
source = source.join(array_split_char).split(array_split_char)
|
127
|
+
if dest.kind_of?(Array)
|
128
|
+
dest = dest.join(array_split_char).split(array_split_char)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
# if there's a naked knockout_prefix in source, that means we are to truncate dest
|
132
|
+
if source.index(knockout_prefix)
|
133
|
+
dest = clear_or_nil(dest); source.delete(knockout_prefix)
|
134
|
+
end
|
135
|
+
if dest.kind_of?(Array)
|
136
|
+
if knockout_prefix
|
137
|
+
print "#{di} knocking out: " if merge_debug
|
138
|
+
# remove knockout prefix items from both source and dest
|
139
|
+
source.delete_if do |ko_item|
|
140
|
+
retval = false
|
141
|
+
item = ko_item.respond_to?(:gsub) ? ko_item.gsub(%r{^#{knockout_prefix}}, "") : ko_item
|
142
|
+
if item != ko_item
|
143
|
+
print "#{ko_item} - " if merge_debug
|
144
|
+
dest.delete(item)
|
145
|
+
dest.delete(ko_item)
|
146
|
+
retval = true
|
147
|
+
end
|
148
|
+
retval
|
149
|
+
end
|
150
|
+
puts if merge_debug
|
151
|
+
end
|
152
|
+
puts "#{di} merging arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
|
153
|
+
source_all_hashes = source.all? { |i| i.kind_of?(Hash) }
|
154
|
+
dest_all_hashes = dest.all? { |i| i.kind_of?(Hash) }
|
155
|
+
if merge_hash_arrays && source_all_hashes && dest_all_hashes
|
156
|
+
# merge hashes in lists
|
157
|
+
list = []
|
158
|
+
dest.each_index do |i|
|
159
|
+
list[i] = deep_merge!(source[i] || {}, dest[i],
|
160
|
+
options.merge(:debug_indent => di + ' '))
|
161
|
+
end
|
162
|
+
list += source[dest.count..-1] if source.count > dest.count
|
163
|
+
dest = list
|
164
|
+
else
|
165
|
+
dest = dest | source
|
166
|
+
end
|
167
|
+
dest.sort! if sort_merged_arrays
|
168
|
+
elsif overwrite_unmergeable
|
169
|
+
puts "#{di} overwriting dest: #{source.inspect} -over-> #{dest.inspect}" if merge_debug
|
170
|
+
dest = overwrite_unmergeables(source, dest, options)
|
171
|
+
end
|
172
|
+
else # src_hash is not an array or hash, so we'll have to overwrite dest
|
173
|
+
puts "#{di}Others: #{source.inspect} :: #{dest.inspect}" if merge_debug
|
174
|
+
dest = overwrite_unmergeables(source, dest, options)
|
175
|
+
end
|
176
|
+
puts "#{di}Returning #{dest.inspect}" if merge_debug
|
177
|
+
dest
|
178
|
+
end # deep_merge!
|
179
|
+
|
180
|
+
# allows deep_merge! to uniformly handle overwriting of unmergeable entities
|
181
|
+
def self.overwrite_unmergeables(source, dest, options)
|
182
|
+
merge_debug = options[:merge_debug] || false
|
183
|
+
overwrite_unmergeable = !options[:preserve_unmergeables]
|
184
|
+
knockout_prefix = options[:knockout_prefix] || false
|
185
|
+
di = options[:debug_indent] || ''
|
186
|
+
if knockout_prefix && overwrite_unmergeable
|
187
|
+
if source.kind_of?(String) # remove knockout string from source before overwriting dest
|
188
|
+
src_tmp = source.gsub(%r{^#{knockout_prefix}},"")
|
189
|
+
elsif source.kind_of?(Array) # remove all knockout elements before overwriting dest
|
190
|
+
src_tmp = source.delete_if {|ko_item| ko_item.kind_of?(String) && ko_item.match(%r{^#{knockout_prefix}}) }
|
191
|
+
else
|
192
|
+
src_tmp = source
|
193
|
+
end
|
194
|
+
if src_tmp == source # if we didn't find a knockout_prefix then we just overwrite dest
|
195
|
+
puts "#{di}#{src_tmp.inspect} -over-> #{dest.inspect}" if merge_debug
|
196
|
+
dest = src_tmp
|
197
|
+
else # if we do find a knockout_prefix, then we just delete dest
|
198
|
+
puts "#{di}\"\" -over-> #{dest.inspect}" if merge_debug
|
199
|
+
dest = ""
|
200
|
+
end
|
201
|
+
elsif overwrite_unmergeable
|
202
|
+
dest = source
|
203
|
+
end
|
204
|
+
dest
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.clear_or_nil(obj)
|
208
|
+
if obj.respond_to?(:clear)
|
209
|
+
obj.clear
|
210
|
+
else
|
211
|
+
obj = nil
|
212
|
+
end
|
213
|
+
obj
|
214
|
+
end
|
215
|
+
|
216
|
+
end # module DeepMerge
|
@@ -0,0 +1,6 @@
|
|
1
|
+
pre_deploy:
|
2
|
+
message: "<%= campout_deployer %> is starting to deploy <%= application %> to <%= stage %>"
|
3
|
+
post_deploy_success:
|
4
|
+
message: "<%= campout_deployer %> deployed <%= application %> to <%= stage %>"
|
5
|
+
post_deploy_failure:
|
6
|
+
message: "The deploy of <%= application %> to <%= stage %> by <%= campout_deployer %> has failed."
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# === COPYRIGHT:
|
2
|
+
# Copyright (c) 2012 Jason Adam Young
|
3
|
+
# === LICENSE:
|
4
|
+
# see LICENSE file
|
5
|
+
module Capistrano
|
6
|
+
module Campout
|
7
|
+
class GitUtils
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
if(localrepo)
|
12
|
+
return self
|
13
|
+
else
|
14
|
+
return nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def localrepo
|
19
|
+
if(@localrepo.nil?)
|
20
|
+
begin
|
21
|
+
@localrepo = Grit::Repo.new(@path)
|
22
|
+
rescue Grit::InvalidGitRepositoryError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
@localrepo
|
26
|
+
end
|
27
|
+
|
28
|
+
def user_name
|
29
|
+
if(localrepo)
|
30
|
+
git_config = Grit::Config.new(localrepo)
|
31
|
+
git_config.fetch('user.name')
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def repository=(repository)
|
38
|
+
@repository = repository
|
39
|
+
end
|
40
|
+
|
41
|
+
def github_url_for_repository
|
42
|
+
if(!@mogrified_repository)
|
43
|
+
if(@repository =~ /git@github.com/)
|
44
|
+
@mogrified_repository = @repository.dup
|
45
|
+
@mogrified_repository.gsub!(/git@/, 'http://')
|
46
|
+
@mogrified_repository.gsub!(/\.com:/,'.com/')
|
47
|
+
@mogrified_repository.gsub!(/\.git/, '')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
@mogrified_repository
|
51
|
+
end
|
52
|
+
|
53
|
+
def repository_is_github?
|
54
|
+
return (!github_url_for_repository.nil?)
|
55
|
+
end
|
56
|
+
|
57
|
+
def github_compare_url(previous_revision, latest_revision)
|
58
|
+
if(repository_is_github?)
|
59
|
+
if(previous_revision != latest_revision)
|
60
|
+
"#{github_url_for_repository}/compare/#{previous_revision}...#{latest_revision}"
|
61
|
+
else
|
62
|
+
"#{github_url_for_repository}/commit/#{latest_revision}"
|
63
|
+
end
|
64
|
+
else
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
@@ -0,0 +1,127 @@
|
|
1
|
+
#
|
2
|
+
# Original source from: https://github.com/engineyard/eycap/blob/master/lib/eycap/lib/ey_logger.rb
|
3
|
+
# Copyright (c) 2008-2011 Engine Yard, released under the MIT License
|
4
|
+
#
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'fileutils'
|
7
|
+
module Capistrano
|
8
|
+
|
9
|
+
class Logger
|
10
|
+
|
11
|
+
def campout_log(level, message, line_prefix = nil)
|
12
|
+
CampoutLogger.log(level, message, line_prefix) if CampoutLogger.setup?
|
13
|
+
log_without_campout_logging(level, message, line_prefix)
|
14
|
+
end
|
15
|
+
|
16
|
+
unless method_defined?(:log_without_campout_logging)
|
17
|
+
alias_method :log_without_campout_logging, :log
|
18
|
+
alias_method :log, :campout_log
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
device.close if @needs_close
|
23
|
+
CampoutLogger.close if CampoutLogger.setup?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class CampoutLogger
|
28
|
+
|
29
|
+
# Sets up the CampoutLogger to begin capturing capistrano's logging. You should pass the capistrano configuration
|
30
|
+
def self.setup(configuration, options = {})
|
31
|
+
@_configuration = configuration
|
32
|
+
@_log_path = options[:deploy_log_path] || Dir.tmpdir
|
33
|
+
@_log_path << "/" unless @_log_path =~ /\/$/
|
34
|
+
FileUtils.mkdir_p(@_log_path)
|
35
|
+
@_setup = true
|
36
|
+
@_success = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.log(level, message, line_prefix=nil)
|
40
|
+
return nil unless setup?
|
41
|
+
@release_name = @_configuration[:release_name] if @release_name.nil?
|
42
|
+
@_log_file_path = @_log_path + @release_name + ".log" unless @_log_file_path
|
43
|
+
@_deploy_log_file = File.open(@_log_file_path, "w") if @_deploy_log_file.nil?
|
44
|
+
|
45
|
+
indent = "%*s" % [Logger::MAX_LEVEL, "*" * (Logger::MAX_LEVEL - level)]
|
46
|
+
message.each_line do |line|
|
47
|
+
if line_prefix
|
48
|
+
@_deploy_log_file << "#{indent} [#{line_prefix}] #{line.strip}\n"
|
49
|
+
else
|
50
|
+
@_deploy_log_file << "#{indent} #{line.strip}\n"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.post_process
|
56
|
+
unless ::Interrupt === $!
|
57
|
+
puts "\n\nPlease wait while the log file is processed\n"
|
58
|
+
# Should dump the stack trace of an exception if there is one
|
59
|
+
error = $!
|
60
|
+
unless error.nil?
|
61
|
+
@_deploy_log_file << error.message << "\n"
|
62
|
+
@_deploy_log_file << error.backtrace.join("\n")
|
63
|
+
@_success = false
|
64
|
+
end
|
65
|
+
self.close
|
66
|
+
|
67
|
+
hooks = [:any]
|
68
|
+
hooks << (self.successful? ? :success : :failure)
|
69
|
+
puts "Executing Post Processing Hooks"
|
70
|
+
hooks.each do |h|
|
71
|
+
@_post_process_hooks[h].each do |key|
|
72
|
+
@_configuration.parent.find_and_execute_task(key)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
puts "Finished Post Processing Hooks"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Adds a post processing hook.
|
80
|
+
#
|
81
|
+
# Provide a task name to execute. These tasks are executed after capistrano has actually run its course.
|
82
|
+
#
|
83
|
+
# Takes a key to control when the hook is executed.'
|
84
|
+
# :any - always executed
|
85
|
+
# :success - only execute on success
|
86
|
+
# :failure - only execute on failure
|
87
|
+
#
|
88
|
+
# ==== Example
|
89
|
+
# Capistrano::CampoutLogger.post_process_hook( "campout:post_log", :any)
|
90
|
+
#
|
91
|
+
def self.post_process_hook(task, key = :any)
|
92
|
+
@_post_process_hooks ||= Hash.new{|h,k| h[k] = []}
|
93
|
+
@_post_process_hooks[key] << task
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.setup?
|
97
|
+
!!@_setup
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.deploy_type
|
101
|
+
@_deploy_type
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.successful?
|
105
|
+
!!@_success
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.failure?
|
109
|
+
!@_success
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.log_file_path
|
113
|
+
@_log_file_path
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.remote_log_file_name
|
117
|
+
@_log_file_name ||= "deploy-#{@_configuration[:release_name]}-#{self.successful? ? "success" : "failure"}.log"
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.close
|
121
|
+
@_deploy_log_file.flush unless @_deploy_log_file.nil?
|
122
|
+
@_deploy_log_file.close unless @_deploy_log_file.nil?
|
123
|
+
@_setup = false
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
#
|
2
|
+
# The core of options.rb comes from rails_config, https://github.com/railsjedi/rails_config
|
3
|
+
#
|
4
|
+
# Copyright (c) 2010 Jacques Crocker, released under the MIT license
|
5
|
+
#
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'ostruct'
|
9
|
+
require 'yaml'
|
10
|
+
require 'erb'
|
11
|
+
|
12
|
+
module Capistrano
|
13
|
+
module Campout
|
14
|
+
module Sources
|
15
|
+
class YAMLSource
|
16
|
+
|
17
|
+
attr_accessor :path
|
18
|
+
|
19
|
+
def initialize(path)
|
20
|
+
@path = path
|
21
|
+
end
|
22
|
+
|
23
|
+
# returns a config hash from the YML file
|
24
|
+
def load
|
25
|
+
if @path and File.exists?(@path.to_s)
|
26
|
+
result = YAML.load(IO.read(@path.to_s))
|
27
|
+
end
|
28
|
+
result || {}
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Options < OpenStruct
|
35
|
+
attr_accessor :defaults
|
36
|
+
|
37
|
+
def valid?
|
38
|
+
# requires the following settings
|
39
|
+
# campfire:
|
40
|
+
# domain:
|
41
|
+
# token:
|
42
|
+
# room:
|
43
|
+
|
44
|
+
return false if(self.campfire.nil?)
|
45
|
+
return false if(self.campfire.domain.nil?)
|
46
|
+
return false if(self.campfire.token.nil?)
|
47
|
+
return false if(self.campfire.room.nil?)
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
|
51
|
+
def files=(*files)
|
52
|
+
if(!files.empty?)
|
53
|
+
@files = [files].flatten.compact.uniq
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def files
|
58
|
+
if(@files.nil? or @files.empty?)
|
59
|
+
@files = ["#{File.join(File.dirname(__FILE__), "defaults.yml").to_s}","./config/campout.yml","./config/campout.local.yml"]
|
60
|
+
end
|
61
|
+
@files
|
62
|
+
end
|
63
|
+
|
64
|
+
def empty?
|
65
|
+
marshal_dump.empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_from_files(*files)
|
69
|
+
self.files=files
|
70
|
+
self.load!
|
71
|
+
end
|
72
|
+
|
73
|
+
def reload_from_files(*files)
|
74
|
+
self.files=files
|
75
|
+
self.reload!
|
76
|
+
end
|
77
|
+
|
78
|
+
def reset_sources!
|
79
|
+
self.files.each do |file|
|
80
|
+
source = (Sources::YAMLSource.new(file)) if file.is_a?(String)
|
81
|
+
@config_sources ||= []
|
82
|
+
@config_sources << source
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# look through all our sources and rebuild the configuration
|
87
|
+
def reload!
|
88
|
+
self.reset_sources!
|
89
|
+
conf = {}
|
90
|
+
@config_sources.each do |source|
|
91
|
+
source_conf = source.load
|
92
|
+
|
93
|
+
if conf.empty?
|
94
|
+
conf = source_conf
|
95
|
+
else
|
96
|
+
DeepMerge.deep_merge!(source_conf, conf, :preserve_unmergeables => false)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# swap out the contents of the OStruct with a hash (need to recursively convert)
|
101
|
+
marshal_load(__convert(conf).marshal_dump)
|
102
|
+
|
103
|
+
return self
|
104
|
+
end
|
105
|
+
|
106
|
+
alias :load! :reload!
|
107
|
+
|
108
|
+
def to_hash
|
109
|
+
result = {}
|
110
|
+
marshal_dump.each do |k, v|
|
111
|
+
result[k] = v.instance_of?(RailsConfig::Options) ? v.to_hash : v
|
112
|
+
end
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_json(*args)
|
117
|
+
require "json" unless defined?(JSON)
|
118
|
+
to_hash.to_json(*args)
|
119
|
+
end
|
120
|
+
|
121
|
+
def merge!(hash)
|
122
|
+
current = to_hash
|
123
|
+
DeepMerge.deep_merge!(current, hash.dup)
|
124
|
+
marshal_load(__convert(hash).marshal_dump)
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
protected
|
129
|
+
|
130
|
+
# Recursively converts Hashes to Options (including Hashes inside Arrays)
|
131
|
+
def __convert(h) #:nodoc:
|
132
|
+
s = self.class.new
|
133
|
+
|
134
|
+
h.each do |k, v|
|
135
|
+
s.new_ostruct_member(k)
|
136
|
+
|
137
|
+
if v.is_a?(Hash)
|
138
|
+
v = v["type"] == "hash" ? v["contents"] : __convert(v)
|
139
|
+
elsif v.is_a?(Array)
|
140
|
+
v = v.collect { |e| e.instance_of?(Hash) ? __convert(e) : e }
|
141
|
+
end
|
142
|
+
|
143
|
+
s.send("#{k}=".to_sym, v)
|
144
|
+
end
|
145
|
+
s
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# === COPYRIGHT:
|
2
|
+
# Copyright (c) 2012 Jason Adam Young
|
3
|
+
# === LICENSE:
|
4
|
+
# see LICENSE file
|
5
|
+
|
6
|
+
#
|
7
|
+
# Post process hooks inspired by: https://github.com/engineyard/eycap/blob/master/lib/eycap/lib/ey_logger_hooks.rb
|
8
|
+
# Copyright (c) 2008-2011 Engine Yard, released under the MIT License
|
9
|
+
#
|
10
|
+
|
11
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
12
|
+
namespace :campout do
|
13
|
+
|
14
|
+
## no descriptions for the following tasks - meant to be hooked by capistrano
|
15
|
+
task :pre_announce do
|
16
|
+
campout_core.pre_announce(binding: binding)
|
17
|
+
end
|
18
|
+
|
19
|
+
task :post_announce_success do
|
20
|
+
campout_core.post_announce_success(binding: binding,
|
21
|
+
repository: repository,
|
22
|
+
previous_revision: previous_revision,
|
23
|
+
latest_revision: latest_revision)
|
24
|
+
end
|
25
|
+
|
26
|
+
task :post_announce_failure do
|
27
|
+
campout_core.post_announce_failure(binding: binding)
|
28
|
+
end
|
29
|
+
|
30
|
+
task :copy_log, :except => { :no_release => true} do
|
31
|
+
logger = Capistrano::CampoutLogger
|
32
|
+
run "mkdir -p #{shared_path}/deploy_logs"
|
33
|
+
put File.open(logger.log_file_path).read, "#{shared_path}/deploy_logs/#{logger.remote_log_file_name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Display campfire messages and actions based on current configuration"
|
37
|
+
task :will_do do
|
38
|
+
campout_core.will_do(binding: binding)
|
39
|
+
end
|
40
|
+
|
41
|
+
task :settings do
|
42
|
+
y campout_core.settings
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Capistrano::CampoutLogger.post_process_hook("campout:post_announce_success",:success)
|
48
|
+
Capistrano::CampoutLogger.post_process_hook("campout:post_announce_failure",:failure)
|
49
|
+
Capistrano::CampoutLogger.post_process_hook("campout:copy_log",:any)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# === COPYRIGHT:
|
2
|
+
# Copyright (c) 2012 Jason Adam Young
|
3
|
+
# === LICENSE:
|
4
|
+
# see LICENSE file
|
5
|
+
|
6
|
+
#
|
7
|
+
# Original source from: https://github.com/engineyard/eycap/blob/master/lib/eycap/recipes/deploy.rb
|
8
|
+
# Copyright (c) 2008-2011 Engine Yard, released under the MIT License
|
9
|
+
#
|
10
|
+
|
11
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
12
|
+
namespace :deploy do
|
13
|
+
# This is here to hook into the logger for deploy tasks
|
14
|
+
before("deploy") do
|
15
|
+
Capistrano::CampoutLogger.setup(self)
|
16
|
+
at_exit{ Capistrano::CampoutLogger.post_process if Capistrano::CampoutLogger.setup? }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# === COPYRIGHT:
|
2
|
+
# Copyright (c) 2012 Jason Adam Young
|
3
|
+
# === LICENSE:
|
4
|
+
# see LICENSE file
|
5
|
+
require 'capistrano'
|
6
|
+
require 'tinder'
|
7
|
+
require 'grit'
|
8
|
+
|
9
|
+
require 'capistrano-campout/logger'
|
10
|
+
require 'capistrano-campout/version'
|
11
|
+
require 'capistrano-campout/deep_merge' unless defined?(DeepMerge)
|
12
|
+
require 'capistrano-campout/options'
|
13
|
+
require 'capistrano-campout/git_utils'
|
14
|
+
require 'capistrano-campout/core'
|
15
|
+
|
16
|
+
module Capistrano
|
17
|
+
module Campout
|
18
|
+
def self.extended(configuration)
|
19
|
+
configuration.load do
|
20
|
+
set :campout_core, Capistrano::Campout::Core.new
|
21
|
+
set :campout_deployer, campout_core.campout_deployer
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if Capistrano::Configuration.instance
|
28
|
+
Capistrano::Configuration.instance.extend(Capistrano::Campout)
|
29
|
+
end
|
30
|
+
|
31
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
32
|
+
if(campout_core.settings.valid?)
|
33
|
+
# load the recipes
|
34
|
+
Dir.glob(File.join(File.dirname(__FILE__), '/capistrano-campout/recipes/*.rb')).sort.each { |f| load f }
|
35
|
+
before "deploy", "campout:pre_announce"
|
36
|
+
else
|
37
|
+
logger.info "The campout configuration is not valid. Make sure that the campfire settings are specified in the campout configuration file(s)"
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano-campout
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jason Adam Young
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: capistrano
|
16
|
+
requirement: &70114418631680 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.11'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70114418631680
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: tinder
|
27
|
+
requirement: &70114418615660 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.8'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70114418615660
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: grit
|
38
|
+
requirement: &70114418614880 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.4'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70114418614880
|
47
|
+
description: ! " Capistrano::Campout is a gem extension to capistrano to post/speak
|
48
|
+
messages and paste logs from a capistrano deployment \n to a campfire room. Settings
|
49
|
+
are configurable using ERB in a \"config/campout.yml\" or \"config/campout.local.yml\"
|
50
|
+
file. \n Capistrano::Campout will a speak a pre-deployment message, and a post-deployment
|
51
|
+
success or failure message. Event sounds\n are also supported.\n"
|
52
|
+
email:
|
53
|
+
- jay@outfielding.net
|
54
|
+
executables: []
|
55
|
+
extensions: []
|
56
|
+
extra_rdoc_files: []
|
57
|
+
files:
|
58
|
+
- .gitignore
|
59
|
+
- Gemfile
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- capistrano-campout.gemspec
|
64
|
+
- lib/capistrano-campout.rb
|
65
|
+
- lib/capistrano-campout/cap_tasks.rb
|
66
|
+
- lib/capistrano-campout/core.rb
|
67
|
+
- lib/capistrano-campout/deep_merge.rb
|
68
|
+
- lib/capistrano-campout/defaults.yml
|
69
|
+
- lib/capistrano-campout/git_utils.rb
|
70
|
+
- lib/capistrano-campout/logger.rb
|
71
|
+
- lib/capistrano-campout/options.rb
|
72
|
+
- lib/capistrano-campout/recipes/campout.rb
|
73
|
+
- lib/capistrano-campout/recipes/deploy.rb
|
74
|
+
- lib/capistrano-campout/version.rb
|
75
|
+
homepage: https://github.com/jasonadamyoung/capistrano-campout
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.8.17
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: Post messages and paste logs from a capistrano deployment to a campfire room
|
100
|
+
test_files: []
|