Change the copyright from GPL-3 to Apache-2.0
[osm/devops.git] / builds / vpe-router / deps / layer / layer-basic / lib / charms / layer / basic.py
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()