~brianlbaird/charms/trusty/pcrf/trunk

« back to all changes in this revision

Viewing changes to deps/layer/layer-basic/lib/charms/layer/basic.py

  • Committer: brian baird
  • Date: 2016-08-24 18:05:17 UTC
  • Revision ID: brianlbaird@gmail.com-20160824180517-uyp6100mfwuj6les
dt demo

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os
 
2
import sys
 
3
import shutil
 
4
import platform
 
5
from glob import glob
 
6
from subprocess import check_call
 
7
 
 
8
from charms.layer.execd import execd_preinstall
 
9
 
 
10
 
 
11
def bootstrap_charm_deps():
 
12
    """
 
13
    Set up the base charm dependencies so that the reactive system can run.
 
14
    """
 
15
    # execd must happen first, before any attempt to install packages or
 
16
    # access the network, because sites use this hook to do bespoke
 
17
    # configuration and install secrets so the rest of this bootstrap
 
18
    # and the charm itself can actually succeed. This call does nothing
 
19
    # unless the operator has created and populated $CHARM_DIR/exec.d.
 
20
    execd_preinstall()
 
21
    # ensure that $CHARM_DIR/bin is on the path, for helper scripts
 
22
    os.environ['PATH'] += ':%s' % os.path.join(os.environ['CHARM_DIR'], 'bin')
 
23
    venv = os.path.abspath('../.venv')
 
24
    vbin = os.path.join(venv, 'bin')
 
25
    vpip = os.path.join(vbin, 'pip')
 
26
    vpy = os.path.join(vbin, 'python')
 
27
    if os.path.exists('wheelhouse/.bootstrapped'):
 
28
        from charms import layer
 
29
        cfg = layer.options('basic')
 
30
        if cfg.get('use_venv') and '.venv' not in sys.executable:
 
31
            # activate the venv
 
32
            os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
 
33
            reload_interpreter(vpy)
 
34
        return
 
35
    # bootstrap wheelhouse
 
36
    if os.path.exists('wheelhouse'):
 
37
        with open('/root/.pydistutils.cfg', 'w') as fp:
 
38
            # make sure that easy_install also only uses the wheelhouse
 
39
            # (see https://github.com/pypa/pip/issues/410)
 
40
            charm_dir = os.environ['CHARM_DIR']
 
41
            fp.writelines([
 
42
                "[easy_install]\n",
 
43
                "allow_hosts = ''\n",
 
44
                "find_links = file://{}/wheelhouse/\n".format(charm_dir),
 
45
            ])
 
46
        apt_install(['python3-pip', 'python3-setuptools', 'python3-yaml'])
 
47
        from charms import layer
 
48
        cfg = layer.options('basic')
 
49
        # include packages defined in layer.yaml
 
50
        apt_install(cfg.get('packages', []))
 
51
        # if we're using a venv, set it up
 
52
        if cfg.get('use_venv'):
 
53
            if not os.path.exists(venv):
 
54
                distname, version, series = platform.linux_distribution()
 
55
                if series in ('precise', 'trusty'):
 
56
                    apt_install(['python-virtualenv'])
 
57
                else:
 
58
                    apt_install(['virtualenv'])
 
59
                cmd = ['virtualenv', '-ppython3', '--never-download', venv]
 
60
                if cfg.get('include_system_packages'):
 
61
                    cmd.append('--system-site-packages')
 
62
                check_call(cmd)
 
63
            os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
 
64
            pip = vpip
 
65
        else:
 
66
            pip = 'pip3'
 
67
            # save a copy of system pip to prevent `pip3 install -U pip`
 
68
            # from changing it
 
69
            if os.path.exists('/usr/bin/pip'):
 
70
                shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
 
71
        # need newer pip, to fix spurious Double Requirement error:
 
72
        # https://github.com/pypa/pip/issues/56
 
73
        check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
 
74
                    'pip'])
 
75
        # install the rest of the wheelhouse deps
 
76
        check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
 
77
                   glob('wheelhouse/*'))
 
78
        if not cfg.get('use_venv'):
 
79
            # restore system pip to prevent `pip3 install -U pip`
 
80
            # from changing it
 
81
            if os.path.exists('/usr/bin/pip.save'):
 
82
                shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
 
83
                os.remove('/usr/bin/pip.save')
 
84
        os.remove('/root/.pydistutils.cfg')
 
85
        # flag us as having already bootstrapped so we don't do it again
 
86
        open('wheelhouse/.bootstrapped', 'w').close()
 
87
        # Ensure that the newly bootstrapped libs are available.
 
88
        # Note: this only seems to be an issue with namespace packages.
 
89
        # Non-namespace-package libs (e.g., charmhelpers) are available
 
90
        # without having to reload the interpreter. :/
 
91
        reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
 
92
 
 
93
 
 
94
def reload_interpreter(python):
 
95
    """
 
96
    Reload the python interpreter to ensure that all deps are available.
 
97
 
 
98
    Newly installed modules in namespace packages sometimes seemt to
 
99
    not be picked up by Python 3.
 
100
    """
 
101
    os.execle(python, python, sys.argv[0], os.environ)
 
102
 
 
103
 
 
104
def apt_install(packages):
 
105
    """
 
106
    Install apt packages.
 
107
 
 
108
    This ensures a consistent set of options that are often missed but
 
109
    should really be set.
 
110
    """
 
111
    if isinstance(packages, (str, bytes)):
 
112
        packages = [packages]
 
113
 
 
114
    env = os.environ.copy()
 
115
 
 
116
    if 'DEBIAN_FRONTEND' not in env:
 
117
        env['DEBIAN_FRONTEND'] = 'noninteractive'
 
118
 
 
119
    cmd = ['apt-get',
 
120
           '--option=Dpkg::Options::=--force-confold',
 
121
           '--assume-yes',
 
122
           'install']
 
123
    check_call(cmd + packages, env=env)
 
124
 
 
125
 
 
126
def init_config_states():
 
127
    import yaml
 
128
    from charmhelpers.core import hookenv
 
129
    from charms.reactive import set_state
 
130
    from charms.reactive import toggle_state
 
131
    config = hookenv.config()
 
132
    config_defaults = {}
 
133
    config_defs = {}
 
134
    config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
 
135
    if os.path.exists(config_yaml):
 
136
        with open(config_yaml) as fp:
 
137
            config_defs = yaml.safe_load(fp).get('options', {})
 
138
            config_defaults = {key: value.get('default')
 
139
                               for key, value in config_defs.items()}
 
140
    for opt in config_defs.keys():
 
141
        if config.changed(opt):
 
142
            set_state('config.changed')
 
143
            set_state('config.changed.{}'.format(opt))
 
144
        toggle_state('config.set.{}'.format(opt), config.get(opt))
 
145
        toggle_state('config.default.{}'.format(opt),
 
146
                     config.get(opt) == config_defaults[opt])
 
147
    hookenv.atexit(clear_config_states)
 
148
 
 
149
 
 
150
def clear_config_states():
 
151
    from charmhelpers.core import hookenv, unitdata
 
152
    from charms.reactive import remove_state
 
153
    config = hookenv.config()
 
154
    remove_state('config.changed')
 
155
    for opt in config.keys():
 
156
        remove_state('config.changed.{}'.format(opt))
 
157
        remove_state('config.set.{}'.format(opt))
 
158
        remove_state('config.default.{}'.format(opt))
 
159
    unitdata.kv().flush()