1#!/usr/bin/env python 2# 3# Copyright 2018 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16"""Common code used by acloud setup tools.""" 17 18from __future__ import print_function 19import logging 20import re 21import subprocess 22 23from acloud import errors 24 25 26logger = logging.getLogger(__name__) 27 28PKG_INSTALL_CMD = "sudo apt-get --assume-yes install %s" 29APT_CHECK_CMD = "LANG=en_US.UTF-8 apt-cache policy %s" 30_INSTALLED_RE = re.compile(r"(.*\s*Installed:)(?P<installed_ver>.*\s?)") 31_CANDIDATE_RE = re.compile(r"(.*\s*Candidate:)(?P<candidate_ver>.*\s?)") 32 33 34def CheckCmdOutput(cmd, print_cmd=True, **kwargs): 35 """Helper function to run subprocess.check_output. 36 37 This function will return the command output for parsing the result and will 38 raise Error if command return code was non-zero. 39 40 Args: 41 cmd: String, the cmd string. 42 print_cmd: True to print cmd to stdout. 43 kwargs: Other option args to subprocess. 44 45 Returns: 46 Return cmd output as a byte string. 47 If the return code was non-zero it raises a CalledProcessError. 48 """ 49 if print_cmd: 50 print("Run command: %s" % cmd) 51 52 logger.debug("Run command: %s", cmd) 53 return subprocess.check_output(cmd, **kwargs) 54 55 56def InstallPackage(pkg): 57 """Install package. 58 59 Args: 60 pkg: String, the name of package. 61 62 Raises: 63 PackageInstallError: package is not installed. 64 """ 65 try: 66 print(CheckCmdOutput(PKG_INSTALL_CMD % pkg, 67 shell=True, 68 stderr=subprocess.STDOUT)) 69 except subprocess.CalledProcessError as cpe: 70 logger.error("Package install for %s failed: %s", pkg, cpe.output) 71 raise errors.PackageInstallError( 72 "Could not install package [" + pkg + "], :" + str(cpe.output)) 73 74 if not PackageInstalled(pkg, compare_version=False): 75 raise errors.PackageInstallError( 76 "Package was not detected as installed after installation [" + 77 pkg + "]") 78 79 80def PackageInstalled(pkg_name, compare_version=True): 81 """Check if the package is installed or not. 82 83 This method will validate that the specified package is installed 84 (via apt cache policy) and check if the installed version is up-to-date. 85 86 Args: 87 pkg_name: String, the package name. 88 compare_version: Boolean, True to compare version. 89 90 Returns: 91 True if package is installed.and False if not installed or 92 the pre-installed package is not the same version as the repo candidate 93 version. 94 95 Raises: 96 UnableToLocatePkgOnRepositoryError: Unable to locate package on repository. 97 """ 98 try: 99 pkg_info = CheckCmdOutput( 100 APT_CHECK_CMD % pkg_name, 101 print_cmd=False, 102 shell=True, 103 stderr=subprocess.STDOUT) 104 105 logger.debug("Check package install status") 106 logger.debug(pkg_info) 107 except subprocess.CalledProcessError as error: 108 # Unable locate package name on repository. 109 raise errors.UnableToLocatePkgOnRepositoryError( 110 "Could not find package [" + pkg_name + "] on repository, :" + 111 str(error.output) + ", have you forgotten to run 'apt update'?") 112 113 installed_ver = None 114 candidate_ver = None 115 for line in pkg_info.splitlines(): 116 match = _INSTALLED_RE.match(line) 117 if match: 118 installed_ver = match.group("installed_ver").strip() 119 continue 120 match = _CANDIDATE_RE.match(line) 121 if match: 122 candidate_ver = match.group("candidate_ver").strip() 123 continue 124 125 # package isn't installed 126 if installed_ver == "(none)": 127 logger.debug("Package is not installed, status is (none)") 128 return False 129 # couldn't find the package 130 if not (installed_ver and candidate_ver): 131 logger.debug("Version info not found [installed: %s ,candidate: %s]", 132 installed_ver, 133 candidate_ver) 134 return False 135 # TODO(148116924):Setup process should ask user to update package if the 136 # minimax required version is specified. 137 if compare_version and installed_ver != candidate_ver: 138 logger.warning("Package %s version at %s, expected %s", 139 pkg_name, installed_ver, candidate_ver) 140 return True 141