houcho 0.0.6 → 0.0.8

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/Gemfile.lock +32 -0
  4. data/bin/houcho +3 -262
  5. data/houcho.gemspec +2 -1
  6. data/lib/houcho/attribute.rb +99 -0
  7. data/lib/houcho/ci.rb +18 -13
  8. data/lib/houcho/cli/attribute.rb +70 -0
  9. data/lib/houcho/cli/host.rb +46 -0
  10. data/lib/houcho/cli/outerrole.rb +53 -0
  11. data/lib/houcho/cli/role.rb +69 -0
  12. data/lib/houcho/cli/spec.rb +103 -0
  13. data/lib/houcho/cli.rb +52 -0
  14. data/lib/houcho/config.rb +28 -0
  15. data/lib/houcho/database.rb +99 -0
  16. data/lib/houcho/element.rb +79 -50
  17. data/lib/houcho/host.rb +30 -9
  18. data/lib/houcho/outerrole/cloudforecast.rb +94 -0
  19. data/{templates/role/cf_roles.yaml → lib/houcho/outerrole/yabitz.rb} +0 -0
  20. data/lib/houcho/outerrole.rb +52 -0
  21. data/lib/houcho/repository.rb +59 -0
  22. data/lib/houcho/role.rb +78 -91
  23. data/lib/houcho/spec/runner.rb +156 -85
  24. data/lib/houcho/spec.rb +72 -3
  25. data/lib/houcho/version.rb +1 -1
  26. data/lib/houcho.rb +10 -52
  27. data/spec/houcho_spec.rb +334 -91
  28. metadata +31 -22
  29. data/lib/houcho/cloudforecast/host.rb +0 -25
  30. data/lib/houcho/cloudforecast/role.rb +0 -25
  31. data/lib/houcho/cloudforecast.rb +0 -59
  32. data/lib/houcho/yamlhandle.rb +0 -31
  33. data/spec/spec_helper.rb +0 -15
  34. data/templates/conf/houcho.conf +0 -10
  35. data/templates/conf/kk.rb +0 -14
  36. data/templates/conf/rspec.conf +0 -1
  37. data/templates/master +0 -94
  38. data/templates/role/cloudforecast/.gitkeep +0 -0
  39. data/templates/role/cloudforecast.yaml +0 -0
  40. data/templates/role/hosts.yaml +0 -0
  41. data/templates/role/hosts_ignored.yaml +0 -0
  42. data/templates/role/roles.yaml +0 -0
  43. data/templates/role/specs.yaml +0 -0
  44. data/templates/spec/sample_spec.rb +0 -13
  45. data/templates/spec/spec_helper.rb +0 -29
@@ -0,0 +1,52 @@
1
+ require "houcho/element"
2
+ require "houcho/attribute"
3
+
4
+ module Houcho
5
+ class OuterRoleExistenceException < Exception; end
6
+
7
+ class OuterRole < Element
8
+ include Houcho::Attribute
9
+
10
+ def initialize
11
+ super("outerrole")
12
+ @type_id = 1
13
+ end
14
+
15
+ def details(outer_role)
16
+ outer_role = outer_role.is_a?(Array) ? outer_role : [outer_role]
17
+ result = {}
18
+ outer_role.each do |role|
19
+ hosts = hostlist(role)
20
+ if !hosts.empty?
21
+ result[role] = {}
22
+ result[role]["host"] = hosts
23
+ end
24
+ end
25
+
26
+ result
27
+ end
28
+
29
+ def hostlist(outer_role)
30
+ outer_role = outer_role.is_a?(Array) ? outer_role : [outer_role]
31
+ hosts = []
32
+
33
+ outer_role.each do |role|
34
+ id = id(role)
35
+ hosts << @db.execute("
36
+ SELECT host.name
37
+ FROM host
38
+ JOIN outerrole_host ORHOST
39
+ ON host.id = ORHOST.host_id
40
+ WHERE ORHOST.outerrole_id = ?
41
+ ", id)
42
+ end
43
+
44
+ hosts.flatten.uniq.sort
45
+ end
46
+
47
+ private
48
+ def raise_target_does_not_exist(target)
49
+ raise OuterRoleExistenceException, "outer role does not exist - #{target}"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,59 @@
1
+ require "houcho/config"
2
+ require "houcho/database"
3
+
4
+ module Houcho
5
+ class Repository
6
+ def self.init
7
+ [
8
+ Houcho::Config::APPROOT,
9
+ Houcho::Config::SPECDIR,
10
+ Houcho::Config::SCRIPTDIR,
11
+ Houcho::Config::OUTERROLESOURCEDIR,
12
+ Houcho::Config::CFYAMLDIR,
13
+ Houcho::Config::LOGDIR
14
+ ].each do |d|
15
+ Dir.mkdir(d) unless Dir.exist?(d)
16
+ end
17
+
18
+ File.write("#{Houcho::Config::FILE}", {
19
+ "ukigumo" => { "host" => "", "port" => "" },
20
+ "ikachan" => { "host" => "", "port" => "", "channel" => [] },
21
+ "git" => { "uri" => "" },
22
+ "rspec" => [],
23
+ }.to_yaml) unless File.exist?("#{Houcho::Config::FILE}")
24
+
25
+ File.write("#{Houcho::Config::SPECDIR}/spec_helper.rb", <<EOD
26
+ require "serverspec"
27
+ require 'pathname'
28
+ require 'net/ssh'
29
+ require "json"
30
+
31
+ include Serverspec::Helper::Ssh
32
+ include Serverspec::Helper::DetectOS
33
+ include Serverspec::Helper::Attributes
34
+
35
+ RSpec.configure do |c|
36
+ if ENV['ASK_SUDO_PASSWORD']
37
+ require 'highline/import'
38
+ c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false }
39
+ else
40
+ c.sudo_password = ENV['SUDO_PASSWORD']
41
+ end
42
+
43
+ c.ssh.close if c.ssh
44
+ c.host = ENV['TARGET_HOST']
45
+ options = Net::SSH::Config.for(c.host)
46
+ user = options[:user] || Etc.getlogin
47
+ c.ssh = Net::SSH.start(c.host, user, options)
48
+
49
+ if ENV['TARGET_HOST_ATTR']
50
+ attr_set JSON.parse(ENV['TARGET_HOST_ATTR'], { :symbolize_names => true })
51
+ end
52
+ end
53
+ EOD
54
+ ) unless File.exist?("#{Houcho::Config::SPECDIR}/spec_helper.rb")
55
+
56
+ Houcho::Database.new.create_tables
57
+ end
58
+ end
59
+ end
data/lib/houcho/role.rb CHANGED
@@ -1,128 +1,115 @@
1
- module Houcho
2
- module Role
3
- @roles = YamlHandle::Editor.new('./role/roles.yaml')
4
-
5
- module_function
1
+ require "houcho/database"
2
+ require "houcho/host"
3
+ require "houcho/spec"
4
+ require "houcho/outerrole"
5
+ require "houcho/attribute"
6
6
 
7
- def create(role, exists = [])
8
- role = [role] if role.class == String
9
- target = role.shift
7
+ module Houcho
8
+ class RoleExistenceException < Exception; end
10
9
 
11
- if self.index(target)
12
- exists << target
13
- else
14
- @roles.data[(@roles.data.keys.max||0) + 1] = target
15
- end
10
+ class Role
11
+ include Houcho::Attribute
16
12
 
17
- if role.size == 0
18
- @roles.save_to_file
19
- raise("role(#{exists.join(',')}) already exist.") if exists.size != 0
20
- else
21
- self.create(role, exists)
22
- end
13
+ def initialize
14
+ @db = Houcho::Database.new.handle
15
+ @type = "role"
16
+ @type_id = 0
23
17
  end
24
18
 
25
19
 
26
- def delete(role, errors = {exists:[], hosts:[], specs:[], cf:[]})
27
- role = [role] if role.class == String
28
- target = role.shift
29
- index = self.index(target)
30
- del = true
31
-
32
- if ! index
33
- errors[:exists] << target; del = false
34
- end
35
- if Host.has_data?(index)
36
- errors[:hosts] << target if Host.has_data?(index); del = false
37
- end
38
- if Spec.has_data?(index)
39
- errors[:specs] << target if Spec.has_data?(index); del = false
40
- end
41
- if CloudForecast::Role.has_data?(index)
42
- errors[:cf] << target if CloudForecast::Role.has_data?(index); del = false
43
- end
44
-
45
- @roles.data.delete(index) if del
46
-
47
- if role.size.zero?
48
- @roles.save_to_file
49
- e = []
50
- e << "role(#{errors[:exists].join(',')}) does not exist" if ! errors[:exists].size.zero?
51
- e << "detach host from #{errors[:hosts].join(',')} before delete" if ! errors[:hosts].size.zero?
52
- e << "detach spec from #{errors[:specs].join(',')} before delete" if ! errors[:specs].size.zero?
53
- e << "detach cloudforecast's role from #{errors[:cf].join(',')} before delete" if ! errors[:cf].size.zero?
54
- raise("#{e.join(', ')}.") if ! e.size.zero?
20
+ def id(role)
21
+ if role.is_a?(Regexp)
22
+ @db.execute("SELECT id, name FROM role").map do |record|
23
+ record[0] if record[1] =~ role
24
+ end
55
25
  else
56
- self.delete(role, errors)
26
+ @db.execute("SELECT id FROM role WHERE name = ?", role).flatten.first
57
27
  end
58
28
  end
59
29
 
60
30
 
61
- def rename(role, name)
62
- index = self.index(role)
63
- raise("#{role} does not exist") if ! index
64
- raise("#{name} already exist") if self.index(name)
65
-
66
- @roles.data[index] = name
67
- @roles.save_to_file
31
+ def name(id)
32
+ @db.execute("SELECT name FROM role WHERE id = ?", id).flatten.first
68
33
  end
69
34
 
70
35
 
71
- def all
72
- YamlHandle::Loader.new('./role/roles.yaml').data.values.sort
36
+ def exist?(role)
37
+ !id(role).nil?
73
38
  end
74
39
 
75
40
 
76
- def details(roles)
77
- result = {}
41
+ def create(role)
42
+ role = [role] unless role.is_a?(Array)
78
43
 
79
- # too lengthy implementation... I think necessary to change...
80
- roles = roles.map do |role|
81
- if self.index(role)
82
- role
83
- else
84
- self.indexes_regexp(Regexp.new(role)).map {|index|self.name(index)}
44
+ @db.transaction do
45
+ role.each do |r|
46
+ begin
47
+ @db.execute("INSERT INTO role(name) VALUES(?)", r)
48
+ rescue SQLite3::ConstraintException, "column name is not unique"
49
+ raise RoleExistenceException, "role already exist - #{r}"
50
+ end
85
51
  end
86
- end.flatten.sort.uniq
87
-
88
- roles.each do |role|
89
- index = self.index(role)
90
- next if ! index
52
+ end
53
+ end
91
54
 
92
- hosts = Host.elements(index)
93
- specs = Spec.elements(index)
94
- cfroles = CloudForecast::Role.elements(index)
95
- cfhosts = CloudForecast::Role.details(cfroles)
96
55
 
97
- r = {}
98
- r['host'] = hosts if ! hosts.empty?
99
- r['spec'] = specs if ! specs.empty?
100
- r['cf'] = cfhosts if ! cfhosts.empty?
56
+ def delete(role)
57
+ role = [role] unless role.is_a?(Array)
101
58
 
102
- result[role] = r
59
+ @db.transaction do
60
+ role.each do |r|
61
+ raise RoleExistenceException, "role does not exist - #{r}" unless exist?(r)
62
+ @db.execute("DELETE FROM role WHERE name = ?", r)
63
+ end
103
64
  end
104
-
105
- result
106
65
  end
107
66
 
108
67
 
109
- def index(role)
110
- @roles.data.invert[role]
68
+ def rename(exist_role, name)
69
+ raise RoleExistenceException, "role does not exist - #{exist_role}" unless exist?(exist_role)
70
+ raise RoleExistenceException, "role already exist - #{name}" if exist?(name)
71
+ @db.execute("UPDATE role SET name = '#{name}' WHERE name = '#{exist_role}'")
111
72
  end
112
73
 
113
74
 
114
- def indexes_regexp(role)
115
- @roles.data.select {|index, rolename| rolename =~ role }.keys
75
+ def list
76
+ @db.execute("SELECT name FROM role").flatten
116
77
  end
117
78
 
118
79
 
119
- def name(index)
120
- @roles.data[index]
121
- end
80
+ def details(role)
81
+ role = role.is_a?(Array) ? role : [role]
82
+ result = {}
83
+ hostobj = Host.new
84
+ specobj = Spec.new
85
+ orobj = OuterRole.new
86
+
87
+ role.each do |r|
88
+ id = id(r)
89
+ next if ! id
90
+ id = id.is_a?(Array) ? id : [id]
91
+
92
+ id.each do |i|
93
+ hosts = hostobj.list(i)
94
+ specs = specobj.list(i)
95
+ outerroles = orobj.list(i)
96
+ outerhosts = orobj.details(outerroles)
97
+
98
+ tmp = {}
99
+ tmp["host"] = hosts unless hosts.empty?
100
+ tmp["spec"] = specs unless specs.empty?
101
+ tmp["outer role"] = outerhosts unless outerhosts.empty?
102
+
103
+ result[r] = tmp
104
+ end
105
+ end
122
106
 
107
+ result
108
+ end
123
109
 
124
- def exist?(role)
125
- ! self.index(role).nil?
110
+ private
111
+ def raise_target_does_not_exist(target)
112
+ raise RoleExistenceException, "role does not exist - #{target}"
126
113
  end
127
114
  end
128
115
  end
@@ -1,125 +1,196 @@
1
+ require "yaml"
2
+ require "systemu"
3
+ require "json"
4
+ require "logger"
5
+ require "parallel"
6
+ require "houcho/role"
7
+ require "houcho/spec"
8
+ require "houcho/ci"
9
+ require "houcho/config"
10
+
1
11
  module Houcho
2
- module Spec::Runner
3
- module_function
4
12
 
5
- def prepare(roles, ex_hosts, hosts, specs)
6
- runlist = {}
7
- spec_not_exist = []
13
+ class Spec
14
+ class Runner
15
+ def initialize
16
+ @role = Houcho::Role.new
17
+ @host = Houcho::Host.new
18
+ @spec = Houcho::Spec.new
19
+ @outerrole = Houcho::OuterRole.new
20
+ @specdir = Houcho::Config::SPECDIR
21
+ @logger = Logger.new(Houcho::Config::SPECLOG, 10)
22
+ end
8
23
 
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
24
 
15
- Role.details(roles).each do |role, detail|
16
- r = {}
17
- r['spec'] = spec_check.call(detail['spec']||[])
18
- r['host'] = detail['host']||[]
25
+ def check(spec, host_count, dry_run = false, console_output = false) #dry_run for test
26
+ spec = spec.is_a?(Array) ? spec : [spec]
27
+ role = spec.map { |s| @spec.details(s)[s]["role"] }.flatten
28
+ host = []
19
29
 
20
- (detail['cf']||{}).each do |cfrole, value|
21
- r['host'] += value['host']
22
- end
23
- r['host'] -= ex_hosts
30
+ @role.details(role).each do |rolename, value|
31
+ host.concat(value["host"]).uniq if value["host"]
24
32
 
25
- runlist[role] = r
33
+ if value["outer role"]
34
+ value["outer role"].each do |outerrolename, v|
35
+ host.concat(v["host"]).uniq if v["host"]
36
+ end
37
+ end
26
38
  end
27
39
 
28
- m = {}
29
- m['host'] = hosts if ! hosts.empty?
30
- m['spec'] = spec_check.call(specs) if ! specs.empty?
40
+ execute_manually(host.sample(host_count), spec, dry_run, console_output)
41
+ end
42
+
43
+
44
+ def execute_manually(host, spec, dryrun = false, console_output = false)
45
+ host = host.is_a?(Array) ? host : [host]
46
+ spec = spec.is_a?(Array) ? spec : [spec]
47
+
48
+ run(
49
+ { rand(36**50).to_s(36) => { "host" => host, "spec" => spec } },
50
+ dryrun,
51
+ false,
52
+ console_output
53
+ )
54
+ end
55
+
56
+
57
+ def execute_role(role, ex_host = [], dryrun = false, console_output = false)
58
+ role = role.is_a?(Array) ? role : [role]
59
+ ex_host = ex_host.is_a?(Array) ? ex_host : [ex_host]
60
+
61
+ role_valiables = {}
31
62
 
32
- runlist[:'run manually'] = m if m != {}
63
+ @role.details(role).each do |rolename, value|
64
+ next unless value["spec"]
33
65
 
34
- if ! spec_not_exist.empty?
35
- raise "spec(#{spec_not_exist.join(',')}) file not exist in ./spec directory."
66
+ rv = {}
67
+ rv["host"] = []
68
+ rv["spec"] = []
69
+ rv["outer role"] = []
70
+
71
+ rv["spec"].concat(value["spec"]).uniq
72
+ rv["host"].concat(value["host"]).uniq if value["host"]
73
+
74
+ if value["outer role"]
75
+ rv["outer role"].concat(value["outer role"].keys).uniq
76
+ value["outer role"].each do |outerrolename, v|
77
+ rv["host"].concat(v["host"]).uniq if v["host"]
78
+ end
79
+ end
80
+
81
+ rv["host"] = rv["host"] - ex_host
82
+ role_valiables[rolename] = rv
36
83
  end
37
- runlist
84
+
85
+ run(role_valiables, dryrun, true, console_output)
38
86
  end
39
87
 
40
88
 
41
- def exec(roles, ex_hosts, hosts, specs, ci = {}, dryrun = nil)
89
+ def run(target, dryrun, ci = false, console_output = false)
42
90
  messages = []
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(' ')}"
91
+ failure = false
92
+
93
+ target.each do |role, v|
94
+ @spec.check_existence(v["spec"])
95
+ spec = v["spec"].map { |spec| "#{@specdir}/#{spec}_spec.rb" }
96
+ command = "rspec --format documentation #{spec.sort.uniq.join(" ")}"
46
97
 
47
98
  if dryrun
48
- v['host'].each do |host|
49
- messages << "TARGET_HOST=#{host} #{command}"
99
+ v["host"].each do |host|
100
+ drymsg = "TARGET_HOST=#{host} #{command}"
101
+ puts drymsg if console_output
102
+ messages << drymsg
50
103
  end
51
104
  next
52
105
  end
53
106
 
54
- v['host'].each do |host|
55
- ENV['TARGET_HOST'] = host
56
- result = systemu command
57
- messages << result[1].scan(/\d* examples?, \d* failures?\n/).first.chomp + "\t#{host}, #{command}\n"
107
+ Parallel.each(v["host"], :in_threads => Parallel.processor_count) do |host|
108
+ attr_role = @role.get_attr(role)
109
+ attr_host = @host.get_attr(host)
110
+ attr_outerrole = {}
58
111
 
59
- post_result(result, role, host, command, ci) if ci != {}
60
- $houcho_fail = true if result[0] != 0
112
+ if v["outer role"]
113
+ v["outer role"].each do |o|
114
+ attr_outerrole.merge!(@outerrole.get_attr(o))
115
+ end
116
+ end
117
+
118
+ attr = attr_role.merge(attr_outerrole)
119
+ attr = attr.merge(attr_host)
120
+
121
+ logmesg = ""
122
+ if attr != {} && attr_host == {} && v["outer role"].size > 1
123
+ logmsg = "might not be given the appropriate attribute value, because #{host} have no attributes and belongs to more than one outer role - #{v["outer role"].join(", ")}"
124
+ @logger.warn(host) { message }
125
+ logmsg += "\n"
126
+ end
127
+
128
+ result = systemu(
129
+ command,
130
+ :env => {
131
+ "TARGET_HOST" => host,
132
+ "TARGET_HOST_ATTR" => JSON.generate(attr)
133
+ },
134
+ :cwd => File.join(@specdir, "..")
135
+ )
136
+
137
+ @logger.info(host) { "#{result[1]}\n#{result[2]}" }
138
+ failure = true if result[0] != 0
139
+
140
+ msg = result[1].scan(/\d* examples?, \d* failures?\n/).first
141
+ msg = msg ? msg.chomp : "error"
142
+ msg += "\t#{host} => #{v["spec"].join(", ")}\n"
143
+ puts msg if console_output
144
+
145
+ if ci
146
+ begin
147
+ post_result(result, role, host, v["spec"], command, logmsg)
148
+ rescue
149
+ ci = false
150
+ end
151
+ end
61
152
  end
62
153
  end
63
- messages
154
+
155
+ failure ? false : messages
64
156
  end
65
157
 
66
158
 
67
- def post_result(result, role, host, command, ci)
68
- conf = YAML.load_file('conf/houcho.conf')
159
+ private
160
+ def post_result(result, role, host, spec, command, message)
69
161
  result_status = result[0] == 0 ? 1 : 2
70
162
 
71
- if ci[:ukigumo]
72
- ukigumo_report = CI::UkigumoClient.new(conf['ukigumo']['host'], conf['ukigumo']['port']).post({
163
+ ukigumo = Houcho::Config::UKIGUMO
164
+ ikachan = Houcho::Config::IKACHAN
165
+ git = Houcho::Config::GIT
166
+
167
+ if ukigumo["host"] != "" && ukigumo["port"] != "" && git["uri"]
168
+ u = CI::UkigumoClient.new(ukigumo["host"], ukigumo["port"])
169
+ ukigumo_report = u.post({
73
170
  :status => result_status,
74
- :project => role,
75
- :branch => host.gsub(/\./, '-'),
76
- :repo => conf['git']['uri'],
77
- :revision => `git log spec/| grep '^commit' | head -1 | awk '{print $2}'`.chomp,
171
+ :project => host.gsub(/\./, "-"),
172
+ :branch => role,
173
+ :repo => git["uri"],
174
+ :revision => spec.join(", "),
78
175
  :vc_log => command,
79
- :body => result[1],
176
+ :body => ( message || "" ) + result[1],
80
177
  })
81
178
  end
82
179
 
83
- if ci[:ikachan] && result_status != 1
84
- message = "[serverspec fail]\`TARGET_HOST=#{host} #{command}\` "
85
- message += JSON.parse(ukigumo_report)['report']['url'] if ukigumo_report
86
- CI::IkachanClient.new(
87
- conf['ikachan']['channel'],
88
- conf['ikachan']['host'],
89
- conf['ikachan']['port']
90
- ).post(message)
91
- end
92
- end
180
+ if ikachan["host"] != "" && ikachan["port"] != "" && result_status != 1
181
+ message = "[serverspec fail] #{host} => #{spec.join(", ")}"
182
+ message += " (#{JSON.parse(ukigumo_report)["report"]["url"]})" if ukigumo_report
93
183
 
184
+ i = CI::IkachanClient.new(
185
+ ikachan["channel"],
186
+ ikachan["host"],
187
+ ikachan["port"]
188
+ )
94
189
 
95
- def check(specs, host_count, dryrun = false) # dryrun is for test
96
- specs = specs.flatten
97
- error = []
98
- messages = []
99
-
100
- specs.each do |spec|
101
- hosts = []
102
- indexes = Spec.indexes(spec)
103
-
104
- if indexes.empty?
105
- error << spec
106
- next
107
- end
108
-
109
- indexes.each do |index|
110
- hosts += Host.elements(index)
111
- CloudForecast::Role.elements(index).each do |cfrole|
112
- hosts += CloudForecast::Host.new.hosts(cfrole)
113
- end
114
- end
115
- hosts.sample(host_count).each {|host| messages += exec([], [], [host], [spec], {}, dryrun)}
116
- end
117
-
118
- if error.empty?
119
- messages
120
- else
121
- raise("role(#{error.join(',')}) has not attached to any roles")
190
+ i.post(message)
122
191
  end
123
192
  end
124
193
  end
125
194
  end
195
+
196
+ end
data/lib/houcho/spec.rb CHANGED
@@ -1,6 +1,75 @@
1
+ require "houcho/role"
2
+ require "houcho/element"
3
+ require "houcho/config"
4
+
1
5
  module Houcho
2
- class Spec
3
- @elements = YamlHandle::Editor.new('./role/specs.yaml')
4
- extend Element
6
+ class SpecFileException < Exception; end
7
+
8
+ class Spec < Element
9
+ def initialize
10
+ super("serverspec")
11
+ @specdir = Houcho::Config::SPECDIR
12
+ end
13
+
14
+
15
+ def check_existence(specs)
16
+ specs = [specs] unless specs.is_a?(Array)
17
+ files = specs.partition { |spec| File.exist?("#{@specdir}/#{spec}_spec.rb") }
18
+ raise SpecFileException, "No such spec file - #{files[1].join(",")}" unless files[1].empty?
19
+
20
+ files[0]
21
+ end
22
+
23
+
24
+ def attach(specs, roles)
25
+ specs = [specs] unless specs.is_a?(Array)
26
+ roles = [roles] unless roles.is_a?(Array)
27
+ files = check_existence(specs)
28
+
29
+ super(files, roles)
30
+ end
31
+
32
+
33
+ def rename(from, to)
34
+ if File.exist?("#{@specdir}/#{to}_spec.rb")
35
+ raise SpecFileException, "spec file already exist - #{to}"
36
+ end
37
+
38
+ check_existence(from)
39
+ File.rename("#{@specdir}/#{from}_spec.rb", "#{@specdir}/#{to}_spec.rb")
40
+ @db.execute("UPDATE #{@type} SET name = ? WHERE name = ?", to, from)
41
+ end
42
+
43
+
44
+ def delete(specs, force = false)
45
+ specs = [specs] unless specs.is_a?(Array)
46
+
47
+ detach_from_all(specs) if force
48
+
49
+ @db.transaction do
50
+
51
+ specs.each do |spec|
52
+ begin
53
+ @db.execute("DELETE FROM #{@type} WHERE name = ?", spec)
54
+ rescue SQLite3::ConstraintException, "foreign key constraint failed"
55
+ raise SpecFileException, "spec file has been attached to role - #{spec}"
56
+ end
57
+ end
58
+
59
+ end #end of transaction
60
+
61
+ begin
62
+ check_existence(specs).each do |spec|
63
+ File.delete("#{@specdir}/#{spec}_spec.rb")
64
+ end
65
+ rescue => e
66
+ raise e.class, "#{e.message}" unless force
67
+ end
68
+ end
69
+
70
+
71
+ def delete!(specs, force = true)
72
+ delete(specs, true)
73
+ end
5
74
  end
6
75
  end
@@ -1,3 +1,3 @@
1
1
  module Houcho
2
- VERSION = '0.0.6'
2
+ VERSION = '0.0.8'
3
3
  end