cast-ssh 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZmRlOTFjNjZiMjViMDFjODExYWI2NTIzOTUyZDYzZmNhZTliNWZmMQ==
5
+ data.tar.gz: !binary |-
6
+ MDJhMmNlYjU4MDYwOTU3OGVmYjU2NTBmZWIyOWJkNzhkMTdmZTQ3MA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NDVjY2MyM2Q5NDQ3NzdmZDZjODNmMTk3YjJjNWMwZDNhN2QxMmY1NzA3YmY5
10
+ YTk1ZmRmZGFiZDNlN2JlOTczYjIwNmE3MmE4NTA4ZjE2ZjE5Y2NjMDEyNDY4
11
+ MTc4OGQzZjJiOGJlYjI3MWNjOGIyNjQyMzU5NWVjNTA3MWQyNDc=
12
+ data.tar.gz: !binary |-
13
+ Yjg2OGUxZTMzOGFhMmI5Yzc5ZjY1OTJkODhlMGJiMDViMTA1YmEwMGY0ZDQ4
14
+ N2QxNmI0NDUwZTUwOWJhZDUyMWNkODk0NjgzYjFlNTE3ODY0YmEzM2RlOTY3
15
+ ZWY1MDgxYTE0OWE4ZWM2YWMwMDNlYTYwNTVjNzYwNDE3OTcxOGU=
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - rbx-18mode
4
+ - rbx-19mode
5
+ - jruby-18mode
6
+ - jruby-19mode
7
+ - 1.8.7
8
+ - 1.9.2
9
+ - 1.9.3
10
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ gem 'peach'
4
+ gem 'trollop'
5
+
6
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (C) 2013 Peter Bakkum
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Cast
2
+
3
+ This is a Ruby gem that executes remote commands via ssh on groups of servers defined in a YAML file.
4
+
5
+ ### Setup
6
+
7
+ Grab the gem:
8
+
9
+ gem install cast-ssh
10
+
11
+ Create your group file at ~/.cast.yml, like this:
12
+
13
+ group1:
14
+ - host1
15
+ - host2
16
+ - host3
17
+
18
+ group2:
19
+ - host1
20
+ - host4
21
+ - host5
22
+
23
+ Run commands in your shell:
24
+
25
+ cast group1 echo test
26
+ cast group1,group2 sudo whoami
27
+ cast -s group1,host4 df -h
28
+
29
+ The output from the second command will look something like this:
30
+
31
+ [cast] loading groups from ~/.cast.yml
32
+ [cast] running ssh host1 'sudo whoami'
33
+ [cast] running ssh host2 'sudo whoami'
34
+ [cast] running ssh host3 'sudo whoami'
35
+ [cast] running ssh host4 'sudo whoami'
36
+ [cast] running ssh host5 'sudo whoami'
37
+ [host3] root
38
+ [host1] root
39
+ [host2] root
40
+ [host5] root
41
+ [host4] root
42
+
43
+ Note that the commands are run in parallel, so the output may be out of order.
44
+
45
+ ### Options
46
+
47
+ --serial, -s: Execute commands serially, rather than in parallel over the group
48
+ --delay, -d <f>: Delay in seconds between execution of serial commands (switches to serial mode if defined)
49
+ --groupfile, -g <s>: YAML file of server groups (default: ~/.cast.yml)
50
+ --list, -l: Print out contents of groupfile without executing command
51
+ --clusters, -c: Print out only groupnames without executing command
52
+ --version, -v: Print version and exit
53
+ --help, -h: Show this message
54
+
55
+ ### API
56
+
57
+ You can also access the same functionality from within Ruby. The following methods are available:
58
+
59
+ * __Cast::remote__ host, cmd
60
+
61
+ Run a remote command on the given host, sending output to stdout.
62
+
63
+ * __Cast::local__ cmd, prefix = nil -> int
64
+
65
+ Run a command locally, printing stdout and stderr from the command. Returns the process' return value.
66
+
67
+ * __Cast::log__ msg, source = 'cast', stream = $stdout
68
+
69
+ Log a message through the Cast mutex using the given prefix and stream. Output will look like "[prefix] msg". The prefix box will be left out if the argument is nil.
70
+
71
+ * __Cast::load_groups__ file -> Hash
72
+
73
+ Load Cast groups from the given file, returning them as a hash.
74
+
75
+ * __Cast::expand_groups__ cmdgroups, groups = @@groups -> Array
76
+
77
+ Takes an array of groups and hosts, and expands the groups into their constituents given the input group hash. If no hash is given, use the value loaded most recently in load_groups. Returns an array of hostnames.
78
+
data/bin/cast ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cast'
4
+ require 'trollop'
5
+
6
+ p = Trollop::Parser.new do
7
+ version "cast #{Cast::VERSION} (c) Peter Bakkum"
8
+ banner <<-EOS
9
+ Cast executes remote commands via ssh on servers or groups of servers, as defined by ~/.cast.yml
10
+
11
+ Usage:
12
+ cast [options] <servers/groups> <command>
13
+
14
+ Examples:
15
+ cast hadoop_cluster df
16
+ cast -s hadoop_cluster,machine1,machine2 whoami
17
+
18
+ Your group file (~/.cast.yml by default) should be proper YAML and look like this:
19
+
20
+ groupname:
21
+ - host1
22
+ - host2
23
+ - host3
24
+
25
+ Command Line Options:
26
+
27
+ EOS
28
+
29
+ opt :serial, 'Execute commands serially, rather than in parallel over the group', :short => 's'
30
+ opt :delay, 'Delay in seconds between execution of serial commands (switches to serial mode if defined)', :type => :float
31
+ opt :groupfile, 'YAML file of server groups', :default => Cast::DEFAULTGROUPS, :short => 'g'
32
+ opt :list, 'Print out contents of groupfile without executing command', :short => 'l'
33
+ opt :clusters, 'Print out only groupnames without executing command', :short => 'c'
34
+ end
35
+
36
+ opt = Trollop::with_standard_exception_handling p do
37
+ opt = p.parse ARGV
38
+ raise Trollop::HelpNeeded if ARGV.size < 2 && !opt[:list] && !opt[:clusters]
39
+ opt
40
+ end
41
+
42
+ groups = Cast::load_groups opt[:groupfile]
43
+
44
+ if opt[:list]
45
+ puts YAML::dump(groups)
46
+ exit
47
+ end
48
+
49
+ if opt[:clusters]
50
+ puts groups.keys.join("\n")
51
+ exit
52
+ end
53
+
54
+ cmd = ARGV[1..-1].join(' ')
55
+ hosts = Cast::expand_groups ARGV[0].split(','), groups
56
+
57
+ Cast::run hosts, cmd, opt[:serial], opt[:delay]
58
+
data/cast.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ require File.expand_path("../lib/cast.rb", __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.add_development_dependency 'mocha'
6
+ gem.add_development_dependency 'autotest-standalone'
7
+
8
+ gem.authors = ["Peter Bakkum"]
9
+ gem.bindir = 'bin'
10
+ gem.description = %q{Execute remote commands via ssh on groups of machines}
11
+ gem.email = ['pbb7c@virginia.edu']
12
+ gem.executables = ['cast']
13
+ gem.extra_rdoc_files = ['LICENSE.md', 'README.md']
14
+ gem.files = Dir['LICENSE.md', 'README.md', 'cast.gemspec', 'Gemfile', '.rspec', '.travis.yml', 'lib/**/*', 'bin/*', 'spec/**/*']
15
+ gem.homepage = 'http://github.com/bakks/cast'
16
+ gem.name = 'cast-ssh'
17
+ gem.rdoc_options = ["--charset=UTF-8"]
18
+ gem.require_paths = ['lib']
19
+ gem.required_rubygems_version = Gem::Requirement.new(">= 1.3.6")
20
+ gem.summary = %q{Execute remote commands via ssh on groups of machines}
21
+ gem.test_files = Dir['spec/**/*']
22
+ gem.version = Cast::VERSION
23
+ gem.license = 'MIT'
24
+ end
25
+
data/lib/cast.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'thread'
2
+ require 'open3'
3
+ require 'yaml'
4
+ require 'peach'
5
+
6
+ STDOUT.sync = true
7
+ STDERR.sync = true
8
+
9
+ module Cast
10
+ VERSION = '0.1.1'
11
+ DEFAULTGROUPS = '~/.cast.yml'
12
+
13
+ @@mux = Mutex.new
14
+ @@pids = []
15
+ @@groups = nil
16
+
17
+ def self.log msg, source = 'cast', stream = $stdout
18
+ @@mux.synchronize do
19
+ prefix = "[#{source}] " unless source == nil
20
+ stream.puts "#{prefix}#{msg}"
21
+ end
22
+ end
23
+
24
+ def self.load_groups groupfile
25
+ file = File.expand_path groupfile
26
+ exists = File.exists? file
27
+ @@groups ||= {}
28
+
29
+ if !exists
30
+ raise "could not find #{groupfile}" if groupfile != DEFAULTGROUPS
31
+ log "no group file found at #{groupfile}"
32
+ else
33
+ log "loading groups from #{groupfile}"
34
+ @@groups = YAML.load_file file
35
+ end
36
+
37
+ return @@groups
38
+ end
39
+
40
+ def self.expand_groups cmdgroups, groups = @@groups
41
+ hosts = []
42
+ cmdgroups.each do |group|
43
+ if groups.include? group
44
+ hosts += groups[group]
45
+ else
46
+ hosts << group
47
+ end
48
+ end
49
+
50
+ return hosts.uniq
51
+ end
52
+
53
+ def self.run hosts, cmd, serial = false, delay = nil
54
+ if serial or delay
55
+ hosts.each_with_index do |host, i|
56
+ remote host, cmd
57
+ if delay and i < hosts.size - 1
58
+ log "delay for #{delay} seconds"
59
+ sleep delay
60
+ end
61
+ end
62
+ else
63
+ hosts.peach { |host| remote host, cmd }
64
+ end
65
+ end
66
+
67
+ def self.remote host, cmd
68
+ fullcmd = "ssh #{host} '#{cmd}'"
69
+ log "running #{fullcmd}"
70
+ local fullcmd, host
71
+ end
72
+
73
+ def self.local cmd, prefix = nil
74
+ r = nil
75
+
76
+ Open3.popen3 cmd do |stdin, stdout, stderr, wait_thr|
77
+ @@pids << wait_thr[:pid]
78
+
79
+ stdin.close
80
+
81
+ while true
82
+ streams = []
83
+ streams << stdout if stdout
84
+ streams << stderr if stderr
85
+ break unless streams.size > 0
86
+
87
+ ios = IO.select streams
88
+
89
+ ios.first.each do |stream|
90
+ if stream == stdout
91
+ line = stream.gets
92
+ if line == nil
93
+ stdout = nil
94
+ next
95
+ end
96
+ log line, prefix
97
+
98
+ elsif stream == stderr
99
+ line = stream.gets
100
+ if line == nil
101
+ stderr = nil
102
+ next
103
+ end
104
+ log line, prefix, $stderr
105
+
106
+ else raise "unrecognized stream #{stream}"
107
+ end
108
+
109
+ end
110
+ end
111
+
112
+ r = wait_thr.value
113
+ end
114
+
115
+ return r
116
+ end
117
+
118
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cast do
4
+ it 'should load groups' do
5
+ groups = Cast::load_groups 'spec/test.yml'
6
+ groups.size.should == 2
7
+ groups['group1'].size.should == 3
8
+ groups['group1'][0].should == 'host1'
9
+ end
10
+
11
+ it 'should expand groups' do
12
+ groups = Cast::load_groups 'spec/test.yml'
13
+ hosts = Cast::expand_groups ['group1'], groups
14
+ hosts.size.should == 3
15
+ hosts[0].should == 'host1'
16
+ hosts[1].should == 'host2'
17
+ hosts[2].should == 'host3'
18
+
19
+ hosts = Cast::expand_groups ['group1', 'group2']
20
+ hosts.size.should == 5
21
+ hosts[0].should == 'host1'
22
+ hosts[1].should == 'host2'
23
+ hosts[2].should == 'host3'
24
+ hosts[3].should == 'host4'
25
+ hosts[4].should == 'host5'
26
+
27
+ hosts = Cast::expand_groups ['host10', 'group1', 'host11', 'group2']
28
+ hosts.size.should == 7
29
+ hosts[0].should == 'host10'
30
+ hosts[1].should == 'host1'
31
+ hosts[2].should == 'host2'
32
+ hosts[3].should == 'host3'
33
+ hosts[4].should == 'host11'
34
+ hosts[5].should == 'host4'
35
+ hosts[6].should == 'host5'
36
+ end
37
+ end
38
+
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rubygems'
4
+ require 'rspec'
5
+ require 'mocha/api'
6
+ require 'cast'
7
+
8
+ RSpec.configure do |config|
9
+ config.mock_with :mocha
10
+ config.fail_fast = true
11
+ end
12
+
data/spec/test.yml ADDED
@@ -0,0 +1,10 @@
1
+ group1:
2
+ - host1
3
+ - host2
4
+ - host3
5
+
6
+ group2:
7
+ - host1
8
+ - host4
9
+ - host5
10
+
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cast-ssh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Peter Bakkum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mocha
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: autotest-standalone
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Execute remote commands via ssh on groups of machines
42
+ email:
43
+ - pbb7c@virginia.edu
44
+ executables:
45
+ - cast
46
+ extensions: []
47
+ extra_rdoc_files:
48
+ - LICENSE.md
49
+ - README.md
50
+ files:
51
+ - LICENSE.md
52
+ - README.md
53
+ - cast.gemspec
54
+ - Gemfile
55
+ - .rspec
56
+ - .travis.yml
57
+ - lib/cast.rb
58
+ - bin/cast
59
+ - spec/lib/cast/cast_spec.rb
60
+ - spec/spec_helper.rb
61
+ - spec/test.yml
62
+ homepage: http://github.com/bakks/cast
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --charset=UTF-8
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: 1.3.6
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 2.0.3
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Execute remote commands via ssh on groups of machines
87
+ test_files:
88
+ - spec/lib/cast/cast_spec.rb
89
+ - spec/spec_helper.rb
90
+ - spec/test.yml
91
+ has_rdoc: