taste_tester 0.0.15 → 0.0.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ba0813aa4a09d92549bb1555bef69e883a49b4ef292b9a2d7cc14dca77bce0e
4
- data.tar.gz: 4a51383665bad129bba1e5e4d2e1d9df991eb021700ce6dc9ab1098fa8089ad5
3
+ metadata.gz: 945fe28beeb0251fc2b6236c833e77b8b0f6e7b2242c9f7d4882bd4fcf270506
4
+ data.tar.gz: 1001f712736d466b1fe1db53332d7668ebebf028d62a8566c2923ee88db2f0d4
5
5
  SHA512:
6
- metadata.gz: 612005359c48c5cee20cb95e4c8880a087f1638887de099e0cd15dd99757b54a3e404643241289286c7d33ff9f29ed25dcd64d9bcefcefc2a9b36dfe97fbb6f0
7
- data.tar.gz: 99fae7d5975e0a9c05d1ca0e68c18c03a237eb4e4b17c8e870b16d71e8a434717a01d81dc0aad2b15b35ee1fbc292c3dad230c29ab92f362059f2d5285808de7
6
+ metadata.gz: ccf12a4962ae584ff10f2b0b7782a48d2e9e04f320a0b592895b3ec545a2921372ccd4e5e9b8994435791fbe281fe9d9043881b2f780f6aadd60ec64613e910f
7
+ data.tar.gz: 5b8f422febc1b1f8d2bee6a408aaff95a1dbd41011ad267c4637180a53214219fc44d725543160866ea8947bacd5eec68e002d6afc87352f7fa721fed05157ea
data/bin/taste-tester CHANGED
@@ -39,6 +39,15 @@ module TasteTester
39
39
  exit(1)
40
40
  end
41
41
 
42
+ # Do an initial read of the config file if it's in the default place, so
43
+ # that if people override chef_client_command the help message is correct.
44
+ if File.exists?(File.expand_path(TasteTester::Config.config_file))
45
+ TasteTester::Config.from_file(
46
+ File.expand_path(TasteTester::Config.config_file),
47
+ )
48
+ end
49
+
50
+ cmd = TasteTester::Config.chef_client_command
42
51
  description = <<-ENDOFDESCRIPTION
43
52
  Welcome to taste-tester!
44
53
 
@@ -48,18 +57,17 @@ TLDR; Most common usage is:
48
57
  vi cookbooks/... # Make your changes and commit locally
49
58
  taste-tester test -s [host] # Put host in test mode
50
59
  ssh root@[host] # Log on host
51
- # Run chef and watch it break
60
+ #{format('%-28s', " #{cmd}")} # Run chef and watch it break
52
61
  vi cookbooks/... # Fix your cookbooks
53
62
  taste-tester upload # Upload the diff
54
63
  ssh root@[host] # Log on host
55
- # Run chef and watch it succeed
64
+ #{format('%-28s', " #{cmd}")} # Run chef and watch it succeed
56
65
  <#{verify}>
57
66
  taste-tester untest -s [host] # Put host back in production
58
67
  # (optional - will revert itself after 1 hour)
59
68
 
60
69
  And you're done!
61
- Note: There may be site specific changes, e.g. instead of chef-client you may
62
- use a wrapper. See local documentation for details.
70
+ Note: There may be site specific testing instructions, see local documentation for details.
63
71
 
64
72
  MODES:
65
73
  test
@@ -100,6 +108,11 @@ MODES:
100
108
  status
101
109
  Print out the state of the world.
102
110
 
111
+ run
112
+ Run #{cmd} on the machine specified by '-s' over SSH and print the output.
113
+ NOTE!! This is #{'NOT'.red} a sufficient test, you must log onto the remote
114
+ machine and verify the changes you are trying to make are actually present.
115
+
103
116
  stop
104
117
  You probably don't want this. It will shutdown the chef-zero server on
105
118
  your localhost.
@@ -429,6 +442,8 @@ MODES:
429
442
  TasteTester::Commands.test
430
443
  when :untest
431
444
  TasteTester::Commands.untest
445
+ when :run
446
+ TasteTester::Commands.runchef
432
447
  when :upload
433
448
  TasteTester::Commands.upload
434
449
  when :impact
@@ -149,6 +149,19 @@ module TasteTester
149
149
  end
150
150
  end
151
151
 
152
+ def self.runchef
153
+ hosts = TasteTester::Config.servers
154
+ unless hosts
155
+ logger.warn('You must provide a hostname')
156
+ exit(1)
157
+ end
158
+ server = TasteTester::Server.new
159
+ hosts.each do |hostname|
160
+ host = TasteTester::Host.new(hostname, server)
161
+ host.runchef
162
+ end
163
+ end
164
+
152
165
  def self.keeptesting
153
166
  hosts = TasteTester::Config.servers
154
167
  unless hosts
@@ -45,6 +45,7 @@ module TasteTester
45
45
  knife_config "#{ENV['HOME']}/.chef/knife-#{ENV['USER']}-taste-tester.rb"
46
46
  checksum_dir "#{ENV['HOME']}/.chef/checksums"
47
47
  skip_repo_checks false
48
+ chef_client_command 'chef-client'
48
49
  testing_time 3600
49
50
  chef_port_range [5000, 5500]
50
51
  tunnel_port 4001
@@ -44,6 +44,24 @@ module TasteTester
44
44
  end
45
45
  end
46
46
 
47
+ def runchef
48
+ logger.warn("Running '#{TasteTester::Config.chef_client_command}' " +
49
+ "on #{@name}")
50
+ transport = get_transport
51
+ transport << TasteTester::Config.chef_client_command
52
+
53
+ io = IO.new(1)
54
+ status, = transport.run(io)
55
+ logger.warn("Finished #{TasteTester::Config.chef_client_command}" +
56
+ " on #{@name} with status #{status}")
57
+ if status.zero?
58
+ msg = "#{TasteTester::Config.chef_client_command} was successful" +
59
+ ' - please log to the host and confirm all the intended' +
60
+ ' changes were made'
61
+ logger.error msg.upcase
62
+ end
63
+ end
64
+
47
65
  def get_transport
48
66
  case TasteTester::Config.transport
49
67
  when 'locallink'
@@ -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'
@@ -34,48 +34,54 @@ module TasteTester
34
34
 
35
35
  alias << add
36
36
 
37
- def run
38
- exec(cmd, logger)
37
+ def run(stream = nil)
38
+ exec(cmd, logger, stream)
39
39
  end
40
40
 
41
- def run!
42
- exec!(cmd, logger)
41
+ def run!(stream = nil)
42
+ exec!(cmd, logger, stream)
43
43
  rescue StandardError => e
44
44
  logger.error(e.message)
45
45
  error!
46
46
  end
47
47
 
48
48
  def error!
49
- error = <<-ERRORMESSAGE
49
+ error = <<~ERRORMESSAGE
50
50
  SSH returned error while connecting to #{TasteTester::Config.user}@#{@host}
51
51
  The host might be broken or your SSH access is not working properly
52
52
  Try doing
53
- #{TasteTester::Config.ssh_command} -v #{TasteTester::Config.user}@#{@host}
53
+
54
+ #{ssh_base_cmd} -v #{TasteTester::Config.user}@#{@host}
55
+
54
56
  to see if ssh connection is good.
55
57
  If ssh works, add '-v' key to taste-tester to see the list of commands it's
56
58
  trying to execute, and try to run them manually on destination host
57
59
  ERRORMESSAGE
58
- error.lines.each { |x| logger.error x.strip }
60
+ logger.error(error)
59
61
  fail TasteTester::Exceptions::SshError
60
62
  end
61
63
 
62
64
  private
63
65
 
66
+ def ssh_base_cmd
67
+ jumps = TasteTester::Config.jumps ? "-J #{TasteTester::Config.jumps}" : ''
68
+ "#{TasteTester::Config.ssh_command} #{jumps}"
69
+ end
70
+
64
71
  def cmd
65
72
  @cmds.each do |cmd|
66
73
  logger.info("Will run: '#{cmd}' on #{@host}")
67
74
  end
68
75
  cmds = @cmds.join(' && ')
69
- jumps = TasteTester::Config.jumps ? "-J #{TasteTester::Config.jumps}" : ''
70
- cmd = "#{TasteTester::Config.ssh_command} #{jumps} " +
71
- '-T -o BatchMode=yes ' +
76
+ cmd = "#{ssh_base_cmd} -T -o BatchMode=yes " +
72
77
  "-o ConnectTimeout=#{TasteTester::Config.ssh_connect_timeout} " +
73
78
  "#{TasteTester::Config.user}@#{@host} "
79
+ cc = Base64.encode64(cmds).delete("\n")
80
+ cmd += "\"echo '#{cc}' | base64 --decode"
74
81
  if TasteTester::Config.user != 'root'
75
- cc = Base64.encode64(cmds).delete("\n")
76
- cmd += "\"echo '#{cc}' | base64 --decode | sudo bash -x\""
82
+ cmd += ' | sudo bash -x"'
77
83
  else
78
- cmd += "\'#{cmds}\'"
84
+ cmd += ' | bash -x"'
79
85
  end
80
86
  cmd
81
87
  end
@@ -25,12 +25,6 @@ module TasteTester
25
25
  def initialize(host, server)
26
26
  @host = host
27
27
  @server = server
28
- if TasteTester::Config.testing_until
29
- @delta_secs = TasteTester::Config.testing_until.strftime('%s').to_i -
30
- Time.now.strftime('%s').to_i
31
- else
32
- @delta_secs = TasteTester::Config.testing_time
33
- end
34
28
  end
35
29
 
36
30
  def run
@@ -43,13 +37,99 @@ module TasteTester
43
37
  end
44
38
 
45
39
  def cmd
46
- @max_ping = @delta_secs / 10
47
- pid = '$$'
48
40
  @ts = TasteTester::Config.testing_end_time.strftime('%y%m%d%H%M.%S')
49
- cmds = "ps -o pgid= -p $(ps -o ppid= -p #{pid}) | sed \"s| ||g\" " +
50
- " > #{TasteTester::Config.timestamp_file} &&" +
51
- " touch -t #{@ts} #{TasteTester::Config.timestamp_file} &&" +
52
- " sleep #{@delta_secs}"
41
+
42
+ # Tie the life of our SSH tunnel with the life of timestamp file.
43
+ # taste-testing can be renewed, so we'll wait until:
44
+ # 1. the timestamp file is entirely gone
45
+ # 2. our parent sshd process dies
46
+ # 3. new taste-tester instance is running (file contains different PGID)
47
+ cmds = <<~EOS
48
+ log() {
49
+ [ -e /usr/bin/logger ] || return
50
+ logger -t taste-tester "$*"
51
+ }
52
+ # sets $current_pgid
53
+ # This is important, this should just be called ald let it set the
54
+ # variable. Do NOT call in a subshell like foo=$(get_current_pgid)
55
+ # as then you end up even further down the list of children
56
+ get_current_pgid() {
57
+
58
+ # if TT user is non-root, then it breaks down like this:
59
+ # we are 'bash'
60
+ # our parent is 'sudo'
61
+ # our parent's parent is 'bash "echo ..." | sudo bash -x'
62
+ # our parent's parent's parent is ssh
63
+ # - we want the progress-group ID of *that*
64
+ #
65
+ # EXCEPT... sometimes sudo forks itself one more time so it's
66
+ # we are 'bash'
67
+ # our parent is 'sudo'
68
+ # our parent's parent 'sudo'
69
+ # our parent's parent's parent is 'bash "echo ..." | sudo bash -x'
70
+ # our parent's parent's parent's parent is ssh
71
+ # - we want the progress-group ID of *that*
72
+ #
73
+ # BUT if the TT user is root, no sudo at all...
74
+ # we are 'bash'
75
+ # our parent is 'bash "echo ..." | bash -c
76
+ # our parent's parent is ssh
77
+ # - we want the progress-group ID of *that*
78
+ #
79
+ # We can make all sorts of assumptions, but the most reliable way
80
+ # to do this that's always correct is to is simply to walk parents until
81
+ # we hit something with SSH in the name. Start with PPID and go from
82
+ # there.
83
+ #
84
+ # There's a few commented out 'log's here that are too verbose
85
+ # for operation (since this function runs every minute) but are useful
86
+ # for debugging.
87
+
88
+ relevant_pid=''
89
+ current_pid=$PPID
90
+ while true; do
91
+ name=$(ps -o command= -p $current_pid)
92
+ if [[ "$name" =~ sshd ]]; then
93
+ # Uncomment the following for debugging...
94
+ #log "$current_pid is ssh, that's us!"
95
+ relevant_pid=$current_pid
96
+ break
97
+ fi
98
+ # Uncomment the following for debugging...
99
+ #log "$current_pid is $name, finding parent..."
100
+ current_pid=$(ps -o ppid= -p $current_pid)
101
+ done
102
+ if [ -z "$relevant_pid" ];then
103
+ log "Cannot determine relevant PGID"
104
+ exit 42
105
+ fi
106
+ current_pgid="$(ps -o pgid= -p $relevant_pid | sed "s| ||g")"
107
+ # Uncomment the following for debugging...
108
+ #log "PGID of ssh ($relevant_pid) is $current_pgid"
109
+ }
110
+ get_current_pgid
111
+ SSH_PGID=$current_pgid
112
+
113
+ echo $SSH_PGID > #{TasteTester::Config.timestamp_file} && \
114
+ touch -t #{@ts} #{TasteTester::Config.timestamp_file} && \
115
+ while true; do
116
+ if ! [ -f "#{TasteTester::Config.timestamp_file}" ]; then
117
+ log "Ending tunnel: timestamp file disappeared"
118
+ break
119
+ fi
120
+ current_pid="$(cat #{TasteTester::Config.timestamp_file})"
121
+ if ! [ "$current_pid" = "$SSH_PGID" ]; then
122
+ log "Ending tunnel: timestamp PGID changed"
123
+ break
124
+ fi
125
+ get_current_pgid
126
+ if ! [ "$current_pgid" = "$SSH_PGID" ]; then
127
+ log "Ending tunnel: timestamp PGID isn't ours"
128
+ break
129
+ fi
130
+ sleep 60
131
+ done
132
+ EOS
53
133
  # As great as it would be to have ExitOnForwardFailure=yes,
54
134
  # we had multiple cases of tunnels dying
55
135
  # if -f and ExitOnForwardFailure are used together.
@@ -61,14 +141,17 @@ module TasteTester
61
141
  "-o ConnectTimeout=#{TasteTester::Config.ssh_connect_timeout} " +
62
142
  '-T -o BatchMode=yes ' +
63
143
  '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
64
- "-o ServerAliveInterval=10 -o ServerAliveCountMax=#{@max_ping} " +
144
+ '-o ServerAliveInterval=10 -o ServerAliveCountMax=6 ' +
65
145
  "-f -R #{@port}:localhost:#{@server.port} "
146
+ cc = Base64.encode64(cmds).delete("\n")
147
+
148
+ # always base64 encode the command so we don't have to deal with quoting
149
+ cmd += "#{TasteTester::Config.user}@#{@host} \"echo '#{cc}' | base64" +
150
+ ' --decode'
66
151
  if TasteTester::Config.user != 'root'
67
- cc = Base64.encode64(cmds).delete("\n")
68
- cmd += "#{TasteTester::Config.user}@#{@host} \"echo '#{cc}' | base64" +
69
- ' --decode | sudo bash -x"'
152
+ cmd += ' | sudo bash -x"'
70
153
  else
71
- cmd += "root@#{@host} '#{cmds}'"
154
+ cmd += ' | bash -x"'
72
155
  end
73
156
  cmd
74
157
  end
@@ -79,11 +162,8 @@ module TasteTester
79
162
  # surround this in paryns, and make sure as a whole it evaluates
80
163
  # to true so it doesn't mess up other things... even though this is
81
164
  # the only thing we're currently executing in this SSH.
82
- if TasteTester::Config.user != 'root'
83
- sudo = 'sudo '
84
- end
85
165
  cmd = "( [ -s #{TasteTester::Config.timestamp_file} ]" +
86
- " && #{sudo}kill -9 -- " +
166
+ ' && kill -9 -- ' +
87
167
  "-\$(cat #{TasteTester::Config.timestamp_file}) 2>/dev/null; " +
88
168
  ' true )'
89
169
  ssh << cmd
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taste_tester
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.15
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phil Dibowitz
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-09-19 00:00:00.000000000 Z
12
+ date: 2020-05-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: between_meals
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: 0.0.6
20
+ version: 0.0.10
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: 0.0.6
27
+ version: 0.0.10
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: json
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -166,7 +166,7 @@ files:
166
166
  - scripts/taste-untester
167
167
  homepage: https://github.com/facebook/taste-tester
168
168
  licenses:
169
- - Apache
169
+ - Apache-2.0
170
170
  metadata: {}
171
171
  post_install_message:
172
172
  rdoc_options: []