Skip to content
is 33 KiB
Newer Older
#!/usr/bin/python
Seblu's avatar
Seblu committed
# -*- coding: utf-8 -*-
# This file is part of Installsystems.
# Installsystems is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Installsystems is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with Installsystems.  If not, see <http://www.gnu.org/licenses/>.
Seblu's avatar
Seblu committed

'''
InstallSystems Command line Tool
'''

Sébastien Luttringer's avatar
Sébastien Luttringer committed
from argparse import ArgumentParser
from datetime import timedelta
from installsystems import VERSION
Seblu's avatar
Seblu committed
from installsystems.config import MainConfigFile, RepoConfigFile
Sébastien Luttringer's avatar
Sébastien Luttringer committed
from installsystems.exception import ISError, ISException
from installsystems.image import PackageImage, SourceImage
from installsystems.printer import arrow, arrowlevel, setmode
from installsystems.printer import out, warn, error, debug, confirm
from installsystems.repository import Repository, RepositoryManager, RepositoryConfig
from installsystems.tools import chroot, prepare_chroot, unprepare_chroot
from installsystems.tools import isfile, smd5sum, argv
from os import getpid, getcwdu, chdir
from psutil import IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE
from psutil import Process, IOPRIO_CLASS_NONE
from socket import setdefaulttimeout

# used by os.path.isfile
import os
################################################################################
# Common functions
################################################################################

def load_repositories(args):
Seblu's avatar
Seblu committed
    '''
    Load repositories on a repository manager
    '''
    # remove cache is asked
    if args.no_cache:
        args.cache = None
    # split filter and search in list
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    args.repo_filter = Repository.split_list(args.repo_filter)
    args.repo_search = Repository.split_list(args.repo_search)
    # init repo cache object
    repoman = RepositoryManager(args.cache, timeout=args.repo_timeout or args.timeout,
                                filter=args.repo_filter, search=args.repo_search)
    # register repositories (order matter)
    # load repo configs from command line
    if args.repo_path != "":
Sébastien Luttringer's avatar
Sébastien Luttringer committed
        repoconf = RepositoryConfig(smd5sum(args.repo_path)[:8],
                                    path=args.repo_path)
        repoman.register(repoconf, temp=True, nosync=args.no_sync)
    # load repo configs from config
    for repoconf in RepoConfigFile(args.repo_config).repos:
        repoman.register(repoconf,  nosync=args.no_sync)
def get_images(patterns, repoman, local=True, min=None, max=None):
    '''
    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
    '''
    for pattern in patterns:
        # check if image is a local file
Sébastien Luttringer's avatar
Sébastien Luttringer committed
        if local and isfile(pattern) and os.path.isfile(pattern):
            ans.append((pattern, None))
        else: # we need to find image in a repository
            ans += sorted(repoman.select_images([pattern]).items())
    # check selected images cound
    if min is not None and len(ans) < min:
        raise ISError(u"%s images found. Should be at least %s" % (
                len(ans), min))
    # check max selected images
    if max is not None and  len(ans) > max:
        raise ISError(u"Too many selected images: %s. Max is %s" % (
                ", ".join([n[0] for n in ans]), max))
    for item in ans:
        if item[1] is None:
            yield PackageImage(item[0]), None
            try:
                yield repoman[r["repo"]].get(r["name"], r["version"]), repoman[r["repo"]]
            except IndexError as e:
                raise ISError(e)
################################################################################
# Commands functions
################################################################################

def c_add(args):
Seblu's avatar
Seblu committed
    '''
    Add packaged images into a repository
Seblu's avatar
Seblu committed
    '''
    repoman = load_repositories(args)
    try:
        repo = repoman[args.repository]
    except IndexError as e:
        raise ISError(e)
    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 a source image in the current directory
Seblu's avatar
Seblu committed
    '''
    for path in args.paths:
        arrow("Build %s" % path)
        # chdir inside path if --chdir
        if args.chdir:
Sébastien Luttringer's avatar
Sébastien Luttringer committed
            cwd = getcwdu()
            chdir(path)
            path = "."
        arrowlevel(1)
        # load source image
        simg = SourceImage(path)
        # do the job
        dt = simg.build(force=args.force, force_payload=args.payload,
                        check=not args.no_check, script=not args.no_script)
Sébastien Luttringer's avatar
Sébastien Luttringer committed
        arrow(u"Build time: %s" % timedelta(seconds=dt))
        if args.chdir:
Sébastien Luttringer's avatar
Sébastien Luttringer committed
            chdir(cwd)
    if len(args.paths) > 1:
Sébastien Luttringer's avatar
Sébastien Luttringer committed
        arrow(u"Global build time: %s" % timedelta(seconds=gdt))
def c_cat(args):
    Display files inside a packaged image
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    image = next(get_images([args.pattern], repoman, min=1, max=1))[0]
Seblu's avatar
Seblu committed
    for filename in args.file:
        image.cat(filename)
def c_changelog(args):
    Display changelog of packaged images
    '''
    repoman = load_repositories(args)
    images = list(get_images(args.pattern, repoman, min=1))
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    for image in images:
        if len(images) > 1:
            out("--- #yellow#image: %s v%s#reset#" % (image.name, image.version))
        if args.all_version:
            image.changelog.show_all()
        else:
            image.changelog.show(image.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:
        try:
            repoman[reponame].check()
        except IndexError as e:
            raise ISError(e)
Matthieu Gonnet's avatar
Matthieu Gonnet committed

def c_chroot(args):
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    '''
    Helper to go cleanly inside a chroot
    '''
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    chroot(args.path, shell=args.shell, mount=not args.no_mount)
def c_clean(args):
Seblu's avatar
Seblu committed
    '''
    Remove unreferenced files from repositories
Seblu's avatar
Seblu committed
    '''
Matthieu Gonnet's avatar
Matthieu Gonnet committed
    repoman = load_repositories(args)
    for reponame in args.repository:
        try:
            repoman[reponame].clean(args.force)
        except IndexError as e:
            raise ISError(e)
def c_copy(args):
    '''
    Copy an image from a repository to another one
    '''
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
    try:
        dstrepo = repoman[args.repository]
    except IndexError as e:
        raise ISError(e)
    todo = list(get_images(args.pattern, repoman, local=False, min=1))
    # check user really want to this
    if not args.force:
        out("You will copy the following images:")
        for img, repo in todo:
            out(u"  %s/%s:%s" % (repo.config.name, img.name, img.version))
        out(u"Inside repository: #l##b#%s#R#" % dstrepo.config.name)
            raise ISError("Aborted!")
    # copy it for real
    for srcimg, srcrepo in todo:
        arrow("Copying %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)
        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)
    todo = list(get_images(args.pattern, repoman, local=False, min=1))
    # check all source repository are local (need by deletion)
    for img, repo in todo:
        if not repo.local:
            raise ISError("Repository %s is not local. Unable to delete" %
                            repo.config.name)
    # check user really want to this
    if not args.force:
        out("You will remove the following images:")
        for img, repo in todo:
            out(u"  %s/%s:%s" % (repo.config.name, img.name, img.version))
        if not confirm():
            raise ISError("Aborted!")
    # delete it for real
    for img, repo in todo:
        arrow("Deleting %s v%s from repository %s" %
              (img.name, img.version, repo.config.name))
        arrowlevel(1)
        repo.delete(img.name, img.version, payloads=not args.preserve)
        arrowlevel(-1)
def c_diff(args):
Seblu's avatar
Seblu committed
    '''
    Show difference between two repositories or packaged images
Seblu's avatar
Seblu committed
    '''
    repoman = load_repositories(args)
    if args.object[0] in repoman.onlines and args.object[1] in repoman.onlines:
        try:
            Repository.diff(repoman[args.object[0]], repoman[args.object[1]])
        except IndexError as e:
            raise ISError(e)
Seblu's avatar
Seblu committed
    else:
        img = get_images(args.object, repoman, min=2, max=2)
        img1, repo1 = next(img)
        img2, repo2 = next(img)
Seblu's avatar
Seblu committed
        PackageImage.diff(img1, img2)

def c_extract(args):
Seblu's avatar
Seblu committed
    '''
    Extract a packaged image inside a directory
Seblu's avatar
Seblu committed
    '''
    repoman = load_repositories(args)
    for image, repo in get_images([args.pattern], repoman, min=1, max=1):
        image.extract(args.path, payload=args.payload, force=args.force,
                      gendescription=args.gen_description)
Seblu's avatar
Seblu committed

def c_get(args):
    Get packaged images from repository to current directory
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
    for image, repo in get_images(args.pattern, repoman, local=False, min=1):
        image.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
    '''
    Display info about packaged images
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    '''
    if args.all:
        args.payloads = True
        args.files = True
        args.changelog = True
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    repoman = load_repositories(args)
    for image, repo in get_images(args.pattern, repoman, min=1):
        image.show(o_files=args.files, o_payloads=args.payloads,
                   o_changelog=args.changelog, o_json=args.json)
def c_init(args):
    Initialize an empty repository
    '''
    repoman = load_repositories(args)
    for reponame in args.repository:
        try:
            repoman[reponame].init()
        except IndexError as e:
            raise ISError(e)
def c_install(args):
Seblu's avatar
Seblu committed
    '''
    Install a packaged image
Seblu's avatar
Seblu committed
    '''
    # remove old image args
    args.install_parser._remove_action(
        [d for d in args.install_parser._actions if d.dest == "pattern"][0])
    # create a subparser for current image to have a sexy display of args
    subparser = args.install_parser.add_subparsers().add_parser(args.pattern)
    # select image to install
    repoman = load_repositories(args)
    image, repo = next(get_images([args.pattern], repoman, min=1, max=1))
    if repo is not None:
        arrow("Repository message")
        out(repo.motd, endl="")
    # print setup information
    arrow(u"Installing %s v%s" % (image.name, image.version))
    # let's go
    dt = image.run(args.parser, subparser, run_setup=not args.dry_run)
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    arrow(u"Install time: %s" % timedelta(seconds=dt))
def c_list(args):
Aurélien Dunand's avatar
Aurélien Dunand committed
    '''
    List packaged images in repositories
Aurélien Dunand's avatar
Aurélien Dunand committed
    '''
Seblu's avatar
Seblu committed
    repoman = load_repositories(args)
    if len(args.pattern) == 0 and len(repoman.search) == 0:
        args.pattern = ["*/*"]
    elif len(args.pattern) == 0:
        args.pattern = ["*"]
    repoman.show_images(args.pattern, 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, o_format=args.format,
                        o_min_version=args.is_min_version)
Aurélien Dunand's avatar
Aurélien Dunand committed

Aurélien Dunand's avatar
Aurélien Dunand committed
def c_motd(args):
    '''
    Show and set repository's message
Aurélien Dunand's avatar
Aurélien Dunand committed
    '''
    repoman = load_repositories(args)
    if args.file:
        args.set = open(args.file, "rb").read()
    elif args.remove:
        args.set = ""
    arrowlevel(1)
    for reponame in repoman.select_repositories(args.repository):
        arrow(reponame, -1)
        try:
            if args.set is not None:
                repoman[reponame].setmotd(args.set)
            else:
                out(repoman[reponame].motd)
        except IndexError as e:
            raise ISError(e)
Aurélien Dunand's avatar
Aurélien Dunand committed

def c_move(args):
Seblu's avatar
Seblu committed
    '''
    Move packaged image from a repository to another one
Seblu's avatar
Seblu committed
    '''
    repoman = load_repositories(args)
    try:
        dstrepo = repoman[args.repository]
    except IndexError as e:
        raise ISError(e)
    todo = list(get_images(args.pattern, repoman, local=False, min=1))
    # check all source repository are local (need by deletion)
    for img, repo in todo:
        if not repo.local:
            raise ISError("Repository %s is not local. Unable to move" %
                            repo.config.name)
    # check user really want to this
    if not args.force:
        out("You will copy and remove the following images:")
        for img, repo in todo:
            out(u"  %s/%s:%s" % (repo.config.name, img.name, img.version))
        out(u"Inside repository: #l##b#%s#R#" % dstrepo.config.name)
        if not confirm():
            raise ISError("Aborted!")
    # move it for real
    for srcimg, srcrepo in todo:
        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_payload(args):
    '''
    List payloads
    '''
    repoman = load_repositories(args)
    repoman.show_payloads(args.payload, o_images=args.images, o_json=args.json)

def c_prepare_chroot(args):
    Helper to prepare a path to be chrooted
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    prepare_chroot(args.path, mount=not args.no_mount)
def c_repo(args):
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    '''
    List repositories
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    '''
    # in cleaning mode we doesn't needs to sync repositories
    if args.purge:
        args.no_sync = True
    repoman = load_repositories(args)
    if args.purge:
        repoman.purge_repositories(args.repository)
    else:
        repoman.show_repositories(args.repository,
                                  online=args.online, local=args.local,
                                  o_url=args.url, o_state=args.state,
                                  o_uuid=args.uuid, o_json=args.json,
                                  o_version=args.repo_version)
def c_search(args):
    Search for packaged images in repositories
    '''
    repoman = load_repositories(args)
Aurélien Dunand's avatar
Aurélien Dunand committed
    repoman.search_image(args.pattern)
def c_unprepare_chroot(args):
    Helper to remove chroot preparation of a path
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    unprepare_chroot(args.path, mount=not args.no_umount)
def c_upgrade(args):
    Upgrade repository's to the last version
    '''
    repoman = load_repositories(args)
    repoman[args.repository].upgrade()
def c_version(args):
Seblu's avatar
Seblu committed
    '''
    Display installsystems version
Seblu's avatar
Seblu committed
    '''
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    out(VERSION)
Seblu's avatar
Seblu committed

def arg_parser_init():
    '''
    Create command parser
    '''
    # top level argument parsing
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    parser = ArgumentParser()
    parser.add_argument("-V", "--version", action="version",
Sébastien Luttringer's avatar
Sébastien Luttringer committed
                        version=VERSION)
    # exclusive group on verbosity
    g = parser.add_mutually_exclusive_group()
    g.add_argument("-v", "--verbosity", default=1,
                   type=int, choices=[0,1,2],
                   help="define verbosity level (0: quiet, 1:normal, 2:debug)")
    g.add_argument("-d", "--debug", dest="verbosity",
                   action="store_const", const=2,
                   help="active debug mode")
    g.add_argument("-q", "--quiet", dest="verbosity",
                   action="store_const", const=0,
                   help="active quiet mode")
    # common options
    parser.add_argument("-c", "--config", default=u"installsystems",
                        metavar="PATH", help="config file path")
    parser.add_argument("-R", "--repo-config", default=u"repository",
                        metavar="REPO", help="repository config file path")
    parser.add_argument("-s", "--repo-search", default=u"",
                        metavar="REPO,REPO,...",
                        help="search for images inside those repositories")
    parser.add_argument("-f", "--repo-filter", default=u"",
                        metavar="REPO,REPO,...",
                        help="filter repositories by name")
    parser.add_argument("-r", "--repo-path", default=u"", metavar="PATH",
                        help="define a temporary repository")
    parser.add_argument("-T", "--repo-timeout", type=int, default=None,
                        metavar="SECONDS", help="repository access timeout")
    parser.add_argument("-C", "--cache", default=u"", metavar="PATH",
                        help="path of repositories cache")
    parser.add_argument("-t", "--timeout", dest="timeout", type=int, default=None,
                        metavar="SECONDS", help="socket timeout")
    parser.add_argument("--no-cache", action="store_true",
                        help="not use persistent database caching")
    parser.add_argument("--no-sync", action="store_true",
                        help="doesn't sync repository database cache")
    parser.add_argument("--no-color", action="store_true",
                        help="dot not display colored output")
    parser.add_argument("--nice", type=int, default=None,
                        help="nice of the process")
    parser.add_argument("--ionice-class", choices=["none","rt", "be","idle"],
                        help="ionice class of the process (default: none)")
    parser.add_argument("--ionice-level", type=int, default=None,
                        help="ionice class level of the process")
    # 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",
                   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="local packaged 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",
                   help="do not check compilation before adding scripts")
    p.add_argument("-C", "--chdir", action="store_true",
                   help="build image inside source image directory, not in current directory")
    p.add_argument("-f", "--force", action="store_true",
                   help="rebuild image if already exists")
    p.add_argument("-p", "--payload", action="store_true",
                   help="rebuild payloads if already exists")
Sebastien Luttringer's avatar
Sebastien Luttringer committed
    p.add_argument("-s", "--no-script", action="store_true",
                   help="doesn't execute build script")
    p.add_argument("paths", nargs="*", default=u".")
    p.set_defaults(func=c_build)
    # cat command parser
    p = subparser.add_parser("cat", help=c_cat.__doc__.lower())
    p.add_argument("pattern", 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("-v", "--all-version",  action="store_true",
                   help="display changelog for all versions")
    p.add_argument("pattern", nargs="+", 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",
                   help="disable mounting of /{proc,dev,sys} inside chroot")
    p.add_argument("-s", "--shell", default=u"/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("-f", "--force", action="store_true",
                   help="clean repository without confirmation")
    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("-f", "--force", action="store_true",
                   help="copy image without confirmation")
    p.add_argument("pattern", nargs="+",
                   help="[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("pattern", nargs="+",
                   help="[repository/][image][:version]")
    p.add_argument("-f", "--force", action="store_true",
                   help="delete image without confirmation")
    p.add_argument("-p", "--preserve", action="store_true",
                   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="+",
                   help="path|repository|[repository/][image][:version]")
    p.set_defaults(func=c_diff)
    # extract command parser
    p = subparser.add_parser("extract", help=c_extract.__doc__.lower())
    p.add_argument("-f", "--force", action="store_true",
                   help="overwrite existing destinations")
    p.add_argument("-g", "--gen-description", action="store_true",
                   help="generate a description file from metadata")
    p.add_argument("-p", "--payload", action="store_true",
                   help="extract payloads")
    p.add_argument("pattern",
                   help="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("-f", "--force", action="store_true",
                   help="overwrite existing destinations")
    p.add_argument("-I", "--no-image", action="store_true",
                   help="do not get image")
    p.add_argument("-p", "--payload", action="store_true",
                   help="get payloads")
    p.add_argument("pattern", nargs="+",
                   help="[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("-a", "--all", action="store_true",
                   help="display all information")
    p.add_argument("-j", "--json", action="store_true",
                   help="display all information formated in json")
    p.add_argument("-c", "--changelog", action="store_true",
                   help="display image changelog")
    p.add_argument("-f", "--files", action="store_true",
                   help="display image files")
    p.add_argument("-p", "--payloads", action="store_true",
                   help="display image payloads")
    p.add_argument("pattern", nargs="+",
                   help="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("--dry-run", action="store_true",
                   help="doesn't execute setup scripts")
    p.add_argument("pattern", help="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", "--author", action="store_true",
                   help="display image author")
    p.add_argument("-d", "--date", action="store_true",
                   help="display image date")
    p.add_argument("-D", "--description", action="store_true",
                   help="display image description")
    p.add_argument("-f", "--format", action="store_true",
                   help="display image format")
    p.add_argument("-i", "--is-min-version", action="store_true",
                   help="display minimum Installsystems version required")
    p.add_argument("-j", "--json", action="store_true",
                   help="output is formated in json")
    p.add_argument("-l", "--long", action="store_true",
                   help="long display")
    p.add_argument("-m", "--md5", action="store_true",
                   help="display image md5")
    p.add_argument("-s", "--size", action="store_true",
                   help="display image size")
    p.add_argument("-u", "--url", action="store_true",
                   help="display image url")
    p.add_argument("pattern", nargs="*", default=[],
                   help="[repository/][image][:version]")
    p.set_defaults(func=c_list)
Aurélien Dunand's avatar
Aurélien Dunand committed
    # motd command parser
    p = subparser.add_parser("motd", help=c_motd.__doc__.lower())
    g = p.add_mutually_exclusive_group()
    g.add_argument("-r", "--remove", action="store_true", help="remove the motd")
    g.add_argument("-s", "--set", help="set the motd from command line")
    g.add_argument("-f", "--file", help="set the motd from a file")
    p.add_argument("repository", nargs="*", default=[u"*"], help="image repository")
Aurélien Dunand's avatar
Aurélien Dunand committed
    p.set_defaults(func=c_motd)
    # move command parser
    p = subparser.add_parser("move", help=c_move.__doc__.lower())
    p.add_argument("-f", "--force", action="store_true",
                   help="move image without confirmation")
    p.add_argument("pattern", nargs="+",
                   help="[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",
                   help="overwrite existing source image")
    p.add_argument("path", help="new image directory path")
    p.set_defaults(func=c_new)
    # payload command parser
    p = subparser.add_parser("payload", help=c_payload.__doc__.lower())
    p.add_argument("-j", "--json", action="store_true",
                   help="output is formated in json")
    p.add_argument("-i", "--images", action="store_true",
                   help="list images using payload")
    p.add_argument("payload", nargs='*', default=[u""],
                   help="payload md5 pattern")
    p.set_defaults(func=c_payload)
    # 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",
                   help="disable mounting 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()
    p.add_argument("-j", "--json", action="store_true",
                   help="output is formated in json")
    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",
                   help="display repository state (online/offline/local/remote)")
    p.add_argument("-u", "--url", action="store_true",
                   help="display repository url")
    p.add_argument("-U", "--uuid", action="store_true",
                   help="display repository UUID")
    p.add_argument("-v", "--version", dest="repo_version", action="store_true",
                   help="display repository version")
    p.add_argument("--purge", action="store_true",
                   help="remove cache databases")
    p.add_argument("repository", nargs='*', default=[u"*"], 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",
                   help="disable unmounting of /{proc,dev,sys}")
    p.add_argument("path")
    p.set_defaults(func=c_unprepare_chroot)
    p = subparser.add_parser("upgrade",
                             help=c_upgrade.__doc__.lower())
    p.add_argument("repository", help="repository to upgrade")
    p.set_defaults(func=c_upgrade)
    # 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:
Sébastien Luttringer's avatar
Sébastien Luttringer committed
        # by default full debug
        setmode(2)
        # init arg parser
        arg_parser = arg_parser_init()
        # encode command line arguments to utf-8
Sébastien Luttringer's avatar
Sébastien Luttringer committed
        args = argv()[1:]
        # first partial parsing, to get early debug and config path
        options = arg_parser.parse_known_args(args=args)[0]
        # set early command line verbosity and color
Sébastien Luttringer's avatar
Sébastien Luttringer committed
        setmode(options.verbosity, options.no_color)
        # load main config file options
        config_parser = MainConfigFile(options.config, "installsystems")
        options = config_parser.parse()
        # second partial parsing, command line option overwrite config file
        options = arg_parser.parse_known_args(args=args, namespace=options)[0]
        # set verbosity and color
Sébastien Luttringer's avatar
Sébastien Luttringer committed
        setmode(options.verbosity, options.no_color)
        # nice and ionice process
        if options.nice is not None or options.ionice_class is not None:
Sébastien Luttringer's avatar
Sébastien Luttringer committed
            proc = Process(getpid())
            if options.nice is not None:
                try:
                    proc.nice = options.nice
                    debug("Setting nice to %d" % options.nice)
                except Exception:
                    warn(u"Unable to nice process to %s" % options.nice)
            if options.ionice_class is not None:
Sébastien Luttringer's avatar
Sébastien Luttringer committed
                        "none": IOPRIO_CLASS_NONE,
                        "rt": IOPRIO_CLASS_RT,
                        "be": IOPRIO_CLASS_BE,
                        "idle": IOPRIO_CLASS_IDLE}
                    proc.set_ionice(ioclassmap[options.ionice_class], options.ionice_level)
                    debug(u"Setting ionice to class %s, level %s" %
                          (options.ionice_class, options.ionice_level))
                except Exception:
                    warn(u"Unable to ionice process to %s" % options.ionice_class)
        # set timeout option
        if options.timeout is not None:
Sébastien Luttringer's avatar
Sébastien Luttringer committed
            setdefaulttimeout(options.timeout)
            debug("Global timeout setted to %ds" % options.timeout)
        # except for install command we parse all args!
        # install command is responsible of parsing
        if options.func is not c_install:
            options = arg_parser.parse_args(args=args, namespace=options)
        options.func(options)
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    except UnicodeDecodeError:
        error("Unable to decode some characters. Check your locale settings.")
    except KeyboardInterrupt:
        warn("Keyboard Interrupted")
        exit(1)
Sébastien Luttringer's avatar
Sébastien Luttringer committed
    except ISException as err:
        error(exception=err)
    except Exception as err:
        error(u"Unexpected error, please report it with debug enabled", exception=err)
# Entry point
if __name__ == '__main__':
    main()