Browse Source

make iniset work with files that do not yet exist

This allows the add and set commands to work when files do not yet
exist, which becomes important for extracting things like post config
files.
Sean Dague 2 years ago
parent
commit
e7636a7714
3 changed files with 153 additions and 11 deletions
  1. 63
    11
      devstack/dsconf.py
  2. 19
    0
      devstack/tests/test_ini_set.py
  3. 71
    0
      devstack/tests/test_localconf_extract.py

+ 63
- 11
devstack/dsconf.py View File

@@ -16,6 +16,7 @@
16 16
 # python ConfigFile parser because that ends up rewriting the entire
17 17
 # file and doesn't ensure comments remain.
18 18
 
19
+import os.path
19 20
 import re
20 21
 import shutil
21 22
 import tempfile
@@ -31,7 +32,10 @@ class IniFile(object):
31 32
         """Returns True if section has a key that is name"""
32 33
 
33 34
         current_section = ""
34
-        with open(self.fname) as reader:
35
+        if not os.path.exists(self.fname):
36
+            return False
37
+
38
+        with open(self.fname, "r+") as reader:
35 39
             for line in reader.readlines():
36 40
                 m = re.match("\[([^\[\]]+)\]", line)
37 41
                 if m:
@@ -41,7 +45,6 @@ class IniFile(object):
41 45
                         return True
42 46
         return False
43 47
 
44
-
45 48
     def add(self, section, name, value):
46 49
         """add a key / value to an ini file in a section.
47 50
 
@@ -50,26 +53,38 @@ class IniFile(object):
50 53
         will be added to the end of the file.
51 54
         """
52 55
         temp = tempfile.NamedTemporaryFile(mode='r')
53
-        shutil.copyfile(self.fname, temp.name)
56
+        if os.path.exists(self.fname):
57
+            shutil.copyfile(self.fname, temp.name)
58
+        else:
59
+            with open(temp.name, "w+"):
60
+                pass
61
+
54 62
         found = False
55
-        with open(temp.name) as reader:
56
-            with open(self.fname, "w") as writer:
63
+        with open(self.fname, "w+") as writer:
64
+            with open(temp.name) as reader:
57 65
                 for line in reader.readlines():
58 66
                     writer.write(line)
59 67
                     m = re.match("\[([^\[\]]+)\]", line)
60 68
                     if m and m.group(1) == section:
61 69
                         found = True
62 70
                         writer.write("%s = %s\n" % (name, value))
63
-                if not found:
64
-                    writer.write("[%s]\n" % section)
65
-                    writer.write("%s = %s\n" % (name, value))
71
+            if not found:
72
+                writer.write("[%s]\n" % section)
73
+                writer.write("%s = %s\n" % (name, value))
66 74
 
67 75
     def _at_existing_key(self, section, name, func, match="%s\s*\="):
76
+        """Run a function at a found key.
77
+
78
+        NOTE(sdague): if the file isn't found, we end up
79
+        exploding. This seems like the right behavior in nearly all
80
+        circumstances.
81
+
82
+        """
68 83
         temp = tempfile.NamedTemporaryFile(mode='r')
69 84
         shutil.copyfile(self.fname, temp.name)
70 85
         current_section = ""
71 86
         with open(temp.name) as reader:
72
-            with open(self.fname, "w") as writer:
87
+            with open(self.fname, "w+") as writer:
73 88
                 for line in reader.readlines():
74 89
                     m = re.match("\[([^\[\]]+)\]", line)
75 90
                     if m:
@@ -83,7 +98,6 @@ class IniFile(object):
83 98
                     else:
84 99
                         writer.write(line)
85 100
 
86
-
87 101
     def remove(self, section, name):
88 102
         """remove a key / value from an ini file in a section."""
89 103
         def _do_remove(writer, line):
@@ -91,7 +105,6 @@ class IniFile(object):
91 105
 
92 106
         self._at_existing_key(section, name, _do_remove)
93 107
 
94
-
95 108
     def comment(self, section, name):
96 109
         def _do_comment(writer, line):
97 110
             writer.write("# %s" % line)
@@ -112,3 +125,42 @@ class IniFile(object):
112 125
             self._at_existing_key(section, name, _do_set)
113 126
         else:
114 127
             self.add(section, name, value)
128
+
129
+
130
+class LocalConf(object):
131
+    """Class for manipulating local.conf files in place."""
132
+
133
+    def __init__(self, fname):
134
+        self.fname = fname
135
+
136
+    def _conf(self, group, conf):
137
+        in_section = False
138
+        current_section = ""
139
+        with open(self.fname) as reader:
140
+            for line in reader.readlines():
141
+                if re.match(r"\[\[%s\|%s\]\]" % (
142
+                        re.escape(group),
143
+                        re.escape(conf)),
144
+                        line):
145
+                    in_section = True
146
+                    continue
147
+                # any other meta section means we aren't in the
148
+                # section we want to be.
149
+                elif re.match("\[\[.*\|.*\]\]", line):
150
+                    in_section = False
151
+                    continue
152
+
153
+                if in_section:
154
+                    m = re.match("\[([^\[\]]+)\]", line)
155
+                    if m:
156
+                        current_section = m.group(1)
157
+                        continue
158
+                    else:
159
+                        m2 = re.match(r"(\w+)\s*\=\s*(.+)", line)
160
+                        if m2:
161
+                            yield current_section, m2.group(1), m2.group(2)
162
+
163
+    def extract(self, group, conf, target):
164
+        ini_file = IniFile(target)
165
+        for section, name, value in self._conf(group, conf):
166
+            ini_file.set(section, name, value)

+ 19
- 0
devstack/tests/test_ini_set.py View File

@@ -110,3 +110,22 @@ class TestIniSet(testtools.TestCase):
110 110
         with open(self._path) as f:
111 111
             content = f.read()
112 112
             self.assertEqual(content, RESULT4)
113
+
114
+
115
+class TestIniCreate(testtools.TestCase):
116
+
117
+    def setUp(self):
118
+        super(TestIniCreate, self).setUp()
119
+        self._path = self.useFixture(fixtures.TempDir()).path
120
+        self._path += "/test.ini"
121
+
122
+    def test_add_items(self):
123
+        conf = dsconf.IniFile(self._path)
124
+        conf.set("default", "c", "d")
125
+        conf.set("default", "a", "b")
126
+        conf.set("second", "g", "h")
127
+        conf.set("second", "e", "f")
128
+        conf.set("new", "s", "t")
129
+        with open(self._path) as f:
130
+            content = f.read()
131
+            self.assertEqual(BASIC, content)

+ 71
- 0
devstack/tests/test_localconf_extract.py View File

@@ -0,0 +1,71 @@
1
+# Copyright 2017 IBM
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#    http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+# Implementation of ini add / remove for devstack. We don't use the
16
+# python ConfigFile parser because that ends up rewriting the entire
17
+# file and doesn't ensure comments remain.
18
+
19
+import fixtures
20
+import os.path
21
+import testtools
22
+
23
+from devstack import dsconf
24
+
25
+
26
+BASIC = """
27
+[[local|localrc]]
28
+a = b
29
+c = d
30
+f = 1
31
+[[post-config|$NEUTRON_CONF]]
32
+[DEFAULT]
33
+global_physnet_mtu=1450
34
+[[post-config|$NOVA_CONF]]
35
+[upgrade_levels]
36
+compute = auto
37
+"""
38
+
39
+NOVA = """[upgrade_levels]
40
+compute = auto
41
+"""
42
+
43
+NEUTRON = """[DEFAULT]
44
+global_physnet_mtu = 1450
45
+"""
46
+
47
+
48
+class TestLcExtract(testtools.TestCase):
49
+
50
+    def setUp(self):
51
+        super(TestLcExtract, self).setUp()
52
+        self._path = self.useFixture(fixtures.TempDir()).path
53
+        self._path += "/local.conf"
54
+        with open(self._path, "w") as f:
55
+            f.write(BASIC)
56
+
57
+    def test_extract_neutron(self):
58
+        dirname = self.useFixture(fixtures.TempDir()).path
59
+        neutron = os.path.join(dirname, "neutron.conf")
60
+        nova = os.path.join(dirname, "nova.conf")
61
+        conf = dsconf.LocalConf(self._path)
62
+        conf.extract("post-config", "$NEUTRON_CONF", neutron)
63
+        conf.extract("post-config", "$NOVA_CONF", nova)
64
+
65
+        with open(neutron) as f:
66
+            content = f.read()
67
+            self.assertEqual(content, NEUTRON)
68
+
69
+        with open(nova) as f:
70
+            content = f.read()
71
+            self.assertEqual(content, NOVA)

Loading…
Cancel
Save