houcho 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,44 @@
1
- require 'houcho/version'
1
+ # -*- encoding: utf-8 -*-
2
+ require 'awesome_print'
3
+ require 'rainbow'
4
+ require 'parallel'
5
+ require 'systemu'
6
+ require 'tempfile'
7
+ require 'find'
8
+ require 'yaml'
9
+ require 'json'
10
+ require 'houcho/initialize'
11
+ require 'houcho/yamlhandle'
12
+ require 'houcho/element'
13
+ require 'houcho/role'
14
+ require 'houcho/host'
15
+ require 'houcho/spec'
16
+ require 'houcho/spec/runner'
17
+ require 'houcho/cloudforecast'
18
+ require 'houcho/cloudforecast/role'
19
+ require 'houcho/cloudforecast/host'
20
+ require 'houcho/ci'
2
21
 
3
22
  module Houcho
4
- # Your code goes here...
23
+ def puts_details(e, indentsize = 0, cnt = 1)
24
+ case e
25
+ when Array
26
+ e.sort.each.with_index(1) do |v, i|
27
+ (indentsize-1).times {print ' '}
28
+ print i != e.size ? '├─ ' : '└─ '
29
+ puts v
30
+ end
31
+ puts ''
32
+ when Hash
33
+ e.each do |k,v|
34
+ if ! indentsize.zero?
35
+ (indentsize).times {print ' '}
36
+ end
37
+ k = k.color(0,255,0)
38
+ k = '[' + k.color(219,112,147) + ']' if indentsize.zero?
39
+ puts k
40
+ puts_details(v, indentsize+1, cnt+1)
41
+ end
42
+ end
43
+ end
5
44
  end
@@ -1,7 +1,12 @@
1
1
  require 'net/http'
2
2
  require 'uri'
3
3
 
4
- class CI
4
+ module Houcho
5
+ module CI
6
+ end
7
+ end
8
+
9
+ module Houcho::CI
5
10
  class UkigumoClient
6
11
  def initialize(server, port = 80, url = "http://#{server}:#{port}")
7
12
  @ukigumo_server = server
@@ -57,5 +62,4 @@ class CI
57
62
  end
58
63
  end
59
64
  end
60
-
61
65
  end
@@ -0,0 +1,59 @@
1
+ module Houcho
2
+ module CloudForecast
3
+ module_function
4
+
5
+ def load_yaml
6
+ yaml_file = Tempfile.new('yaml')
7
+ File.open(yaml_file,'a') do |t|
8
+ Find.find('./role/cloudforecast') do |f|
9
+ t.write File.read(f) if f =~ /\.yaml$/
10
+ end
11
+ end
12
+
13
+ role_hosts = {}
14
+ elements = {}
15
+ group = []
16
+ File.open(yaml_file) do |f|
17
+ f.each do |l|
18
+ if l =~ /^---/
19
+ if l =~ /^---\s+#(.+)$/
20
+ group << $1.gsub(/\s/, '_')
21
+ else
22
+ group << 'NOGROUPNAME'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ File.open(yaml_file) do |f|
28
+ i=0
29
+ YAML.load_documents(f) do |data|
30
+ elements[group[i]] ||= []
31
+ elements[group[i]].concat data['servers']
32
+ i+=1
33
+ end
34
+ end
35
+
36
+ elements.each do |groupname, data|
37
+ current_label = 'NOCATEGORYNAME'
38
+
39
+ data.each do |d|
40
+ if ! d['label'].nil?
41
+ label = d['label'].gsub(/\s/, '_')
42
+ current_label = label if current_label != label
43
+ end
44
+
45
+ d['hosts'].map! do |host|
46
+ host = host.split(' ')
47
+ host = host.size == 1 ? host[0] : host[1]
48
+ end
49
+
50
+ r = groupname + '::' + current_label + '::' + d['config'].sub(/\.yaml$/, '')
51
+ ary = (role_hosts[r] || []) | d['hosts']
52
+ role_hosts[r] = ary.uniq
53
+ end
54
+ end
55
+
56
+ File.write('./role/cloudforecast.yaml', role_hosts.to_yaml)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,23 @@
1
+ module Houcho
2
+ module CloudForecast::Host
3
+ @cfdata = YamlHandle::Loader.new('./role/cloudforecast.yaml').data
4
+
5
+ module_function
6
+
7
+ def roles(host)
8
+ @cfdata.select {|cfrole, cfhosts|cfhosts.include?(host)}.keys
9
+ end
10
+
11
+ def hosts(role)
12
+ @cfdata[role] || []
13
+ end
14
+
15
+ def details(host)
16
+ CloudForecast::Role.details(roles(host))
17
+ end
18
+
19
+ def all
20
+ @cfdata.values.flatten.uniq
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module Houcho
2
+ module CloudForecast
3
+ class Role
4
+ @elements = YamlHandle::Editor.new('./role/cf_roles.yaml')
5
+ extend Element
6
+
7
+ def self.details(cfroles)
8
+ result = {}
9
+ cfroles.each do |cfrole|
10
+ hosts = CloudForecast::Host.hosts(cfrole)
11
+ if ! hosts.empty?
12
+ result[cfrole] = {}
13
+ result[cfrole]['host'] = hosts
14
+ end
15
+ end
16
+ result
17
+ end
18
+
19
+ def self.all
20
+ YamlHandle::Loader.new('./role/cloudforecast.yaml').data.keys
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,74 @@
1
+ module Houcho
2
+ module Element
3
+
4
+ def elements(index = nil)
5
+ if index
6
+ (@elements.data[index]||[]).uniq
7
+ else
8
+ @elements.data.values.flatten.uniq
9
+ end
10
+ end
11
+
12
+
13
+ def attach(elements, roles)
14
+ invalid_roles = []
15
+ roles.each do |role|
16
+ index = Role.index(role)
17
+ if ! index
18
+ invalid_roles << role
19
+ next
20
+ end
21
+
22
+ @elements.data[index] ||= []
23
+ @elements.data[index] = (@elements.data[index] + elements).sort.uniq
24
+ end
25
+
26
+ @elements.save_to_file
27
+ abort("role(#{invalid_roles.join(',')}) does not exist") if ! invalid_roles.size.zero?
28
+ end
29
+
30
+
31
+ def detach(elements, roles)
32
+ invalid_roles = []
33
+ roles.each do |role|
34
+ index = Role.index(role)
35
+ if ! index
36
+ invalid_roles << role
37
+ next
38
+ end
39
+
40
+ @elements.data[index] -= elements
41
+ end
42
+
43
+ @elements.save_to_file
44
+ abort("role(#{invalid_roles.join(',')}) does not exist") if ! invalid_roles.size.zero?
45
+ end
46
+
47
+
48
+ def attached?(index, element)
49
+ return false if ! @elements.data.has_key?(index)
50
+ @elements.data[index].include?(element)
51
+ end
52
+
53
+
54
+ def has_data?(index)
55
+ return false if ! @elements.data.has_key?(index)
56
+ @elements.data[index].size != 0
57
+ end
58
+
59
+
60
+ def indexes(element)
61
+ return [] if ! @elements.data.values.flatten.include?(element)
62
+ @elements.data.select {|index, elems|elems.include?(element)}.keys
63
+ end
64
+
65
+
66
+ def details(elements)
67
+ result = {}
68
+ elements.each do |element|
69
+ result[element] = { 'role' => self.indexes(element).map {|index|Role.name(index)} }
70
+ end
71
+ result
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,22 @@
1
+ module Houcho
2
+ class Host
3
+ @elements = YamlHandle::Editor.new('./role/hosts.yaml')
4
+ extend Element
5
+
6
+ def self.details(hosts)
7
+ result = {}
8
+
9
+ hosts.each do |host|
10
+ roles = self.indexes(host).map {|index|Role.name(index)}
11
+ cfroles = CloudForecast::Host.roles(host)
12
+
13
+ result[host] = {}
14
+ result[host]['role'] = roles if ! roles.empty?
15
+ result[host]['cf'] = cfroles if ! cfroles.empty?
16
+
17
+ result.delete(host) if result[host].keys.empty?
18
+ end
19
+ result
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+
3
+ module Houcho
4
+ module Initialize
5
+ templates = File.expand_path("#{File.dirname(__FILE__)}/../../templates")
6
+
7
+ %W{conf role spec}.each do |d|
8
+ FileUtils.cp_r("#{templates}/#{d}", d) if ! Dir.exist?(d)
9
+ end
10
+
11
+ File.symlink('./conf/rspec.conf', './.rspec') if ! File.exists? '.rspec'
12
+
13
+ `git init; git add .; git commit -a -m 'initialized houcho repository'` if ! Dir.exist?('.git')
14
+ end
15
+ end
@@ -0,0 +1,121 @@
1
+ module Houcho
2
+ module Role
3
+ @roles = YamlHandle::Editor.new('./role/roles.yaml')
4
+
5
+ module_function
6
+
7
+ def create(role, exists = [])
8
+ target = role.shift
9
+
10
+ if self.index(target)
11
+ exists << target
12
+ else
13
+ @roles.data[(@roles.data.keys.max||0) + 1] = target
14
+ end
15
+
16
+ if role.size == 0
17
+ @roles.save_to_file
18
+ abort("role(#{exists.join(',')}) already exist.") if exists.size != 0
19
+ else
20
+ self.create(role, exists)
21
+ end
22
+ end
23
+
24
+
25
+ def delete(role, errors = {exists:[], hosts:[], specs:[], cf:[]})
26
+ target = role.shift
27
+ index = self.index(target)
28
+ del = true
29
+
30
+ if ! index
31
+ errors[:exists] << target; del = false
32
+ end
33
+ if Host.has_data?(index)
34
+ errors[:hosts] << target if Host.has_data?(index); del = false
35
+ end
36
+ if Spec.has_data?(index)
37
+ errors[:specs] << target if Spec.has_data?(index); del = false
38
+ end
39
+ if CloudForecast::Role.has_data?(index)
40
+ errors[:cf] << target if CloudForecast::Role.has_data?(index); del = false
41
+ end
42
+
43
+ @roles.data.delete(index) if del
44
+
45
+ if role.size.zero?
46
+ @roles.save_to_file
47
+ e = []
48
+ e << "role(#{errors[:exists].join(',')}) does not exist" if ! errors[:exists].size.zero?
49
+ e << "detach host from #{errors[:hosts].join(',')} before delete" if ! errors[:hosts].size.zero?
50
+ e << "detach spec from #{errors[:specs].join(',')} before delete" if ! errors[:specs].size.zero?
51
+ e << "detach cloudforecast's role from #{errors[:cf].join(',')} before delete" if ! errors[:cf].size.zero?
52
+ abort("#{e.join(', ')}.") if ! e.size.zero?
53
+ else
54
+ self.delete(role, errors)
55
+ end
56
+ end
57
+
58
+
59
+ def rename(role, name)
60
+ index = self.index(role)
61
+ abort("#{role} does not exist") if ! index
62
+ abort("#{name} already exist") if self.index(name)
63
+
64
+ @roles.data[index] = name
65
+ @roles.save_to_file
66
+ end
67
+
68
+
69
+ def all
70
+ YamlHandle::Loader.new('./role/roles.yaml').data.values.sort
71
+ end
72
+
73
+
74
+ def details(roles)
75
+ result = {}
76
+
77
+ # too lengthy implementation... I think necessary to change...
78
+ roles = roles.map do |role|
79
+ if self.index(role)
80
+ role
81
+ else
82
+ self.indexes_regexp(Regexp.new(role)).map {|index|self.name(index)}
83
+ end
84
+ end.flatten.sort.uniq
85
+
86
+ roles.each do |role|
87
+ index = self.index(role)
88
+ next if ! index
89
+
90
+ hosts = Host.elements(index)
91
+ specs = Spec.elements(index)
92
+ cfroles = CloudForecast::Role.elements(index)
93
+ cfhosts = CloudForecast::Role.details(cfroles)
94
+
95
+ r = {}
96
+ r['host'] = hosts if ! hosts.empty?
97
+ r['spec'] = specs if ! specs.empty?
98
+ r['cf'] = cfhosts if ! cfhosts.empty?
99
+
100
+ result[role] = r
101
+ end
102
+
103
+ result
104
+ end
105
+
106
+
107
+ def index(role)
108
+ @roles.data.invert[role]
109
+ end
110
+
111
+
112
+ def indexes_regexp(role)
113
+ @roles.data.select {|index, rolename| rolename =~ role }.keys
114
+ end
115
+
116
+
117
+ def name(index)
118
+ @roles.data[index]
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,6 @@
1
+ module Houcho
2
+ class Spec
3
+ @elements = YamlHandle::Editor.new('./role/specs.yaml')
4
+ extend Element
5
+ end
6
+ end
@@ -0,0 +1,116 @@
1
+ module Houcho
2
+ module Spec::Runner
3
+ module_function
4
+
5
+ def prepare(roles, ex_hosts, hosts, specs)
6
+ runlist = {}
7
+ spec_not_exist = []
8
+
9
+ spec_check = Proc.new do |specs|
10
+ p = specs.map {|spec|'spec/' + spec + '_spec.rb'}.partition {|spes|File.exist?(spes)}
11
+ spec_not_exist += p[1].map {|e|e.sub(/^spec\//,'').sub(/_spec.rb$/,'')}
12
+ p[0]
13
+ end
14
+
15
+ Role.details(roles).each do |role, detail|
16
+ r = {}
17
+ r['spec'] = spec_check.call(detail['spec']||[])
18
+ r['host'] = detail['host']||[]
19
+
20
+ (detail['cf']||{}).each do |cfrole, value|
21
+ r['host'] += value['host']
22
+ end
23
+ r['host'] -= ex_hosts
24
+
25
+ runlist[role] = r
26
+ end
27
+
28
+ m = {}
29
+ m['host'] = hosts
30
+ m['spec'] = spec_check.call(specs)
31
+
32
+ runlist[:'run manually'] = m if m != {}
33
+
34
+ if ! spec_not_exist.empty?
35
+ $houcho_fail = true
36
+ puts "spec(#{spec_not_exist.join(',')}) file not exist in ./spec directory."
37
+ end
38
+ runlist
39
+ end
40
+
41
+
42
+ def exec(roles, ex_hosts, hosts, specs, ci = {}, dryrun = nil)
43
+ self.prepare(roles, ex_hosts, hosts, specs).each do |role, v|
44
+ next if v['spec'].empty?
45
+ command = "parallel_rspec #{v['spec'].sort.uniq.join(' ')}"
46
+
47
+ if dryrun
48
+ v['host'].each do |host|
49
+ puts "TARGET_HOST=#{host} #{command}"
50
+ end
51
+ next
52
+ end
53
+
54
+ v['host'].each do |host|
55
+ ENV['TARGET_HOST'] = host
56
+ result = systemu command
57
+ puts result[1].scan(/\d* examples?, \d* failures?\n/).first.chomp + "\t#{host}, #{command}\n"
58
+
59
+ post_result(result, role, host, command, ci) if ci != {}
60
+ $houcho_fail = true if result[0] != 0
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+ def post_result(result, role, host, command, ci)
67
+ conf = YAML.load_file('conf/houcho.conf')
68
+ result_status = result[0] == 0 ? 1 : 2
69
+
70
+ if ci[:ukigumo]
71
+ ukigumo_report = CI::UkigumoClient.new(conf['ukigumo']['host'], conf['ukigumo']['port']).post({
72
+ :status => result_status,
73
+ :project => role,
74
+ :branch => host.gsub(/\./, '-'),
75
+ :repo => conf['git']['uri'],
76
+ :revision => `git log spec/| grep '^commit' | head -1 | awk '{print $2}'`.chomp,
77
+ :vc_log => command,
78
+ :body => result[1],
79
+ })
80
+ end
81
+
82
+ if ci[:ikachan] && result_status != 1
83
+ message = "[serverspec fail]\`TARGET_HOST=#{host} #{command}\` "
84
+ message += JSON.parse(ukigumo_report)['report']['url'] if ukigumo_report
85
+ CI::IkachanClient.new(
86
+ conf['ikachan']['channel'],
87
+ conf['ikachan']['host'],
88
+ conf['ikachan']['port']
89
+ ).post(message)
90
+ end
91
+ end
92
+
93
+
94
+ def check(specs, host_count)
95
+ specs = specs.flatten
96
+
97
+ specs.each do |spec|
98
+ hosts = []
99
+ indexes = Spec.indexes(spec)
100
+
101
+ if indexes.empty?
102
+ puts "#{spec} has not attached to any roles"
103
+ next
104
+ end
105
+
106
+ indexes.each do |index|
107
+ hosts += Host.elements(index)
108
+ CloudForecast::Role.elements(index).each do |cfrole|
109
+ hosts += CloudForecast::Host(cfrole)
110
+ end
111
+ end
112
+ hosts.sample(host_count).each {|host| exec([], [], [host], [spec])}
113
+ end
114
+ end
115
+ end
116
+ end