taste_tester 0.0.1 → 0.0.2
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.
- data/LICENSE +201 -0
- data/README.md +133 -17
- data/bin/taste-tester +354 -0
- data/lib/taste_tester/client.rb +169 -0
- data/lib/taste_tester/commands.rb +144 -0
- data/lib/taste_tester/config.rb +92 -0
- data/lib/taste_tester/hooks.rb +52 -0
- data/lib/taste_tester/host.rb +187 -0
- data/lib/taste_tester/logging.rb +55 -0
- data/lib/taste_tester/server.rb +122 -0
- data/lib/taste_tester/ssh.rb +60 -0
- data/lib/taste_tester/state.rb +87 -0
- data/lib/taste_tester/tunnel.rb +53 -0
- data/scripts/taste-untester +77 -0
- metadata +63 -35
- checksums.yaml +0 -7
- data/.gitignore +0 -17
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -22
- data/Rakefile +0 -1
- data/lib/taste_tester/version.rb +0 -3
- data/lib/taste_tester.rb +0 -5
- data/taste_tester.gemspec +0 -23
@@ -0,0 +1,92 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
|
3
|
+
require 'mixlib/config'
|
4
|
+
require 'taste_tester/logging'
|
5
|
+
require 'between_meals/util'
|
6
|
+
|
7
|
+
module TasteTester
|
8
|
+
# Config file parser and config object
|
9
|
+
# Uses Mixlib::Config v1 syntax so it works in Chef10 omnibus...
|
10
|
+
# it's compatible with v2, so it should work in 11 too.
|
11
|
+
module Config
|
12
|
+
extend Mixlib::Config
|
13
|
+
extend TasteTester::Logging
|
14
|
+
extend BetweenMeals::Util
|
15
|
+
|
16
|
+
repo "#{ENV['HOME']}/ops"
|
17
|
+
repo_type 'git'
|
18
|
+
base_dir 'chef'
|
19
|
+
cookbook_dirs ['cookbooks']
|
20
|
+
role_dir 'roles'
|
21
|
+
databag_dir 'databags'
|
22
|
+
config_file '/etc/taste-tester-config.rb'
|
23
|
+
plugin_path '/etc/taste-tester-plugin.rb'
|
24
|
+
chef_zero_path '/opt/chef/embedded/bin/chef-zero'
|
25
|
+
verbosity Logger::WARN
|
26
|
+
timestamp false
|
27
|
+
user 'root'
|
28
|
+
ref_file "#{ENV['HOME']}/.chef/taste-tester-ref.json"
|
29
|
+
checksum_dir "#{ENV['HOME']}/.chef/checksums"
|
30
|
+
skip_repo_checks false
|
31
|
+
chef_client_command 'chef-client'
|
32
|
+
testing_time 3600
|
33
|
+
chef_port_range [5000, 5500]
|
34
|
+
tunnel_port 4001
|
35
|
+
timestamp_file '/etc/chef/test_timestamp'
|
36
|
+
use_ssh_tunnels true
|
37
|
+
|
38
|
+
skip_pre_upload_hook false
|
39
|
+
skip_post_upload_hook false
|
40
|
+
skip_pre_test_hook false
|
41
|
+
skip_post_test_hook false
|
42
|
+
skip_repo_checks_hook false
|
43
|
+
|
44
|
+
def self.cookbooks
|
45
|
+
cookbook_dirs.map do |x|
|
46
|
+
File.join(repo, base_dir, x)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.relative_cookbook_dirs
|
51
|
+
cookbook_dirs.map do |x|
|
52
|
+
File.join(base_dir, x)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.roles
|
57
|
+
File.join(repo, base_dir, role_dir)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.relative_role_dir
|
61
|
+
File.join(base_dir, role_dir)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.databags
|
65
|
+
File.join(repo, base_dir, databag_dir)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.relative_databag_dir
|
69
|
+
File.join(base_dir, databag_dir)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.chef_port
|
73
|
+
range = chef_port_range.first.to_i..chef_port_range.last.to_i
|
74
|
+
range.to_a.shuffle.each do |port|
|
75
|
+
unless port_open?(port)
|
76
|
+
return port
|
77
|
+
end
|
78
|
+
end
|
79
|
+
logger.error 'Could not find a free port in range' +
|
80
|
+
" [#{chef_port_range.first}, #{chef_port_range.last}]"
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.testing_end_time
|
85
|
+
if TasteTester::Config.testing_until
|
86
|
+
TasteTester::Config.testing_until.strftime('%y%m%d%H%M.%S')
|
87
|
+
else
|
88
|
+
(Time.now + TasteTester::Config.testing_time).strftime('%y%m%d%H%M.%S')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
|
3
|
+
require 'taste_tester/logging'
|
4
|
+
require 'between_meals/util'
|
5
|
+
|
6
|
+
module TasteTester
|
7
|
+
# Hooks placeholders
|
8
|
+
class Hooks
|
9
|
+
extend TasteTester::Logging
|
10
|
+
extend BetweenMeals::Util
|
11
|
+
|
12
|
+
# Do stuff before we upload to chef-zero
|
13
|
+
def self.pre_upload(_dryrun, _repo, _last_ref, _cur_ref)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Do stuff after we upload to chef-zero
|
17
|
+
def self.post_upload(_dryrun, _repo, _last_ref, _cur_ref)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Do stuff before we put hosts in test mode
|
21
|
+
def self.pre_test(_dryrun, _repo, _hosts)
|
22
|
+
end
|
23
|
+
|
24
|
+
# This should return an array of commands to execute on
|
25
|
+
# remote systems.
|
26
|
+
def self.test_remote_cmds(_dryrun, _hostname)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Should return a string with extra stuff to shove
|
30
|
+
# in the remote client.rb
|
31
|
+
def self.test_remote_client_rb_extra_code(_hostname)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Do stuff after we put hosts in test mode
|
35
|
+
def self.post_test(_dryrun, _repo, _hosts)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Additional checks you want to do on the repo
|
39
|
+
def self.repo_checks(_dryrun, _repo)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.get(file)
|
43
|
+
path = File.expand_path(file)
|
44
|
+
logger.warn("Loading plugin at #{path}")
|
45
|
+
unless File.exists?(path)
|
46
|
+
logger.error('Plugin file not found')
|
47
|
+
exit(1)
|
48
|
+
end
|
49
|
+
class_eval(File.read(path), __FILE__, __LINE__)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'base64'
|
5
|
+
require 'open3'
|
6
|
+
require 'colorize'
|
7
|
+
|
8
|
+
require 'taste_tester/ssh'
|
9
|
+
require 'taste_tester/tunnel'
|
10
|
+
|
11
|
+
module TasteTester
|
12
|
+
# Manage state of the remote node
|
13
|
+
class Host
|
14
|
+
include TasteTester::Logging
|
15
|
+
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
def initialize(name, server)
|
19
|
+
@name = name
|
20
|
+
@user = ENV['USER']
|
21
|
+
@server = server
|
22
|
+
@tunnel = TasteTester::Tunnel.new(@name, @server)
|
23
|
+
end
|
24
|
+
|
25
|
+
def runchef
|
26
|
+
logger.warn("Running '#{TasteTester::Config.command}' on #{@name}")
|
27
|
+
cmd = "ssh #{TasteTester::Config.user}@#{@name} "
|
28
|
+
if TasteTester::Config.user != 'root'
|
29
|
+
cc = Base64.encode64(cmds).gsub(/\n/, '')
|
30
|
+
cmd += "\"echo '#{cc}' | base64 --decode | sudo bash -x\""
|
31
|
+
else
|
32
|
+
cmd += "\"#{cmds}\""
|
33
|
+
end
|
34
|
+
status = IO.popen(
|
35
|
+
cmd
|
36
|
+
) do |io|
|
37
|
+
# rubocop:disable AssignmentInCondition
|
38
|
+
while line = io.gets
|
39
|
+
puts line.chomp!
|
40
|
+
end
|
41
|
+
# rubocop:enable AssignmentInCondition
|
42
|
+
io.close
|
43
|
+
$CHILD_STATUS.to_i
|
44
|
+
end
|
45
|
+
logger.warn("Finished #{TasteTester::Config.command}" +
|
46
|
+
" on #{@name} with status #{status}")
|
47
|
+
if status == 0
|
48
|
+
msg = "#{TasteTester::Config.command} was successful" +
|
49
|
+
' - please log to the host and confirm all the intended' +
|
50
|
+
' changes were made'
|
51
|
+
logger.error msg.upcase
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def test
|
56
|
+
logger.warn("Taste-testing on #{@name}")
|
57
|
+
|
58
|
+
# Nuke any existing tunnels that may be there
|
59
|
+
TasteTester::Tunnel.kill(@name)
|
60
|
+
|
61
|
+
# Then setup the tunnel
|
62
|
+
@tunnel.run
|
63
|
+
@serialized_config = Base64.encode64(config).gsub(/\n/, '')
|
64
|
+
|
65
|
+
# Then setup the testing
|
66
|
+
ssh = TasteTester::SSH.new(@name)
|
67
|
+
ssh << 'logger -t taste-tester Moving server into taste-tester' +
|
68
|
+
" for #{@user}"
|
69
|
+
ssh << "touch -t #{TasteTester::Config.testing_end_time}" +
|
70
|
+
" #{TasteTester::Config.timestamp_file}"
|
71
|
+
ssh << "echo -n '#{@serialized_config}' | base64 --decode" +
|
72
|
+
' > /etc/chef/client-taste-tester.rb'
|
73
|
+
ssh << 'rm -vf /etc/chef/client.rb'
|
74
|
+
ssh << '( ln -vs /etc/chef/client-taste-tester.rb' +
|
75
|
+
' /etc/chef/client.rb; true )'
|
76
|
+
ssh.run!
|
77
|
+
|
78
|
+
# Then run any other stuff they wanted
|
79
|
+
cmds = TasteTester::Hooks.test_remote_cmds(
|
80
|
+
TasteTester::Config.dryrun,
|
81
|
+
@name
|
82
|
+
)
|
83
|
+
|
84
|
+
if cmds && cmds.any?
|
85
|
+
ssh = TasteTester::SSH.new(@name)
|
86
|
+
cmds.each { |c| ssh << c }
|
87
|
+
ssh.run!
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def untest
|
92
|
+
logger.warn("Removing #{@name} from taste-tester")
|
93
|
+
ssh = TasteTester::SSH.new(@name)
|
94
|
+
TasteTester::Tunnel.kill(@name)
|
95
|
+
ssh << 'rm -vf /etc/chef/client.rb'
|
96
|
+
ssh << 'rm -vf /etc/chef/client-taste-tester.rb'
|
97
|
+
ssh << 'ln -vs /etc/chef/client-prod.rb /etc/chef/client.rb'
|
98
|
+
ssh << 'rm -vf /etc/chef/client.pem'
|
99
|
+
ssh << 'ln -vs /etc/chef/client-prod.pem /etc/chef/client.pem'
|
100
|
+
ssh << "rm -vf #{TasteTester::Config.timestamp_file}"
|
101
|
+
ssh << 'logger -t taste-tester Returning server to production'
|
102
|
+
ssh.run!
|
103
|
+
end
|
104
|
+
|
105
|
+
def who_is_testing
|
106
|
+
ssh = TasteTester::SSH.new(@name)
|
107
|
+
ssh << 'grep "^# TasteTester by" /etc/chef/client.rb'
|
108
|
+
output = ssh.run
|
109
|
+
if output.first == 0
|
110
|
+
user = output.last.match(/# TasteTester by (.*)$/)
|
111
|
+
if user
|
112
|
+
return user[1]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Legacy FB stuff, remove after migration. Safe for everyone else.
|
117
|
+
ssh = TasteTester::SSH.new(@name)
|
118
|
+
ssh << 'file /etc/chef/client.rb'
|
119
|
+
output = ssh.run
|
120
|
+
if output.first == 0
|
121
|
+
user = output.last.match(/client-(.*)-(taste-tester|test).rb/)
|
122
|
+
if user
|
123
|
+
return user[1]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def in_test?
|
131
|
+
ssh = TasteTester::SSH.new(@name)
|
132
|
+
ssh << "test -f #{TasteTester::Config.timestamp_file}"
|
133
|
+
if ssh.run.first == 0 && who_is_testing && who_is_testing != ENV['USER']
|
134
|
+
true
|
135
|
+
else
|
136
|
+
false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def keeptesting
|
141
|
+
logger.warn("Renewing taste-tester on #{@name} until" +
|
142
|
+
" #{TasteTester::Config.testing_end_time}")
|
143
|
+
TasteTester::Tunnel.kill(@name)
|
144
|
+
@tunnel = TasteTester::Tunnel.new(@name, @server)
|
145
|
+
@tunnel.run
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def config
|
151
|
+
if TasteTester::Config.use_ssh_tunnels
|
152
|
+
url = "http://localhost:#{@tunnel.port}"
|
153
|
+
else
|
154
|
+
url = "http://#{@server.host}:#{TasteTester::State.port}"
|
155
|
+
end
|
156
|
+
ttconfig = <<-eos
|
157
|
+
# TasteTester by #{@user}
|
158
|
+
# Prevent people from screwing up their permissions
|
159
|
+
if Process.euid != 0
|
160
|
+
puts 'Please run chef as root!'
|
161
|
+
Process.exit!
|
162
|
+
end
|
163
|
+
|
164
|
+
log_level :info
|
165
|
+
log_location STDOUT
|
166
|
+
chef_server_url '#{url}'
|
167
|
+
Ohai::Config[:plugin_path] << '/etc/chef/ohai_plugins'
|
168
|
+
|
169
|
+
eos
|
170
|
+
|
171
|
+
extra = TasteTester::Hooks.test_remote_client_rb_extra_code(@name)
|
172
|
+
if extra
|
173
|
+
ttconfig += <<-eos
|
174
|
+
# Begin user-hook specified code
|
175
|
+
#{extra}
|
176
|
+
# End user-hook secified code
|
177
|
+
|
178
|
+
eos
|
179
|
+
end
|
180
|
+
|
181
|
+
ttconfig += <<-eos
|
182
|
+
puts 'INFO: Running on #{@name} in taste-tester by #{@user}'
|
183
|
+
eos
|
184
|
+
return ttconfig
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
# rubocop:disable ClassVars, UnusedMethodArgument, UnusedBlockArgument
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module TasteTester
|
6
|
+
# Logging wrapper
|
7
|
+
module Logging
|
8
|
+
@@use_log_formatter = false
|
9
|
+
@@level = Logger::WARN
|
10
|
+
@@formatter_proc = nil
|
11
|
+
|
12
|
+
def logger
|
13
|
+
logger = Logging.logger
|
14
|
+
logger.formatter = formatter
|
15
|
+
logger.level = @@level
|
16
|
+
logger
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.logger
|
20
|
+
@logger ||= Logger.new(STDOUT)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.formatterproc=(p)
|
24
|
+
@@formatter_proc = p
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.use_log_formatter=(use_log_formatter)
|
28
|
+
@@use_log_formatter = use_log_formatter
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.verbosity=(level)
|
32
|
+
@@level = level
|
33
|
+
end
|
34
|
+
|
35
|
+
def formatter
|
36
|
+
return @@formatter_proc if @@formatter_proc
|
37
|
+
if @@use_log_formatter
|
38
|
+
proc do |severity, datetime, progname, msg|
|
39
|
+
if severity == 'ERROR'
|
40
|
+
msg = msg.red
|
41
|
+
end
|
42
|
+
"[#{datetime.strftime('%Y-%m-%dT%H:%M:%S%:z')}] #{severity}: #{msg}\n"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
proc do |severity, datetime, progname, msg|
|
46
|
+
msg.to_s.prepend("#{severity}: ") unless severity == 'WARN'
|
47
|
+
if severity == 'ERROR'
|
48
|
+
msg = msg.to_s.red
|
49
|
+
end
|
50
|
+
"#{msg}\n"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'socket'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
require 'between_meals/util'
|
8
|
+
require 'taste_tester/config'
|
9
|
+
require 'taste_tester/state'
|
10
|
+
|
11
|
+
module TasteTester
|
12
|
+
# Stateless chef-zero server management
|
13
|
+
class Server
|
14
|
+
include TasteTester::Config
|
15
|
+
include TasteTester::Logging
|
16
|
+
extend ::BetweenMeals::Util
|
17
|
+
|
18
|
+
attr_accessor :user, :host
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@state = TasteTester::State.new
|
22
|
+
@ref_file = TasteTester::Config.ref_file
|
23
|
+
ref_dir = File.dirname(File.expand_path(@ref_file))
|
24
|
+
@zero_path = TasteTester::Config.chef_zero_path
|
25
|
+
unless File.directory?(ref_dir)
|
26
|
+
begin
|
27
|
+
FileUtils.mkpath(ref_dir)
|
28
|
+
rescue => e
|
29
|
+
logger.warn("Chef temp dir #{ref_dir} missing and can't be created")
|
30
|
+
logger.warn(e)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@user = ENV['USER']
|
35
|
+
|
36
|
+
# If we are using SSH tunneling listen on localhost, otherwise listen
|
37
|
+
# on all addresses - both v4 and v6. Note that on localhost, ::1 is
|
38
|
+
# v6-only, so we default to 127.0.0.1 instead.
|
39
|
+
@addr = TasteTester::Config.use_ssh_tunnels ? '127.0.0.1' : '::'
|
40
|
+
@host = 'localhost'
|
41
|
+
end
|
42
|
+
|
43
|
+
def start
|
44
|
+
return if TasteTester::Server.running?
|
45
|
+
@state.wipe
|
46
|
+
logger.warn('Starting taste-tester server')
|
47
|
+
write_config
|
48
|
+
start_chef_zero
|
49
|
+
end
|
50
|
+
|
51
|
+
def stop
|
52
|
+
logger.warn('Stopping taste-tester server')
|
53
|
+
stop_chef_zero
|
54
|
+
end
|
55
|
+
|
56
|
+
def restart
|
57
|
+
logger.warn('Restarting taste-tester server')
|
58
|
+
if TasteTester::Server.running?
|
59
|
+
stop_chef_zero
|
60
|
+
end
|
61
|
+
write_config
|
62
|
+
start_chef_zero
|
63
|
+
end
|
64
|
+
|
65
|
+
def port
|
66
|
+
@state.port
|
67
|
+
end
|
68
|
+
|
69
|
+
def port=(port)
|
70
|
+
@state.port = port
|
71
|
+
end
|
72
|
+
|
73
|
+
def latest_uploaded_ref
|
74
|
+
@state.ref
|
75
|
+
end
|
76
|
+
|
77
|
+
def latest_uploaded_ref=(ref)
|
78
|
+
@state.ref = ref
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.running?
|
82
|
+
if TasteTester::State.port
|
83
|
+
return port_open?(TasteTester::State.port)
|
84
|
+
end
|
85
|
+
false
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def write_config
|
91
|
+
knife = BetweenMeals::Knife.new(
|
92
|
+
:logger => logger,
|
93
|
+
:user => @user,
|
94
|
+
:host => @host,
|
95
|
+
:port => port,
|
96
|
+
:role_dir => TasteTester::Config.roles,
|
97
|
+
:cookbook_dirs => TasteTester::Config.cookbooks,
|
98
|
+
:checksum_dir => TasteTester::Config.checksum_dir,
|
99
|
+
)
|
100
|
+
knife.write_user_config
|
101
|
+
end
|
102
|
+
|
103
|
+
def start_chef_zero
|
104
|
+
@state.wipe
|
105
|
+
@state.port = TasteTester::Config.chef_port
|
106
|
+
logger.info("Starting chef-zero of port #{@state.port}")
|
107
|
+
Mixlib::ShellOut.new(
|
108
|
+
"/opt/chef/embedded/bin/chef-zero --host #{@addr}" +
|
109
|
+
" --port #{@state.port} -d"
|
110
|
+
).run_command.error!
|
111
|
+
end
|
112
|
+
|
113
|
+
def stop_chef_zero
|
114
|
+
@state.wipe
|
115
|
+
logger.info('Killing your chef-zero instances')
|
116
|
+
s = Mixlib::ShellOut.new("pkill -9 -u #{ENV['USER']} -f bin/chef-zero")
|
117
|
+
s.run_command
|
118
|
+
# You have to give it a moment to stop or the stat fails
|
119
|
+
sleep(1)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
|
3
|
+
module TasteTester
|
4
|
+
# Thin ssh wrapper
|
5
|
+
class SSH
|
6
|
+
include TasteTester::Logging
|
7
|
+
include BetweenMeals::Util
|
8
|
+
|
9
|
+
attr_reader :output
|
10
|
+
|
11
|
+
def initialize(host, timeout = 5, tunnel = false)
|
12
|
+
@host = host
|
13
|
+
@timeout = timeout
|
14
|
+
@tunnel = tunnel
|
15
|
+
@cmds = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(string)
|
19
|
+
@cmds << string
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :<<, :add
|
23
|
+
|
24
|
+
def run
|
25
|
+
@status, @output = exec(cmd, logger)
|
26
|
+
end
|
27
|
+
|
28
|
+
def run!
|
29
|
+
@status, @output = exec!(cmd, logger)
|
30
|
+
rescue => e
|
31
|
+
# rubocop:disable LineLength
|
32
|
+
error = <<-MSG
|
33
|
+
SSH returned error while connecting to #{TasteTester::Config.user}@#{@host}
|
34
|
+
The host might be broken or your SSH access is not working properly
|
35
|
+
Try doing 'ssh -v #{TasteTester::Config.user}@#{@host}' and come back once that works
|
36
|
+
MSG
|
37
|
+
# rubocop:enable LineLength
|
38
|
+
error.lines.each { |x| logger.error x.strip }
|
39
|
+
logger.error(e.message)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def cmd
|
45
|
+
@cmds.each do |cmd|
|
46
|
+
logger.info("Will run: '#{cmd}' on #{@host}")
|
47
|
+
end
|
48
|
+
cmds = @cmds.join(' && ')
|
49
|
+
cmd = "ssh -T -o BatchMode=yes -o ConnectTimeout=#{@timeout} "
|
50
|
+
cmd += "#{TasteTester::Config.user}@#{@host} "
|
51
|
+
if TasteTester::Config.user != 'root'
|
52
|
+
cc = Base64.encode64(cmds).gsub(/\n/, '')
|
53
|
+
cmd += "\"echo '#{cc}' | base64 --decode | sudo bash -x\""
|
54
|
+
else
|
55
|
+
cmd += "\'#{cmds}\'"
|
56
|
+
end
|
57
|
+
cmd
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'socket'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
require 'between_meals/util'
|
8
|
+
require 'taste_tester/config'
|
9
|
+
|
10
|
+
module TasteTester
|
11
|
+
# State of taste-tester processes
|
12
|
+
class State
|
13
|
+
include TasteTester::Config
|
14
|
+
include TasteTester::Logging
|
15
|
+
include ::BetweenMeals::Util
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
ref_dir = File.dirname(File.expand_path(
|
19
|
+
TasteTester::Config.ref_file
|
20
|
+
))
|
21
|
+
unless File.directory?(ref_dir)
|
22
|
+
begin
|
23
|
+
FileUtils.mkpath(ref_dir)
|
24
|
+
rescue => e
|
25
|
+
logger.error("Chef temp dir #{ref_dir} missing and can't be created")
|
26
|
+
logger.error(e)
|
27
|
+
exit(1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def port
|
33
|
+
TasteTester::State.read(:port)
|
34
|
+
end
|
35
|
+
|
36
|
+
def port=(port)
|
37
|
+
write(:port, port)
|
38
|
+
end
|
39
|
+
|
40
|
+
def ref
|
41
|
+
TasteTester::State.read(:ref)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ref=(ref)
|
45
|
+
write(:ref, ref)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.port
|
49
|
+
TasteTester::State.read(:port)
|
50
|
+
end
|
51
|
+
|
52
|
+
def wipe
|
53
|
+
if TasteTester::Config.ref_file &&
|
54
|
+
File.exists?(TasteTester::Config.ref_file)
|
55
|
+
File.delete(TasteTester::Config.ref_file)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def write(key, value)
|
62
|
+
begin
|
63
|
+
state = JSON.parse(File.read(TasteTester::Config.ref_file))
|
64
|
+
rescue
|
65
|
+
state = {}
|
66
|
+
end
|
67
|
+
state[key.to_s] = value
|
68
|
+
ff = File.open(
|
69
|
+
TasteTester::Config.ref_file,
|
70
|
+
'w'
|
71
|
+
)
|
72
|
+
ff.write(state.to_json)
|
73
|
+
ff.close
|
74
|
+
rescue => e
|
75
|
+
logger.error('Unable to write the reffile')
|
76
|
+
logger.debug(e)
|
77
|
+
exit 0
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.read(key)
|
81
|
+
JSON.parse(File.read(TasteTester::Config.ref_file))[key.to_s]
|
82
|
+
rescue => e
|
83
|
+
logger.debug(e)
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
|
3
|
+
module TasteTester
|
4
|
+
# Thin ssh tunnel wrapper
|
5
|
+
class Tunnel
|
6
|
+
include TasteTester::Logging
|
7
|
+
include BetweenMeals::Util
|
8
|
+
|
9
|
+
attr_reader :port
|
10
|
+
|
11
|
+
def initialize(host, server, timeout = 5)
|
12
|
+
@host = host
|
13
|
+
@server = server
|
14
|
+
@timeout = timeout
|
15
|
+
if TasteTester::Config.testing_until
|
16
|
+
@delta_secs = TasteTester::Config.testing_until.strftime('%s').to_i -
|
17
|
+
Time.now.strftime('%s').to_i
|
18
|
+
else
|
19
|
+
@delta_secs = TasteTester::Config.testing_time
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
@port = TasteTester::Config.tunnel_port
|
25
|
+
logger.info("Setting up tunnel on port #{@port}")
|
26
|
+
@status, @output = exec!(cmd, logger)
|
27
|
+
rescue
|
28
|
+
logger.error 'Failed bringing up ssh tunnel'
|
29
|
+
exit(1)
|
30
|
+
end
|
31
|
+
|
32
|
+
def cmd
|
33
|
+
cmds = "echo \\\$\\\$ > #{TasteTester::Config.timestamp_file} &&" +
|
34
|
+
" touch -t #{TasteTester::Config.testing_end_time}" +
|
35
|
+
" #{TasteTester::Config.timestamp_file} && sleep #{@delta_secs}"
|
36
|
+
cmd = "ssh -T -o BatchMode=yes -o ConnectTimeout=#{@timeout} " +
|
37
|
+
"-o ExitOnForwardFailure=yes -f -R #{@port}:localhost:" +
|
38
|
+
"#{@server.port} root@#{@host} \"#{cmds}\""
|
39
|
+
cmd
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.kill(name)
|
43
|
+
ssh = TasteTester::SSH.new(name)
|
44
|
+
# Since commands are &&'d together, and we're using &&, we need to
|
45
|
+
# surround this in paryns, and make sure as a whole it evaluates
|
46
|
+
# to true so it doesn't mess up other things... even though this is
|
47
|
+
# the only thing we're currently executing in this SSH.
|
48
|
+
ssh << "( [ -s #{TasteTester::Config.timestamp_file} ]" +
|
49
|
+
" && kill -- -\$(cat #{TasteTester::Config.timestamp_file}); true )"
|
50
|
+
ssh.run!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|