/usr/share/pyshared/juju/charm/config.py is in juju-0.7 0.7-0ubuntu2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | import copy
import os
import sys
import yaml
from juju.lib import serializer
from juju.lib.format import YAMLFormat
from juju.lib.schema import (SchemaError, KeyDict, Dict, String,
Constant, OneOf, Int, Float)
from juju.charm.errors import (
ServiceConfigError, ServiceConfigValueError)
OPTION_SCHEMA = KeyDict({
"type": OneOf(Constant("string"),
Constant("str"), # Obsolete
Constant("int"),
Constant("boolean"),
Constant("float")),
"default": OneOf(String(), Int(), Float()),
"description": String(),
},
optional=["default", "description"],
)
# Schema used to validate ConfigOptions specifications
CONFIG_SCHEMA = KeyDict({
"options": Dict(String(), OPTION_SCHEMA),
})
WARNED_STR_IS_OBSOLETE = False
class ConfigOptions(object):
"""Represents the configuration options exposed by a charm.
The intended usage is that Charm provide access to these objects
and then use them to `validate` inputs provided in the `juju
set` and `juju deploy` code paths.
"""
def __init__(self):
self._data = {}
def as_dict(self):
return copy.deepcopy(self._data)
def load(self, pathname):
"""Construct a ConfigOptions instance from a YAML file.
If is currently allowed for `pathname` to be missing. An empty
file with no allowable options will be assumed in that case.
"""
data = None
if os.path.exists(pathname):
with open(pathname) as fh:
data = fh.read()
else:
pathname = None
data = "options: {}\n"
if not data:
raise ServiceConfigError(
pathname, "Missing required service options metadata")
self.parse(data, pathname)
return self
def parse(self, data, pathname=None):
"""Load data into the config object.
Data can be a properly encoded YAML string or an dict, such as
one returned by `get_serialization_data`.
Each call to `parse` replaces any existing data.
`data`: Python dict or YAML encoded dict containing a valid
config options specification.
`pathname`: optional pathname included in some errors
"""
if isinstance(data, basestring):
try:
raw_data = serializer.yaml_load(data)
except yaml.MarkedYAMLError, e:
# Capture the path name on the error if present.
if pathname is not None:
e.problem_mark = serializer.yaml_mark_with_path(
pathname, e.problem_mark)
raise
elif isinstance(data, dict):
raw_data = data
else:
raise ServiceConfigError(
pathname or "",
"Unknown data type for config options: %s" % type(data))
data = self.parse_serialization_data(raw_data, pathname)
self._data = data
# validate defaults
self.get_defaults()
def parse_serialization_data(self, data, pathname=None):
"""Verify we have sensible option metadata.
Returns the `options` dict from within the YAML data.
"""
if not data or not isinstance(data, dict):
raise ServiceConfigError(
pathname or "",
"Expected YAML dict of options metadata")
try:
data = CONFIG_SCHEMA.coerce(data, [])
except SchemaError, error:
raise ServiceConfigError(
pathname or "", "Invalid options specification: %s" % error)
# XXX Drop this after everyone has migrated their config to 'string'.
global WARNED_STR_IS_OBSOLETE
if not WARNED_STR_IS_OBSOLETE:
for name, info in data["options"].iteritems():
for field, value in info.iteritems():
if field == "type" and value == "str":
sys.stderr.write(
"WARNING: Charm is using obsolete 'str' type "
"in config.yaml. Rename it to 'string'. %r \n" % (
pathname or ""))
WARNED_STR_IS_OBSOLETE = True
break
return data["options"]
def _validate_one(self, name, value):
# see if there is a type associated with the option
kind = self._data[name].get("type", "string")
if kind not in validation_kinds:
raise ServiceConfigValueError(
"Unknown service option type: %s" % kind)
# apply validation
validator = validation_kinds[kind]
value, valid = validator(value, self._data[name])
if not valid:
# Return value such that it roundtrips; this allows us to
# report back the boolean false instead of the Python
# output format, False
raise ServiceConfigValueError(
"Invalid value for %s: %s" % (
name, YAMLFormat().format_raw(value)))
return value
def get_defaults(self):
"""Return a mapping of option: default for all options."""
d = {}
for name, options in self._data.items():
if "default" in options:
d[name] = self._validate_one(name, options["default"])
return d
def validate(self, options):
"""Validate options using the loaded validation data.
This method validates all the provided options, and returns a
new dictionary with values properly typed.
If a provided option is unknown or its value fails validation,
ServiceConfigError is raised.
"""
d = {}
for option, value in options.items():
if option not in self._data:
raise ServiceConfigValueError(
"%s is not a valid configuration option." % (option))
d[option] = self._validate_one(option, value)
return d
def get_serialization_data(self):
return dict(options=self._data.copy())
# Validators return (type mapped value, valid boolean)
def validate_str(value, options):
if isinstance(value, basestring):
return value, True
return value, False
def validate_int(value, options):
try:
return int(value), True
except ValueError:
return value, False
def validate_float(value, options):
try:
return float(value), True
except ValueError:
return value, False
def validate_boolean(value, options):
if isinstance(value, bool):
return value, True
if value.lower() == "true":
return True, True
if value.lower() == "false":
return False, True
return value, False
# maps service option types to callables
validation_kinds = {
"string": validate_str,
"str": validate_str, # Obsolete
"int": validate_int,
"float": validate_float,
"boolean": validate_boolean,
}
|