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 ADDED
@@ -0,0 +1,14 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ gem "net-ssh"
5
+ gem "net-scp"
6
+ gem "net-ssh-gateway"
7
+
8
+ group :development do
9
+ gem 'hoe'
10
+ gem 'newgem'
11
+ gem 'ruby-debug'
12
+ gem 'mocha'
13
+ gem 'rspec', '1.3.1'
14
+ end
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
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2010-10-17
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
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
@@ -0,0 +1,11 @@
1
+ class Array
2
+ def blank?
3
+ self.nil? || self.size == 0
4
+ end
5
+ end
6
+
7
+ class NilClass
8
+ def blank?
9
+ true
10
+ end
11
+ end
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