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.
- 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
|
+

|
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
|