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