Move VyOS charm from descriptor repo to here
Signed-off-by: Philip Joseph <philip.joseph@riftio.com>
diff --git a/builds/VyOS-proxy/lib/charms/layer/__init__.py b/builds/VyOS-proxy/lib/charms/layer/__init__.py
new file mode 100644
index 0000000..33d37e9
--- /dev/null
+++ b/builds/VyOS-proxy/lib/charms/layer/__init__.py
@@ -0,0 +1,21 @@
+import os
+
+
+class LayerOptions(dict):
+ def __init__(self, layer_file, section=None):
+ import yaml # defer, might not be available until bootstrap
+ with open(layer_file) as f:
+ layer = yaml.safe_load(f.read())
+ opts = layer.get('options', {})
+ if section and section in opts:
+ super(LayerOptions, self).__init__(opts.get(section))
+ else:
+ super(LayerOptions, self).__init__(opts)
+
+
+def options(section=None, layer_file=None):
+ if not layer_file:
+ base_dir = os.environ.get('CHARM_DIR', os.getcwd())
+ layer_file = os.path.join(base_dir, 'layer.yaml')
+
+ return LayerOptions(layer_file, section)
diff --git a/builds/VyOS-proxy/lib/charms/layer/basic.py b/builds/VyOS-proxy/lib/charms/layer/basic.py
new file mode 100644
index 0000000..50bd625
--- /dev/null
+++ b/builds/VyOS-proxy/lib/charms/layer/basic.py
@@ -0,0 +1,159 @@
+import os
+import sys
+import shutil
+import platform
+from glob import glob
+from subprocess import check_call
+
+from charms.layer.execd import execd_preinstall
+
+
+def bootstrap_charm_deps():
+ """
+ Set up the base charm dependencies so that the reactive system can run.
+ """
+ # execd must happen first, before any attempt to install packages or
+ # access the network, because sites use this hook to do bespoke
+ # configuration and install secrets so the rest of this bootstrap
+ # and the charm itself can actually succeed. This call does nothing
+ # unless the operator has created and populated $CHARM_DIR/exec.d.
+ execd_preinstall()
+ # ensure that $CHARM_DIR/bin is on the path, for helper scripts
+ os.environ['PATH'] += ':%s' % os.path.join(os.environ['CHARM_DIR'], 'bin')
+ venv = os.path.abspath('../.venv')
+ vbin = os.path.join(venv, 'bin')
+ vpip = os.path.join(vbin, 'pip')
+ vpy = os.path.join(vbin, 'python')
+ if os.path.exists('wheelhouse/.bootstrapped'):
+ from charms import layer
+ cfg = layer.options('basic')
+ if cfg.get('use_venv') and '.venv' not in sys.executable:
+ # activate the venv
+ os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
+ reload_interpreter(vpy)
+ return
+ # bootstrap wheelhouse
+ if os.path.exists('wheelhouse'):
+ with open('/root/.pydistutils.cfg', 'w') as fp:
+ # make sure that easy_install also only uses the wheelhouse
+ # (see https://github.com/pypa/pip/issues/410)
+ charm_dir = os.environ['CHARM_DIR']
+ fp.writelines([
+ "[easy_install]\n",
+ "allow_hosts = ''\n",
+ "find_links = file://{}/wheelhouse/\n".format(charm_dir),
+ ])
+ apt_install(['python3-pip', 'python3-setuptools', 'python3-yaml'])
+ from charms import layer
+ cfg = layer.options('basic')
+ # include packages defined in layer.yaml
+ apt_install(cfg.get('packages', []))
+ # if we're using a venv, set it up
+ if cfg.get('use_venv'):
+ if not os.path.exists(venv):
+ distname, version, series = platform.linux_distribution()
+ if series in ('precise', 'trusty'):
+ apt_install(['python-virtualenv'])
+ else:
+ apt_install(['virtualenv'])
+ cmd = ['virtualenv', '-ppython3', '--never-download', venv]
+ if cfg.get('include_system_packages'):
+ cmd.append('--system-site-packages')
+ check_call(cmd)
+ os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
+ pip = vpip
+ else:
+ pip = 'pip3'
+ # save a copy of system pip to prevent `pip3 install -U pip`
+ # from changing it
+ if os.path.exists('/usr/bin/pip'):
+ shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
+ # need newer pip, to fix spurious Double Requirement error:
+ # https://github.com/pypa/pip/issues/56
+ check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
+ 'pip'])
+ # install the rest of the wheelhouse deps
+ check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
+ glob('wheelhouse/*'))
+ if not cfg.get('use_venv'):
+ # restore system pip to prevent `pip3 install -U pip`
+ # from changing it
+ if os.path.exists('/usr/bin/pip.save'):
+ shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
+ os.remove('/usr/bin/pip.save')
+ os.remove('/root/.pydistutils.cfg')
+ # flag us as having already bootstrapped so we don't do it again
+ open('wheelhouse/.bootstrapped', 'w').close()
+ # Ensure that the newly bootstrapped libs are available.
+ # Note: this only seems to be an issue with namespace packages.
+ # Non-namespace-package libs (e.g., charmhelpers) are available
+ # without having to reload the interpreter. :/
+ reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
+
+
+def reload_interpreter(python):
+ """
+ Reload the python interpreter to ensure that all deps are available.
+
+ Newly installed modules in namespace packages sometimes seemt to
+ not be picked up by Python 3.
+ """
+ os.execle(python, python, sys.argv[0], os.environ)
+
+
+def apt_install(packages):
+ """
+ Install apt packages.
+
+ This ensures a consistent set of options that are often missed but
+ should really be set.
+ """
+ if isinstance(packages, (str, bytes)):
+ packages = [packages]
+
+ env = os.environ.copy()
+
+ if 'DEBIAN_FRONTEND' not in env:
+ env['DEBIAN_FRONTEND'] = 'noninteractive'
+
+ cmd = ['apt-get',
+ '--option=Dpkg::Options::=--force-confold',
+ '--assume-yes',
+ 'install']
+ check_call(cmd + packages, env=env)
+
+
+def init_config_states():
+ import yaml
+ from charmhelpers.core import hookenv
+ from charms.reactive import set_state
+ from charms.reactive import toggle_state
+ config = hookenv.config()
+ config_defaults = {}
+ config_defs = {}
+ config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
+ if os.path.exists(config_yaml):
+ with open(config_yaml) as fp:
+ config_defs = yaml.load(fp).get('options', {})
+ config_defaults = {key: value.get('default')
+ for key, value in config_defs.items()}
+ for opt in config_defs.keys():
+ if config.changed(opt):
+ set_state('config.changed')
+ set_state('config.changed.{}'.format(opt))
+ toggle_state('config.set.{}'.format(opt), config.get(opt))
+ toggle_state('config.default.{}'.format(opt),
+ config.get(opt) == config_defaults[opt])
+ hookenv.atexit(clear_config_states)
+
+
+def clear_config_states():
+ from charmhelpers.core import hookenv, unitdata
+ from charms.reactive import remove_state
+ config = hookenv.config()
+ remove_state('config.changed')
+ for opt in config.keys():
+ remove_state('config.changed.{}'.format(opt))
+ remove_state('config.set.{}'.format(opt))
+ remove_state('config.default.{}'.format(opt))
+ unitdata.kv().flush()
diff --git a/builds/VyOS-proxy/lib/charms/layer/execd.py b/builds/VyOS-proxy/lib/charms/layer/execd.py
new file mode 100644
index 0000000..30574190
--- /dev/null
+++ b/builds/VyOS-proxy/lib/charms/layer/execd.py
@@ -0,0 +1,138 @@
+# Copyright 2014-2016 Canonical Limited.
+#
+# This file is part of layer-basic, the reactive base layer for Juju.
+#
+# charm-helpers is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# charm-helpers is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
+
+# This module may only import from the Python standard library.
+import os
+import sys
+import subprocess
+import time
+
+'''
+execd/preinstall
+
+It is often necessary to configure and reconfigure machines
+after provisioning, but before attempting to run the charm.
+Common examples are specialized network configuration, enabling
+of custom hardware, non-standard disk partitioning and filesystems,
+adding secrets and keys required for using a secured network.
+
+The reactive framework's base layer invokes this mechanism as
+early as possible, before any network access is made or dependencies
+unpacked or non-standard modules imported (including the charms.reactive
+framework itself).
+
+Operators needing to use this functionality may branch a charm and
+create an exec.d directory in it. The exec.d directory in turn contains
+one or more subdirectories, each of which contains an executable called
+charm-pre-install and any other required resources. The charm-pre-install
+executables are run, and if successful, state saved so they will not be
+run again.
+
+ $CHARM_DIR/exec.d/mynamespace/charm-pre-install
+
+An alternative to branching a charm is to compose a new charm that contains
+the exec.d directory, using the original charm as a layer,
+
+A charm author could also abuse this mechanism to modify the charm
+environment in unusual ways, but for most purposes it is saner to use
+charmhelpers.core.hookenv.atstart().
+'''
+
+
+def default_execd_dir():
+ return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
+
+
+def execd_module_paths(execd_dir=None):
+ """Generate a list of full paths to modules within execd_dir."""
+ if not execd_dir:
+ execd_dir = default_execd_dir()
+
+ if not os.path.exists(execd_dir):
+ return
+
+ for subpath in os.listdir(execd_dir):
+ module = os.path.join(execd_dir, subpath)
+ if os.path.isdir(module):
+ yield module
+
+
+def execd_submodule_paths(command, execd_dir=None):
+ """Generate a list of full paths to the specified command within exec_dir.
+ """
+ for module_path in execd_module_paths(execd_dir):
+ path = os.path.join(module_path, command)
+ if os.access(path, os.X_OK) and os.path.isfile(path):
+ yield path
+
+
+def execd_sentinel_path(submodule_path):
+ module_path = os.path.dirname(submodule_path)
+ execd_path = os.path.dirname(module_path)
+ module_name = os.path.basename(module_path)
+ submodule_name = os.path.basename(submodule_path)
+ return os.path.join(execd_path,
+ '.{}_{}.done'.format(module_name, submodule_name))
+
+
+def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
+ """Run command for each module within execd_dir which defines it."""
+ if stderr is None:
+ stderr = sys.stdout
+ for submodule_path in execd_submodule_paths(command, execd_dir):
+ # Only run each execd once. We cannot simply run them in the
+ # install hook, as potentially storage hooks are run before that.
+ # We cannot rely on them being idempotent.
+ sentinel = execd_sentinel_path(submodule_path)
+ if os.path.exists(sentinel):
+ continue
+
+ try:
+ subprocess.check_call([submodule_path], stderr=stderr,
+ universal_newlines=True)
+ with open(sentinel, 'w') as f:
+ f.write('{} ran successfully {}\n'.format(submodule_path,
+ time.ctime()))
+ f.write('Removing this file will cause it to be run again\n')
+ except subprocess.CalledProcessError as e:
+ # Logs get the details. We can't use juju-log, as the
+ # output may be substantial and exceed command line
+ # length limits.
+ print("ERROR ({}) running {}".format(e.returncode, e.cmd),
+ file=stderr)
+ print("STDOUT<<EOM", file=stderr)
+ print(e.output, file=stderr)
+ print("EOM", file=stderr)
+
+ # Unit workload status gets a shorter fail message.
+ short_path = os.path.relpath(submodule_path)
+ block_msg = "Error ({}) running {}".format(e.returncode,
+ short_path)
+ try:
+ subprocess.check_call(['status-set', 'blocked', block_msg],
+ universal_newlines=True)
+ if stop_on_error:
+ sys.exit(0) # Leave unit in blocked state.
+ except Exception:
+ pass # We care about the exec.d/* failure, not status-set.
+
+ if stop_on_error:
+ sys.exit(e.returncode or 1) # Error state for pre-1.24 Juju
+
+
+def execd_preinstall(execd_dir=None):
+ """Run charm-pre-install for each module within execd_dir."""
+ execd_run('charm-pre-install', execd_dir=execd_dir)