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