Source code for reproman.interface.install

# -*- coding: utf-8 -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the reproman package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Helper utility to install packages
"""

__docformat__ = 'restructuredtext'

from .base import Interface
from .common_opts import resref_arg
from .common_opts import resref_type_opt
from ..support.param import Parameter
from ..support.constraints import EnsureStr
from ..formats import Provenance
from ..resource import get_manager

from logging import getLogger
lgr = getLogger('reproman.api.install')


class Install(Interface):
    """Install packages according to the provided specification(s)

    Examples
    --------

      $ reproman install docker recipe_for_failure.yml

    """

    _params_ = dict(
        resref=resref_arg,
        resref_type=resref_type_opt,
        spec=Parameter(
            args=("spec",),
            doc="file with specifications (in supported formats) of"
                " packages used in executed environment",
            metavar='SPEC',
            nargs="+",
            constraints=EnsureStr(),
            # TODO:  here we need to elaborate options for sub-parsers to
            # provide options, like --no-exec, etc  per each spec
            # ACTUALLY this type doesn't work for us since it is --spec SPEC SPEC... TODO
        ),
    )

    @staticmethod
    def __call__(resref, spec, resref_type="auto"):
        # Load, while possible merging/augmenting sequentially
        assert len(spec) == 1, "For now supporting having only a single spec"
        filename = spec[0]
        provenance = Provenance.factory(filename)

        # TODO
        #  - provenance might contain a 'base' which would instruct which
        #    resource to use

        env_resource = get_manager().get_resource(resref, resref_type)
        env_resource.connect()

        #  TODOs:
        #  - resource might be a backend, so we would need to do analysis
        #    to choose a base environment (e.g. docker image) first, given
        #    provenance details
        #  - might need to create a new session if by now we have only an
        #    "image" which cannot be used as a session right away
        #  - eventually we would need to implement an analysis to determine
        #    details of the provenance.distribution(s) before passing them
        #    for initiation/installation.  That would also "kick back" on the
        #    steps above making things "tricky" ;)

        # For now we deal with simple resources providing a session
        # and a complete, exhaustive and non conflicting with the specified
        # resource
        session = env_resource.get_session()
        environment_spec = provenance.get_environment()
        for distribution in environment_spec.distributions:
            # TODO: add option to skip initiation
            distribution.initiate(session)
            distribution.install_packages(session)
        #env_resource.execute_command_buffer()
        # ??? verify that everything was installed according to the specs
        #     so would need pretty much going through the spec and querying
        #     all those packages.  If something differs -- report
        # session.close()
        if environment_spec.files:
            lgr.warning("Got extra files listed %s", environment_spec.files)