scatter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'scatter'
4
+ require 'optparse'
5
+
6
+ Scatter::CLI.new(STDOUT).run(ARGV)
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/scatter/cli'
2
+ require File.dirname(__FILE__) + '/scatter/command'
3
+ require File.dirname(__FILE__) + '/scatter/super_command'
4
+ require File.dirname(__FILE__) + '/scatter/sub_command'
5
+ require File.dirname(__FILE__) + '/scatter/commands/list'
6
+ require File.dirname(__FILE__) + '/scatter/commands/push'
7
+ require File.dirname(__FILE__) + '/scatter/commands/receive'
8
+ require File.dirname(__FILE__) + '/scatter/commands/remote'
9
+ require File.dirname(__FILE__) + '/scatter/commands/remote/add'
10
+ require File.dirname(__FILE__) + '/scatter/commands/remote/list'
11
+ require File.dirname(__FILE__) + '/scatter/commands/remote/rm'
12
+ require File.dirname(__FILE__) + '/scatter/commands/node'
13
+ require File.dirname(__FILE__) + '/scatter/commands/node/add'
14
+ require File.dirname(__FILE__) + '/scatter/commands/node/list'
15
+ require File.dirname(__FILE__) + '/scatter/commands/node/rm'
16
+ require File.dirname(__FILE__) + '/scatter/commands/init'
17
+ require File.dirname(__FILE__) + '/scatter/config'
18
+ require File.dirname(__FILE__) + '/scatter/logger'
19
+ require File.dirname(__FILE__) + '/scatter/node'
20
+ require File.dirname(__FILE__) + '/scatter/remote'
21
+ require File.dirname(__FILE__) + '/scatter/gemlist'
22
+ require File.dirname(__FILE__) + '/scatter/shell'
23
+ require File.dirname(__FILE__) + '/scatter/version'
24
+
25
+ module Scatter
26
+ class CommandLineError< StandardError; end
27
+ end
@@ -0,0 +1,64 @@
1
+ module Scatter
2
+ class CLI
3
+ def initialize(out)
4
+ @out = out
5
+ end
6
+
7
+ def run!(args)
8
+ raise CommandLineError, 'No command given' if args.empty?
9
+
10
+ case command_name = args.shift
11
+ when /^-?-?h(elp)?$/
12
+ if args.empty?
13
+ usage
14
+ else
15
+ if args.first == 'commands'
16
+ commands_help
17
+ else
18
+ @out.puts command_class(args.first).help
19
+ end
20
+ end
21
+ when /^-?-?v(ersion)?$/
22
+ @out.puts "scatter version #{Scatter.version}"
23
+ else
24
+ klass = command_class(command_name)
25
+ command = klass.new(@out, *args)
26
+ command.execute!
27
+ end
28
+ end
29
+
30
+ def run(args)
31
+ run!(args)
32
+ rescue CommandLineError => e
33
+ @out.puts "Error: #{e.message}"
34
+ usage
35
+ end
36
+
37
+ protected
38
+ def usage
39
+ @out.puts "Usage: scatter command [options ...]"
40
+ @out.puts "For information about the commands use: scatter help commands"
41
+ end
42
+
43
+ def commands_help
44
+ @out.puts "To get help for a command, use: scatter help <command_name>"
45
+ @out.puts
46
+ hash = Scatter::Command.command_classes.inject({}) do |hash, cmd|
47
+ next hash if cmd.abstract
48
+ command_name = cmd.name.split('::').last.downcase
49
+ hash[command_name] = cmd.short_help
50
+ hash
51
+ end
52
+
53
+ hash.sort.each do |command, help|
54
+ @out.puts "#{command} - #{help}"
55
+ end
56
+ end
57
+
58
+ def command_class(command_name)
59
+ eval("Scatter::Commands::#{command_name.capitalize}")
60
+ rescue
61
+ raise CommandLineError, "Unknown command #{command_name}"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ module Scatter
2
+ class Command
3
+ def initialize(out)
4
+ @out = out
5
+ end
6
+
7
+ class << self
8
+ attr_reader :short_help
9
+ attr_reader :command_classes
10
+ attr_accessor :abstract
11
+
12
+ def usage(short_help, long_help = nil)
13
+ @short_help, @help = short_help, long_help || short_help
14
+ end
15
+
16
+ def help
17
+ lines = @help.split("\n")
18
+ indent = lines.collect { |l| l[/^(\s*)/, 1].size }.min
19
+ lines.collect { |l| l[indent..-1] }.join("\n")
20
+ end
21
+
22
+ def inherited(sub)
23
+ (@command_classes ||= []) << sub
24
+ end
25
+
26
+ def command_name
27
+ name.split('::').last.downcase
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+
3
+ module Scatter
4
+ module Commands
5
+ class Init < Scatter::Command
6
+ usage "Initialize scatter configuration", <<-end
7
+ Initializes your machine to use scatter. You need to run this command only once.
8
+ However, it should not break anything if you run it again.
9
+ end
10
+
11
+ def execute!
12
+ FileUtils.mkdir_p File.dirname(Scatter::Config.file_name)
13
+ Scatter::Config.save!
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ module Scatter
2
+ module Commands
3
+ class List < Scatter::Command
4
+ usage "List all gems installed on node", <<-end
5
+ List all gems on a node. The node can be specified by name or by fully qualified
6
+ node name. Examples:
7
+
8
+ scatter list nodename
9
+ scatter list remote/nodename
10
+ end
11
+
12
+ def initialize(out, node_name)
13
+ super(out)
14
+ @node_name = node_name
15
+ end
16
+
17
+ def execute!
18
+ gemlist = node.list
19
+ @out.puts gemlist.gem_output
20
+ end
21
+
22
+ def node
23
+ @node ||= Scatter::Config.find_node(@node_name)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ module Scatter
2
+ module Commands
3
+ class Node < Scatter::SuperCommand
4
+ usage "List, add or remove nodes", <<-end
5
+ List, add or remove nodes from remote.
6
+ To list nodes use the following syntax:
7
+
8
+ scatter node list <remote_name>
9
+
10
+ To add a node to a remote, use
11
+
12
+ scatter node add <remote_name> <node_name> <username>@<hostname>
13
+
14
+ To remove a node, use either of
15
+
16
+ scatter node rm <node_name>
17
+ scatter node rm <remote_name>/<node_name>
18
+ scatter node rm <remote_name> <node_name>
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ module Scatter
2
+ module Commands
3
+ class Node
4
+ class Add < Scatter::SubCommand
5
+ def initialize(out, remote_name, node, credentials)
6
+ super(out)
7
+ @remote_name, @node, @credentials = remote_name, node, credentials
8
+ end
9
+
10
+ def execute!
11
+ remote = Scatter::Config.find_remote(@remote_name)
12
+ raise CommandLineError, "Unknown remote #{@remote_name}" unless remote
13
+ raise CommandLineError, "Remote #{@remote_name} already has node #{@node}" if remote.find_node(@node)
14
+ remote.nodes << Scatter::Node.new(remote, @node, *(@credentials.split('@')))
15
+ Scatter::Config.save!
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ module Scatter
2
+ module Commands
3
+ class Node
4
+ class List < Scatter::SubCommand
5
+ def initialize(out, name = nil)
6
+ super(out)
7
+ @name = name
8
+ end
9
+
10
+ def execute!
11
+ nodes = if @name
12
+ remote = Scatter::Config.find_remote(@name)
13
+ raise CommandLineError, "Unknown remote #{@name}" unless remote
14
+ remote.nodes
15
+ else
16
+ Scatter::Config.nodes
17
+ end
18
+
19
+ nodes.each do |node|
20
+ @out.puts "#{node.remote.name}/#{node.name}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ module Scatter
2
+ module Commands
3
+ class Node
4
+ class Rm < Scatter::SubCommand
5
+ def initialize(out, remote, node = nil)
6
+ super(out)
7
+ @remote, @node = remote, node
8
+ end
9
+
10
+ def execute!
11
+ node = if @node
12
+ remote = Scatter::Config.find_remote(@remote)
13
+ raise CommandLineError, "Unknown remote #{@remote}" unless remote
14
+ remote.find_node(@node)
15
+ else
16
+ Scatter::Config.find_destination(@remote)
17
+ end
18
+
19
+ unless node
20
+ dest = [@remote, @node].compact.join('/')
21
+ raise CommandLineError, "Unknown node #{dest}"
22
+ end
23
+
24
+ node.remote.nodes.delete(node)
25
+ Scatter::Config.save!
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ module Scatter
2
+ module Commands
3
+ class Push < Scatter::Command
4
+ usage "Push gem to remote or node", <<-end
5
+ Pushes a gem to a remote or node. Use the remote name, node name or fully
6
+ qualified node name to specify the target. Examples:
7
+
8
+ # to push to node my_node
9
+ scatter push my_gem.gem my_node
10
+ scatter push my_gem.gem my_remote/my_node
11
+
12
+ # to push to remote my_remote
13
+ scatter push my_gem.gem my_remote
14
+ end
15
+
16
+ def initialize(out, gemfile, destination)
17
+ super(out)
18
+ @gemfile, @destination = gemfile, destination
19
+ end
20
+
21
+ def execute!
22
+ dest.push(@gemfile)
23
+ end
24
+
25
+ def dest
26
+ @dest ||= Scatter::Config.find_destination(@destination)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ module Scatter
2
+ module Commands
3
+ class Receive < Scatter::Command
4
+ usage "Inform scatter that gem was received; calls post receive hook"
5
+
6
+ def initialize(out, gemfile)
7
+ super(out)
8
+ @gemfile = gemfile
9
+ end
10
+
11
+ def execute!
12
+ if File.executable?("#{ENV['HOME']}/.scatter/post-receive")
13
+ Scatter::Logger.log("Executing post receive hook")
14
+ Scatter::Shell.execute "#{ENV['HOME']}/.scatter/post-receive #{@gemfile}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Scatter
2
+ module Commands
3
+ class Remote < Scatter::SuperCommand
4
+ usage "List, add or remove remotes", <<-end
5
+ List, add or remove remotes.
6
+ To list remotes use the following syntax:
7
+
8
+ scatter remote list
9
+
10
+ To add a remote, use
11
+
12
+ scatter remote add <remote_name>
13
+
14
+ To remove a remote, use
15
+
16
+ scatter remote rm <remote_name>
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module Scatter
2
+ module Commands
3
+ class Remote
4
+ class Add < Scatter::SubCommand
5
+ def initialize(out, name)
6
+ super(out)
7
+ @name = name
8
+ end
9
+
10
+ def execute!
11
+ raise CommandLineError, "Remote #{@name} already exists" if Scatter::Config.find_remote(@name)
12
+ Scatter::Config.remotes << Scatter::Remote.new(@name)
13
+ Scatter::Config.save!
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Scatter
2
+ module Commands
3
+ class Remote
4
+ class List < Scatter::SubCommand
5
+ def execute!
6
+ Scatter::Config.remotes.each do |remote|
7
+ @out.puts remote.name
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Scatter
2
+ module Commands
3
+ class Remote
4
+ class Rm < Scatter::SubCommand
5
+ def initialize(out, name)
6
+ super(out)
7
+ @name = name
8
+ end
9
+
10
+ def execute!
11
+ remote = Scatter::Config.find_remote(@name)
12
+ raise CommandLineError, "Remote #{@name} does not exist" unless remote
13
+ Scatter::Config.remotes.delete(remote)
14
+ Scatter::Config.save!
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,72 @@
1
+ module Scatter
2
+ class Config
3
+ class << self
4
+ def instance
5
+ @instance ||= new
6
+ end
7
+
8
+ def clear
9
+ @instance = nil
10
+ end
11
+
12
+ def reload
13
+ @instance = new(@instance.file_name)
14
+ end
15
+
16
+ def method_missing(method, *args)
17
+ if instance.respond_to?(method)
18
+ instance.send(method, *args)
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
24
+
25
+ attr_accessor :file_name
26
+
27
+ def initialize(file_name = nil)
28
+ self.file_name = file_name || "#{ENV['HOME']}/.scatter/config"
29
+ end
30
+
31
+ def remotes
32
+ load!
33
+ @remotes ||= (@config['remotes'] || {}).collect { |name, config| Scatter::Remote.decode_from_config(name, config) }
34
+ end
35
+
36
+ def nodes
37
+ @nodes ||= remotes.collect { |remote| remote.nodes }.flatten
38
+ end
39
+
40
+ def load!
41
+ @loaded ||= begin
42
+ @config = YAML.load(File.read(file_name)) rescue {}
43
+ true
44
+ end
45
+ end
46
+
47
+ def save!
48
+ config = {
49
+ 'remotes' => self.remotes.inject({}) { |hash, remote| hash[remote.name] = remote.encode_for_config; hash }
50
+ }
51
+ File.open(file_name, 'w') { |file| file.puts config.to_yaml }
52
+ end
53
+
54
+ def find_remote(name)
55
+ remotes.find { |remote| remote.name == name }
56
+ end
57
+
58
+ def find_node(name)
59
+ nodes.find { |node| node.name == name || node.full_name == name }
60
+ end
61
+
62
+ def find_destination(name)
63
+ if name =~ /\//
64
+ remote, node = *(name.split('/'))
65
+ remote_object = find_remote(remote)
66
+ remote_object.find_node(node) if remote_object
67
+ else
68
+ (remotes + nodes).find { |dest| dest.name == name }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,13 @@
1
+ module Scatter
2
+ class Gemlist
3
+ attr_accessor :gem_output
4
+
5
+ def initialize(gem_output)
6
+ @gem_output = gem_output
7
+ end
8
+
9
+ def self.parse(gem_output)
10
+ new(gem_output)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module Scatter
2
+ class Logger
3
+ def self.log(message)
4
+ puts message
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ module Scatter
2
+ class Node
3
+ attr_reader :remote, :name, :username, :hostname
4
+
5
+ def initialize(remote, name, username, hostname)
6
+ @remote, @name, @username, @hostname = remote, name, username, hostname
7
+ end
8
+
9
+ def full_name
10
+ [remote.name, name].join('/')
11
+ end
12
+
13
+ def list
14
+ Scatter::Gemlist.parse(Scatter::Shell.capture("ssh #{@username}@#{@hostname} gem list"))
15
+ end
16
+
17
+ def push(gemfile)
18
+ Scatter::Shell.execute "scp #{gemfile} #{@username}@#{@hostname}:/tmp"
19
+ Scatter::Shell.execute "ssh #{@username}@#{@hostname} sudo gem install /tmp/#{File.basename(gemfile)}"
20
+ Scatter::Shell.execute "ssh #{@username}@#{@hostname} scatter receive /tmp/#{File.basename(gemfile)}"
21
+ end
22
+
23
+ def self.decode_from_config(remote, name, config)
24
+ new(remote, name, config['username'], config['hostname'])
25
+ end
26
+
27
+ def encode_for_config
28
+ {
29
+ 'username' => @username,
30
+ 'hostname' => @hostname
31
+ }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ module Scatter
2
+ class Remote
3
+ attr_reader :name, :nodes
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ @nodes = []
8
+ end
9
+
10
+ def push(gemfile)
11
+ @nodes.each do |node|
12
+ node.push(gemfile)
13
+ end
14
+ end
15
+
16
+ def find_node(name)
17
+ nodes.find { |node| node.name == name }
18
+ end
19
+
20
+ def self.decode_from_config(name, config)
21
+ remote = new(name)
22
+ nodes = config['nodes'].collect { |nodename, config| Node.decode_from_config(remote, nodename, config) } rescue []
23
+ remote.nodes.concat(nodes)
24
+ remote
25
+ end
26
+
27
+ def encode_for_config
28
+ {
29
+ 'nodes' => @nodes.inject({}) { |hash, node| hash[node.name] = node.encode_for_config; hash }
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ module Scatter
2
+ module Shell
3
+ extend self
4
+
5
+ attr_accessor :dry_run
6
+ attr_accessor :command_log
7
+
8
+ def execute(command)
9
+ log_command command
10
+ system command unless dry_run
11
+ end
12
+
13
+ def capture(command)
14
+ log_command command
15
+ %x{#{command}} unless dry_run
16
+ end
17
+
18
+ def clear_log
19
+ self.command_log = []
20
+ end
21
+
22
+ protected
23
+ def log_command(command)
24
+ self.command_log ||= []
25
+ self.command_log << command
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ module Scatter
2
+ class SubCommand
3
+ def initialize(out)
4
+ @out = out
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ module Scatter
2
+ class SuperCommand < Command
3
+ self.abstract = true
4
+
5
+ # needed?
6
+ class << self
7
+ def inherited(sub)
8
+ Command.inherited(sub)
9
+ end
10
+ end
11
+
12
+ def initialize(out, *args)
13
+ super(out)
14
+ @subcmd = args.shift
15
+ @arguments = args
16
+ end
17
+
18
+ def execute!
19
+ subcmd_class = eval("#{self.class.name}::#{@subcmd.capitalize}")
20
+ cmd = subcmd_class.new(@out, *@arguments)
21
+ cmd.execute!
22
+ rescue ArgumentError
23
+ raise CommandLineError, "Wrong number of mandatory arguments for command #{self.class.command_name} #{@subcmd}"
24
+ rescue NameError
25
+ raise CommandLineError, "Unknown subcommand #{@subcmd} for command #{self.class.command_name}"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module Scatter
2
+ def self.version
3
+ "0.0.1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scatter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - imedo GmbH
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-04-17 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: entwicker@imedo.de
18
+ executables:
19
+ - scatter
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - bin/scatter
26
+ - lib/scatter
27
+ - lib/scatter/cli.rb
28
+ - lib/scatter/command.rb
29
+ - lib/scatter/commands
30
+ - lib/scatter/commands/init.rb
31
+ - lib/scatter/commands/list.rb
32
+ - lib/scatter/commands/node
33
+ - lib/scatter/commands/node/add.rb
34
+ - lib/scatter/commands/node/list.rb
35
+ - lib/scatter/commands/node/rm.rb
36
+ - lib/scatter/commands/node.rb
37
+ - lib/scatter/commands/push.rb
38
+ - lib/scatter/commands/receive.rb
39
+ - lib/scatter/commands/remote
40
+ - lib/scatter/commands/remote/add.rb
41
+ - lib/scatter/commands/remote/list.rb
42
+ - lib/scatter/commands/remote/rm.rb
43
+ - lib/scatter/commands/remote.rb
44
+ - lib/scatter/config.rb
45
+ - lib/scatter/gemlist.rb
46
+ - lib/scatter/logger.rb
47
+ - lib/scatter/node.rb
48
+ - lib/scatter/remote.rb
49
+ - lib/scatter/shell.rb
50
+ - lib/scatter/sub_command.rb
51
+ - lib/scatter/super_command.rb
52
+ - lib/scatter/version.rb
53
+ - lib/scatter.rb
54
+ has_rdoc: false
55
+ homepage: http://www.imedo.de/
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.1
77
+ signing_key:
78
+ specification_version: 2
79
+ summary: Gem deployment tool
80
+ test_files: []
81
+