Merge lp:~paelzer/cloud-init/test-apt-source into lp:~cloud-init-dev/cloud-init/trunk
- test-apt-source
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1224 |
Proposed branch: | lp:~paelzer/cloud-init/test-apt-source |
Merge into: | lp:~cloud-init-dev/cloud-init/trunk |
Diff against target: |
1134 lines (+945/-56) 6 files modified
cloudinit/config/cc_apt_configure.py (+82/-32) cloudinit/templater.py (+5/-0) cloudinit/util.py (+10/-0) doc/examples/cloud-config.txt (+131/-24) tests/unittests/test_handler/test_handler_apt_configure_sources_list.py (+166/-0) tests/unittests/test_handler/test_handler_apt_source.py (+551/-0) |
To merge this branch: | bzr merge lp:~paelzer/cloud-init/test-apt-source |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott Moser | Needs Fixing | ||
Review via email:
|
Commit message
Apt sources configuration improvements
- keyid-only (no source statement)
- key only (no source statement)
- custom source.list template
- support long gpg key fingerprints with spaces
- fix issue with key's that were already in the local gpg keyring
- allowing a new format to specify apt_sources in a dictionary instead of a list to allow merging of configurations
Along the way to ensure things are working this series also added several tests. Related to the new format and the realization of corner cases related to "no" or "multiple" specified filenames further unittests were added.
Testing various combinations of the execution as it was so far:
- test_apt_
- test_apt_
- test_apt_
- test_apt_source_key Test specification of a source + key
- test_apt_
- test_apt_
- test_apt_source_ppa Test specification of a ppa
- test_apt_
- test_apt_
- test_apt_
- test_apt_
- test_apt_
- test_apt_
- test_apt_
Testing variations of above cases but without filename being specified
- test_apt_
- test_apt_
- test_apt_
- test_apt_
Testing variations of above cases with multiple specifications in one call:
test_apt_
test_apt_
test_apt_
test_apt_
Testing the new format:
test_apt_
test_apt_
test_apt_
test_convert_
Description of the change
As discussed, the cloud-init portion of key-only (no source) and alternative source.list template.
I refused to do all the major changes you indicated as "might" in your mail.
Especially since IIRC the thought is to SRU it back.
The impact on backward compatibility could be too big.
Instead I tried to take a safe approach.
For none of the already existing features that touched existed unit tests.
So I wrote these, then added functionality and verified at least nothing on the old tests broke.
Finally I added tests for the new functions and also documentations in the example files.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Scott Moser (smoser) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Scott Moser (smoser) wrote : | # |
I *do* want the dictionary format supported though.
dictionaries just merge so much more easily.
so this MP doesn't have to have it, but we want dictionary 'apt_sources' to support being a dictionary to fix that bug, and want that in curtin.
- 1246. By Christian Ehrhardt
-
remove no more applicable "not supported" statements
- 1247. By Christian Ehrhardt
-
move errorlist.append out of add_key
- 1248. By Christian Ehrhardt
-
remove Unnecessary parens in add_key
- 1249. By Christian Ehrhardt
-
fix EXPORT_GPG_KEYID for long key fingerprints
- 1250. By Christian Ehrhardt
-
split add_key and add_key_raw fior better testability
- 1251. By Christian Ehrhardt
-
Adding test_apt_
source_ keyid_real and test_apt_ source_ longkeyid_ real This now ensures that the stack of fetching IDs from keyservers and adding them
really works by comparing against known good keys that are expected. - 1252. By Christian Ehrhardt
-
remove superfluous import
- 1253. By Christian Ehrhardt
-
alphabetical import order
- 1254. By Christian Ehrhardt
-
alphabetical order on imports
- 1255. By Christian Ehrhardt
-
fix old typo in example
- 1256. By Christian Ehrhardt
-
improve spacing in apt_source_list test
- 1257. By Christian Ehrhardt
-
streamline code and sanitize expected result string definition
- 1258. By Christian Ehrhardt
-
make pep8 happy with a few spaces
- 1259. By Christian Ehrhardt
-
generalize test_apt_
source_ basic to be reusable across more testcases - 1260. By Christian Ehrhardt
-
test_apt_
source_ basic_nofn check for non-specified filename Cloud-inint uses a default fallback, we want to ensure no code change modfies
this behaviour. - 1261. By Christian Ehrhardt
-
drop unused mockappsubp
- 1262. By Christian Ehrhardt
-
extend test_apt_
source_ replace by a no-filename case - 1263. By Christian Ehrhardt
-
extend test_apt_
source_ keyid by no filename case - 1264. By Christian Ehrhardt
-
put fallbackfn to init
This was now used by multiple methods, no need to duplicate code.
- 1265. By Christian Ehrhardt
-
extend test_apt_source_key by nofn case
- 1266. By Christian Ehrhardt
-
support apt_sources to be a dictionary
key is the filename, and "old" input shall be handled as it was all the time.
For compatibility this will (continue to) overwrite the file of multiple
options that did not specify an output file (they all get the same default).
Yet it will process them all - as it always did - e.g. to add the keys of all
of them.Any users of the new format won't have these issues, as they will always have
a key. - 1267. By Christian Ehrhardt
-
warn about multiple colliding apt_source without filenames
- 1268. By Christian Ehrhardt
-
fix function names in inline doc
- 1269. By Christian Ehrhardt
-
testcases with multiple source list entries
- 1270. By Christian Ehrhardt
-
add triple case for test_apt_
source_ keyid_triple incl triple key check - 1271. By Christian Ehrhardt
-
make checkers happy about unused loop index
- 1272. By Christian Ehrhardt
-
add triple test for ppa adding
- 1273. By Christian Ehrhardt
-
fix issue with dictionary style apt_sources handling filenames
- 1274. By Christian Ehrhardt
-
add test_apt_
source_ basic_dict This is the basic testcase but in the new dictionary format
- 1275. By Christian Ehrhardt
-
unify basic triple check and add test_apt_
src_basic_ dict_triple based on it - 1276. By Christian Ehrhardt
-
make sure we only handle list or dict apt_sources and bail out for others
- 1277. By Christian Ehrhardt
-
shorten method names to follow python rules
- 1278. By Christian Ehrhardt
-
add test_apt_
src_replace_ dict_tri This includes a test for the weird but valid case in the new dictionary syntax
that one sets a key (which is the filename) but overwrites the filename value
inside of it. - 1279. By Christian Ehrhardt
-
modify cloud-config examples to match the new apt_source format
- 1280. By Christian Ehrhardt
-
final pep8 check fixups
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Christian Ehrhardt (paelzer) wrote : | # |
Following the discussion with smoser I added the requested new dictionary based format to apt_sources. This goes along with a collection of extra testcases to ensure the behavior of new (and old) configuration works.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Scott Moser (smoser) wrote : | # |
so, some comments inline . that might seem like a lot, and that i'm nit picking. Sorry if it seems like that.
- 1281. By Christian Ehrhardt
-
fix typo in examples doc
- 1282. By Christian Ehrhardt
-
improve examples of ap_source
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Christian Ehrhardt (paelzer) wrote : | # |
> so, some comments inline . that might seem like a lot, and that i'm nit
> picking. Sorry if it seems like that.
You are not, I'm happy about a good review and that it is!
If the file would be free of warnings in general it would be easier to see the new ones :-)
I usually found a lot of missing docstrings and invalid constant/variable name issues of pylint littering my view of checker warnings probably too much.
Anyway - most of the (not nit) picks are stuff I copied or moved and now you just look at it again - so I don't feel offended but assisted to make the code better.
I found even more minor things on my own in the meantime that I can merge in given that changes of a respin.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Christian Ehrhardt (paelzer) wrote : | # |
On Tue, May 24, 2016 at 4:01 PM, Scott Moser <email address hidden> wrote:
> so, some comments inline . that might seem like a lot, and that i'm nit
> picking. Sorry if it seems like that.
>
Having worked on the list of feedback I really found that they are mostly
picks on older code I moved - so I feel good :-)
And for my English sometimes I think I just shouldn't try to race to a new
MP at the end of a day :-)
> > + """
> > + if 'keyid' in ent and 'key' not in ent:
> > + keyserver = "keyserver.
> > + if 'keyserver' in ent:
> > + keyserver = ent['keyserver']
> > + try:
> > + ent['key'] = getkeybyid(
> > + except:
> > + raise Exception('failed to get key from %s' % keyserver)
>
> KeyError ? or LookupError maybe? might seem reasonable.
> catch the subp.ProcessExe
> through would be better than swallowing it with a more generic error.
>
>
Yeah, given a second thought I like not catching it at that level the most.
> >
> > errorlist = []
> > - for ent in srclist:
> > + # convert old list format to new dict based format
>
> converting the array to a dictionary seems like a method itself that would
> be stand alone be easily unit tested.
>
I first thought to refuse that since so many older tests are testing that
implicitly but ten decided to add it (refusing to go home to the family -
remote this week for better internet access).
Uploading updated MP now ...
- 1283. By Christian Ehrhardt
-
rebased with upstream and reolved merge conflicts
- 1284. By Christian Ehrhardt
-
integrate further smaller review feedback
- 1285. By Christian Ehrhardt
-
pacify pep8 regarding the new changes
- 1286. By Christian Ehrhardt
-
add test for the now isolated convert_
to_new_ format function
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Christian Ehrhardt (paelzer) wrote : | # |
Review feedback integrated and new test test_convert_
Ready for another review or merging.
- 1287. By Christian Ehrhardt
-
make test_apt_
srcl_custom independent to where it is executed
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Christian Ehrhardt (paelzer) wrote : | # |
by ongoing testing I found that one of the unittests depended on the system being xenial, this last commit fixes that dependency
- 1288. By Christian Ehrhardt
-
fix inline doc of test_apt_
src_longkeyid_ real
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Scott Moser (smoser) wrote : | # |
one more, but i think we're there.
- 1289. By Christian Ehrhardt
-
drop errorlist from convert_
to_new_ format - 1290. By Christian Ehrhardt
-
add test for wrong apt_source format
- 1291. By Christian Ehrhardt
-
improve wording in the examples
- 1292. By Christian Ehrhardt
-
fix EXPORT_GPG_KEYID for existing keys
This was broken for keys already existing in the local keyring.
There instead of the keycontent it reported the header like:
pub 1024R/03683F77 2009-10-27
uid Launchpad PPA for Scott Moser - 1293. By Christian Ehrhardt
-
merge with last upstream to avoid merging conflicts on MP
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Christian Ehrhardt (paelzer) wrote : | # |
Thanks for the repeated great review, due to that I changed:
- warning/errors in convert_
- improved example wording
Further on I:
- extended test_convert_
- fixed an issue identified the key* tests depending if it was already imported in gpg (that turned out to be a real issue with the implementation)
- rebased again to avoid merge conflicts
Will update commit message now ...
Preview Diff
1 | === modified file 'cloudinit/config/cc_apt_configure.py' |
2 | --- cloudinit/config/cc_apt_configure.py 2016-05-12 17:56:26 +0000 |
3 | +++ cloudinit/config/cc_apt_configure.py 2016-05-30 11:10:57 +0000 |
4 | @@ -40,9 +40,9 @@ |
5 | k=${1} ks=${2}; |
6 | exec 2>/dev/null |
7 | [ -n "$k" ] || exit 1; |
8 | - armour=$(gpg --list-keys --armour "${k}") |
9 | + armour=$(gpg --export --armour "${k}") |
10 | if [ -z "${armour}" ]; then |
11 | - gpg --keyserver ${ks} --recv $k >/dev/null && |
12 | + gpg --keyserver ${ks} --recv "${k}" >/dev/null && |
13 | armour=$(gpg --export --armour "${k}") && |
14 | gpg --batch --yes --delete-keys "${k}" |
15 | fi |
16 | @@ -70,7 +70,7 @@ |
17 | |
18 | if not util.get_cfg_option_bool(cfg, |
19 | 'apt_preserve_sources_list', False): |
20 | - generate_sources_list(release, mirrors, cloud, log) |
21 | + generate_sources_list(cfg, release, mirrors, cloud, log) |
22 | old_mirrors = cfg.get('apt_old_mirrors', |
23 | {"primary": "archive.ubuntu.com/ubuntu", |
24 | "security": "security.ubuntu.com/ubuntu"}) |
25 | @@ -149,7 +149,17 @@ |
26 | return stdout.strip() |
27 | |
28 | |
29 | -def generate_sources_list(codename, mirrors, cloud, log): |
30 | +def generate_sources_list(cfg, codename, mirrors, cloud, log): |
31 | + params = {'codename': codename} |
32 | + for k in mirrors: |
33 | + params[k] = mirrors[k] |
34 | + |
35 | + custtmpl = cfg.get('apt_custom_sources_list', None) |
36 | + if custtmpl is not None: |
37 | + templater.render_string_to_file(custtmpl, |
38 | + '/etc/apt/sources.list', params) |
39 | + return |
40 | + |
41 | template_fn = cloud.get_template_filename('sources.list.%s' % |
42 | (cloud.distro.name)) |
43 | if not template_fn: |
44 | @@ -158,12 +168,60 @@ |
45 | log.warn("No template found, not rendering /etc/apt/sources.list") |
46 | return |
47 | |
48 | - params = {'codename': codename} |
49 | - for k in mirrors: |
50 | - params[k] = mirrors[k] |
51 | templater.render_to_file(template_fn, '/etc/apt/sources.list', params) |
52 | |
53 | |
54 | +def add_key_raw(key): |
55 | + """ |
56 | + actual adding of a key as defined in key argument |
57 | + to the system |
58 | + """ |
59 | + try: |
60 | + util.subp(('apt-key', 'add', '-'), key) |
61 | + except util.ProcessExecutionError: |
62 | + raise Exception('failed add key') |
63 | + |
64 | + |
65 | +def add_key(ent): |
66 | + """ |
67 | + add key to the system as defined in ent (if any) |
68 | + supports raw keys or keyid's |
69 | + The latter will as a first step fetch the raw key from a keyserver |
70 | + """ |
71 | + if 'keyid' in ent and 'key' not in ent: |
72 | + keyserver = "keyserver.ubuntu.com" |
73 | + if 'keyserver' in ent: |
74 | + keyserver = ent['keyserver'] |
75 | + ent['key'] = getkeybyid(ent['keyid'], keyserver) |
76 | + |
77 | + if 'key' in ent: |
78 | + add_key_raw(ent['key']) |
79 | + |
80 | + |
81 | +def convert_to_new_format(srclist): |
82 | + """ convert_to_new_format |
83 | + convert the old list based format to the new dict based one |
84 | + """ |
85 | + srcdict = {} |
86 | + if isinstance(srclist, list): |
87 | + for srcent in srclist: |
88 | + if 'filename' not in srcent: |
89 | + # file collides for multiple !filename cases for compatibility |
90 | + # yet we need them all processed, so not same dictionary key |
91 | + srcent['filename'] = "cloud_config_sources.list" |
92 | + key = util.rand_dict_key(srcdict, "cloud_config_sources.list") |
93 | + else: |
94 | + # all with filename use that as key (matching new format) |
95 | + key = srcent['filename'] |
96 | + srcdict[key] = srcent |
97 | + elif isinstance(srclist, dict): |
98 | + srcdict = srclist |
99 | + else: |
100 | + raise ValueError("unknown apt_sources format") |
101 | + |
102 | + return srcdict |
103 | + |
104 | + |
105 | def add_sources(srclist, template_params=None, aa_repo_match=None): |
106 | """ |
107 | add entries in /etc/apt/sources.list.d for each abbreviated |
108 | @@ -178,14 +236,29 @@ |
109 | return False |
110 | |
111 | errorlist = [] |
112 | - for ent in srclist: |
113 | + srcdict = convert_to_new_format(srclist) |
114 | + |
115 | + for filename in srcdict: |
116 | + ent = srcdict[filename] |
117 | + if 'filename' not in ent: |
118 | + ent['filename'] = filename |
119 | + |
120 | + # keys can be added without specifying a source |
121 | + try: |
122 | + add_key(ent) |
123 | + except Exception as detail: |
124 | + errorlist.append([ent, detail]) |
125 | + |
126 | if 'source' not in ent: |
127 | errorlist.append(["", "missing source"]) |
128 | continue |
129 | - |
130 | source = ent['source'] |
131 | source = templater.render_string(source, template_params) |
132 | |
133 | + if not ent['filename'].startswith(os.path.sep): |
134 | + ent['filename'] = os.path.join("/etc/apt/sources.list.d/", |
135 | + ent['filename']) |
136 | + |
137 | if aa_repo_match(source): |
138 | try: |
139 | util.subp(["add-apt-repository", source]) |
140 | @@ -194,29 +267,6 @@ |
141 | ("add-apt-repository failed. " + str(e))]) |
142 | continue |
143 | |
144 | - if 'filename' not in ent: |
145 | - ent['filename'] = 'cloud_config_sources.list' |
146 | - |
147 | - if not ent['filename'].startswith("/"): |
148 | - ent['filename'] = os.path.join("/etc/apt/sources.list.d/", |
149 | - ent['filename']) |
150 | - |
151 | - if ('keyid' in ent and 'key' not in ent): |
152 | - ks = "keyserver.ubuntu.com" |
153 | - if 'keyserver' in ent: |
154 | - ks = ent['keyserver'] |
155 | - try: |
156 | - ent['key'] = getkeybyid(ent['keyid'], ks) |
157 | - except Exception: |
158 | - errorlist.append([source, "failed to get key from %s" % ks]) |
159 | - continue |
160 | - |
161 | - if 'key' in ent: |
162 | - try: |
163 | - util.subp(('apt-key', 'add', '-'), ent['key']) |
164 | - except Exception: |
165 | - errorlist.append([source, "failed add key"]) |
166 | - |
167 | try: |
168 | contents = "%s\n" % (source) |
169 | util.write_file(ent['filename'], contents, omode="ab") |
170 | |
171 | === modified file 'cloudinit/templater.py' |
172 | --- cloudinit/templater.py 2015-01-23 02:21:04 +0000 |
173 | +++ cloudinit/templater.py 2016-05-30 11:10:57 +0000 |
174 | @@ -142,6 +142,11 @@ |
175 | util.write_file(outfn, contents, mode=mode) |
176 | |
177 | |
178 | +def render_string_to_file(content, outfn, params, mode=0o644): |
179 | + contents = render_string(content, params) |
180 | + util.write_file(outfn, contents, mode=mode) |
181 | + |
182 | + |
183 | def render_string(content, params): |
184 | if not params: |
185 | params = {} |
186 | |
187 | === modified file 'cloudinit/util.py' |
188 | --- cloudinit/util.py 2016-05-12 17:56:26 +0000 |
189 | +++ cloudinit/util.py 2016-05-30 11:10:57 +0000 |
190 | @@ -336,6 +336,16 @@ |
191 | return "".join([random.choice(select_from) for _x in range(0, strlen)]) |
192 | |
193 | |
194 | +def rand_dict_key(dictionary, postfix=None): |
195 | + if not postfix: |
196 | + postfix = "" |
197 | + while True: |
198 | + newkey = rand_str(strlen=8) + "_" + postfix |
199 | + if newkey not in dictionary: |
200 | + break |
201 | + return newkey |
202 | + |
203 | + |
204 | def read_conf(fname): |
205 | try: |
206 | return load_yaml(load_file(fname), default={}) |
207 | |
208 | === modified file 'doc/examples/cloud-config.txt' |
209 | --- doc/examples/cloud-config.txt 2015-03-04 17:42:34 +0000 |
210 | +++ doc/examples/cloud-config.txt 2016-05-30 11:10:57 +0000 |
211 | @@ -72,14 +72,87 @@ |
212 | # then apt_mirror above will have no effect |
213 | apt_preserve_sources_list: true |
214 | |
215 | +# Provide a custom template for rendering sources.list |
216 | +# Default: a default template for Ubuntu/Debain will be used as packaged in |
217 | +# Ubuntu: /etc/cloud/templates/sources.list.ubuntu.tmpl |
218 | +# Debian: /etc/cloud/templates/sources.list.debian.tmpl |
219 | +# Others: n/a |
220 | +# This will follow the normal mirror/codename replacement rules before |
221 | +# being written to disk. |
222 | +apt_custom_sources_list: | |
223 | + ## template:jinja |
224 | + ## Note, this file is written by cloud-init on first boot of an instance |
225 | + ## modifications made here will not survive a re-bundle. |
226 | + ## if you wish to make changes you can: |
227 | + ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg |
228 | + ## or do the same in user-data |
229 | + ## b.) add sources in /etc/apt/sources.list.d |
230 | + ## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl |
231 | + deb {{mirror}} {{codename}} main restricted |
232 | + deb-src {{mirror}} {{codename}} main restricted |
233 | + |
234 | + # could drop some of the usually used entries |
235 | + |
236 | + # could refer to other mirrors |
237 | + deb http://ddebs.ubuntu.com {{codename}} main restricted universe multiverse |
238 | + deb http://ddebs.ubuntu.com {{codename}}-updates main restricted universe multiverse |
239 | + deb http://ddebs.ubuntu.com {{codename}}-proposed main restricted universe multiverse |
240 | + |
241 | + # or even more uncommon examples like local or NFS mounted repos, |
242 | + # eventually whatever is compatible with sources.list syntax |
243 | + deb file:/home/apt/debian unstable main contrib non-free |
244 | + |
245 | # 'source' entries in apt-sources that match this python regex |
246 | # expression will be passed to add-apt-repository |
247 | add_apt_repo_match: '^[\w-]+:\w' |
248 | |
249 | +# 'apt_sources' is a dictionary |
250 | +# The key is the filename and will be prepended by /etc/apt/sources.list.d/ if |
251 | +# it doesn't start with a '/'. |
252 | +# There are certain cases - where no content is written into a source.list file |
253 | +# where the filename will be ignored - yet it can still be used as index for |
254 | +# merging. |
255 | +# The value it maps to is a dictionary with the following optional entries: |
256 | +# source: a sources.list entry (some variable replacements apply) |
257 | +# keyid: providing a key to import via shortid or fingerprint |
258 | +# key: providing a raw PGP key |
259 | +# keyserver: keyserver to fetch keys from, default is keyserver.ubuntu.com |
260 | +# filename: for compatibility with the older format (now the key to this |
261 | +# dictionary is the filename). If specified this overwrites the |
262 | +# filename given as key. |
263 | + |
264 | +# the new "filename: {specification-dictionary}, filename2: ..." format allows |
265 | +# better merging between multiple input files than a list like: |
266 | +# cloud-config1 |
267 | +# sources: |
268 | + s1: {'key': 'key1', 'source': 'source1'} |
269 | +# cloud-config2 |
270 | +# sources: |
271 | + s2: {'key': 'key2'} |
272 | + s1: {filename: 'foo'} |
273 | +# this would be merged to |
274 | +#sources: |
275 | +# s1: |
276 | +# filename: foo |
277 | +# key: key1 |
278 | +# source: source1 |
279 | +# s2: |
280 | +# key: key2 |
281 | +# Be aware that this style of merging is not the default (for backward |
282 | +# compatibility reasons). You should specify the following merge_how to get |
283 | +# this more complete and modern merging behaviour: |
284 | +# merge_how: "list()+dict()+str()" |
285 | +# This would then also be equivalent to the config merging used in curtin |
286 | +# (https://launchpad.net/curtin). |
287 | + |
288 | +# for more details see below in the various examples |
289 | + |
290 | apt_sources: |
291 | - - source: "deb http://ppa.launchpad.net/byobu/ppa/ubuntu karmic main" |
292 | + byobu-ppa.list: |
293 | + source: "deb http://ppa.launchpad.net/byobu/ppa/ubuntu karmic main" |
294 | keyid: F430BBA5 # GPG key ID published on a key server |
295 | - filename: byobu-ppa.list |
296 | + # adding a source.list line, importing a gpg key for a given key id and |
297 | + # storing it in the file /etc/apt/sources.list.d/byobu-ppa.list |
298 | |
299 | # PPA shortcut: |
300 | # * Setup correct apt sources.list line |
301 | @@ -87,7 +160,9 @@ |
302 | # |
303 | # See https://help.launchpad.net/Packaging/PPA for more information |
304 | # this requires 'add-apt-repository' |
305 | - - source: "ppa:smoser/ppa" # Quote the string |
306 | + # due to that the filename key is ignored in this case |
307 | + ignored1: |
308 | + source: "ppa:smoser/ppa" # Quote the string |
309 | |
310 | # Custom apt repository: |
311 | # * all that is required is 'source' |
312 | @@ -95,42 +170,74 @@ |
313 | # * [optional] Import the apt signing key from the keyserver |
314 | # * Defaults: |
315 | # + keyserver: keyserver.ubuntu.com |
316 | - # + filename: cloud_config_sources.list |
317 | # |
318 | # See sources.list man page for more information about the format |
319 | - - source: deb http://archive.ubuntu.com/ubuntu karmic-backports main universe multiverse restricted |
320 | + my-repo.list: |
321 | + source: deb http://archive.ubuntu.com/ubuntu karmic-backports main universe multiverse restricted |
322 | |
323 | # sources can use $MIRROR and $RELEASE and they will be replaced |
324 | # with the local mirror for this cloud, and the running release |
325 | # the entry below would be possibly turned into: |
326 | - # - source: deb http://us-east-1.ec2.archive.ubuntu.com/ubuntu natty multiverse |
327 | - - source: deb $MIRROR $RELEASE multiverse |
328 | + # source: deb http://us-east-1.ec2.archive.ubuntu.com/ubuntu natty multiverse |
329 | + my-repo.list: |
330 | + source: deb $MIRROR $RELEASE multiverse |
331 | |
332 | # this would have the same end effect as 'ppa:byobu/ppa' |
333 | - - source: "deb http://ppa.launchpad.net/byobu/ppa/ubuntu karmic main" |
334 | + my-repo.list: |
335 | + source: "deb http://ppa.launchpad.net/byobu/ppa/ubuntu karmic main" |
336 | keyid: F430BBA5 # GPG key ID published on a key server |
337 | filename: byobu-ppa.list |
338 | |
339 | + # this would only import the key without adding a ppa or other source spec |
340 | + # since this doesn't generate a source.list file the filename key is ignored |
341 | + ignored2: |
342 | + keyid: F430BBA5 # GPG key ID published on a key server |
343 | + |
344 | + # In general keyid's can also be specified via their long fingerprints |
345 | + # since this doesn't generate a source.list file the filename key is ignored |
346 | + ignored3: |
347 | + keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77 |
348 | + |
349 | # Custom apt repository: |
350 | # * The apt signing key can also be specified |
351 | # by providing a pgp public key block |
352 | - # * Providing the PBG key here is the most robust method for |
353 | + # * Providing the PGP key here is the most robust method for |
354 | # specifying a key, as it removes dependency on a remote key server |
355 | - |
356 | - - source: deb http://ppa.launchpad.net/alestic/ppa/ubuntu karmic main |
357 | - key: | # The value needs to start with -----BEGIN PGP PUBLIC KEY BLOCK----- |
358 | - -----BEGIN PGP PUBLIC KEY BLOCK----- |
359 | - Version: SKS 1.0.10 |
360 | - |
361 | - mI0ESpA3UQEEALdZKVIMq0j6qWAXAyxSlF63SvPVIgxHPb9Nk0DZUixn+akqytxG4zKCONz6 |
362 | - qLjoBBfHnynyVLfT4ihg9an1PqxRnTO+JKQxl8NgKGz6Pon569GtAOdWNKw15XKinJTDLjnj |
363 | - 9y96ljJqRcpV9t/WsIcdJPcKFR5voHTEoABE2aEXABEBAAG0GUxhdW5jaHBhZCBQUEEgZm9y |
364 | - IEFsZXN0aWOItgQTAQIAIAUCSpA3UQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEA7H |
365 | - 5Qi+CcVxWZ8D/1MyYvfj3FJPZUm2Yo1zZsQ657vHI9+pPouqflWOayRR9jbiyUFIn0VdQBrP |
366 | - t0FwvnOFArUovUWoKAEdqR8hPy3M3APUZjl5K4cMZR/xaMQeQRZ5CHpS4DBKURKAHC0ltS5o |
367 | - uBJKQOZm5iltJp15cgyIkBkGe8Mx18VFyVglAZey |
368 | - =Y2oI |
369 | - -----END PGP PUBLIC KEY BLOCK----- |
370 | + my-repo.list: |
371 | + source: deb http://ppa.launchpad.net/alestic/ppa/ubuntu karmic main |
372 | + key: | # The value needs to start with -----BEGIN PGP PUBLIC KEY BLOCK----- |
373 | + -----BEGIN PGP PUBLIC KEY BLOCK----- |
374 | + Version: SKS 1.0.10 |
375 | + |
376 | + mI0ESpA3UQEEALdZKVIMq0j6qWAXAyxSlF63SvPVIgxHPb9Nk0DZUixn+akqytxG4zKCONz6 |
377 | + qLjoBBfHnynyVLfT4ihg9an1PqxRnTO+JKQxl8NgKGz6Pon569GtAOdWNKw15XKinJTDLjnj |
378 | + 9y96ljJqRcpV9t/WsIcdJPcKFR5voHTEoABE2aEXABEBAAG0GUxhdW5jaHBhZCBQUEEgZm9y |
379 | + IEFsZXN0aWOItgQTAQIAIAUCSpA3UQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEA7H |
380 | + 5Qi+CcVxWZ8D/1MyYvfj3FJPZUm2Yo1zZsQ657vHI9+pPouqflWOayRR9jbiyUFIn0VdQBrP |
381 | + t0FwvnOFArUovUWoKAEdqR8hPy3M3APUZjl5K4cMZR/xaMQeQRZ5CHpS4DBKURKAHC0ltS5o |
382 | + uBJKQOZm5iltJp15cgyIkBkGe8Mx18VFyVglAZey |
383 | + =Y2oI |
384 | + -----END PGP PUBLIC KEY BLOCK----- |
385 | + |
386 | + # Custom gpg key: |
387 | + # * As with keyid, a key may also be specified without a related source. |
388 | + # * all other facts mentioned above still apply |
389 | + # since this doesn't generate a source.list file the filename key is ignored |
390 | + ignored4: |
391 | + key: | # The value needs to start with -----BEGIN PGP PUBLIC KEY BLOCK----- |
392 | + -----BEGIN PGP PUBLIC KEY BLOCK----- |
393 | + Version: SKS 1.0.10 |
394 | + |
395 | + mI0ESpA3UQEEALdZKVIMq0j6qWAXAyxSlF63SvPVIgxHPb9Nk0DZUixn+akqytxG4zKCONz6 |
396 | + qLjoBBfHnynyVLfT4ihg9an1PqxRnTO+JKQxl8NgKGz6Pon569GtAOdWNKw15XKinJTDLjnj |
397 | + 9y96ljJqRcpV9t/WsIcdJPcKFR5voHTEoABE2aEXABEBAAG0GUxhdW5jaHBhZCBQUEEgZm9y |
398 | + IEFsZXN0aWOItgQTAQIAIAUCSpA3UQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEA7H |
399 | + 5Qi+CcVxWZ8D/1MyYvfj3FJPZUm2Yo1zZsQ657vHI9+pPouqflWOayRR9jbiyUFIn0VdQBrP |
400 | + t0FwvnOFArUovUWoKAEdqR8hPy3M3APUZjl5K4cMZR/xaMQeQRZ5CHpS4DBKURKAHC0ltS5o |
401 | + uBJKQOZm5iltJp15cgyIkBkGe8Mx18VFyVglAZey |
402 | + =Y2oI |
403 | + -----END PGP PUBLIC KEY BLOCK----- |
404 | + |
405 | |
406 | ## apt config via system_info: |
407 | # under the 'system_info', you can further customize cloud-init's interaction |
408 | |
409 | === added file 'tests/unittests/test_handler/test_handler_apt_configure_sources_list.py' |
410 | --- tests/unittests/test_handler/test_handler_apt_configure_sources_list.py 1970-01-01 00:00:00 +0000 |
411 | +++ tests/unittests/test_handler/test_handler_apt_configure_sources_list.py 2016-05-30 11:10:57 +0000 |
412 | @@ -0,0 +1,166 @@ |
413 | +""" test_handler_apt_configure_sources_list |
414 | +Test templating of sources list |
415 | +""" |
416 | +import logging |
417 | +import os |
418 | +import re |
419 | +import shutil |
420 | +import tempfile |
421 | + |
422 | +try: |
423 | + from unittest import mock |
424 | +except ImportError: |
425 | + import mock |
426 | + |
427 | +from cloudinit import cloud |
428 | +from cloudinit import distros |
429 | +from cloudinit import helpers |
430 | +from cloudinit import templater |
431 | +from cloudinit import util |
432 | + |
433 | +from cloudinit.config import cc_apt_configure |
434 | +from cloudinit.sources import DataSourceNone |
435 | + |
436 | +from .. import helpers as t_help |
437 | + |
438 | +LOG = logging.getLogger(__name__) |
439 | + |
440 | +YAML_TEXT_CUSTOM_SL = """ |
441 | +apt_mirror: http://archive.ubuntu.com/ubuntu/ |
442 | +apt_custom_sources_list: | |
443 | + ## template:jinja |
444 | + ## Note, this file is written by cloud-init on first boot of an instance |
445 | + ## modifications made here will not survive a re-bundle. |
446 | + ## if you wish to make changes you can: |
447 | + ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg |
448 | + ## or do the same in user-data |
449 | + ## b.) add sources in /etc/apt/sources.list.d |
450 | + ## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl |
451 | + |
452 | + # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to |
453 | + # newer versions of the distribution. |
454 | + deb {{mirror}} {{codename}} main restricted |
455 | + deb-src {{mirror}} {{codename}} main restricted |
456 | + # FIND_SOMETHING_SPECIAL |
457 | +""" |
458 | + |
459 | +EXPECTED_CONVERTED_CONTENT = ( |
460 | + """## Note, this file is written by cloud-init on first boot of an instance |
461 | +## modifications made here will not survive a re-bundle. |
462 | +## if you wish to make changes you can: |
463 | +## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg |
464 | +## or do the same in user-data |
465 | +## b.) add sources in /etc/apt/sources.list.d |
466 | +## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl |
467 | + |
468 | +# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to |
469 | +# newer versions of the distribution. |
470 | +deb http://archive.ubuntu.com/ubuntu/ fakerelease main restricted |
471 | +deb-src http://archive.ubuntu.com/ubuntu/ fakerelease main restricted |
472 | +# FIND_SOMETHING_SPECIAL |
473 | +""") |
474 | + |
475 | + |
476 | +def load_tfile_or_url(*args, **kwargs): |
477 | + """ load_tfile_or_url |
478 | + load file and return content after decoding |
479 | + """ |
480 | + return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
481 | + |
482 | + |
483 | +class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): |
484 | + """ TestAptSourceConfigSourceList |
485 | + Main Class to test sources list rendering |
486 | + """ |
487 | + def setUp(self): |
488 | + super(TestAptSourceConfigSourceList, self).setUp() |
489 | + self.subp = util.subp |
490 | + self.new_root = tempfile.mkdtemp() |
491 | + self.addCleanup(shutil.rmtree, self.new_root) |
492 | + |
493 | + def _get_cloud(self, distro, metadata=None): |
494 | + self.patchUtils(self.new_root) |
495 | + paths = helpers.Paths({}) |
496 | + cls = distros.fetch(distro) |
497 | + mydist = cls(distro, {}, paths) |
498 | + myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
499 | + if metadata: |
500 | + myds.metadata.update(metadata) |
501 | + return cloud.Cloud(myds, paths, {}, mydist, None) |
502 | + |
503 | + def apt_source_list(self, distro, mirror, mirrorcheck=None): |
504 | + """ apt_source_list |
505 | + Test rendering of a source.list from template for a given distro |
506 | + """ |
507 | + if mirrorcheck is None: |
508 | + mirrorcheck = mirror |
509 | + |
510 | + if isinstance(mirror, list): |
511 | + cfg = {'apt_mirror_search': mirror} |
512 | + else: |
513 | + cfg = {'apt_mirror': mirror} |
514 | + mycloud = self._get_cloud(distro) |
515 | + |
516 | + with mock.patch.object(templater, 'render_to_file') as mocktmpl: |
517 | + with mock.patch.object(os.path, 'isfile', |
518 | + return_value=True) as mockisfile: |
519 | + cc_apt_configure.handle("notimportant", cfg, mycloud, |
520 | + LOG, None) |
521 | + |
522 | + mockisfile.assert_any_call( |
523 | + ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) |
524 | + mocktmpl.assert_called_once_with( |
525 | + ('/etc/cloud/templates/sources.list.%s.tmpl' % distro), |
526 | + '/etc/apt/sources.list', |
527 | + {'codename': '', 'primary': mirrorcheck, 'mirror': mirrorcheck}) |
528 | + |
529 | + def test_apt_source_list_debian(self): |
530 | + """ test_apt_source_list_debian |
531 | + Test rendering of a source.list from template for debian |
532 | + """ |
533 | + self.apt_source_list('debian', 'http://httpredir.debian.org/debian') |
534 | + |
535 | + def test_apt_source_list_ubuntu(self): |
536 | + """ test_apt_source_list_ubuntu |
537 | + Test rendering of a source.list from template for ubuntu |
538 | + """ |
539 | + self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') |
540 | + |
541 | + def test_apt_srcl_debian_mirrorfail(self): |
542 | + """ test_apt_source_list_debian_mirrorfail |
543 | + Test rendering of a source.list from template for debian |
544 | + """ |
545 | + self.apt_source_list('debian', ['http://does.not.exist', |
546 | + 'http://httpredir.debian.org/debian'], |
547 | + 'http://httpredir.debian.org/debian') |
548 | + |
549 | + def test_apt_srcl_ubuntu_mirrorfail(self): |
550 | + """ test_apt_source_list_ubuntu_mirrorfail |
551 | + Test rendering of a source.list from template for ubuntu |
552 | + """ |
553 | + self.apt_source_list('ubuntu', ['http://does.not.exist', |
554 | + 'http://archive.ubuntu.com/ubuntu/'], |
555 | + 'http://archive.ubuntu.com/ubuntu/') |
556 | + |
557 | + def test_apt_srcl_custom(self): |
558 | + """ test_apt_srcl_custom |
559 | + Test rendering from a custom source.list template |
560 | + """ |
561 | + cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL) |
562 | + mycloud = self._get_cloud('ubuntu') |
563 | + |
564 | + # the second mock restores the original subp |
565 | + with mock.patch.object(util, 'write_file') as mockwrite: |
566 | + with mock.patch.object(util, 'subp', self.subp): |
567 | + with mock.patch.object(cc_apt_configure, 'get_release', |
568 | + return_value='fakerelease'): |
569 | + cc_apt_configure.handle("notimportant", cfg, mycloud, |
570 | + LOG, None) |
571 | + |
572 | + mockwrite.assert_called_once_with( |
573 | + '/etc/apt/sources.list', |
574 | + EXPECTED_CONVERTED_CONTENT, |
575 | + mode=420) |
576 | + |
577 | + |
578 | +# vi: ts=4 expandtab |
579 | |
580 | === added file 'tests/unittests/test_handler/test_handler_apt_source.py' |
581 | --- tests/unittests/test_handler/test_handler_apt_source.py 1970-01-01 00:00:00 +0000 |
582 | +++ tests/unittests/test_handler/test_handler_apt_source.py 2016-05-30 11:10:57 +0000 |
583 | @@ -0,0 +1,551 @@ |
584 | +""" test_handler_apt_source |
585 | +Testing various config variations of the apt_source config |
586 | +""" |
587 | +import os |
588 | +import re |
589 | +import shutil |
590 | +import tempfile |
591 | + |
592 | +try: |
593 | + from unittest import mock |
594 | +except ImportError: |
595 | + import mock |
596 | +from mock import call |
597 | + |
598 | +from cloudinit.config import cc_apt_configure |
599 | +from cloudinit import util |
600 | + |
601 | +from ..helpers import TestCase |
602 | + |
603 | +EXPECTEDKEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- |
604 | +Version: GnuPG v1 |
605 | + |
606 | +mI0ESuZLUgEEAKkqq3idtFP7g9hzOu1a8+v8ImawQN4TrvlygfScMU1TIS1eC7UQ |
607 | +NUA8Qqgr9iUaGnejb0VciqftLrU9D6WYHSKz+EITefgdyJ6SoQxjoJdsCpJ7o9Jy |
608 | +8PQnpRttiFm4qHu6BVnKnBNxw/z3ST9YMqW5kbMQpfxbGe+obRox59NpABEBAAG0 |
609 | +HUxhdW5jaHBhZCBQUEEgZm9yIFNjb3R0IE1vc2VyiLYEEwECACAFAkrmS1ICGwMG |
610 | +CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRAGILvPA2g/d3aEA/9tVjc10HOZwV29 |
611 | +OatVuTeERjjrIbxflO586GLA8cp0C9RQCwgod/R+cKYdQcHjbqVcP0HqxveLg0RZ |
612 | +FJpWLmWKamwkABErwQLGlM/Hwhjfade8VvEQutH5/0JgKHmzRsoqfR+LMO6OS+Sm |
613 | +S0ORP6HXET3+jC8BMG4tBWCTK/XEZw== |
614 | +=ACB2 |
615 | +-----END PGP PUBLIC KEY BLOCK-----""" |
616 | + |
617 | + |
618 | +def load_tfile_or_url(*args, **kwargs): |
619 | + """ load_tfile_or_url |
620 | + load file and return content after decoding |
621 | + """ |
622 | + return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
623 | + |
624 | + |
625 | +class TestAptSourceConfig(TestCase): |
626 | + """ TestAptSourceConfig |
627 | + Main Class to test apt_source configs |
628 | + """ |
629 | + def setUp(self): |
630 | + super(TestAptSourceConfig, self).setUp() |
631 | + self.tmp = tempfile.mkdtemp() |
632 | + self.addCleanup(shutil.rmtree, self.tmp) |
633 | + self.aptlistfile = os.path.join(self.tmp, "single-deb.list") |
634 | + self.aptlistfile2 = os.path.join(self.tmp, "single-deb2.list") |
635 | + self.aptlistfile3 = os.path.join(self.tmp, "single-deb3.list") |
636 | + self.join = os.path.join |
637 | + # mock fallback filename into writable tmp dir |
638 | + self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", |
639 | + "cloud_config_sources.list") |
640 | + |
641 | + @staticmethod |
642 | + def _get_default_params(): |
643 | + """ get_default_params |
644 | + Get the most basic default mrror and release info to be used in tests |
645 | + """ |
646 | + params = {} |
647 | + params['RELEASE'] = cc_apt_configure.get_release() |
648 | + params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" |
649 | + return params |
650 | + |
651 | + def myjoin(self, *args, **kwargs): |
652 | + """ myjoin - redir into writable tmpdir""" |
653 | + if (args[0] == "/etc/apt/sources.list.d/" and |
654 | + args[1] == "cloud_config_sources.list" and |
655 | + len(args) == 2): |
656 | + return self.join(self.tmp, args[0].lstrip("/"), args[1]) |
657 | + else: |
658 | + return self.join(*args, **kwargs) |
659 | + |
660 | + def apt_src_basic(self, filename, cfg): |
661 | + """ apt_src_basic |
662 | + Test Fix deb source string, has to overwrite mirror conf in params |
663 | + """ |
664 | + params = self._get_default_params() |
665 | + |
666 | + cc_apt_configure.add_sources(cfg, params) |
667 | + |
668 | + self.assertTrue(os.path.isfile(filename)) |
669 | + |
670 | + contents = load_tfile_or_url(filename) |
671 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
672 | + ("deb", "http://archive.ubuntu.com/ubuntu", |
673 | + "karmic-backports", |
674 | + "main universe multiverse restricted"), |
675 | + contents, flags=re.IGNORECASE)) |
676 | + |
677 | + def test_apt_src_basic(self): |
678 | + """ test_apt_src_basic |
679 | + Test Fix deb source string, has to overwrite mirror conf in params. |
680 | + Test with a filename provided in config. |
681 | + """ |
682 | + cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
683 | + ' karmic-backports' |
684 | + ' main universe multiverse restricted'), |
685 | + 'filename': self.aptlistfile} |
686 | + self.apt_src_basic(self.aptlistfile, [cfg]) |
687 | + |
688 | + def test_apt_src_basic_dict(self): |
689 | + """ test_apt_src_basic_dict |
690 | + Test Fix deb source string, has to overwrite mirror conf in params. |
691 | + Test with a filename provided in config. |
692 | + Provided in a dictionary with filename being the key (new format) |
693 | + """ |
694 | + cfg = {self.aptlistfile: {'source': |
695 | + ('deb http://archive.ubuntu.com/ubuntu' |
696 | + ' karmic-backports' |
697 | + ' main universe multiverse restricted')}} |
698 | + self.apt_src_basic(self.aptlistfile, cfg) |
699 | + |
700 | + def apt_src_basic_tri(self, cfg): |
701 | + """ apt_src_basic_tri |
702 | + Test Fix three deb source string, has to overwrite mirror conf in |
703 | + params. Test with filenames provided in config. |
704 | + generic part to check three files with different content |
705 | + """ |
706 | + self.apt_src_basic(self.aptlistfile, cfg) |
707 | + |
708 | + # extra verify on two extra files of this test |
709 | + contents = load_tfile_or_url(self.aptlistfile2) |
710 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
711 | + ("deb", "http://archive.ubuntu.com/ubuntu", |
712 | + "precise-backports", |
713 | + "main universe multiverse restricted"), |
714 | + contents, flags=re.IGNORECASE)) |
715 | + contents = load_tfile_or_url(self.aptlistfile3) |
716 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
717 | + ("deb", "http://archive.ubuntu.com/ubuntu", |
718 | + "lucid-backports", |
719 | + "main universe multiverse restricted"), |
720 | + contents, flags=re.IGNORECASE)) |
721 | + |
722 | + def test_apt_src_basic_tri(self): |
723 | + """ test_apt_src_basic_tri |
724 | + Test Fix three deb source string, has to overwrite mirror conf in |
725 | + params. Test with filenames provided in config. |
726 | + """ |
727 | + cfg1 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
728 | + ' karmic-backports' |
729 | + ' main universe multiverse restricted'), |
730 | + 'filename': self.aptlistfile} |
731 | + cfg2 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
732 | + ' precise-backports' |
733 | + ' main universe multiverse restricted'), |
734 | + 'filename': self.aptlistfile2} |
735 | + cfg3 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
736 | + ' lucid-backports' |
737 | + ' main universe multiverse restricted'), |
738 | + 'filename': self.aptlistfile3} |
739 | + self.apt_src_basic_tri([cfg1, cfg2, cfg3]) |
740 | + |
741 | + def test_apt_src_basic_dict_tri(self): |
742 | + """ test_apt_src_basic_dict_tri |
743 | + Test Fix three deb source string, has to overwrite mirror conf in |
744 | + params. Test with filenames provided in config. |
745 | + Provided in a dictionary with filename being the key (new format) |
746 | + """ |
747 | + cfg = {self.aptlistfile: {'source': |
748 | + ('deb http://archive.ubuntu.com/ubuntu' |
749 | + ' karmic-backports' |
750 | + ' main universe multiverse restricted')}, |
751 | + self.aptlistfile2: {'source': |
752 | + ('deb http://archive.ubuntu.com/ubuntu' |
753 | + ' precise-backports' |
754 | + ' main universe multiverse restricted')}, |
755 | + self.aptlistfile3: {'source': |
756 | + ('deb http://archive.ubuntu.com/ubuntu' |
757 | + ' lucid-backports' |
758 | + ' main universe multiverse restricted')}} |
759 | + self.apt_src_basic_tri(cfg) |
760 | + |
761 | + def test_apt_src_basic_nofn(self): |
762 | + """ test_apt_src_basic_nofn |
763 | + Test Fix deb source string, has to overwrite mirror conf in params. |
764 | + Test without a filename provided in config and test for known fallback. |
765 | + """ |
766 | + cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
767 | + ' karmic-backports' |
768 | + ' main universe multiverse restricted')} |
769 | + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
770 | + self.apt_src_basic(self.fallbackfn, [cfg]) |
771 | + |
772 | + def apt_src_replacement(self, filename, cfg): |
773 | + """ apt_src_replace |
774 | + Test Autoreplacement of MIRROR and RELEASE in source specs |
775 | + """ |
776 | + params = self._get_default_params() |
777 | + cc_apt_configure.add_sources(cfg, params) |
778 | + |
779 | + self.assertTrue(os.path.isfile(filename)) |
780 | + |
781 | + contents = load_tfile_or_url(filename) |
782 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
783 | + ("deb", params['MIRROR'], params['RELEASE'], |
784 | + "multiverse"), |
785 | + contents, flags=re.IGNORECASE)) |
786 | + |
787 | + def test_apt_src_replace(self): |
788 | + """ test_apt_src_replace |
789 | + Test Autoreplacement of MIRROR and RELEASE in source specs with |
790 | + Filename being set |
791 | + """ |
792 | + cfg = {'source': 'deb $MIRROR $RELEASE multiverse', |
793 | + 'filename': self.aptlistfile} |
794 | + self.apt_src_replacement(self.aptlistfile, [cfg]) |
795 | + |
796 | + def apt_src_replace_tri(self, cfg): |
797 | + """ apt_src_replace_tri |
798 | + Test three autoreplacements of MIRROR and RELEASE in source specs with |
799 | + generic part |
800 | + """ |
801 | + self.apt_src_replacement(self.aptlistfile, cfg) |
802 | + |
803 | + # extra verify on two extra files of this test |
804 | + params = self._get_default_params() |
805 | + contents = load_tfile_or_url(self.aptlistfile2) |
806 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
807 | + ("deb", params['MIRROR'], params['RELEASE'], |
808 | + "main"), |
809 | + contents, flags=re.IGNORECASE)) |
810 | + contents = load_tfile_or_url(self.aptlistfile3) |
811 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
812 | + ("deb", params['MIRROR'], params['RELEASE'], |
813 | + "universe"), |
814 | + contents, flags=re.IGNORECASE)) |
815 | + |
816 | + def test_apt_src_replace_tri(self): |
817 | + """ test_apt_src_replace_tri |
818 | + Test three autoreplacements of MIRROR and RELEASE in source specs with |
819 | + Filename being set |
820 | + """ |
821 | + cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', |
822 | + 'filename': self.aptlistfile} |
823 | + cfg2 = {'source': 'deb $MIRROR $RELEASE main', |
824 | + 'filename': self.aptlistfile2} |
825 | + cfg3 = {'source': 'deb $MIRROR $RELEASE universe', |
826 | + 'filename': self.aptlistfile3} |
827 | + self.apt_src_replace_tri([cfg1, cfg2, cfg3]) |
828 | + |
829 | + def test_apt_src_replace_dict_tri(self): |
830 | + """ test_apt_src_replace_dict_tri |
831 | + Test three autoreplacements of MIRROR and RELEASE in source specs with |
832 | + Filename being set |
833 | + Provided in a dictionary with filename being the key (new format) |
834 | + We also test a new special conditions of the new format that allows |
835 | + filenames to be overwritten inside the directory entry. |
836 | + """ |
837 | + cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, |
838 | + 'notused': {'source': 'deb $MIRROR $RELEASE main', |
839 | + 'filename': self.aptlistfile2}, |
840 | + self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}} |
841 | + self.apt_src_replace_tri(cfg) |
842 | + |
843 | + def test_apt_src_replace_nofn(self): |
844 | + """ test_apt_src_replace_nofn |
845 | + Test Autoreplacement of MIRROR and RELEASE in source specs with |
846 | + No filename being set |
847 | + """ |
848 | + cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} |
849 | + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
850 | + self.apt_src_replacement(self.fallbackfn, [cfg]) |
851 | + |
852 | + def apt_src_keyid(self, filename, cfg, keynum): |
853 | + """ apt_src_keyid |
854 | + Test specification of a source + keyid |
855 | + """ |
856 | + params = self._get_default_params() |
857 | + |
858 | + with mock.patch.object(util, 'subp', |
859 | + return_value=('fakekey 1234', '')) as mockobj: |
860 | + cc_apt_configure.add_sources(cfg, params) |
861 | + |
862 | + # check if it added the right ammount of keys |
863 | + calls = [] |
864 | + for _ in range(keynum): |
865 | + calls.append(call(('apt-key', 'add', '-'), 'fakekey 1234')) |
866 | + mockobj.assert_has_calls(calls, any_order=True) |
867 | + |
868 | + self.assertTrue(os.path.isfile(filename)) |
869 | + |
870 | + contents = load_tfile_or_url(filename) |
871 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
872 | + ("deb", |
873 | + ('http://ppa.launchpad.net/smoser/' |
874 | + 'cloud-init-test/ubuntu'), |
875 | + "xenial", "main"), |
876 | + contents, flags=re.IGNORECASE)) |
877 | + |
878 | + def test_apt_src_keyid(self): |
879 | + """ test_apt_src_keyid |
880 | + Test specification of a source + keyid with filename being set |
881 | + """ |
882 | + cfg = {'source': ('deb ' |
883 | + 'http://ppa.launchpad.net/' |
884 | + 'smoser/cloud-init-test/ubuntu' |
885 | + ' xenial main'), |
886 | + 'keyid': "03683F77", |
887 | + 'filename': self.aptlistfile} |
888 | + self.apt_src_keyid(self.aptlistfile, [cfg], 1) |
889 | + |
890 | + def test_apt_src_keyid_tri(self): |
891 | + """ test_apt_src_keyid_tri |
892 | + Test specification of a source + keyid with filename being set |
893 | + Setting three of such, check for content and keys |
894 | + """ |
895 | + cfg1 = {'source': ('deb ' |
896 | + 'http://ppa.launchpad.net/' |
897 | + 'smoser/cloud-init-test/ubuntu' |
898 | + ' xenial main'), |
899 | + 'keyid': "03683F77", |
900 | + 'filename': self.aptlistfile} |
901 | + cfg2 = {'source': ('deb ' |
902 | + 'http://ppa.launchpad.net/' |
903 | + 'smoser/cloud-init-test/ubuntu' |
904 | + ' xenial universe'), |
905 | + 'keyid': "03683F77", |
906 | + 'filename': self.aptlistfile2} |
907 | + cfg3 = {'source': ('deb ' |
908 | + 'http://ppa.launchpad.net/' |
909 | + 'smoser/cloud-init-test/ubuntu' |
910 | + ' xenial multiverse'), |
911 | + 'keyid': "03683F77", |
912 | + 'filename': self.aptlistfile3} |
913 | + |
914 | + self.apt_src_keyid(self.aptlistfile, [cfg1, cfg2, cfg3], 3) |
915 | + contents = load_tfile_or_url(self.aptlistfile2) |
916 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
917 | + ("deb", |
918 | + ('http://ppa.launchpad.net/smoser/' |
919 | + 'cloud-init-test/ubuntu'), |
920 | + "xenial", "universe"), |
921 | + contents, flags=re.IGNORECASE)) |
922 | + contents = load_tfile_or_url(self.aptlistfile3) |
923 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
924 | + ("deb", |
925 | + ('http://ppa.launchpad.net/smoser/' |
926 | + 'cloud-init-test/ubuntu'), |
927 | + "xenial", "multiverse"), |
928 | + contents, flags=re.IGNORECASE)) |
929 | + |
930 | + def test_apt_src_keyid_nofn(self): |
931 | + """ test_apt_src_keyid_nofn |
932 | + Test specification of a source + keyid without filename being set |
933 | + """ |
934 | + cfg = {'source': ('deb ' |
935 | + 'http://ppa.launchpad.net/' |
936 | + 'smoser/cloud-init-test/ubuntu' |
937 | + ' xenial main'), |
938 | + 'keyid': "03683F77"} |
939 | + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
940 | + self.apt_src_keyid(self.fallbackfn, [cfg], 1) |
941 | + |
942 | + def apt_src_key(self, filename, cfg): |
943 | + """ apt_src_key |
944 | + Test specification of a source + key |
945 | + """ |
946 | + params = self._get_default_params() |
947 | + |
948 | + with mock.patch.object(util, 'subp') as mockobj: |
949 | + cc_apt_configure.add_sources([cfg], params) |
950 | + |
951 | + mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 4321') |
952 | + |
953 | + self.assertTrue(os.path.isfile(filename)) |
954 | + |
955 | + contents = load_tfile_or_url(filename) |
956 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
957 | + ("deb", |
958 | + ('http://ppa.launchpad.net/smoser/' |
959 | + 'cloud-init-test/ubuntu'), |
960 | + "xenial", "main"), |
961 | + contents, flags=re.IGNORECASE)) |
962 | + |
963 | + def test_apt_src_key(self): |
964 | + """ test_apt_src_key |
965 | + Test specification of a source + key with filename being set |
966 | + """ |
967 | + cfg = {'source': ('deb ' |
968 | + 'http://ppa.launchpad.net/' |
969 | + 'smoser/cloud-init-test/ubuntu' |
970 | + ' xenial main'), |
971 | + 'key': "fakekey 4321", |
972 | + 'filename': self.aptlistfile} |
973 | + self.apt_src_key(self.aptlistfile, cfg) |
974 | + |
975 | + def test_apt_src_key_nofn(self): |
976 | + """ test_apt_src_key_nofn |
977 | + Test specification of a source + key without filename being set |
978 | + """ |
979 | + cfg = {'source': ('deb ' |
980 | + 'http://ppa.launchpad.net/' |
981 | + 'smoser/cloud-init-test/ubuntu' |
982 | + ' xenial main'), |
983 | + 'key': "fakekey 4321"} |
984 | + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
985 | + self.apt_src_key(self.fallbackfn, cfg) |
986 | + |
987 | + def test_apt_src_keyonly(self): |
988 | + """ test_apt_src_keyonly |
989 | + Test specification key without source |
990 | + """ |
991 | + params = self._get_default_params() |
992 | + cfg = {'key': "fakekey 4242", |
993 | + 'filename': self.aptlistfile} |
994 | + |
995 | + with mock.patch.object(util, 'subp') as mockobj: |
996 | + cc_apt_configure.add_sources([cfg], params) |
997 | + |
998 | + mockobj.assert_called_once_with(('apt-key', 'add', '-'), |
999 | + 'fakekey 4242') |
1000 | + |
1001 | + # filename should be ignored on key only |
1002 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
1003 | + |
1004 | + def test_apt_src_keyidonly(self): |
1005 | + """ test_apt_src_keyidonly |
1006 | + Test specification of a keyid without source |
1007 | + """ |
1008 | + params = self._get_default_params() |
1009 | + cfg = {'keyid': "03683F77", |
1010 | + 'filename': self.aptlistfile} |
1011 | + |
1012 | + with mock.patch.object(util, 'subp', |
1013 | + return_value=('fakekey 1212', '')) as mockobj: |
1014 | + cc_apt_configure.add_sources([cfg], params) |
1015 | + |
1016 | + mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1212') |
1017 | + |
1018 | + # filename should be ignored on key only |
1019 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
1020 | + |
1021 | + def test_apt_src_keyid_real(self): |
1022 | + """ test_apt_src_keyid_real |
1023 | + Test specification of a keyid without source incl |
1024 | + up to addition of the key (nothing but add_key_raw mocked) |
1025 | + """ |
1026 | + keyid = "03683F77" |
1027 | + params = self._get_default_params() |
1028 | + cfg = {'keyid': keyid, |
1029 | + 'filename': self.aptlistfile} |
1030 | + |
1031 | + with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: |
1032 | + cc_apt_configure.add_sources([cfg], params) |
1033 | + |
1034 | + mockobj.assert_called_with(EXPECTEDKEY) |
1035 | + |
1036 | + # filename should be ignored on key only |
1037 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
1038 | + |
1039 | + def test_apt_src_longkeyid_real(self): |
1040 | + """ test_apt_src_longkeyid_real |
1041 | + Test specification of a long key fingerprint without source incl |
1042 | + up to addition of the key (nothing but add_key_raw mocked) |
1043 | + """ |
1044 | + keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" |
1045 | + params = self._get_default_params() |
1046 | + cfg = {'keyid': keyid, |
1047 | + 'filename': self.aptlistfile} |
1048 | + |
1049 | + with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: |
1050 | + cc_apt_configure.add_sources([cfg], params) |
1051 | + |
1052 | + mockobj.assert_called_with(EXPECTEDKEY) |
1053 | + |
1054 | + # filename should be ignored on key only |
1055 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
1056 | + |
1057 | + def test_apt_src_ppa(self): |
1058 | + """ test_apt_src_ppa |
1059 | + Test specification of a ppa |
1060 | + """ |
1061 | + params = self._get_default_params() |
1062 | + cfg = {'source': 'ppa:smoser/cloud-init-test', |
1063 | + 'filename': self.aptlistfile} |
1064 | + |
1065 | + # default matcher needed for ppa |
1066 | + matcher = re.compile(r'^[\w-]+:\w').search |
1067 | + |
1068 | + with mock.patch.object(util, 'subp') as mockobj: |
1069 | + cc_apt_configure.add_sources([cfg], params, aa_repo_match=matcher) |
1070 | + mockobj.assert_called_once_with(['add-apt-repository', |
1071 | + 'ppa:smoser/cloud-init-test']) |
1072 | + |
1073 | + # adding ppa should ignore filename (uses add-apt-repository) |
1074 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
1075 | + |
1076 | + def test_apt_src_ppa_tri(self): |
1077 | + """ test_apt_src_ppa_tri |
1078 | + Test specification of a ppa |
1079 | + """ |
1080 | + params = self._get_default_params() |
1081 | + cfg1 = {'source': 'ppa:smoser/cloud-init-test', |
1082 | + 'filename': self.aptlistfile} |
1083 | + cfg2 = {'source': 'ppa:smoser/cloud-init-test2', |
1084 | + 'filename': self.aptlistfile2} |
1085 | + cfg3 = {'source': 'ppa:smoser/cloud-init-test3', |
1086 | + 'filename': self.aptlistfile3} |
1087 | + |
1088 | + # default matcher needed for ppa |
1089 | + matcher = re.compile(r'^[\w-]+:\w').search |
1090 | + |
1091 | + with mock.patch.object(util, 'subp') as mockobj: |
1092 | + cc_apt_configure.add_sources([cfg1, cfg2, cfg3], params, |
1093 | + aa_repo_match=matcher) |
1094 | + calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test']), |
1095 | + call(['add-apt-repository', 'ppa:smoser/cloud-init-test2']), |
1096 | + call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'])] |
1097 | + mockobj.assert_has_calls(calls, any_order=True) |
1098 | + |
1099 | + # adding ppa should ignore all filenames (uses add-apt-repository) |
1100 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
1101 | + self.assertFalse(os.path.isfile(self.aptlistfile2)) |
1102 | + self.assertFalse(os.path.isfile(self.aptlistfile3)) |
1103 | + |
1104 | + def test_convert_to_new_format(self): |
1105 | + """ test_convert_to_new_format |
1106 | + Test the conversion of old to new format |
1107 | + And the noop conversion of new to new format as well |
1108 | + """ |
1109 | + cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', |
1110 | + 'filename': self.aptlistfile} |
1111 | + cfg2 = {'source': 'deb $MIRROR $RELEASE main', |
1112 | + 'filename': self.aptlistfile2} |
1113 | + cfg3 = {'source': 'deb $MIRROR $RELEASE universe', |
1114 | + 'filename': self.aptlistfile3} |
1115 | + checkcfg = {self.aptlistfile: {'filename': self.aptlistfile, |
1116 | + 'source': 'deb $MIRROR $RELEASE ' |
1117 | + 'multiverse'}, |
1118 | + self.aptlistfile2: {'filename': self.aptlistfile2, |
1119 | + 'source': 'deb $MIRROR $RELEASE main'}, |
1120 | + self.aptlistfile3: {'filename': self.aptlistfile3, |
1121 | + 'source': 'deb $MIRROR $RELEASE ' |
1122 | + 'universe'}} |
1123 | + |
1124 | + newcfg = cc_apt_configure.convert_to_new_format([cfg1, cfg2, cfg3]) |
1125 | + self.assertEqual(newcfg, checkcfg) |
1126 | + |
1127 | + newcfg2 = cc_apt_configure.convert_to_new_format(newcfg) |
1128 | + self.assertEqual(newcfg2, checkcfg) |
1129 | + |
1130 | + with self.assertRaises(ValueError): |
1131 | + cc_apt_configure.convert_to_new_format(5) |
1132 | + |
1133 | + |
1134 | +# vi: ts=4 expandtab |
looks really good. a couple things to tweak.