taste_tester 0.0.12 → 0.0.17

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.
@@ -22,8 +22,6 @@ module TasteTester
22
22
  include TasteTester::Logging
23
23
  include BetweenMeals::Util
24
24
 
25
- attr_reader :output
26
-
27
25
  def initialize
28
26
  @host = 'localhost'
29
27
  @cmds = []
@@ -36,14 +34,18 @@ module TasteTester
36
34
  alias << add
37
35
 
38
36
  def run
39
- @status, @output = exec(cmd, logger)
37
+ exec(cmd, logger)
40
38
  end
41
39
 
42
40
  def run!
43
- @status, @output = exec!(cmd, logger)
44
- rescue => e
41
+ exec!(cmd, logger)
42
+ rescue StandardError => e
45
43
  logger.error(e.message)
46
- raise TasteTester::Exceptions::LocalLinkError
44
+ error!
45
+ end
46
+
47
+ def error!
48
+ fail TasteTester::Exceptions::LocalLinkError
47
49
  end
48
50
 
49
51
  private
@@ -14,7 +14,7 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- # rubocop:disable ClassVars, UnusedMethodArgument, UnusedBlockArgument
17
+ # rubocop:disable ClassVars
18
18
  require 'logger'
19
19
 
20
20
  module TasteTester
@@ -35,8 +35,8 @@ module TasteTester
35
35
  @logger ||= Logger.new(STDOUT)
36
36
  end
37
37
 
38
- def self.formatterproc=(p)
39
- @@formatter_proc = p
38
+ def self.formatterproc=(process)
39
+ @@formatter_proc = process
40
40
  end
41
41
 
42
42
  def self.use_log_formatter=(use_log_formatter)
@@ -49,16 +49,17 @@ module TasteTester
49
49
 
50
50
  def formatter
51
51
  return @@formatter_proc if @@formatter_proc
52
+
52
53
  if @@use_log_formatter
53
- proc do |severity, datetime, progname, msg|
54
+ proc do |severity, datetime, _progname, msg|
54
55
  if severity == 'ERROR'
55
56
  msg = msg.red
56
57
  end
57
58
  "[#{datetime.strftime('%Y-%m-%dT%H:%M:%S%:z')}] #{severity}: #{msg}\n"
58
59
  end
59
60
  else
60
- proc do |severity, datetime, progname, msg|
61
- msg.to_s.prepend("#{severity}: ") unless severity == 'WARN'
61
+ proc do |severity, _datetime, _progname, msg|
62
+ msg.dup.to_s.prepend("#{severity}: ") unless severity == 'WARN'
62
63
  if severity == 'ERROR'
63
64
  msg = msg.to_s.red
64
65
  end
@@ -68,3 +69,4 @@ module TasteTester
68
69
  end
69
70
  end
70
71
  end
72
+ # rubocop:enable ClassVars
@@ -0,0 +1,69 @@
1
+ # vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
2
+
3
+ # Copyright 2013-present Facebook
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'taste_tester/exceptions'
18
+
19
+ module TasteTester
20
+ # Wrapper for running commands on local system
21
+ class NoOp
22
+ include TasteTester::Logging
23
+ include BetweenMeals::Util
24
+
25
+ def initialize
26
+ print_noop_warning
27
+ @host = 'localhost'
28
+ @user = ENV['USER']
29
+ @cmds = []
30
+ end
31
+
32
+ def print_noop_warning
33
+ # This needs to be a Class var as this class is initialized more
34
+ # than once in a given tt run and we only want to warn once.
35
+ # rubocop:disable Style/ClassVars
36
+ @@printedwarning ||= logger.warn(
37
+ 'No-op plugin active, no remote commands will be run!',
38
+ )
39
+ # rubocop:enable Style/ClassVars
40
+ end
41
+
42
+ def add(string)
43
+ @cmds << string
44
+ end
45
+
46
+ alias << add
47
+
48
+ def run
49
+ run!
50
+ end
51
+
52
+ def run!
53
+ cmd
54
+ [0, "# TasteTester by #{@user}"]
55
+ end
56
+
57
+ def error!
58
+ # never fails, but interface requires a definition
59
+ end
60
+
61
+ private
62
+
63
+ def cmd
64
+ @cmds.each do |cmd|
65
+ logger.info("No-op, faking run of: '#{cmd}' on #{@host}")
66
+ end
67
+ end
68
+ end
69
+ end
@@ -16,7 +16,6 @@
16
16
 
17
17
  require 'fileutils'
18
18
  require 'socket'
19
- require 'timeout'
20
19
 
21
20
  require 'between_meals/util'
22
21
  require 'taste_tester/config'
@@ -30,22 +29,27 @@ module TasteTester
30
29
  include TasteTester::Logging
31
30
  extend ::BetweenMeals::Util
32
31
 
33
- attr_accessor :user, :host
32
+ attr_accessor :user, :host, :bundle_dir
34
33
 
35
34
  def initialize
36
35
  @state = TasteTester::State.new
37
36
  @ref_file = TasteTester::Config.ref_file
38
37
  ref_dir = File.dirname(File.expand_path(@ref_file))
39
- @log_file = "#{ref_dir}/chef-zero.log"
38
+ @log_file = File.join(ref_dir, 'chef-zero.log')
39
+ @fsroot = File.join(ref_dir, 'root')
40
40
  @zero_path = TasteTester::Config.chef_zero_path
41
41
  unless File.directory?(ref_dir)
42
42
  begin
43
43
  FileUtils.mkpath(ref_dir)
44
- rescue => e
44
+ rescue StandardError => e
45
45
  logger.warn("Chef temp dir #{ref_dir} missing and can't be created")
46
46
  logger.warn(e)
47
47
  end
48
48
  end
49
+ if TasteTester::Config.bundle
50
+ @bundle_dir = File.join(@fsroot, 'organizations/chef/file_store')
51
+ FileUtils.mkpath(@bundle_dir)
52
+ end
49
53
 
50
54
  @user = ENV['USER']
51
55
 
@@ -53,19 +57,20 @@ module TasteTester
53
57
  # determines if we listen only on localhost or not
54
58
  @need_restart = @state.ssl != TasteTester::Config.use_ssl ||
55
59
  @state.logging != TasteTester::Config.chef_zero_logging ||
56
- @state.ssh != TasteTester::Config.use_ssh_tunnels
60
+ @state.ssh != TasteTester::Config.use_ssh_tunnels ||
61
+ @state.bundle != TasteTester::Config.bundle
57
62
 
58
63
  # If we are using SSH tunneling listen on localhost, otherwise listen
59
64
  # on all addresses - both v4 and v6. Note that on localhost, ::1 is
60
65
  # v6-only, so we default to 127.0.0.1 instead.
61
66
  if TasteTester::Config.use_ssh_tunnels
62
- @addr = '127.0.0.1'
67
+ @addrs = ['127.0.0.1']
63
68
  @host = 'localhost'
64
69
  else
65
- @addr = '::'
70
+ @addrs = ['::', '0.0.0.0']
66
71
  begin
67
72
  @host = TasteTester::Config.my_hostname || Socket.gethostname
68
- rescue
73
+ rescue StandardError
69
74
  logger.error('Unable to find fqdn')
70
75
  exit 1
71
76
  end
@@ -121,10 +126,20 @@ module TasteTester
121
126
  @state.ref = ref
122
127
  end
123
128
 
129
+ def last_upload_time
130
+ @state.last_upload_time
131
+ end
132
+
133
+ def last_upload_time=(time)
134
+ @state.last_upload_time = time
135
+ end
136
+
124
137
  def self.running?
125
138
  if TasteTester::State.port
126
- return port_open?(TasteTester::State.port)
139
+ return chef_zero_running?(TasteTester::State.port,
140
+ TasteTester::Config.use_ssl)
127
141
  end
142
+
128
143
  false
129
144
  end
130
145
 
@@ -140,29 +155,33 @@ module TasteTester
140
155
  :role_dir => TasteTester::Config.roles,
141
156
  :cookbook_dirs => TasteTester::Config.cookbooks,
142
157
  :checksum_dir => TasteTester::Config.checksum_dir,
158
+ :config => TasteTester::Config.knife_config,
143
159
  )
144
160
  knife.write_user_config
145
161
  end
146
162
 
147
163
  def start_chef_zero
148
- File.unlink(@log_file) if File.exists?(@log_file)
164
+ File.unlink(@log_file) if File.exist?(@log_file)
149
165
  @state.update({
150
166
  :port => TasteTester::Config.chef_port,
151
167
  :ssl => TasteTester::Config.use_ssl,
152
168
  :ssh => TasteTester::Config.use_ssh_tunnels,
153
169
  :logging => TasteTester::Config.chef_zero_logging,
170
+ :bundle => TasteTester::Config.bundle,
154
171
  })
155
172
  logger.info("Starting chef-zero of port #{@state.port}")
156
173
  if windows?
157
174
  extend ::TasteTester::Windows
158
175
  start_win_chef_zero_server
159
176
  else
160
- cmd = "#{chef_zero_path} --host #{@addr} --port #{@state.port} -d"
177
+ hostarg = @addrs.map { |addr| "--host #{addr}" }.join(' ')
178
+ cmd = +"#{chef_zero_path} #{hostarg} --port #{@state.port} -d"
161
179
  if TasteTester::Config.chef_zero_logging
162
180
  cmd << " --log-file #{@log_file}" +
163
181
  ' --log-level debug'
164
182
  end
165
183
  cmd << ' --ssl' if TasteTester::Config.use_ssl
184
+ cmd << " --file-store #{@fsroot}" if TasteTester::Config.bundle
166
185
  Mixlib::ShellOut.new(cmd).run_command.error!
167
186
  end
168
187
  end
@@ -15,18 +15,17 @@
15
15
  # limitations under the License.
16
16
 
17
17
  require 'taste_tester/exceptions'
18
+ require 'taste_tester/ssh_util'
18
19
 
19
20
  module TasteTester
20
21
  # Thin ssh wrapper
21
22
  class SSH
22
23
  include TasteTester::Logging
23
24
  include BetweenMeals::Util
25
+ include TasteTester::SSH::Util
24
26
 
25
- attr_reader :output
26
-
27
- def initialize(host, timeout = 5, tunnel = false)
27
+ def initialize(host, tunnel = false)
28
28
  @host = host
29
- @timeout = timeout
30
29
  @tunnel = tunnel
31
30
  @cmds = []
32
31
  end
@@ -37,25 +36,15 @@ module TasteTester
37
36
 
38
37
  alias << add
39
38
 
40
- def run
41
- @status, @output = exec(cmd, logger)
39
+ def run(stream = nil)
40
+ exec(cmd, logger, stream)
42
41
  end
43
42
 
44
- def run!
45
- @status, @output = exec!(cmd, logger)
46
- rescue => e
47
- # rubocop:disable LineLength
48
- error = <<-MSG
49
- SSH returned error while connecting to #{TasteTester::Config.user}@#{@host}
50
- The host might be broken or your SSH access is not working properly
51
- Try doing
52
- #{TasteTester::Config.ssh_command} -v #{TasteTester::Config.user}@#{@host}
53
- and come back once that works
54
- MSG
55
- # rubocop:enable LineLength
56
- error.lines.each { |x| logger.error x.strip }
43
+ def run!(stream = nil)
44
+ exec!(cmd, logger, stream)
45
+ rescue StandardError => e
57
46
  logger.error(e.message)
58
- raise TasteTester::Exceptions::SshError
47
+ error!
59
48
  end
60
49
 
61
50
  private
@@ -64,17 +53,7 @@ MSG
64
53
  @cmds.each do |cmd|
65
54
  logger.info("Will run: '#{cmd}' on #{@host}")
66
55
  end
67
- cmds = @cmds.join(' && ')
68
- cmd = "#{TasteTester::Config.ssh_command} " +
69
- "-T -o BatchMode=yes -o ConnectTimeout=#{@timeout} " +
70
- "#{TasteTester::Config.user}@#{@host} "
71
- if TasteTester::Config.user != 'root'
72
- cc = Base64.encode64(cmds).delete("\n")
73
- cmd += "\"echo '#{cc}' | base64 --decode | sudo bash -x\""
74
- else
75
- cmd += "\'#{cmds}\'"
76
- end
77
- cmd
56
+ build_ssh_cmd(ssh_base_cmd, @cmds)
78
57
  end
79
58
  end
80
59
  end
@@ -0,0 +1,127 @@
1
+ module TasteTester
2
+ class SSH
3
+ module Util
4
+ def ssh_base_cmd
5
+ jumps = TasteTester::Config.jumps ?
6
+ "-J #{TasteTester::Config.jumps}" : ''
7
+ "#{TasteTester::Config.ssh_command} #{jumps} -T -o BatchMode=yes " +
8
+ '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
9
+ "-o ConnectTimeout=#{TasteTester::Config.ssh_connect_timeout} " +
10
+ "#{TasteTester::Config.user}@#{@host} "
11
+ end
12
+
13
+ def error!
14
+ error = <<~ERRORMESSAGE
15
+ SSH returned error while connecting to #{TasteTester::Config.user}@#{@host}
16
+ The host might be broken or your SSH access is not working properly
17
+ Try doing
18
+
19
+ #{ssh_base_cmd} -v
20
+
21
+ to see if ssh connection is good.
22
+ If ssh works, add '-v' key to taste-tester to see the list of commands it's
23
+ trying to execute, and try to run them manually on destination host
24
+ ERRORMESSAGE
25
+ logger.error(error)
26
+ fail TasteTester::Exceptions::SshError
27
+ end
28
+
29
+ def build_ssh_cmd(ssh, command_list)
30
+ if TasteTester::Config.windows_target
31
+ # Powershell has no `&&`. So originally we looked into joining the
32
+ # various commands with `; if ($LASTEXITCODE -ne 0) { exit 42 }; `
33
+ # except that it turns out lots of Powershell commands don't set
34
+ # $LASTEXITCODE and so that crashes a lot.
35
+ #
36
+ # There is an `-and`, but it only works if you group things together
37
+ # with `()`, but that loses any output.
38
+ #
39
+ # Technically in the latest preview of Powershell 7, `&&` exists, but
40
+ # we cannot rely on this.
41
+ #
42
+ # So here we are. Thanks Windows Team.
43
+ #
44
+ # Anyway, what we *really* care about is that we exit if we_testing()
45
+ # errors out, and on Windows, we can do that straight from the
46
+ # powershell we generate there (we're not forking off awk), so the
47
+ # `&&` isn't as critical. It's still a bummer that we continue on
48
+ # if one of the commands fails, but... Well, it's Windows,
49
+ # whatchyagonnado?
50
+
51
+ cmds = command_list.join(' ; ')
52
+ else
53
+ cmds = command_list.join(' && ')
54
+ end
55
+ cmd = ssh
56
+ cc = Base64.encode64(cmds).delete("\n")
57
+ if TasteTester::Config.windows_target
58
+
59
+ # This is pretty horrible, but because there's no way I can find to
60
+ # take base64 as stdin and output text, we end up having to do use
61
+ # these PS functions. But they're going to pass through *both* bash
62
+ # *and* powershell, so in order to preserve the quotes, it gets
63
+ # pretty ugly.
64
+ #
65
+ # The tldr here is that in shell you can't escape quotes you're
66
+ # using to quote something. So if you use single quotes, there's no
67
+ # way to escape a single quote inside, and same with double-quotes.
68
+ # As such we switch between quote-styles as necessary. As long as the
69
+ # strings are back-to-back, shell handles this well. To make this
70
+ # clear, imagine you want to echo this:
71
+ # '"'"
72
+ # Exactly like that. You would quote the first single quotes in double
73
+ # quotes: "'"
74
+ # Then the double quotes in single quotes: '"'
75
+ # Now repeat twice and you get: echo "'"'"'"'"'"'
76
+ # And that works reliably.
77
+ #
78
+ # We're doing the same thing here. What we want on the other side of
79
+ # the ssh is:
80
+ # [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String('...'))
81
+ #
82
+ # But for this to work right the command we pass to SSH has to be in
83
+ # single quotes too. For simplicity lets call those two functions
84
+ # above GetString() and Base64(). So we'll start with:
85
+ # ssh host 'GetString(Base64('
86
+ # We've closed that string, now we add the single quote we want there,
87
+ # as well as the stuff inside of those double quotes, so we'll add:
88
+ # '#{cc}'))
89
+ # but that must be in double quotes since we're using single quotes.
90
+ # Put that together:
91
+ # ssh host 'GetString(Base64('"'#{cc}'))"
92
+ # ^-----------------^^---------^
93
+ # string 1 string2
94
+ # No we're doing with needing single quotes inside of our string, go
95
+ # back to using single-quotes so no variables get interpolated. We now
96
+ # add: ' | powershell.exe -c -; exit $LASTEXITCODE'
97
+ # ssh host 'GetString(Base64('"'#{cc}'))"' | powershell.exe ...'
98
+ # ^-----------------^^---------^^---------------------^
99
+ #
100
+ # More than you ever wanted to know about shell. You're welcome.
101
+ #
102
+ # But now we have to put it inside of a ruby string, :)
103
+
104
+ # just for readability, put these crazy function names inside of
105
+ # variables
106
+ fun1 = '[Text.Encoding]::Utf8.GetString'
107
+ fun2 = '[Convert]::FromBase64String'
108
+ cmd += "'#{fun1}(#{fun2}('\"'#{cc}'))\"' | "
109
+ # ^----------------^ ^----------^^---
110
+ # single-q double-q single-q
111
+ # string 1 string2 string3
112
+ cmd += 'powershell.exe -c -; exit $LASTEXITCODE\''
113
+ # ----------------------------------------^
114
+ # continued string3
115
+ else
116
+ cmd += "\"echo '#{cc}' | base64 --decode"
117
+ if TasteTester::Config.user != 'root'
118
+ cmd += ' | sudo bash -x"'
119
+ else
120
+ cmd += ' | bash -x"'
121
+ end
122
+ end
123
+ cmd
124
+ end
125
+ end
126
+ end
127
+ end