taste_tester 0.0.13 → 0.0.18
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 -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
|
-
[](https://circleci.com/gh/facebook/taste-tester)
|
|
3
|
+

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