taste_tester 0.0.13 → 0.0.14

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: dbe88f9386cbb7c68b721ea057a30881dd98ba374a0e84528e43da739eb88d31
4
+ data.tar.gz: 642d43ce2aa006331680178e0ab15afba8d88323ab89d2e1cb0321f74c663b07
5
5
  SHA512:
6
- metadata.gz: f4da6aa14ee5b8c3f732eae1480f9793318b377f1574bf1bb12f2ec8b39c91edd8e8635d9ebd6e9042bf62a767ae55655ee447b0b47bfb83fbba4d1b13832e66
7
- data.tar.gz: 67db565bdee6e84b0ad23c73183f88af0873ac12a84ae1451c8587038222634e0bf6888ef1e9a30280631586ad7a350087ad7a2f74f249f3a324f208a3a2395a
6
+ metadata.gz: 3a256a6d7e67b346aad620a1782032394f68848ef9c085c24854b4d70ffdd718b64d4970c479b7080aba11b468423905759d0ae8f92d305aa38fe654187745b7
7
+ data.tar.gz: '048097e573d03b748a08d09438820d9ee144c90d0fb43e0375fbd91e1354215d7c82d8a0b39f97452b0752ab8dc79fe5b5863c9108727ba87fc2810271f27ee8'
data/README.md CHANGED
@@ -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,7 @@ 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
64
65
 
65
66
  ## Automatic Untesting
66
67
 
@@ -79,15 +80,15 @@ is also killing the ssh tunnel whose PID is in `/etc/chef/test_timestamp`
79
80
  ## Config file
80
81
 
81
82
  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
83
+ specify another. The config file works the same as `client.rb` does for Chef -
84
+ there are a series of keywords that take an argument and anything else is just
84
85
  standard Ruby.
85
86
 
86
87
  All command-line options are available in the config file:
87
88
  * debug (bool, default: `false`)
88
89
  * timestamp (bool, default: `false`)
89
90
  * config_file (string, default: `/etc/taste-tester-config.rb`)
90
- * plugin_path (string, default: `/etc/taste-tester-plugin.rb`)
91
+ * plugin_path (string, no default)
91
92
  * repo (string, default: `#{ENV['HOME']}/ops`)
92
93
  * testing_time (int, default: `3600`)
93
94
  * chef_client_command (strng, default: `chef-client`)
@@ -110,6 +111,10 @@ The following options are also available:
110
111
  `#{ENV['HOME']}/.chef/taste-tester-ref.txt`
111
112
  * checksum_dir - The checksum directory to put in knife.conf for users. Default:
112
113
  `#{ENV['HOME']}/.chef/checksums`
114
+ * bundle - use a single tar.gz file for transporting cookbooks, roles and
115
+ databags to clients. Experimental.
116
+ Default: false
117
+
113
118
 
114
119
  ## Plugin
115
120
 
@@ -150,3 +155,24 @@ Stuff to do after putting all hosts in test mode.
150
155
  * self.repo_checks(dryrun, repo)
151
156
 
152
157
  Additional checks you want to do on the repo as sanity checks.
158
+
159
+ **Plugin example**
160
+
161
+ This is an example `/etc/taste-tester-plugin.rb` to add a user-defined string
162
+ to `client-taste-tester.rb` on the remote system:
163
+ ```
164
+ Hooks.class_eval do
165
+ def self.test_remote_client_rb_extra_code(_hostname)
166
+ %(
167
+ # This comment gets added to client-taste-tester.rb
168
+ # This one too.
169
+ )
170
+ end
171
+ end
172
+ ```
173
+ Be sure to pass this plugin file with `-p` on the command line or set it as
174
+ `plugin_path` in your `taste-tester-config.rb` file.
175
+
176
+ ## License
177
+
178
+ See the `LICENSE` file.
data/bin/taste-tester CHANGED
@@ -28,17 +28,17 @@ 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
44
  if File.exists?(File.expand_path(TasteTester::Config.config_file))
@@ -48,7 +48,7 @@ module TasteTester
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>]
@@ -74,6 +74,13 @@ MODES:
74
74
  point some production server specified by -s to your virtual chef server for
75
75
  testing. If you have you a plugin that uses the hookpoint, it'll may amend
76
76
  your commit message to denote the server you tested.
77
+ Returns:
78
+ 0 Success.
79
+ 1 Failure, e.g. host not reachable or usage error.
80
+ 2 Partial success, mixture of success and failure due to hosts being taste
81
+ tested by other users.
82
+ 3 All hosts were not taste-tested because they are being taste-tested by
83
+ other users.
77
84
 
78
85
  upload
79
86
  Sync your local repo to your virtual Chef server (i.e. just the first step
@@ -83,6 +90,13 @@ MODES:
83
90
  cookbooks and roles. It also does a fair amount of sanity checking on
84
91
  your repo and you may specify --skip-repo-checks to bypass this.
85
92
 
93
+ impact
94
+ Examine local repo changes to determine which roles are potentially
95
+ impacted by the changes. Compares the set of modified cookbooks with the
96
+ dependency lists of each role and reports any role which depends on a
97
+ modified cookbook. It is recommended to test your changes on at least one
98
+ server of each role, potentially also on multiple platforms.
99
+
86
100
  keeptesting
87
101
  Extend the testing time on server specified by -s by 1 hour unless
88
102
  otherwise specified by -t.
@@ -111,7 +125,7 @@ MODES:
111
125
  You probably don't want this. It will restart up the chef-zero server on
112
126
  your localhost. taste-tester dynamically starts this if it's down, so there
113
127
  should be no need to do this manually.
114
- EOF
128
+ ENDOFDESCRIPTION
115
129
 
116
130
  mode = ARGV.shift unless !ARGV.empty? && ARGV[0].start_with?('-')
117
131
 
@@ -241,7 +255,7 @@ MODES:
241
255
  opts.on(
242
256
  '-t', '--testing-time TIME',
243
257
  'How long should the host remain in testing.' +
244
- ' Takes a simple relative time string, such as "45m", "4h" or "2d".'
258
+ ' Takes a simple relative time string, such as "45m", "4h" or "1d".'
245
259
  ) do |time|
246
260
  m = time.match(/^(\d+)([d|h|m]+)$/)
247
261
  if m
@@ -260,7 +274,7 @@ MODES:
260
274
 
261
275
  opts.on(
262
276
  '-r', '--repo DIR',
263
- "Custom repo location, current deafult is #{TasteTester::Config.repo}." +
277
+ "Custom repo location, current default is #{TasteTester::Config.repo}." +
264
278
  ' Works on upload and test.'
265
279
  ) do |dir|
266
280
  options[:repo] = dir
@@ -273,6 +287,16 @@ MODES:
273
287
  options[:repo_type] = type
274
288
  end
275
289
 
290
+ opts.on(
291
+ '--clowntown-no-repo', 'This option enables taste-tester to run ' +
292
+ 'without a SCM repo of any sort. This has negative implications like ' +
293
+ 'always doing a full upload. This option is here for weird corner ' +
294
+ 'cases like having to sync out the export of a repo, but is not ' +
295
+ 'generally a good idea or well supported. Use at your own risk.'
296
+ ) do
297
+ options[:no_repo] = true
298
+ end
299
+
276
300
  opts.on(
277
301
  '-R', '--roles ROLE[,ROLE]', Array,
278
302
  'Specific roles to upload. Intended mostly for debugging,' +
@@ -311,6 +335,12 @@ MODES:
311
335
  options[:ssh_command] = c
312
336
  end
313
337
 
338
+ opts.on(
339
+ '--ssh-connect-timeout TIMEOUT', 'SSH \'-o ConnectTimeout\' value'
340
+ ) do |c|
341
+ options[:ssh_connect_timeout] = c
342
+ end
343
+
314
344
  opts.on('--skip-repo-checks', 'Skip repository sanity checks') do
315
345
  options[:skip_repo_checks] = true
316
346
  end
@@ -319,6 +349,13 @@ MODES:
319
349
  options[:yes] = true
320
350
  end
321
351
 
352
+ opts.on(
353
+ '--json', 'Format output as JSON for programatic consumption.' +
354
+ ' Default to false. Works on impact.'
355
+ ) do
356
+ options[:json] = true
357
+ end
358
+
322
359
  opts.separator ''
323
360
  opts.separator 'Control local hook behavior with these options:'
324
361
 
@@ -397,6 +434,8 @@ MODES:
397
434
  TasteTester::Commands.runchef
398
435
  when :upload
399
436
  TasteTester::Commands.upload
437
+ when :impact
438
+ TasteTester::Commands.impact
400
439
  else
401
440
  logger.error("Invalid mode: #{mode}")
402
441
  puts parser
@@ -14,6 +14,8 @@
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'
@@ -44,16 +46,22 @@ module TasteTester
44
46
  :databag_dir => TasteTester::Config.databags,
45
47
  :checksum_dir => TasteTester::Config.checksum_dir,
46
48
  :role_type => TasteTester::Config.role_type,
49
+ :config => TasteTester::Config.knife_config,
47
50
  )
48
51
  @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?
52
+ if TasteTester::Config.no_repo
53
+ @repo = nil
54
+ else
55
+ @repo = BetweenMeals::Repo.get(
56
+ TasteTester::Config.repo_type,
57
+ TasteTester::Config.repo,
58
+ logger,
59
+ )
60
+ end
61
+ if @repo && !@repo.exists?
55
62
  fail "Could not open repo from #{TasteTester::Config.repo}"
56
63
  end
64
+
57
65
  @track_symlinks = TasteTester::Config.track_symlinks
58
66
  end
59
67
 
@@ -64,26 +72,30 @@ module TasteTester
64
72
  end
65
73
 
66
74
  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]}")
75
+ head_rev = nil
76
+ if @repo
77
+ head_rev = @repo.head_rev
78
+ checks unless @skip_checks
79
+ logger.info("Last commit: #{head_rev} " +
80
+ "'#{@repo.last_msg.split("\n").first}'" +
81
+ " by #{@repo.last_author[:email]}")
82
+ end
72
83
 
73
- if @force || !@server.latest_uploaded_ref
84
+ if @force || !@server.latest_uploaded_ref || !@repo
74
85
  logger.info('Full upload forced') if @force
86
+ logger.info('No repo, doing full upload') unless @repo
75
87
  unless TasteTester::Config.skip_pre_upload_hook
76
88
  TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun,
77
89
  @repo,
78
90
  nil,
79
- @repo.head_rev)
91
+ head_rev)
80
92
  end
81
93
  time(logger) { full }
82
94
  unless TasteTester::Config.skip_post_upload_hook
83
95
  TasteTester::Hooks.post_upload(TasteTester::Config.dryrun,
84
96
  @repo,
85
97
  nil,
86
- @repo.head_rev)
98
+ head_rev)
87
99
  end
88
100
  else
89
101
  # Since we also upload the index, we always need to run the
@@ -93,7 +105,7 @@ module TasteTester
93
105
  TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun,
94
106
  @repo,
95
107
  @server.latest_uploaded_ref,
96
- @repo.head_rev)
108
+ head_rev)
97
109
  end
98
110
  begin
99
111
  time(logger) { partial }
@@ -105,16 +117,99 @@ module TasteTester
105
117
  TasteTester::Hooks.post_upload(TasteTester::Config.dryrun,
106
118
  @repo,
107
119
  @server.latest_uploaded_ref,
108
- @repo.head_rev)
120
+ head_rev)
109
121
  end
110
122
  end
111
123
 
112
- @server.latest_uploaded_ref = @repo.head_rev
124
+ @server.latest_uploaded_ref = head_rev
125
+ @server.last_upload_time = Time.new.strftime('%Y-%m-%d %H:%M:%S')
113
126
  end
114
127
 
115
128
  private
116
129
 
130
+ def populate(stream, writer, path, destination)
131
+ full_path = File.join(File.join(TasteTester::Config.repo, path))
132
+ return unless File.directory?(full_path)
133
+ # everything is relative to the repo dir. chdir makes handling all the
134
+ # paths within this simpler
135
+ Dir.chdir(full_path) do
136
+ Find.find('.') do |p|
137
+ # ignore current directory. The File.directory? would also skip it,
138
+ # but we need to do it early because the string is too short for the
139
+ # next statement.
140
+ next if p == '.'
141
+ # paths are enumerated as relative to the input path '.', so we get
142
+ # './dir/file'. Stripping off the first two characters gives us a
143
+ # a cleaner 'dir/file' path.
144
+ name = File.join(destination, p[2..-1])
145
+ if File.directory?(p)
146
+ # skip it. This also handles symlinks to directories which aren't
147
+ # useful either.
148
+ elsif File.symlink?(p)
149
+ # tar handling of filenames > 100 characters gets complex. We'd use
150
+ # split_name from Minitar, but it's a private method. It's
151
+ # reasonable to assume that all symlink names in the bundle are
152
+ # less than 100 characters long. Long term, the version of minitar
153
+ # in chefdk should be upgraded.
154
+ fail 'Add support for long symlink paths' if name.size > 100
155
+ # The version of Minitar included in chefdk does not support
156
+ # symlinks directly. Therefore we use direct writes to the
157
+ # underlying stream to reproduce the symlinks
158
+ symlink = {
159
+ :name => name,
160
+ :mode => 0644,
161
+ :typeflag => '2',
162
+ :size => 0,
163
+ :linkname => File.readlink(p),
164
+ :prefix => '',
165
+ }
166
+ stream.write(Minitar::PosixHeader.new(symlink))
167
+ else
168
+ File.open(p, 'rb') do |r|
169
+ writer.add_file_simple(
170
+ name, :mode => 0644, :size => File.size(r)
171
+ ) do |d, _opts|
172
+ IO.copy_stream(r, d)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ def bundle_upload
181
+ dest = File.join(@server.bundle_dir, 'tt.tgz')
182
+ begin
183
+ Tempfile.create(['tt', '.tgz'], @server.bundle_dir) do |tempfile|
184
+ stream = Zlib::GzipWriter.new(tempfile)
185
+ Minitar::Writer.open(stream) do |writer|
186
+ TasteTester::Config.relative_cookbook_dirs.each do |cb_dir|
187
+ populate(stream, writer, cb_dir, 'cookbooks')
188
+ end
189
+ populate(
190
+ stream, writer, TasteTester::Config.relative_role_dir, 'roles'
191
+ )
192
+ populate(
193
+ stream, writer, TasteTester::Config.relative_databag_dir,
194
+ 'databag'
195
+ )
196
+ end
197
+ stream.close
198
+ File.rename(tempfile.path, dest)
199
+ end
200
+ rescue Errno::ENOENT
201
+ # Normally the temporary file is renamed to the dest name. If this
202
+ # happens, then the cleanup of of the temporary file doesn't work,
203
+ # but this is fine and expected.
204
+ nil
205
+ end
206
+ end
207
+
117
208
  def full
209
+ if TasteTester::Config.bundle
210
+ bundle_upload
211
+ return
212
+ end
118
213
  logger.warn('Doing full upload')
119
214
  @knife.cookbook_upload_all
120
215
  @knife.role_upload_all
@@ -122,6 +217,11 @@ module TasteTester
122
217
  end
123
218
 
124
219
  def partial
220
+ if TasteTester::Config.bundle
221
+ logger.info('No partial support for bundle mode, doing full upload')
222
+ bundle_upload
223
+ return
224
+ end
125
225
  logger.info('Doing differential upload from ' +
126
226
  @server.latest_uploaded_ref)
127
227
  changeset = BetweenMeals::Changeset.new(