#!/usr/bin/python # -*- python -*- # -*- coding: utf-8 -*- # Started 30/06/2011 by Seblu <seblu@seblu.net> ''' InstallSystems Command line Tool ''' import os import time import datetime import re import fnmatch import warnings import argparse import installsystems import installsystems.printer import installsystems.tools as istools from installsystems.printer import * from installsystems.repository import Repository from installsystems.repository import RepositoryManager from installsystems.repository import RepositoryConfig from installsystems.image import PackageImage, SourceImage from installsystems.config import MainConfigFile, RepoConfigFile ################################################################################ # Common functions ################################################################################ def load_repositories(args): ''' Load repositories on a repository manager ''' # remove cache is asked if hasattr(args, "no_cache") and args.no_cache: args.cache = None # split filter in list if args.repo_filter is not None: args.repo_filter = split_repositories(args.repo_filter) # init repo cache object repoman = RepositoryManager(args.cache, timeout=args.timeout, filter=args.repo_filter) # register repositories (order matter) # load repo configs from command line if args.repo_path is not None: repoman.register(RepositoryConfig(istools.smd5sum(args.repo_path)[:8], path=args.repo_path), temp=True, nosync=hasattr(args, "no_sync") and args.no_sync, offline=hasattr(args, "force_offline") and args.force_offline) # load repo configs from config for repoconf in RepoConfigFile(args.repo_config).repos: repoman.register(repoconf, nosync=hasattr(args, "no_sync") and args.no_sync, offline=hasattr(args, "force_offline") and args.force_offline) return repoman def split_repositories(repos, filter=None): ''' Return a list of repository from an comma/space separated list of repo ''' if filter is None: filter = lambda x: x != "" return [r for r in re.split("[ ,\n\t\v]+", repos) if filter(r)] def select_image(name, repoman, best=False): ''' Select and load a package image from a standard naming type Allowed type are a direct filename on filesystem or [repo/]image[:version] Return the repository as second argument ''' # image is a local file if istools.isfile(name) and os.path.isfile(name): return PackageImage(name), None # we need to find image in a repository else: if len(repoman.onlines) == 0: raise Exception('No online repository') (repo, image, version) = istools.split_image_path(name) debug("Requested image: %s v%s in %s" % (image, version, repo)) # repo is not specified, we need to crawl in path if repo is None: # split search path as a list if args.repo_search is not None: args.repo_search = split_repositories(args.repo_search, lambda x: x in repoman.onlines) else: args.repo_search = [] # if we have only one repo, search in it if len(args.repo_search) == 0 and len(repoman.onlines) == 1: args.repo_search = repoman.onlines elif len(args.repo_search) == 0 and len(repoman.onlines) > 1: raise Exception('You must use a full image path or set a valid search path') return repoman.get(image, version, search=args.repo_search, best=best) # we can ask directly repository about image else: return repoman[repo].get(image, version), repoman[repo] ################################################################################ # Commands functions ################################################################################ def c_add(parser, args): ''' Add an image package into a repository ''' repoman = load_repositories(args) repo = repoman[args.repository] for image in args.path: pkg = PackageImage(image) repo.add(pkg, delete=not args.preserve) def c_build(parser, args): ''' Build an image source in current directory ''' # build start time t0 = time.time() # load source image simg = SourceImage(args.path) # do the job simg.build(force=args.force, force_payload=args.payload, check=not args.no_check) # compute building time t1 = time.time() dt = int(t1 - t0) arrow("Build time: %s" % datetime.timedelta(seconds=dt)) def c_cat(parser, args): ''' Display image's file ''' # looks if arguments is a file or image name repoman = load_repositories(args) img, repo = select_image(args.image, repoman, args.best) for filename in args.file: img.cat(filename) def c_changelog(parser, args): ''' Display image's changelog ''' # looks if arguments is a file or image name repoman = load_repositories(args) img, repo = select_image(args.image, repoman, args.best) img.changelog.show(int(img.version), args.all_version) def c_check(parser, args): ''' Sanity checks on repositories ''' repoman = load_repositories(args) for reponame in args.repository: repoman[reponame].check() def c_chroot(parser, args): ''' Helper to go cleanly inside a chroot ''' istools.chroot(args.path, shell=args.shell, mount=not args.no_mount) def c_clean(parser, args): ''' Remove unreferenced files from repoistory ''' repoman = load_repositories(args) for reponame in args.repository: repoman[reponame].clean() def c_copy(parser, args): ''' Copy an image from a repository to another one ''' repoman = load_repositories(args) dstrepo = repoman[args.repository] for image in args.image: srcimg, srcrepo = select_image(image, repoman, args.best) arrow("Copying %s v%s from repository %s to %s" % (srcimg.name, srcimg.version, srcrepo.config.name, dstrepo.config.name)) arrowlevel(1) dstrepo.add(srcimg) arrowlevel(-1) def c_del(parser, args): ''' Remove an image package from a repository ''' repoman = load_repositories(args) for image in args.image: img, repo = select_image(image, repoman, args.best) if repo is None: raise Exception("You cannot delete an image outside a repository") if not args.force: warn("The following opereation cannot be reversed!") out("You will delete %s v%s in repository %s" % (img.name, img.version, repo.config.name)) if not confirm(): raise Exception("Aborted!") repo.delete(img.name, img.version, payloads=not args.preserve) def c_diff(parser, args): ''' Show diff between two repositories or images ''' repoman = load_repositories(args) if args.object[0] in repoman.onlines and args.object[1] in repoman.onlines: Repository.diff(repoman[args.object[0]], repoman[args.object[1]]) else: img1, repo1 = select_image(args.object[0], repoman, args.best) img2, repo2 = select_image(args.object[1], repoman, args.best) PackageImage.diff(img1, img2) def c_extract(parser, args): ''' Extract an image package inside a directory ''' repoman = load_repositories(args) img, repo = select_image(args.image, repoman, args.best) img.extract(args.path, payload=args.payload, force=args.force, gendescription=args.gen_description) def c_get(parser, args): ''' Download a remote image in current directory ''' repoman = load_repositories(args) for image in args.image: img, repo = select_image(image, repoman, args.best) img.download(".", image=not args.no_image, payload=args.payload, force=args.force) def c_help(parser, args): ''' Show help ''' if args.command not in args.subparser.choices: parser.print_help() else: args.subparser.choices[args.command].print_help() def c_info(parser, args): ''' Get info about an image ''' repoman = load_repositories(args) for image in args.image: img, repo = select_image(image, repoman, args.best) img.show(verbose=args.verbose, changelog=args.changelog) def c_init(parser, args): ''' Create an empty repository ''' repoman = load_repositories(args) for reponame in args.repository: repoman[reponame].init() def c_install(parser, args): ''' Install an image ''' # remove old image args args.parser._remove_action([d for d in args.parser._actions if d.dest == "image"][0]) # create a subparser for current image to have a sexy display of args #args.image = "roger" subparser = args.parser.add_subparsers().add_parser(args.image) # select image to install repoman = load_repositories(args) img = select_image(args.image, repoman, args.best)[0] # Print setup information arrow("Installing %s v%s" % (img.name, img.version)) # install start time t0 = time.time() # run parser scripts with parser parser argument img.run_parser(parser=subparser) # call parser again, with extended attributes arrow("Parsing arguments") #parser.print_help() args = parser.parse_args() # run setup scripts img.run_setup(namespace=args) # compute building time t1 = time.time() dt = int(t1 - t0) arrow("Install time: %s" % datetime.timedelta(seconds=dt)) def c_list(parser, args): ''' List images in repository or image content ''' repoman = load_repositories(args) if args.search is not None: args.search = split_repositories(args.repo_search, lambda x: x in repoman.onlines) for pattern in args.image: repoman.show_images(pattern, all_version=args.all_version, search=args.search, o_long=args.long, o_json=args.json, o_md5=args.md5, o_date=args.date, o_author=args.author, o_size=args.size, o_url=args.url, o_description=args.description) def c_move(parser, args): ''' Move an image from a repository to another one ''' repoman = load_repositories(args) dstrepo = repoman[args.repository] for image in args.image: srcimg, srcrepo = select_image(image, repoman, args.best) if not args.force: out("You will move %s v%s from %s to %s" % (srcimg.name, srcimg.version, srcrepo.config.name, dstrepo.config.name)) if not confirm(): raise Exception("Aborted!") arrow("Moving %s v%s from repository %s to %s" % (srcimg.name, srcimg.version, srcrepo.config.name, dstrepo.config.name)) arrowlevel(1) dstrepo.add(srcimg) srcrepo.delete(srcimg.name, srcimg.version) arrowlevel(-1) def c_new(parser, args): ''' Create a new source image ''' SourceImage.create(args.path, args.force) def c_prepare_chroot(parser, args): ''' Prepare a chroot ''' istools.prepare_chroot(args.path, mount=not args.no_mount) def c_repo(parser, args): ''' Get information about repositories ''' repoman = load_repositories(args) for pattern in args.repository: repoman.show_repos(pattern, online=args.online, local=args.local, url=args.url, state=args.state) def c_search(parser, args): ''' Search in repository ''' repoman = load_repositories(args) repoman.search(args.pattern) def c_unprepare_chroot(parser, args): ''' Remove preparation of a chroot ''' istools.unprepare_chroot(args.path, mount=not args.no_umount) def c_version(parser, args): ''' Print installsystems version ''' out(installsystems.version) ################################################################################ # Parser definition ################################################################################ # Top level argument parsing p_main = argparse.ArgumentParser() p_main.add_argument("-V", "--version", action="version", version=installsystems.version, help="show installsystems version") # exclusive group on debug/quiet ex_group = p_main.add_mutually_exclusive_group() ex_group.add_argument("-d", "--debug", action="store_true", default=None, help="active debug mode") ex_group.add_argument("-q", "--quiet", action="store_true", default=None, help="active quiet mode") # common options p_main.add_argument("-c", "--config", default="installsystems", metavar="PATH", help="config file path") p_main.add_argument("-R", "--repo-config", default="repository", metavar="REPO", help="repository config file path") p_main.add_argument("-s", "--repo-search", metavar="REPO,REPO,...", help="search for images inside those repositories") p_main.add_argument("-f", "--repo-filter", metavar="REPO,REPO,...", help="filter repositories by name") p_main.add_argument("-r", "--repo-path", metavar="PATH", help="define a temporary repository") p_main.add_argument("-C", "--cache", metavar="PATH", help="path of the repository cache") p_main.add_argument("--no-cache", action="store_true", default=None, help="not use persistent db caching") p_main.add_argument("--no-color", action="store_true", default=None, help="dot not display colored output") p_main.add_argument("-t", "--timeout", dest="timeout", type=int, default=None, metavar="SECONDS", help="download timeout (default 3)") # create a subparsers for each command subparsers = p_main.add_subparsers() # add command parser p_add = subparsers.add_parser("add", help=c_add.__doc__.lower()) p_add.add_argument("-p", "--preserve", action="store_true", default=False, help="don't remove image after adding to database") p_add.add_argument("repository", help="repository where images will be added") p_add.add_argument("path", nargs="+", help="image path") p_add.set_defaults(func=c_add) # build command parser p_build = subparsers.add_parser("build", help=c_build.__doc__.lower()) p_build.add_argument("-c", "--no-check", action="store_true", default=False, help="do not check compilation before adding scripts") p_build.add_argument("-f", "--force", action="store_true", default=False, help="rebuild image if already exists") p_build.add_argument("-p", "--payload", action="store_true", default=False, help="rebuild payloads if already exists") p_build.add_argument("path", nargs="?", default=".") p_build.set_defaults(func=c_build) # cat command parser p_cat = subparsers.add_parser("cat", help=c_cat.__doc__.lower()) p_cat.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_cat.add_argument("image", help="<path|[repository/]image[:version]>") p_cat.add_argument("file", nargs="+", help="file inside image to cat (globbing allowed)") p_cat.set_defaults(func=c_cat) # changelog command parser p_changelog = subparsers.add_parser("changelog", help=c_changelog.__doc__.lower()) p_changelog.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_changelog.add_argument("-v", "--all-version", action="store_true", default=False, help="display changelog for all versions") p_changelog.add_argument("image", help="<path|[repository/]image[:version]>") p_changelog.set_defaults(func=c_changelog) # check command parser p_check = subparsers.add_parser("check", help=c_check.__doc__.lower()) p_check.add_argument("repository", nargs="+", help="repositories to check") p_check.set_defaults(func=c_check) # chroot command parser p_chroot = subparsers.add_parser("chroot", help=c_chroot.__doc__.lower()) p_chroot.add_argument("-m", "--no-mount", action="store_true", default=False, help="disable mouting of /{proc,dev,sys} inside chroot") p_chroot.add_argument("-s", "--shell", default="/bin/bash", help="shell to call inside chroot") p_chroot.add_argument("path") p_chroot.set_defaults(func=c_chroot) # clean command parser p_clean = subparsers.add_parser("clean", help=c_clean.__doc__.lower()) p_clean.add_argument("repository", nargs="+", help="repositories to clean") p_clean.set_defaults(func=c_clean) # copy command parser p_copy = subparsers.add_parser("copy", help=c_copy.__doc__.lower()) p_copy.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_copy.add_argument("image", nargs="+", help="image syntax is <path|[repository/]image[:version]>") p_copy.add_argument("repository", help="destination repository") p_copy.set_defaults(func=c_copy) # del command parser p_del = subparsers.add_parser("del", help=c_del.__doc__.lower()) p_del.add_argument("image", nargs="+", help="image syntax is <path|[repository/]image[:version]>") p_del.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_del.add_argument("-f", "--force", action="store_true", default=False, help="delete image without confirmation") p_del.add_argument("-p", "--preserve", action="store_true", default=False, help="preserve payloads. doesn't remove it from repository") p_del.set_defaults(func=c_del) # diff command parser p_diff = subparsers.add_parser("diff", help=c_diff.__doc__.lower()) p_diff.add_argument("object", nargs=2, help="object syntax is <path|repository|[repository/]image[:version]>") p_diff.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_diff.set_defaults(func=c_diff) # extract command parser p_extract = subparsers.add_parser("extract", help=c_extract.__doc__.lower()) p_extract.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_extract.add_argument("-f", "--force", action="store_true", default=False, help="overwrite existing destinations") p_extract.add_argument("-g", "--gen-description", action="store_true", default=False, help="generate a description file from metadata") p_extract.add_argument("-p", "--payload", action="store_true", default=False, help="extract payloads") p_extract.add_argument("image", help="image syntax is <path|[repository/]image[:version]>") p_extract.add_argument("path", help="image will be extracted in path") p_extract.set_defaults(func=c_extract) # get command parser p_get = subparsers.add_parser("get", help=c_get.__doc__.lower()) p_get.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_get.add_argument("-f", "--force", action="store_true", default=False, help="overwrite existing destinations") p_get.add_argument("-I", "--no-image", action="store_true", default=False, help="do not get image") p_get.add_argument("-p", "--payload", action="store_true", default=False, help="get payloads") p_get.add_argument("image", nargs="+", help="image syntax is <path|[repository/]image[:version]>") p_get.set_defaults(func=c_get) # help command parser p_help = subparsers.add_parser("help", help=c_help.__doc__.lower()) p_help.add_argument("command", nargs="?", help="command name") p_help.set_defaults(func=c_help, subparser=subparsers) # info command parser p_info = subparsers.add_parser("info", help=c_info.__doc__.lower()) p_info.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_info.add_argument("-c", "--changelog", action="store_true", default=False, help="display image changelog") p_info.add_argument("-v", "--verbose", action="store_true", default=False, help="verbose output") p_info.add_argument("image", nargs="+", help="image syntax is <path|[repository/]image[:version]>") p_info.set_defaults(func=c_info) # init command parser p_init = subparsers.add_parser("init", help=c_init.__doc__.lower()) p_init.add_argument("repository", nargs="+", help="repository to initialize") p_init.set_defaults(func=c_init) # install command parser p_install = subparsers.add_parser("install", add_help=False, help=c_install.__doc__.lower()) p_install.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_install.add_argument("image", help="image syntax is <path|[repository/]image[:version]>") p_install.set_defaults(func=c_install, parser=p_install) # list command parser p_list = subparsers.add_parser("list", help=c_list.__doc__.lower()) p_list.add_argument("-a", "--all-version", action="store_true", default=False, help="list all versions of the same image") p_list.add_argument("-A", "--author", action="store_true", default=False, help="display image author") p_list.add_argument("-d", "--date", action="store_true", default=False, help="display image date") p_list.add_argument("-D", "--description", action="store_true", default=False, help="display image description") p_list.add_argument("-j", "--json", action="store_true", default=False, help="long display") p_list.add_argument("-l", "--long", action="store_true", default=False, help="long display") p_list.add_argument("-m", "--md5", action="store_true", default=False, help="display image md5") p_list.add_argument("--no-sync", action="store_true", default=False, help="doesn't sync repository before listing") p_list.add_argument("-s", "--size", action="store_true", default=False, help="display image size") p_list.add_argument("-u", "--url", action="store_true", default=False, help="display image url") p_list.add_argument("-S", "--search", action="store_true", default=None, help="only list image in search path") p_list.add_argument("image", nargs="*", default=['*'], help="image syntax is [repository/]image[:version]") p_list.set_defaults(func=c_list) # move command parser p_move = subparsers.add_parser("move", help=c_move.__doc__.lower()) p_move.add_argument("-b", "--best", action="store_true", default=False, help="take the most recent image in all searchable repositories") p_move.add_argument("-f", "--force", action="store_true", default=False, help="move image without confirmation") p_move.add_argument("image", nargs="+", help="image syntax is <path|[repository/]image[:version]>") p_move.add_argument("repository", help="destination repository") p_move.set_defaults(func=c_move) # new command parser p_new = subparsers.add_parser("new", help=c_new.__doc__.lower()) p_new.add_argument("-f", "--force", action="store_true", default=False, help="overwrite existing source image") p_new.add_argument("path", help="new image directory path") p_new.set_defaults(func=c_new) # prepare_chroot command parser p_prepare_chroot = subparsers.add_parser("prepare_chroot", help=c_prepare_chroot.__doc__.lower()) p_prepare_chroot.add_argument("-m", "--no-mount", action="store_true", default=False, help="disable mouting of /{proc,dev,sys}") p_prepare_chroot.add_argument("path") p_prepare_chroot.set_defaults(func=c_prepare_chroot) # repo command parser p_repo = subparsers.add_parser("repo", help=c_repo.__doc__.lower()) p_repo.add_argument("--force-offline", action="store_true", default=False, help="force repository to be offline") p_repo_mgroup = p_repo.add_mutually_exclusive_group() p_repo_mgroup.add_argument("-l", "--local", action="store_true", default=None, help="list local repository (filter)") p_repo_mgroup.add_argument("-r", "--remote", action="store_false", dest="local", help="list remote repository (filter)") p_repo_mgroup = p_repo.add_mutually_exclusive_group() p_repo_mgroup.add_argument("-o", "--online", action="store_true", default=None, help="list online repository (filter)") p_repo_mgroup.add_argument("-O", "--offline", action="store_false", dest="online", help="list offline repository (filter)") p_repo.add_argument("-s", "--state", action="store_true", default=False, help="display repository state (online/offline/local/remote)") p_repo.add_argument("-u", "--url", action="store_true", default=False, help="display repository url") p_repo.add_argument("repository", nargs='*', default=["*"], help="repository pattern") p_repo.set_defaults(func=c_repo) # search command parser p_search = subparsers.add_parser("search", help=c_search.__doc__.lower()) p_search.add_argument("pattern", help="pattern to search in repositories") p_search.set_defaults(func=c_search) # unprepare_chroot command parser p_unprepare_chroot = subparsers.add_parser("unprepare_chroot", help=c_unprepare_chroot.__doc__.lower()) p_unprepare_chroot.add_argument("-m", "--no-umount", action="store_true", default=False, help="disable unmouting of /{proc,dev,sys}") p_unprepare_chroot.add_argument("path") p_unprepare_chroot.set_defaults(func=c_unprepare_chroot) # version command parser p_version = subparsers.add_parser("version", help=c_version.__doc__.lower()) p_version.set_defaults(func=c_version) ################################################################################ # Main ################################################################################ try: # first (partial) parsing args = p_main.parse_known_args()[0] # set debug and quiet mode before merge installsystems.debug = args.debug installsystems.quiet = args.quiet # load isinstall config config = MainConfigFile(args.config, "installsystems") config.merge(args) # set debug and quiet mode after merge installsystems.debug = args.debug installsystems.quiet = args.quiet # no warning if we are not in debug mode if not installsystems.debug: warnings.filterwarnings("ignore") # disable coloring if asked if args.no_color: installsystems.printer.NOCOLOR = True # except for install command we parse all args! # install command is responsible of parsing if args.func is not c_install: args = p_main.parse_args(namespace=args) # let's go args.func(p_main, args) except Exception as e: error(e) except KeyboardInterrupt: warn("Keyboard Interrupted") exit(1)