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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b43e9bbdc0fa2bccc9c0bca3f4c749d6c0b4f356
4
- data.tar.gz: 941177bd83e37b1830ca0f24add48768261541e9
2
+ SHA256:
3
+ metadata.gz: 01cc261c810b1efa71e7eba767be42c7292a2e7ebfa1f0ad2a81cb0aab465504
4
+ data.tar.gz: 2891b4d9a14b03183b24dc2fd3baada23b3a86e0ba741eb2ff43ed4e9bb15ef0
5
5
  SHA512:
6
- metadata.gz: 7446d6bef3d9f68860d2747a1a67141dcb3eb9fbbb9751713811afed02da87e192db4b4e473d11433fb908b4eafd14960ef610de052fbd3e8e3c82c5ccb037f2
7
- data.tar.gz: db111fd123f9faff1a37b40f128e2e8a2041b27ad5be234dd62e2cf99bede17218cbbb5450543c1b999f5eed001fce18f1f8150309155e8bf250b208081d2f73
6
+ metadata.gz: aa9dee7d09f5a0ffd03c63d2bd96cabde536b498fb8f4efeab8fbfd564ecb21166954200f8d62702833b3c7977aba0b979e920e93d047a27e9ea4d5538d88dbf
7
+ data.tar.gz: 593d8b054ff209d1c5ed116d90a00bca9bb2a64a3d8e2c576e068e91321d286a3e2b30347179d22dafc09ca4525c70c2d49351e8b838fa3c8063ed5a006a89e4
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Taste Tester
2
2
 
3
- [![Build Status](https://travis-ci.org/facebook/taste-tester.svg)](http://travis-ci.org/facebook/taste-tester)
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 futher information.
40
+ See the help for further information.
40
41
 
41
42
  ## Prerequisites
42
43
 
43
- * Taste Tester assumes that /etc/chef/client.rb and /etc/chef/client.pem on your
44
- servers is a symlink and that your real config is /etc/chef/client-prod.rb and
45
- /etc/chef/client-prod.pem, respectively.
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 arguement and anything else is just
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: `/etc/taste-tester-plugin.rb`)
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 (strng, default: `chef-client`)
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.
@@ -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(File.dirname(File.realpath(__FILE__)) + '/../lib')
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.exists?(File.expand_path(TasteTester::Config.config_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 = <<-EOF
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! See the above wiki page for more details.
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
- EOF
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.exists?(File.expand_path(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.exists?(File.expand_path(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
- '-L', '--locallink', 'Configure the local chef client without ssh.'
224
- ) do
225
- options[:locallink] = true
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
- 'Until when should the host remain in testing.' +
231
- ' Anything parsable is ok, such as "5/18 4:35" or "16/9/13".'
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
- 'How long should the host remain in testing.' +
244
- ' Takes a simple relative time string, such as "45m", "4h" or "2d".'
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 deafult is #{TasteTester::Config.repo}." +
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.exists?(File.expand_path(options[:config_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.exists?(path)
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 __FILE__ == $PROGRAM_NAME
411
- include TasteTester
469
+ if $PROGRAM_NAME == __FILE__
470
+ module TasteTester
471
+ include TasteTester
472
+ end
412
473
  end
@@ -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
- @repo = BetweenMeals::Repo.get(
50
- TasteTester::Config.repo_type,
51
- TasteTester::Config.repo,
52
- logger,
53
- )
54
- unless @repo.exists?
55
- raise "Could not open repo from #{TasteTester::Config.repo}"
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
- checks unless @skip_checks
67
-
68
- logger.info("Last commit: #{@repo.head_rev} " +
69
- "'#{@repo.last_msg.split("\n").first}'" +
70
- " by #{@repo.last_author[:email]}")
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
- @repo.head_rev)
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
- @repo.head_rev)
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
- @repo.head_rev)
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
- @repo.head_rev)
122
+ head_rev)
108
123
  end
109
124
  end
110
125
 
111
- @server.latest_uploaded_ref = @repo.head_rev
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