summaryrefslogtreecommitdiff
path: root/cloudinit/sources/helpers/vmware/imc/config_file.py
blob: 3f9938dad0347dedd3011f4896bf6452b7f661dd (plain)
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
221
import logging
import re

from cloudinit.sources.helpers.vmware.imc.config_source import ConfigSource

logger = logging.getLogger(__name__)


class ConfigFile(ConfigSource):
    def __init__(self):
        self._configData = {}

    def __getitem__(self, key):
        return self._configData[key]

    def get(self, key, default=None):
        return self._configData.get(key, default)

    # Removes all the properties.
    #
    # Args:
    #   None
    # Results:
    #   None
    # Throws:
    #   None
    def clear(self):
        self._configData.clear()

    # Inserts k/v pair.
    #
    # Does not do any key/cross-key validation.
    #
    # Args:
    #   key: string: key
    #   val: string: value
    # Results:
    #   None
    # Throws:
    #   None
    def _insertKey(self, key, val):
        # cleaning up on all "input" path

        # remove end char \n (chomp)
        key = key.strip()
        val = val.strip()

        if key.startswith('-') or '|-' in key:
            canLog = 0
        else:
            canLog = 1

        # "sensitive" settings shall not be logged
        if canLog:
            logger.debug("ADDED KEY-VAL :: '%s' = '%s'" % (key, val))
        else:
            logger.debug("ADDED KEY-VAL :: '%s' = '*****************'" % key)

        self._configData[key] = val

    # Determines properties count.
    #
    # Args:
    #   None
    # Results:
    #   integer: properties count
    # Throws:
    #   None
    def size(self):
        return len(self._configData)

    # Parses properties from a .cfg file content.
    #
    # Any previously available properties will be removed.
    #
    # Sensitive data will not be logged in case key starts from '-'.
    #
    # Args:
    #   content: string: e.g. content of config/cust.cfg
    # Results:
    #   None
    # Throws:
    #   None
    def loadConfigContent(self, content):
        self.clear()

        # remove end char \n (chomp)
        for line in content.split('\n'):
            # TODO validate against allowed characters (not done in Perl)

            # spaces at the end are not allowed, things like passwords must be
            # at least base64-encoded
            line = line.strip()

            # "sensitive" settings shall not be logged
            if line.startswith('-'):
                canLog = 0
            else:
                canLog = 1

            if canLog:
                logger.debug("Processing line: '%s'" % line)
            else:
                logger.debug("Processing line: '***********************'")

            if not line:
                logger.debug("Empty line. Ignored.")
                continue

            if line.startswith('#'):
                logger.debug("Comment found. Line ignored.")
                continue

            matchObj = re.match(r'\[(.+)\]', line)
            if matchObj:
                category = matchObj.group(1)
                logger.debug("FOUND CATEGORY = '%s'" % category)
            else:
                # POSIX.2 regex doesn't support non-greedy like in (.+?)=(.*)
                # key value pair (non-eager '=' for base64)
                matchObj = re.match(r'([^=]+)=(.*)', line)
                if matchObj:
                    # cleaning up on all "input" paths
                    key = category + "|" + matchObj.group(1).strip()
                    val = matchObj.group(2).strip()

                    self._insertKey(key, val)
                else:
                    # TODO document
                    raise Exception("Unrecognizable line: '%s'" % line)

        self.validate()

    # Parses properties from a .cfg file
    #
    # Any previously available properties will be removed.
    #
    # Sensitive data will not be logged in case key starts from '-'.
    #
    # Args:
    #   filename: string: full path to a .cfg file
    # Results:
    #   None
    # Throws:
    #   None
    def loadConfigFile(self, filename):
        logger.info("Opening file name %s." % filename)
        # TODO what throws?
        with open(filename, "r") as myfile:
            self.loadConfigContent(myfile.read())

    # Determines whether a property with a given key exists.
    #
    # Args:
    #   key: string: key
    # Results:
    #   boolean: True if such property exists, otherwise - False.
    # Throws:
    #   None
    def hasKey(self, key):
        return key in self._configData

    # Determines whether a value for a property must be kept.
    #
    # If the property is missing, it's treated as it should be not changed by
    # the engine.
    #
    # Args:
    #   key: string: key
    # Results:
    #   boolean: True if property must be kept, otherwise - False.
    # Throws:
    #   None
    def keepCurrentValue(self, key):
        # helps to distinguish from "empty" value which is used to indicate
        # "removal"
        return not self.hasKey(key)

    # Determines whether a value for a property must be removed.
    #
    # If the property is empty, it's treated as it should be removed by the
    # engine.
    #
    # Args:
    #   key: string: key
    # Results:
    #   boolean: True if property must be removed, otherwise - False.
    # Throws:
    #   None
    def removeCurrentValue(self, key):
        # helps to distinguish from "missing" value which is used to indicate
        # "keeping unchanged"
        if self.hasKey(key):
            return not bool(self._configData[key])
        else:
            return False

    # TODO
    def getCnt(self, prefix):
        res = 0
        for key in self._configData.keys():
            if key.startswith(prefix):
                res += 1

        return res

    # TODO
    # TODO pass base64
    # Throws:
    #   Dies in case timezone is present but empty.
    #   Dies in case password is present but empty.
    #   Dies in case hostname is present but empty or greater than 63 chars.
    #   Dies in case UTC is present, but is not yes/YES or no/NO.
    #   Dies in case NICS is not present.
    def validate(self):
        # TODO must log all the errors
        keyValidators = {'NIC1|IPv6GATEWAY|': None}
        crossValidators = {}

        for key in self._configData.keys():
            pass