9c12812735
Some major changes: * the charm has been rebased (from a Python perspective) to be rooted in the charm directory. This is a single root. * Imports have been changed so that the don't add lots of imports to the namespace of the module doing the import. * The code that used to run at module import time has been made lazy such that it only has to run if the relevant functions are called. This includes restart_on_change parameters, the harden function and the parameters to the guard_map. Appropriate changes will be submitted to charm-helpers. * Several tests had to be re-written as (incorrect) mocking meant that text fixtures didn't actually match what the code was doing. Thus, the tests were meaningless. * This has had a net positive impact on the unit tests wrt to importing modules and mocking. Change-Id: Id07d9d1caaa9b29453a63c2e49ba831071e9457f
172 lines
5.5 KiB
Python
172 lines
5.5 KiB
Python
# Copyright 2014-2015 Canonical Limited.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import subprocess
|
|
import os
|
|
import time
|
|
import six
|
|
import yum
|
|
|
|
from tempfile import NamedTemporaryFile
|
|
from charmhelpers.core.hookenv import log
|
|
|
|
YUM_NO_LOCK = 1 # The return code for "couldn't acquire lock" in YUM.
|
|
YUM_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
|
|
YUM_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
|
|
|
|
|
|
def filter_installed_packages(packages):
|
|
"""Return a list of packages that require installation."""
|
|
yb = yum.YumBase()
|
|
package_list = yb.doPackageLists()
|
|
temp_cache = {p.base_package_name: 1 for p in package_list['installed']}
|
|
|
|
_pkgs = [p for p in packages if not temp_cache.get(p, False)]
|
|
return _pkgs
|
|
|
|
|
|
def install(packages, options=None, fatal=False):
|
|
"""Install one or more packages."""
|
|
cmd = ['yum', '--assumeyes']
|
|
if options is not None:
|
|
cmd.extend(options)
|
|
cmd.append('install')
|
|
if isinstance(packages, six.string_types):
|
|
cmd.append(packages)
|
|
else:
|
|
cmd.extend(packages)
|
|
log("Installing {} with options: {}".format(packages,
|
|
options))
|
|
_run_yum_command(cmd, fatal)
|
|
|
|
|
|
def upgrade(options=None, fatal=False, dist=False):
|
|
"""Upgrade all packages."""
|
|
cmd = ['yum', '--assumeyes']
|
|
if options is not None:
|
|
cmd.extend(options)
|
|
cmd.append('upgrade')
|
|
log("Upgrading with options: {}".format(options))
|
|
_run_yum_command(cmd, fatal)
|
|
|
|
|
|
def update(fatal=False):
|
|
"""Update local yum cache."""
|
|
cmd = ['yum', '--assumeyes', 'update']
|
|
log("Update with fatal: {}".format(fatal))
|
|
_run_yum_command(cmd, fatal)
|
|
|
|
|
|
def purge(packages, fatal=False):
|
|
"""Purge one or more packages."""
|
|
cmd = ['yum', '--assumeyes', 'remove']
|
|
if isinstance(packages, six.string_types):
|
|
cmd.append(packages)
|
|
else:
|
|
cmd.extend(packages)
|
|
log("Purging {}".format(packages))
|
|
_run_yum_command(cmd, fatal)
|
|
|
|
|
|
def yum_search(packages):
|
|
"""Search for a package."""
|
|
output = {}
|
|
cmd = ['yum', 'search']
|
|
if isinstance(packages, six.string_types):
|
|
cmd.append(packages)
|
|
else:
|
|
cmd.extend(packages)
|
|
log("Searching for {}".format(packages))
|
|
result = subprocess.check_output(cmd)
|
|
for package in list(packages):
|
|
output[package] = package in result
|
|
return output
|
|
|
|
|
|
def add_source(source, key=None):
|
|
"""Add a package source to this system.
|
|
|
|
@param source: a URL with a rpm package
|
|
|
|
@param key: A key to be added to the system's keyring and used
|
|
to verify the signatures on packages. Ideally, this should be an
|
|
ASCII format GPG public key including the block headers. A GPG key
|
|
id may also be used, but be aware that only insecure protocols are
|
|
available to retrieve the actual public key from a public keyserver
|
|
placing your Juju environment at risk.
|
|
"""
|
|
if source is None:
|
|
log('Source is not present. Skipping')
|
|
return
|
|
|
|
if source.startswith('http'):
|
|
directory = '/etc/yum.repos.d/'
|
|
for filename in os.listdir(directory):
|
|
with open(directory + filename, 'r') as rpm_file:
|
|
if source in rpm_file.read():
|
|
break
|
|
else:
|
|
log("Add source: {!r}".format(source))
|
|
# write in the charms.repo
|
|
with open(directory + 'Charms.repo', 'a') as rpm_file:
|
|
rpm_file.write('[%s]\n' % source[7:].replace('/', '_'))
|
|
rpm_file.write('name=%s\n' % source[7:])
|
|
rpm_file.write('baseurl=%s\n\n' % source)
|
|
else:
|
|
log("Unknown source: {!r}".format(source))
|
|
|
|
if key:
|
|
if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
|
|
with NamedTemporaryFile('w+') as key_file:
|
|
key_file.write(key)
|
|
key_file.flush()
|
|
key_file.seek(0)
|
|
subprocess.check_call(['rpm', '--import', key_file.name])
|
|
else:
|
|
subprocess.check_call(['rpm', '--import', key])
|
|
|
|
|
|
def _run_yum_command(cmd, fatal=False):
|
|
"""Run an YUM command.
|
|
|
|
Checks the output and retry if the fatal flag is set to True.
|
|
|
|
:param: cmd: str: The yum command to run.
|
|
:param: fatal: bool: Whether the command's output should be checked and
|
|
retried.
|
|
"""
|
|
env = os.environ.copy()
|
|
|
|
if fatal:
|
|
retry_count = 0
|
|
result = None
|
|
|
|
# If the command is considered "fatal", we need to retry if the yum
|
|
# lock was not acquired.
|
|
|
|
while result is None or result == YUM_NO_LOCK:
|
|
try:
|
|
result = subprocess.check_call(cmd, env=env)
|
|
except subprocess.CalledProcessError as e:
|
|
retry_count = retry_count + 1
|
|
if retry_count > YUM_NO_LOCK_RETRY_COUNT:
|
|
raise
|
|
result = e.returncode
|
|
log("Couldn't acquire YUM lock. Will retry in {} seconds."
|
|
"".format(YUM_NO_LOCK_RETRY_DELAY))
|
|
time.sleep(YUM_NO_LOCK_RETRY_DELAY)
|
|
|
|
else:
|
|
subprocess.call(cmd, env=env)
|