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