rigger 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "net-ssh-multi", "1.0.1"
4
+ gem "net-sftp", "2.0.5"
5
+ gem "popen4", "0.1.2"
6
+
7
+ group :development do
8
+ gem "rspec", "~> 2.3.0"
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.2"
11
+ gem "rcov", ">= 0"
12
+ gem "mocha", ">= 0.9.8"
13
+ gem "bourne", ">= 1.0"
14
+ end
@@ -0,0 +1,50 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ Platform (0.4.0)
5
+ bourne (1.0)
6
+ mocha (= 0.9.8)
7
+ diff-lcs (1.1.2)
8
+ git (1.2.5)
9
+ jeweler (1.5.2)
10
+ bundler (~> 1.0.0)
11
+ git (>= 1.2.5)
12
+ rake
13
+ mocha (0.9.8)
14
+ rake
15
+ net-sftp (2.0.5)
16
+ net-ssh (>= 2.0.9)
17
+ net-ssh (2.1.3)
18
+ net-ssh-gateway (1.0.1)
19
+ net-ssh (>= 1.99.1)
20
+ net-ssh-multi (1.0.1)
21
+ net-ssh (>= 1.99.2)
22
+ net-ssh-gateway (>= 0.99.0)
23
+ open4 (0.9.6)
24
+ popen4 (0.1.2)
25
+ Platform (>= 0.4.0)
26
+ open4 (>= 0.4.0)
27
+ rake (0.8.7)
28
+ rcov (0.9.9)
29
+ rspec (2.3.0)
30
+ rspec-core (~> 2.3.0)
31
+ rspec-expectations (~> 2.3.0)
32
+ rspec-mocks (~> 2.3.0)
33
+ rspec-core (2.3.1)
34
+ rspec-expectations (2.3.0)
35
+ diff-lcs (~> 1.1.2)
36
+ rspec-mocks (2.3.0)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ bourne (>= 1.0)
43
+ bundler (~> 1.0.0)
44
+ jeweler (~> 1.5.2)
45
+ mocha (>= 0.9.8)
46
+ net-sftp (= 2.0.5)
47
+ net-ssh-multi (= 1.0.1)
48
+ popen4 (= 0.1.2)
49
+ rcov
50
+ rspec (~> 2.3.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 James Golick
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.
@@ -0,0 +1,19 @@
1
+ = rigger
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to rigger
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 James Golick. See LICENSE.txt for
18
+ further details.
19
+
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "rigger"
16
+ gem.homepage = "http://github.com/jamesgolick/rigger"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{}
19
+ gem.description = %Q{}
20
+ gem.email = "jamesgolick@gmail.com"
21
+ gem.authors = ["James Golick"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ gem.add_runtime_dependency 'net-ssh-multi', '1.0.1'
25
+ gem.add_runtime_dependency 'popen4', '0.1.2'
26
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec) do |spec|
33
+ spec.pattern = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
37
+ spec.pattern = 'spec/**/*_spec.rb'
38
+ spec.rcov = true
39
+ end
40
+
41
+ task :default => :spec
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "rigger #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/TODO ADDED
@@ -0,0 +1 @@
1
+ * Make it easier to load recipes from installed gems or other files by hooking in to $LOAD_PATH.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.10
data/bin/rig ADDED
@@ -0,0 +1,4 @@
1
+ require "rigger"
2
+ require "rigger/cli"
3
+
4
+ Rigger::CLI.new.start
@@ -0,0 +1,22 @@
1
+ require "stringio"
2
+
3
+ set :current_path, "/var/www/fetlife"
4
+ server :app, "james@jamesgolick.com"
5
+ server :db, "fetlife@app1.dal.fetlife"
6
+
7
+ namespace :fuck do
8
+ task :test, :depends => [] do
9
+ #run_task "fuck:you"
10
+ run "ls -la"
11
+ end
12
+
13
+ task :you, :single => true do
14
+ run "mkdir -p /home/james/fuckit"
15
+ put "config/rig.rb", "/home/james/asdf.txt"
16
+ run "ls -la"
17
+ end
18
+
19
+ task :this do
20
+ run "ls -lah"
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ module Rigger
2
+ class Error < RuntimeError; end
3
+ class CommandError < Error; end
4
+ end
@@ -0,0 +1,78 @@
1
+ require "rigger/dsl"
2
+ require "rigger/task_execution_service"
3
+ require "optparse"
4
+
5
+ module Rigger
6
+ class CLI
7
+ def initialize(args = ARGV,
8
+ dsl = DSL,
9
+ task_execution_service_factory = TaskExecutionService)
10
+ @args = args
11
+ @dsl = dsl
12
+ @task_execution_service_factory = task_execution_service_factory
13
+ end
14
+
15
+ def start
16
+ @config = @dsl.new
17
+ options = parse_options
18
+ load_builtin_recipes
19
+ load_config_file
20
+
21
+ if options[:display_tasks]
22
+ display_tasks
23
+ else
24
+ run_tasks
25
+ end
26
+ end
27
+
28
+ protected
29
+ def load_config_file
30
+ @config_file = locate_config_file
31
+ @config.load_from_file(@config_file)
32
+ end
33
+
34
+ def load_builtin_recipes
35
+ Dir[File.dirname(__FILE__) + "/recipes/**/*.rb"].each do |f|
36
+ @config.load_from_file f
37
+ end
38
+ end
39
+
40
+ def locate_config_file
41
+ location = possible_config_locations.detect { |l| File.exist?(l) }
42
+ location || raise_missing_config_file
43
+ end
44
+
45
+ def possible_config_locations
46
+ ["config/rig.rb"]
47
+ end
48
+
49
+ def raise_missing_config_file
50
+ raise "Couldn't find a config file in #{possible_config_locations.inspect}"
51
+ end
52
+
53
+ def run_tasks
54
+ executor = @task_execution_service_factory.new(@config)
55
+
56
+ @args.each do |task_name|
57
+ executor.call(task_name)
58
+ end
59
+ end
60
+
61
+ def parse_options
62
+ {}.tap do |options|
63
+ OptionParser.new do |opts|
64
+ opts.banner = "Usage: #{$0} [options]"
65
+ opts.on("-T", "--tasks", "Display tasks.") do
66
+ options[:display_tasks] = true
67
+ end
68
+ end.parse!
69
+ end
70
+ end
71
+
72
+ def display_tasks
73
+ @config.tasks.sort_by { |t| t.name }.each do |task|
74
+ puts "rig #{task.name}"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,13 @@
1
+ require "net/ssh"
2
+
3
+ module Rigger
4
+ class ConnectionSet
5
+ def initialize
6
+ @connections = {}
7
+ end
8
+
9
+ def call(server)
10
+ @connections[server] ||= Net::SSH.start(server.host, server.user)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,64 @@
1
+ require "rigger/task"
2
+ require "rigger/server"
3
+
4
+ module Rigger
5
+ class DSL
6
+ attr_reader :tasks, :servers
7
+
8
+ def initialize(server_factory = Server,
9
+ task_factory = Task,
10
+ file = File)
11
+ @server_factory = server_factory
12
+ @task_factory = task_factory
13
+ @file = file
14
+ @tasks = {}
15
+ @servers = []
16
+ @current_namespace = []
17
+ @vars = {}
18
+ end
19
+
20
+ def namespace(name, &block)
21
+ @current_namespace << name
22
+ yield
23
+ @current_namespace.pop
24
+ end
25
+
26
+ def server(role, host, options = {})
27
+ @servers << @server_factory.new(role, host, options)
28
+ end
29
+
30
+ def task(name, options = {}, &block)
31
+ full_name = (@current_namespace + [name]).join(":")
32
+ @tasks[full_name] = @task_factory.new(full_name, options, block)
33
+ end
34
+
35
+ def load_from_file(filename)
36
+ instance_eval(@file.read(filename), filename)
37
+ end
38
+
39
+ def locate_task(name)
40
+ @tasks[name.to_s]
41
+ end
42
+
43
+ def set(name, value)
44
+ @vars[name.to_s] = value
45
+ end
46
+
47
+ def get(name)
48
+ @vars[name.to_s] || missing_var(name)
49
+ end
50
+
51
+ def fetch(name, default)
52
+ @vars.fetch(name.to_s, default)
53
+ end
54
+
55
+ protected
56
+ def missing_task(name)
57
+ raise "Can't find a task called #{name}."
58
+ end
59
+
60
+ def missing_var(name)
61
+ raise "Can't find a variable named #{name}."
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,57 @@
1
+ module Rigger
2
+ module ExecutionStrategy
3
+ class BasicExecutionStrategy
4
+ def initialize(executor_factory = TaskExecutor)
5
+ @executor_factory = TaskExecutor
6
+ end
7
+
8
+ def call(task, servers, config, execution_service)
9
+ @executor_factory.new(task, servers, execution_service, config).call
10
+ end
11
+ end
12
+
13
+ class SerialExecutionStrategy < BasicExecutionStrategy
14
+ def call(task, servers, config, execution_service)
15
+ servers.each do |s|
16
+ super(task, [s], config, execution_service)
17
+ end
18
+ end
19
+
20
+ def appropriate_strategy_for?(task)
21
+ task.options[:serial]
22
+ end
23
+ end
24
+
25
+ class SingleExecutionStrategy < BasicExecutionStrategy
26
+ def call(task, servers, config, execution_service)
27
+ super(task, [servers.first], config, execution_service)
28
+ end
29
+
30
+ def appropriate_strategy_for?(task)
31
+ task.options[:single]
32
+ end
33
+ end
34
+
35
+ class Selector
36
+ def initialize(basic_strategy = BasicExecutionStrategy.new,
37
+ strategies = [SerialExecutionStrategy.new,
38
+ SingleExecutionStrategy.new])
39
+ @basic_strategy = basic_strategy
40
+ @strategies = strategies
41
+ end
42
+
43
+ def call(task)
44
+ appropriate_strategies = @strategies.select do |s|
45
+ s.appropriate_strategy_for?(task)
46
+ end
47
+
48
+ if appropriate_strategies.length > 1
49
+ raise MultipleAppropriateStrategies,
50
+ "More than one strategy was appropriate for #{task.name}."
51
+ else
52
+ appropriate_strategies.first || @basic_strategy
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,21 @@
1
+ namespace :chef do
2
+ task :write_json, :serial => true do
3
+ role = @current_servers.first.role
4
+ conf = get(:chef_config).merge(:role => role, :run_list => "role[#{role}]").to_json
5
+ run "mkdir -p #{get(:release_path)}/config"
6
+ chef_config =<<-_END_
7
+ cookbook_path ["/var/chef/cookbooks",
8
+ "#{get(:release_path)}/cookbooks"]
9
+ log_level :info
10
+ file_cache_path "/var/chef"
11
+ role_path "#{get(:release_path)}/roles"
12
+ Chef::Log::Formatter.show_time = false
13
+ _END_
14
+ put chef_config, get(:release_path) + "/config/chef-solo.rb"
15
+ put conf, get(:release_path) + "/config/chef.json"
16
+ end
17
+
18
+ task :run do
19
+ run "chef-solo --config #{get(:release_path)}/config/chef-solo.rb -j #{get(:release_path)}/config/chef.json"
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ namespace :deploy do
2
+ task :bootstrap do
3
+ set :shared_path, "#{get(:deploy_to)}/shared"
4
+ set :releases_path, "#{get(:deploy_to)}/releases"
5
+ set :release_path, "#{get(:deploy_to)}/releases/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}"
6
+ set :current_path, "#{get(:deploy_to)}/current"
7
+ end
8
+
9
+ task :setup do
10
+ run_task("deploy:bootstrap")
11
+
12
+ run "mkdir -p #{get(:deploy_to)}"
13
+ run "mkdir -p #{get(:shared_path)}"
14
+ run "mkdir -p #{get(:releases_path)}"
15
+ end
16
+
17
+ # ripped off mostly from capistrano
18
+ task :keep_releases do
19
+ keep = fetch(:keep_releases, 5)
20
+ releases_path = get(:releases_path)
21
+ releases = capture("cd #{releases_path} && ls -x").strip.split.sort
22
+
23
+ if keep >= releases.length
24
+ puts "no old releases to clean up"
25
+ else
26
+ puts "keeping #{keep} of #{releases.length} deployed releases"
27
+
28
+ directories = (releases - releases.last(keep)).map do |release|
29
+ File.join(releases_path, release)
30
+ end.join(" ")
31
+
32
+ run "rm -rf #{directories}"
33
+ end
34
+ end
35
+
36
+ task :rollback do
37
+ releases = capture("cd #{get(:releases_path)} && ls -x").strip.split.sort
38
+ run "rm -rf #{get(:release_path)}; true"
39
+
40
+ if releases.length > 1
41
+ run "ln -nfs #{File.join(get(:releases_path), releases[-2])} #{get(:current_path)}"
42
+ else
43
+ puts " no releases to rollback to. skipping rollback of symlink..."
44
+ end
45
+
46
+ run_task "deploy:restart"
47
+ end
48
+
49
+ task :symlink do
50
+ run "ln -nfs #{get(:release_path)} #{get(:current_path)}"
51
+ end
52
+
53
+ task :restart, :role => :app, :serial => true do
54
+ run "sudo god restart #{get(:application)}"
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ namespace :sbt do
2
+ task :assembly do
3
+ run_locally("~/bin/sbt assembly")
4
+ end
5
+
6
+ task :read_properties do
7
+ file = File.read(Dir.pwd + "/project/build.properties")
8
+ props = Hash[*file.split("\n").map { |p| p.split("=") }.flatten]
9
+
10
+ set :sbt_name, props["project.name"]
11
+ set :sbt_version, props["project.version"]
12
+ set :sbt_build_version, props["build.scala.versions"]
13
+ set :sbt_assembly_filename, "#{get(:sbt_name)}-assembly-#{get(:sbt_version)}.jar"
14
+ set :sbt_assembly_path, Dir.pwd + "/target/scala_#{get(:sbt_build_version)}/#{get(:sbt_assembly_filename)}"
15
+ end
16
+
17
+ task :update_code do
18
+ run "mkdir #{get(:release_path)}"
19
+
20
+ @current_servers.map do |server|
21
+ assembly_path = get(:sbt_assembly_path)
22
+ cmd = "rsync --progress -az --delete --rsh='ssh -p 22' #{assembly_path} #{server.connection_string}:#{get(:release_path)}/"
23
+ Thread.new { system(cmd) }
24
+ end.each { |t| t.join }
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ namespace :service do
2
+ task :package do
3
+ set :bundle_name, "#{get(:application)}-#{get(:sbt_version)}.tar.gz"
4
+ set :bundle_path, "target/#{get(:bundle_name)}"
5
+ run_locally "tar -czv --file=#{get(:bundle_path)} config cookbooks roles -C target/scala_#{get(:sbt_build_version)} #{get(:sbt_assembly_filename)}"
6
+ end
7
+
8
+ task :update_code do
9
+ run "mkdir #{get(:release_path)}"
10
+ put File.read(get(:bundle_path)), get(:release_path) + "/" + get(:bundle_name)
11
+ run "cd #{get(:release_path)} && tar zxvf #{get(:bundle_name)}"
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Rigger
2
+ class Server < Struct.new(:role, :connection_string, :options)
3
+ def connection
4
+ @connection ||= Net::SSH.start(host, user)
5
+ end
6
+
7
+ def host
8
+ connection_string.split("@").last
9
+ end
10
+
11
+ def user
12
+ connection_string.split("@").first
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module Rigger
2
+ class ServerResolver
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ def call(task)
8
+ roles = [*task.options[:role]].compact
9
+ only = task.options.fetch(:only, {})
10
+
11
+ role_servers = roles.empty? ? @config.servers : from_roles(roles)
12
+ only_servers = only.empty? ? @config.servers : from_only(only)
13
+
14
+ (role_servers & only_servers)
15
+ end
16
+
17
+ protected
18
+ def from_roles(roles)
19
+ roles.map do |role|
20
+ @config.servers.select do |server|
21
+ server.role == role
22
+ end
23
+ end.flatten
24
+ end
25
+
26
+ def from_only(only)
27
+ @config.servers.select do |server|
28
+ only.all? do |key, value|
29
+ server.options[key] == value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,4 @@
1
+ module Rigger
2
+ class Task < Struct.new(:name, :options, :block)
3
+ end
4
+ end
@@ -0,0 +1,26 @@
1
+ require "rigger/connection_set"
2
+ require "rigger/server_resolver"
3
+ require "rigger/task_executor"
4
+ require "rigger/execution_strategy"
5
+
6
+ module Rigger
7
+ class TaskExecutionService
8
+ def initialize(config,
9
+ server_resolver = ServerResolver.new(config),
10
+ execution_strategy_selector = ExecutionStrategy::Selector.new)
11
+ @config = config
12
+ @server_resolver = server_resolver
13
+ @execution_strategy_selector = execution_strategy_selector
14
+ end
15
+
16
+ def call(task_name)
17
+ task = @config.locate_task(task_name)
18
+ servers = @server_resolver.call(task)
19
+
20
+ puts " * executing '#{task_name}'"
21
+
22
+ strategy = @execution_strategy_selector.call(task)
23
+ strategy.call(task, servers, @config, self)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,195 @@
1
+ require "net/ssh"
2
+ require "net/sftp"
3
+ require "popen4"
4
+
5
+ module Rigger
6
+ class TaskExecutor
7
+ class SFTPTransferWrapper
8
+ attr_reader :operation
9
+
10
+ def initialize(session, &callback)
11
+ @sftp = session.sftp(false).connect do |sftp|
12
+ @operation = callback.call(sftp)
13
+ end
14
+ end
15
+
16
+ def active?
17
+ @operation.nil? || @operation.active?
18
+ end
19
+
20
+ def [](key)
21
+ @operation[key]
22
+ end
23
+
24
+ def []=(key, value)
25
+ @operation[key] = value
26
+ end
27
+
28
+ def abort!
29
+ @operation.abort!
30
+ end
31
+ end
32
+
33
+ def initialize(task, servers, execution_service, config)
34
+ @task = task
35
+ @current_servers = servers
36
+ @execution_service = execution_service
37
+ @config = config
38
+ end
39
+
40
+ def call
41
+ instance_eval(&@task.block)
42
+ end
43
+
44
+ def run(command)
45
+ execute(command, @current_servers) do |ch|
46
+ ch.on_data do |c, data|
47
+ data.split("\n").each do |line|
48
+ puts " ** [#{ch[:host]} :: stdout] #{line}"
49
+ $stdout.flush
50
+ end
51
+ end
52
+
53
+ ch.on_extended_data do |c, type, data|
54
+ data.split("\n").each do |line|
55
+ puts " ** [#{ch[:host]} :: stderr] #{line}"
56
+ $stderr.flush
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def capture(command)
63
+ "".tap do |captured|
64
+ execute(command, [@current_servers.first]) do |ch|
65
+ ch.on_data do |c, data|
66
+ captured << data
67
+ end
68
+
69
+ ch.on_extended_data do |c, type, data|
70
+ data.split("\n").each do |line|
71
+ puts " ** [#{server.connection_string} :: stderr] #{line}"
72
+ $stderr.flush
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def run_task(task_name)
80
+ @execution_service.call(task_name)
81
+ end
82
+
83
+ def run_locally(command)
84
+ puts " * executing `#{command}` locally"
85
+ status = POpen4.popen4(command) do |stdout, stderr, stdin, pid|
86
+ stdout.each_line do |line|
87
+ puts " ** [locally :: stdout] #{line}"
88
+ end
89
+
90
+ stderr.each_line do |line|
91
+ puts " ** [locally :: stderr] #{line}"
92
+ end
93
+ end
94
+
95
+ if status && status.exitstatus == 0
96
+ puts " * command finished"
97
+ else
98
+ raise CommandError, "Local command `#{command}` failed."
99
+ end
100
+ end
101
+
102
+ def get(name)
103
+ @config.get(name)
104
+ end
105
+
106
+ def set(name, value)
107
+ @config.set(name, value)
108
+ end
109
+
110
+ def fetch(name, default)
111
+ @config.fetch(name, default)
112
+ end
113
+
114
+ def put(data, path)
115
+ servers = @current_servers.dup
116
+ channels = servers.map do |s|
117
+ callback = Proc.new do |channel, name, sent, total|
118
+ puts "[#{channel[:host]}] #{name}" if sent == 0
119
+ end
120
+
121
+ SFTPTransferWrapper.new(s.connection) do |sftp|
122
+ io = StringIO.new(data.respond_to?(:read) ? data.read : data)
123
+ sftp.upload(io, path, {}) do |status, we|
124
+ if status == :finish
125
+ sftp.close_channel
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ puts " * transerring data to #{path}"
132
+
133
+ connections = servers.dup
134
+
135
+ failing_servers = []
136
+ errors = []
137
+
138
+ loop do
139
+ connections.delete_if do |server|
140
+ begin
141
+ !server.connection.process(0.1) { |s| s.busy? }
142
+ rescue Net::SFTP::StatusException => e
143
+ failing_servers << server
144
+ errors << e.message
145
+ end
146
+ end
147
+ break if connections.empty?
148
+ end
149
+
150
+ if !failing_servers.empty?
151
+ raise CommandError, "Upload failed on #{failing_servers.map { |s| s.connection_string }.inspect} with #{errors.join(", ")}."
152
+ end
153
+
154
+ puts " * finished"
155
+ end
156
+
157
+ protected
158
+ def execute(command, servers)
159
+ puts " * executing `#{command}`"
160
+ puts " servers: #{servers.map { |s| s.connection_string }.inspect}"
161
+ channels = servers.map do |server|
162
+ puts " [#{server.connection_string}] executing command"
163
+ server.connection.open_channel do |ch|
164
+ ch.exec(command) do |ch, success|
165
+ ch[:host] = server.connection_string
166
+
167
+ yield ch
168
+
169
+ ch.on_request("exit-status") do |ch, data|
170
+ ch[:status] = data.read_long
171
+ end
172
+
173
+ ch.on_close do |ch|
174
+ ch[:closed] = true
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ connections = servers.map { |s| s.connection }
181
+
182
+ loop do
183
+ connections.delete_if { |ssh| !ssh.process(0.1) { |s| s.busy? } }
184
+ break if connections.empty?
185
+ end
186
+
187
+ failing_servers = channels.select { |ch| ch[:status] != 0 }
188
+ if failing_servers.empty?
189
+ puts " * command finished"
190
+ else
191
+ raise CommandError, "Command `#{command}` failed on #{failing_servers.map { |ch| ch[:host] }.inspect}."
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+ require "rigger"
3
+ require "rigger/server_resolver"
4
+
5
+ describe "Rigger::ServerResolver" do
6
+ before do
7
+ @server1 = stub("Server", :options => {}, :role => :db)
8
+ @server2 = stub("Server", :options => {}, :role => :app)
9
+ @server3 = stub("Server", :options => {
10
+ :fuck => true
11
+ }, :role => :search)
12
+ @servers = [@server1, @server2, @server3]
13
+ @config = stub("Config", :servers => @servers)
14
+ @resolver = Rigger::ServerResolver.new(@config)
15
+ end
16
+
17
+ describe "when the task has no options" do
18
+ before do
19
+ @task = stub("Config", :options => {})
20
+ end
21
+
22
+ it "returns all the servers" do
23
+ @resolver.call(@task).should == @servers
24
+ end
25
+ end
26
+
27
+ describe "when the task specifies a role" do
28
+ before do
29
+ @task = stub("Config", :options => {:role => :app})
30
+ end
31
+
32
+ it "returns all the servers matching that role" do
33
+ @resolver.call(@task).should == [@server2]
34
+ end
35
+ end
36
+
37
+ describe "when the task specifies an option" do
38
+ before do
39
+ @task = stub("Config", :options => {:only => {:fuck => true}})
40
+ end
41
+
42
+ it "returns all the servers matching that role" do
43
+ @resolver.call(@task).should == [@server3]
44
+ end
45
+ end
46
+
47
+ describe "when no servers match" do
48
+ before do
49
+ @task = stub("Config", :options => {:only => {:FUCK => true}})
50
+ end
51
+
52
+ it "returns []" do
53
+ @resolver.call(@task).should == []
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'rigger'
5
+ require 'mocha'
6
+ require 'bourne'
7
+
8
+ # Requires supporting files with custom matchers and macros, etc,
9
+ # in ./support/ and its subdirectories.
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
11
+
12
+ RSpec.configure do |config|
13
+ config.mock_with :mocha
14
+ end
metadata ADDED
@@ -0,0 +1,268 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rigger
3
+ version: !ruby/object:Gem::Version
4
+ hash: 3
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 10
10
+ version: 0.2.10
11
+ platform: ruby
12
+ authors:
13
+ - James Golick
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-21 00:00:00 -07:00
19
+ default_executable: rig
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - "="
26
+ - !ruby/object:Gem::Version
27
+ hash: 21
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 1
32
+ version: 1.0.1
33
+ prerelease: false
34
+ type: :runtime
35
+ requirement: *id001
36
+ name: net-ssh-multi
37
+ - !ruby/object:Gem::Dependency
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - "="
42
+ - !ruby/object:Gem::Version
43
+ hash: 5
44
+ segments:
45
+ - 2
46
+ - 0
47
+ - 5
48
+ version: 2.0.5
49
+ prerelease: false
50
+ type: :runtime
51
+ requirement: *id002
52
+ name: net-sftp
53
+ - !ruby/object:Gem::Dependency
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - "="
58
+ - !ruby/object:Gem::Version
59
+ hash: 31
60
+ segments:
61
+ - 0
62
+ - 1
63
+ - 2
64
+ version: 0.1.2
65
+ prerelease: false
66
+ type: :runtime
67
+ requirement: *id003
68
+ name: popen4
69
+ - !ruby/object:Gem::Dependency
70
+ version_requirements: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 2
78
+ - 3
79
+ - 0
80
+ version: 2.3.0
81
+ prerelease: false
82
+ type: :development
83
+ requirement: *id004
84
+ name: rspec
85
+ - !ruby/object:Gem::Dependency
86
+ version_requirements: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ hash: 23
92
+ segments:
93
+ - 1
94
+ - 0
95
+ - 0
96
+ version: 1.0.0
97
+ prerelease: false
98
+ type: :development
99
+ requirement: *id005
100
+ name: bundler
101
+ - !ruby/object:Gem::Dependency
102
+ version_requirements: &id006 !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ~>
106
+ - !ruby/object:Gem::Version
107
+ hash: 7
108
+ segments:
109
+ - 1
110
+ - 5
111
+ - 2
112
+ version: 1.5.2
113
+ prerelease: false
114
+ type: :development
115
+ requirement: *id006
116
+ name: jeweler
117
+ - !ruby/object:Gem::Dependency
118
+ version_requirements: &id007 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ hash: 3
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ prerelease: false
128
+ type: :development
129
+ requirement: *id007
130
+ name: rcov
131
+ - !ruby/object:Gem::Dependency
132
+ version_requirements: &id008 !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ hash: 43
138
+ segments:
139
+ - 0
140
+ - 9
141
+ - 8
142
+ version: 0.9.8
143
+ prerelease: false
144
+ type: :development
145
+ requirement: *id008
146
+ name: mocha
147
+ - !ruby/object:Gem::Dependency
148
+ version_requirements: &id009 !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ hash: 15
154
+ segments:
155
+ - 1
156
+ - 0
157
+ version: "1.0"
158
+ prerelease: false
159
+ type: :development
160
+ requirement: *id009
161
+ name: bourne
162
+ - !ruby/object:Gem::Dependency
163
+ version_requirements: &id010 !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - "="
167
+ - !ruby/object:Gem::Version
168
+ hash: 21
169
+ segments:
170
+ - 1
171
+ - 0
172
+ - 1
173
+ version: 1.0.1
174
+ prerelease: false
175
+ type: :runtime
176
+ requirement: *id010
177
+ name: net-ssh-multi
178
+ - !ruby/object:Gem::Dependency
179
+ version_requirements: &id011 !ruby/object:Gem::Requirement
180
+ none: false
181
+ requirements:
182
+ - - "="
183
+ - !ruby/object:Gem::Version
184
+ hash: 31
185
+ segments:
186
+ - 0
187
+ - 1
188
+ - 2
189
+ version: 0.1.2
190
+ prerelease: false
191
+ type: :runtime
192
+ requirement: *id011
193
+ name: popen4
194
+ description: ""
195
+ email: jamesgolick@gmail.com
196
+ executables:
197
+ - rig
198
+ extensions: []
199
+
200
+ extra_rdoc_files:
201
+ - LICENSE.txt
202
+ - README.rdoc
203
+ - TODO
204
+ files:
205
+ - .document
206
+ - .rspec
207
+ - Gemfile
208
+ - Gemfile.lock
209
+ - LICENSE.txt
210
+ - README.rdoc
211
+ - Rakefile
212
+ - TODO
213
+ - VERSION
214
+ - bin/rig
215
+ - config/rig.rb
216
+ - lib/rigger.rb
217
+ - lib/rigger/cli.rb
218
+ - lib/rigger/connection_set.rb
219
+ - lib/rigger/dsl.rb
220
+ - lib/rigger/execution_strategy.rb
221
+ - lib/rigger/recipes/chef.rb
222
+ - lib/rigger/recipes/deploy.rb
223
+ - lib/rigger/recipes/sbt.rb
224
+ - lib/rigger/recipes/service.rb
225
+ - lib/rigger/server.rb
226
+ - lib/rigger/server_resolver.rb
227
+ - lib/rigger/task.rb
228
+ - lib/rigger/task_execution_service.rb
229
+ - lib/rigger/task_executor.rb
230
+ - spec/server_resolver_spec.rb
231
+ - spec/spec_helper.rb
232
+ has_rdoc: true
233
+ homepage: http://github.com/jamesgolick/rigger
234
+ licenses:
235
+ - MIT
236
+ post_install_message:
237
+ rdoc_options: []
238
+
239
+ require_paths:
240
+ - lib
241
+ required_ruby_version: !ruby/object:Gem::Requirement
242
+ none: false
243
+ requirements:
244
+ - - ">="
245
+ - !ruby/object:Gem::Version
246
+ hash: 3
247
+ segments:
248
+ - 0
249
+ version: "0"
250
+ required_rubygems_version: !ruby/object:Gem::Requirement
251
+ none: false
252
+ requirements:
253
+ - - ">="
254
+ - !ruby/object:Gem::Version
255
+ hash: 3
256
+ segments:
257
+ - 0
258
+ version: "0"
259
+ requirements: []
260
+
261
+ rubyforge_project:
262
+ rubygems_version: 1.3.9.2
263
+ signing_key:
264
+ specification_version: 3
265
+ summary: ""
266
+ test_files:
267
+ - spec/server_resolver_spec.rb
268
+ - spec/spec_helper.rb