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 os
29 import sys
30 import time
31 import fcntl
32 import pipes
33 import socket
34 import urllib
35 import optparse
36 import subprocess
37 from operator import methodcaller
38
39 import ansible.runner
40
41
42 mockchain = "/usr/bin/mockchain"
43
44 rsync = "/usr/bin/rsync"
45
46 DEF_REMOTE_BASEDIR = "/var/tmp"
47 DEF_TIMEOUT = 3600
48 DEF_REPOS = []
49 DEF_CHROOT = None
50 DEF_USER = "mockbuilder"
51 DEF_DESTDIR = os.getcwd()
52 DEF_MACROS = {}
53 DEF_BUILDROOT_PKGS = ""
57
58 """Optparser which sorts the options by opt before outputting --help"""
59
63
66 if os.path.exists(path + '/repodata/repomd.xml'):
67 comm = ['/usr/bin/createrepo_c', '--database', '--ignore-lock',
68 '--update', path]
69 else:
70 comm = ['/usr/bin/createrepo_c', '--database', '--ignore-lock', path]
71 if lock:
72 lock.acquire()
73 cmd = subprocess.Popen(comm,
74 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
75 out, err = cmd.communicate()
76 if lock:
77 lock.release()
78 return cmd.returncode, out, err
79
82 lst = []
83 f = open(fn, "r")
84 for line in f.readlines():
85 line = line.replace("\n", "")
86 line = line.strip()
87 if line.startswith("#"):
88 continue
89 lst.append(line)
90
91 return lst
92
93
94 -def log(lf, msg, quiet=None):
95 if lf:
96 now = time.time()
97 try:
98 with open(lf, "a") as lfh:
99 fcntl.flock(lfh, fcntl.LOCK_EX)
100 lfh.write(str(now) + ":" + msg + "\n")
101 fcntl.flock(lfh, fcntl.LOCK_UN)
102 except (IOError, OSError) as e:
103 sys.stderr.write(
104 "Could not write to logfile {0} - {1}\n".format(lf, str(e)))
105 if not quiet:
106 print(msg)
107
110 if hostname in results["dark"]:
111 return results["dark"][hostname]
112 if hostname in results["contacted"]:
113 return results["contacted"][hostname]
114
115 return {}
116
119 ans_conn = ansible.runner.Runner(remote_user=username,
120 host_list=hostname + ",",
121 pattern=hostname,
122 forks=1,
123 transport="ssh",
124 timeout=timeout)
125 return ans_conn
126
127
128 -def check_for_ans_error(results, hostname, err_codes=[], success_codes=[0],
129 return_on_error=["stdout", "stderr"]):
130 """
131 Return True or False + dict
132 dict includes 'msg'
133 may include 'rc', 'stderr', 'stdout' and any other requested result codes
134 """
135
136 err_results = {}
137
138 if "dark" in results and hostname in results["dark"]:
139 err_results["msg"] = "Error: Could not contact/connect" \
140 " to {0}.".format(hostname)
141
142 return (True, err_results)
143
144 error = False
145
146 if err_codes or success_codes:
147 if hostname in results["contacted"]:
148 if "rc" in results["contacted"][hostname]:
149 rc = int(results["contacted"][hostname]["rc"])
150 err_results["rc"] = rc
151
152 if rc in err_codes:
153 error = True
154 err_results["msg"] = "rc {0} matched err_codes".format(rc)
155 elif rc not in success_codes:
156 error = True
157 err_results["msg"] = "rc {0} not in success_codes".format(rc)
158
159 elif ("failed" in results["contacted"][hostname] and
160 results["contacted"][hostname]["failed"]):
161
162 error = True
163 err_results["msg"] = "results included failed as true"
164
165 if error:
166 for item in return_on_error:
167 if item in results["contacted"][hostname]:
168 err_results[item] = results["contacted"][hostname][item]
169
170 return error, err_results
171
181
185
188
190 self.quiet = kwargs.get("quiet", False)
191 self.logfn = kwargs.get("logfn", None)
192
195
198
201
204
206 self.log("Error: {0}".format(msg))
207
208 - def log(self, msg):
209 if not self.quiet:
210 print(msg)
211
214
217
219 msg = "Start build: {0}".format(pkg)
220 self.log(msg)
221
223 msg = "End Build: {0}".format(pkg)
224 self.log(msg)
225
227 msg = "Start retrieve results for: {0}".format(pkg)
228 self.log(msg)
229
231 msg = "End retrieve results for: {0}".format(pkg)
232 self.log(msg)
233
235 self.log("Error: {0}".format(msg))
236
237 - def log(self, msg):
238 log(self.logfn, msg, self.quiet)
239
242
243 - def __init__(self, hostname, username,
244 timeout, mockremote, buildroot_pkgs):
245
246 self.hostname = hostname
247 self.username = username
248 self.timeout = timeout
249 self.chroot = mockremote.chroot
250 self.repos = mockremote.repos
251 self.mockremote = mockremote
252
253 if buildroot_pkgs is None:
254 self.buildroot_pkgs = ""
255 else:
256 self.buildroot_pkgs = buildroot_pkgs
257
258 self.checked = False
259 self._tempdir = None
260
261 self.conn = _create_ans_conn(
262 self.hostname, self.username, self.timeout)
263 self.root_conn = _create_ans_conn(self.hostname, "root", self.timeout)
264
265 self.check()
266
267 @property
269 return self.tempdir + "/build/"
270
271 @property
273 if self.mockremote.remote_tempdir:
274 return self.mockremote.remote_tempdir
275
276 if self._tempdir:
277 return self._tempdir
278
279 cmd = "/bin/mktemp -d {0}/{1}-XXXXX".format(
280 self.mockremote.remote_basedir, "mockremote")
281
282 self.conn.module_name = "shell"
283 self.conn.module_args = str(cmd)
284 results = self.conn.run()
285 tempdir = None
286 for hn, resdict in results["contacted"].items():
287 tempdir = resdict["stdout"]
288
289
290 if not tempdir:
291 raise BuilderError("Could not make tmpdir on {0}".format(
292 self.hostname))
293
294 cmd = "/bin/chmod 755 {0}".format(tempdir)
295 self.conn.module_args = str(cmd)
296 self.conn.run()
297 self._tempdir = tempdir
298
299 return self._tempdir
300
301 @tempdir.setter
303 self._tempdir = value
304
306
307
308 s_pkg = os.path.basename(pkg)
309 pdn = s_pkg.replace(".src.rpm", "")
310 remote_pkg_dir = os.path.normpath(
311 os.path.join(self.remote_build_dir,
312 "results",
313 self.chroot,
314 pdn))
315
316 return remote_pkg_dir
317
319 """
320 Modify mock config for current chroot.
321
322 Packages in buildroot_pkgs are added to minimal buildroot
323 """
324
325 if ("'{0} '".format(self.buildroot_pkgs) !=
326 pipes.quote(str(self.buildroot_pkgs) + ' ')):
327
328
329
330 raise BuilderError("Do not try this kind of attack on me")
331
332 self.root_conn.module_name = "lineinfile"
333 if self.chroot == "epel-7-x86_64":
334 self.root_conn.module_args = (
335 "dest=/etc/mock/epel-7-x86_64.cfg"
336 " line=\"config_opts['chroot_setup_cmd'] = 'install"
337 " bash bzip2 coreutils cpio diffutils findutils"
338 " gawk gcc gcc-c++ grep gzip info make patch"
339 " redhat-release-server redhat-rpm-config rpm-build"
340 " sed shadow-utils tar unzip util-linux which xz {0}'\""
341 " regexp=\"^.*chroot_setup_cmd.*$\"".format(
342 self.buildroot_pkgs))
343 else:
344 self.root_conn.module_args = (
345 "dest=/etc/mock/{0}.cfg"
346 " line=\"config_opts['chroot_setup_cmd'] ="
347 " 'install @buildsys-build {1}'\""
348 " regexp=\"^.*chroot_setup_cmd.*$\"".format(
349 self.chroot, self.buildroot_pkgs))
350
351 self.mockremote.callback.log(
352 "putting {0} into minimal buildroot of {1}".format(
353 self.buildroot_pkgs, self.chroot))
354
355 results = self.root_conn.run()
356
357 is_err, err_results = check_for_ans_error(
358 results, self.hostname, success_codes=[0],
359 return_on_error=["stdout", "stderr"])
360
361 if is_err:
362 self.mockremote.callback.log("Error: {0}".format(err_results))
363 myresults = get_ans_results(results, self.hostname)
364 self.mockremote.callback.log("{0}".format(myresults))
365
367
368
369
370
371
372
373
374 success = False
375 self.modify_base_buildroot()
376
377
378 dest = None
379 if os.path.exists(pkg):
380 dest = os.path.normpath(
381 os.path.join(self.tempdir, os.path.basename(pkg)))
382
383 self.conn.module_name = "copy"
384 margs = "src={0} dest={1}".format(pkg, dest)
385 self.conn.module_args = margs
386 self.mockremote.callback.log(
387 "Sending {0} to {1} to build".format(
388 os.path.basename(pkg), self.hostname))
389
390
391 self.conn.run()
392 else:
393 dest = pkg
394
395
396 buildcmd = "{0} -r {1} -l {2} ".format(
397 mockchain, pipes.quote(self.chroot),
398 pipes.quote(self.remote_build_dir))
399
400 for r in self.repos:
401 if "rawhide" in self.chroot:
402 r = r.replace("$releasever", "rawhide")
403
404 buildcmd += "-a {0} ".format(pipes.quote(r))
405
406 if self.mockremote.macros:
407 for k, v in self.mockremote.macros.items():
408 mock_opt = "--define={0} {1}".format(k, v)
409 buildcmd += "-m {0} ".format(pipes.quote(mock_opt))
410
411 buildcmd += dest
412
413
414
415 self.mockremote.callback.log("executing: {0}".format(buildcmd))
416 self.conn.module_name = "shell"
417 self.conn.module_args = buildcmd
418 results = self.conn.run()
419
420 is_err, err_results = check_for_ans_error(
421 results, self.hostname, success_codes=[0],
422 return_on_error=["stdout", "stderr"])
423
424 if is_err:
425 return (success, err_results.get("stdout", ""),
426 err_results.get("stderr", ""))
427
428
429
430 myresults = get_ans_results(results, self.hostname)
431 out = myresults.get("stdout", "")
432 err = myresults.get("stderr", "")
433
434 successfile = os.path.join(self._get_remote_pkg_dir(pkg), "success")
435 testcmd = "/usr/bin/test -f {0}".format(successfile)
436 self.conn.module_args = testcmd
437 results = self.conn.run()
438 is_err, err_results = check_for_ans_error(
439 results, self.hostname, success_codes=[0])
440
441 if not is_err:
442 success = True
443
444 return success, out, err
445
447
448
449
450 success = False
451 rpd = self._get_remote_pkg_dir(pkg)
452
453 destdir = "'" + destdir.replace("'", "'\\''") + "'"
454
455 remote_src = "{0}@{1}:{2}".format(self.username, self.hostname, rpd)
456 ssh_opts = "'ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no'"
457 command = "{0} -avH -e {1} {2} {3}/".format(
458 rsync, ssh_opts, remote_src, destdir)
459
460 cmd = subprocess.Popen(command, shell=True,
461 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
462
463
464 out, err = cmd.communicate()
465 if cmd.returncode:
466 success = False
467 else:
468 success = True
469
470 return success, out, err
471
473
474
475
476
477 if self.checked:
478 return True, []
479
480 errors = []
481
482 try:
483 socket.gethostbyname(self.hostname)
484 except socket.gaierror:
485 raise BuilderError("{0} could not be resolved".format(
486 self.hostname))
487
488 self.conn.module_name = "shell"
489 self.conn.module_args = "/bin/rpm -q mock rsync"
490 res = self.conn.run()
491
492
493 is_err, err_results = check_for_ans_error(
494 res, self.hostname, success_codes=[0])
495
496 if is_err:
497 if "rc" in err_results:
498 errors.append(
499 "Warning: {0} does not have mock or rsync installed"
500 .format(self.hostname))
501 else:
502 errors.append(err_results["msg"])
503
504
505
506 self.conn.module_name = "shell"
507 self.conn.module_args = "/usr/bin/test -f {0}" \
508 " && /usr/bin/test -f /etc/mock/{1}.cfg".format(
509 mockchain, self.chroot)
510 res = self.conn.run()
511
512 is_err, err_results = check_for_ans_error(
513 res, self.hostname, success_codes=[0])
514
515 if is_err:
516 if "rc" in err_results:
517 errors.append(
518 "Warning: {0} lacks mockchain on chroot {1}".format(
519 self.hostname, self.chroot))
520 else:
521 errors.append(err_results["msg"])
522
523 if not errors:
524 self.checked = True
525 else:
526 msg = "\n".join(errors)
527 raise BuilderError(msg)
528
531
532 - def __init__(self, builder=None, user=DEF_USER, timeout=DEF_TIMEOUT,
533 destdir=DEF_DESTDIR, chroot=DEF_CHROOT, cont=False,
534 recurse=False, repos=DEF_REPOS, callback=None,
535 remote_basedir=DEF_REMOTE_BASEDIR, remote_tempdir=None,
536 macros=DEF_MACROS, lock=None,
537 buildroot_pkgs=DEF_BUILDROOT_PKGS):
538
539 self.destdir = destdir
540 self.chroot = chroot
541 self.repos = repos
542 self.cont = cont
543 self.recurse = recurse
544 self.callback = callback
545 self.remote_basedir = remote_basedir
546 self.remote_tempdir = remote_tempdir
547 self.macros = macros
548 self.lock = lock
549
550 if not self.callback:
551 self.callback = DefaultCallBack()
552
553 self.callback.log("Setting up builder: {0}".format(builder))
554 self.builder = Builder(builder, user, timeout, self, buildroot_pkgs)
555
556 if not self.chroot:
557 raise MockRemoteError("No chroot specified!")
558
559 self.failed = []
560 self.finished = []
561 self.pkg_list = []
562
564 s_pkg = os.path.basename(pkg)
565 pdn = s_pkg.replace(".src.rpm", "")
566 resdir = "{0}/{1}/{2}".format(self.destdir, self.chroot, pdn)
567 resdir = os.path.normpath(resdir)
568 return resdir
569
571
572 if not pkgs:
573 pkgs = self.pkg_list
574
575 built_pkgs = []
576 downloaded_pkgs = {}
577
578 try_again = True
579 to_be_built = pkgs
580 while try_again:
581 self.failed = []
582 just_built = []
583 for pkg in to_be_built:
584 pkg = urllib.unquote(str(pkg))
585 if pkg in just_built:
586 self.callback.log(
587 "skipping duplicate pkg in this list: {0}".format(pkg))
588 continue
589 else:
590 just_built.append(pkg)
591
592 p_path = self._get_pkg_destpath(pkg)
593
594
595 if os.path.exists(p_path):
596 if os.path.exists(os.path.join(p_path, "success")):
597 self.callback.log(
598 "Skipping already built pkg {0}".format(
599 os.path.basename(pkg)))
600
601 continue
602
603
604 elif os.path.exists(os.path.join(p_path, "fail")):
605 os.unlink(os.path.join(p_path, "fail"))
606
607
608
609 self.callback.start_build(pkg)
610 b_status, b_out, b_err = self.builder.build(pkg)
611 self.callback.end_build(pkg)
612
613
614 self.callback.start_download(pkg)
615
616
617 chroot_dir = os.path.normpath(
618 os.path.join(self.destdir, self.chroot))
619
620 d_ret, d_out, d_err = self.builder.download(pkg, chroot_dir)
621 if not d_ret:
622 msg = "Failure to download {0}: {1}".format(
623 pkg, d_out + d_err)
624
625 if not self.cont:
626 raise MockRemoteError(msg)
627 self.callback.error(msg)
628
629 self.callback.end_download(pkg)
630
631
632 if not os.path.exists(chroot_dir):
633 os.makedirs(
634 os.path.join(self.destdir, self.chroot))
635
636 r_log = open(os.path.join(chroot_dir, "mockchain.log"), 'a')
637 fcntl.flock(r_log, fcntl.LOCK_EX)
638 r_log.write("\n\n{0}\n\n".format(pkg))
639 r_log.write(b_out)
640 if b_err:
641 r_log.write("\nstderr\n")
642 r_log.write(b_err)
643 fcntl.flock(r_log, fcntl.LOCK_UN)
644 r_log.close()
645
646
647 if not b_status:
648 if self.recurse:
649 self.failed.append(pkg)
650 self.callback.error(
651 "Error building {0}, will try again".format(
652 os.path.basename(pkg)))
653 else:
654 msg = "Error building {0}\nSee logs/results in {1}" \
655 .format(os.path.basename(pkg), self.destdir)
656
657 if not self.cont:
658 raise MockRemoteError(msg)
659 self.callback.error(msg)
660
661 else:
662 self.callback.log("Success building {0}".format(
663 os.path.basename(pkg)))
664 built_pkgs.append(pkg)
665
666 rc, out, err = createrepo(chroot_dir, self.lock)
667 if err.strip():
668 self.callback.error(
669 "Error making local repo: {0}".format(chroot_dir))
670
671 self.callback.error(str(err))
672
673
674
675 if self.failed:
676 if len(self.failed) != len(to_be_built):
677 to_be_built = self.failed
678 try_again = True
679 self.callback.log(
680 "Trying to rebuild {0} failed pkgs".format(
681 len(self.failed)))
682 else:
683 self.callback.log(
684 "Tried twice - following pkgs could not be"
685 " successfully built:")
686
687 for pkg in self.failed:
688 msg = pkg
689 if pkg in downloaded_pkgs:
690 msg = downloaded_pkgs[pkg]
691 self.callback.log(msg)
692
693 try_again = False
694 else:
695 try_again = False
696
699
700 parser = SortedOptParser(
701 "mockremote -b hostname -u user -r chroot pkg pkg pkg")
702 parser.add_option("-r", "--root", default=DEF_CHROOT, dest="chroot",
703 help="chroot config name/base to use in the mock build")
704 parser.add_option("-c", "--continue", default=False, action="store_true",
705 dest="cont",
706 help="if a pkg fails to build, continue to the next one")
707 parser.add_option("-a", "--addrepo", default=DEF_REPOS, action="append",
708 dest="repos",
709 help="add these repo baseurls to the chroot's yum config")
710 parser.add_option("--recurse", default=False, action="store_true",
711 help="if more than one pkg and it fails to build,"
712 " try to build the rest and come back to it")
713 parser.add_option("--log", default=None, dest="logfile",
714 help="log to the file named by this option,"
715 " defaults to not logging")
716 parser.add_option("-b", "--builder", dest="builder", default=None,
717 help="builder to use")
718 parser.add_option("-u", dest="user", default=DEF_USER,
719 help="user to run as/connect as on builder systems")
720 parser.add_option("-t", "--timeout", dest="timeout", type="int",
721 default=DEF_TIMEOUT,
722 help="maximum time in seconds a build can take to run")
723 parser.add_option("--destdir", dest="destdir", default=DEF_DESTDIR,
724 help="place to download all the results/packages")
725 parser.add_option("--packages", dest="packages_file", default=None,
726 help="file to read list of packages from")
727 parser.add_option("-q", "--quiet", dest="quiet", default=False,
728 action="store_true",
729 help="output very little to the terminal")
730
731 opts, args = parser.parse_args(args)
732
733 if not opts.builder:
734 sys.stderr.write("Must specify a system to build on")
735 sys.exit(1)
736
737 if opts.packages_file and os.path.exists(opts.packages_file):
738 args.extend(read_list_from_file(opts.packages_file))
739
740
741
742
743 if not args:
744 sys.stderr.write("Must specify at least one pkg to build")
745 sys.exit(1)
746
747 if not opts.chroot:
748 sys.stderr.write("Must specify a mock chroot")
749 sys.exit(1)
750
751 for url in opts.repos:
752 if not (url.startswith("http://") or
753 url.startswith("https://") or url.startswith("file://")):
754
755 sys.stderr.write("Only http[s] or file urls allowed for repos")
756 sys.exit(1)
757
758 return opts, args
759
760
761
762
763
764
765
766 -def main(args):
767
768
769 opts, pkgs = parse_args(args)
770
771 if not os.path.exists(opts.destdir):
772 os.makedirs(opts.destdir)
773
774 try:
775
776 callback = CliLogCallBack(logfn=opts.logfile, quiet=opts.quiet)
777
778 mr = MockRemote(builder=opts.builder,
779 user=opts.user,
780 timeout=opts.timeout,
781 destdir=opts.destdir,
782 chroot=opts.chroot,
783 cont=opts.cont,
784 recurse=opts.recurse,
785 repos=opts.repos,
786 callback=callback)
787
788
789
790
791
792
793
794
795
796
797 if not opts.quiet:
798 print("Building {0} pkgs".format(len(pkgs)))
799
800 mr.build_pkgs(pkgs)
801
802 if not opts.quiet:
803 print("Output written to: {0}".format(mr.destdir))
804
805 except MockRemoteError as e:
806 sys.stderr.write("Error on build:\n")
807 sys.stderr.write("{0}\n".format(e))
808 return
809
810
811 if __name__ == "__main__":
812 main(sys.argv[1:])
813