Merge ~paelzer/cloud-init:fix-bug-1616831-new-and-old-apt-config into cloud-init:master

Proposed by Christian Ehrhardt 
Status: Merged
Merged at revision: 763f403c7b848b31780ef869fb7728b0d5e571a2
Proposed branch: ~paelzer/cloud-init:fix-bug-1616831-new-and-old-apt-config
Merge into: cloud-init:master
Diff against target: 195 lines (+117/-31)
2 files modified
cloudinit/config/cc_apt_configure.py (+36/-25)
tests/unittests/test_handler/test_handler_apt_source_v1.py (+81/-6)
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+304064@code.launchpad.net

Description of the change

apt-config: Prefer V3 format if specified together with V1/2 (LP: #1616831)

This set of changes contains the code addressing an issue if V1/2 AND V3 configs are specified at the same time. With these changes the V3 config is preferred now.

There are some sanity checks in place that kick in if the old (now dropped due to the preference) values differ from those that will be used (there would be too much danger to "silently" ignore something of the V1 spec and the user wondering why his changes take no effect.

It also extends and adds unitests in that area to cover even more of the existing and now added format conversion logic.

Passed make check, tox and bddeb+sbuild and thereby I hope ready for review.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py
2index 609dbb5..42c5641 100644
3--- a/cloudinit/config/cc_apt_configure.py
4+++ b/cloudinit/config/cc_apt_configure.py
5@@ -464,13 +464,19 @@ def convert_mirror(oldcfg, aptcfg):
6
7 def convert_v2_to_v3_apt_format(oldcfg):
8 """convert old to new keys and adapt restructured mirror spec"""
9- oldkeys = ['apt_sources', 'apt_mirror', 'apt_mirror_search',
10- 'apt_mirror_search_dns', 'apt_proxy', 'apt_http_proxy',
11- 'apt_ftp_proxy', 'apt_https_proxy',
12- 'apt_preserve_sources_list', 'apt_custom_sources_list',
13- 'add_apt_repo_match']
14+ mapoldkeys = {'apt_sources': 'sources',
15+ 'apt_mirror': None,
16+ 'apt_mirror_search': None,
17+ 'apt_mirror_search_dns': None,
18+ 'apt_proxy': 'proxy',
19+ 'apt_http_proxy': 'http_proxy',
20+ 'apt_ftp_proxy': 'https_proxy',
21+ 'apt_https_proxy': 'ftp_proxy',
22+ 'apt_preserve_sources_list': 'preserve_sources_list',
23+ 'apt_custom_sources_list': 'sources_list',
24+ 'add_apt_repo_match': 'add_apt_repo_match'}
25 needtoconvert = []
26- for oldkey in oldkeys:
27+ for oldkey in mapoldkeys:
28 if oldcfg.get(oldkey, None) is not None:
29 needtoconvert.append(oldkey)
30
31@@ -480,32 +486,37 @@ def convert_v2_to_v3_apt_format(oldcfg):
32 LOG.debug("apt config: convert V2 to V3 format for keys '%s'",
33 ", ".join(needtoconvert))
34
35- if oldcfg.get('apt', None) is not None:
36- msg = ("Error in apt configuration: "
37- "old and new format of apt features are mutually exclusive "
38- "('apt':'%s' vs '%s' key)" % (oldcfg.get('apt', None),
39- ", ".join(needtoconvert)))
40- LOG.error(msg)
41- raise ValueError(msg)
42+ # if old AND new config are provided, prefer the new one (LP #1616831)
43+ newaptcfg = oldcfg.get('apt', None)
44+ if newaptcfg is not None:
45+ LOG.debug("apt config: V1/2 and V3 format specified, preferring V3")
46+ for oldkey in needtoconvert:
47+ newkey = mapoldkeys[oldkey]
48+ verify = oldcfg[oldkey] # drop, but keep a ref for verification
49+ del oldcfg[oldkey]
50+ if newkey is None or newaptcfg.get(newkey, None) is None:
51+ # no simple mapping or no collision on this particular key
52+ continue
53+ if verify != newaptcfg[newkey]:
54+ raise ValueError("Old and New apt format defined with unequal "
55+ "values %s vs %s @ %s" % (verify,
56+ newaptcfg[newkey],
57+ oldkey))
58+ # return conf after clearing conflicting V1/2 keys
59+ return oldcfg
60
61 # create new format from old keys
62 aptcfg = {}
63
64- # renames / moves under the apt key
65- convert_key(oldcfg, aptcfg, 'add_apt_repo_match', 'add_apt_repo_match')
66- convert_key(oldcfg, aptcfg, 'apt_proxy', 'proxy')
67- convert_key(oldcfg, aptcfg, 'apt_http_proxy', 'http_proxy')
68- convert_key(oldcfg, aptcfg, 'apt_https_proxy', 'https_proxy')
69- convert_key(oldcfg, aptcfg, 'apt_ftp_proxy', 'ftp_proxy')
70- convert_key(oldcfg, aptcfg, 'apt_custom_sources_list', 'sources_list')
71- convert_key(oldcfg, aptcfg, 'apt_preserve_sources_list',
72- 'preserve_sources_list')
73- # dict format not changed since v2, just renamed and moved
74- convert_key(oldcfg, aptcfg, 'apt_sources', 'sources')
75+ # simple renames / moves under the apt key
76+ for oldkey in mapoldkeys:
77+ if mapoldkeys[oldkey] is not None:
78+ convert_key(oldcfg, aptcfg, oldkey, mapoldkeys[oldkey])
79
80+ # mirrors changed in a more complex way
81 convert_mirror(oldcfg, aptcfg)
82
83- for oldkey in oldkeys:
84+ for oldkey in mapoldkeys:
85 if oldcfg.get(oldkey, None) is not None:
86 raise ValueError("old apt key '%s' left after conversion" % oldkey)
87
88diff --git a/tests/unittests/test_handler/test_handler_apt_source_v1.py b/tests/unittests/test_handler/test_handler_apt_source_v1.py
89index d96779c..ddff434 100644
90--- a/tests/unittests/test_handler/test_handler_apt_source_v1.py
91+++ b/tests/unittests/test_handler/test_handler_apt_source_v1.py
92@@ -528,6 +528,7 @@ class TestAptSourceConfig(TestCase):
93 'filename': self.aptlistfile2}
94 cfg3 = {'source': 'deb $MIRROR $RELEASE universe',
95 'filename': self.aptlistfile3}
96+ cfg = {'apt_sources': [cfg1, cfg2, cfg3]}
97 checkcfg = {self.aptlistfile: {'filename': self.aptlistfile,
98 'source': 'deb $MIRROR $RELEASE '
99 'multiverse'},
100@@ -537,15 +538,89 @@ class TestAptSourceConfig(TestCase):
101 'source': 'deb $MIRROR $RELEASE '
102 'universe'}}
103
104- newcfg = cc_apt_configure.convert_v1_to_v2_apt_format([cfg1, cfg2,
105- cfg3])
106- self.assertEqual(newcfg, checkcfg)
107+ newcfg = cc_apt_configure.convert_to_v3_apt_format(cfg)
108+ self.assertEqual(newcfg['apt']['sources'], checkcfg)
109
110- newcfg2 = cc_apt_configure.convert_v1_to_v2_apt_format(newcfg)
111- self.assertEqual(newcfg2, checkcfg)
112+ # convert again, should stay the same
113+ newcfg2 = cc_apt_configure.convert_to_v3_apt_format(newcfg)
114+ self.assertEqual(newcfg2['apt']['sources'], checkcfg)
115
116+ # should work without raising an exception
117+ cc_apt_configure.convert_to_v3_apt_format({})
118+
119+ with self.assertRaises(ValueError):
120+ cc_apt_configure.convert_to_v3_apt_format({'apt_sources': 5})
121+
122+ def test_convert_to_new_format_collision(self):
123+ """Test the conversion of old to new format with collisions
124+ That matches e.g. the MAAS case specifying old and new config"""
125+ cfg_1_and_3 = {'apt': {'proxy': 'http://192.168.122.1:8000/'},
126+ 'apt_proxy': 'http://192.168.122.1:8000/'}
127+ cfg_3_only = {'apt': {'proxy': 'http://192.168.122.1:8000/'}}
128+ cfgconflict = {'apt': {'proxy': 'http://192.168.122.1:8000/'},
129+ 'apt_proxy': 'ftp://192.168.122.1:8000/'}
130+
131+ # collision (equal)
132+ newcfg = cc_apt_configure.convert_to_v3_apt_format(cfg_1_and_3)
133+ self.assertEqual(newcfg, cfg_3_only)
134+ # collision (equal, so ok to remove)
135+ newcfg = cc_apt_configure.convert_to_v3_apt_format(cfg_3_only)
136+ self.assertEqual(newcfg, cfg_3_only)
137+ # collision (unequal)
138+ with self.assertRaises(ValueError):
139+ cc_apt_configure.convert_to_v3_apt_format(cfgconflict)
140+
141+ def test_convert_to_new_format_dict_collision(self):
142+ cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse',
143+ 'filename': self.aptlistfile}
144+ cfg2 = {'source': 'deb $MIRROR $RELEASE main',
145+ 'filename': self.aptlistfile2}
146+ cfg3 = {'source': 'deb $MIRROR $RELEASE universe',
147+ 'filename': self.aptlistfile3}
148+ fullv3 = {self.aptlistfile: {'filename': self.aptlistfile,
149+ 'source': 'deb $MIRROR $RELEASE '
150+ 'multiverse'},
151+ self.aptlistfile2: {'filename': self.aptlistfile2,
152+ 'source': 'deb $MIRROR $RELEASE main'},
153+ self.aptlistfile3: {'filename': self.aptlistfile3,
154+ 'source': 'deb $MIRROR $RELEASE '
155+ 'universe'}}
156+ cfg_3_only = {'apt': {'sources': fullv3}}
157+ cfg_1_and_3 = {'apt_sources': [cfg1, cfg2, cfg3]}
158+ cfg_1_and_3.update(cfg_3_only)
159+
160+ # collision (equal, so ok to remove)
161+ newcfg = cc_apt_configure.convert_to_v3_apt_format(cfg_1_and_3)
162+ self.assertEqual(newcfg, cfg_3_only)
163+ # no old spec (same result)
164+ newcfg = cc_apt_configure.convert_to_v3_apt_format(cfg_3_only)
165+ self.assertEqual(newcfg, cfg_3_only)
166+
167+ diff = {self.aptlistfile: {'filename': self.aptlistfile,
168+ 'source': 'deb $MIRROR $RELEASE '
169+ 'DIFFERENTVERSE'},
170+ self.aptlistfile2: {'filename': self.aptlistfile2,
171+ 'source': 'deb $MIRROR $RELEASE main'},
172+ self.aptlistfile3: {'filename': self.aptlistfile3,
173+ 'source': 'deb $MIRROR $RELEASE '
174+ 'universe'}}
175+ cfg_3_only = {'apt': {'sources': diff}}
176+ cfg_1_and_3_different = {'apt_sources': [cfg1, cfg2, cfg3]}
177+ cfg_1_and_3_different.update(cfg_3_only)
178+
179+ # collision (unequal by dict having a different entry)
180+ with self.assertRaises(ValueError):
181+ cc_apt_configure.convert_to_v3_apt_format(cfg_1_and_3_different)
182+
183+ missing = {self.aptlistfile: {'filename': self.aptlistfile,
184+ 'source': 'deb $MIRROR $RELEASE '
185+ 'multiverse'}}
186+ cfg_3_only = {'apt': {'sources': missing}}
187+ cfg_1_and_3_missing = {'apt_sources': [cfg1, cfg2, cfg3]}
188+ cfg_1_and_3_missing.update(cfg_3_only)
189+ # collision (unequal by dict missing an entry)
190 with self.assertRaises(ValueError):
191- cc_apt_configure.convert_v1_to_v2_apt_format(5)
192+ cc_apt_configure.convert_to_v3_apt_format(cfg_1_and_3_missing)
193
194
195 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches