1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 import fcntl
29 import os
30 import sys
31 import subprocess
32
33 import ansible.runner
34 import optparse
35 from operator import methodcaller
36 import pipes
37 import time
38 import socket
39 import traceback
40 import urllib
41
42
43 mockchain = '/usr/bin/mockchain'
44
45 rsync = '/usr/bin/rsync'
46
47 DEF_REMOTE_BASEDIR = '/var/tmp'
48 DEF_TIMEOUT = 3600
49 DEF_REPOS = []
50 DEF_CHROOT = None
51 DEF_USER = 'mockbuilder'
52 DEF_DESTDIR = os.getcwd()
53 DEF_MACROS = {}
54 DEF_BUILDROOT_PKGS = ''
57 '''Optparser which sorts the options by opt before outputting --help'''
61
64 if os.path.exists(path + '/repodata/repomd.xml'):
65 comm = ['/usr/bin/createrepo_c', '--database', '--ignore-lock', '--update', path]
66 else:
67 comm = ['/usr/bin/createrepo_c', '--database', '--ignore-lock', path]
68 cmd = subprocess.Popen(comm,
69 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
70 out, err = cmd.communicate()
71 return cmd.returncode, out, err
72
74 lst = []
75 f = open(fn, 'r')
76 for line in f.readlines():
77 line = line.replace('\n','')
78 line = line.strip()
79 if line.startswith('#'):
80 continue
81 lst.append(line)
82
83 return lst
84
85 -def log(lf, msg, quiet=None):
86 if lf:
87 now = time.time()
88 try:
89 with open(lf, 'a') as lfh:
90 fcntl.flock(lfh, fcntl.LOCK_EX)
91 lfh.write(str(now) + ':' + msg + '\n')
92 fcntl.flock(lfh, fcntl.LOCK_UN)
93 except (IOError, OSError), e:
94 sys.stderr.write('Could not write to logfile %s - %s\n' % (lf, str(e)))
95 if not quiet:
96 print msg
97
99 if hostname in results['dark']:
100 return results['dark'][hostname]
101 if hostname in results['contacted']:
102 return results['contacted'][hostname]
103
104 return {}
105
107 ans_conn = ansible.runner.Runner(remote_user=username,
108 host_list=hostname + ',', pattern=hostname, forks=1, transport='ssh',
109 timeout=timeout)
110 return ans_conn
111
112 -def check_for_ans_error(results, hostname, err_codes=[], success_codes=[0],
113 return_on_error=['stdout', 'stderr']):
114 """ returns True or False + dict
115 dict includes 'msg'
116 may include 'rc', 'stderr', 'stdout' and any other
117 requested result codes
118 """
119 err_results = {}
120
121 if 'dark' in results and hostname in results['dark']:
122 err_results['msg'] = "Error: Could not contact/connect to %s." % hostname
123 return (True, err_results)
124
125 error = False
126
127 if err_codes or success_codes:
128 if hostname in results['contacted']:
129 if 'rc' in results['contacted'][hostname]:
130 rc = int(results['contacted'][hostname]['rc'])
131 err_results['rc'] = rc
132
133 if rc in err_codes:
134 error = True
135 err_results['msg'] = 'rc %s matched err_codes' % rc
136 elif rc not in success_codes:
137 error = True
138 err_results['msg'] = 'rc %s not in success_codes' % rc
139 elif 'failed' in results['contacted'][hostname] and results['contacted'][hostname]['failed']:
140 error = True
141 err_results['msg'] = 'results included failed as true'
142
143 if error:
144 for item in return_on_error:
145 if item in results['contacted'][hostname]:
146 err_results[item] = results['contacted'][hostname][item]
147
148 return error, err_results
149
159
162
165 self.quiet = kwargs.get('quiet', False)
166 self.logfn = kwargs.get('logfn', None)
167
170
173
176
179
181 self.log("Error: %s" % msg)
182
183 - def log(self, msg):
184 if not self.quiet:
185 print msg
186
190
192 msg = "Start build: %s" % pkg
193 self.log(msg)
194
195
197 msg = "End Build: %s" % pkg
198 self.log(msg)
199
201 msg = "Start retrieve results for: %s" % pkg
202 self.log(msg)
203
205 msg = "End retrieve results for: %s" % pkg
206 self.log(msg)
207
209 self.log("Error: %s" % msg)
210
211 - def log(self, msg):
212 log(self.logfn, msg, self.quiet)
213
215 - def __init__(self, hostname, username, timeout, mockremote, buildroot_pkgs):
233
234 @property
236 return self.tempdir + '/build/'
237
238 @property
240 if self.mockremote.remote_tempdir:
241 return self.mockremote.remote_tempdir
242
243 if self._tempdir:
244 return self._tempdir
245
246 cmd = '/bin/mktemp -d %s/%s-XXXXX' % (self.mockremote.remote_basedir, 'mockremote')
247 self.conn.module_name = "shell"
248 self.conn.module_args = str(cmd)
249 results = self.conn.run()
250 tempdir = None
251 for hn, resdict in results['contacted'].items():
252 tempdir = resdict['stdout']
253
254
255 if not tempdir:
256 raise BuilderError('Could not make tmpdir on %s' % self.hostname)
257
258 cmd = "/bin/chmod 755 %s" % tempdir
259 self.conn.module_args = str(cmd)
260 self.conn.run()
261 self._tempdir = tempdir
262
263 return self._tempdir
264
265 @tempdir.setter
267 self._tempdir = value
268
270
271
272 s_pkg = os.path.basename(pkg)
273 pdn = s_pkg.replace('.src.rpm', '')
274 remote_pkg_dir = os.path.normpath(self.remote_build_dir + '/results/' + self.chroot + '/' + pdn)
275 return remote_pkg_dir
276
278 """ modify mock config for current chroot
279
280 packages in buildroot_pkgs are added to minimal buildroot """
281 if "'%s '" % self.buildroot_pkgs != pipes.quote(str(self.buildroot_pkgs)+' '):
282
283 raise BuilderError("Do not try this kind of attack on me")
284 self.root_conn.module_name = "lineinfile"
285 if (self.chroot == 'epel-7-x86_64'):
286 self.root_conn.module_args = """dest=/etc/mock/epel-7-x86_64.cfg line="config_opts['chroot_setup_cmd'] = 'install bash bzip2 coreutils cpio diffutils findutils gawk gcc gcc-c++ grep gzip info make patch redhat-release-server redhat-rpm-config rpm-build sed shadow-utils tar unzip util-linux which xz %s'" regexp="^.*chroot_setup_cmd.*$" """ % (self.buildroot_pkgs)
287 else:
288 self.root_conn.module_args = """dest=/etc/mock/%s.cfg line="config_opts['chroot_setup_cmd'] = 'install @buildsys-build %s'" regexp="^.*chroot_setup_cmd.*$" """ % (self.chroot, self.buildroot_pkgs)
289 self.mockremote.callback.log('putting %s into minimal buildroot of %s' % (self.buildroot_pkgs, self.chroot))
290 results = self.root_conn.run()
291
292 is_err, err_results = check_for_ans_error(results, self.hostname, success_codes=[0],
293 return_on_error=['stdout', 'stderr'])
294 if is_err:
295 self.mockremote.callback.log("Error: %s" % err_results)
296 myresults = get_ans_results(results, self.hostname)
297 self.mockremote.callback.log("%s" % myresults)
298
299
301
302
303
304
305
306
307
308 success = False
309 self.modify_base_buildroot()
310
311
312 dest = None
313 if os.path.exists(pkg):
314 dest = os.path.normpath(self.tempdir + '/' + os.path.basename(pkg))
315 self.conn.module_name = "copy"
316 margs = 'src=%s dest=%s' % (pkg, dest)
317 self.conn.module_args = str(margs)
318 self.mockremote.callback.log("Sending %s to %s to build" % (os.path.basename(pkg), self.hostname))
319
320
321 self.conn.run()
322 else:
323 dest = pkg
324
325
326 buildcmd = "%s -r %s -l %s " % (mockchain, pipes.quote(self.chroot), pipes.quote(self.remote_build_dir))
327 for r in self.repos:
328 if "rawhide" in self.chroot:
329 r = r.replace("$releasever", "rawhide")
330 buildcmd += "-a %s " % pipes.quote(r)
331
332 if self.mockremote.macros:
333 for k, v in self.mockremote.macros.items():
334 mock_opt = '--define=%s %s' % (k, v)
335 buildcmd += '-m %s ' % pipes.quote(mock_opt)
336
337 buildcmd += dest
338
339
340
341
342 self.mockremote.callback.log('executing: %r' % buildcmd)
343 self.conn.module_name = "shell"
344 self.conn.module_args = str(buildcmd)
345 results = self.conn.run()
346
347 is_err, err_results = check_for_ans_error(results, self.hostname, success_codes=[0],
348 return_on_error=['stdout', 'stderr'])
349 if is_err:
350 return success, err_results.get('stdout', ''), err_results.get('stderr', '')
351
352
353 myresults = get_ans_results(results, self.hostname)
354 out = myresults.get('stdout', '')
355 err = myresults.get('stderr', '')
356
357 successfile = self._get_remote_pkg_dir(pkg) + '/success'
358 testcmd = '/usr/bin/test -f %s' % successfile
359 self.conn.module_args = str(testcmd)
360 results = self.conn.run()
361 is_err, err_results = check_for_ans_error(results, self.hostname, success_codes=[0])
362 if not is_err:
363 success = True
364
365 return success, out, err
366
368
369
370
371 success = False
372 rpd = self._get_remote_pkg_dir(pkg)
373 destdir = "'" + destdir.replace("'", "'\\''") + "'"
374
375 remote_src = '%s@%s:%s' % (self.username, self.hostname, rpd)
376 ssh_opts = "'ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no'"
377 command = "%s -avH -e %s %s %s/" % (rsync, ssh_opts, remote_src, destdir)
378 cmd = subprocess.Popen(command, shell=True,
379 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
380
381
382 out, err = cmd.communicate()
383 if cmd.returncode:
384 success = False
385 else:
386 success = True
387
388 return success, out, err
389
391
392
393
394
395 if self.checked:
396 return True, []
397
398 errors = []
399
400 try:
401 socket.gethostbyname(self.hostname)
402 except socket.gaierror:
403 raise BuilderError('%s could not be resolved' % self.hostname)
404
405 self.conn.module_name = "shell"
406 self.conn.module_args = str("/bin/rpm -q mock rsync")
407 res = self.conn.run()
408
409 is_err, err_results = check_for_ans_error(res, self.hostname, success_codes=[0])
410 if is_err:
411 if 'rc' in err_results:
412 errors.append('Warning: %s does not have mock or rsync installed' % self.hostname)
413 else:
414 errors.append(err_results['msg'])
415
416
417
418 self.conn.module_name = "shell"
419 self.conn.module_args = "/usr/bin/test -f %s && /usr/bin/test -f /etc/mock/%s.cfg" % (mockchain, self.chroot)
420 res = self.conn.run()
421
422 is_err, err_results = check_for_ans_error(res, self.hostname, success_codes=[0])
423 if is_err:
424 if 'rc' in err_results:
425 errors.append('Warning: %s lacks mockchain or the chroot %s' % (self.hostname, self.chroot))
426 else:
427 errors.append(err_results['msg'])
428
429 if not errors:
430 self.checked = True
431 else:
432 msg = '\n'.join(errors)
433 raise BuilderError(msg)
434
437 - def __init__(self, builder=None, user=DEF_USER, timeout=DEF_TIMEOUT,
438 destdir=DEF_DESTDIR, chroot=DEF_CHROOT, cont=False, recurse=False,
439 repos=DEF_REPOS, callback=None,
440 remote_basedir=DEF_REMOTE_BASEDIR, remote_tempdir=None,
441 macros=DEF_MACROS,
442 buildroot_pkgs=DEF_BUILDROOT_PKGS):
443
444 self.destdir = destdir
445 self.chroot = chroot
446 self.repos = repos
447 self.cont = cont
448 self.recurse = recurse
449 self.callback = callback
450 self.remote_basedir = remote_basedir
451 self.remote_tempdir = remote_tempdir
452 self.macros = macros
453
454 if not self.callback:
455 self.callback = DefaultCallBack()
456
457 self.callback.log("Setting up builder: %s" % builder)
458 self.builder = Builder(builder, user, timeout, self, buildroot_pkgs)
459
460 if not self.chroot:
461 raise MockRemoteError("No chroot specified!")
462
463
464 self.failed = []
465 self.finished = []
466 self.pkg_list = []
467
468
470 s_pkg = os.path.basename(pkg)
471 pdn = s_pkg.replace('.src.rpm', '')
472 resdir = '%s/%s/%s' % (self.destdir, self.chroot, pdn)
473 resdir = os.path.normpath(resdir)
474 return resdir
475
477
478 if not pkgs:
479 pkgs = self.pkg_list
480
481 built_pkgs = []
482 downloaded_pkgs = {}
483
484 try_again = True
485 to_be_built = pkgs
486 while try_again:
487 self.failed = []
488 just_built = []
489 for pkg in to_be_built:
490 pkg = urllib.unquote("%s" % pkg)
491 if pkg in just_built:
492 self.callback.log("skipping duplicate pkg in this list: %s" % pkg)
493 continue
494 else:
495 just_built.append(pkg)
496
497 p_path = self._get_pkg_destpath(pkg)
498
499
500 if os.path.exists(p_path):
501 if os.path.exists(p_path + '/success'):
502 self.callback.log("Skipping already built pkg %s" % os.path.basename(pkg))
503 continue
504
505
506 elif os.path.exists(p_path + '/fail'):
507 os.unlink(p_path + '/fail')
508
509
510
511 self.callback.start_build(pkg)
512 b_status, b_out, b_err = self.builder.build(pkg)
513 self.callback.end_build(pkg)
514
515
516 self.callback.start_download(pkg)
517
518
519 chroot_dir = os.path.normpath(self.destdir + '/' + self.chroot)
520 d_ret, d_out, d_err = self.builder.download(pkg, chroot_dir)
521 if not d_ret:
522 msg = "Failure to download %s: %s" % (pkg, d_out + d_err)
523 if not self.cont:
524 raise MockRemoteError, msg
525 self.callback.error(msg)
526
527 self.callback.end_download(pkg)
528
529 if not os.path.exists(chroot_dir):
530 os.makedirs(self.destdir + '/' + self.chroot)
531 r_log = open(chroot_dir + '/mockchain.log', 'a')
532 fcntl.flock(r_log, fcntl.LOCK_EX)
533 r_log.write('\n\n%s\n\n' % pkg)
534 r_log.write(b_out)
535 if b_err:
536 r_log.write('\nstderr\n')
537 r_log.write(b_err)
538 fcntl.flock(r_log, fcntl.LOCK_UN)
539 r_log.close()
540
541
542
543 if not b_status:
544 if self.recurse:
545 self.failed.append(pkg)
546 self.callback.error("Error building %s, will try again" % os.path.basename(pkg))
547 else:
548 msg = "Error building %s\nSee logs/resultsin %s" % (os.path.basename(pkg), self.destdir)
549 if not self.cont:
550 raise MockRemoteError, msg
551 self.callback.error(msg)
552
553 else:
554 self.callback.log("Success building %s" % os.path.basename(pkg))
555 built_pkgs.append(pkg)
556
557 for d in [self.destdir, chroot_dir]:
558 rc, out, err = createrepo(d)
559 if err.strip():
560 self.callback.error("Error making local repo: %s" % d)
561 self.callback.error("%s" % err)
562
563
564 if self.failed:
565 if len(self.failed) != len(to_be_built):
566 to_be_built = self.failed
567 try_again = True
568 self.callback.log('Trying to rebuild %s failed pkgs' % len(self.failed))
569 else:
570 self.callback.log("Tried twice - following pkgs could not be successfully built:")
571 for pkg in self.failed:
572 msg = pkg
573 if pkg in downloaded_pkgs:
574 msg = downloaded_pkgs[pkg]
575 self.callback.log(msg)
576
577 try_again = False
578 else:
579 try_again = False
580
584
585 parser = SortedOptParser("mockremote -b hostname -u user -r chroot pkg pkg pkg")
586 parser.add_option('-r', '--root', default=DEF_CHROOT, dest='chroot',
587 help="chroot config name/base to use in the mock build")
588 parser.add_option('-c', '--continue', default=False, action='store_true',
589 dest='cont',
590 help="if a pkg fails to build, continue to the next one")
591 parser.add_option('-a', '--addrepo', default=DEF_REPOS, action='append',
592 dest='repos',
593 help="add these repo baseurls to the chroot's yum config")
594 parser.add_option('--recurse', default=False, action='store_true',
595 help="if more than one pkg and it fails to build, try to build the rest and come back to it")
596 parser.add_option('--log', default=None, dest='logfile',
597 help="log to the file named by this option, defaults to not logging")
598 parser.add_option("-b", "--builder", dest='builder', default=None,
599 help="builder to use")
600 parser.add_option("-u", dest="user", default=DEF_USER,
601 help="user to run as/connect as on builder systems")
602 parser.add_option("-t", "--timeout", dest="timeout", type="int",
603 default=DEF_TIMEOUT, help="maximum time in seconds a build can take to run")
604 parser.add_option("--destdir", dest="destdir", default=DEF_DESTDIR,
605 help="place to download all the results/packages")
606 parser.add_option("--packages", dest="packages_file", default=None,
607 help="file to read list of packages from")
608 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true",
609 help="output very little to the terminal")
610
611 opts, args = parser.parse_args(args)
612
613 if not opts.builder:
614 sys.stderr.write("Must specify a system to build on")
615 sys.exit(1)
616
617 if opts.packages_file and os.path.exists(opts.packages_file):
618 args.extend(read_list_from_file(opts.packages_file))
619
620
621
622
623 if not args:
624 sys.stderr.write("Must specify at least one pkg to build")
625 sys.exit(1)
626
627 if not opts.chroot:
628 sys.stderr.write("Must specify a mock chroot")
629 sys.exit(1)
630
631 for url in opts.repos:
632 if not (url.startswith('http://') or url.startswith('https://') or url.startswith('file://')):
633 sys.stderr.write("Only http[s] or file urls allowed for repos")
634 sys.exit(1)
635
636 return opts, args
637
638
639
640
641
642
643
644 -def main(args):
645
646
647 opts, pkgs = parse_args(args)
648
649 if not os.path.exists(opts.destdir):
650 os.makedirs(opts.destdir)
651
652 try:
653
654 callback = CliLogCallBack(logfn=opts.logfile, quiet=opts.quiet)
655
656 mr = MockRemote(builder=opts.builder, user=opts.user,
657 timeout=opts.timeout, destdir=opts.destdir, chroot=opts.chroot,
658 cont=opts.cont, recurse=opts.recurse, repos=opts.repos,
659 callback=callback)
660
661
662
663
664
665
666
667
668
669
670 if not opts.quiet:
671 print "Building %s pkgs" % len(pkgs)
672
673 mr.build_pkgs(pkgs)
674
675 if not opts.quiet:
676 print "Output written to: %s" % mr.destdir
677
678 except MockRemoteError, e:
679 print >> sys.stderr, "Error on build:"
680 print >> sys.stderr, str(e)
681 return
682
683
684 if __name__ == '__main__':
685 main(sys.argv[1:])
686