tdi 0.1.6 → 0.1.7
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.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/Gemfile.lock +6 -2
- data/README.md +257 -22
- data/bin/tdi +25 -13
- data/doc/json/acl.json +25 -0
- data/doc/json/file.json +31 -0
- data/doc/json/http.json +38 -0
- data/doc/json/ssh.json +16 -0
- data/helper/acl.rb +28 -17
- data/helper/file.rb +9 -8
- data/helper/http.rb +65 -65
- data/helper/ssh.rb +23 -12
- data/lib/planner.rb +9 -7
- data/lib/rblank.rb +1 -1
- data/lib/rmerge.rb +1 -1
- data/lib/runner.rb +36 -16
- data/lib/tdi.rb +20 -8
- data/lib/tdi/version.rb +2 -2
- data/lib/util.rb +110 -0
- data/tdi.gemspec +2 -0
- metadata +35 -2
data/lib/planner.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (C) 2013-
|
2
|
+
# Copyright (C) 2013-2015 Globo.com
|
3
3
|
#
|
4
4
|
|
5
5
|
# This file is part of TDI.
|
@@ -92,7 +92,7 @@ def plan_compiler(opts, plan)
|
|
92
92
|
compiled_plan = {}
|
93
93
|
|
94
94
|
# Role.
|
95
|
-
# Ex: {"
|
95
|
+
# Ex: {"app": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
|
96
96
|
plan.select { |key, val|
|
97
97
|
val.is_a?(Hash)
|
98
98
|
}.each_with_index do |(role_name, role_content), index|
|
@@ -169,7 +169,7 @@ def plan_inheriter(opts, plan)
|
|
169
169
|
inherited_plan = {}
|
170
170
|
|
171
171
|
# Role may inherit from role.
|
172
|
-
# Ex: {"
|
172
|
+
# Ex: {"app": {"desc": "...", "inherits": "other_role", "acl": {"domain1": {"port": 80}...}...}...}
|
173
173
|
plan.select { |key, val|
|
174
174
|
val.is_a?(Hash)
|
175
175
|
}.each_with_index do |(role_name, role_content), index|
|
@@ -243,10 +243,12 @@ def plan_filter(opts, plan)
|
|
243
243
|
filtered_plan = {}
|
244
244
|
flag_err = false
|
245
245
|
|
246
|
-
# Ex:
|
247
|
-
# Ex:
|
248
|
-
# Ex:
|
249
|
-
# Ex:
|
246
|
+
# Ex: -p admin
|
247
|
+
# Ex: -p admin::acl
|
248
|
+
# Ex: --plan app
|
249
|
+
# Ex: --plan app::acl
|
250
|
+
# Ex: --plan fe
|
251
|
+
# Ex: --plan admin::acl,app,fe
|
250
252
|
if opts.plan?
|
251
253
|
# Do filter.
|
252
254
|
puts 'Filtering following test plan from input file:'.cyan if opts[:verbose] > 0
|
data/lib/rblank.rb
CHANGED
data/lib/rmerge.rb
CHANGED
data/lib/runner.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (C) 2013-
|
2
|
+
# Copyright (C) 2013-2015 Globo.com
|
3
3
|
#
|
4
4
|
|
5
5
|
# This file is part of TDI.
|
@@ -19,6 +19,7 @@
|
|
19
19
|
|
20
20
|
require 'os'
|
21
21
|
require_relative 'tdi'
|
22
|
+
require_relative 'util'
|
22
23
|
|
23
24
|
# Run tests.
|
24
25
|
def runner(opts, filename, plan)
|
@@ -59,7 +60,6 @@ def runner(opts, filename, plan)
|
|
59
60
|
else
|
60
61
|
puts "* #{role_name.capitalize} - #{role_content['desc']}".cyan
|
61
62
|
end
|
62
|
-
# puts "Running tests for role: #{role_name}".cyan if opts[:verbose] > 0
|
63
63
|
puts "Total test plans to run for this role: #{total_plans}".cyan if opts[:verbose] > 1
|
64
64
|
|
65
65
|
# Test plan.
|
@@ -70,7 +70,6 @@ def runner(opts, filename, plan)
|
|
70
70
|
total_cases = plan_content.select { |key, val| val.is_a?(Hash) }.size
|
71
71
|
|
72
72
|
puts "* #{plan_name.upcase}".cyan
|
73
|
-
# puts "Test plan: #{role_name}::#{plan_name}".cyan if opts[:verbose] > 0
|
74
73
|
puts "Total test cases to run for this plan: #{total_cases}".cyan if opts[:verbose] > 1
|
75
74
|
|
76
75
|
if opts[:verbose] > 3
|
@@ -82,7 +81,7 @@ def runner(opts, filename, plan)
|
|
82
81
|
# Test plan content (test cases).
|
83
82
|
# Ex: {"domain1": {"port": 80}, "domain2": {"port": 80}...}
|
84
83
|
if tdiplan.respond_to?(plan_name)
|
85
|
-
tdiplan.send(plan_name, plan_content)
|
84
|
+
tdiplan.send(plan_name, role_name, plan_name, plan_content)
|
86
85
|
else
|
87
86
|
puts "Skipping not supported test plan type \"#{plan_name}\" for \"#{role_name}::#{plan_name}\".".yellow
|
88
87
|
tdiplan.skip = tdiplan.skip + total_cases
|
@@ -95,7 +94,40 @@ def runner(opts, filename, plan)
|
|
95
94
|
# Summary.
|
96
95
|
summary(opts, tdiplan)
|
97
96
|
|
97
|
+
# Report file.
|
98
|
+
report(opts, tdiplan)
|
99
|
+
|
98
100
|
# Shred.
|
101
|
+
shred(opts, filename, tdiplan)
|
102
|
+
|
103
|
+
puts 'Running tests... done.'.green if opts[:verbose] > 1
|
104
|
+
|
105
|
+
ret = tdiplan.plan_passed? ? 0 : 1
|
106
|
+
ret = 0 if opts.nofail?
|
107
|
+
ret
|
108
|
+
end
|
109
|
+
|
110
|
+
# Display test summary.
|
111
|
+
def summary(opts, tdiplan)
|
112
|
+
puts '=' * 79
|
113
|
+
puts "Total: #{tdiplan.total} | Skip: #{tdiplan.skip} | Pass: #{tdiplan.pass} | Warn: #{tdiplan.warn} | Fail: #{tdiplan.fail}"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Generate report file.
|
117
|
+
def report(opts, tdiplan)
|
118
|
+
if opts[:verbose] > 2
|
119
|
+
puts 'Report:'.cyan
|
120
|
+
a_p tdiplan.report
|
121
|
+
end
|
122
|
+
|
123
|
+
if opts.reportfile?
|
124
|
+
puts "Generating report file: \"#{opts[:reportfile]}\"".cyan if opts[:verbose] > 1
|
125
|
+
File.open(opts[:reportfile], 'w') { |file| file.write(JSON.pretty_generate(tdiplan.report)) }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Remove tdi plan file.
|
130
|
+
def shred(opts, filename, tdiplan)
|
99
131
|
if opts.shred?
|
100
132
|
puts "Shreding and removing test plan file: \"#{filename}\"...".cyan if opts[:verbose] > 2
|
101
133
|
if OS.linux?
|
@@ -112,16 +144,4 @@ def runner(opts, filename, plan)
|
|
112
144
|
puts "ERR: Shreding and removing test plan file: \"#{filename}\".".light_magenta
|
113
145
|
end
|
114
146
|
end
|
115
|
-
|
116
|
-
puts 'Running tests... done.'.green if opts[:verbose] > 1
|
117
|
-
|
118
|
-
ret = tdiplan.plan_passed? ? 0 : 1
|
119
|
-
ret = 0 if opts.nofail?
|
120
|
-
ret
|
121
|
-
end
|
122
|
-
|
123
|
-
# Display test summary.
|
124
|
-
def summary(opts, tdiplan)
|
125
|
-
puts '=' * 79
|
126
|
-
puts "Total: #{tdiplan.total} | Skip: #{tdiplan.skip} | Pass: #{tdiplan.pass} | Warn: #{tdiplan.warn} | Fail: #{tdiplan.fail}"
|
127
147
|
end
|
data/lib/tdi.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (C) 2013-
|
2
|
+
# Copyright (C) 2013-2015 Globo.com
|
3
3
|
#
|
4
4
|
|
5
5
|
# This file is part of TDI.
|
@@ -17,13 +17,14 @@
|
|
17
17
|
# You should have received a copy of the GNU General Public License
|
18
18
|
# along with TDI. If not, see <http://www.gnu.org/licenses/>.
|
19
19
|
|
20
|
+
require 'socket'
|
20
21
|
require_relative 'tdi/version'
|
21
22
|
|
22
23
|
class TDI
|
23
24
|
attr_accessor :plan_passed, :case_passed
|
24
25
|
alias :plan_passed? :plan_passed
|
25
26
|
alias :case_passed? :case_passed
|
26
|
-
attr_accessor :skip, :pass, :warn, :fail
|
27
|
+
attr_accessor :skip, :pass, :warn, :fail, :report
|
27
28
|
|
28
29
|
def initialize
|
29
30
|
@plan_passed = true
|
@@ -32,21 +33,32 @@ class TDI
|
|
32
33
|
@pass = 0
|
33
34
|
@warn = 0
|
34
35
|
@fail = 0
|
36
|
+
@report = {hostname: Socket.gethostname}
|
35
37
|
end
|
36
38
|
|
37
|
-
def
|
39
|
+
def update_report(status, role_name, plan_name, res_dict)
|
40
|
+
@report[role_name] = {} unless @report.has_key?(role_name)
|
41
|
+
@report[role_name][plan_name] = [] unless @report[role_name].has_key?(plan_name)
|
42
|
+
res_dict[:status] = status
|
43
|
+
@report[role_name][plan_name] << res_dict
|
44
|
+
end
|
45
|
+
|
46
|
+
def success(role_name, plan_name, res_msg, res_dict)
|
47
|
+
update_report(:pass, role_name, plan_name, res_dict)
|
38
48
|
# I like the seventies.
|
39
|
-
printf("%-70s [ %s ]\n",
|
49
|
+
printf("%-70s [ %s ]\n", res_msg, 'PASS'.light_green)
|
40
50
|
@pass += 1
|
41
51
|
end
|
42
52
|
|
43
|
-
def warning(
|
44
|
-
|
53
|
+
def warning(role_name, plan_name, res_msg, res_dict)
|
54
|
+
update_report(:warn, role_name, plan_name, res_dict)
|
55
|
+
printf("%-70s [ %s ]\n", res_msg, 'WARN'.light_yellow)
|
45
56
|
@warn += 1
|
46
57
|
end
|
47
58
|
|
48
|
-
def failure(
|
49
|
-
|
59
|
+
def failure(role_name, plan_name, res_msg, res_dict)
|
60
|
+
update_report(:fail, role_name, plan_name, res_dict)
|
61
|
+
printf("%-70s [ %s ]\n", res_msg, 'FAIL'.light_red)
|
50
62
|
@plan_passed = false
|
51
63
|
@case_passed = false
|
52
64
|
@fail += 1
|
data/lib/tdi/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (C) 2013-
|
2
|
+
# Copyright (C) 2013-2015 Globo.com
|
3
3
|
#
|
4
4
|
|
5
5
|
# This file is part of TDI.
|
@@ -18,5 +18,5 @@
|
|
18
18
|
# along with TDI. If not, see <http://www.gnu.org/licenses/>.
|
19
19
|
|
20
20
|
module Tdi
|
21
|
-
VERSION = '0.1.
|
21
|
+
VERSION = '0.1.7'
|
22
22
|
end
|
data/lib/util.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2013-2015 Globo.com
|
3
|
+
#
|
4
|
+
|
5
|
+
# This file is part of TDI.
|
6
|
+
|
7
|
+
# TDI is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
|
12
|
+
# TDI is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with TDI. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
require 'awesome_print'
|
21
|
+
require 'socket'
|
22
|
+
require 'ipaddress'
|
23
|
+
require 'resolv'
|
24
|
+
|
25
|
+
# Awesome Print config.
|
26
|
+
def a_p(obj)
|
27
|
+
ap obj, {
|
28
|
+
indent: 2,
|
29
|
+
index: false,
|
30
|
+
color: {
|
31
|
+
args: :pale,
|
32
|
+
array: :white,
|
33
|
+
bigdecimal: :blue,
|
34
|
+
class: :yellow,
|
35
|
+
date: :greenish,
|
36
|
+
falseclass: :red,
|
37
|
+
fixnum: :blue,
|
38
|
+
float: :blue,
|
39
|
+
hash: :blue,
|
40
|
+
keyword: :cyan,
|
41
|
+
method: :purpleish,
|
42
|
+
nilclass: :red,
|
43
|
+
rational: :blue,
|
44
|
+
string: :green,
|
45
|
+
struct: :pale,
|
46
|
+
symbol: :cyanish,
|
47
|
+
time: :greenish,
|
48
|
+
trueclass: :green,
|
49
|
+
variable: :cyanish,
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a list of local networks and it's details.
|
55
|
+
def local_networks
|
56
|
+
Socket.getifaddrs.each.select { |ifaddr| ifaddr.addr.ipv4? and ! ifaddr.name.start_with?('lo') }.
|
57
|
+
map do |ifaddr|
|
58
|
+
ip = IPAddress::IPv4.new("#{ifaddr.addr.ip_address}/#{ifaddr.netmask.ip_address}")
|
59
|
+
{
|
60
|
+
interface: ifaddr.name,
|
61
|
+
net: ip.network.to_string,
|
62
|
+
network: ip.network.address,
|
63
|
+
netmask: ip.netmask,
|
64
|
+
prefix: ip.prefix,
|
65
|
+
broadcast: ip.broadcast.address,
|
66
|
+
ipv4: ip.address,
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Return the origin network to be used when trying to connect to a remote
|
72
|
+
# service.
|
73
|
+
#
|
74
|
+
# Stolen from:
|
75
|
+
#
|
76
|
+
# https://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
|
77
|
+
#
|
78
|
+
# The above code does NOT make a connection or send any packets. Since UDP is a
|
79
|
+
# stateless protocol connect() merely makes a system call which figures out how
|
80
|
+
# to route the packets based on the address and what interface (and therefore IP
|
81
|
+
# address) it should bind to. addr() returns an array containing the family
|
82
|
+
# (AF_INET), local port, and local address (which is what we want) of the socket.
|
83
|
+
# This is a good alternative to `ifconfig`/`ipconfig` solutions because it
|
84
|
+
# doesn’t spawn a shell and it works the same on all systems.
|
85
|
+
def origin_network(remote)
|
86
|
+
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
|
87
|
+
|
88
|
+
host = remote # host (name or IP)
|
89
|
+
if IPAddress.valid?(remote)
|
90
|
+
addr = remote # use address (already IP)
|
91
|
+
else
|
92
|
+
addr = Resolv.getaddress(remote) rescue nil # get address (resolve name to IP)
|
93
|
+
end
|
94
|
+
|
95
|
+
UDPSocket.open do |s|
|
96
|
+
begin
|
97
|
+
s.connect(remote, 1)
|
98
|
+
res = {from: local_networks.each.select { |locnet| locnet[:ipv4].eql?(s.addr.last) }.first}
|
99
|
+
rescue
|
100
|
+
res = {from: nil}
|
101
|
+
end
|
102
|
+
|
103
|
+
res[:to] = {host: host, addr: addr}
|
104
|
+
|
105
|
+
return res
|
106
|
+
end
|
107
|
+
|
108
|
+
ensure
|
109
|
+
Socket.do_not_reverse_lookup = orig
|
110
|
+
end
|
data/tdi.gemspec
CHANGED
@@ -29,4 +29,6 @@ validating your deployed infrastructure and external dependencies.)
|
|
29
29
|
spec.add_runtime_dependency 'colorize'
|
30
30
|
spec.add_runtime_dependency 'timeout', '0.0.0'
|
31
31
|
spec.add_runtime_dependency 'net-ssh'
|
32
|
+
spec.add_runtime_dependency 'awesome_print'
|
33
|
+
spec.add_runtime_dependency 'ipaddress'
|
32
34
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tdi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rogério Carvalho Schneider
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-
|
13
|
+
date: 2015-07-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -124,6 +124,34 @@ dependencies:
|
|
124
124
|
- - ">="
|
125
125
|
- !ruby/object:Gem::Version
|
126
126
|
version: '0'
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
name: awesome_print
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :runtime
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
name: ipaddress
|
143
|
+
requirement: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
type: :runtime
|
149
|
+
prerelease: false
|
150
|
+
version_requirements: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
127
155
|
description: |-
|
128
156
|
Test Driven Infrastructure acceptance helpers for
|
129
157
|
validating your deployed infrastructure and external dependencies.
|
@@ -145,6 +173,10 @@ files:
|
|
145
173
|
- README.md
|
146
174
|
- Rakefile
|
147
175
|
- bin/tdi
|
176
|
+
- doc/json/acl.json
|
177
|
+
- doc/json/file.json
|
178
|
+
- doc/json/http.json
|
179
|
+
- doc/json/ssh.json
|
148
180
|
- helper/acl.rb
|
149
181
|
- helper/file.rb
|
150
182
|
- helper/http.rb
|
@@ -155,6 +187,7 @@ files:
|
|
155
187
|
- lib/runner.rb
|
156
188
|
- lib/tdi.rb
|
157
189
|
- lib/tdi/version.rb
|
190
|
+
- lib/util.rb
|
158
191
|
- tdi.gemspec
|
159
192
|
homepage: https://github.com/globocom/tdi
|
160
193
|
licenses:
|