Skip to content
is 31 KiB
Newer Older
#!/usr/bin/python
Seblu's avatar
Seblu committed
# -*- python -*-
Seblu's avatar
Seblu committed
# -*- 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 argparse
Seblu's avatar
Seblu committed
import installsystems
import installsystems.printer
Seblu's avatar
Seblu committed
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):
Seblu's avatar
Seblu committed
    '''
    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_repository_list(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=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=args.no_sync,
                         offline=hasattr(args, "force_offline") and args.force_offline)
def split_repository_list(repolist, filter=None):
    Return a list of repository from an comma/spaces separated names of repo
    '''
    if filter is None:
        filter = Repository.is_repository_name
    return [r for r in  re.split("[ ,\n\t\v]+", repolist) if filter(r)]
def show_repositories(repoman, pattern, local=None, online=None,
                      url=False, state=True):
    '''
    Show repository inside manager
    if :param online: is true, list only online repositories
    if :param online: is false, list only offline repostiories
    if :param online: is None, list both online and offline repostiories.
    if :param local: is true, list only local repositories
    if :param local: is false, list only remote repostiories
    if :param local: is None, list both local and remote repostiories.
    '''
    for reponame in fnmatch.filter(repoman.names, pattern):
        repo = repoman[reponame]
        if repo.config.offline and online is True:
            continue
        if not repo.config.offline and online is False:
            continue
        if repo.local and local is False:
            continue
        if not repo.local and local is True:
            continue
        so = "#l##r#Off#R# " if repo.config.offline else "#l##g#On#R#  "
        sl = "#l##y#Local#R#  " if repo.local else "#l##c#Remote#R# "
        rc = "#l##r#" if repo.config.offline else "#l##g#"
        s = ""
        if state:
            s +=  "%s%s " % (so, sl)
            rc = "#l##b#"
        s += "%s%s#R#"% (rc, repo.config.name)
        if url:
            s += "  (%s)" % repo.config.path
        out(s)

def select_image(name, repoman, search=None, 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
        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 search is not None:
                search = split_repository_list(search,
                                            lambda x: x in repoman.onlines)
            # if we have only one repo, search in it
            if len(search) == 0 and len(repoman.onlines) == 1:
                search = repoman.onlines
            elif len(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=search, best=best)
        # we can ask directly repository about image
        else:
            return repoman[repo].get(image, version), repoman[repo]

def show_images(repoman, pattern, all_version=True, search=None,
                o_json=False, o_long=False,
                o_md5=False, o_date=False, o_author=False, o_size=False,
                o_url=False, o_description=False):
    '''
    Show repository inside manager
    json: display output in json
    long: display output in long format
    all images parameter can be given in arguments to displayed
    '''
    # get image list
    images = repoman.images(pattern, all_version, search)
    # display result
    if o_json:
        s = json.dumps(images)
    else:
        l = []
        for imgp in sorted(images.keys()):
            img = images[imgp]
            l.append(u"%s#R#/#l##b#%s#R#:#p#%s#R#" % (
                    img["repo"], img["name"], img["version"]))
            if o_md5 or o_long:
                l[-1] = l[-1] + u" (#y#%s#R#)" % img["md5"]
            if o_date or o_long:
                l.append(u"  #l#date:#R# %s" % istools.time_rfc2822(img["date"]))
            if o_author or o_long:
                l.append(u"  #l#author:#R# %s" % img["author"])
            if o_size or o_long:
                l.append(u"  #l#size:#R# %s" % istools.human_size(img["size"]))
            if o_url or o_long:
                l.append(u"  #l#url:#R# %s" % img["url"])
            if o_description or o_long:
                l.append(u"  #l#description:#R# %s" % img["description"])
        s = os.linesep.join(l)
    if len(s) > 0:
        out(s)

################################################################################
# Commands functions
################################################################################

def c_add(args):
Seblu's avatar
Seblu committed
    '''
    Add an image package into a repository
Seblu's avatar
Seblu committed
    '''
    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(args):
Seblu's avatar
Seblu committed
    '''
    Build an image source in current directory
Seblu's avatar
Seblu committed
    '''
Seblu's avatar
Seblu committed
    # 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)
Seblu's avatar
Seblu committed
    # compute building time
    t1 = time.time()
    dt = int(t1 - t0)
    arrow("Build time: %s" % datetime.timedelta(seconds=dt))
def c_cat(args):
Seblu's avatar
Seblu committed
    Display image's file
    # looks if arguments is a file or image name
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
    img, repo = select_image(args.image, repoman,
                             search=args.repo_search, best=args.best)
Seblu's avatar
Seblu committed
    for filename in args.file:
        img.cat(filename)
def c_changelog(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,
                             search=args.repo_search, best=args.best)
    img.changelog.show(int(img.version), args.all_version)
def c_check(args):
Matthieu Gonnet's avatar
Matthieu Gonnet committed
    '''
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    Sanity checks on repositories
Matthieu Gonnet's avatar
Matthieu Gonnet committed
    '''
    repoman = load_repositories(args)
    for reponame in args.repository:
        repoman[reponame].check()

def c_chroot(args):
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    '''
    Helper to go cleanly inside a chroot
    '''
    istools.chroot(args.path, shell=args.shell, mount=not args.no_mount)

def c_clean(args):
Seblu's avatar
Seblu committed
    '''
Seblu's avatar
Seblu committed
    Remove unreferenced files from repoistory
Seblu's avatar
Seblu committed
    '''
Matthieu Gonnet's avatar
Matthieu Gonnet committed
    repoman = load_repositories(args)
    for reponame in args.repository:
        repoman[reponame].clean()
def c_copy(args):
    '''
    Copy an image from a repository to another one
    '''
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
Seblu's avatar
Seblu committed
    dstrepo = repoman[args.repository]
    for image in args.image:
        srcimg, srcrepo = select_image(image, repoman,
                                       search=args.repo_search, best=args.best)
Seblu's avatar
Seblu committed
        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(args):
Seblu's avatar
Seblu committed
    '''
    Remove an image package from a repository
    '''
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
    for image in args.image:
        img, repo = select_image(image, repoman,
                                 search=args.repo_search, best=args.best)
        if repo is None:
            raise Exception("You cannot delete an image outside a repository")
Seblu's avatar
Seblu committed
        if not args.force:
Sebastien Luttringer's avatar
Sebastien Luttringer committed
            warn("The following operation 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(args):
Seblu's avatar
Seblu committed
    '''
    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,
                                   search=args.repo_search, best=args.best)
        img2, repo2 = select_image(args.object[1], repoman,
                                   search=args.repo_search, best=args.best)
Seblu's avatar
Seblu committed
        PackageImage.diff(img1, img2)

def c_extract(args):
Seblu's avatar
Seblu committed
    '''
    Extract an image package inside a directory
    '''
    repoman = load_repositories(args)
    img, repo = select_image(args.image, repoman,
                             search=args.repo_search, best=args.best)
    img.extract(args.path, payload=args.payload, force=args.force,
                gendescription=args.gen_description)
Seblu's avatar
Seblu committed

def c_get(args):
Seblu's avatar
Seblu committed
    Download a remote image in current directory
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
    for image in args.image:
        img, repo = select_image(image, repoman,
                                 search=args.repo_search, best=args.best)
        img.download(".", image=not args.no_image, payload=args.payload, force=args.force)
def c_help(args):
    '''
    Show help
    '''
    if args.command not in args.subparser.choices:
        args.parser.print_help()
    else:
        args.subparser.choices[args.command].print_help()

def c_info(args):
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    '''
    Get info about an image
    '''
    repoman = load_repositories(args)
    for image in args.image:
        img, repo = select_image(image, repoman,
                                 search=args.repo_search, best=args.best)
Sebastien Luttringer's avatar
Sebastien Luttringer committed
        img.show(verbose=args.verbose, changelog=args.changelog)

def c_init(args):
    '''
    Create an empty repository
    '''
    repoman = load_repositories(args)
    for reponame in args.repository:
        repoman[reponame].init()

def c_install(args):
Seblu's avatar
Seblu committed
    '''
    Install an image
    '''
    # remove old image args
    args.install_parser._remove_action(
        [d for d in args.install_parser._actions if d.dest == "image"][0])
    # create a subparser for current image to have a sexy display of args
    subparser = args.install_parser.add_subparsers().add_parser(args.image)
    # select image to install
    repoman = load_repositories(args)
    img = select_image(args.image, repoman,
                       search=args.repo_search, best=args.best)[0]
Seblu's avatar
Seblu committed
    # Print setup information
    arrow("Installing %s v%s" % (img.name, img.version))
Seblu's avatar
Seblu committed
    # install start time
    t0 = time.time()
    # run parser scripts with parser parser argument
    img.run_parser(parser=subparser)
Seblu's avatar
Seblu committed
    # call parser again, with extended attributes
    arrow("Parsing arguments")
    args = args.parser.parse_args()
Seblu's avatar
Seblu committed
    # run setup scripts
    img.run_setup(namespace=args)
Seblu's avatar
Seblu committed
    # compute building time
    t1 = time.time()
    dt = int(t1 - t0)
    arrow("Install time: %s" % datetime.timedelta(seconds=dt))

def c_list(args):
Aurélien Dunand's avatar
Aurélien Dunand committed
    '''
    List images in repository or image content
    '''
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
    if args.repo_search is None:
        search = repoman.onlines
    else:
        search = split_repository_list(args.repo_search, lambda x: x in repoman.onlines)
    for pattern in args.image:
        show_images(repoman, pattern,
                    all_version=args.all_version, search=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)
Aurélien Dunand's avatar
Aurélien Dunand committed

def c_move(args):
Seblu's avatar
Seblu committed
    '''
    Move an image from a repository to another one
    '''
    repoman = load_repositories(args)
Seblu's avatar
Seblu committed
    dstrepo = repoman[args.repository]
    for image in args.image:
        srcimg, srcrepo = select_image(image, repoman,
                                       search=args.repo_search, best=args.best)
Seblu's avatar
Seblu committed
        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!")
Seblu's avatar
Seblu committed
        arrow("Moving %s v%s from repository %s to %s" % (srcimg.name,
                                                          srcimg.version,
                                                          srcrepo.config.name,
                                                          dstrepo.config.name))
Seblu's avatar
Seblu committed
        arrowlevel(1)
        dstrepo.add(srcimg)
        srcrepo.delete(srcimg.name, srcimg.version)
        arrowlevel(-1)
Seblu's avatar
Seblu committed

def c_new(args):
Seblu's avatar
Seblu committed
    '''
    Create a new source image
Seblu's avatar
Seblu committed
    '''
    SourceImage.create(args.path, args.force)
def c_prepare_chroot(args):
    '''
    Prepare a chroot
    '''
    istools.prepare_chroot(args.path, mount=not args.no_mount)

def c_repo(args):
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    '''
    Get information about repositories
    '''
    # in cleaning mode we doesn't needs to get repositories
    if args.purge:
        args.force_offline = True
    repoman = load_repositories(args)
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    for pattern in args.repository:
        if args.purge:
            repoman.purge_cache(pattern)
        else:
            show_repositories(repoman, pattern,
                              online=args.online, local=args.local,
                              url=args.url, state=args.state)
def c_search(args):
    '''
    Search in repository
    '''
    repoman = load_repositories(args)
    repoman.search(args.pattern)
def c_unprepare_chroot(args):
    '''
    Remove preparation of a chroot
    '''
    istools.unprepare_chroot(args.path, mount=not args.no_umount)

def c_version(args):
Seblu's avatar
Seblu committed
    '''
    Print installsystems version
    '''
    out(installsystems.version)

def parser_init():
    '''
    Create command parser
    '''
    # Top level argument parsing
    parser = argparse.ArgumentParser()
    parser.add_argument("-V", "--version", action="version",
                        version=installsystems.version,
                        help="show installsystems version")
    # exclusive group on debug/quiet
    g = parser.add_mutually_exclusive_group()
    g.add_argument("-d", "--debug", action="store_true", default=None,
                   help="active debug mode")
    g.add_argument("-q", "--quiet", action="store_true", default=None,
                   help="active quiet mode")
    # common options
    parser.add_argument("-c", "--config", default="installsystems", metavar="PATH",
                        help="config file path")
    parser.add_argument("-R", "--repo-config", default="repository", metavar="REPO",
                        help="repository config file path")
    parser.add_argument("-s", "--repo-search", metavar="REPO,REPO,...",
                        help="search for images inside those repositories")
    parser.add_argument("-f", "--repo-filter", metavar="REPO,REPO,...",
                        help="filter repositories by name")
    parser.add_argument("-r", "--repo-path", metavar="PATH",
                        help="define a temporary repository")
    parser.add_argument("-C", "--cache", metavar="PATH",
                        help="path of repositories cache")
    parser.add_argument("-t", "--timeout", dest="timeout", type=int, default=None,
                        metavar="SECONDS", help="download timeout (default 3)")
    parser.add_argument("--no-cache", action="store_true", default=None,
                        help="not use persistent database caching")
    parser.add_argument("--no-sync", action="store_true", default=False,
                        help="doesn't sync repository database cache")
    parser.add_argument("--no-color", action="store_true", default=None,
                        help="dot not display colored output")
    # create a subparser for commands
    subparser = parser.add_subparsers()
    # add command parser
    p =  subparser.add_parser("add", help=c_add.__doc__.lower())
    p.add_argument("-p", "--preserve", action="store_true", default=False,
                   help="don't remove image after adding to database")
    p.add_argument("repository", help="repository where images will be added")
    p.add_argument("path", nargs="+", help="image path")
    p.set_defaults(func=c_add)
    # build command parser
    p = subparser.add_parser("build", help=c_build.__doc__.lower())
    p.add_argument("-c", "--no-check", action="store_true", default=False,
                   help="do not check compilation before adding scripts")
    p.add_argument("-f", "--force", action="store_true", default=False,
                   help="rebuild image if already exists")
    p.add_argument("-p", "--payload", action="store_true", default=False,
                   help="rebuild payloads if already exists")
    p.add_argument("path", nargs="?", default=".")
    p.set_defaults(func=c_build)
    # cat command parser
    p = subparser.add_parser("cat", help=c_cat.__doc__.lower())
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("image", help="<path|[repository/]image[:version]>")
    p.add_argument("file", nargs="+",
                   help="file inside image to cat (globbing allowed)")
    p.set_defaults(func=c_cat)
    # changelog command parser
    p = subparser.add_parser("changelog", help=c_changelog.__doc__.lower())
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("-v", "--all-version",  action="store_true", default=False,
                   help="display changelog for all versions")
    p.add_argument("image", help="<path|[repository/]image[:version]>")
    p.set_defaults(func=c_changelog)
    # check command parser
    p = subparser.add_parser("check", help=c_check.__doc__.lower())
    p.add_argument("repository", nargs="+", help="repositories to check")
    p.set_defaults(func=c_check)
    # chroot command parser
    p = subparser.add_parser("chroot", help=c_chroot.__doc__.lower())
    p.add_argument("-m", "--no-mount", action="store_true", default=False,
                   help="disable mouting of /{proc,dev,sys} inside chroot")
    p.add_argument("-s", "--shell", default="/bin/bash",
                   help="shell to call inside chroot")
    p.add_argument("path")
    p.set_defaults(func=c_chroot)
    # clean command parser
    p = subparser.add_parser("clean", help=c_clean.__doc__.lower())
    p.add_argument("repository", nargs="+", help="repositories to clean")
    p.set_defaults(func=c_clean)
    # copy command parser
    p = subparser.add_parser("copy", help=c_copy.__doc__.lower())
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("image", nargs="+",
                   help="image syntax is <path|[repository/]image[:version]>")
    p.add_argument("repository", help="destination repository")
    p.set_defaults(func=c_copy)
    # del command parser
    p =  subparser.add_parser("del", help=c_del.__doc__.lower())
    p.add_argument("image", nargs="+",
                   help="image syntax is <path|[repository/]image[:version]>")
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("-f", "--force", action="store_true", default=False,
                   help="delete image without confirmation")
    p.add_argument("-p", "--preserve", action="store_true", default=False,
                   help="preserve payloads. doesn't remove it from repository")
    p.set_defaults(func=c_del)
    # diff command parser
    p = subparser.add_parser("diff", help=c_diff.__doc__.lower())
    p.add_argument("object", nargs=2,
                   help="object syntax is <path|repository|[repository/]image[:version]>")
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.set_defaults(func=c_diff)
    # extract command parser
    p = subparser.add_parser("extract", help=c_extract.__doc__.lower())
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("-f", "--force", action="store_true", default=False,
                   help="overwrite existing destinations")
    p.add_argument("-g", "--gen-description", action="store_true", default=False,
                   help="generate a description file from metadata")
    p.add_argument("-p", "--payload", action="store_true", default=False,
                   help="extract payloads")
    p.add_argument("image",
                   help="image syntax is <path|[repository/]image[:version]>")
    p.add_argument("path", help="image will be extracted in path")
    p.set_defaults(func=c_extract)
    # get command parser
    p = subparser.add_parser("get", help=c_get.__doc__.lower())
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("-f", "--force", action="store_true", default=False,
                   help="overwrite existing destinations")
    p.add_argument("-I", "--no-image", action="store_true", default=False,
                   help="do not get image")
    p.add_argument("-p", "--payload", action="store_true", default=False,
                   help="get payloads")
    p.add_argument("image", nargs="+",
                   help="image syntax is <path|[repository/]image[:version]>")
    p.set_defaults(func=c_get)
    # help command parser
    p = subparser.add_parser("help", help=c_help.__doc__.lower())
    p.add_argument("command", nargs="?", help="command name")
    p.set_defaults(func=c_help, parser=parser, subparser=subparser)
    # info command parser
    p = subparser.add_parser("info", help=c_info.__doc__.lower())
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("-c", "--changelog", action="store_true", default=False,
                   help="display image changelog")
    p.add_argument("-v", "--verbose", action="store_true", default=False,
                   help="verbose output")
    p.add_argument("image", nargs="+",
                   help="image syntax is <path|[repository/]image[:version]>")
    p.set_defaults(func=c_info)
    # init command parser
    p = subparser.add_parser("init", help=c_init.__doc__.lower())
    p.add_argument("repository", nargs="+",
                   help="repository to initialize")
    p.set_defaults(func=c_init)
    # install command parser
    p = subparser.add_parser("install", add_help=False,
                              help=c_install.__doc__.lower())
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("image",
                   help="image syntax is <path|[repository/]image[:version]>")
    p.set_defaults(func=c_install, parser=parser, install_parser=p)
    # list command parser
    p = subparser.add_parser("list", help=c_list.__doc__.lower())
    p.add_argument("-a", "--all-version", action="store_true", default=False,
                   help="list all versions of the same image")
    p.add_argument("-A", "--author", action="store_true", default=False,
                   help="display image author")
    p.add_argument("-d", "--date", action="store_true", default=False,
                   help="display image date")
    p.add_argument("-D", "--description", action="store_true", default=False,
                   help="display image description")
    p.add_argument("-j", "--json", action="store_true", default=False,
                   help="output is formated in json")
    p.add_argument("-l", "--long", action="store_true", default=False,
                   help="long display")
    p.add_argument("-m", "--md5", action="store_true", default=False,
                   help="display image md5")
    p.add_argument("-s", "--size", action="store_true", default=False,
                   help="display image size")
    p.add_argument("-u", "--url", action="store_true", default=False,
                   help="display image url")
    p.add_argument("image", nargs="*", default=['*'],
                   help="image syntax is [repository/]image[:version]")
    p.set_defaults(func=c_list)
    # move command parser
    p = subparser.add_parser("move", help=c_move.__doc__.lower())
    p.add_argument("-b", "--best", action="store_true", default=False,
                   help="take the most recent image in all searchable repositories")
    p.add_argument("-f", "--force", action="store_true", default=False,
                   help="move image without confirmation")
    p.add_argument("image", nargs="+",
                   help="image syntax is <path|[repository/]image[:version]>")
    p.add_argument("repository", help="destination repository")
    p.set_defaults(func=c_move)
    # new command parser
    p = subparser.add_parser("new", help=c_new.__doc__.lower())
    p.add_argument("-f", "--force", action="store_true", default=False,
                   help="overwrite existing source image")
    p.add_argument("path", help="new image directory path")
    p.set_defaults(func=c_new)
    # prepare_chroot command parser
    p = subparser.add_parser("prepare_chroot",
                              help=c_prepare_chroot.__doc__.lower())
    p.add_argument("-m", "--no-mount", action="store_true", default=False,
                   help="disable mouting of /{proc,dev,sys}")
    p.add_argument("path")
    p.set_defaults(func=c_prepare_chroot)
    # repo command parser
    p = subparser.add_parser("repo", help=c_repo.__doc__.lower())
    g = p.add_mutually_exclusive_group()
    g.add_argument("-l", "--local", action="store_true", default=None,
                   help="list local repository (filter)")
    g.add_argument("-r", "--remote", action="store_false", dest="local",
                   help="list remote repository (filter)")
    g = p.add_mutually_exclusive_group()
    g.add_argument("-o", "--online", action="store_true", default=None,
                   help="list online repository (filter)")
    g.add_argument("-O", "--offline", action="store_false", dest="online",
                   help="list offline repository (filter)")
    p.add_argument("-s", "--state", action="store_true", default=False,
                   help="display repository state (online/offline/local/remote)")
    p.add_argument("-u", "--url", action="store_true", default=False,
                   help="display repository url")
    p.add_argument("--purge", action="store_true", default=False,
                   help="remove cache databases")
    p.add_argument("--force-offline", action="store_true", default=False,
                   help="force repository to be offline")
    p.add_argument("repository", nargs='*', default=["*"], help="repository pattern")
    p.set_defaults(func=c_repo)
    # search command parser
    p = subparser.add_parser("search", help=c_search.__doc__.lower())
    p.add_argument("pattern", help="pattern to search in repositories")
    p.set_defaults(func=c_search)
    # unprepare_chroot command parser
    p = subparser.add_parser("unprepare_chroot",
                              help=c_unprepare_chroot.__doc__.lower())
    p.add_argument("-m", "--no-umount", action="store_true", default=False,
                   help="disable unmouting of /{proc,dev,sys}")
    p.add_argument("path")
    p.set_defaults(func=c_unprepare_chroot)
    # version command parser
    p = subparser.add_parser("version", help=c_version.__doc__.lower())
    p.set_defaults(func=c_version)
    # return main parser
    return parser

def main():
    '''
    Program main
    '''
    try:
        parser = parser_init()
        # first (partial) parsing
        args = parser.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 = parser.parse_args(namespace=args)
        # let's go
        args.func(args)
        exit(0)
    except Exception as e:
        error(e)
    except KeyboardInterrupt:
        warn("Keyboard Interrupted")
        exit(1)

# Entry point
if __name__ == '__main__':
    main()