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/doc/json/http.json
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
{
|
2
|
+
"app": {
|
3
|
+
"desc": "Test role",
|
4
|
+
"http": {
|
5
|
+
"globoesporte.globo.com": {
|
6
|
+
"match": "<html"
|
7
|
+
},
|
8
|
+
|
9
|
+
"http://globoesporte.com": {
|
10
|
+
"code": 301,
|
11
|
+
"expect_header": "Location: http://globoesporte.globo.com/"
|
12
|
+
},
|
13
|
+
|
14
|
+
"http://api.sde.globo.com/docs": {
|
15
|
+
"code" : 301
|
16
|
+
},
|
17
|
+
|
18
|
+
"https://api.sde.globo.com/path/to/resource": {
|
19
|
+
"code" : 401
|
20
|
+
},
|
21
|
+
|
22
|
+
"doesnotexist.globo.com": {},
|
23
|
+
|
24
|
+
"https://api.cartola.globo.com/mercado/status.json": {},
|
25
|
+
|
26
|
+
"https://api.cartola.globo.com/wrong-url": {},
|
27
|
+
|
28
|
+
"http://g1.globo.com": {
|
29
|
+
"code": 301,
|
30
|
+
"match": "<html"
|
31
|
+
},
|
32
|
+
|
33
|
+
"http://g1.globo.com/index.html": {
|
34
|
+
"match": "<html"
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
data/doc/json/ssh.json
ADDED
data/helper/acl.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_relative '../lib/util'
|
20
21
|
require 'socket'
|
21
22
|
require 'timeout'
|
22
23
|
require 'resolv'
|
23
24
|
|
24
25
|
class TDIPlan < TDI
|
25
|
-
def acl(
|
26
|
-
|
26
|
+
def acl(role_name, plan_name, plan_content)
|
27
|
+
plan_content.select { |key, val|
|
27
28
|
val.is_a?(Hash)
|
28
29
|
}.each_pair do |case_name, case_content|
|
29
30
|
# Parse.
|
@@ -36,26 +37,36 @@ class TDIPlan < TDI
|
|
36
37
|
|
37
38
|
# ACL.
|
38
39
|
ports.each do |port|
|
40
|
+
# Initialize vars.
|
41
|
+
addr = nil
|
42
|
+
res_str = "#{host}:#{port}"
|
43
|
+
res_dict = {host: host, addr: addr, port: port, origin_network: origin_network(host)}
|
44
|
+
|
39
45
|
begin
|
46
|
+
addr = Resolv.getaddress(host)
|
47
|
+
res_str = "#{host}/#{addr}:#{port}"
|
48
|
+
res_dict = {host: host, addr: addr, port: port, origin_network: origin_network(host)}
|
49
|
+
|
40
50
|
timeout(timeout_limit) do
|
41
51
|
begin
|
42
|
-
|
43
|
-
Resolv.getaddress(host)
|
44
|
-
sock = TCPSocket.open(host, port)
|
52
|
+
sock = TCPSocket.open(addr, port)
|
45
53
|
sock.close
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
rescue
|
52
|
-
|
53
|
-
|
54
|
-
failure "ACL (#{user}): #{rt.message}"
|
54
|
+
res_msg = "ACL (#{user}): #{res_str}"
|
55
|
+
success role_name, plan_name, res_msg, res_dict
|
56
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET => e
|
57
|
+
res_msg = "ACL (#{user}): #{res_str} (#{e.message})"
|
58
|
+
warning role_name, plan_name, res_msg, res_dict
|
59
|
+
rescue => e
|
60
|
+
res_msg = "ACL (#{user}): #{res_str} (#{e.message})"
|
61
|
+
failure role_name, plan_name, res_msg, res_dict
|
55
62
|
end
|
56
63
|
end
|
57
|
-
rescue Timeout::Error
|
58
|
-
|
64
|
+
rescue Timeout::Error => e
|
65
|
+
res_msg = "ACL (#{user}): #{res_str} (Timed out (#{timeout_limit}s) #{e.message})"
|
66
|
+
failure role_name, plan_name, res_msg, res_dict
|
67
|
+
rescue => e
|
68
|
+
res_msg = "ACL (#{user}): #{res_str} (#{e.message})"
|
69
|
+
failure role_name, plan_name, res_msg, res_dict
|
59
70
|
end
|
60
71
|
end
|
61
72
|
end
|
data/helper/file.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.
|
@@ -21,8 +21,8 @@ require 'fileutils'
|
|
21
21
|
require 'etc'
|
22
22
|
|
23
23
|
class TDIPlan < TDI
|
24
|
-
def file(
|
25
|
-
|
24
|
+
def file(role_name, plan_name, plan_content)
|
25
|
+
plan_content.select { |key, val|
|
26
26
|
val.is_a?(Hash)
|
27
27
|
}.each_pair do |case_name, case_content|
|
28
28
|
# Parse.
|
@@ -51,7 +51,7 @@ class TDIPlan < TDI
|
|
51
51
|
exit 1
|
52
52
|
end
|
53
53
|
|
54
|
-
# Apply
|
54
|
+
# Apply permissions test.
|
55
55
|
def testPerm filename, perm, type
|
56
56
|
# Perm.
|
57
57
|
begin
|
@@ -89,8 +89,7 @@ class TDIPlan < TDI
|
|
89
89
|
else
|
90
90
|
df_path = path
|
91
91
|
end
|
92
|
-
|
93
|
-
device = `#{fs_location_query_cmd}`
|
92
|
+
device = %x(df -P #{df_path} | tail -n 1 | awk '{print $1}')
|
94
93
|
|
95
94
|
case location
|
96
95
|
when 'local'
|
@@ -103,10 +102,12 @@ class TDIPlan < TDI
|
|
103
102
|
end
|
104
103
|
|
105
104
|
# Verdict.
|
105
|
+
res_msg = "FILE (#{user}): #{path} => #{perm} #{type} #{location}"
|
106
|
+
res_dict = case_content
|
106
107
|
if @flag_success
|
107
|
-
success
|
108
|
+
success role_name, plan_name, res_msg, res_dict
|
108
109
|
else
|
109
|
-
failure
|
110
|
+
failure role_name, plan_name, res_msg, res_dict
|
110
111
|
end
|
111
112
|
end
|
112
113
|
|
data/helper/http.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,125 +17,125 @@
|
|
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_relative '../lib/util'
|
20
21
|
require 'net/http'
|
21
|
-
require
|
22
|
+
require 'net/https'
|
22
23
|
require 'timeout'
|
23
24
|
require 'uri'
|
24
25
|
require 'resolv'
|
25
26
|
|
26
27
|
class TDIPlan < TDI
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# Normalizing
|
28
|
+
def _parse(uri, params)
|
29
|
+
# Normalizing.
|
31
30
|
if not uri =~ /^https?:\/\//
|
32
|
-
|
31
|
+
uri = 'http://' + uri.to_s
|
33
32
|
end
|
34
33
|
|
35
|
-
# URI
|
34
|
+
# URI.
|
36
35
|
_uri = URI(uri)
|
37
|
-
ssl = _uri.scheme.eql?(
|
36
|
+
ssl = _uri.scheme.eql?('https')
|
38
37
|
host = _uri.host
|
39
38
|
port = _uri.port
|
40
39
|
path = _uri.path.empty? ? '/' : _uri.path
|
41
40
|
|
42
|
-
# Params
|
41
|
+
# Params.
|
43
42
|
code = params['code'].nil? ? 200 : params['code'].to_i
|
44
43
|
match = params['match']
|
45
44
|
expect_header = params['expect_header']
|
46
45
|
timeout_limit = params['timeout'].nil? ? 2 : params['timeout'].to_i
|
47
46
|
|
48
47
|
if not params['proxy'].nil?
|
49
|
-
|
50
|
-
|
48
|
+
proxy, proxy_port = params['proxy'].split(/:/)
|
49
|
+
proxy_port = 3128 unless not proxy_port.nil?
|
51
50
|
end
|
52
51
|
|
53
52
|
if not params['expect_header'].nil?
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
53
|
+
expect_header_key = params['expect_header'].split(':').first
|
54
|
+
expect_header_value = nil
|
55
|
+
if params['expect_header'].include?(':')
|
56
|
+
expect_header_value = params['expect_header'][params['expect_header'].index(':')+1..-1].strip
|
57
|
+
end
|
59
58
|
end
|
60
59
|
|
61
|
-
return host, port, path,
|
60
|
+
return host, port, path, proxy, proxy_port, code, match, expect_header_key, expect_header_value, ssl, timeout_limit
|
62
61
|
end
|
63
62
|
|
64
|
-
def http(
|
65
|
-
|
63
|
+
def http(role_name, plan_name, plan_content)
|
64
|
+
plan_content.select { |key, val|
|
66
65
|
val.is_a?(Hash)
|
67
|
-
}.each_pair do |case_name,case_content|
|
68
|
-
|
69
|
-
host, port, path,
|
66
|
+
}.each_pair do |case_name, case_content|
|
67
|
+
# Parse params.
|
68
|
+
host, port, path, proxy, proxy_port, code, match, expect_header_key, expect_header_value, ssl, timeout_limit = _parse(case_name, case_content)
|
70
69
|
|
71
|
-
# User
|
70
|
+
# User.
|
72
71
|
user = Etc.getpwuid(Process.euid).name
|
73
72
|
|
73
|
+
# Initialize vars.
|
74
|
+
addr = nil
|
75
|
+
proxy_addr = nil
|
76
|
+
res_str = case_name
|
77
|
+
res_dict = {url: case_name, origin_network: origin_network(host)}
|
74
78
|
response = nil
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
Resolv.getaddress(proxy_addr)
|
79
|
-
http = Net::HTTP::Proxy(proxy_addr, proxy_port)
|
80
|
+
begin
|
81
|
+
addr = Resolv.getaddress(host)
|
80
82
|
|
83
|
+
if not proxy.nil? and not proxy_port.nil?
|
84
|
+
proxy_addr = Resolv.getaddress(proxy)
|
85
|
+
http = Net::HTTP::Proxy(proxy, proxy_port)
|
81
86
|
timeout(timeout_limit) do
|
82
87
|
begin
|
83
|
-
|
84
|
-
http.start(host,port,:use_ssl => ssl, :verify_mode => OpenSSL::SSL::VERIFY_NONE) { |http|
|
88
|
+
http.start(host, port, use_ssl: ssl, verify_mode: OpenSSL::SSL::VERIFY_NONE) { |http|
|
85
89
|
response = http.get(path)
|
86
90
|
}
|
87
|
-
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
88
|
-
|
91
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET => e
|
92
|
+
res_msg = "HTTP (#{user}): #{res_str} (#{e.message})"
|
93
|
+
warning role_name, plan_name, res_msg, res_dict
|
89
94
|
end
|
90
95
|
end
|
91
|
-
rescue Resolv::ResolvError => re
|
92
|
-
failure "HTTP (#{user}): #{re.message}"
|
93
|
-
rescue Resolv::ResolvTimeout => rt
|
94
|
-
failure "HTTP (#{user}): #{rt.message}"
|
95
|
-
rescue Timeout::Error
|
96
|
-
failure "HTTP (#{user}): #{case_name} - Timed out (#{timeout_limit}s)."
|
97
|
-
end
|
98
96
|
|
99
|
-
|
100
|
-
begin
|
101
|
-
Resolv.getaddress(host)
|
97
|
+
else
|
102
98
|
http = Net::HTTP.new(host, port)
|
103
|
-
|
104
99
|
if ssl
|
105
|
-
|
106
|
-
|
100
|
+
http.use_ssl = true
|
101
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
107
102
|
end
|
108
|
-
|
109
103
|
timeout(timeout_limit) do
|
110
104
|
begin
|
111
105
|
http.start() { |http|
|
112
106
|
response = http.get(path)
|
113
107
|
}
|
114
|
-
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
115
|
-
|
108
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET => e
|
109
|
+
res_msg = "HTTP (#{user}): #{res_str} (#{e.message})"
|
110
|
+
warning role_name, plan_name, res_msg, res_dict
|
116
111
|
end
|
117
112
|
end
|
118
|
-
rescue Resolv::ResolvError => re
|
119
|
-
failure "HTTP (#{user}): #{re.message}"
|
120
|
-
rescue Resolv::ResolvTimeout => rt
|
121
|
-
failure "HTTP (#{user}): #{rt.message}"
|
122
|
-
rescue Timeout::Error
|
123
|
-
failure "HTTP (#{user}): #{case_name} - Timed out."
|
124
113
|
end
|
114
|
+
rescue Timeout::Error => e
|
115
|
+
res_msg = "HTTP (#{user}): #{res_str} (Timed out (#{timeout_limit}s) #{e.message})"
|
116
|
+
failure role_name, plan_name, res_msg, res_dict
|
117
|
+
rescue => e
|
118
|
+
res_msg = "HTTP (#{user}): #{res_str} (#{e.message})"
|
119
|
+
failure role_name, plan_name, res_msg, res_dict
|
125
120
|
end
|
126
121
|
|
127
122
|
if not response.nil?
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
123
|
+
if not match.nil? and not response.body.chomp.include?(match.chomp)
|
124
|
+
res_msg = "HTTP (#{user}): #{res_str} (Expect string '#{match.chomp}')"
|
125
|
+
failure role_name, plan_name, res_msg, res_dict
|
126
|
+
elsif not expect_header_key.nil? and not expect_header_value.nil? and not response[expect_header_key].eql?(expect_header_value)
|
127
|
+
res_msg = "HTTP (#{user}): #{res_str} (Expect header with content '#{expect_header_key}: #{expect_header_value}')"
|
128
|
+
failure role_name, plan_name, res_msg, res_dict
|
129
|
+
elsif not expect_header_key.nil? and response[expect_header_key].nil?
|
130
|
+
res_msg = "HTTP (#{user}): #{res_str} (Expect header '#{expect_header_key}')"
|
131
|
+
failure role_name, plan_name, res_msg, res_dict
|
132
|
+
elsif not code.nil? and (response.code.to_i != code)
|
133
|
+
res_msg = "HTTP (#{user}): #{res_str} (Expect HTTP response code #{code})"
|
134
|
+
failure role_name, plan_name, res_msg, res_dict
|
135
|
+
else
|
136
|
+
res_msg = "HTTP (#{user}): #{res_str}"
|
137
|
+
success role_name, plan_name, res_msg, res_dict
|
138
|
+
end
|
139
139
|
end
|
140
140
|
end
|
141
141
|
end
|
data/helper/ssh.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,12 +17,13 @@
|
|
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_relative '../lib/util'
|
20
21
|
require 'net/ssh'
|
21
22
|
require 'resolv'
|
22
23
|
|
23
24
|
class TDIPlan < TDI
|
24
|
-
def ssh(
|
25
|
-
|
25
|
+
def ssh(role_name, plan_name, plan_content)
|
26
|
+
plan_content.select { |key, val|
|
26
27
|
val.is_a?(Hash)
|
27
28
|
}.each_pair do |case_name, case_content|
|
28
29
|
# Validate.
|
@@ -35,6 +36,7 @@ class TDIPlan < TDI
|
|
35
36
|
remote_user = case_name.split('@').first
|
36
37
|
host = case_name.split('@').last
|
37
38
|
local_users = [case_content['local_user']].flatten
|
39
|
+
timeout_limit = case_content['timeout'].nil? ? 5 : case_content['timeout'].to_i
|
38
40
|
|
39
41
|
# Users.
|
40
42
|
local_users.each do |local_user|
|
@@ -57,21 +59,30 @@ class TDIPlan < TDI
|
|
57
59
|
exit 1
|
58
60
|
end
|
59
61
|
|
62
|
+
# Initialize vars.
|
63
|
+
addr = nil
|
64
|
+
res_str = "#{remote_user}@#{host}"
|
65
|
+
res_dict = {local_user: local_user, remote_user: remote_user, host: host, addr: addr, origin_network: origin_network(host)}
|
66
|
+
|
60
67
|
begin
|
61
|
-
|
62
|
-
|
68
|
+
addr = Resolv.getaddress(host)
|
69
|
+
res_str = "#{remote_user}@#{host}/#{addr}"
|
70
|
+
res_dict = {local_user: local_user, remote_user: remote_user, host: host, addr: addr, origin_network: origin_network(host)}
|
71
|
+
|
72
|
+
timeout(timeout_limit) do
|
63
73
|
ssh_session = Net::SSH.start(host,
|
64
74
|
remote_user,
|
65
|
-
:
|
75
|
+
auth_methods: ['publickey'])
|
66
76
|
ssh_session.close
|
67
|
-
|
77
|
+
res_msg = "SSH (#{local_user}): #{res_str}"
|
78
|
+
success role_name, plan_name, res_msg, res_dict
|
68
79
|
end
|
69
|
-
rescue
|
70
|
-
|
71
|
-
|
72
|
-
failure "SSH (#{local_user}): #{rt.message}"
|
80
|
+
rescue Timeout::Error => e
|
81
|
+
res_msg = "SSH (#{local_user}): #{res_str} (Timed out (#{timeout_limit}s) #{e.message})"
|
82
|
+
failure role_name, plan_name, res_msg, res_dict
|
73
83
|
rescue => e
|
74
|
-
|
84
|
+
res_msg = "SSH (#{local_user}): #{res_str} (#{e.message})"
|
85
|
+
failure role_name, plan_name, res_msg, res_dict
|
75
86
|
end
|
76
87
|
end
|
77
88
|
end
|