taste_tester 0.0.13 → 0.0.18

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