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