anywhere 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in anywhere.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Tobias Schwab
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,43 @@
1
+ # Anywhere
2
+
3
+ Simple wrapper for Net/SSH.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'anywhere'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install anywhere
18
+
19
+ ## Usage
20
+
21
+ ### From ruby
22
+ pry> require "anywhere/ssh"
23
+ pry> ssh = Anywhere::SSH.new(host = "test.host", user = "root", port: 1234)
24
+ pry> ssh.execute("uptime")
25
+ => <run_time=0.659416, cmd=<uptime>, stdout=<1 lines, 61 chars>, stderr=<empty>, exit_status=0>
26
+
27
+ ### From command line
28
+
29
+ $ anywhere root@host1 root@host2
30
+
31
+ pry> _"uptime"
32
+ 2013-06-09T22:35:51.303413Z [host1] DEBUG 00:35:51 up 179 days, 7:48, 0 users, load average: 0.00, 0.00, 0.00
33
+ 2013-06-09T22:35:51.307168Z [host2] DEBUG 22:35:51 up 299 days, 10:49, 0 users, load average: 0.08, 0.05, 0.06
34
+ => [<run_time=0.06875, cmd=<uptime>, stdout=<1 lines, 72 chars>, stderr=<empty>, exit_status=0>,
35
+ <run_time=0.067885, cmd=<uptime>, stdout=<1 lines, 72 chars>, stderr=<empty>, exit_status=0>]
36
+
37
+ ## Contributing
38
+
39
+ 1. Fork it
40
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
41
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
42
+ 4. Push to the branch (`git push origin my-new-feature`)
43
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'anywhere/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "anywhere"
8
+ spec.version = Anywhere::VERSION
9
+ spec.authors = ["Tobias Schwab"]
10
+ spec.email = ["tobias.schwab@dynport.de"]
11
+ spec.description = %q{Simple wrapper for Net/SSH}
12
+ spec.summary = %q{Simple wrapper for Net/SSH}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "net-ssh"
22
+ spec.add_dependency "colorize"
23
+ spec.add_dependency "pry"
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ end
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ $:.push(File.expand_path("../../lib", __FILE__))
3
+ require "anywhere"
4
+ require "anywhere/ssh"
5
+ RUNNER = Anywhere::SSH.new(host = "test.host", user = "root")
6
+ RUNNER.logger.prefix = "[" + RUNNER.host + "]"
7
+
8
+ hosts = []
9
+ names = []
10
+ RUNNERS = ARGV.map do |host|
11
+ name, user = host.split("@").reverse
12
+ user ||= "root"
13
+ names << name
14
+ Anywhere::SSH.new(name, user)
15
+ end
16
+
17
+ max_name = names.sort_by(&:length).last.length
18
+
19
+ RUNNERS.each do |runner|
20
+ runner.logger.prefix = "[%0#{max_name}s]" % [runner.host]
21
+ end
22
+
23
+ def _(*args)
24
+ RUNNERS.map do |runner|
25
+ Thread.new do
26
+ begin
27
+ runner.execute(args.join(" "))
28
+ rescue Anywhere::ExecutionError => err
29
+ err.result
30
+ end
31
+ end
32
+ end.map(&:join).map(&:value)
33
+ end
34
+
35
+ begin
36
+ puts "hosts: #{RUNNERS.map(&:host).join(",")}"
37
+ puts %(usage: _"ls -la")
38
+ require "pry"
39
+ pry.binding
40
+ rescue SystemExit, NoMethodError
41
+ end
@@ -0,0 +1,10 @@
1
+ require "anywhere/version"
2
+ require "anywhere/logger"
3
+
4
+ module Anywhere
5
+ class << self
6
+ def logger
7
+ @logger ||= Anywhere::Logger.new
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,124 @@
1
+ require "benchmark"
2
+ require "anywhere/logger"
3
+
4
+ module Anywhere
5
+ class Base
6
+ attr_writer :logger
7
+
8
+ def logger
9
+ @logger ||= Anywhere::Logger.new
10
+ end
11
+
12
+ def root?
13
+ whoami == "root"
14
+ end
15
+
16
+ def execute(cmd, stdin = nil)
17
+ do_execute(cmd, stdin) do |stream, data|
18
+ data.split("\n").each do |line|
19
+ if stream == :stderr
20
+ logger.error line
21
+ else
22
+ logger.debug line
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def whoami
29
+ @whoami ||= execute("whoami").stdout.strip
30
+ end
31
+
32
+ def do_execute(*args)
33
+ raise "implement me in subclass"
34
+ end
35
+
36
+ def file_exists?(path)
37
+ execute("test -e #{path}")
38
+ true
39
+ rescue Anywhere::ExecutionError => err
40
+ if err.result.exit_status == 1
41
+ false
42
+ else
43
+ raise
44
+ end
45
+ end
46
+
47
+ def md5sum(path)
48
+ execute("md5sum #{path} | awk '{ print $1 }'").stdout.strip
49
+ end
50
+
51
+ def sudo_cmd
52
+ @sudo_cmd ||= root? ? "" : "sudo"
53
+ end
54
+
55
+ def add_system_user(login)
56
+ raise "user #{login} already exists" if user_exists?(login)
57
+ logger.info "adding system user #{login}"
58
+ execute!("#{sudo_cmd} adduser --system #{login}")
59
+ end
60
+
61
+ def run_as(user, cmd)
62
+ execute("sudo -- sudo -u #{user} -- #{cmd}")
63
+ end
64
+
65
+ def user_exists?(login)
66
+ execute("id #{login} 2>/dev/null").success?
67
+ end
68
+
69
+ def write_file(path, content, attributes = {})
70
+ md5 = Digest::MD5.hexdigest(content).to_s
71
+ if file_exists?(path)
72
+ logger.debug "file #{path} already exists"
73
+ file_md5 = md5sum(path)
74
+ if file_md5 == md5
75
+ logger.info "file #{path} did not change => not writing"
76
+ return :not_changed
77
+ end
78
+ end
79
+ logger.info "writing #{content.length} bytes to #{path} (md5: #{md5})"
80
+ tmp_path = "/opt/urknall/tmp/files.#{md5}"
81
+ execute("#{sudo_cmd} mkdir -p #{File.dirname(tmp_path)}")
82
+ execute("#{sudo_cmd} chown #{whoami} #{File.dirname(tmp_path)}")
83
+ logger.debug "writing to #{tmp_path}"
84
+ execute(%(rm -f #{tmp_path}; cat - > #{tmp_path}), content)
85
+ if mode = attributes[:mode]
86
+ logger.info "changing mode to #{mode}"
87
+ execute("chmod #{mode} #{tmp_path}")
88
+ end
89
+ if owner = attributes[:owner]
90
+ logger.info "changing owner to #{owner}"
91
+ execute("#{sudo_cmd} chown #{owner} #{tmp_path}")
92
+ end
93
+ execute("#{sudo_cmd} mkdir -p #{File.dirname(path)}")
94
+ logger.debug "moving #{tmp_path} to #{path}"
95
+ if file_exists?(path)
96
+ logger.debug "diff #{path} #{tmp_path}"
97
+ execute("diff #{tmp_path} #{path}").stdout.split("\n").each do |line|
98
+ logger.debug line
99
+ end
100
+ end
101
+ execute("#{sudo_cmd} mv #{tmp_path} #{path}")
102
+ end
103
+
104
+ def extract_tar(path, dst)
105
+ ms = Benchmark.measure do
106
+ data = File.open(path, "rb") do |f|
107
+ f.read
108
+ end
109
+ logger.info "writing #{data.length} bytes"
110
+ execute(%(mkdir -p #{dst} && cd #{dst} && tar xfz -), data)
111
+ end
112
+ logger.info "extracted archive in %.3f" % [ms.real]
113
+ end
114
+
115
+ def mkdir_p(path)
116
+ logger.info "creating directory #{path}"
117
+ execute("mkdir -p #{path}")
118
+ end
119
+
120
+ def home_dir
121
+ execute("env | grep HOME | cut -d = -f 2").stdout.strip
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,9 @@
1
+ module Anywhere
2
+ class ExecutionError < StandardError
3
+ attr_reader :result
4
+
5
+ def initialize(result)
6
+ @result = result
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ require "anywhere"
2
+ require "anywhere/result"
3
+ require "anywhere/execution_error"
4
+ require "anywhere/base"
5
+
6
+ module Anywhere
7
+ class Local < Base
8
+ def do_execute(cmd, stdin_data = nil)
9
+ require "open3"
10
+ result = Result.new(cmd)
11
+ result.started!
12
+ Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
13
+ stdin.print stdin_data if stdin_data
14
+ stdin.close
15
+ while true
16
+ streams, _ = IO.select([stdout, stderr], [], [], 1)
17
+ break if streams.nil? || streams.all? { |s| s.eof? }
18
+ streams.compact.each do |stream|
19
+ stream.each do |line|
20
+ if stream == stdout
21
+ result.add_stdout(line.strip)
22
+ logger.info(line) if logger
23
+ elsif stream == stderr
24
+ result.add_stderr(line.strip)
25
+ logger.error(line) if logger
26
+ end
27
+ end
28
+ end
29
+ end
30
+ result.exit_status = wait_thr.value.exitstatus
31
+ end
32
+ result.finished!
33
+ if !result.success?
34
+ raise ExecutionError.new(result)
35
+ end
36
+ result
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,51 @@
1
+ require "colorize"
2
+ require "time"
3
+
4
+ module Anywhere
5
+ class Logger
6
+ attr_accessor :prefix
7
+
8
+ class << self
9
+ def mutex
10
+ @mutex ||= Mutex.new
11
+ end
12
+ end
13
+
14
+ def initialize(attributes = {})
15
+ @attributes = attributes
16
+ end
17
+
18
+ def prefix
19
+ @prefix ||= @attributes[:prefix]
20
+ end
21
+
22
+ def stream
23
+ @attributes[:stream] ||= STDOUT
24
+ end
25
+
26
+ def info(message)
27
+ print_with_prefix "INFO ".green + " #{message}"
28
+ end
29
+
30
+ def error(message)
31
+ print_with_prefix "ERROR".red + " #{message}"
32
+ end
33
+
34
+ def debug(message)
35
+ print_with_prefix "DEBUG".blue + " #{message}"
36
+ end
37
+
38
+ def print_with_prefix(message)
39
+ out = [Time.now.utc.iso8601(6)]
40
+ if prefix.is_a?(String)
41
+ out << prefix
42
+ elsif prefix.respond_to?(:call)
43
+ out << prefix.call
44
+ end
45
+ out << message
46
+ self.class.mutex.synchronize do
47
+ stream.puts out.join(" ")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,60 @@
1
+ module Anywhere
2
+ class Result
3
+ attr_accessor :cmd, :stdout, :stderr, :exit_status
4
+
5
+ def initialize(cmd)
6
+ @cmd = cmd
7
+ @stdout = []
8
+ @stderr = []
9
+ end
10
+
11
+ def add_stderr(line)
12
+ @stderr << line
13
+ end
14
+
15
+ def add_stdout(line)
16
+ @stdout << line
17
+ end
18
+
19
+ def stderr
20
+ @stderr.join("\n")
21
+ end
22
+
23
+ def stdout
24
+ @stdout.join("\n")
25
+ end
26
+
27
+ def started!(time = Time.now)
28
+ @started = time
29
+ end
30
+
31
+ def finished!(time = Time.now)
32
+ @finished = time
33
+ end
34
+
35
+ def success?
36
+ @exit_status == 0
37
+ end
38
+
39
+ def run_time
40
+ @finished - @started
41
+ end
42
+
43
+ def inspect
44
+ parts = ["run_time=#{run_time}"]
45
+ parts << "cmd=<#{@cmd}>"
46
+ parts << "stdout=#{inspect_string(@stdout)}"
47
+ parts << "stderr=#{inspect_string(@stderr)}"
48
+ parts << "exit_status=#{@exit_status}"
49
+ "<" + parts.join(", ") + ">"
50
+ end
51
+
52
+ def inspect_string(string)
53
+ if string.empty?
54
+ "<empty>"
55
+ else
56
+ "<#{string.count} lines, #{string.join(" ").length} chars>"
57
+ end
58
+ end
59
+ end
60
+ end