Revision cae67d7b kamaki/config.py
b/kamaki/config.py | ||
---|---|---|
31 | 31 |
# interpreted as representing official policies, either expressed |
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 |
import json |
|
35 |
import logging |
|
36 |
import os |
|
34 |
from collections import defaultdict |
|
35 |
from ConfigParser import RawConfigParser, NoOptionError, NoSectionError |
|
37 | 36 |
|
38 |
from os.path import exists, expanduser
|
|
37 |
from .utils import OrderedDict
|
|
39 | 38 |
|
40 | 39 |
|
41 |
# Path to the file that stores the configuration |
|
42 |
CONFIG_PATH = expanduser('~/.kamakirc') |
|
40 |
HEADER = """ |
|
41 |
# Kamaki configuration file |
|
42 |
""" |
|
43 | 43 |
|
44 |
# Name of a shell variable to bypass the CONFIG_PATH value |
|
45 |
CONFIG_ENV = 'KAMAKI_CONFIG' |
|
46 |
|
|
47 |
# The defaults also determine the allowed keys |
|
48 |
CONFIG_DEFAULTS = { |
|
49 |
'apis': 'compute image storage cyclades pithos', |
|
50 |
'token': '', |
|
51 |
'url': '', |
|
52 |
'compute_token': '', |
|
53 |
'compute_url': 'https://okeanos.grnet.gr/api/v1', |
|
54 |
'image_token': '', |
|
55 |
'image_url': 'https://okeanos.grnet.gr/plankton', |
|
56 |
'storage_account': '', |
|
57 |
'storage_container': '', |
|
58 |
'storage_token': '', |
|
59 |
'storage_url': 'https://plus.pithos.grnet.gr/v1' |
|
44 |
DEFAULTS = { |
|
45 |
'global': { |
|
46 |
'colors': 'on', |
|
47 |
'token': '' |
|
48 |
}, |
|
49 |
'compute': { |
|
50 |
'enable': 'on', |
|
51 |
'cyclades_extensions': 'on', |
|
52 |
'url': 'https://okeanos.grnet.gr/api/v1.1', |
|
53 |
'token': '' |
|
54 |
}, |
|
55 |
'image': { |
|
56 |
'enable': 'on', |
|
57 |
'url': 'https://okeanos.grnet.gr/plankton', |
|
58 |
'token': '' |
|
59 |
}, |
|
60 |
'storage': { |
|
61 |
'enable': 'on', |
|
62 |
'pithos_extensions': 'on', |
|
63 |
'url': 'https://plus.pithos.grnet.gr/v1', |
|
64 |
'account': '', |
|
65 |
'container': '', |
|
66 |
'token': '' |
|
67 |
} |
|
60 | 68 |
} |
61 | 69 |
|
62 | 70 |
|
63 |
log = logging.getLogger('kamaki.config') |
|
64 |
|
|
65 |
|
|
66 |
class ConfigError(Exception): |
|
67 |
pass |
|
68 |
|
|
69 |
|
|
70 |
class Config(object): |
|
71 |
def __init__(self): |
|
72 |
self.path = os.environ.get(CONFIG_ENV, CONFIG_PATH) |
|
73 |
self.defaults = CONFIG_DEFAULTS |
|
74 |
|
|
75 |
d = self.read() |
|
76 |
for key, val in d.items(): |
|
77 |
if key not in self.defaults: |
|
78 |
log.warning('Ignoring unknown config key "%s".', key) |
|
79 |
|
|
80 |
self.d = d |
|
81 |
self.overrides = {} |
|
71 |
class Config(RawConfigParser): |
|
72 |
def __init__(self, path=None): |
|
73 |
RawConfigParser.__init__(self, dict_type=OrderedDict) |
|
74 |
self.path = path |
|
75 |
self._overrides = defaultdict(dict) |
|
76 |
self.read(path) |
|
82 | 77 |
|
83 |
def read(self): |
|
84 |
if not exists(self.path): |
|
85 |
return {} |
|
86 |
|
|
87 |
with open(self.path) as f: |
|
88 |
data = f.read() |
|
78 |
def sections(self): |
|
79 |
return DEFAULTS.keys() |
|
80 |
|
|
81 |
def get(self, section, option): |
|
82 |
value = self._overrides.get(section, {}).get(option) |
|
83 |
if value is not None: |
|
84 |
return value |
|
89 | 85 |
|
90 | 86 |
try: |
91 |
d = json.loads(data) |
|
92 |
assert isinstance(d, dict) |
|
93 |
return d |
|
94 |
except (ValueError, AssertionError): |
|
95 |
msg = '"%s" does not look like a kamaki config file.' % self.path |
|
96 |
raise ConfigError(msg) |
|
97 |
|
|
98 |
def write(self): |
|
99 |
self.read() # Make sure we don't overwrite anything wrong |
|
100 |
with open(self.path, 'w') as f: |
|
101 |
data = json.dumps(self.d, indent=True) |
|
102 |
f.write(data) |
|
87 |
return RawConfigParser.get(self, section, option) |
|
88 |
except (NoSectionError, NoOptionError) as e: |
|
89 |
return DEFAULTS.get(section, {}).get(option) |
|
103 | 90 |
|
104 |
def items(self): |
|
105 |
for key, val in self.defaults.items(): |
|
106 |
yield key, self.get(key) |
|
91 |
def set(self, section, option, value): |
|
92 |
if section not in RawConfigParser.sections(self): |
|
93 |
self.add_section(section) |
|
94 |
RawConfigParser.set(self, section, option, value) |
|
107 | 95 |
|
108 |
def get(self, key): |
|
109 |
if key in self.overrides: |
|
110 |
return self.overrides[key] |
|
111 |
if key in self.d: |
|
112 |
return self.d[key] |
|
113 |
return self.defaults.get(key, '') |
|
96 |
def remove_option(self, section, option): |
|
97 |
try: |
|
98 |
RawConfigParser.remove_option(self, section, option) |
|
99 |
except NoSectionError: |
|
100 |
pass |
|
114 | 101 |
|
115 |
def set(self, key, val): |
|
116 |
if key not in self.defaults: |
|
117 |
log.warning('Ignoring unknown config key "%s".', key) |
|
118 |
return |
|
119 |
self.d[key] = val |
|
120 |
self.write() |
|
102 |
def items(self, section, include_defaults=False): |
|
103 |
d = dict(DEFAULTS[section]) if include_defaults else {} |
|
104 |
try: |
|
105 |
d.update(RawConfigParser.items(self, section)) |
|
106 |
except NoSectionError: |
|
107 |
pass |
|
108 |
return d.items() |
|
121 | 109 |
|
122 |
def delete(self, key): |
|
123 |
if key not in self.defaults: |
|
124 |
log.warning('Ignoring unknown config key "%s".', key) |
|
125 |
return |
|
126 |
self.d.pop(key, None) |
|
127 |
self.write() |
|
110 |
def override(self, section, option, value): |
|
111 |
self._overrides[section][option] = value |
|
128 | 112 |
|
129 |
def override(self, key, val): |
|
130 |
assert key in self.defaults |
|
131 |
if val is not None: |
|
132 |
self.overrides[key] = val |
|
113 |
def write(self): |
|
114 |
with open(self.path, 'w') as f: |
|
115 |
f.write(HEADER.lstrip()) |
|
116 |
RawConfigParser.write(self, f) |
Also available in: Unified diff