screwcap 0.1

Sign up to get free protection for your applications and to get access to all the features.
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