tdi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ require 'net/ssh'
2
+ require 'etc'
3
+
4
+ class TDIPlan < TDI
5
+ def ssh(plan)
6
+ plan.select { |key, val|
7
+ val.is_a?(Hash)
8
+ }.each_pair do |case_name, case_content|
9
+ # Validate.
10
+ unless /^[^@]+@[^@]+$/.match(case_name)
11
+ puts "ERR: Invalid ssh plan format \"#{case_name}\". Case name must match pattern \"user@host\".".light_magenta
12
+ exit 1
13
+ end
14
+
15
+ # Parse.
16
+ remote_user = case_name.split('@').first
17
+ host = case_name.split('@').last
18
+ local_users = [case_content['local_user']].flatten
19
+
20
+ # Users.
21
+ local_users.each do |local_user|
22
+ # Privileged user.
23
+ begin
24
+ Process.euid = 0
25
+ rescue => e
26
+ puts "ERR: Must run as root to change user credentials #{e}).".light_magenta
27
+ exit 1
28
+ end
29
+
30
+ # Change credentials to local user.
31
+ begin
32
+ Process.euid = Etc.getpwnam(local_user).uid
33
+ # SSH needs to know the user homedir in order to use the right
34
+ # private/public key pair to authenticate.
35
+ ENV['HOME'] = Etc.getpwnam(local_user).dir
36
+ rescue => e
37
+ puts "ERR: User \"#{local_user}\" not found (#{e}).".light_magenta
38
+ exit 1
39
+ end
40
+
41
+ begin
42
+ timeout(5) do
43
+ ssh_session = Net::SSH.start(host,
44
+ remote_user,
45
+ :auth_methods => ['publickey'])
46
+ ssh_session.close
47
+ success "SSH (#{local_user}): #{remote_user}@#{host}"
48
+ end
49
+ rescue
50
+ failure "SSH (#{local_user}): #{remote_user}@#{host}"
51
+ end
52
+ end
53
+ end
54
+
55
+ # Change credentials back to privileged user.
56
+ Process.euid = 0
57
+ end
58
+ end
@@ -0,0 +1,298 @@
1
+ require_relative 'rblank'
2
+ require_relative 'rmerge'
3
+
4
+ # Test plan builder.
5
+ def planner(opts, plan)
6
+ # Compila um plano completo.
7
+ # Processa a herança.
8
+ # Compila novamente para gerar um plano intermediário.
9
+ #
10
+ # Pass 1.
11
+ if opts[:verbose] > 1
12
+ puts '* Pass 1...'.cyan
13
+ puts
14
+ end
15
+ compiled_plan1 = plan_compiler(opts, plan)
16
+ inherited_plan1 = plan_inheriter(opts, compiled_plan1)
17
+ recompiled_plan1 = plan_compiler(opts, inherited_plan1)
18
+ if opts[:verbose] > 1
19
+ puts '* Pass 1... done.'.green
20
+ puts
21
+ end
22
+
23
+ # Zera o plano, mantendo apenas a estrutura de hashes. Desta forma é possível
24
+ # gerar um esqueleto de plano com todas as entradas de test cases com valores
25
+ # vazios (originais e herdados).
26
+ # Logo em seguida ocorre um merge recursivo com os valores originais para
27
+ # depois ser processado por completo novamente.
28
+ # O objetivo é dar precedência aos valores globais locais sobre os valores
29
+ # herdados.
30
+ #
31
+ # Blank and repopulate with original values.
32
+ blanked_plan = recompiled_plan1.rblank
33
+ if opts[:verbose] > 2
34
+ puts 'Blanked plan:'
35
+ puts "* #{blanked_plan}".yellow
36
+ end
37
+
38
+ repopulated_plan = blanked_plan.rmerge(compiled_plan1)
39
+ if opts[:verbose] > 2
40
+ puts 'Repopulated plan:'
41
+ puts "* #{repopulated_plan}".yellow
42
+ end
43
+
44
+ # Compila um plano completo.
45
+ # Processa a herança.
46
+ # Compila novamente para gerar um plano final.
47
+ #
48
+ # Pass 2.
49
+ if opts[:verbose] > 1
50
+ puts '* Pass 2...'.cyan
51
+ puts
52
+ end
53
+ compiled_plan2 = plan_compiler(opts, repopulated_plan)
54
+ inherited_plan2 = plan_inheriter(opts, compiled_plan2)
55
+ recompiled_plan2 = plan_compiler(opts, inherited_plan2)
56
+ if opts[:verbose] > 1
57
+ puts '* Pass 2... done.'.green
58
+ puts
59
+ end
60
+
61
+ # Final plan.
62
+ plan_filter(opts, recompiled_plan2)
63
+ end
64
+
65
+ # Test plan compile.
66
+ def plan_compiler(opts, plan)
67
+ # Gera um plano de teste baseado em todos os valores dos hashes, desde o mais
68
+ # global (role) até o mais específico (test case).
69
+ # Ordem de precedência: test case > test plan.
70
+
71
+ puts 'Compiling test plan...'.cyan if opts[:verbose] > 1
72
+
73
+ compiled_plan = {}
74
+
75
+ # Role.
76
+ # Ex: {"admin": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
77
+ plan.select { |key, val|
78
+ val.is_a?(Hash)
79
+ }.each_with_index do |(role_name, role_content), index|
80
+ if opts[:verbose] > 2
81
+ puts '=' * 60
82
+ puts "Role: #{role_name}"
83
+ puts 'Role content:'
84
+ puts "* #{role_content}".yellow
85
+ end
86
+
87
+ # Role (if not already).
88
+ compiled_plan[role_name] ||= role_content
89
+
90
+ # Test plan.
91
+ # Ex: {"acl": {"domain1": {"port": 80}...}...}
92
+ role_content.select { |key, val|
93
+ val.is_a?(Hash)
94
+ }.each_pair do |plan_name, plan_content|
95
+ if opts[:verbose] > 2
96
+ puts "Plan: #{plan_name}"
97
+ puts 'Plan content:'
98
+ puts "* #{plan_content}".yellow
99
+ end
100
+
101
+ # Test plan (if not already).
102
+ compiled_plan[role_name][plan_name] ||= plan_content
103
+
104
+ # Test case.
105
+ # Ex: {"domain1": {"port": 80}...}
106
+ plan_content.select { |key, val|
107
+ val.is_a?(Hash)
108
+ }.each_pair do |case_name, case_content|
109
+ if opts[:verbose] > 2
110
+ puts "Case: #{case_name}"
111
+ puts 'Case content:'
112
+ puts "* #{case_content}".yellow
113
+ end
114
+
115
+ # Test case compile.
116
+ new_case_content = role_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) or val.is_a?(Hash) }
117
+ new_case_content.merge!(plan_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) or val.is_a?(Hash) })
118
+ new_case_content.merge!(case_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) or val.is_a?(Hash) })
119
+
120
+ # Test case (new, merged).
121
+ compiled_plan[role_name][plan_name][case_name] = new_case_content
122
+
123
+ if opts[:verbose] > 2
124
+ puts 'Compiled case content:'
125
+ puts "* #{new_case_content}".yellow
126
+ end
127
+ end
128
+ end
129
+
130
+ if opts[:verbose] > 2
131
+ puts '=' * 60
132
+ puts unless index == plan.size - 1
133
+ end
134
+ end
135
+
136
+ if opts[:verbose] > 1
137
+ puts 'Compiling test plan... done.'.green
138
+ puts
139
+ end
140
+
141
+ compiled_plan
142
+ end
143
+
144
+ # Poor's man test plan inheritance.
145
+ def plan_inheriter(opts, plan)
146
+ # Processa a herança entre roles e test plans.
147
+
148
+ puts 'Inheriting test plan...'.cyan if opts[:verbose] > 1
149
+
150
+ inherited_plan = {}
151
+
152
+ # Role may inherit from role.
153
+ # Ex: {"admin": {"desc": "...", "inherits": "other_role", "acl": {"domain1": {"port": 80}...}...}...}
154
+ plan.select { |key, val|
155
+ val.is_a?(Hash)
156
+ }.each_with_index do |(role_name, role_content), index|
157
+ if opts[:verbose] > 2
158
+ puts '=' * 60
159
+ puts "Role: #{role_name}"
160
+ puts 'Role content:'
161
+ puts "* #{role_content}".yellow
162
+ end
163
+
164
+ # Role (if not already).
165
+ inherited_plan[role_name] ||= role_content
166
+
167
+ # Inheritance present?
168
+ i_role_name = role_content['inherits']
169
+ unless i_role_name.nil?
170
+ puts "Role #{role_name} inherits #{i_role_name}" if opts[:verbose] > 2
171
+ inherited_plan[role_name] = plan[i_role_name].rmerge(role_content)
172
+ end
173
+
174
+ # Plan may inherit from plan.
175
+ # Ex: {"acl": {"inherits": "other_role::other_plan", "domain1": {"port": 80}...}...}
176
+ role_content.select { |key, val|
177
+ val.is_a?(Hash)
178
+ }.each_pair do |plan_name, plan_content|
179
+ if opts[:verbose] > 2
180
+ puts "Plan: #{plan_name}"
181
+ puts 'Plan content:'
182
+ puts "* #{plan_content}".yellow
183
+ end
184
+
185
+ # Test plan (if not already).
186
+ inherited_plan[role_name][plan_name] ||= plan_content
187
+
188
+ # Inheritance present?
189
+ i_plan = plan_content['inherits']
190
+ unless i_plan.nil?
191
+ i_role_name, i_plan_name = role_plan_split(i_plan)
192
+
193
+ if i_role_name.nil? or i_plan_name.nil?
194
+ puts "ERR: Invalid inheritance \"#{i_plan}\". Must match pattern \"role::plan\".".light_magenta
195
+ exit 1
196
+ end
197
+
198
+ # TODO: Tratar quando chave não existe.
199
+ puts "Plan #{plan_name} inherits #{i_plan}" if opts[:verbose] > 2
200
+ inherited_plan[role_name][plan_name] = plan[i_role_name][i_plan_name].rmerge(plan_content)
201
+ end
202
+ end
203
+
204
+ if opts[:verbose] > 2
205
+ puts '=' * 60
206
+ puts unless index == plan.size - 1
207
+ end
208
+ end
209
+
210
+ if opts[:verbose] > 1
211
+ puts 'Inheriting test plan... done.'.green
212
+ puts
213
+ end
214
+
215
+ inherited_plan
216
+ end
217
+
218
+ # Test plan filter.
219
+ def plan_filter(opts, plan)
220
+ # Filtra roles e test plans desejados a partir do plano fornecido.
221
+
222
+ puts 'Filtering test plan...'.cyan if opts[:verbose] > 1
223
+
224
+ filtered_plan = {}
225
+ flag_err = false
226
+
227
+ # Ex: tdi -p admin
228
+ # Ex: tdi -p admin::acl
229
+ # Ex: tdi --plan fe
230
+ # Ex: tdi --plan admin::acl,solr,be
231
+ if opts.plan?
232
+ # Do filter.
233
+ puts 'Filtering following test plan from input file:'.cyan if opts[:verbose] > 0
234
+ opts[:plan].each do |plan_name|
235
+ puts " - #{plan_name}".cyan if opts[:verbose] > 0
236
+
237
+ # Pattern from command line is already validate by validate_args().
238
+ # Does not need to check for nil.
239
+ f_role_name, f_plan_name = role_plan_split(plan_name)
240
+
241
+ if plan.has_key?(f_role_name)
242
+ unless f_plan_name.nil?
243
+ # Test plan only.
244
+ if plan[f_role_name].has_key?(f_plan_name)
245
+ # Initialize hash key if not present.
246
+ filtered_plan[f_role_name] ||= {}
247
+ filtered_plan[f_role_name][f_plan_name] = plan[f_role_name][f_plan_name]
248
+ puts " Test plan \"#{plan_name}\" included.".green if opts[:verbose] > 0
249
+ else
250
+ puts "ERR: Test plan \"#{plan_name}\" not found in input file. This test plan can not be included.".light_magenta
251
+ flag_err = true
252
+ end
253
+ else
254
+ # Role test plan (entire).
255
+ filtered_plan[f_role_name] = plan[f_role_name]
256
+ puts " Role \"#{plan_name}\" included.".green if opts[:verbose] > 0
257
+ end
258
+ else
259
+ puts "ERR: Role \"#{f_role_name}\" not found in input file. Test plan \"#{plan_name}\" can not be included.".light_magenta
260
+ flag_err = true
261
+ end
262
+ end
263
+
264
+ puts if opts[:verbose] > 0
265
+ else
266
+ # No filter.
267
+ filtered_plan = plan
268
+ end
269
+
270
+ if opts[:verbose] > 2
271
+ puts 'Filtered test plan:'.cyan
272
+ puts "* #{filtered_plan}".yellow
273
+ end
274
+
275
+ exit 1 if flag_err
276
+
277
+ if opts[:verbose] > 1
278
+ puts 'Filtering test plan... done.'.green
279
+ puts
280
+ end
281
+
282
+ filtered_plan
283
+ end
284
+
285
+ # Split role_name and plan_name.
286
+ def role_plan_split(name)
287
+ if name.include?('::')
288
+ # Test plan only.
289
+ f_role_name = name.split('::').first
290
+ f_plan_name = name.split('::').last
291
+ else
292
+ # Role test plan (entire).
293
+ f_role_name = name
294
+ f_plan_name = nil
295
+ end
296
+
297
+ return f_role_name, f_plan_name
298
+ end
@@ -0,0 +1,23 @@
1
+ module HashRecursiveBlank
2
+ def rblank
3
+ r = {}
4
+
5
+ each_pair do |key, val|
6
+ r[key] = val.rblank if val.is_a?(Hash)
7
+ end
8
+
9
+ r.keep_if { |key, val| val.is_a?(Hash) }
10
+ end
11
+
12
+ def rblank!
13
+ each_pair do |key, val|
14
+ self[key] = val.rblank! if val.is_a?(Hash)
15
+ end
16
+
17
+ keep_if { |key, val| val.is_a?(Hash) }
18
+ end
19
+ end
20
+
21
+ class Hash
22
+ include HashRecursiveBlank
23
+ end
@@ -0,0 +1,19 @@
1
+ module HashRecursiveMerge
2
+ def rmerge(other_hash)
3
+ r = {}
4
+
5
+ merge(other_hash) do |key, oldval, newval|
6
+ r[key] = oldval.is_a?(Hash) ? oldval.rmerge(newval) : newval
7
+ end
8
+ end
9
+
10
+ def rmerge!(other_hash)
11
+ merge!(other_hash) do |key, oldval, newval|
12
+ oldval.is_a?(Hash) ? oldval.rmerge!(newval) : newval
13
+ end
14
+ end
15
+ end
16
+
17
+ class Hash
18
+ include HashRecursiveMerge
19
+ end
@@ -0,0 +1,99 @@
1
+ require_relative 'tdi'
2
+
3
+ # Run tests.
4
+ def runner(opts, filename, plan)
5
+ puts 'Running tests...'.cyan if opts[:verbose] > 1
6
+
7
+ # Skip reserved roles.
8
+ # Ex: {"global": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
9
+ # Ex: {"common": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
10
+ plan.select { |role_name, role_content|
11
+ if role_content.is_a?(Hash)
12
+ UNTESTABLE_ROLE_LIST.include?(role_name) or role_content['notest'].eql?('true')
13
+ end
14
+ }.each_pair do |role_name, role_content|
15
+ puts "Skipping reserved or disabled role: #{role_name}".yellow if opts[:verbose] > 0
16
+ end
17
+
18
+ # Remove untestable roles.
19
+ plan.reject! { |role_name, role_content|
20
+ if role_content.is_a?(Hash)
21
+ UNTESTABLE_ROLE_LIST.include?(role_name) or role_content['notest'].eql?('true')
22
+ end
23
+ }
24
+ total_roles = plan.select { |key, val| val.is_a?(Hash) }.size
25
+ puts "Total roles to run: #{total_roles}".cyan if opts[:verbose] > 1
26
+
27
+ # Run the rest.
28
+ tdiplan = TDIPlan.new
29
+
30
+ # Role.
31
+ # Ex: {"admin": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
32
+ plan.select { |key, val|
33
+ val.is_a?(Hash)
34
+ }.each_with_index do |(role_name, role_content), index|
35
+ total_plans = role_content.select { |key, val| val.is_a?(Hash) }.size
36
+
37
+ if role_content['desc'].nil?
38
+ puts "* #{role_name.capitalize}".cyan
39
+ else
40
+ puts "* #{role_name.capitalize} - #{role_content['desc']}".cyan
41
+ end
42
+ # puts "Running tests for role: #{role_name}".cyan if opts[:verbose] > 0
43
+ puts "Total test plans to run for this role: #{total_plans}".cyan if opts[:verbose] > 1
44
+
45
+ # Test plan.
46
+ # Ex: {"acl": {"domain1": {"port": 80}...}...}
47
+ role_content.select { |key, val|
48
+ val.is_a?(Hash)
49
+ }.each_pair do |plan_name, plan_content|
50
+ total_cases = plan_content.select { |key, val| val.is_a?(Hash) }.size
51
+
52
+ puts "* #{plan_name.upcase}".cyan
53
+ # puts "Test plan: #{role_name}::#{plan_name}".cyan if opts[:verbose] > 0
54
+ puts "Total test cases to run for this plan: #{total_cases}".cyan if opts[:verbose] > 1
55
+
56
+ if opts[:verbose] > 3
57
+ puts "Plan: #{plan_name}"
58
+ puts 'Plan content:'
59
+ puts "* #{plan_content}".yellow
60
+ end
61
+
62
+ # Test plan content (test cases).
63
+ # Ex: {"domain1": {"port": 80}, "domain2": {"port": 80}...}
64
+ if tdiplan.respond_to?(plan_name)
65
+ tdiplan.send(plan_name, plan_content)
66
+ else
67
+ puts "Skipping not supported test plan type \"#{plan_name}\" for \"#{role_name}::#{plan_name}\".".yellow
68
+ tdiplan.skip = tdiplan.skip + total_cases
69
+ end
70
+ end
71
+
72
+ puts unless index == plan.size - 1
73
+ end
74
+
75
+ # Summary.
76
+ summary(opts, tdiplan)
77
+
78
+ # Shred.
79
+ if opts.shred?
80
+ puts "Shreding and removing test plan file: \"#{filename}\"...".cyan if opts[:verbose] > 2
81
+ if system("shred -f -n 38 -u -z #{filename}")
82
+ puts "Shreding and removing test plan file: \"#{filename}\"... done.".green if opts[:verbose] > 2
83
+ else
84
+ puts "ERR: Shreding and removing test plan file: \"#{filename}\".".light_magenta
85
+ end
86
+ end
87
+
88
+ puts 'Running tests... done.'.green if opts[:verbose] > 1
89
+
90
+ ret = tdiplan.passed? ? 0 : 1
91
+ ret = 0 if opts.nofail?
92
+ ret
93
+ end
94
+
95
+ # Display test summary.
96
+ def summary(opts, tdiplan)
97
+ puts '=' * 79
98
+ puts "Total: #{tdiplan.total} | Skip: #{tdiplan.skip} | Pass: #{tdiplan.pass} | Warn: #{tdiplan.warn} | Fail: #{tdiplan.fail}"
99
+ end