|
| 1 | +# Copyright 2017 Canonical Ltd. |
| 2 | +# All rights reserved. |
| 3 | +# |
| 4 | +# Written by: |
| 5 | +# Jonathan Cave <jonathan.cave@canonical.com> |
| 6 | +# Maciej Kisielewski <maciej.kisielewski@canonical.com> |
| 7 | + |
| 8 | +import configparser |
| 9 | +import json |
| 10 | +import os |
| 11 | +import re |
| 12 | +import subprocess as sp |
| 13 | +import sys |
| 14 | + |
| 15 | + |
| 16 | +def get_snapctl_config(): |
| 17 | + """Query snapctl for config file variables""" |
| 18 | + out = ( |
| 19 | + sp.check_output(["snapctl", "get", "conf"]) |
| 20 | + .decode(sys.stdout.encoding) |
| 21 | + .strip() |
| 22 | + ) |
| 23 | + if out: |
| 24 | + return json.loads(out) |
| 25 | + return {} |
| 26 | + |
| 27 | + |
| 28 | +def get_configuration_set(): |
| 29 | + """ |
| 30 | + Get names and their default values declared in Snap's config_vars. |
| 31 | +
|
| 32 | + config_vars should list all the configuration variables in a `key=value` |
| 33 | + syntax. The line can list variable name only, if the variable should not |
| 34 | + have a default value. All keys should comprise of CAPS, numbers and |
| 35 | + undescores (_). |
| 36 | +
|
| 37 | + The returned keys are lowercase, as required by snapctl. |
| 38 | + """ |
| 39 | + config_set_path = os.path.expandvars("$SNAP/config_vars") |
| 40 | + config_set = dict() |
| 41 | + key_re = re.compile(r"^(?:[A-Z0-9]+_?)*[A-Z](?:_?[A-Z0-9])*$") |
| 42 | + try: |
| 43 | + for line in open(config_set_path, "rt").readlines(): |
| 44 | + line = line.strip() |
| 45 | + if not line or line.startswith("#"): |
| 46 | + continue |
| 47 | + k, _, v = line.partition("=") |
| 48 | + if not key_re.match(k): |
| 49 | + raise ValueError("%s is not a valid configuration key" % k) |
| 50 | + # snapd accepts lowercase and dashes only for config names |
| 51 | + # so let's "mangle" the names to match the requirement |
| 52 | + k = k.replace("_", "-").lower() |
| 53 | + config_set[k] = v |
| 54 | + except FileNotFoundError: |
| 55 | + # silently ignore missing config_vars |
| 56 | + pass |
| 57 | + return config_set |
| 58 | + |
| 59 | + |
| 60 | +def write_checkbox_conf(configuration): |
| 61 | + """Write checkbox.conf in $SNAP_DATA dir.""" |
| 62 | + config = configparser.ConfigParser() |
| 63 | + config.optionxform = str |
| 64 | + config.add_section("environment") |
| 65 | + for key in sorted(configuration.keys()): |
| 66 | + val = str(configuration[key]) |
| 67 | + # unmangle the key |
| 68 | + key = key.replace("-", "_").upper() |
| 69 | + config.set("environment", key, val) |
| 70 | + |
| 71 | + checkbox_conf_path = os.path.expandvars("$SNAP_DATA/checkbox.conf") |
| 72 | + os.makedirs(os.path.dirname(checkbox_conf_path), exist_ok=True) |
| 73 | + with open(checkbox_conf_path, "wt") as stream: |
| 74 | + config.write(stream) |
| 75 | + |
| 76 | + |
| 77 | +def print_checkbox_conf(): |
| 78 | + """Print the current checkbox.conf in $SNAP_DATA.""" |
| 79 | + checkbox_conf_path = os.path.expandvars("$SNAP_DATA/checkbox.conf") |
| 80 | + config = configparser.ConfigParser() |
| 81 | + config.optionxform = str |
| 82 | + config.read(checkbox_conf_path) |
| 83 | + if config.has_section("environment"): |
| 84 | + for key in config["environment"]: |
| 85 | + print("{}={}".format(key, config["environment"][key])) |
| 86 | + |
| 87 | + |
| 88 | +def refresh_configuration(): |
| 89 | + """ |
| 90 | + Read config_vars, write the ones missing in snapd |
| 91 | + and call update configuration in both, snapd and checkbox.conf. |
| 92 | +
|
| 93 | + This is called from the snap configure hook. |
| 94 | + """ |
| 95 | + config_vars = get_configuration_set() |
| 96 | + if config_vars: |
| 97 | + current = get_snapctl_config() |
| 98 | + for key in config_vars.keys(): |
| 99 | + if key not in current.keys() or not current[key]: |
| 100 | + current[key] = config_vars[key] |
| 101 | + update_configuration(current) |
| 102 | + |
| 103 | + |
| 104 | +def update_configuration(updated_entries): |
| 105 | + """ |
| 106 | + Update snapd configuration and write checkbox.conf file |
| 107 | +
|
| 108 | + The is called from the configure snap app. |
| 109 | +
|
| 110 | + :param updated_entries: |
| 111 | + A dict containing the configuration to set. |
| 112 | + Keys should contain lowercase letters, dashes and number only. |
| 113 | + """ |
| 114 | + vars_to_set = [] |
| 115 | + key_re = re.compile(r"^(?:[a-z0-9]+-?)*[a-z](?:-?[a-z0-9])*$") |
| 116 | + for k, v in updated_entries.items(): |
| 117 | + if not key_re.match(k): |
| 118 | + raise ValueError("'%s' is not a valid key" % k) |
| 119 | + vars_to_set.append("conf.{}={}".format(k.replace("_", "-").lower(), v)) |
| 120 | + sp.run(["snapctl", "set"] + sorted(vars_to_set)) |
| 121 | + write_checkbox_conf(get_snapctl_config()) |
0 commit comments