~adam-collard/landscape-charm/install-sources-keys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import os
import subprocess
import yaml

from six.moves.urllib.parse import urlparse, urlunparse

from charmhelpers.core import hookenv

from lib.error import CharmError


def is_valid_url(value):
    """
    A helper to validate a string is a URL suitable to use as root-url.
    """
    if not value[-1] == "/":
        return False
    if not value.startswith("http"):
        return False
    if "://" not in value:
        return False

    return True


def get_required_data(manager, service_name, key):
    """Get the service manager required_data entry matching the given key.

    This function will scan the required_data of the given ServiceManager
    and search for an entry matching the given key.

    @param manager: A ServiceManager instance.
    @param service_name: The name of the service on which to access data. In
        this charm's case, it is always "landscape", but simply passing the
        service_name given to a callback's __call__ is safer.
    @param key: The key for the particular value we are looking for inside all
        of the service's required_data.
    """
    service = manager.get_service(service_name)
    for data in service["required_data"]:
        if key in data:
            return data[key]


def update_persisted_data(key, value, hookenv=hookenv):
    """Persist the given 'value' for the given 'key' and return the old value.

    This function manages a local key->value store that can be used to persist
    data and compare it against previous versions.

    @param key: The key to update.
    @param value: The value to persist.
    @return: The old value of the key, or None if it's a new value.
    """
    filename = os.path.join(hookenv.charm_dir(), ".landscape-persisted-data")
    if os.path.exists(filename):
        with open(filename) as fd:
            data = yaml.load(fd)
    else:
        data = {}
    old = data.get(key, None)
    data[key] = value
    with open(filename, "w") as fd:
        data = yaml.dump(data, fd)
    return old


def get_archive_url(config_data):
    """
    Return the archive URL (absolute or relative).

    If root-url is set, prepends "archive." to the hostname, otherwise
    returns a string "RELATIVE".
    """
    root_url = config_data.get("root-url")
    if root_url:
        parsed_url = urlparse(root_url)
        netloc = "archive." + parsed_url.netloc
        return urlunparse(
            (parsed_url.scheme, netloc, parsed_url.path, parsed_url.params,
             parsed_url.query, parsed_url.fragment))
    return "RELATIVE"


class CommandRunner(object):
    """A landscape-charm-specific wrapper around subprocess.

    All calls are logged and failures are converted into CharmError (as
    well as logging the return code).
    """

    def __init__(self, hookenv=hookenv, subprocess=subprocess):
        self._hookenv = hookenv
        self._subprocess = subprocess
        self._cwd = None

    def in_dir(self, dirname):
        """Return a new runner that runs commands in the given directory."""
        runner = self.__class__(hookenv=self._hookenv,
                                subprocess=self._subprocess)
        runner._cwd = dirname
        return runner

    def _run(self, args, shell=False):
        kwargs = {}
        if shell:
            kwargs['shell'] = True
        if self._cwd:
            kwargs['cwd'] = self._cwd
        cmdstr = args if isinstance(args, str) else ' '.join(args)

        self._hookenv.log('running {!r}'.format(cmdstr),
                          level=hookenv.DEBUG)
        try:
            self._subprocess.check_call(args, **kwargs)
        except subprocess.CalledProcessError as err:
            self._hookenv.log('got return code {} running {!r}'
                              .format(err.returncode, cmdstr),
                              level=hookenv.ERROR)
            raise CharmError('command failed (see unit logs): {}'
                             .format(cmdstr))

    def run(self, cmd, *args):
        """Run cmd with the given arguments."""
        args = list(args)
        args.insert(0, cmd)
        self._run(args)

    def shell(self, script):
        """Run a shell script."""
        self._run(script, shell=True)