6
from subprocess import check_call
8
from charms.layer.execd import execd_preinstall
11
def bootstrap_charm_deps():
13
Set up the base charm dependencies so that the reactive system can run.
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.
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:
32
os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
33
reload_interpreter(vpy)
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']
44
"find_links = file://{}/wheelhouse/\n".format(charm_dir),
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'])
58
apt_install(['virtualenv'])
59
cmd = ['virtualenv', '-ppython3', '--never-download', venv]
60
if cfg.get('include_system_packages'):
61
cmd.append('--system-site-packages')
63
os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
67
# save a copy of system pip to prevent `pip3 install -U pip`
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',
75
# install the rest of the wheelhouse deps
76
check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
78
if not cfg.get('use_venv'):
79
# restore system pip to prevent `pip3 install -U pip`
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])
94
def reload_interpreter(python):
96
Reload the python interpreter to ensure that all deps are available.
98
Newly installed modules in namespace packages sometimes seemt to
99
not be picked up by Python 3.
101
os.execle(python, python, sys.argv[0], os.environ)
104
def apt_install(packages):
106
Install apt packages.
108
This ensures a consistent set of options that are often missed but
109
should really be set.
111
if isinstance(packages, (str, bytes)):
112
packages = [packages]
114
env = os.environ.copy()
116
if 'DEBIAN_FRONTEND' not in env:
117
env['DEBIAN_FRONTEND'] = 'noninteractive'
120
'--option=Dpkg::Options::=--force-confold',
123
check_call(cmd + packages, env=env)
126
def init_config_states():
128
from charmhelpers.core import hookenv
129
from charms.reactive import set_state
130
from charms.reactive import toggle_state
131
config = hookenv.config()
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.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)
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()