PK廕HM񇅟//dbp.py"""Simple program to manage gbp-docker container lifecycle.""" __version__ = "0.6.0" import argparse import logging import os import shlex import shutil import sys from pathlib import Path from subprocess import run, PIPE, DEVNULL, STDOUT from time import sleep from typing import List import controlgraph import networkx as nx IMAGE = "opxhub/gbp" IMAGE_VERSION = "v1.0.3" CONTAINER_NAME = "{}-dbp-{}".format(os.getenv("USER"), Path.cwd().stem) ENV_UID = "-e=UID={}".format(os.getuid()) ENV_GID = "-e=GID={}".format(os.getgid()) ENV_TZ = "-e=TZ={}".format("/".join(Path("/etc/localtime").resolve().parts[-2:])) ENV_MAINT_NAME = "-e=DEBFULLNAME={}".format(os.getenv("DEBFULLNAME", "Dell EMC")) ENV_MAINT_MAIL = "-e=DEBEMAIL={}".format( os.getenv("DEBEMAIL", "ops-dev@lists.openswitch.net") ) LOG_BUILD_COMMAND = ( '--- cd {0}; gbp buildpackage --git-export-dir="/mnt/pool/{1}-amd64/{0}" {2}\n' ) L = logging.getLogger("dbp") L.addHandler(logging.NullHandler()) ### Commands ########################################################################## def cmd_build(args: argparse.Namespace) -> int: rc = docker_pull_images(args.image, args.dist, check_first=True) if rc != 0: return rc rc = 0 remove = True # generate build order through dfs on builddepends graph if not args.targets: dirs = [p for p in Path.cwd().iterdir() if p.is_dir()] G = controlgraph.graph(controlgraph.parse_all_controlfiles(dirs)) isolates = list(nx.isolates(G)) if args.isolates_first: G.remove_nodes_from(isolates) args.targets = [Path(i) for i in isolates] + [ Path(n) for n in nx.dfs_postorder_nodes(G) ] elif args.isolates_last: G.remove_nodes_from(isolates) args.targets = [Path(n) for n in nx.dfs_postorder_nodes(G)] + [ Path(i) for i in isolates ] elif args.no_isolates: G.remove_nodes_from(isolates) args.targets = [Path(n) for n in nx.dfs_postorder_nodes(G)] else: args.targets = [Path(n) for n in nx.dfs_postorder_nodes(G)] if not args.targets: return 0 if args.print: print(" ".join([p.stem for p in args.targets])) return 0 if docker_container_exists(): remove = False else: rc = docker_run(args.image, args.dist, args.extra_sources, dev=False) if rc != 0: L.error("Could not run container") return rc if not docker_container_running(args.dist): rc = docker_start(args.dist) if rc != 0: L.error("Could not start stopped container") return rc sys.stdout.write("--- Building {} repositories\n".format(len(args.targets))) for t in args.targets: sys.stdout.write(LOG_BUILD_COMMAND.format(t.stem, args.dist, args.gbp)) sys.stdout.flush() rc = dexec_buildpackage(args.dist, t, args.extra_sources, args.gbp) if rc != 0: L.error("Could not build package {}".format(t.stem)) break if remove: docker_remove_container() return 0 def cmd_pull(args: argparse.Namespace) -> int: return docker_pull_images(args.image, args.dist) def cmd_rm(args: argparse.Namespace) -> int: docker_remove_container() return 0 def cmd_run(args: argparse.Namespace) -> int: rc = docker_pull_images(args.image, args.dist, check_first=True) if rc != 0: return rc return docker_run(args.image, args.dist, args.extra_sources, dev=True) def cmd_shell(args: argparse.Namespace) -> int: rc = docker_pull_images(args.image, args.dist, check_first=True) if rc != 0: return rc remove = True if docker_container_exists(): remove = False else: rc = docker_run(args.image, args.dist, args.extra_sources, dev=True) if rc != 0: L.error("Could not run container") return rc if not docker_container_running(args.dist): rc = docker_start(args.dist) if rc != 0: L.error("Could not start stopped container") return rc cmd = [ "docker", "exec", "-it", "--user=build", ENV_UID, ENV_GID, ENV_TZ, ENV_MAINT_NAME, ENV_MAINT_MAIL, "-e=EXTRA_SOURCES={}".format(args.extra_sources), CONTAINER_NAME, "bash", "-l", ] if args.command: cmd.extend(["-c", args.command]) rc = irun(cmd, quiet=False) if remove: docker_remove_container() return rc ### Docker functions ################################################################## def dexec_buildpackage(dist: str, target: Path, sources: str, gbp_options: str) -> int: """Runs gbp buildpackage --git-export-dir=pool/{dist}-amd64/{target} Container must already be started. """ if not target.exists(): L.error("Build target `{}` does not exist".format(target)) return 1 cmd = [ "docker", "exec", "-it" if sys.stdin.isatty() else "-t", "--user=build", ENV_UID, ENV_GID, ENV_TZ, ENV_MAINT_NAME, ENV_MAINT_MAIL, "-e=EXTRA_SOURCES={}".format(sources), CONTAINER_NAME, "build", target.stem, ] cmd.extend(shlex.split(gbp_options)) return irun(cmd) def docker_container_exists() -> bool: """Returns true if our dbp container can be inspected""" return irun(["docker", "inspect", CONTAINER_NAME], quiet=True) == 0 def docker_container_running(dist: str) -> bool: """Returns true if our dbp container is running""" proc = run( ["docker", "inspect", CONTAINER_NAME, "--format={{.State.Running}}"], stdout=PIPE, stderr=DEVNULL, ) return proc.returncode == 0 and "true" in str(proc.stdout) def docker_image_name(image: str, dist: str, dev: bool) -> str: """Returns the Docker image to use, allowing for custom images.""" if ":" in image: return image if dev: template = "{}:{}-{}-dev" else: template = "{}:{}-{}" return template.format(image, IMAGE_VERSION, dist) def docker_pull_images(image: str, dist: str, check_first=False) -> int: """Runs docker pull for both build and development images and returns the return code""" if check_first: tag = docker_image_name(image, dist, dev=True)[len(image) + 1 :] proc = run(["docker", "images"], stdout=PIPE, stderr=STDOUT) if image in proc.stdout.decode("utf-8") and tag in proc.stdout.decode("utf-8"): return 0 print("Pulling Docker image {}".format(image)) cmd = ["docker", "pull", docker_image_name(image, dist, False)] rc = irun(cmd) if rc != 0: return rc cmd = ["docker", "pull", docker_image_name(image, dist, True)] return irun(cmd) def docker_remove_container() -> int: """Runs docker rm -f for the dbp container""" if docker_container_exists(): cmd = ["docker", "rm", "-f", CONTAINER_NAME] return irun(cmd, quiet=True) L.info("Container does not exist.") return 1 def docker_run(image: str, dist: str, sources: str, dev=True) -> int: if docker_container_exists(): L.info("Container already exists") return 0 cmd = [ "docker", "run", "-d", "-it", "--name={}".format(CONTAINER_NAME), "--hostname={}".format(dist), "-v={}:/mnt".format(Path.cwd()), ] gitconfig = Path(Path.home() / ".gitconfig") if gitconfig.exists(): cmd.append("-v={}:/etc/skel/.gitconfig:ro".format(gitconfig)) cmd.extend( [ ENV_UID, ENV_GID, ENV_TZ, ENV_MAINT_NAME, ENV_MAINT_MAIL, "-e=EXTRA_SOURCES={}".format(sources), docker_image_name(image, dist, dev), ] ) if not dev: cmd.extend(["bash", "-l"]) rc = irun(cmd, quiet=True) # wait for user to be created sleep(1) return rc def docker_start(dist: str) -> int: """Runs docker start and returns the return code""" cmd = ["docker", "start", CONTAINER_NAME] return irun(cmd, quiet=True) ### Utilities ######################################################################### def irun(cmd: List[str], quiet=False) -> int: """irun runs an interactive command.""" L.debug("Running {}".format(" ".join(cmd))) if quiet: proc = run(cmd, stdin=sys.stdin, stdout=DEVNULL, stderr=DEVNULL) else: proc = run(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr) return proc.returncode ### Main ############################################################################## def main() -> int: parser = argparse.ArgumentParser(description=__doc__) # general arguments parser.add_argument( "--version", "-V", action="store_true", help="print program version" ) parser.add_argument( "--verbose", "-v", help="-v for info, -vv for debug", action="count", default=0 ) parser.add_argument( "--dist", "-d", help="Debian distribution", default=os.getenv("DIST", "stretch") ) parser.add_argument( "--extra-sources", "-e", help="Apt-style sources", default=os.getenv("EXTRA_SOURCES", ""), ) parser.add_argument("--image", "-i", help="Docker image to use", default=IMAGE) sps = parser.add_subparsers(help="commands") # build subcommand build_parser = sps.add_parser("build", help="run gbp buildpackage") build_parser.add_argument( "--gbp", "-g", default="", help="additional git-buildpackage options to pass" ) build_parser.add_argument( "--print", "-p", action="store_true", help="print build order and exit" ) build_parser.add_argument( "targets", nargs="*", type=Path, help="directories to build" ) build_isolates = build_parser.add_mutually_exclusive_group() build_isolates.add_argument( "--isolates-first", action="store_true", help="build free-standing repos first" ) build_isolates.add_argument( "--isolates-last", action="store_true", help="build free-standing repos last" ) build_isolates.add_argument( "--no-isolates", action="store_true", help="do not build free-standing repos" ) build_parser.set_defaults(func=cmd_build) # pull subcommand pull_parser = sps.add_parser("pull", help="pull latest images") pull_parser.set_defaults(func=cmd_pull) # rm subcommand rm_parser = sps.add_parser("rm", help="remove workspace container") rm_parser.set_defaults(func=cmd_rm) # run subcommand run_parser = sps.add_parser("run", help="run development container in background") run_parser.set_defaults(func=cmd_run) # shell subcommand shell_parser = sps.add_parser("shell", help="launch development environment") shell_parser.add_argument("--command", "-c", help="command to run noninteractively") shell_parser.set_defaults(func=cmd_shell) args = parser.parse_args() if args.version: print("dbp {}".format(__version__)) return 0 # set up logging logging.basicConfig( format="[%(levelname)s] %(message)s", level=10 * (3 - min(args.verbose, 2)) ) # check for prereqs if shutil.which("docker") is None: L.error("Docker not found in PATH. Please install docker.") sys.exit(1) # read sources from ./extra_sources and ~/.extra_sources if args.extra_sources == "": extra_sources = [Path("extra_sources"), Path.home() / ".extra_sources"] for s in extra_sources: if s.exists(): args.extra_sources = s.read_text() break if args.extra_sources != "": L.info("Loaded extra sources:\n{}".format(args.extra_sources)) # print help if no subcommand specified if getattr(args, "func", None) is None: parser.print_help() return 0 # run subcommand return args.func(args) if __name__ == "__main__": sys.exit(main()) PK!H囖? $dbp-0.6.0.dist-info/entry_points.txt婲蜗+蜗I/N.,()庡JI*b奶<..PK%紺M〦z44dbp-0.6.0.dist-info/LICENSEThe MIT License (MIT) Copyright (c) 2018 Dell inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HS續POdbp-0.6.0.dist-info/WHEEL 螲M脱 K-*翁铣R03鄏O蚄-J,/睷H松,r彗楄zユd&Y)暒r$)T腈彗リ&鎁rPK!HW鷞-dbp-0.6.0.dist-info/METADATA臱mo8_罧4 "9i窑5.嬎K&A^n颬jJn$QGRv紜葸鞠悞,;.娒}竳h+妡f8筇3#枾茯B┦!{准C柶U-D'袮p_讒!粭E Vi5鸭`V眰梶"$T%OB矰晼薳&扙拫( V9dSk+3 T%J36橣グ羐權~}篌up^郓褻v%驕2墯昉\鍯Κ鏟枡[C━&糫`?.裼貿魐習0墫暸章K)J>,*碘p千T退2缙萀 榦黚!还斌 棢墧灴谧K邑 俈9M走燎挂O想胝"U6^ _蝓vq;b3煀锎q媦:QV隕$諣Z =2成顁K胆dd_墺-僝偧╡2c弓:dL餅Q玲樯i蝾$9:.=:>耖椠I,廜廜藿9~;~灛9 噇 簩咅g77搯醆R蒒A0捍A~罷鏂.)洞1憊Z黔>Xn躚抭焿捛,'X+*檭熂LY*@,嵘T)2銡+槇聇,鎅舦~:c犝n剬絙1蛃N 倧\t^cE嬅灌u屒銟沬P申瑅埋!=!鲐槵>_:寵甂煓猰ú衘!汍tcy,h+3瀀l * 姓嗲俇5BaFk萀Qfc鳾. #Q  r;鹳!D篩*f"WUBhgR媥 非8 o2辰-d虛Vu檝g \[(8呈嬀澿B賗/\c怇0TxQ) [濪t2V獮#杵<漲/舏棏OB V0-cYM>#-T 顢,U "=;學套焢J5喹鎸W礜h3!癦饠eZ瞠櫴s5'"S柂@朥m=?_EY#+j湁E/SnJ-<屫8 `仂@酩悍pw耦蛎齲%3%狫竲 r0:楲6攸*盥┅煊R緄+但汿膶@罓0poxFm碌殶!DA齏:;V5,sI3俴l陆CpU豬NO沦哺冄跚!V嚐OA9熯&绁,ft:%8#浰睰Y_<铻4=、皔]蓌漑待XRQW烁v潞u鹴Z褩告!/覔c胸lP瓺磃砃畝 滝馓(c缘 寢瀊 ~斒z樨G@z瞗X偄禜ǹ4=罭碯%犀撍戯t浱饻詀>摱霚m鄸葅餲﨡>聬3>!;Nr沝v釒0y%啙+俽禘忐1/彚冭狈6畴<咓肼c茏绬躓緫Q3柀XmQ&譪#倠呜w:芇QP鞐 魢8べD.蒒PT/H古s曀DZ<槻鵗斔緢0搛N冑泞1)L喸J作鷠栽=棊瑀ˋw堟裬5gs啕 懪ミ;,E,l>娛頑?D3m(G荴寇NO谀禟埂嗳颾酺2t/C4F:t溒 埾旚s匼v茦$.俰/yr羨4*mS擯Rv"3_xY8F 寋踺#nSID偶Q驁W綮槯滅枘詠adQ诓德赹y:儻w犤zV.怗xsq穉X4S$6=t夘抐C!P橅锚夻匐犭鄌,Cm饇獳贆O箄19埂郀 齿訃爇fHOIA叓uYX萠"菠1q枪蝧<"蹶教&机p耥鈗豉贳晚描纡u>伴@=y疥蕠giWk慑R函;W鮧ユ$ %唚x著牆郜h%邎D峨hDsoD2咵环7d穖彞猈眷R褴魶 %韜士砧鱬蠡薕茟9遘锨⺶Dm炛Va晧+湸m*/縞'J撊-滴9錅儏堼珯芣 齘A礍gB 賰[Jf)0丠C{&u ~&黢列汮$鬝HD醫顫楀zL轸呣z鵳I竵Kz. ︶ ZB咽Kgs憢磐{监",a閹奋 痠*iㄢ樄┰7f数Hb&tW聁桳猗鍝壂f|T覐&M_l硵鍇騗'Q鉸u跺K@!駆g峏騬廉/K eǐ>猈A+l鮾M5n蒩 PK!H鉿#dbp-0.6.0.dist-info/RECORDu嗡vC@衹緀(:A]0F"&hⅠ8!M樐爘}Wv躶髮騮]旿k 蘜#%搀OU:y娜=PK廕HM񇅟//dbp.pyPK!H囖? $/dbp-0.6.0.dist-info/entry_points.txtPK%紺M〦z44N0dbp-0.6.0.dist-info/LICENSEPK!HS續PO4dbp-0.6.0.dist-info/WHEELPK!HW鷞-B5dbp-0.6.0.dist-info/METADATAPK!H鉿#=dbp-0.6.0.dist-info/RECORDPK>