hadouken 0.1.4.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -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