chap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # ---------------------------------------------------------------------------
4
+ # A simple copy script for doing hard links and symbolic links instead of
5
+ # explicit copies. Some OS's will already have a utility to do this, but
6
+ # some won't; this file suffices in either case.
7
+ #
8
+ # Usage: ruby copy.rb <source> <target> <exclude> ...
9
+ #
10
+ # The <source> directory is recursively descended, and hard links to all of
11
+ # the files are created in corresponding locations under <target>. Symbolic
12
+ # links in <source> map to symbolic links in <target> that point to the same
13
+ # destination.
14
+ #
15
+ # All arguments after <target> are taken to be exclude patterns. Any file
16
+ # or directory in <source> that matches any of those patterns will be
17
+ # skipped, and will thus not be present in <target>.
18
+ # ---------------------------------------------------------------------------
19
+ # This file is distributed under the terms of the MIT license by 37signals,
20
+ # LLC, and is copyright (c) 2008 by the same. See the LICENSE file distributed
21
+ # with this file for the complete text of the license.
22
+ # ---------------------------------------------------------------------------
23
+ require 'fileutils'
24
+
25
+ from = ARGV.shift or abort "need source directory"
26
+ to = ARGV.shift or abort "need target directory"
27
+
28
+ exclude = ARGV
29
+
30
+ from = File.expand_path(from)
31
+ to = File.expand_path(to)
32
+
33
+ Dir.chdir(from) do
34
+ FileUtils.mkdir_p(to)
35
+ queue = Dir.glob("*", File::FNM_DOTMATCH)
36
+ while queue.any?
37
+ item = queue.shift
38
+ name = File.basename(item)
39
+
40
+ next if name == "." || name == ".."
41
+ next if exclude.any? { |pattern| File.fnmatch(pattern, item) }
42
+
43
+ source = File.join(from, item)
44
+ target = File.join(to, item)
45
+
46
+ if File.symlink?(item)
47
+ FileUtils.ln_s(File.readlink(source), target)
48
+ elsif File.directory?(item)
49
+ queue += Dir.glob("#{item}/*", File::FNM_DOTMATCH)
50
+ FileUtils.mkdir_p(target, :mode => File.stat(item).mode)
51
+ else
52
+ FileUtils.ln(source, target)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,6 @@
1
+ strategy = File.expand_path('../strategy', __FILE__)
2
+ require "#{strategy}/base"
3
+ require "#{strategy}/checkout"
4
+ Dir.glob("#{strategy}/*.rb").each do |file|
5
+ require file
6
+ end
data/lib/chap/task.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Chap
2
+ class Task
3
+ def self.setup(options={})
4
+ puts "Generating config files" unless options[:quiet]
5
+ setup = File.expand_path("../../setup", __FILE__)
6
+ output = options[:output] || '.'
7
+ Dir.glob("#{setup}/*").each do |source|
8
+ dest = "#{output}/#{File.basename(source)}"
9
+ if File.exist?(dest)
10
+ if options[:force]
11
+ puts "Overwriting: #{dest}" unless options[:quiet]
12
+ FileUtils.cp(source, dest)
13
+ else
14
+ puts "Already exist: #{dest}" unless options[:quiet]
15
+ end
16
+ else
17
+ FileUtils.cp(source, dest)
18
+ puts "Created: #{dest}" unless options[:quiet]
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.deploy(options)
24
+ runner = options.empty? ? Runner.new : Runner.new(options)
25
+ runner.deploy
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Chap
2
+ VERSION = "0.0.1"
3
+ end
data/lib/chap.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "chap/version"
2
+ require "yaml"
3
+ require "json"
4
+ require "colorize"
5
+ require "logger"
6
+ require "thor"
7
+ require "pp"
8
+
9
+ $:.unshift File.expand_path('../', __FILE__)
10
+ require 'mash'
11
+ require 'chap/cli'
12
+ require 'chap/task'
13
+ require 'chap/special_methods'
14
+ require 'chap/config'
15
+ require 'chap/runner'
16
+ require 'chap/hook'
17
+ require 'chap/strategy'
data/lib/mash.rb ADDED
@@ -0,0 +1,225 @@
1
+ # Copyright (c) 2009 Dan Kubb
2
+
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ # ---
23
+ # ---
24
+
25
+ # Some portions of blank.rb and mash.rb are verbatim copies of software
26
+ # licensed under the MIT license. That license is included below:
27
+
28
+ # Copyright (c) 2005-2008 David Heinemeier Hansson
29
+
30
+ # Permission is hereby granted, free of charge, to any person obtaining
31
+ # a copy of this software and associated documentation files (the
32
+ # "Software"), to deal in the Software without restriction, including
33
+ # without limitation the rights to use, copy, modify, merge, publish,
34
+ # distribute, sublicense, and/or sell copies of the Software, and to
35
+ # permit persons to whom the Software is furnished to do so, subject to
36
+ # the following conditions:
37
+
38
+ # The above copyright notice and this permission notice shall be
39
+ # included in all copies or substantial portions of the Software.
40
+
41
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
42
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
44
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
45
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
46
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
47
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
48
+
49
+ # This class has dubious semantics and we only have it so that people can write
50
+ # params[:key] instead of params['key'].
51
+ class Mash < Hash
52
+
53
+ # @param constructor<Object>
54
+ # The default value for the mash. Defaults to an empty hash.
55
+ #
56
+ # @details [Alternatives]
57
+ # If constructor is a Hash, a new mash will be created based on the keys of
58
+ # the hash and no default value will be set.
59
+ def initialize(constructor = {})
60
+ if constructor.is_a?(Hash)
61
+ super()
62
+ update(constructor)
63
+ else
64
+ super(constructor)
65
+ end
66
+ end
67
+
68
+ # @param orig<Object> Mash being copied
69
+ #
70
+ # @return [Object] A new copied Mash
71
+ def initialize_copy(orig)
72
+ super
73
+ # Handle nested values
74
+ each do |k,v|
75
+ if v.kind_of?(Mash) || v.is_a?(Array)
76
+ self[k] = v.dup
77
+ end
78
+ end
79
+ self
80
+ end
81
+
82
+ # @param key<Object> The default value for the mash. Defaults to nil.
83
+ #
84
+ # @details [Alternatives]
85
+ # If key is a Symbol and it is a key in the mash, then the default value will
86
+ # be set to the value matching the key.
87
+ def default(key = nil)
88
+ if key.is_a?(Symbol) && include?(key = key.to_s)
89
+ self[key]
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
96
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
97
+
98
+ # @param key<Object> The key to set.
99
+ # @param value<Object>
100
+ # The value to set the key to.
101
+ #
102
+ # @see Mash#convert_key
103
+ # @see Mash#convert_value
104
+ def []=(key, value)
105
+ regular_writer(convert_key(key), convert_value(value))
106
+ end
107
+
108
+ # @param other_hash<Hash>
109
+ # A hash to update values in the mash with. The keys and the values will be
110
+ # converted to Mash format.
111
+ #
112
+ # @return [Mash] The updated mash.
113
+ def update(other_hash)
114
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
115
+ self
116
+ end
117
+
118
+ alias_method :merge!, :update
119
+
120
+ # @param key<Object> The key to check for. This will be run through convert_key.
121
+ #
122
+ # @return [Boolean] True if the key exists in the mash.
123
+ def key?(key)
124
+ super(convert_key(key))
125
+ end
126
+
127
+ # def include? def has_key? def member?
128
+ alias_method :include?, :key?
129
+ alias_method :has_key?, :key?
130
+ alias_method :member?, :key?
131
+
132
+ # @param key<Object> The key to fetch. This will be run through convert_key.
133
+ # @param *extras<Array> Default value.
134
+ #
135
+ # @return [Object] The value at key or the default value.
136
+ def fetch(key, *extras)
137
+ super(convert_key(key), *extras)
138
+ end
139
+
140
+ # @param *indices<Array>
141
+ # The keys to retrieve values for. These will be run through +convert_key+.
142
+ #
143
+ # @return [Array] The values at each of the provided keys
144
+ def values_at(*indices)
145
+ indices.collect {|key| self[convert_key(key)]}
146
+ end
147
+
148
+ # @param hash<Hash> The hash to merge with the mash.
149
+ #
150
+ # @return [Mash] A new mash with the hash values merged in.
151
+ def merge(hash)
152
+ self.dup.update(hash)
153
+ end
154
+
155
+ # @param key<Object>
156
+ # The key to delete from the mash.\
157
+ def delete(key)
158
+ super(convert_key(key))
159
+ end
160
+
161
+ # @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
162
+ #
163
+ # @return [Mash] A new mash without the selected keys.
164
+ #
165
+ # @example
166
+ # { :one => 1, :two => 2, :three => 3 }.except(:one)
167
+ # #=> { "two" => 2, "three" => 3 }
168
+ def except(*keys)
169
+ super(*keys.map {|k| convert_key(k)})
170
+ end
171
+
172
+ # Used to provide the same interface as Hash.
173
+ #
174
+ # @return [Mash] This mash unchanged.
175
+ def stringify_keys!; self end
176
+
177
+ # @return [Hash] The mash as a Hash with symbolized keys.
178
+ def symbolize_keys
179
+ h = Hash.new(default)
180
+ each { |key, val| h[key.to_sym] = val }
181
+ h
182
+ end
183
+
184
+ # @return [Hash] The mash as a Hash with string keys.
185
+ def to_hash
186
+ Hash.new(default).merge(self)
187
+ end
188
+
189
+ # @return [Mash] Convert a Hash into a Mash
190
+ # The input Hash's default value is maintained
191
+ def self.from_hash(hash)
192
+ mash = Mash.new(hash)
193
+ mash.default = hash.default
194
+ mash
195
+ end
196
+
197
+ protected
198
+ # @param key<Object> The key to convert.
199
+ #
200
+ # @param [Object]
201
+ # The converted key. If the key was a symbol, it will be converted to a
202
+ # string.
203
+ #
204
+ # @api private
205
+ def convert_key(key)
206
+ key.kind_of?(Symbol) ? key.to_s : key
207
+ end
208
+
209
+ # @param value<Object> The value to convert.
210
+ #
211
+ # @return [Object]
212
+ # The converted value. A Hash or an Array of hashes, will be converted to
213
+ # their Mash equivalents.
214
+ #
215
+ # @api private
216
+ def convert_value(value)
217
+ if value.class == Hash
218
+ Mash.from_hash(value)
219
+ elsif value.is_a?(Array)
220
+ value.collect { |e| convert_value(e) }
221
+ else
222
+ value
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,10 @@
1
+ {
2
+ "repo": "git@github.com:tongueroo/chapdemo.git",
3
+ "branch": "master",
4
+ "application": "chapdemo",
5
+ "deploy_to": "/data/chapdemo",
6
+ "strategy": "checkout",
7
+ "keep": 5,
8
+ "user": "deploy",
9
+ "group": "deploy"
10
+ }
@@ -0,0 +1,2 @@
1
+ chap: /etc/chef/chap.json
2
+ node: /etc/chef/node.json
@@ -0,0 +1,5 @@
1
+ {
2
+ "environment": "staging",
3
+ "application": "chapdemo",
4
+ "instance_role": "app"
5
+ }
@@ -0,0 +1,15 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile ~/.gitignore_global
6
+
7
+ # Ignore bundler config
8
+ /.bundle
9
+
10
+ # Ignore the default SQLite database.
11
+ /db/*.sqlite3
12
+
13
+ # Ignore all logfiles and tempfiles.
14
+ /log/*.log
15
+ /tmp
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rack'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ log "cd #{release_path} && bundle"
4
+ run "cd #{release_path} && echo bundle"
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ restart = case node[:instance_role]
4
+ when 'app'
5
+ "touch tmp/restart.txt"
6
+ when 'resque'
7
+ "rvmsudo bluepill restart resque"
8
+ end
9
+ log "cd #{current_path} && #{restart}"
10
+ run "cd #{current_path} && echo '#{restart}'"
@@ -0,0 +1 @@
1
+ run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["OK"]]}
File without changes
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chap do
4
+ before(:each) do
5
+ @root = File.expand_path("../../../", __FILE__)
6
+ @system_root = "#{@root}/spec/fixtures/system_root"
7
+ end
8
+
9
+ describe "deploy" do
10
+ before(:each) do
11
+ setup_files
12
+
13
+ @chap = Chap::Runner.new(
14
+ :quiet => true,
15
+ :config => "#{@system_root}/etc/chef/chap.yml"
16
+ )
17
+ timestamp = @chap.config.timestamp
18
+ @release_path = "#{@system_root}/data/chapdemo/releases/#{timestamp}"
19
+ end
20
+
21
+ it "should deploy code" do
22
+ @chap.deploy
23
+ File.exist?("#{@system_root}/data/chapdemo/shared").should be_true
24
+ File.exist?(@release_path).should be_true
25
+ File.symlink?("#{@system_root}/data/chapdemo/current").should be_true
26
+ releases = Dir.glob("#{@system_root}/data/chapdemo/releases/*").size
27
+ releases.should >= 1
28
+ releases.should <= 2 # test the keep option
29
+ end
30
+
31
+ it "should deploy code via command line" do
32
+ system("cd #{@root} && ./bin/chap deploy -q -c #{@system_root}/etc/chef/chap.yml")
33
+ releases = Dir.glob("#{@system_root}/data/chapdemo/releases/*").sort
34
+ timestamp = releases.last.split('/').last
35
+ release_path = "#{@system_root}/data/chapdemo/releases/#{timestamp}"
36
+
37
+ File.exist?("#{@system_root}/data/chapdemo/shared").should be_true
38
+ File.exist?(release_path).should be_true
39
+ File.symlink?("#{@system_root}/data/chapdemo/current").should be_true
40
+ releases = Dir.glob("#{@system_root}/data/chapdemo/releases/*").size
41
+ releases.should >= 1
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ require "pp"
2
+ require "bundler"
3
+
4
+ Bundler.require(:development)
5
+
6
+ $root = File.expand_path('../../', __FILE__)
7
+
8
+ require "#{$root}/lib/chap"
9
+
10
+ # use to overwrite the demo one created from examples/chap.json for testing
11
+ def setup_files(options={})
12
+ FileUtils.mkdir_p("#{@system_root}/etc/chef")
13
+ system("cd #{@root} && ./bin/chap setup -q -f -o #{@system_root}/etc/chef")
14
+ update_chap_yml(options)
15
+ update_chap_json(options)
16
+ end
17
+
18
+ # change system_root for chap.yml
19
+ def update_chap_yml(options)
20
+ path = "#{@system_root}/etc/chef/chap.yml"
21
+ yaml = YAML.load(IO.read(path))
22
+ yaml['chap'] = @system_root + yaml['chap']
23
+ yaml['node'] = @system_root + yaml['node']
24
+ File.open("#{@system_root}/etc/chef/chap.yml", "w") do |file|
25
+ data = YAML.dump(yaml)
26
+ file.write(data)
27
+ end
28
+ end
29
+
30
+ # change system_root for chap.json
31
+ def update_chap_json(options)
32
+ chap = JSON.parse(IO.read("#{@system_root}/etc/chef/chap.json"))
33
+ chap['deploy_to'] = @system_root + chap['deploy_to']
34
+ chap['strategy'] = ENV['TIER'] == '2' ? "checkout" : "copy"
35
+ chap['source'] = @root + "/spec/fixtures/chapdemo"
36
+ chap['keep'] = 2
37
+ chap['user'] = ENV['USER']
38
+ chap['group'] = nil
39
+ File.open("#{@system_root}/etc/chef/chap.json", "w") do |file|
40
+ json = JSON.pretty_generate(chap)
41
+ file.write(json)
42
+ end
43
+ end