screwcap 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +14 -0
- data/Gemfile.lock +49 -0
- data/History.txt +4 -0
- data/Manifest.txt +37 -0
- data/README.rdoc +50 -0
- data/Rakefile +25 -0
- data/bin/screwcap +35 -0
- data/lib/exts.rb +11 -0
- data/lib/screwcap.rb +29 -0
- data/lib/screwcap/base.rb +56 -0
- data/lib/screwcap/command_set.rb +56 -0
- data/lib/screwcap/deployer.rb +104 -0
- data/lib/screwcap/sequence.rb +18 -0
- data/lib/screwcap/server.rb +55 -0
- data/lib/screwcap/task.rb +139 -0
- data/lib/trollop.rb +782 -0
- data/screwcap.gemspec +22 -0
- data/spec/command_set_spec.rb +51 -0
- data/spec/deployer_spec.rb +76 -0
- data/spec/sequence_spec.rb +21 -0
- data/spec/server_spec.rb +44 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/task_spec.rb +75 -0
- data/tasks/rspec.rake +21 -0
- data/test/config/command_sets.rb +87 -0
- data/test/config/expect.rb +23 -0
- data/test/config/gateway.rb +8 -0
- data/test/config/no_server.rb +3 -0
- data/test/config/rails_tasks.rb +37 -0
- data/test/config/simple_recipe.rb +40 -0
- data/test/config/undefined_command_set.rb +9 -0
- data/test/config/undefined_item.rb +4 -0
- data/test/config/undefined_server.rb +3 -0
- data/test/config/unknown_use.rb +1 -0
- data/test/config/use.rb +1 -0
- data/test/test_command_set.rb +40 -0
- metadata +171 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
RedCloth (4.2.3)
|
5
|
+
activesupport (2.3.9)
|
6
|
+
columnize (0.3.1)
|
7
|
+
hoe (2.6.2)
|
8
|
+
rake (>= 0.8.7)
|
9
|
+
rubyforge (>= 2.0.4)
|
10
|
+
json_pure (1.4.6)
|
11
|
+
linecache (0.43)
|
12
|
+
mocha (0.9.8)
|
13
|
+
rake
|
14
|
+
net-scp (1.0.4)
|
15
|
+
net-ssh (>= 1.99.1)
|
16
|
+
net-ssh (2.0.23)
|
17
|
+
net-ssh-gateway (1.0.1)
|
18
|
+
net-ssh (>= 1.99.1)
|
19
|
+
newgem (1.5.3)
|
20
|
+
RedCloth (>= 4.1.1)
|
21
|
+
activesupport (~> 2.3.4)
|
22
|
+
hoe (>= 2.4.0)
|
23
|
+
rubigen (>= 1.5.3)
|
24
|
+
syntax (>= 1.0.0)
|
25
|
+
rake (0.8.7)
|
26
|
+
rspec (1.3.1)
|
27
|
+
rubigen (1.5.5)
|
28
|
+
activesupport (~> 2.3.5)
|
29
|
+
ruby-debug (0.10.3)
|
30
|
+
columnize (>= 0.1)
|
31
|
+
ruby-debug-base (~> 0.10.3.0)
|
32
|
+
ruby-debug-base (0.10.3)
|
33
|
+
linecache (>= 0.3)
|
34
|
+
rubyforge (2.0.4)
|
35
|
+
json_pure (>= 1.1.7)
|
36
|
+
syntax (1.0.0)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
hoe
|
43
|
+
mocha
|
44
|
+
net-scp
|
45
|
+
net-ssh
|
46
|
+
net-ssh-gateway
|
47
|
+
newgem
|
48
|
+
rspec (= 1.3.1)
|
49
|
+
ruby-debug
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Gemfile
|
2
|
+
Gemfile.lock
|
3
|
+
History.txt
|
4
|
+
Manifest.txt
|
5
|
+
README.rdoc
|
6
|
+
Rakefile
|
7
|
+
bin/screwcap
|
8
|
+
lib/exts.rb
|
9
|
+
lib/screwcap.rb
|
10
|
+
lib/screwcap/base.rb
|
11
|
+
lib/screwcap/command_set.rb
|
12
|
+
lib/screwcap/deployer.rb
|
13
|
+
lib/screwcap/sequence.rb
|
14
|
+
lib/screwcap/server.rb
|
15
|
+
lib/screwcap/task.rb
|
16
|
+
lib/trollop.rb
|
17
|
+
screwcap.gemspec
|
18
|
+
spec/command_set_spec.rb
|
19
|
+
spec/deployer_spec.rb
|
20
|
+
spec/sequence_spec.rb
|
21
|
+
spec/server_spec.rb
|
22
|
+
spec/spec.opts
|
23
|
+
spec/spec_helper.rb
|
24
|
+
spec/task_spec.rb
|
25
|
+
tasks/rspec.rake
|
26
|
+
test/config/command_sets.rb
|
27
|
+
test/config/expect.rb
|
28
|
+
test/config/gateway.rb
|
29
|
+
test/config/no_server.rb
|
30
|
+
test/config/rails_tasks.rb
|
31
|
+
test/config/simple_recipe.rb
|
32
|
+
test/config/undefined_command_set.rb
|
33
|
+
test/config/undefined_item.rb
|
34
|
+
test/config/undefined_server.rb
|
35
|
+
test/config/unknown_use.rb
|
36
|
+
test/config/use.rb
|
37
|
+
test/test_command_set.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
= screwcap
|
2
|
+
|
3
|
+
* http://github.com/#{github_username}/#{project_name}
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Screwcap is a library that wraps Net::SSH and makes it easy to perform actions on remote servers. It's not smart and does not make any assumptions about your setup.
|
8
|
+
|
9
|
+
The most obvious task you could use screwcap for is deploying rails applications to remote servers.
|
10
|
+
|
11
|
+
== FEATURES/PROBLEMS:
|
12
|
+
|
13
|
+
* Screwcap is currently not ready for production use as it is under active development.
|
14
|
+
|
15
|
+
== SYNOPSIS:
|
16
|
+
|
17
|
+
FIX (code sample of usage)
|
18
|
+
|
19
|
+
== REQUIREMENTS:
|
20
|
+
|
21
|
+
* FIX (list of requirements)
|
22
|
+
|
23
|
+
== INSTALL:
|
24
|
+
|
25
|
+
* gem install screwcap
|
26
|
+
|
27
|
+
== LICENSE:
|
28
|
+
|
29
|
+
(The MIT License)
|
30
|
+
|
31
|
+
Copyright (c) 2010 Grant Ammons
|
32
|
+
|
33
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
34
|
+
a copy of this software and associated documentation files (the
|
35
|
+
'Software'), to deal in the Software without restriction, including
|
36
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
37
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
38
|
+
permit persons to whom the Software is furnished to do so, subject to
|
39
|
+
the following conditions:
|
40
|
+
|
41
|
+
The above copyright notice and this permission notice shall be
|
42
|
+
included in all copies or substantial portions of the Software.
|
43
|
+
|
44
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
45
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
46
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
47
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
48
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
49
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
50
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/screwcap'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'screwcap' do
|
14
|
+
self.version = '0.1'
|
15
|
+
self.developer 'Grant Ammons', 'grant@pipelinedeals.com'
|
16
|
+
self.rubyforge_name = self.name # TODO this is default value
|
17
|
+
self.extra_deps = [['net-ssh','>= 2.0.23'],['net-ssh-gateway','>=1.0.1']]
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'newgem/tasks'
|
21
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
22
|
+
|
23
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
24
|
+
remove_task :default
|
25
|
+
task :default => :spec
|
data/bin/screwcap
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/screwcap')
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/trollop')
|
6
|
+
|
7
|
+
|
8
|
+
p = Trollop::Parser.new do
|
9
|
+
opt :silent, "Be silent"
|
10
|
+
opt :nocolor, "Do not color output"
|
11
|
+
opt :debug, "Turn on debugger. Will print full stacktrace if an exeception was raised"
|
12
|
+
opt :help, "Show this message"
|
13
|
+
banner <<-EOF
|
14
|
+
|
15
|
+
Usage: screwcap [options] file task_name [[task_name2], [task_name3], ...]
|
16
|
+
Example: screwcap config/deploy.rb deploy_to_staging
|
17
|
+
EOF
|
18
|
+
end
|
19
|
+
|
20
|
+
opts = Trollop::with_standard_exception_handling p do
|
21
|
+
opts = p.parse ARGV
|
22
|
+
if opts[:debug] == true
|
23
|
+
require 'ruby-debug'
|
24
|
+
Debugger.start
|
25
|
+
end
|
26
|
+
raise Trollop::HelpNeeded if ARGV.size < 2
|
27
|
+
recipe_file = ARGV.shift
|
28
|
+
begin
|
29
|
+
Deployer.new(opts.merge(:recipe_file => recipe_file)).run! ARGV.map {|a| a.to_sym }
|
30
|
+
rescue Exception => e
|
31
|
+
raise e if opts[:debug] == true
|
32
|
+
$stderr << e
|
33
|
+
$stderr << "\n"
|
34
|
+
end
|
35
|
+
end
|
data/lib/exts.rb
ADDED
data/lib/screwcap.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'net/ssh'
|
5
|
+
require 'net/scp'
|
6
|
+
require 'net/ssh/gateway'
|
7
|
+
require 'ostruct'
|
8
|
+
require 'logger'
|
9
|
+
|
10
|
+
require 'exts'
|
11
|
+
require 'screwcap/base'
|
12
|
+
require 'screwcap/command_set'
|
13
|
+
require 'screwcap/task'
|
14
|
+
require 'screwcap/server'
|
15
|
+
require 'screwcap/sequence'
|
16
|
+
require 'screwcap/deployer'
|
17
|
+
|
18
|
+
module Screwcap
|
19
|
+
VERSION='0.1'
|
20
|
+
|
21
|
+
class TaskNotFound < RuntimeError; end
|
22
|
+
class NoServersDefined < Exception; end
|
23
|
+
class NoServerSelected < Exception; end
|
24
|
+
class ConfigurationError < Exception; end
|
25
|
+
class IncludeFileNotFound < Exception; end
|
26
|
+
class InvalidServer < Exception; end
|
27
|
+
class CommandSetDependencyError < Exception; end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Screwcap
|
2
|
+
class Base < OpenStruct
|
3
|
+
|
4
|
+
# for debugging purposes only
|
5
|
+
#def method_missing(m, *args, &block)
|
6
|
+
# $stdout << "For #{self.class.to_s}: calling #{m}\n"
|
7
|
+
# super(m, args.first)
|
8
|
+
#end
|
9
|
+
|
10
|
+
def set(var, *args)
|
11
|
+
method_missing((var.to_s + "=").to_sym, args.first)
|
12
|
+
end
|
13
|
+
|
14
|
+
def log(msg, options = {})
|
15
|
+
$stdout << msg unless self.__options[:silent] == true
|
16
|
+
end
|
17
|
+
|
18
|
+
def errorlog(msg)
|
19
|
+
$stderr << msg unless self.__options[:silent] == true
|
20
|
+
end
|
21
|
+
|
22
|
+
def bluebold(msg, options = {:clear => true})
|
23
|
+
if self.__options[:nocolor] == true
|
24
|
+
msg
|
25
|
+
else
|
26
|
+
"\033[1;36m#{msg}#{"\033[0m" if options[:clear]}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def blue(msg, options = {:clear => true})
|
31
|
+
if self.__options[:nocolor] == true
|
32
|
+
msg
|
33
|
+
else
|
34
|
+
"\033[0;36m#{msg}#{"\033[0m" if options[:clear]}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def red(msg)
|
40
|
+
if self.__options[:nocolor] == true
|
41
|
+
msg
|
42
|
+
else
|
43
|
+
"\033[0;31m#{msg}\033[0m"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def green(msg)
|
48
|
+
if self.__options[:nocolor] == true
|
49
|
+
msg
|
50
|
+
else
|
51
|
+
"\033[0;32m#{msg}\033[0m"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class CommandSet < Screwcap::Base
|
2
|
+
def initialize(opts = {}, &block)
|
3
|
+
super
|
4
|
+
self.__name = opts[:name]
|
5
|
+
self.__options = opts
|
6
|
+
self.__commands = []
|
7
|
+
self.__command_sets = []
|
8
|
+
self.__block = block
|
9
|
+
end
|
10
|
+
|
11
|
+
# run a command. basically just pass it a string containing the command you want to run.
|
12
|
+
def run arg, options = {}
|
13
|
+
if arg.class == Symbol
|
14
|
+
self.__commands << {:command => self.send(arg), :type => :remote}
|
15
|
+
else
|
16
|
+
self.__commands << {:command => arg, :type => :remote}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# run a command. basically just pass it a string containing the command you want to run.
|
21
|
+
def local arg, options = {}
|
22
|
+
if arg.class == Symbol
|
23
|
+
self.__commands << {:command => self.send(arg), :type => :local}
|
24
|
+
else
|
25
|
+
self.__commands << {:command => arg, :type => :local}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def method_missing(m, *args)
|
33
|
+
if m.to_s[0..1] == "__" or [:run].include?(m) or m.to_s.reverse[0..0] == "="
|
34
|
+
super(m, args.first)
|
35
|
+
else
|
36
|
+
if cs = self.__command_sets.find {|cs| cs.name == m }
|
37
|
+
# eval what is in the block
|
38
|
+
clone_table_for(cs)
|
39
|
+
cs.__commands = []
|
40
|
+
cs.instance_eval(&cs.__block)
|
41
|
+
self.__commands += cs.__commands
|
42
|
+
else
|
43
|
+
raise NoMethodError, "Undefined method '#{m.to_s}' for Command Set :#{self.name.to_s}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def clone_table_for(object)
|
51
|
+
self.table.each do |k,v|
|
52
|
+
object.set(k, v) unless [:__command_sets, :name, :__commands, :__options, :__block].include?(k)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# The deployer is the class that holds the overall params from the screwcap tasks file, and it is also in charge of running the requested task.
|
2
|
+
#
|
3
|
+
# The deployer can be thought of as the "global scope" of your tasks file.
|
4
|
+
class Deployer < Screwcap::Base
|
5
|
+
|
6
|
+
# create a new deployer.
|
7
|
+
def initialize(opts = {})
|
8
|
+
opts = {:recipe_file => File.expand_path("./config/recipe.rb")}.merge opts
|
9
|
+
super
|
10
|
+
self.__options = opts
|
11
|
+
self.__tasks = []
|
12
|
+
self.__servers = []
|
13
|
+
self.__command_sets = []
|
14
|
+
self.__sequences = []
|
15
|
+
|
16
|
+
# ensure that deployer options will not be passed to tasks
|
17
|
+
opts.each_key {|k| self.delete_field(k) }
|
18
|
+
|
19
|
+
log "Reading #{self.__options[:recipe_file]}\n" unless self.__options[:silent] == true
|
20
|
+
|
21
|
+
file = File.open(File.expand_path("./#{self.__options[:recipe_file]}"))
|
22
|
+
data = file.read
|
23
|
+
|
24
|
+
instance_eval(data)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# create a task. Minimally, a task needs a :server specified to run the task on.
|
29
|
+
def task_for name, options = {}, &block
|
30
|
+
t = Task.new(options.merge(:name => name, :nocolor => self.__options[:nocolor], :silent => self.__options[:silent], :deployment_servers => self.__servers), &block)
|
31
|
+
clone_table_for(t)
|
32
|
+
t.instance_eval(&block)
|
33
|
+
self.__tasks << t
|
34
|
+
end
|
35
|
+
|
36
|
+
def command_set(name,options = {},&block)
|
37
|
+
t = CommandSet.new(options.merge(:name => name), &block)
|
38
|
+
clone_table_for(t)
|
39
|
+
self.__command_sets << t
|
40
|
+
end
|
41
|
+
|
42
|
+
def server(name, options = {}, &block)
|
43
|
+
server = Server.new(options.merge(:name => name, :servers => self.__servers, :silent => self.__options[:silent]))
|
44
|
+
self.__servers << server
|
45
|
+
end
|
46
|
+
|
47
|
+
def gateway(name, options = {}, &block)
|
48
|
+
server = Server.new(options.merge(:name => name, :is_gateway => true))
|
49
|
+
self.__servers << server
|
50
|
+
end
|
51
|
+
|
52
|
+
def sequence(name, options = {}, &block)
|
53
|
+
self.__sequences << Sequence.new(options.merge(:name => name, :deployment_task_names => self.__tasks.map(&:name)))
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def run!(*tasks)
|
58
|
+
tasks.flatten!
|
59
|
+
# sanity check each task
|
60
|
+
|
61
|
+
tasks_and_sequences = (self.__sequences.map(&:__name) + self.__tasks.map(&:__name))
|
62
|
+
tasks.each do |task_to_run|
|
63
|
+
raise(Screwcap::TaskNotFound, "Could not find task or sequence '#{task_to_run}' in recipe file #{self.__options[:recipe_file]}") unless tasks_and_sequences.include? task_to_run
|
64
|
+
end
|
65
|
+
|
66
|
+
tasks.each do |t|
|
67
|
+
sequence = self.__sequences.find {|s| s.__name == t }
|
68
|
+
if sequence
|
69
|
+
sequence.__task_names.each {|task_name| self.__tasks.find {|task| task.__name == task_name }.execute!}
|
70
|
+
else
|
71
|
+
self.__tasks.select {|task| task.name.to_s == t.to_s }.first.execute!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
$stdout << "\033[0m"
|
75
|
+
end
|
76
|
+
|
77
|
+
# dynamically include another file into an existing configuration file.
|
78
|
+
# by default, it looks for the include file in the same path as your tasks file you specified.
|
79
|
+
def use arg
|
80
|
+
if arg.is_a? Symbol
|
81
|
+
begin
|
82
|
+
dirname = File.dirname(self.__options[:recipe_file])
|
83
|
+
instance_eval(File.open(File.dirname(File.expand_path(self.__options[:recipe_file])) + "/" + arg.to_s + ".rb").read)
|
84
|
+
rescue Errno::ENOENT => e
|
85
|
+
raise Screwcap::IncludeFileNotFound, "Could not find #{File.expand_path("./"+arg.to_s + ".rb")}! If the file is elsewhere, call it by using 'use '/path/to/file.rb'"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
begin
|
89
|
+
instance_eval(File.open(File.dirname(File.expand_path(self.__options[:recipe_file])) + "/" + arg).read)
|
90
|
+
rescue Errno::ENOENT => e
|
91
|
+
raise Screwcap::IncludeFileNotFound, "Could not find #{File.expand_path(arg)}! If the file is elsewhere, call it by using 'use '/path/to/file.rb'"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def clone_table_for(object)
|
100
|
+
self.table.each do |k,v|
|
101
|
+
object.set(k, v) unless [:__options, :__tasks, :__servers].include?(k)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|