Newer
Older
# 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/>.
'''
InstallSystems Command line Tool
'''
from argparse import ArgumentParser
from datetime import timedelta
from installsystems import VERSION
from installsystems.config import MainConfigFile, RepoConfigFile
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
################################################################################
Load repositories on a repository manager
'''
# remove cache is asked
# split filter and search in list
args.repo_filter = Repository.split_list(args.repo_filter)
args.repo_search = Repository.split_list(args.repo_search)
repoman = RepositoryManager(args.cache, timeout=args.repo_timeout or args.timeout,
filter=args.repo_filter, search=args.repo_search)
# register repositories (order matter)
if args.repo_path != "":
repoconf = RepositoryConfig(smd5sum(args.repo_path)[:8],
path=args.repo_path)
repoman.register(repoconf, temp=True, nosync=args.no_sync)
Seblu
committed
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
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
################################################################################
Add packaged images into a repository
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)
Build a source image in the current directory
for path in args.paths:
arrow("Build %s" % path)
# chdir inside path if --chdir
if args.chdir:
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)
arrow(u"Build time: %s" % timedelta(seconds=dt))
arrow(u"Global build time: %s" % timedelta(seconds=gdt))
Display files inside a packaged image
image = next(get_images([args.pattern], repoman, min=1, max=1))[0]
images = list(get_images(args.pattern, repoman, min=1))
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)
'''
repoman = load_repositories(args)
for reponame in args.repository:
try:
repoman[reponame].check()
except IndexError as e:
raise ISError(e)
'''
Helper to go cleanly inside a chroot
'''
chroot(args.path, shell=args.shell, mount=not args.no_mount)
Remove unreferenced files from repositories
repoman = load_repositories(args)
for reponame in args.repository:
try:
repoman[reponame].clean(args.force)
except IndexError as e:
raise ISError(e)
'''
Copy an image from a repository to another one
'''
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)
if not confirm():
# 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))
arrowlevel(1)
dstrepo.add(srcimg)
arrowlevel(-1)
'''
Remove an image package from a repository
'''
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))
# 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)
Show difference between two repositories or packaged images
'''
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)
img = get_images(args.object, repoman, min=2, max=2)
img1, repo1 = next(img)
img2, repo2 = next(img)
Extract a packaged image inside a directory
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)
Get packaged images from repository to current directory
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)
'''
Show help
'''
if args.command not in args.subparser.choices:
else:
args.subparser.choices[args.command].print_help()
if args.all:
args.payloads = True
args.files = True
args.changelog = True
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)
'''
repoman = load_repositories(args)
for reponame in args.repository:
try:
repoman[reponame].init()
except IndexError as e:
raise ISError(e)
# 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)
arrow(u"Install time: %s" % timedelta(seconds=dt))
List packaged images in repositories
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)
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)
Move packaged image from a repository to another one
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)
# 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))
arrowlevel(1)
dstrepo.add(srcimg)
srcrepo.delete(srcimg.name, srcimg.version)
arrowlevel(-1)
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)
Helper to prepare a path to be chrooted
prepare_chroot(args.path, mount=not args.no_mount)
# in cleaning mode we doesn't needs to sync repositories
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)
Search for packaged images in repositories
'''
repoman = load_repositories(args)
def c_unprepare_chroot(args):
Helper to remove chroot preparation of a path
unprepare_chroot(args.path, mount=not args.no_umount)
Upgrade repository's to the last version
'''
repoman = load_repositories(args)
repoman[args.repository].upgrade()
def arg_parser_init():
'''
Create command parser
'''
parser.add_argument("-V", "--version", action="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,
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")
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",
Nicolas Delvaux
committed
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")
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())
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())
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="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",
p.add_argument("-p", "--payload", action="store_true",
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")
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",
p.add_argument("-d", "--date", action="store_true",
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",
p.add_argument("-m", "--md5", action="store_true",
p.add_argument("-s", "--size", action="store_true",
p.add_argument("-u", "--url", action="store_true",
p.add_argument("pattern", nargs="*", default=[],
help="[repository/][image][:version]")
# 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")
# 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")
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",
Nicolas Delvaux
committed
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",
Nicolas Delvaux
committed
help="disable unmounting of /{proc,dev,sys}")
p.add_argument("path")
p.set_defaults(func=c_unprepare_chroot)
# upgrade_db command parser
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:
arg_parser = arg_parser_init()
# encode command line arguments to utf-8
# 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
# 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]
if options.nice is not None or options.ionice_class is not None:
if options.nice is not None:
try:
proc.nice = options.nice
debug("Setting nice to %d" % options.nice)
warn(u"Unable to nice process to %s" % options.nice)
if options.ionice_class is not None:
"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))
warn(u"Unable to ionice process to %s" % options.ionice_class)
# set timeout option
if options.timeout is not None:
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)
error("Unable to decode some characters. Check your locale settings.")
except KeyboardInterrupt:
warn("Keyboard Interrupted")
exit(1)
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()