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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 13e9b1fb00c7044fe801ccb1158bbaacbbe30dd9
4
- data.tar.gz: 3fcd362ea2bbdfed358847a485a695c9669d44df
2
+ SHA256:
3
+ metadata.gz: e8206a39afa26db4e27600fa362c26d33d16fcefe6a5e79a4869fe339b9e1e18
4
+ data.tar.gz: 2eb1a8bdfd58d5713dc1f5197da4b10cd0167542fefa641bfe3d6863c252fc58
5
5
  SHA512:
6
- metadata.gz: f4da6aa14ee5b8c3f732eae1480f9793318b377f1574bf1bb12f2ec8b39c91edd8e8635d9ebd6e9042bf62a767ae55655ee447b0b47bfb83fbba4d1b13832e66
7
- data.tar.gz: 67db565bdee6e84b0ad23c73183f88af0873ac12a84ae1451c8587038222634e0bf6888ef1e9a30280631586ad7a350087ad7a2f74f249f3a324f208a3a2395a
6
+ metadata.gz: 40e15c8cf0858f82e65e0b94e4bdcfa44223b0c891ef9242aa0cbb264c36c6931e033b9b10cf70fcc68b74be4ff4628042872d78466f761bfc367de69f116897
7
+ data.tar.gz: 28dbed4002b508d4eb7222b3007bc6580dd4c881d4894e456a849bc882339d6b97ccec6858682c5bc71b0dbf8aa03d377de47eefa85e1c5cff09601884ca19bf
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Taste Tester
2
2
 
3
- [![TravisCI](https://travis-ci.org/facebook/taste-tester.svg)](http://travis-ci.org/facebook/taste-tester)
4
- [![CircleCI](https://circleci.com/gh/facebook/taste-tester.svg?style=svg)](https://circleci.com/gh/facebook/taste-tester)
3
+ ![Continuous Integration](https://github.com/facebook/taste-tester/workflows/Continuous%20Integration/badge.svg?event=push)
5
4
 
6
5
  ## Intro
7
6
  Ohai!
@@ -25,6 +24,7 @@ Typical usage is:
25
24
 
26
25
  ```text
27
26
  vi cookbooks/... # Make your changes and commit locally
27
+ taste-tester impact # Check where your changes are used
28
28
  taste-tester test -s [host] # Put host in Taste Tester mode
29
29
  ssh root@[host] # Log in to host
30
30
  # Run chef and watch it break
@@ -37,13 +37,13 @@ taste-tester untest [host] # Put host back in production
37
37
  # (optional - will revert itself after 1 hour)
38
38
  ```
39
39
 
40
- See the help for futher information.
40
+ See the help for further information.
41
41
 
42
42
  ## Prerequisites
43
43
 
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.
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 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
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: `/etc/taste-tester-plugin.rb`)
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 (strng, default: `chef-client`)
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.
@@ -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.exists?(File.expand_path(TasteTester::Config.config_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 = <<-EOF
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! 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.
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
- EOF
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.exists?(File.expand_path(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.exists?(File.expand_path(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 "2d".'
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
@@ -408,5 +467,7 @@ MODES:
408
467
  end
409
468
 
410
469
  if $PROGRAM_NAME == __FILE__
411
- include TasteTester
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,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
- @repo = BetweenMeals::Repo.get(
50
- TasteTester::Config.repo_type,
51
- TasteTester::Config.repo,
52
- logger,
53
- )
54
- unless @repo.exists?
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
- checks unless @skip_checks
68
-
69
- logger.info("Last commit: #{@repo.head_rev} " +
70
- "'#{@repo.last_msg.split("\n").first}'" +
71
- " 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
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
- @repo.head_rev)
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
- @repo.head_rev)
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
- @repo.head_rev)
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
- @repo.head_rev)
122
+ head_rev)
109
123
  end
110
124
  end
111
125
 
112
- @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')
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(