taste_tester 0.0.12 → 0.0.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml 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