doo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +38 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/bin/doo +5 -0
- data/examples/sample.rb +37 -0
- data/lib/doo/base.rb +41 -0
- data/lib/doo/cli.rb +55 -0
- data/lib/doo/stock/common.rb +0 -0
- data/lib/doo/stock/prereqs.rb +13 -0
- data/lib/doo/stock/render_to_file.rb +16 -0
- data/lib/doo/stock/run_locally.rb +12 -0
- data/lib/doo/stock/run_on_server.rb +38 -0
- data/lib/doo.rb +7 -0
- data/spec/doo/base_spec.rb +73 -0
- data/spec/doo/cli_spec.rb +38 -0
- metadata +84 -0
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
## Just Doo It
|
2
|
+
|
3
|
+
Doo is an experiment in taking a relentlessly polymorphic approach to deployment scripting. Like most deployment tools, doo lets you define executable blocks of deployment code independent of the configuration particulars that get bound into them at runtime. Where doo is different is in its succinctness (its core is just shy of 100 LoC), and in its use of polymorphism to realize stacking contexts. This lets you do useful things like defining configuration parameters on a project-wide, server-specific, and even task-specific level without having to rewrite (or doctor the hell out of) your deployment code.
|
4
|
+
|
5
|
+
Because executable blocks are also polymorphic, you can mix and match provider blocks into your recipes. Want to have one deployment recipe for local and remote targets? No problem. Want to abstract away the particulars of your server's OS? We can do that, too.
|
6
|
+
|
7
|
+
Doo layers a thin DSL on top of bare Ruby that facilitates polymorphic behaviour, and leans on a small number of built-in (and optional) deployment recipes to provide useful functionality.
|
8
|
+
|
9
|
+
## Play With It
|
10
|
+
|
11
|
+
gem install doo
|
12
|
+
|
13
|
+
Check out examples/sample.rb to get started.
|
14
|
+
|
15
|
+
## LICENSE
|
16
|
+
|
17
|
+
(The MIT License)
|
18
|
+
|
19
|
+
Copyright (c) 2010 Mat Trudel
|
20
|
+
|
21
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
22
|
+
a copy of this software and associated documentation files (the
|
23
|
+
'Software'), to deal in the Software without restriction, including
|
24
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
25
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
26
|
+
permit persons to whom the Software is furnished to do so, subject to
|
27
|
+
the following conditions:
|
28
|
+
|
29
|
+
The above copyright notice and this permission notice shall be
|
30
|
+
included in all copies or substantial portions of the Software.
|
31
|
+
|
32
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
33
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
34
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
35
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
36
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
37
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
38
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gem|
|
4
|
+
gem.version
|
5
|
+
gem.name = "doo"
|
6
|
+
gem.summary = %Q{Doo - an stacked-cotnext approach to deployment scripting }
|
7
|
+
gem.description = %Q{Doo is a deployment scripting tool in the vein of capistrano and sprinkle that uses stacked contexts and a aspect-ish data model}
|
8
|
+
gem.homepage = "http://github.com/mtrudel/doo"
|
9
|
+
gem.authors = [ "Mat Trudel" ]
|
10
|
+
gem.email = [ "mat@geeky.net" ]
|
11
|
+
gem.executables = %W(doo)
|
12
|
+
gem.files = FileList["[A-Z]*", "{bin,examples,lib,spec}/**/*", 'lib/jeweler/templates/.gitignore']
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'spec/rake/spectask'
|
19
|
+
desc "Run all tests"
|
20
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
21
|
+
t.spec_files = FileList['spec/**/*.rb']
|
22
|
+
end
|
23
|
+
|
24
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/doo
ADDED
data/examples/sample.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# You can set options like so
|
2
|
+
set :web, { :server => "example.com", :production => true }
|
3
|
+
set :arg, "foo"
|
4
|
+
|
5
|
+
# options can also be blocks, in which case they become callable
|
6
|
+
set :show_uptime do
|
7
|
+
run "uptime"
|
8
|
+
end
|
9
|
+
|
10
|
+
# You can bind a variable's content into the current context like so
|
11
|
+
using web # Now 'server' and 'production' will be top-level variables
|
12
|
+
|
13
|
+
# You can run local commands inside a block
|
14
|
+
run_locally do
|
15
|
+
run "echo #{arg}" # Will output 'foo'
|
16
|
+
end
|
17
|
+
|
18
|
+
# optionally giving the blocks overriding arguments
|
19
|
+
run_locally :arg => "bar" do
|
20
|
+
run "echo #{arg}"# Will output 'bar'
|
21
|
+
end
|
22
|
+
|
23
|
+
# You can run a set of commands on a remote server like so
|
24
|
+
run_on_server web do
|
25
|
+
run "whoami"
|
26
|
+
end
|
27
|
+
# You can define servers as hashes or bare hostnames...
|
28
|
+
run_on_server [web, "otherhost.com"] do
|
29
|
+
run "hostname"
|
30
|
+
# This runs the show_uptime block with the current set of variables
|
31
|
+
show_uptime
|
32
|
+
end
|
33
|
+
|
34
|
+
# You can run a block into a file like so
|
35
|
+
render_to_file 'sample.out' do
|
36
|
+
run "echo #{arg}" # Will ouput 'echo foo' to sample.out
|
37
|
+
end
|
data/lib/doo/base.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Doo
|
2
|
+
class Base
|
3
|
+
def load(filename)
|
4
|
+
instance_eval(File.read(filename), filename)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(variables = {})
|
8
|
+
using variables
|
9
|
+
end
|
10
|
+
|
11
|
+
def using(variables = {})
|
12
|
+
variables.each { |k,v| set k, v }
|
13
|
+
end
|
14
|
+
|
15
|
+
def set(k, v=nil, &block)
|
16
|
+
singleton_class.class_eval do
|
17
|
+
define_method k, (block_given?)? Proc.new { |*args|
|
18
|
+
if args.empty?
|
19
|
+
instance_eval &block
|
20
|
+
else
|
21
|
+
with_clone(args[0], &block)
|
22
|
+
end
|
23
|
+
} : lambda {v}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_clone(variables = {}, &block)
|
28
|
+
obj = clone
|
29
|
+
obj.using variables
|
30
|
+
obj.instance_eval &block if block_given?
|
31
|
+
obj
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def singleton_class
|
36
|
+
class << self
|
37
|
+
self
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/doo/cli.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'doo'
|
3
|
+
|
4
|
+
module Doo
|
5
|
+
module CLI
|
6
|
+
module Options
|
7
|
+
def self.parse!(args)
|
8
|
+
options = {}
|
9
|
+
optparse = OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: doo [options] file1 file2 ..."
|
11
|
+
|
12
|
+
options[:verbose] = false
|
13
|
+
opts.on( '-v', '--verbose', 'Output more information' ) do
|
14
|
+
options[:verbose] = true
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on( '-s', '--set key=value', 'Set runtime values' ) do |arg|
|
18
|
+
options[arg.split('=')[0]] = arg.split('=')[1]
|
19
|
+
end
|
20
|
+
|
21
|
+
options[:dry_run] = false
|
22
|
+
opts.on( '-d', '--dry-run', 'Do a dry-run' ) do
|
23
|
+
options[:dry_run] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
27
|
+
puts opts
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
begin
|
32
|
+
optparse.parse!(args)
|
33
|
+
raise "You need to specify one or more files to process" if args.empty?
|
34
|
+
rescue
|
35
|
+
puts $!
|
36
|
+
puts optparse
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
options
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Doo
|
47
|
+
module CLI
|
48
|
+
def self.start
|
49
|
+
inst = Doo::Base.new(Options.parse!(ARGV))
|
50
|
+
ARGV.each do |filename|
|
51
|
+
inst.load(filename)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Doo::Base.class_eval do
|
2
|
+
def render_to_file(filename, variables = {}, &block)
|
3
|
+
File.open(filename, 'w') do |file|
|
4
|
+
with_clone(variables) do
|
5
|
+
@file = file
|
6
|
+
def run(cmd)
|
7
|
+
puts "Rendering #{cmd}" if verbose
|
8
|
+
@file.puts cmd
|
9
|
+
end
|
10
|
+
check_prereqs do
|
11
|
+
instance_eval &block if block_given?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
SshCommand = "ssh -S '~/.ssh/master-%l-%r@%h:%p'"
|
2
|
+
Doo::Base.class_eval do
|
3
|
+
def run_on_server(servers, variables = {}, &block)
|
4
|
+
[servers].flatten.each do |server|
|
5
|
+
with_clone(server.is_a?(Hash)? variables.merge(server) : variables) do
|
6
|
+
@host = server.is_a?(Hash)? server[:server] : server
|
7
|
+
def run(cmd, opt = {})
|
8
|
+
puts "Running ssh #{(!opt.include? :pty || opt[:pty])? '-t' : ''} #{@host} \"#{cmd}\"" if verbose
|
9
|
+
system("#{SshCommand} #{(!opt.include? :pty || opt[:pty])? '-t' : ''} #{@host} \"#{cmd}\"") || raise("SSH Error") unless dry_run
|
10
|
+
$?
|
11
|
+
end
|
12
|
+
|
13
|
+
def put(local, remote)
|
14
|
+
puts "scp -r \"#{local}\" \"#{@host}:#{remote}\"" if verbose
|
15
|
+
system("scp -r \"#{local}\" \"#{@host}:#{remote}\"") || raise("SSH Error") unless dry_run
|
16
|
+
$?
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
system("#{SshCommand} -MNf #{@host}") || raise("SSH Error") unless dry_run
|
21
|
+
instance_eval &block if block_given?
|
22
|
+
ensure
|
23
|
+
system("#{SshCommand} -Oexit #{@host}") || raise("SSH Error") unless dry_run
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Array.class_eval do
|
31
|
+
def with_role(role)
|
32
|
+
select { |obj| [obj[:role]].flatten.member? role }
|
33
|
+
end
|
34
|
+
|
35
|
+
def except_role(role)
|
36
|
+
reject { |obj| [obj[:role]].flatten.member? role }
|
37
|
+
end
|
38
|
+
end
|
data/lib/doo.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'doo/base'
|
3
|
+
|
4
|
+
describe Doo::Base do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@inst = Doo::Base.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "can read in other files" do
|
11
|
+
@inst.should_receive("spamcan").once
|
12
|
+
Tempfile.open(nil) do |tmpfile|
|
13
|
+
tmpfile.puts("spamcan")
|
14
|
+
tmpfile.rewind
|
15
|
+
@inst.load(tmpfile.path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sets itself up properly" do
|
20
|
+
inst = Doo::Base.new(:extra => "cheeps")
|
21
|
+
inst.should respond_to :extra
|
22
|
+
inst.extra.should == "cheeps"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "knows how to add context to itself" do
|
26
|
+
@inst.using(:extra => "ham")
|
27
|
+
@inst.should respond_to :extra
|
28
|
+
@inst.extra.should == "ham"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "knows how to set a variable" do
|
32
|
+
@inst.set :extra, "bologna"
|
33
|
+
@inst.should respond_to :extra
|
34
|
+
@inst.extra.should == "bologna"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "knows how to set a block" do
|
38
|
+
@inst.set :extra do
|
39
|
+
its_not_real_meat
|
40
|
+
end
|
41
|
+
@inst.should respond_to :extra
|
42
|
+
@inst.should_receive(:its_not_real_meat).exactly(1).times
|
43
|
+
@inst.extra
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns a clone of itself" do
|
47
|
+
@inst.instance_eval do
|
48
|
+
def something_extra
|
49
|
+
"bacon"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
@inst.with_clone.something_extra.should == "bacon"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "runs code in a clone" do
|
56
|
+
@inst.instance_eval do
|
57
|
+
def something_extra
|
58
|
+
"bacon"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@inst.with_clone do
|
62
|
+
something_extra.should == "bacon"
|
63
|
+
end
|
64
|
+
@inst.should_receive(:something_extra).exactly(0).times
|
65
|
+
end
|
66
|
+
|
67
|
+
it "sets variables in the clone but not in itself" do
|
68
|
+
@inst.with_clone(:extra => "fries") do
|
69
|
+
extra.should == "fries"
|
70
|
+
end
|
71
|
+
@inst.should_not respond_to :extra
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'doo/cli'
|
2
|
+
|
3
|
+
describe Doo::CLI::Options do
|
4
|
+
it "should message and error on no args" do
|
5
|
+
lambda { Doo::CLI::Options.parse!([]) }.should raise_error SystemExit
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should message and error on --help" do
|
9
|
+
lambda { Doo::CLI::Options.parse!(["--help"]) }.should raise_error SystemExit
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should set on --dry-run" do
|
13
|
+
Doo::CLI::Options.parse!(["-d", "foo"]).member?(:dry_run).should == true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should set on --verbose" do
|
17
|
+
Doo::CLI::Options.parse!(["-v", "foo"]).member?(:verbose).should == true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should set variables" do
|
21
|
+
Doo::CLI::Options.parse!(["-swoz=bar", "foo"])["woz"].should == "bar"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should pull off varialbes and leave files" do
|
25
|
+
args = ["-d", "foo", "bar"]
|
26
|
+
Doo::CLI::Options.parse!(args)
|
27
|
+
args.should == ["foo", "bar"]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should run all files" do
|
31
|
+
ARGV = ["-d", "foo", "bar"]
|
32
|
+
inst = Doo::Base.new
|
33
|
+
Doo::Base.should_receive(:new).and_return inst
|
34
|
+
inst.should_receive(:load).with("foo")
|
35
|
+
inst.should_receive(:load).with("bar")
|
36
|
+
Doo::CLI.start
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: doo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mat Trudel
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-15 00:00:00 -04:00
|
19
|
+
default_executable: doo
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Doo is a deployment scripting tool in the vein of capistrano and sprinkle that uses stacked contexts and a aspect-ish data model
|
23
|
+
email:
|
24
|
+
- mat@geeky.net
|
25
|
+
executables:
|
26
|
+
- doo
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files:
|
30
|
+
- README.md
|
31
|
+
files:
|
32
|
+
- README.md
|
33
|
+
- Rakefile
|
34
|
+
- VERSION
|
35
|
+
- bin/doo
|
36
|
+
- examples/sample.rb
|
37
|
+
- lib/doo.rb
|
38
|
+
- lib/doo/base.rb
|
39
|
+
- lib/doo/cli.rb
|
40
|
+
- lib/doo/stock/common.rb
|
41
|
+
- lib/doo/stock/prereqs.rb
|
42
|
+
- lib/doo/stock/render_to_file.rb
|
43
|
+
- lib/doo/stock/run_locally.rb
|
44
|
+
- lib/doo/stock/run_on_server.rb
|
45
|
+
- spec/doo/base_spec.rb
|
46
|
+
- spec/doo/cli_spec.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/mtrudel/doo
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --charset=UTF-8
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.7
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Doo - an stacked-cotnext approach to deployment scripting
|
81
|
+
test_files:
|
82
|
+
- spec/doo/base_spec.rb
|
83
|
+
- spec/doo/cli_spec.rb
|
84
|
+
- examples/sample.rb
|