taste_tester 0.0.13 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +33 -7
- data/bin/taste-tester +50 -11
- data/lib/taste_tester/client.rb +117 -17
- data/lib/taste_tester/commands.rb +200 -12
- data/lib/taste_tester/config.rb +17 -4
- data/lib/taste_tester/exceptions.rb +7 -0
- data/lib/taste_tester/hooks.rb +15 -0
- data/lib/taste_tester/host.rb +131 -75
- data/lib/taste_tester/locallink.rb +6 -2
- data/lib/taste_tester/logging.rb +4 -3
- data/lib/taste_tester/noop.rb +6 -2
- data/lib/taste_tester/server.rb +23 -5
- data/lib/taste_tester/ssh.rb +12 -10
- data/lib/taste_tester/state.rb +22 -3
- data/lib/taste_tester/tunnel.rb +5 -5
- data/lib/taste_tester/windows.rb +1 -0
- metadata +37 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dbe88f9386cbb7c68b721ea057a30881dd98ba374a0e84528e43da739eb88d31
|
4
|
+
data.tar.gz: 642d43ce2aa006331680178e0ab15afba8d88323ab89d2e1cb0321f74c663b07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
40
|
+
See the help for further information.
|
41
41
|
|
42
42
|
## Prerequisites
|
43
43
|
|
44
|
-
* Taste Tester assumes that
|
45
|
-
servers is a symlink and that your real config is
|
46
|
-
|
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
|
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
|
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 = <<-
|
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
|
-
|
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 "
|
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
|
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
|
data/lib/taste_tester/client.rb
CHANGED
@@ -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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
"
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
120
|
+
head_rev)
|
109
121
|
end
|
110
122
|
end
|
111
123
|
|
112
|
-
@server.latest_uploaded_ref =
|
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(
|