1
# Copyright 2012 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""MAAS Provisioning Configuration."""
6
from __future__ import (
17
from getpass import getuser
22
from os.path import abspath
23
from threading import RLock
25
from formencode import Schema
26
from formencode.declarative import DeclarativeMeta
27
from formencode.validators import (
36
class ConfigOops(Schema):
37
"""Configuration validator for OOPS options."""
41
directory = String(if_missing=b"")
42
reporter = String(if_missing=b"")
44
chained_validators = (
45
RequireIfPresent("reporter", present="directory"),
49
class ConfigBroker(Schema):
50
"""Configuration validator for message broker options."""
54
host = String(if_missing=b"localhost")
55
port = Int(min=1, max=65535, if_missing=5673)
56
username = String(if_missing=getuser())
57
password = String(if_missing=b"test")
58
vhost = String(if_missing="/")
61
class ConfigCobbler(Schema):
62
"""Configuration validator for connecting to Cobbler."""
67
add_http=True, require_tld=False,
68
if_missing=b"http://localhost/cobbler_api",
70
username = String(if_missing=getuser())
71
password = String(if_missing=b"test")
74
class ConfigTFTP(Schema):
75
"""Configuration validator for the TFTP service."""
79
root = String(if_missing="/var/lib/tftpboot")
80
port = Int(min=1, max=65535, if_missing=5244)
82
add_http=True, require_tld=False,
83
if_missing=b"http://localhost:5243/api/1.0/pxeconfig",
87
class ConfigMeta(DeclarativeMeta):
88
"""Metaclass for the root configuration schema."""
90
def _get_default_filename(cls):
91
# Get the configuration filename from the environment. Failing that,
92
# return a hard-coded default.
94
"MAAS_PROVISIONING_SETTINGS",
95
"/etc/maas/pserv.yaml")
97
def _set_default_filename(cls, filename):
98
# Set the configuration filename in the environment.
99
environ["MAAS_PROVISIONING_SETTINGS"] = filename
101
def _delete_default_filename(cls):
102
# Remove any setting of the configuration filename from the
104
environ.pop("MAAS_PROVISIONING_SETTINGS", None)
106
DEFAULT_FILENAME = property(
107
_get_default_filename, _set_default_filename,
108
_delete_default_filename, doc=(
109
"The default config file to load. Refers to "
110
"MAAS_PROVISIONING_SETTINGS in the environment."))
113
class Config(Schema):
114
"""Configuration validator."""
116
__metaclass__ = ConfigMeta
118
if_key_missing = None
120
interface = String(if_empty=b"", if_missing=b"127.0.0.1")
121
port = Int(min=1, max=65535, if_missing=5241)
122
username = String(not_empty=True, if_missing=getuser())
123
password = String(not_empty=True, if_missing=urandom(12))
124
logfile = String(if_empty=b"pserv.log", if_missing=b"pserv.log")
126
broker = ConfigBroker
127
cobbler = ConfigCobbler
131
def parse(cls, stream):
132
"""Load a YAML configuration from `stream` and validate."""
133
return cls.to_python(yaml.safe_load(stream))
136
def load(cls, filename=None):
137
"""Load a YAML configuration from `filename` and validate."""
139
filename = cls.DEFAULT_FILENAME
140
with open(filename, "rb") as stream:
141
return cls.parse(stream)
144
_cache_lock = RLock()
147
def load_from_cache(cls, filename=None):
148
"""Load or return a previously loaded configuration.
150
This is thread-safe, so is okay to use from Django, for example.
153
filename = cls.DEFAULT_FILENAME
154
filename = abspath(filename)
155
with cls._cache_lock:
156
if filename not in cls._cache:
157
with open(filename, "rb") as stream:
158
cls._cache[filename] = cls.parse(stream)
159
return cls._cache[filename]
162
def field(target, *steps):
163
"""Obtain a field by following `steps`."""
165
target = target.fields[step]