taste_tester 0.0.1 → 0.0.2
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.
- data/LICENSE +201 -0
- data/README.md +133 -17
- data/bin/taste-tester +354 -0
- data/lib/taste_tester/client.rb +169 -0
- data/lib/taste_tester/commands.rb +144 -0
- data/lib/taste_tester/config.rb +92 -0
- data/lib/taste_tester/hooks.rb +52 -0
- data/lib/taste_tester/host.rb +187 -0
- data/lib/taste_tester/logging.rb +55 -0
- data/lib/taste_tester/server.rb +122 -0
- data/lib/taste_tester/ssh.rb +60 -0
- data/lib/taste_tester/state.rb +87 -0
- data/lib/taste_tester/tunnel.rb +53 -0
- data/scripts/taste-untester +77 -0
- metadata +63 -35
- checksums.yaml +0 -7
- data/.gitignore +0 -17
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -22
- data/Rakefile +0 -1
- data/lib/taste_tester/version.rb +0 -3
- data/lib/taste_tester.rb +0 -5
- data/taste_tester.gemspec +0 -23
data/bin/taste-tester
ADDED
@@ -0,0 +1,354 @@
|
|
1
|
+
#!/opt/chef/embedded/bin/ruby
|
2
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
3
|
+
# rubocop:disable UnusedBlockArgument, AlignParameters
|
4
|
+
|
5
|
+
if ENV['MY_RUBY_HOME'] && ENV['MY_RUBY_HOME'].include?('rvm')
|
6
|
+
puts 'Please disable RVM before running taste-tester'
|
7
|
+
exit(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'time'
|
12
|
+
require 'optparse'
|
13
|
+
require 'colorize'
|
14
|
+
|
15
|
+
require 'taste_tester/logging'
|
16
|
+
require 'taste_tester/config'
|
17
|
+
require 'taste_tester/commands'
|
18
|
+
require 'taste_tester/hooks'
|
19
|
+
|
20
|
+
include TasteTester::Logging
|
21
|
+
|
22
|
+
if ENV['USER'] == 'root'
|
23
|
+
logger.warn('You should not be running as root')
|
24
|
+
exit(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Command line parsing and param descriptions
|
28
|
+
module TasteTester
|
29
|
+
verify = 'Verify your changes were actually applied as intended!'.red
|
30
|
+
cmd = TasteTester::Config.chef_client_command
|
31
|
+
description = <<-EOF
|
32
|
+
Welcome to taste-tester!
|
33
|
+
|
34
|
+
Usage: taste-tester <mode> [<options>]
|
35
|
+
|
36
|
+
TLDR; Most common usage is:
|
37
|
+
vi cookbooks/... # Make your changes and commit locally
|
38
|
+
taste-tester test -s [host] # Put host in test mode
|
39
|
+
ssh root@[host] # Log on host
|
40
|
+
#{format('%-28s', " #{cmd}")} # Run chef and watch it break
|
41
|
+
vi cookbooks/... # Fix your cookbooks
|
42
|
+
taste-tester upload # Upload the diff
|
43
|
+
ssh root@[host] # Log on host
|
44
|
+
#{format('%-28s', " #{cmd}")} # Run chef and watch it succeed
|
45
|
+
<#{verify}>
|
46
|
+
taste-tester untest [host] # Put host back in production
|
47
|
+
# (optional - will revert itself after 1 hour)
|
48
|
+
|
49
|
+
And you're done! See the above wiki page for more details.
|
50
|
+
|
51
|
+
MODES:
|
52
|
+
test
|
53
|
+
Sync your local repo to your virtual Chef server (same as 'upload'), and
|
54
|
+
point some production server specified by -s to your virtual chef server for
|
55
|
+
testing. If you have you a plugin that uses the hookpoint, it'll may amend
|
56
|
+
your commit message to denote the server you tested.
|
57
|
+
|
58
|
+
upload
|
59
|
+
Sync your local repo to your virtual Chef server (i.e. just the first step
|
60
|
+
of 'test'). By defailt, it intelligently uploads whatever has changed since
|
61
|
+
the last time you ran upload (or test), but tracking git changes (even
|
62
|
+
across branch changes). You may specify -f to force a full upload of all
|
63
|
+
cookbooks and roles. It also does a fair amount of sanity checking on
|
64
|
+
your repo and you may specify --skip-repo-checks to bypass this.
|
65
|
+
|
66
|
+
keeptesting
|
67
|
+
Extend the testing time on server specified by -s by 1 hour unless
|
68
|
+
otherwise specified by -t.
|
69
|
+
|
70
|
+
untest
|
71
|
+
Return the server specified in -s to production.
|
72
|
+
|
73
|
+
status
|
74
|
+
Print out the state of the world.
|
75
|
+
|
76
|
+
run
|
77
|
+
Run #{cmd} on the machine specified by '-s' over SSH and print the output.
|
78
|
+
NOTE!! This is #{'NOT'.red} a sufficient test, you must log onto the remote
|
79
|
+
machine and verify the changes you are trying to make are actually present.
|
80
|
+
|
81
|
+
stop
|
82
|
+
You probably don't want this. It will shutdown the chef-zero server on
|
83
|
+
your localhost.
|
84
|
+
|
85
|
+
start
|
86
|
+
You probably don't want this. It will start up the chef-zero server on
|
87
|
+
your localhost. taste-tester dynamically starts this if it's down, so there
|
88
|
+
should be no need to do this manually.
|
89
|
+
|
90
|
+
restart
|
91
|
+
You probably don't want this. It will restart up the chef-zero server on
|
92
|
+
your localhost. taste-tester dynamically starts this if it's down, so there
|
93
|
+
should be no need to do this manually.
|
94
|
+
EOF
|
95
|
+
|
96
|
+
mode = ARGV.shift unless ARGV.size > 0 && ARGV[0].start_with?('-')
|
97
|
+
|
98
|
+
unless mode
|
99
|
+
mode = 'help'
|
100
|
+
puts "ERROR: No mode specified\n\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
options = { :config_file => TasteTester::Config.config_file }
|
104
|
+
parser = OptionParser.new do |opts|
|
105
|
+
opts.banner = description
|
106
|
+
|
107
|
+
opts.separator ''
|
108
|
+
opts.separator 'Global options:'.upcase
|
109
|
+
|
110
|
+
opts.on('-c', '--config FILE', 'Config file') do |file|
|
111
|
+
unless File.exists?(File.expand_path(file))
|
112
|
+
logger.error("Sorry, cannot find #{file}")
|
113
|
+
exit(1)
|
114
|
+
end
|
115
|
+
options[:config_file] = file
|
116
|
+
end
|
117
|
+
|
118
|
+
opts.on('-v', '--verbose', 'Verbosity, provide twice for all debug') do
|
119
|
+
# If -vv is supplied this block is executed twice
|
120
|
+
if options[:verbosity]
|
121
|
+
options[:verbosity] = Logger::DEBUG
|
122
|
+
else
|
123
|
+
options[:verbosity] = Logger::INFO
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
opts.on('-p', '--plugin-path FILE', String, 'Plugin file') do |file|
|
128
|
+
unless File.exists?(File.expand_path(file))
|
129
|
+
logger.error("Sorry, cannot find #{file}")
|
130
|
+
exit(1)
|
131
|
+
end
|
132
|
+
options[:plugin_path] = file
|
133
|
+
end
|
134
|
+
|
135
|
+
opts.on('-h', '--help', 'Print help message.') do
|
136
|
+
print opts
|
137
|
+
exit
|
138
|
+
end
|
139
|
+
|
140
|
+
opts.on('-T', '--timestamp', 'Time-stamped log style output') do
|
141
|
+
options[:timestamp] = true
|
142
|
+
end
|
143
|
+
|
144
|
+
opts.separator ''
|
145
|
+
opts.separator 'Sub-command options:'.upcase
|
146
|
+
|
147
|
+
opts.on(
|
148
|
+
'-C', '--cookbooks COOKBOOK[,COOKBOOK]', Array,
|
149
|
+
'Specific cookbooks to upload. Intended mostly for debugging,' +
|
150
|
+
' not recommended. Works on upload and test. Not yet implemented.'
|
151
|
+
) do |cbs|
|
152
|
+
options[:cookbooks] = cbs
|
153
|
+
end
|
154
|
+
|
155
|
+
opts.on(
|
156
|
+
'-D', '--databag DATABAG/ITEM[,DATABAG/ITEM]', Array,
|
157
|
+
'Specific cookbooks to upload. Intended mostly for debugging,' +
|
158
|
+
' not recommended. Works on upload and test. Not yet implemented.'
|
159
|
+
) do |cbs|
|
160
|
+
options[:databags] = cbs
|
161
|
+
end
|
162
|
+
|
163
|
+
opts.on(
|
164
|
+
'-f', '--force-upload',
|
165
|
+
'Force upload everything. Works on upload and test.'
|
166
|
+
) do
|
167
|
+
options[:force_upload] = true
|
168
|
+
end
|
169
|
+
|
170
|
+
opts.on(
|
171
|
+
'--chef-port-range PORT1,PORT2', Array,
|
172
|
+
'Port range for chef-zero'
|
173
|
+
) do |ports|
|
174
|
+
unless ports.count == 2
|
175
|
+
logger.error("Invalid port range: #{ports}")
|
176
|
+
exit 1
|
177
|
+
end
|
178
|
+
options[:chef_port_range] = ports
|
179
|
+
end
|
180
|
+
|
181
|
+
opts.on(
|
182
|
+
'--tunnel-port PORT', 'Port for ssh tunnel'
|
183
|
+
) do |port|
|
184
|
+
options[:user_tunnel_port] = port
|
185
|
+
end
|
186
|
+
|
187
|
+
opts.on(
|
188
|
+
'-l', '--linkonly', 'Only setup the remote server, skip uploading.'
|
189
|
+
) do
|
190
|
+
options[:linkonly] = true
|
191
|
+
end
|
192
|
+
|
193
|
+
opts.on(
|
194
|
+
'-t', '--testing-timestamp TIME',
|
195
|
+
'Until when should the host remain in testing.' +
|
196
|
+
' Anything parsable is ok, such as "5/18 4:35" or "16/9/13".'
|
197
|
+
) do |time|
|
198
|
+
begin
|
199
|
+
options[:testing_until] = Time.parse(time)
|
200
|
+
rescue
|
201
|
+
logger.error("Invalid date: #{time}")
|
202
|
+
exit 1
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
opts.on(
|
207
|
+
'-t', '--testing-time TIME',
|
208
|
+
'How long should the host remain in testing.' +
|
209
|
+
' Takes a simple relative time string, such as "45m", "4h" or "2d".'
|
210
|
+
) do |time|
|
211
|
+
m = time.match(/^(\d+)([d|h|m]+)$/)
|
212
|
+
if m
|
213
|
+
exp = {
|
214
|
+
:d => 60 * 60 * 24,
|
215
|
+
:h => 60 * 60,
|
216
|
+
:m => 60,
|
217
|
+
}[m[2].to_sym]
|
218
|
+
delta = m[1].to_i * exp
|
219
|
+
options[:testing_until] = Time.now + delta.to_i
|
220
|
+
else
|
221
|
+
logger.error("Invalid testing-time: #{time}")
|
222
|
+
exit 1
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
opts.on(
|
227
|
+
'-r', '--repo DIR',
|
228
|
+
"Custom repo location, current deafult is #{TasteTester::Config.repo}." +
|
229
|
+
' Works on upload and test.'
|
230
|
+
) do |dir|
|
231
|
+
options[:repo] = dir
|
232
|
+
end
|
233
|
+
|
234
|
+
opts.on(
|
235
|
+
'-R', '--roles ROLE[,ROLE]', Array,
|
236
|
+
'Specific roles to upload. Intended mostly for debugging,' +
|
237
|
+
' not recommended. Works on upload and test. Not yet implemented.'
|
238
|
+
) do |roles|
|
239
|
+
options[:roles] = roles
|
240
|
+
end
|
241
|
+
|
242
|
+
opts.on('--really', 'Really do link-only. DANGEROUS!') do |r|
|
243
|
+
options[:really] = r
|
244
|
+
end
|
245
|
+
|
246
|
+
opts.on(
|
247
|
+
'-s', '--servers SERVER[,SERVER]', Array,
|
248
|
+
'Server to test/untest/keeptesting.'
|
249
|
+
) do |s|
|
250
|
+
options[:servers] = s
|
251
|
+
end
|
252
|
+
|
253
|
+
opts.on(
|
254
|
+
'--user USER', 'Custom username for SSH, defaults to "root".' +
|
255
|
+
' If custom user is specified, we will use sudo for all commands.'
|
256
|
+
) do |user|
|
257
|
+
options[:user] = user
|
258
|
+
end
|
259
|
+
|
260
|
+
opts.on(
|
261
|
+
'-S', '--[no-]use-ssh-tunnels', 'Protect Chef traffic with SSH tunnels'
|
262
|
+
) do |s|
|
263
|
+
options[:use_ssh_tunnels] = s
|
264
|
+
end
|
265
|
+
|
266
|
+
opts.on('--skip-repo-checks', 'Skip repository sanity checks') do
|
267
|
+
options[:skip_repo_checks] = true
|
268
|
+
end
|
269
|
+
|
270
|
+
opts.on('-y', '--yes', 'Do not prompt before testing.') do
|
271
|
+
options[:yes] = true
|
272
|
+
end
|
273
|
+
|
274
|
+
opts.separator ''
|
275
|
+
opts.separator 'Control local hook behavior with these options:'
|
276
|
+
|
277
|
+
opts.on(
|
278
|
+
'--skip-pre-upload-hook', 'Skip pre-upload hook. Works on upload, test.'
|
279
|
+
) do
|
280
|
+
options[:skip_pre_upload_hook] = true
|
281
|
+
end
|
282
|
+
|
283
|
+
opts.on(
|
284
|
+
'--skip-post-upload-hook', 'Skip post-upload hook. Works on upload, test.'
|
285
|
+
) do
|
286
|
+
options[:skip_post_upload_hook] = true
|
287
|
+
end
|
288
|
+
|
289
|
+
opts.on(
|
290
|
+
'--skip-pre-test-hook', 'Skip pre-test hook. Works on test.'
|
291
|
+
) do
|
292
|
+
options[:skip_pre_test_hook] = true
|
293
|
+
end
|
294
|
+
|
295
|
+
opts.on(
|
296
|
+
'--skip-post-test-hook', 'Skip post-test hook. Works on test.'
|
297
|
+
) do
|
298
|
+
options[:skip_post_test_hook] = true
|
299
|
+
end
|
300
|
+
|
301
|
+
opts.on(
|
302
|
+
'--skip-repo-checks-hook', 'Skip repo-checks hook. Works on upload, test.'
|
303
|
+
) do
|
304
|
+
options[:skip_post_test_hook] = true
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
if mode == 'help'
|
309
|
+
puts parser
|
310
|
+
exit
|
311
|
+
end
|
312
|
+
|
313
|
+
parser.parse!
|
314
|
+
|
315
|
+
if File.exists?(File.expand_path(options[:config_file]))
|
316
|
+
TasteTester::Config.from_file(File.expand_path(options[:config_file]))
|
317
|
+
end
|
318
|
+
TasteTester::Config.merge!(options)
|
319
|
+
TasteTester::Logging.verbosity = TasteTester::Config.verbosity
|
320
|
+
TasteTester::Logging.use_log_formatter = TasteTester::Config.timestamp
|
321
|
+
|
322
|
+
if File.exists?(File.expand_path(TasteTester::Config.plugin_path))
|
323
|
+
TasteTester::Hooks.get(File.expand_path(TasteTester::Config[:plugin_path]))
|
324
|
+
end
|
325
|
+
|
326
|
+
case mode.to_sym
|
327
|
+
when :start
|
328
|
+
TasteTester::Commands.start
|
329
|
+
when :stop
|
330
|
+
TasteTester::Commands.stop
|
331
|
+
when :restart
|
332
|
+
TasteTester::Commands.restart
|
333
|
+
when :keeptesting
|
334
|
+
TasteTester::Commands.keeptesting
|
335
|
+
when :status
|
336
|
+
TasteTester::Commands.status
|
337
|
+
when :test
|
338
|
+
TasteTester::Commands.test
|
339
|
+
when :untest
|
340
|
+
TasteTester::Commands.untest
|
341
|
+
when :run
|
342
|
+
TasteTester::Commands.runchef
|
343
|
+
when :upload
|
344
|
+
TasteTester::Commands.upload
|
345
|
+
else
|
346
|
+
logger.error("Invalid mode: #{mode}")
|
347
|
+
puts parser
|
348
|
+
exit(1)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
if __FILE__ == $PROGRAM_NAME
|
353
|
+
include TasteTester
|
354
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
|
3
|
+
require 'taste_tester/logging'
|
4
|
+
require 'between_meals/repo'
|
5
|
+
require 'between_meals/knife'
|
6
|
+
require 'between_meals/changeset'
|
7
|
+
|
8
|
+
module TasteTester
|
9
|
+
# Client side upload functionality
|
10
|
+
# Ties together Repo/Changeset diff logic
|
11
|
+
# and Server/Knife uploads
|
12
|
+
class Client
|
13
|
+
include TasteTester::Logging
|
14
|
+
include BetweenMeals::Util
|
15
|
+
|
16
|
+
attr_accessor :force, :skip_checks
|
17
|
+
|
18
|
+
def initialize(server)
|
19
|
+
@server = server
|
20
|
+
@knife = BetweenMeals::Knife.new(
|
21
|
+
:logger => logger,
|
22
|
+
:user => @server.user,
|
23
|
+
:host => @server.host,
|
24
|
+
:port => @server.port,
|
25
|
+
:role_dir => TasteTester::Config.roles,
|
26
|
+
:cookbook_dirs => TasteTester::Config.cookbooks,
|
27
|
+
:databag_dir => TasteTester::Config.databags,
|
28
|
+
:checksum_dir => TasteTester::Config.checksum_dir,
|
29
|
+
)
|
30
|
+
@knife.write_user_config
|
31
|
+
@repo = BetweenMeals::Repo.get(TasteTester::Config.repo_type,
|
32
|
+
TasteTester::Config.repo, logger)
|
33
|
+
fail 'Could not read repo' unless @repo
|
34
|
+
end
|
35
|
+
|
36
|
+
def checks
|
37
|
+
unless @skip_checks
|
38
|
+
TasteTester::Hooks.repo_checks(TasteTester::Config.dryrun, @repo)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def upload
|
43
|
+
checks unless @skip_checks
|
44
|
+
|
45
|
+
logger.warn("Using #{TasteTester::Config.repo}")
|
46
|
+
logger.info("Last commit: #{@repo.head_rev} " +
|
47
|
+
"'#{@repo.last_msg.split("\n").first}'" +
|
48
|
+
" by #{@repo.last_author[:email]}")
|
49
|
+
|
50
|
+
if @force || !@server.latest_uploaded_ref
|
51
|
+
logger.info('Full upload forced') if @force
|
52
|
+
unless TasteTester::Config.skip_pre_upload_hook
|
53
|
+
TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun,
|
54
|
+
@repo,
|
55
|
+
nil,
|
56
|
+
@repo.head_rev)
|
57
|
+
end
|
58
|
+
time(logger) { full }
|
59
|
+
unless TasteTester::Config.skip_post_upload_hook
|
60
|
+
TasteTester::Hooks.post_upload(TasteTester::Config.dryrun,
|
61
|
+
@repo,
|
62
|
+
nil,
|
63
|
+
@repo.head_rev)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
# Since we also upload the index, we always need to run the
|
67
|
+
# diff even if the version we're on is the same as the last
|
68
|
+
# revision
|
69
|
+
unless TasteTester::Config.skip_pre_upload_hook
|
70
|
+
TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun,
|
71
|
+
@repo,
|
72
|
+
@server.latest_uploaded_ref,
|
73
|
+
@repo.head_rev)
|
74
|
+
end
|
75
|
+
begin
|
76
|
+
time(logger) { partial }
|
77
|
+
rescue BetweenMeals::Changeset::ReferenceError
|
78
|
+
logger.warn('Something changed with your repo, doing full upload')
|
79
|
+
time(logger) { full }
|
80
|
+
end
|
81
|
+
unless TasteTester::Config.skip_post_upload_hook
|
82
|
+
TasteTester::Hooks.post_upload(TasteTester::Config.dryrun,
|
83
|
+
@repo,
|
84
|
+
@server.latest_uploaded_ref,
|
85
|
+
@repo.head_rev)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@server.latest_uploaded_ref = @repo.head_rev
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def full
|
95
|
+
logger.warn('Doing full upload')
|
96
|
+
@knife.cookbook_upload_all
|
97
|
+
@knife.role_upload_all
|
98
|
+
@knife.databag_upload_all
|
99
|
+
end
|
100
|
+
|
101
|
+
def partial
|
102
|
+
logger.info('Doing differential upload from ' +
|
103
|
+
@server.latest_uploaded_ref)
|
104
|
+
changeset = BetweenMeals::Changeset.new(
|
105
|
+
logger,
|
106
|
+
@repo,
|
107
|
+
@server.latest_uploaded_ref,
|
108
|
+
nil,
|
109
|
+
{
|
110
|
+
:cookbook_dirs =>
|
111
|
+
TasteTester::Config.relative_cookbook_dirs,
|
112
|
+
:role_dir =>
|
113
|
+
TasteTester::Config.relative_role_dir,
|
114
|
+
:databag_dir =>
|
115
|
+
TasteTester::Config.relative_databag_dir,
|
116
|
+
},
|
117
|
+
)
|
118
|
+
|
119
|
+
cbs = changeset.cookbooks
|
120
|
+
deleted_cookbooks = cbs.select { |x| x.status == :deleted }
|
121
|
+
modified_cookbooks = cbs.select { |x| x.status == :modified }
|
122
|
+
roles = changeset.roles
|
123
|
+
deleted_roles = roles.select { |x| x.status == :deleted }
|
124
|
+
modified_roles = roles.select { |x| x.status == :modified }
|
125
|
+
databags = changeset.databags
|
126
|
+
deleted_databags = databags.select { |x| x.status == :deleted }
|
127
|
+
modified_databags = databags.select { |x| x.status == :modified }
|
128
|
+
|
129
|
+
didsomething = false
|
130
|
+
unless deleted_cookbooks.empty?
|
131
|
+
didsomething = true
|
132
|
+
logger.warn("Deleting cookbooks: [#{deleted_cookbooks.join(' ')}]")
|
133
|
+
@knife.cookbook_delete(deleted_cookbooks)
|
134
|
+
end
|
135
|
+
|
136
|
+
unless modified_cookbooks.empty?
|
137
|
+
didsomething = true
|
138
|
+
logger.warn("Uploading cookbooks: [#{modified_cookbooks.join(' ')}]")
|
139
|
+
@knife.cookbook_upload(modified_cookbooks)
|
140
|
+
end
|
141
|
+
|
142
|
+
unless deleted_roles.empty?
|
143
|
+
didsomething = true
|
144
|
+
logger.warn("Deleting roles: [#{deleted_roles.join(' ')}]")
|
145
|
+
@knife.role_delete(deleted_roles)
|
146
|
+
end
|
147
|
+
|
148
|
+
unless modified_roles.empty?
|
149
|
+
didsomething = true
|
150
|
+
logger.warn("Uploading roles: [#{modified_roles.join(' ')}]")
|
151
|
+
@knife.role_upload(modified_roles)
|
152
|
+
end
|
153
|
+
|
154
|
+
unless deleted_databags.empty?
|
155
|
+
didsomething = true
|
156
|
+
logger.warn("Deleting databags: [#{deleted_databags.join(' ')}]")
|
157
|
+
@knife.databag_delete(deleted_databags)
|
158
|
+
end
|
159
|
+
|
160
|
+
unless modified_databags.empty?
|
161
|
+
didsomething = true
|
162
|
+
logger.warn("Uploading databags: [#{modified_databags.join(' ')}]")
|
163
|
+
@knife.databag_upload(modified_databags)
|
164
|
+
end
|
165
|
+
|
166
|
+
logger.warn('Nothing to upload!') unless didsomething
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
|
2
|
+
# rubocop:disable UnusedBlockArgument, UnusedMethodArgument
|
3
|
+
|
4
|
+
require 'taste_tester/server'
|
5
|
+
require 'taste_tester/host'
|
6
|
+
require 'taste_tester/config'
|
7
|
+
require 'taste_tester/client'
|
8
|
+
require 'taste_tester/logging'
|
9
|
+
|
10
|
+
module TasteTester
|
11
|
+
# Functionality dispatch
|
12
|
+
module Commands
|
13
|
+
extend TasteTester::Logging
|
14
|
+
|
15
|
+
def self.start
|
16
|
+
server = TasteTester::Server.new
|
17
|
+
return if TasteTester::Server.running?
|
18
|
+
server.start
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.restart
|
22
|
+
server = TasteTester::Server.new
|
23
|
+
server.stop if TasteTester::Server.running?
|
24
|
+
server.start
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.stop
|
28
|
+
server = TasteTester::Server.new
|
29
|
+
server.stop
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.status
|
33
|
+
server = TasteTester::Server.new
|
34
|
+
if TasteTester::Server.running?
|
35
|
+
logger.warn("Local taste-tester server running on port #{server.port}")
|
36
|
+
if server.latest_uploaded_ref
|
37
|
+
logger.warn('Latest uploaded revision is ' +
|
38
|
+
server.latest_uploaded_ref)
|
39
|
+
else
|
40
|
+
logger.warn('No cookbooks/roles uploads found')
|
41
|
+
end
|
42
|
+
else
|
43
|
+
logger.warn('Local taste-tester server not running')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.test
|
48
|
+
hosts = TasteTester::Config.servers
|
49
|
+
unless hosts
|
50
|
+
logger.warn('You must provide a hostname')
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
unless TasteTester::Config.yes
|
54
|
+
printf("Set #{TasteTester::Config.servers} to test mode? [y/N] ")
|
55
|
+
ans = STDIN.gets.chomp
|
56
|
+
exit(1) unless ans =~ /^[yY](es)?$/
|
57
|
+
end
|
58
|
+
if TasteTester::Config.linkonly && TasteTester::Config.really
|
59
|
+
logger.warn('Skipping upload at user request... potentially dangerous!')
|
60
|
+
else
|
61
|
+
if TasteTester::Config.linkonly
|
62
|
+
logger.warn('Ignoring --linkonly because --really not set')
|
63
|
+
end
|
64
|
+
upload
|
65
|
+
end
|
66
|
+
server = TasteTester::Server.new
|
67
|
+
repo = BetweenMeals::Repo.get(TasteTester::Config.repo_type,
|
68
|
+
TasteTester::Config.repo, logger)
|
69
|
+
unless TasteTester::Config.skip_pre_test_hook
|
70
|
+
TasteTester::Hooks.pre_test(TasteTester::Config.dryrun, repo, hosts)
|
71
|
+
end
|
72
|
+
tested_hosts = []
|
73
|
+
hosts.each do |hostname|
|
74
|
+
host = TasteTester::Host.new(hostname, server)
|
75
|
+
if host.in_test?
|
76
|
+
username = host.who_is_testing
|
77
|
+
logger.error("User #{username} is already testing on #{hostname}")
|
78
|
+
else
|
79
|
+
host.test
|
80
|
+
tested_hosts << hostname
|
81
|
+
end
|
82
|
+
end
|
83
|
+
unless TasteTester::Config.skip_post_test_hook
|
84
|
+
TasteTester::Hooks.post_test(TasteTester::Config.dryrun, repo,
|
85
|
+
tested_hosts)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.untest
|
90
|
+
hosts = TasteTester::Config.servers
|
91
|
+
unless hosts
|
92
|
+
logger.warn('You must provide a hostname')
|
93
|
+
exit(1)
|
94
|
+
end
|
95
|
+
server = TasteTester::Server.new
|
96
|
+
hosts.each do |hostname|
|
97
|
+
host = TasteTester::Host.new(hostname, server)
|
98
|
+
host.untest
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.runchef
|
103
|
+
hosts = TasteTester::Config.servers
|
104
|
+
unless hosts
|
105
|
+
logger.warn('You must provide a hostname')
|
106
|
+
exit(1)
|
107
|
+
end
|
108
|
+
server = TasteTester::Server.new
|
109
|
+
hosts.each do |hostname|
|
110
|
+
host = TasteTester::Host.new(hostname, server)
|
111
|
+
host.run
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.keeptesting
|
116
|
+
hosts = TasteTester::Config.servers
|
117
|
+
unless hosts
|
118
|
+
logger.warn('You must provide a hostname')
|
119
|
+
exit(1)
|
120
|
+
end
|
121
|
+
server = TasteTester::Server.new
|
122
|
+
hosts.each do |hostname|
|
123
|
+
host = TasteTester::Host.new(hostname, server)
|
124
|
+
host.keeptesting
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.upload
|
129
|
+
server = TasteTester::Server.new
|
130
|
+
# On a fore-upload rather than try to clean up whatever's on the server
|
131
|
+
# we'll restart chef-zero which will clear everything and do a full
|
132
|
+
# upload
|
133
|
+
if TasteTester::Config.force_upload
|
134
|
+
server.restart
|
135
|
+
else
|
136
|
+
server.start unless TasteTester::Server.running?
|
137
|
+
end
|
138
|
+
client = TasteTester::Client.new(server)
|
139
|
+
client.skip_checks = true if TasteTester::Config.skip_checks
|
140
|
+
client.force = true if TasteTester::Config.force_upload
|
141
|
+
client.upload
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|