taste_tester 0.0.12 → 0.0.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +62 -10
- data/bin/taste-tester +93 -32
- data/lib/taste_tester/client.rb +129 -18
- data/lib/taste_tester/commands.rb +206 -16
- data/lib/taste_tester/config.rb +22 -4
- data/lib/taste_tester/exceptions.rb +9 -0
- data/lib/taste_tester/hooks.rb +24 -16
- data/lib/taste_tester/host.rb +286 -118
- data/lib/taste_tester/locallink.rb +8 -6
- data/lib/taste_tester/logging.rb +8 -6
- data/lib/taste_tester/noop.rb +69 -0
- data/lib/taste_tester/server.rb +30 -11
- data/lib/taste_tester/ssh.rb +10 -31
- data/lib/taste_tester/ssh_util.rb +127 -0
- data/lib/taste_tester/state.rb +30 -8
- data/lib/taste_tester/tunnel.rb +167 -37
- data/lib/taste_tester/windows.rb +1 -0
- data/scripts/taste-untester +8 -0
- data/scripts/taste-untester.ps1 +85 -0
- metadata +22 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 01cc261c810b1efa71e7eba767be42c7292a2e7ebfa1f0ad2a81cb0aab465504
|
4
|
+
data.tar.gz: 2891b4d9a14b03183b24dc2fd3baada23b3a86e0ba741eb2ff43ed4e9bb15ef0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa9dee7d09f5a0ffd03c63d2bd96cabde536b498fb8f4efeab8fbfd564ecb21166954200f8d62702833b3c7977aba0b979e920e93d047a27e9ea4d5538d88dbf
|
7
|
+
data.tar.gz: 593d8b054ff209d1c5ed116d90a00bca9bb2a64a3d8e2c576e068e91321d286a3e2b30347179d22dafc09ca4525c70c2d49351e8b838fa3c8063ed5a006a89e4
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Taste Tester
|
2
2
|
|
3
|
-
|
3
|
+
![Continuous Integration](https://github.com/facebook/taste-tester/workflows/Continuous%20Integration/badge.svg?event=push)
|
4
4
|
|
5
5
|
## Intro
|
6
6
|
Ohai!
|
@@ -24,6 +24,7 @@ Typical usage is:
|
|
24
24
|
|
25
25
|
```text
|
26
26
|
vi cookbooks/... # Make your changes and commit locally
|
27
|
+
taste-tester impact # Check where your changes are used
|
27
28
|
taste-tester test -s [host] # Put host in Taste Tester mode
|
28
29
|
ssh root@[host] # Log in to host
|
29
30
|
# Run chef and watch it break
|
@@ -36,13 +37,13 @@ taste-tester untest [host] # Put host back in production
|
|
36
37
|
# (optional - will revert itself after 1 hour)
|
37
38
|
```
|
38
39
|
|
39
|
-
See the help for
|
40
|
+
See the help for further information.
|
40
41
|
|
41
42
|
## Prerequisites
|
42
43
|
|
43
|
-
* Taste Tester assumes that
|
44
|
-
servers is a symlink and that your real config is
|
45
|
-
|
44
|
+
* Taste Tester assumes that `/etc/chef/client.rb` and `/etc/chef/client.pem` on your
|
45
|
+
servers is a symlink and that your real config is `/etc/chef/client-prod.rb` and
|
46
|
+
`/etc/chef/client-prod.pem`, respectively.
|
46
47
|
|
47
48
|
* Taste Tester assumes that it's generally safe to "go back" to production. I.e.
|
48
49
|
We set things up so you can set a cronjob to un-taste-test a server after the
|
@@ -60,6 +61,8 @@ you want to test on, i.e. SSH public/private keys, SSH certificates, Kerberos
|
|
60
61
|
* Mixlib::Config
|
61
62
|
* Colorize
|
62
63
|
* BetweenMeals
|
64
|
+
* Minitar
|
65
|
+
* Chef
|
63
66
|
|
64
67
|
## Automatic Untesting
|
65
68
|
|
@@ -78,18 +81,19 @@ is also killing the ssh tunnel whose PID is in `/etc/chef/test_timestamp`
|
|
78
81
|
## Config file
|
79
82
|
|
80
83
|
The default config file is `/etc/taste-tester-config.rb` but you may use -c to
|
81
|
-
specify another. The config file works the same as client.rb does for Chef -
|
82
|
-
there are a series of keywords that take an
|
84
|
+
specify another. The config file works the same as `client.rb` does for Chef -
|
85
|
+
there are a series of keywords that take an argument and anything else is just
|
83
86
|
standard Ruby.
|
84
87
|
|
85
88
|
All command-line options are available in the config file:
|
86
89
|
* debug (bool, default: `false`)
|
87
90
|
* timestamp (bool, default: `false`)
|
88
91
|
* config_file (string, default: `/etc/taste-tester-config.rb`)
|
89
|
-
* plugin_path (string, default
|
92
|
+
* plugin_path (string, no default)
|
90
93
|
* repo (string, default: `#{ENV['HOME']}/ops`)
|
91
94
|
* testing_time (int, default: `3600`)
|
92
|
-
* chef_client_command (
|
95
|
+
* chef_client_command (string, default: `chef-client`)
|
96
|
+
* json (bool, default: `false`)
|
93
97
|
* skip_repo_checks (bool, default: `false`)
|
94
98
|
* skip_pre_upload_hook (bool, default: `false`)
|
95
99
|
* skip_post_upload_hook (bool, default: `false`)
|
@@ -101,7 +105,7 @@ The following options are also available:
|
|
101
105
|
* base_dir - The directory in the repo under which to find chef configs.
|
102
106
|
Default: `chef`
|
103
107
|
* cookbook_dirs - An array of cookbook directories relative to base_dir.
|
104
|
-
Default: `['cookbooks']
|
108
|
+
Default: `['cookbooks']`
|
105
109
|
* role_dir - A directory of roles, relative to base_dir. Default: `roles`
|
106
110
|
* databag_dir - A directory of databags, relative to base_dir.
|
107
111
|
Default: `databags`
|
@@ -109,6 +113,14 @@ The following options are also available:
|
|
109
113
|
`#{ENV['HOME']}/.chef/taste-tester-ref.txt`
|
110
114
|
* checksum_dir - The checksum directory to put in knife.conf for users. Default:
|
111
115
|
`#{ENV['HOME']}/.chef/checksums`
|
116
|
+
* bundle - use a single tar.gz file for transporting cookbooks, roles and
|
117
|
+
databags to clients. Experimental. Value is tri-state:
|
118
|
+
* `false` - server uses knife upload, client uses `chef_server`
|
119
|
+
* `:compatible` - make server support both methods, client uses tar.gz
|
120
|
+
* `true` - server only creates tar.gz, client uses tar.gz
|
121
|
+
Default: false
|
122
|
+
* impact - analyze local changes to determine which hosts/roles to test.
|
123
|
+
Default: false
|
112
124
|
|
113
125
|
## Plugin
|
114
126
|
|
@@ -149,3 +161,43 @@ Stuff to do after putting all hosts in test mode.
|
|
149
161
|
* self.repo_checks(dryrun, repo)
|
150
162
|
|
151
163
|
Additional checks you want to do on the repo as sanity checks.
|
164
|
+
|
165
|
+
* self.find_impact(changes)
|
166
|
+
|
167
|
+
Custom implementation of impact analysis. Uses `knife deps` by default. May
|
168
|
+
return any data structure, provided one or both of `self.post_impact` or
|
169
|
+
`self.print_impact` are defined.
|
170
|
+
|
171
|
+
* self.post_impact(basic_impact)
|
172
|
+
|
173
|
+
Stuff to do after preliminary impact analysis. May be used to extend the
|
174
|
+
information generated by `self.find_impact`, reformat the data structure, etc.
|
175
|
+
|
176
|
+
* self.print_impact(final_impact)
|
177
|
+
|
178
|
+
Custom output of calculated impact, useful if defining either of the other
|
179
|
+
impact hooks. Must return a truthy value to prevent the default output from
|
180
|
+
printing.
|
181
|
+
|
182
|
+
## Plugin example
|
183
|
+
|
184
|
+
This is an example `/etc/taste-tester-plugin.rb` to add a user-defined string
|
185
|
+
to `client-taste-tester.rb` on the remote system:
|
186
|
+
|
187
|
+
```
|
188
|
+
Hooks.class_eval do
|
189
|
+
def self.test_remote_client_rb_extra_code(_hostname)
|
190
|
+
%(
|
191
|
+
# This comment gets added to client-taste-tester.rb
|
192
|
+
# This one too.
|
193
|
+
)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
198
|
+
Be sure to pass this plugin file with `-p` on the command line or set it as
|
199
|
+
`plugin_path` in your `taste-tester-config.rb` file.
|
200
|
+
|
201
|
+
## License
|
202
|
+
|
203
|
+
See the `LICENSE` file.
|
data/bin/taste-tester
CHANGED
@@ -15,9 +15,7 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
|
18
|
-
$LOAD_PATH.unshift(
|
19
|
-
|
20
|
-
# rubocop:disable UnusedBlockArgument, AlignParameters
|
18
|
+
$LOAD_PATH.unshift(__dir__ + '/../lib')
|
21
19
|
|
22
20
|
require 'rubygems'
|
23
21
|
require 'time'
|
@@ -30,27 +28,27 @@ require 'taste_tester/commands'
|
|
30
28
|
require 'taste_tester/hooks'
|
31
29
|
require 'taste_tester/exceptions'
|
32
30
|
|
33
|
-
include TasteTester::Logging
|
34
|
-
|
35
|
-
if ENV['USER'] == 'root'
|
36
|
-
logger.warn('You should not be running as root')
|
37
|
-
exit(1)
|
38
|
-
end
|
39
|
-
|
40
31
|
# Command line parsing and param descriptions
|
41
32
|
module TasteTester
|
33
|
+
extend TasteTester::Logging
|
34
|
+
|
42
35
|
verify = 'Verify your changes were actually applied as intended!'.red
|
43
36
|
|
37
|
+
if ENV['USER'] == 'root'
|
38
|
+
logger.warn('You should not be running as root')
|
39
|
+
exit(1)
|
40
|
+
end
|
41
|
+
|
44
42
|
# Do an initial read of the config file if it's in the default place, so
|
45
43
|
# that if people override chef_client_command the help message is correct.
|
46
|
-
if File.
|
44
|
+
if File.exist?(File.expand_path(TasteTester::Config.config_file))
|
47
45
|
TasteTester::Config.from_file(
|
48
46
|
File.expand_path(TasteTester::Config.config_file),
|
49
47
|
)
|
50
48
|
end
|
51
49
|
|
52
50
|
cmd = TasteTester::Config.chef_client_command
|
53
|
-
description = <<-
|
51
|
+
description = <<-ENDOFDESCRIPTION
|
54
52
|
Welcome to taste-tester!
|
55
53
|
|
56
54
|
Usage: taste-tester <mode> [<options>]
|
@@ -68,7 +66,8 @@ TLDR; Most common usage is:
|
|
68
66
|
taste-tester untest -s [host] # Put host back in production
|
69
67
|
# (optional - will revert itself after 1 hour)
|
70
68
|
|
71
|
-
And you're done!
|
69
|
+
And you're done!
|
70
|
+
Note: There may be site specific testing instructions, see local documentation for details.
|
72
71
|
|
73
72
|
MODES:
|
74
73
|
test
|
@@ -76,6 +75,13 @@ MODES:
|
|
76
75
|
point some production server specified by -s to your virtual chef server for
|
77
76
|
testing. If you have you a plugin that uses the hookpoint, it'll may amend
|
78
77
|
your commit message to denote the server you tested.
|
78
|
+
Returns:
|
79
|
+
0 Success.
|
80
|
+
1 Failure, e.g. host not reachable or usage error.
|
81
|
+
2 Partial success, mixture of success and failure due to hosts being taste
|
82
|
+
tested by other users.
|
83
|
+
3 All hosts were not taste-tested because they are being taste-tested by
|
84
|
+
other users.
|
79
85
|
|
80
86
|
upload
|
81
87
|
Sync your local repo to your virtual Chef server (i.e. just the first step
|
@@ -85,6 +91,13 @@ MODES:
|
|
85
91
|
cookbooks and roles. It also does a fair amount of sanity checking on
|
86
92
|
your repo and you may specify --skip-repo-checks to bypass this.
|
87
93
|
|
94
|
+
impact
|
95
|
+
Examine local repo changes to determine which roles are potentially
|
96
|
+
impacted by the changes. Compares the set of modified cookbooks with the
|
97
|
+
dependency lists of each role and reports any role which depends on a
|
98
|
+
modified cookbook. It is recommended to test your changes on at least one
|
99
|
+
server of each role, potentially also on multiple platforms.
|
100
|
+
|
88
101
|
keeptesting
|
89
102
|
Extend the testing time on server specified by -s by 1 hour unless
|
90
103
|
otherwise specified by -t.
|
@@ -113,7 +126,7 @@ MODES:
|
|
113
126
|
You probably don't want this. It will restart up the chef-zero server on
|
114
127
|
your localhost. taste-tester dynamically starts this if it's down, so there
|
115
128
|
should be no need to do this manually.
|
116
|
-
|
129
|
+
ENDOFDESCRIPTION
|
117
130
|
|
118
131
|
mode = ARGV.shift unless !ARGV.empty? && ARGV[0].start_with?('-')
|
119
132
|
|
@@ -123,7 +136,6 @@ MODES:
|
|
123
136
|
end
|
124
137
|
|
125
138
|
options = { :config_file => TasteTester::Config.config_file }
|
126
|
-
# rubocop:disable Metrics/BlockLength
|
127
139
|
parser = OptionParser.new do |opts|
|
128
140
|
opts.banner = description
|
129
141
|
|
@@ -131,7 +143,7 @@ MODES:
|
|
131
143
|
opts.separator 'Global options:'.upcase
|
132
144
|
|
133
145
|
opts.on('-c', '--config FILE', 'Config file') do |file|
|
134
|
-
unless File.
|
146
|
+
unless File.exist?(File.expand_path(file))
|
135
147
|
logger.error("Sorry, cannot find #{file}")
|
136
148
|
exit(1)
|
137
149
|
end
|
@@ -148,7 +160,7 @@ MODES:
|
|
148
160
|
end
|
149
161
|
|
150
162
|
opts.on('-p', '--plugin-path FILE', String, 'Plugin file') do |file|
|
151
|
-
unless File.
|
163
|
+
unless File.exist?(File.expand_path(file))
|
152
164
|
logger.error("Sorry, cannot find #{file}")
|
153
165
|
exit(1)
|
154
166
|
end
|
@@ -219,20 +231,23 @@ MODES:
|
|
219
231
|
options[:linkonly] = true
|
220
232
|
end
|
221
233
|
|
222
|
-
opts.on(
|
223
|
-
|
224
|
-
|
225
|
-
|
234
|
+
opts.on('--transport TRANSPORT', ['locallink', 'ssh', 'noop'],
|
235
|
+
"Set the transport\n" +
|
236
|
+
"\t\tssh - [default] Use SSH to talk to remote host\n" +
|
237
|
+
"\t\tlocallink - Assume the remote host is ourself\n" +
|
238
|
+
"\t\tnoop - Ignore all remote commands") do |transport|
|
239
|
+
options[:transport] = transport
|
226
240
|
end
|
227
241
|
|
228
242
|
opts.on(
|
229
243
|
'-t', '--testing-timestamp TIME',
|
230
|
-
|
231
|
-
|
244
|
+
'Until when should the host remain in testing.' +
|
245
|
+
' Anything parsable is ok, such as "5/18 4:35" or "16/9/13".'
|
232
246
|
) do |time|
|
247
|
+
# can make this an implicit rescue after we drop ruby 2.4
|
233
248
|
begin
|
234
249
|
options[:testing_until] = Time.parse(time)
|
235
|
-
rescue
|
250
|
+
rescue StandardError
|
236
251
|
logger.error("Invalid date: #{time}")
|
237
252
|
exit 1
|
238
253
|
end
|
@@ -240,8 +255,8 @@ MODES:
|
|
240
255
|
|
241
256
|
opts.on(
|
242
257
|
'-t', '--testing-time TIME',
|
243
|
-
|
244
|
-
|
258
|
+
'How long should the host remain in testing.' +
|
259
|
+
' Takes a simple relative time string, such as "45m", "4h" or "1d".'
|
245
260
|
) do |time|
|
246
261
|
m = time.match(/^(\d+)([d|h|m]+)$/)
|
247
262
|
if m
|
@@ -260,7 +275,7 @@ MODES:
|
|
260
275
|
|
261
276
|
opts.on(
|
262
277
|
'-r', '--repo DIR',
|
263
|
-
"Custom repo location, current
|
278
|
+
"Custom repo location, current default is #{TasteTester::Config.repo}." +
|
264
279
|
' Works on upload and test.'
|
265
280
|
) do |dir|
|
266
281
|
options[:repo] = dir
|
@@ -273,6 +288,16 @@ MODES:
|
|
273
288
|
options[:repo_type] = type
|
274
289
|
end
|
275
290
|
|
291
|
+
opts.on(
|
292
|
+
'--clowntown-no-repo', 'This option enables taste-tester to run ' +
|
293
|
+
'without a SCM repo of any sort. This has negative implications like ' +
|
294
|
+
'always doing a full upload. This option is here for weird corner ' +
|
295
|
+
'cases like having to sync out the export of a repo, but is not ' +
|
296
|
+
'generally a good idea or well supported. Use at your own risk.'
|
297
|
+
) do
|
298
|
+
options[:no_repo] = true
|
299
|
+
end
|
300
|
+
|
276
301
|
opts.on(
|
277
302
|
'-R', '--roles ROLE[,ROLE]', Array,
|
278
303
|
'Specific roles to upload. Intended mostly for debugging,' +
|
@@ -281,6 +306,17 @@ MODES:
|
|
281
306
|
options[:roles] = roles
|
282
307
|
end
|
283
308
|
|
309
|
+
opts.on(
|
310
|
+
'-J', '--jumps JUMP',
|
311
|
+
'Uses ssh\'s `ProxyJump` support to ssh across bastion/jump hosts. ' +
|
312
|
+
'This is particularly useful in tunnel mode to test machines that ' +
|
313
|
+
'your workstatation doesn\'t have direct access to. The format is ' +
|
314
|
+
'the same as `ssh -J`: a comma-separated list of hosts to forward ' +
|
315
|
+
'through.'
|
316
|
+
) do |jumps|
|
317
|
+
options[:jumps] = jumps
|
318
|
+
end
|
319
|
+
|
284
320
|
opts.on('--really', 'Really do link-only. DANGEROUS!') do |r|
|
285
321
|
options[:really] = r
|
286
322
|
end
|
@@ -311,6 +347,12 @@ MODES:
|
|
311
347
|
options[:ssh_command] = c
|
312
348
|
end
|
313
349
|
|
350
|
+
opts.on(
|
351
|
+
'--ssh-connect-timeout TIMEOUT', 'SSH \'-o ConnectTimeout\' value'
|
352
|
+
) do |c|
|
353
|
+
options[:ssh_connect_timeout] = c
|
354
|
+
end
|
355
|
+
|
314
356
|
opts.on('--skip-repo-checks', 'Skip repository sanity checks') do
|
315
357
|
options[:skip_repo_checks] = true
|
316
358
|
end
|
@@ -319,6 +361,22 @@ MODES:
|
|
319
361
|
options[:yes] = true
|
320
362
|
end
|
321
363
|
|
364
|
+
opts.on(
|
365
|
+
'--json', 'Format output as JSON for programatic consumption.' +
|
366
|
+
' Default to false. Works on impact.'
|
367
|
+
) do
|
368
|
+
options[:json] = true
|
369
|
+
end
|
370
|
+
|
371
|
+
opts.on(
|
372
|
+
'-w', '--windows-target',
|
373
|
+
'The target is a Windows machine. You will likely want to override ' +
|
374
|
+
'`test_timestamp` and `chef_config_path`, but *not* `config_file`. ' +
|
375
|
+
'Requires the target be running PowerShell >= 5.1 as the default shell.'
|
376
|
+
) do
|
377
|
+
options[:windows_target] = true
|
378
|
+
end
|
379
|
+
|
322
380
|
opts.separator ''
|
323
381
|
opts.separator 'Control local hook behavior with these options:'
|
324
382
|
|
@@ -352,7 +410,6 @@ MODES:
|
|
352
410
|
options[:skip_post_test_hook] = true
|
353
411
|
end
|
354
412
|
end
|
355
|
-
# rubocop:enable Metrics/BlockLength
|
356
413
|
|
357
414
|
if mode == 'help'
|
358
415
|
puts parser
|
@@ -361,7 +418,7 @@ MODES:
|
|
361
418
|
|
362
419
|
parser.parse!
|
363
420
|
|
364
|
-
if File.
|
421
|
+
if File.exist?(File.expand_path(options[:config_file]))
|
365
422
|
TasteTester::Config.from_file(File.expand_path(options[:config_file]))
|
366
423
|
end
|
367
424
|
TasteTester::Config.merge!(options)
|
@@ -370,7 +427,7 @@ MODES:
|
|
370
427
|
|
371
428
|
if TasteTester::Config.plugin_path
|
372
429
|
path = File.expand_path(TasteTester::Config.plugin_path)
|
373
|
-
unless File.
|
430
|
+
unless File.exist?(path)
|
374
431
|
logger.error("Plugin not found (#{path})")
|
375
432
|
exit(1)
|
376
433
|
end
|
@@ -397,6 +454,8 @@ MODES:
|
|
397
454
|
TasteTester::Commands.runchef
|
398
455
|
when :upload
|
399
456
|
TasteTester::Commands.upload
|
457
|
+
when :impact
|
458
|
+
TasteTester::Commands.impact
|
400
459
|
else
|
401
460
|
logger.error("Invalid mode: #{mode}")
|
402
461
|
puts parser
|
@@ -407,6 +466,8 @@ MODES:
|
|
407
466
|
end
|
408
467
|
end
|
409
468
|
|
410
|
-
if
|
411
|
-
|
469
|
+
if $PROGRAM_NAME == __FILE__
|
470
|
+
module TasteTester
|
471
|
+
include TasteTester
|
472
|
+
end
|
412
473
|
end
|
data/lib/taste_tester/client.rb
CHANGED
@@ -14,10 +14,14 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
+
require 'minitar'
|
18
|
+
require 'find'
|
17
19
|
require 'taste_tester/logging'
|
18
20
|
require 'between_meals/repo'
|
19
21
|
require 'between_meals/knife'
|
20
22
|
require 'between_meals/changeset'
|
23
|
+
require 'chef/log'
|
24
|
+
require 'chef/cookbook/chefignore'
|
21
25
|
|
22
26
|
module TasteTester
|
23
27
|
# Client side upload functionality
|
@@ -44,16 +48,23 @@ module TasteTester
|
|
44
48
|
:databag_dir => TasteTester::Config.databags,
|
45
49
|
:checksum_dir => TasteTester::Config.checksum_dir,
|
46
50
|
:role_type => TasteTester::Config.role_type,
|
51
|
+
:config => TasteTester::Config.knife_config,
|
47
52
|
)
|
48
53
|
@knife.write_user_config
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
if TasteTester::Config.no_repo
|
55
|
+
@repo = nil
|
56
|
+
else
|
57
|
+
@repo = BetweenMeals::Repo.get(
|
58
|
+
TasteTester::Config.repo_type,
|
59
|
+
TasteTester::Config.repo,
|
60
|
+
logger,
|
61
|
+
)
|
56
62
|
end
|
63
|
+
if @repo && !@repo.exists?
|
64
|
+
fail "Could not open repo from #{TasteTester::Config.repo}"
|
65
|
+
end
|
66
|
+
|
67
|
+
@track_symlinks = TasteTester::Config.track_symlinks
|
57
68
|
end
|
58
69
|
|
59
70
|
def checks
|
@@ -63,26 +74,30 @@ module TasteTester
|
|
63
74
|
end
|
64
75
|
|
65
76
|
def upload
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
"
|
77
|
+
head_rev = nil
|
78
|
+
if @repo
|
79
|
+
head_rev = @repo.head_rev
|
80
|
+
checks unless @skip_checks
|
81
|
+
logger.info("Last commit: #{head_rev} " +
|
82
|
+
"'#{@repo.last_msg.split("\n").first}'" +
|
83
|
+
" by #{@repo.last_author[:email]}")
|
84
|
+
end
|
71
85
|
|
72
|
-
if @force || !@server.latest_uploaded_ref
|
86
|
+
if @force || !@server.latest_uploaded_ref || !@repo
|
73
87
|
logger.info('Full upload forced') if @force
|
88
|
+
logger.info('No repo, doing full upload') unless @repo
|
74
89
|
unless TasteTester::Config.skip_pre_upload_hook
|
75
90
|
TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun,
|
76
91
|
@repo,
|
77
92
|
nil,
|
78
|
-
|
93
|
+
head_rev)
|
79
94
|
end
|
80
95
|
time(logger) { full }
|
81
96
|
unless TasteTester::Config.skip_post_upload_hook
|
82
97
|
TasteTester::Hooks.post_upload(TasteTester::Config.dryrun,
|
83
98
|
@repo,
|
84
99
|
nil,
|
85
|
-
|
100
|
+
head_rev)
|
86
101
|
end
|
87
102
|
else
|
88
103
|
# Since we also upload the index, we always need to run the
|
@@ -92,7 +107,7 @@ module TasteTester
|
|
92
107
|
TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun,
|
93
108
|
@repo,
|
94
109
|
@server.latest_uploaded_ref,
|
95
|
-
|
110
|
+
head_rev)
|
96
111
|
end
|
97
112
|
begin
|
98
113
|
time(logger) { partial }
|
@@ -104,23 +119,118 @@ module TasteTester
|
|
104
119
|
TasteTester::Hooks.post_upload(TasteTester::Config.dryrun,
|
105
120
|
@repo,
|
106
121
|
@server.latest_uploaded_ref,
|
107
|
-
|
122
|
+
head_rev)
|
108
123
|
end
|
109
124
|
end
|
110
125
|
|
111
|
-
@server.latest_uploaded_ref =
|
126
|
+
@server.latest_uploaded_ref = head_rev
|
127
|
+
@server.last_upload_time = Time.new.strftime('%Y-%m-%d %H:%M:%S')
|
112
128
|
end
|
113
129
|
|
114
130
|
private
|
115
131
|
|
132
|
+
def populate(stream, writer, path, destination)
|
133
|
+
full_path = File.join(File.join(TasteTester::Config.repo, path))
|
134
|
+
return unless File.directory?(full_path)
|
135
|
+
chefignores = Chef::Cookbook::Chefignore.new(full_path)
|
136
|
+
# everything is relative to the repo dir. chdir makes handling all the
|
137
|
+
# paths within this simpler
|
138
|
+
Dir.chdir(full_path) do
|
139
|
+
look_at = ['']
|
140
|
+
while (prefix = look_at.pop)
|
141
|
+
Dir.glob(File.join("#{prefix}**", '*'), File::FNM_DOTMATCH) do |p|
|
142
|
+
minus_first = p.split(
|
143
|
+
File::SEPARATOR,
|
144
|
+
)[1..-1].join(File::SEPARATOR)
|
145
|
+
next if chefignores.ignored?(p) ||
|
146
|
+
chefignores.ignored?(minus_first)
|
147
|
+
name = File.join(destination, p)
|
148
|
+
if File.directory?(p)
|
149
|
+
# we don't store directories in the tar, but we do want to follow
|
150
|
+
# top level symlinked directories as they are used to share
|
151
|
+
# cookbooks between codebases.
|
152
|
+
if minus_first == '' && File.symlink?(p)
|
153
|
+
look_at.push("#{p}#{File::SEPARATOR}")
|
154
|
+
end
|
155
|
+
elsif File.symlink?(p)
|
156
|
+
# tar handling of filenames > 100 characters gets complex. We'd
|
157
|
+
# use split_name from Minitar, but it's a private method. It's
|
158
|
+
# reasonable to assume that all symlink names in the bundle are
|
159
|
+
# less than 100 characters long. Long term, the version of minitar
|
160
|
+
# in chefdk should be upgraded.
|
161
|
+
fail 'Add support for long symlink paths' if name.size > 100
|
162
|
+
# The version of Minitar included in chefdk does not support
|
163
|
+
# symlinks directly. Therefore we use direct writes to the
|
164
|
+
# underlying stream to reproduce the symlinks
|
165
|
+
symlink = {
|
166
|
+
:name => name,
|
167
|
+
:mode => 0644,
|
168
|
+
:typeflag => '2',
|
169
|
+
:size => 0,
|
170
|
+
:linkname => File.readlink(p),
|
171
|
+
:prefix => '',
|
172
|
+
}
|
173
|
+
stream.write(Minitar::PosixHeader.new(symlink))
|
174
|
+
else
|
175
|
+
File.open(p, 'rb') do |r|
|
176
|
+
writer.add_file_simple(
|
177
|
+
name, :mode => 0644, :size => File.size(r)
|
178
|
+
) do |d, _opts|
|
179
|
+
IO.copy_stream(r, d)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def bundle_upload
|
189
|
+
dest = File.join(@server.bundle_dir, 'tt.tgz')
|
190
|
+
begin
|
191
|
+
Tempfile.create(['tt', '.tgz'], @server.bundle_dir) do |tempfile|
|
192
|
+
stream = Zlib::GzipWriter.new(tempfile)
|
193
|
+
Minitar::Writer.open(stream) do |writer|
|
194
|
+
TasteTester::Config.relative_cookbook_dirs.each do |cb_dir|
|
195
|
+
populate(stream, writer, cb_dir, 'cookbooks')
|
196
|
+
end
|
197
|
+
populate(
|
198
|
+
stream, writer, TasteTester::Config.relative_role_dir, 'roles'
|
199
|
+
)
|
200
|
+
populate(
|
201
|
+
stream, writer, TasteTester::Config.relative_databag_dir,
|
202
|
+
'databag'
|
203
|
+
)
|
204
|
+
end
|
205
|
+
stream.close
|
206
|
+
File.rename(tempfile.path, dest)
|
207
|
+
end
|
208
|
+
rescue Errno::ENOENT
|
209
|
+
# Normally the temporary file is renamed to the dest name. If this
|
210
|
+
# happens, then the cleanup of of the temporary file doesn't work,
|
211
|
+
# but this is fine and expected.
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
116
216
|
def full
|
117
217
|
logger.warn('Doing full upload')
|
218
|
+
if TasteTester::Config.bundle
|
219
|
+
bundle_upload
|
220
|
+
# only leave early if true (strictly bundle mode only)
|
221
|
+
return if TasteTester::Config.bundle == true
|
222
|
+
end
|
118
223
|
@knife.cookbook_upload_all
|
119
224
|
@knife.role_upload_all
|
120
225
|
@knife.databag_upload_all
|
121
226
|
end
|
122
227
|
|
123
228
|
def partial
|
229
|
+
if TasteTester::Config.bundle
|
230
|
+
logger.info('No partial support for bundle mode, doing full upload')
|
231
|
+
bundle_upload
|
232
|
+
return if TasteTester::Config.bundle == true
|
233
|
+
end
|
124
234
|
logger.info('Doing differential upload from ' +
|
125
235
|
@server.latest_uploaded_ref)
|
126
236
|
changeset = BetweenMeals::Changeset.new(
|
@@ -136,6 +246,7 @@ module TasteTester
|
|
136
246
|
:databag_dir =>
|
137
247
|
TasteTester::Config.relative_databag_dir,
|
138
248
|
},
|
249
|
+
@track_symlinks,
|
139
250
|
)
|
140
251
|
|
141
252
|
cbs = changeset.cookbooks
|