hadouken 0.1.4.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ module Net::SSH::Multi::SessionActions
2
+
3
+ def hadouken_exec(command)
4
+ open_channel do |channel|
5
+
6
+ channel.exec(command) do |ch, success|
7
+ raise "could not execute command: #{command.inspect} (#{ch[:host]})" unless success
8
+ channel.on_data do |ch, data|
9
+ ch[:stdout] = []
10
+ data.chomp.each_line do |line|
11
+ ch[:stdout] << line
12
+ end
13
+ end
14
+ end
15
+
16
+ channel.on_extended_data do |ch, type, data|
17
+ ch[:stderr] = []
18
+ data.chomp.each_line do |line|
19
+ ch[:stderr] << line
20
+ end
21
+ end
22
+
23
+ channel.on_request("exit-status") do |ch, data|
24
+ ch[:exit_status] = data.read_long
25
+ end
26
+
27
+ end #open_channel
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,32 @@
1
+ class Hadouken::Group
2
+ attr_reader :name
3
+ attr_reader :range
4
+ attr_reader :pattern
5
+
6
+ def initialize(opts)
7
+ @name = opts[:name]
8
+ @range = opts[:range]
9
+ @pattern = opts[:pattern]
10
+ end
11
+
12
+ def count
13
+ @count ||= hosts.size
14
+ end
15
+ alias :size :count
16
+
17
+ def hosts
18
+ @hosts ||= @range.map{|idx| "#{@pattern}" % [ idx ] }.map do |hostname|
19
+ Hadouken::Hosts.add({:name => hostname})
20
+ end
21
+ end
22
+
23
+ def has_host?(name)
24
+ @hosts_by_name ||= hosts.inject({}){|h,host| h[host]=true; h}
25
+ @hosts_by_name.has_key?(name)
26
+ end
27
+
28
+ def self.create!(name, opts)
29
+ new(opts.merge(:name => name))
30
+ end
31
+
32
+ end
@@ -0,0 +1,42 @@
1
+ class Hadouken::Groups
2
+ include Enumerable
3
+
4
+ def initialize
5
+ @groups = {}
6
+ @order = []
7
+ end
8
+
9
+ def count
10
+ @groups.values.size
11
+ end
12
+ alias :size :count
13
+
14
+ def hosts
15
+ @groups.values.map{|group| group.hosts}.flatten
16
+ end
17
+
18
+ def each
19
+ @order.uniq!
20
+ @order.each do |name|
21
+ yield @groups[name]
22
+ end
23
+ end
24
+
25
+ def [](name)
26
+ fetch name
27
+ end
28
+
29
+ def fetch (name)
30
+ @groups[ name ]
31
+ end
32
+
33
+ def add (name, opts)
34
+ store(Hadouken::Group.create!(name, opts))
35
+ end
36
+
37
+ def store (group)
38
+ raise ArgumentError.new("8==D~") unless group.is_a?(Hadouken::Group)
39
+ @groups[ group.name ] = group
40
+ @order << group.name
41
+ end
42
+ end
@@ -0,0 +1,120 @@
1
+ module Hadouken::Hosts
2
+
3
+ class << self
4
+ attr_accessor :history_filepath
5
+ end
6
+
7
+ @@hosts = {}
8
+
9
+ def self.add(opts={})
10
+ @@hosts[opts[:name]] ||= Hadouken::Host.new(opts)
11
+ end
12
+
13
+ def self.get(hostname)
14
+ @@hosts[hostname]
15
+ end
16
+
17
+ def self.exists?(hostname)
18
+ @@hosts.exists?(hostname)
19
+ end
20
+
21
+ def self.count
22
+ @@hosts.keys.count
23
+ end
24
+
25
+ def self.any?
26
+ @@hosts.keys.any?
27
+ end
28
+
29
+ def self.each
30
+ @@hosts.each do |hostname, host|
31
+ yield host
32
+ end
33
+ end
34
+
35
+ def self.disable_all!
36
+ each do |host|
37
+ host.disable! if host.enabled?
38
+ end
39
+ end
40
+ end
41
+
42
+ class Hadouken::Host
43
+ attr_reader :name
44
+ attr_reader :history
45
+
46
+ # used to store the net-ssh server object
47
+ attr_accessor :server
48
+
49
+ def initialize(opts={})
50
+ @name = opts[:name]
51
+ @enabled = true
52
+ @history = History.new(self)
53
+ end
54
+
55
+ def disable!
56
+ history.add :disabled, :noop
57
+ @enabled = false
58
+ end
59
+
60
+ def enable!
61
+ history.add :enabled, :noop
62
+ @enabled = true
63
+ end
64
+
65
+ def enabled?
66
+ @enabled
67
+ end
68
+
69
+ def to_s
70
+ name
71
+ end
72
+
73
+ def history_filepath
74
+ File.join(Hadouken::Hosts.history_filepath, "#{name}.log")
75
+ end
76
+
77
+
78
+ class History
79
+ include Enumerable
80
+
81
+ def initialize(host)
82
+ @host = host
83
+ @history = []
84
+ end
85
+
86
+ def add(command, status, stdout=nil, stderr=nil, epoch=Time.now.to_f)
87
+ stdoutJoined = stdout ? stdout.join("\n") : nil
88
+ stderrJoined = stderr ? stderr.join("\n") : nil
89
+ @history << [command, status, epoch, stdoutJoined, stderrJoined]
90
+ File.open(@host.history_filepath, 'a') do |history_file|
91
+ history_file.write(Yajl::Encoder.encode(command_to_hash(command, status, epoch, stdoutJoined, stderrJoined)))
92
+ history_file.write("\n")
93
+ end
94
+ end
95
+
96
+ def each
97
+ @history.each do |command, status, epoch, stdout, stderr|
98
+ yield command, status, epoch, stdout, stderr
99
+ end
100
+ end
101
+
102
+ def to_json
103
+ Yajl::Encoder.encode self.map do |command, status, epoch, stdout, stderr|
104
+ command_to_hash(command, status, epoch, stdout, stderr)
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def command_to_hash(command, status, epoch, stdout, stderr)
111
+ return {
112
+ :command => command,
113
+ :status => status,
114
+ :time => (epoch * 1000).round,
115
+ :stdout => stdout,
116
+ :stderr => stderr
117
+ }
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,48 @@
1
+ class Hadouken::Plan
2
+ attr_accessor :name
3
+ attr_accessor :root
4
+ attr_accessor :user
5
+
6
+ attr_accessor :environment
7
+ attr_accessor :dry_run
8
+ attr_accessor :interactive
9
+
10
+ attr_accessor :history_path
11
+ attr_accessor :planfile
12
+ attr_accessor :artifact
13
+
14
+ attr_reader :timestamp
15
+
16
+ def initialize
17
+ @tasks = Hadouken::Tasks.new
18
+ @groups = Hadouken::Groups.new
19
+ @timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
20
+ end
21
+
22
+ def groups
23
+ @groups
24
+ end
25
+
26
+ def tasks
27
+ @tasks
28
+ end
29
+
30
+ def dry_run?
31
+ !!@dry_run
32
+ end
33
+
34
+ def interactive?
35
+ !!@interactive
36
+ end
37
+
38
+ def env
39
+ environment
40
+ end
41
+
42
+
43
+ def logger
44
+ Hadouken.logger
45
+ end
46
+
47
+ end
48
+
@@ -0,0 +1,84 @@
1
+ class Hadouken::Runner
2
+
3
+ attr_accessor :args
4
+ attr_accessor :plan
5
+
6
+ def initialize
7
+ @args = self.class.optparse
8
+ @plan = Hadouken::Plan.new
9
+ @plan.environment = @args[:environment]
10
+ @plan.dry_run = @args[:dry_run]
11
+ @plan.interactive = @args[:interactive]
12
+ @plan.planfile = @args[:planfile]
13
+ @plan.artifact = @args[:artifact]
14
+ end
15
+
16
+ def self.run!
17
+ runner = Hadouken::Runner.new
18
+ plan = runner.plan
19
+
20
+ yield(plan)
21
+
22
+ ts0 = Time.now
23
+ Hadouken::Executor.run!(plan)
24
+ te0 = Time.now
25
+
26
+ Hadouken.logger.info "plan executed in %0.2f" % (te0 - ts0)
27
+ end
28
+
29
+ def self.optparse
30
+ args = {}
31
+ parser = OptionParser.new do |opts|
32
+ opts.banner = "Usage: #{$0} [options]"
33
+ opts.separator ""
34
+ opts.separator "options:"
35
+
36
+ opts.on("--interactive", "output stdout/stderr to console") {|o| args[:interactive] = o}
37
+ opts.on("--dry-run", "take no action" ) {|o| args[:dry_run] = o}
38
+ opts.on("--env ENV", "stage|production|ding-dong|..." ) {|o| args[:environment] = o}
39
+
40
+ opts.on("--history PATH", "where to store history files") do |o|
41
+ args[:history] = o || 'history'
42
+ FileUtils.mkdir_p args[:history]
43
+ Hadouken::Hosts.history_filepath = args[:history]
44
+ end
45
+
46
+ opts.on("--artifact URL", "URL to the service artifact" ) do |o|
47
+ begin
48
+ args[:artifact] = URI.parse(o)
49
+ raise URI::InvalidURIError unless args[:artifact].is_a?(URI::HTTP)
50
+ rescue URI::InvalidURIError
51
+ puts "Sorry, invalid artifact url: #{o}"
52
+ exit 1
53
+ end
54
+ end
55
+
56
+ opts.on("--level LEVEL", "debug|info|warn|error|fatal" ) do |o|
57
+ if o !~ /^(debug|info|warn|error|fatal)$/i
58
+ puts "Sorry, I don't know what that log level is ..."
59
+ exit -1
60
+ else
61
+ args[:level] = case o.downcase
62
+ when /debug/ then Logger::DEBUG
63
+ when /info/ then Logger::INFO
64
+ when /warn/ then Logger::WARN
65
+ when /error/ then Logger::ERROR
66
+ when /fatal/ then Logger::FATAL
67
+ end
68
+ end
69
+
70
+ Hadouken.logger.level = args[:level] || Logger::INFO
71
+ end
72
+ end
73
+ parser.parse!
74
+
75
+ unless args.has_key?(:artifact) &&
76
+ args.has_key?(:environment)
77
+ puts parser
78
+ exit 1
79
+ end
80
+
81
+ return args
82
+ end
83
+
84
+ end
@@ -0,0 +1,15 @@
1
+ class Hadouken::Strategy::Base
2
+ attr_reader :plan
3
+ attr_reader :max_hosts
4
+ attr_reader :traversal
5
+
6
+ def initialize(plan, opts={})
7
+ @plan = plan
8
+ @max_hosts = opts[:max_hosts]
9
+ @traversal = opts[:traversal] || :breadth
10
+ end
11
+
12
+ def host_strategy
13
+ raise ArgumentError, "not implemneted here"
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ class Hadouken::Strategy::ByGroup < Hadouken::Strategy::Base
2
+ def host_strategy
3
+ host_sets = []
4
+ plan.groups.each do |group|
5
+ hosts = group.hosts
6
+ slice = max_hosts || hosts.size
7
+
8
+ hosts.each_slice(slice) do |host_slice|
9
+ host_sets << host_slice
10
+ end
11
+ end
12
+
13
+ balanced
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ class Hadouken::Strategy::ByGroupParallel < Hadouken::Strategy::Base
2
+ def host_strategy
3
+ return @host_sets if @host_sets
4
+
5
+ # transform a array of groups, hosts into a new array that balances
6
+ # hosts from each group into a single array.
7
+ @host_sets = []
8
+ regroup = []
9
+ groups = []
10
+ max_size = 0
11
+
12
+ plan.groups.each do |group|
13
+ hosts = group.hosts
14
+ max_size = [max_size, hosts.size].max
15
+ groups << hosts
16
+ end
17
+
18
+ [max_size, groups.size].max.times do
19
+ groups.each do |hosts|
20
+ if hosts.size == 0
21
+ #TODO groups.delete(name)
22
+ else
23
+ regroup << hosts.shift
24
+ end
25
+ end
26
+ end
27
+
28
+ regroup.each_slice(max_size) do |host_slice|
29
+ @host_sets << host_slice
30
+ end
31
+
32
+ @balanced
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ class Hadouken::Strategy::ByHost < Hadouken::Strategy::Base
2
+ def host_strategy
3
+ hosts = plan.groups.map{|g| g.hosts}.flatten.uniq
4
+ slice = max_hosts || hosts.size
5
+ host_sets = []
6
+
7
+ slice = max_hosts || hosts.size
8
+ hosts.each_slice(slice) do |host_slice|
9
+ host_sets << host_slice
10
+ end
11
+
12
+ host_sets
13
+ end
14
+ end