houcho 0.0.6 → 0.0.8

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