taste_tester 0.0.13 → 0.0.18
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 -11
- data/bin/taste-tester +81 -20
- data/lib/taste_tester/client.rb +126 -17
- data/lib/taste_tester/commands.rb +204 -13
- data/lib/taste_tester/config.rb +19 -4
- data/lib/taste_tester/exceptions.rb +7 -0
- data/lib/taste_tester/hooks.rb +17 -2
- data/lib/taste_tester/host.rb +277 -114
- data/lib/taste_tester/locallink.rb +7 -5
- data/lib/taste_tester/logging.rb +4 -3
- data/lib/taste_tester/noop.rb +4 -2
- data/lib/taste_tester/server.rb +27 -9
- data/lib/taste_tester/ssh.rb +9 -30
- data/lib/taste_tester/ssh_util.rb +127 -0
- data/lib/taste_tester/state.rb +26 -4
- data/lib/taste_tester/tunnel.rb +169 -38
- data/lib/taste_tester/windows.rb +1 -0
- data/scripts/taste-untester +8 -8
- data/scripts/taste-untester.ps1 +85 -0
- metadata +21 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e8206a39afa26db4e27600fa362c26d33d16fcefe6a5e79a4869fe339b9e1e18
|
4
|
+
data.tar.gz: 2eb1a8bdfd58d5713dc1f5197da4b10cd0167542fefa641bfe3d6863c252fc58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40e15c8cf0858f82e65e0b94e4bdcfa44223b0c891ef9242aa0cbb264c36c6931e033b9b10cf70fcc68b74be4ff4628042872d78466f761bfc367de69f116897
|
7
|
+
data.tar.gz: 28dbed4002b508d4eb7222b3007bc6580dd4c881d4894e456a849bc882339d6b97ccec6858682c5bc71b0dbf8aa03d377de47eefa85e1c5cff09601884ca19bf
|
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# Taste Tester
|
2
2
|
|
3
|
-
|
4
|
-
[![CircleCI](https://circleci.com/gh/facebook/taste-tester.svg?style=svg)](https://circleci.com/gh/facebook/taste-tester)
|
3
|
+
![Continuous Integration](https://github.com/facebook/taste-tester/workflows/Continuous%20Integration/badge.svg?event=push)
|
5
4
|
|
6
5
|
## Intro
|
7
6
|
Ohai!
|
@@ -25,6 +24,7 @@ Typical usage is:
|
|
25
24
|
|
26
25
|
```text
|
27
26
|
vi cookbooks/... # Make your changes and commit locally
|
27
|
+
taste-tester impact # Check where your changes are used
|
28
28
|
taste-tester test -s [host] # Put host in Taste Tester mode
|
29
29
|
ssh root@[host] # Log in to host
|
30
30
|
# Run chef and watch it break
|
@@ -37,13 +37,13 @@ taste-tester untest [host] # Put host back in production
|
|
37
37
|
# (optional - will revert itself after 1 hour)
|
38
38
|
```
|
39
39
|
|
40
|
-
See the help for
|
40
|
+
See the help for further information.
|
41
41
|
|
42
42
|
## Prerequisites
|
43
43
|
|
44
|
-
* Taste Tester assumes that
|
45
|
-
servers is a symlink and that your real config is
|
46
|
-
|
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.
|
47
47
|
|
48
48
|
* Taste Tester assumes that it's generally safe to "go back" to production. I.e.
|
49
49
|
We set things up so you can set a cronjob to un-taste-test a server after the
|
@@ -61,6 +61,8 @@ you want to test on, i.e. SSH public/private keys, SSH certificates, Kerberos
|
|
61
61
|
* Mixlib::Config
|
62
62
|
* Colorize
|
63
63
|
* BetweenMeals
|
64
|
+
* Minitar
|
65
|
+
* Chef
|
64
66
|
|
65
67
|
## Automatic Untesting
|
66
68
|
|
@@ -79,18 +81,19 @@ is also killing the ssh tunnel whose PID is in `/etc/chef/test_timestamp`
|
|
79
81
|
## Config file
|
80
82
|
|
81
83
|
The default config file is `/etc/taste-tester-config.rb` but you may use -c to
|
82
|
-
specify another. The config file works the same as client.rb does for Chef -
|
83
|
-
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
|
84
86
|
standard Ruby.
|
85
87
|
|
86
88
|
All command-line options are available in the config file:
|
87
89
|
* debug (bool, default: `false`)
|
88
90
|
* timestamp (bool, default: `false`)
|
89
91
|
* config_file (string, default: `/etc/taste-tester-config.rb`)
|
90
|
-
* plugin_path (string, default
|
92
|
+
* plugin_path (string, no default)
|
91
93
|
* repo (string, default: `#{ENV['HOME']}/ops`)
|
92
94
|
* testing_time (int, default: `3600`)
|
93
|
-
* chef_client_command (
|
95
|
+
* chef_client_command (string, default: `chef-client`)
|
96
|
+
* json (bool, default: `false`)
|
94
97
|
* skip_repo_checks (bool, default: `false`)
|
95
98
|
* skip_pre_upload_hook (bool, default: `false`)
|
96
99
|
* skip_post_upload_hook (bool, default: `false`)
|
@@ -102,7 +105,7 @@ The following options are also available:
|
|
102
105
|
* base_dir - The directory in the repo under which to find chef configs.
|
103
106
|
Default: `chef`
|
104
107
|
* cookbook_dirs - An array of cookbook directories relative to base_dir.
|
105
|
-
Default: `['cookbooks']
|
108
|
+
Default: `['cookbooks']`
|
106
109
|
* role_dir - A directory of roles, relative to base_dir. Default: `roles`
|
107
110
|
* databag_dir - A directory of databags, relative to base_dir.
|
108
111
|
Default: `databags`
|
@@ -110,6 +113,14 @@ The following options are also available:
|
|
110
113
|
`#{ENV['HOME']}/.chef/taste-tester-ref.txt`
|
111
114
|
* checksum_dir - The checksum directory to put in knife.conf for users. Default:
|
112
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
|
113
124
|
|
114
125
|
## Plugin
|
115
126
|
|
@@ -150,3 +161,43 @@ Stuff to do after putting all hosts in test mode.
|
|
150
161
|
* self.repo_checks(dryrun, repo)
|
151
162
|
|
152
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
@@ -28,27 +28,27 @@ require 'taste_tester/commands'
|
|
28
28
|
require 'taste_tester/hooks'
|
29
29
|
require 'taste_tester/exceptions'
|
30
30
|
|
31
|
-
include TasteTester::Logging
|
32
|
-
|
33
|
-
if ENV['USER'] == 'root'
|
34
|
-
logger.warn('You should not be running as root')
|
35
|
-
exit(1)
|
36
|
-
end
|
37
|
-
|
38
31
|
# Command line parsing and param descriptions
|
39
32
|
module TasteTester
|
33
|
+
extend TasteTester::Logging
|
34
|
+
|
40
35
|
verify = 'Verify your changes were actually applied as intended!'.red
|
41
36
|
|
37
|
+
if ENV['USER'] == 'root'
|
38
|
+
logger.warn('You should not be running as root')
|
39
|
+
exit(1)
|
40
|
+
end
|
41
|
+
|
42
42
|
# Do an initial read of the config file if it's in the default place, so
|
43
43
|
# that if people override chef_client_command the help message is correct.
|
44
|
-
if File.
|
44
|
+
if File.exist?(File.expand_path(TasteTester::Config.config_file))
|
45
45
|
TasteTester::Config.from_file(
|
46
46
|
File.expand_path(TasteTester::Config.config_file),
|
47
47
|
)
|
48
48
|
end
|
49
49
|
|
50
50
|
cmd = TasteTester::Config.chef_client_command
|
51
|
-
description = <<-
|
51
|
+
description = <<-ENDOFDESCRIPTION
|
52
52
|
Welcome to taste-tester!
|
53
53
|
|
54
54
|
Usage: taste-tester <mode> [<options>]
|
@@ -66,7 +66,8 @@ TLDR; Most common usage is:
|
|
66
66
|
taste-tester untest -s [host] # Put host back in production
|
67
67
|
# (optional - will revert itself after 1 hour)
|
68
68
|
|
69
|
-
And you're done!
|
69
|
+
And you're done!
|
70
|
+
Note: There may be site specific testing instructions, see local documentation for details.
|
70
71
|
|
71
72
|
MODES:
|
72
73
|
test
|
@@ -74,6 +75,13 @@ MODES:
|
|
74
75
|
point some production server specified by -s to your virtual chef server for
|
75
76
|
testing. If you have you a plugin that uses the hookpoint, it'll may amend
|
76
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.
|
77
85
|
|
78
86
|
upload
|
79
87
|
Sync your local repo to your virtual Chef server (i.e. just the first step
|
@@ -83,6 +91,13 @@ MODES:
|
|
83
91
|
cookbooks and roles. It also does a fair amount of sanity checking on
|
84
92
|
your repo and you may specify --skip-repo-checks to bypass this.
|
85
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
|
+
|
86
101
|
keeptesting
|
87
102
|
Extend the testing time on server specified by -s by 1 hour unless
|
88
103
|
otherwise specified by -t.
|
@@ -111,7 +126,7 @@ MODES:
|
|
111
126
|
You probably don't want this. It will restart up the chef-zero server on
|
112
127
|
your localhost. taste-tester dynamically starts this if it's down, so there
|
113
128
|
should be no need to do this manually.
|
114
|
-
|
129
|
+
ENDOFDESCRIPTION
|
115
130
|
|
116
131
|
mode = ARGV.shift unless !ARGV.empty? && ARGV[0].start_with?('-')
|
117
132
|
|
@@ -121,7 +136,6 @@ MODES:
|
|
121
136
|
end
|
122
137
|
|
123
138
|
options = { :config_file => TasteTester::Config.config_file }
|
124
|
-
# rubocop:disable Metrics/BlockLength
|
125
139
|
parser = OptionParser.new do |opts|
|
126
140
|
opts.banner = description
|
127
141
|
|
@@ -129,7 +143,7 @@ MODES:
|
|
129
143
|
opts.separator 'Global options:'.upcase
|
130
144
|
|
131
145
|
opts.on('-c', '--config FILE', 'Config file') do |file|
|
132
|
-
unless File.
|
146
|
+
unless File.exist?(File.expand_path(file))
|
133
147
|
logger.error("Sorry, cannot find #{file}")
|
134
148
|
exit(1)
|
135
149
|
end
|
@@ -146,7 +160,7 @@ MODES:
|
|
146
160
|
end
|
147
161
|
|
148
162
|
opts.on('-p', '--plugin-path FILE', String, 'Plugin file') do |file|
|
149
|
-
unless File.
|
163
|
+
unless File.exist?(File.expand_path(file))
|
150
164
|
logger.error("Sorry, cannot find #{file}")
|
151
165
|
exit(1)
|
152
166
|
end
|
@@ -230,6 +244,7 @@ MODES:
|
|
230
244
|
'Until when should the host remain in testing.' +
|
231
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
250
|
rescue StandardError
|
@@ -241,7 +256,7 @@ MODES:
|
|
241
256
|
opts.on(
|
242
257
|
'-t', '--testing-time TIME',
|
243
258
|
'How long should the host remain in testing.' +
|
244
|
-
' Takes a simple relative time string, such as "45m", "4h" or "
|
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
|
@@ -408,5 +467,7 @@ MODES:
|
|
408
467
|
end
|
409
468
|
|
410
469
|
if $PROGRAM_NAME == __FILE__
|
411
|
-
|
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,22 @@ 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
|
-
|
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
|
+
)
|
62
|
+
end
|
63
|
+
if @repo && !@repo.exists?
|
55
64
|
fail "Could not open repo from #{TasteTester::Config.repo}"
|
56
65
|
end
|
66
|
+
|
57
67
|
@track_symlinks = TasteTester::Config.track_symlinks
|
58
68
|
end
|
59
69
|
|
@@ -64,26 +74,30 @@ module TasteTester
|
|
64
74
|
end
|
65
75
|
|
66
76
|
def upload
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
"
|
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
|
72
85
|
|
73
|
-
if @force || !@server.latest_uploaded_ref
|
86
|
+
if @force || !@server.latest_uploaded_ref || !@repo
|
74
87
|
logger.info('Full upload forced') if @force
|
88
|
+
logger.info('No repo, doing full upload') unless @repo
|
75
89
|
unless TasteTester::Config.skip_pre_upload_hook
|
76
90
|
TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun,
|
77
91
|
@repo,
|
78
92
|
nil,
|
79
|
-
|
93
|
+
head_rev)
|
80
94
|
end
|
81
95
|
time(logger) { full }
|
82
96
|
unless TasteTester::Config.skip_post_upload_hook
|
83
97
|
TasteTester::Hooks.post_upload(TasteTester::Config.dryrun,
|
84
98
|
@repo,
|
85
99
|
nil,
|
86
|
-
|
100
|
+
head_rev)
|
87
101
|
end
|
88
102
|
else
|
89
103
|
# Since we also upload the index, we always need to run the
|
@@ -93,7 +107,7 @@ module TasteTester
|
|
93
107
|
TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun,
|
94
108
|
@repo,
|
95
109
|
@server.latest_uploaded_ref,
|
96
|
-
|
110
|
+
head_rev)
|
97
111
|
end
|
98
112
|
begin
|
99
113
|
time(logger) { partial }
|
@@ -105,23 +119,118 @@ module TasteTester
|
|
105
119
|
TasteTester::Hooks.post_upload(TasteTester::Config.dryrun,
|
106
120
|
@repo,
|
107
121
|
@server.latest_uploaded_ref,
|
108
|
-
|
122
|
+
head_rev)
|
109
123
|
end
|
110
124
|
end
|
111
125
|
|
112
|
-
@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')
|
113
128
|
end
|
114
129
|
|
115
130
|
private
|
116
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
|
+
|
117
216
|
def full
|
118
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
|
119
223
|
@knife.cookbook_upload_all
|
120
224
|
@knife.role_upload_all
|
121
225
|
@knife.databag_upload_all
|
122
226
|
end
|
123
227
|
|
124
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
|
125
234
|
logger.info('Doing differential upload from ' +
|
126
235
|
@server.latest_uploaded_ref)
|
127
236
|
changeset = BetweenMeals::Changeset.new(
|