1# install.py
2# Install CLI command.
3#
4# Copyright (C) 2014-2016 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use,
7# modify, copy, or redistribute it subject to the terms and conditions of
8# the GNU General Public License v.2, or (at your option) any later version.
9# This program is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY expressed or implied, including the implied warranties of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
12# Public License for more details.  You should have received a copy of the
13# GNU General Public License along with this program; if not, write to the
14# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
16# source code or documentation are not subject to the GNU General Public
17# License and may only be used or replicated with the express permission of
18# Red Hat, Inc.
19#
20
21from __future__ import absolute_import
22from __future__ import unicode_literals
23
24import logging
25from itertools import chain
26
27import hawkey
28
29import dnf.exceptions
30from dnf.cli import commands
31from dnf.cli.option_parser import OptionParser
32from dnf.i18n import _
33
34logger = logging.getLogger('dnf')
35
36
37class InstallCommand(commands.Command):
38    """A class containing methods needed by the cli to execute the
39    install command.
40    """
41    nevra_forms = {'install-n': hawkey.FORM_NAME,
42                   'install-na': hawkey.FORM_NA,
43                   'install-nevra': hawkey.FORM_NEVRA}
44    alternatives_provide = 'alternative-for({})'
45
46    aliases = ('install', 'localinstall', 'in') + tuple(nevra_forms.keys())
47    summary = _('install a package or packages on your system')
48
49    @staticmethod
50    def set_argparser(parser):
51        parser.add_argument('package', nargs='+', metavar=_('PACKAGE'),
52                            action=OptionParser.ParseSpecGroupFileCallback,
53                            help=_('Package to install'))
54
55    def configure(self):
56        """Verify that conditions are met so that this command can run.
57        That there are enabled repositories with gpg keys, and that
58        this command is called with appropriate arguments.
59        """
60        demands = self.cli.demands
61        demands.sack_activation = True
62        demands.available_repos = True
63        demands.resolving = True
64        demands.root_user = True
65        commands._checkGPGKey(self.base, self.cli)
66        if not self.opts.filenames:
67            commands._checkEnabledRepo(self.base)
68
69    def run(self):
70        err_pkgs = []
71        errs = []
72        error_module_specs = []
73
74        nevra_forms = self._get_nevra_forms_from_command()
75
76        self.cli._populate_update_security_filter(self.opts)
77        if self.opts.command == 'localinstall' and (self.opts.grp_specs or self.opts.pkg_specs):
78            self._log_not_valid_rpm_file_paths(self.opts.grp_specs)
79            if self.base.conf.strict:
80                raise dnf.exceptions.Error(_('Nothing to do.'))
81        skipped_grp_specs = []
82        if self.opts.grp_specs and self.opts.command != 'localinstall':
83            if dnf.base.WITH_MODULES:
84                try:
85                    module_base = dnf.module.module_base.ModuleBase(self.base)
86                    module_base.install(self.opts.grp_specs, strict=self.base.conf.strict)
87                except dnf.exceptions.MarkingErrors as e:
88                    if e.no_match_group_specs:
89                        for e_spec in e.no_match_group_specs:
90                            skipped_grp_specs.append(e_spec)
91                    if e.error_group_specs:
92                        for e_spec in e.error_group_specs:
93                            error_module_specs.append("@" + e_spec)
94                    module_depsolv_errors = e.module_depsolv_errors
95                    if module_depsolv_errors:
96                        logger.error(dnf.module.module_base.format_modular_solver_errors(
97                            module_depsolv_errors[0]))
98            else:
99                skipped_grp_specs = self.opts.grp_specs
100        if self.opts.filenames and nevra_forms:
101            self._inform_not_a_valid_combination(self.opts.filenames)
102            if self.base.conf.strict:
103                raise dnf.exceptions.Error(_('Nothing to do.'))
104        else:
105            err_pkgs = self._install_files()
106
107        if skipped_grp_specs and nevra_forms:
108            self._inform_not_a_valid_combination(skipped_grp_specs)
109            if self.base.conf.strict:
110                raise dnf.exceptions.Error(_('Nothing to do.'))
111        elif skipped_grp_specs and self.opts.command != 'localinstall':
112            self._install_groups(skipped_grp_specs)
113
114        if self.opts.command != 'localinstall':
115            errs = self._install_packages(nevra_forms)
116
117        if (len(errs) != 0 or len(err_pkgs) != 0 or error_module_specs) and self.base.conf.strict:
118            raise dnf.exceptions.PackagesNotAvailableError(_("Unable to find a match"),
119                                                           pkg_spec=' '.join(errs),
120                                                           packages=err_pkgs)
121
122    def _get_nevra_forms_from_command(self):
123        if self.opts.command in self.nevra_forms:
124            return [self.nevra_forms[self.opts.command]]
125        else:
126            return []
127
128    def _log_not_valid_rpm_file_paths(self, grp_specs):
129        group_names = map(lambda g: '@' + g, grp_specs)
130        for pkg in chain(self.opts.pkg_specs, group_names):
131            msg = _('Not a valid rpm file path: %s')
132            logger.info(msg, self.base.output.term.bold(pkg))
133
134    def _inform_not_a_valid_combination(self, forms):
135        for form in forms:
136            msg = _('Not a valid form: %s')
137            logger.warning(msg, self.base.output.term.bold(form))
138
139    def _install_files(self):
140        err_pkgs = []
141        strict = self.base.conf.strict
142        for pkg in self.base.add_remote_rpms(self.opts.filenames, strict=strict,
143                                             progress=self.base.output.progress):
144            try:
145                self.base.package_install(pkg, strict=strict)
146            except dnf.exceptions.MarkingError:
147                msg = _('No match for argument: %s')
148                logger.info(msg, self.base.output.term.bold(pkg.location))
149                err_pkgs.append(pkg)
150
151        return err_pkgs
152
153    def _install_groups(self, grp_specs):
154        try:
155            self.base.env_group_install(grp_specs,
156                                        tuple(self.base.conf.group_package_types),
157                                        strict=self.base.conf.strict)
158        except dnf.exceptions.Error:
159            if self.base.conf.strict:
160                raise
161
162    def _report_alternatives(self, pkg_spec):
163        query = self.base.sack.query().filterm(
164            provides=self.alternatives_provide.format(pkg_spec))
165        if query:
166            msg = _('There are following alternatives for "{0}": {1}')
167            logger.info(msg.format(
168                pkg_spec,
169                ', '.join(sorted(set([alt.name for alt in query])))))
170
171    def _install_packages(self, nevra_forms):
172        errs = []
173        strict = self.base.conf.strict
174        for pkg_spec in self.opts.pkg_specs:
175            try:
176                self.base.install(pkg_spec, strict=strict, forms=nevra_forms)
177            except dnf.exceptions.MarkingError as e:
178                msg = '{}: {}'.format(e.value, self.base.output.term.bold(pkg_spec))
179                logger.info(msg)
180                self.base._report_icase_hint(pkg_spec)
181                self._report_alternatives(pkg_spec)
182                errs.append(pkg_spec)
183
184        return errs
185