Merge ~oddbloke/cloud-init/+git/cloud-init:ubuntu/devel into cloud-init:ubuntu/devel
- Git
- lp:~oddbloke/cloud-init/+git/cloud-init
- ubuntu/devel
- Merge into ubuntu/devel
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 1639758c62b57cbe38afbf583472aaf67cbf6616 | ||||||||
Proposed branch: | ~oddbloke/cloud-init/+git/cloud-init:ubuntu/devel | ||||||||
Merge into: | cloud-init:ubuntu/devel | ||||||||
Diff against target: |
2218 lines (+1161/-150) 51 files modified
.gitignore (+1/-0) cloudinit/cmd/clean.py (+14/-13) cloudinit/cmd/tests/test_clean.py (+4/-2) cloudinit/config/cc_apt_pipelining.py (+2/-2) cloudinit/config/cc_chef.py (+3/-0) cloudinit/config/cc_rsyslog.py (+1/-1) cloudinit/config/schema.py (+1/-1) cloudinit/config/tests/test_apt_pipelining.py (+28/-0) cloudinit/distros/__init__.py (+9/-4) cloudinit/handlers/upstart_job.py (+1/-1) cloudinit/net/netplan.py (+2/-1) cloudinit/net/network_state.py (+2/-2) cloudinit/net/tests/test_dhcp.py (+1/-0) cloudinit/netinfo.py (+5/-2) cloudinit/safeyaml.py (+7/-0) cloudinit/sources/DataSourceAzure.py (+9/-4) cloudinit/sources/DataSourceEc2.py (+22/-1) cloudinit/sources/DataSourceOVF.py (+3/-1) cloudinit/sources/helpers/azure.py (+78/-31) cloudinit/sources/helpers/openstack.py (+6/-6) cloudinit/stages.py (+2/-2) cloudinit/tests/helpers.py (+2/-21) cloudinit/tests/test_netinfo.py (+14/-0) cloudinit/url_helper.py (+1/-1) cloudinit/util.py (+11/-17) debian/changelog (+44/-0) debian/cloud-init.lintian-overrides (+4/-0) debian/control (+4/-5) doc/examples/cloud-config-chef.txt (+3/-0) doc/examples/cloud-config-disk-setup.txt (+17/-2) doc/rtd/topics/datasources/ec2.rst (+11/-0) doc/rtd/topics/instancedata.rst (+1/-1) doc/rtd/topics/merging.rst (+84/-6) templates/chef_client.rb.tmpl (+4/-1) tests/cloud_tests/verify.py (+7/-2) tests/data/azure/parse_certificates_fingerprints (+4/-0) tests/data/azure/parse_certificates_pem (+152/-0) tests/data/azure/pubkey_extract_cert (+13/-0) tests/data/azure/pubkey_extract_ssh_key (+1/-0) tests/data/netinfo/freebsd-ifconfig-output (+17/-0) tests/data/netinfo/freebsd-netdev-formatted-output (+11/-0) tests/unittests/test_datasource/test_azure.py (+2/-3) tests/unittests/test_datasource/test_azure_helper.py (+65/-6) tests/unittests/test_datasource/test_configdrive.py (+27/-8) tests/unittests/test_datasource/test_ec2.py (+24/-0) tests/unittests/test_distros/test_create_users.py (+28/-0) tests/unittests/test_distros/test_netconfig.py (+1/-1) tests/unittests/test_ds_identify.py (+1/-1) tests/unittests/test_handler/test_handler_chef.py (+3/-0) tests/unittests/test_net.py (+397/-0) tools/cloud-init-per (+7/-1) |
||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper | Approve | ||
Server Team CI bot | continuous-integration | Needs Fixing | |
Review via email: mp+364099@code.launchpad.net |
Commit message
Description of the change
Server Team CI bot (server-team-bot) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:d78d4d09dfd
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:8f319b3aeff
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:1639758c62b
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Preview Diff
1 | diff --git a/.gitignore b/.gitignore |
2 | index 75565ed..80c509e 100644 |
3 | --- a/.gitignore |
4 | +++ b/.gitignore |
5 | @@ -11,3 +11,4 @@ prime |
6 | stage |
7 | *.snap |
8 | *.cover |
9 | +.idea/ |
10 | diff --git a/cloudinit/cmd/clean.py b/cloudinit/cmd/clean.py |
11 | index de22f7f..30e49de 100644 |
12 | --- a/cloudinit/cmd/clean.py |
13 | +++ b/cloudinit/cmd/clean.py |
14 | @@ -5,12 +5,13 @@ |
15 | """Define 'clean' utility and handler as part of cloud-init commandline.""" |
16 | |
17 | import argparse |
18 | +import glob |
19 | import os |
20 | import sys |
21 | |
22 | from cloudinit.stages import Init |
23 | from cloudinit.util import ( |
24 | - ProcessExecutionError, chdir, del_dir, del_file, get_config_logfiles, |
25 | + ProcessExecutionError, del_dir, del_file, get_config_logfiles, |
26 | is_link, subp) |
27 | |
28 | |
29 | @@ -61,18 +62,18 @@ def remove_artifacts(remove_logs, remove_seed=False): |
30 | |
31 | if not os.path.isdir(init.paths.cloud_dir): |
32 | return 0 # Artifacts dir already cleaned |
33 | - with chdir(init.paths.cloud_dir): |
34 | - for path in os.listdir('.'): |
35 | - if path == 'seed' and not remove_seed: |
36 | - continue |
37 | - try: |
38 | - if os.path.isdir(path) and not is_link(path): |
39 | - del_dir(path) |
40 | - else: |
41 | - del_file(path) |
42 | - except OSError as e: |
43 | - error('Could not remove {0}: {1}'.format(path, str(e))) |
44 | - return 1 |
45 | + seed_path = os.path.join(init.paths.cloud_dir, 'seed') |
46 | + for path in glob.glob('%s/*' % init.paths.cloud_dir): |
47 | + if path == seed_path and not remove_seed: |
48 | + continue |
49 | + try: |
50 | + if os.path.isdir(path) and not is_link(path): |
51 | + del_dir(path) |
52 | + else: |
53 | + del_file(path) |
54 | + except OSError as e: |
55 | + error('Could not remove {0}: {1}'.format(path, str(e))) |
56 | + return 1 |
57 | return 0 |
58 | |
59 | |
60 | diff --git a/cloudinit/cmd/tests/test_clean.py b/cloudinit/cmd/tests/test_clean.py |
61 | index 5a3ec3b..f092ab3 100644 |
62 | --- a/cloudinit/cmd/tests/test_clean.py |
63 | +++ b/cloudinit/cmd/tests/test_clean.py |
64 | @@ -22,7 +22,8 @@ class TestClean(CiTestCase): |
65 | class FakeInit(object): |
66 | cfg = {'def_log_file': self.log1, |
67 | 'output': {'all': '|tee -a {0}'.format(self.log2)}} |
68 | - paths = mypaths(cloud_dir=self.artifact_dir) |
69 | + # Ensure cloud_dir has a trailing slash, to match real behaviour |
70 | + paths = mypaths(cloud_dir='{}/'.format(self.artifact_dir)) |
71 | |
72 | def __init__(self, ds_deps): |
73 | pass |
74 | @@ -136,7 +137,8 @@ class TestClean(CiTestCase): |
75 | clean.remove_artifacts, remove_logs=False) |
76 | self.assertEqual(1, retcode) |
77 | self.assertEqual( |
78 | - 'ERROR: Could not remove dir1: oops\n', m_stderr.getvalue()) |
79 | + 'ERROR: Could not remove %s/dir1: oops\n' % self.artifact_dir, |
80 | + m_stderr.getvalue()) |
81 | |
82 | def test_handle_clean_args_reboots(self): |
83 | """handle_clean_args_reboots when reboot arg is provided.""" |
84 | diff --git a/cloudinit/config/cc_apt_pipelining.py b/cloudinit/config/cc_apt_pipelining.py |
85 | index cdf28cd..459332a 100644 |
86 | --- a/cloudinit/config/cc_apt_pipelining.py |
87 | +++ b/cloudinit/config/cc_apt_pipelining.py |
88 | @@ -49,7 +49,7 @@ APT_PIPE_TPL = ("//Written by cloud-init per 'apt_pipelining'\n" |
89 | |
90 | def handle(_name, cfg, _cloud, log, _args): |
91 | |
92 | - apt_pipe_value = util.get_cfg_option_str(cfg, "apt_pipelining", False) |
93 | + apt_pipe_value = util.get_cfg_option_str(cfg, "apt_pipelining", 'os') |
94 | apt_pipe_value_s = str(apt_pipe_value).lower().strip() |
95 | |
96 | if apt_pipe_value_s == "false": |
97 | @@ -59,7 +59,7 @@ def handle(_name, cfg, _cloud, log, _args): |
98 | elif apt_pipe_value_s in [str(b) for b in range(0, 6)]: |
99 | write_apt_snippet(apt_pipe_value_s, log, DEFAULT_FILE) |
100 | else: |
101 | - log.warn("Invalid option for apt_pipeling: %s", apt_pipe_value) |
102 | + log.warn("Invalid option for apt_pipelining: %s", apt_pipe_value) |
103 | |
104 | |
105 | def write_apt_snippet(setting, log, f_name): |
106 | diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py |
107 | index 46abedd..a624030 100644 |
108 | --- a/cloudinit/config/cc_chef.py |
109 | +++ b/cloudinit/config/cc_chef.py |
110 | @@ -51,6 +51,7 @@ file). |
111 | |
112 | chef: |
113 | client_key: |
114 | + encrypted_data_bag_secret: |
115 | environment: |
116 | file_backup_path: |
117 | file_cache_path: |
118 | @@ -114,6 +115,7 @@ CHEF_RB_TPL_DEFAULTS = { |
119 | 'file_backup_path': "/var/backups/chef", |
120 | 'pid_file': "/var/run/chef/client.pid", |
121 | 'show_time': True, |
122 | + 'encrypted_data_bag_secret': None, |
123 | } |
124 | CHEF_RB_TPL_BOOL_KEYS = frozenset(['show_time']) |
125 | CHEF_RB_TPL_PATH_KEYS = frozenset([ |
126 | @@ -124,6 +126,7 @@ CHEF_RB_TPL_PATH_KEYS = frozenset([ |
127 | 'json_attribs', |
128 | 'file_cache_path', |
129 | 'pid_file', |
130 | + 'encrypted_data_bag_secret', |
131 | ]) |
132 | CHEF_RB_TPL_KEYS = list(CHEF_RB_TPL_DEFAULTS.keys()) |
133 | CHEF_RB_TPL_KEYS.extend(CHEF_RB_TPL_BOOL_KEYS) |
134 | diff --git a/cloudinit/config/cc_rsyslog.py b/cloudinit/config/cc_rsyslog.py |
135 | index 27d2366..22b1753 100644 |
136 | --- a/cloudinit/config/cc_rsyslog.py |
137 | +++ b/cloudinit/config/cc_rsyslog.py |
138 | @@ -203,7 +203,7 @@ LOG = logging.getLogger(__name__) |
139 | COMMENT_RE = re.compile(r'[ ]*[#]+[ ]*') |
140 | HOST_PORT_RE = re.compile( |
141 | r'^(?P<proto>[@]{0,2})' |
142 | - r'(([[](?P<bracket_addr>[^\]]*)[\]])|(?P<addr>[^:]*))' |
143 | + r'(([\[](?P<bracket_addr>[^\]]*)[\]])|(?P<addr>[^:]*))' |
144 | r'([:](?P<port>[0-9]+))?$') |
145 | |
146 | |
147 | diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py |
148 | index 080a6d0..807c3ee 100644 |
149 | --- a/cloudinit/config/schema.py |
150 | +++ b/cloudinit/config/schema.py |
151 | @@ -367,7 +367,7 @@ def handle_schema_args(name, args): |
152 | if not args.annotate: |
153 | error(str(e)) |
154 | except RuntimeError as e: |
155 | - error(str(e)) |
156 | + error(str(e)) |
157 | else: |
158 | print("Valid cloud-config file {0}".format(args.config_file)) |
159 | if args.doc: |
160 | diff --git a/cloudinit/config/tests/test_apt_pipelining.py b/cloudinit/config/tests/test_apt_pipelining.py |
161 | new file mode 100644 |
162 | index 0000000..2a6bb10 |
163 | --- /dev/null |
164 | +++ b/cloudinit/config/tests/test_apt_pipelining.py |
165 | @@ -0,0 +1,28 @@ |
166 | +# This file is part of cloud-init. See LICENSE file for license information. |
167 | + |
168 | +"""Tests cc_apt_pipelining handler""" |
169 | + |
170 | +import cloudinit.config.cc_apt_pipelining as cc_apt_pipelining |
171 | + |
172 | +from cloudinit.tests.helpers import CiTestCase, mock |
173 | + |
174 | + |
175 | +class TestAptPipelining(CiTestCase): |
176 | + |
177 | + @mock.patch('cloudinit.config.cc_apt_pipelining.util.write_file') |
178 | + def test_not_disabled_by_default(self, m_write_file): |
179 | + """ensure that default behaviour is to not disable pipelining""" |
180 | + cc_apt_pipelining.handle('foo', {}, None, mock.MagicMock(), None) |
181 | + self.assertEqual(0, m_write_file.call_count) |
182 | + |
183 | + @mock.patch('cloudinit.config.cc_apt_pipelining.util.write_file') |
184 | + def test_false_disables_pipelining(self, m_write_file): |
185 | + """ensure that pipelining can be disabled with correct config""" |
186 | + cc_apt_pipelining.handle( |
187 | + 'foo', {'apt_pipelining': 'false'}, None, mock.MagicMock(), None) |
188 | + self.assertEqual(1, m_write_file.call_count) |
189 | + args, _ = m_write_file.call_args |
190 | + self.assertEqual(cc_apt_pipelining.DEFAULT_FILE, args[0]) |
191 | + self.assertIn('Pipeline-Depth "0"', args[1]) |
192 | + |
193 | +# vi: ts=4 expandtab |
194 | diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py |
195 | index ef618c2..20c994d 100644 |
196 | --- a/cloudinit/distros/__init__.py |
197 | +++ b/cloudinit/distros/__init__.py |
198 | @@ -577,11 +577,16 @@ class Distro(object): |
199 | """ |
200 | Lock the password of a user, i.e., disable password logins |
201 | """ |
202 | + # passwd must use short '-l' due to SLES11 lacking long form '--lock' |
203 | + lock_tools = (['passwd', '-l', name], ['usermod', '--lock', name]) |
204 | try: |
205 | - # Need to use the short option name '-l' instead of '--lock' |
206 | - # (which would be more descriptive) since SLES 11 doesn't know |
207 | - # about long names. |
208 | - util.subp(['passwd', '-l', name]) |
209 | + cmd = next(l for l in lock_tools if util.which(l[0])) |
210 | + except StopIteration: |
211 | + raise RuntimeError(( |
212 | + "Unable to lock user account '%s'. No tools available. " |
213 | + " Tried: %s.") % (name, [c[0] for c in lock_tools])) |
214 | + try: |
215 | + util.subp(cmd) |
216 | except Exception as e: |
217 | util.logexc(LOG, 'Failed to disable password for user %s', name) |
218 | raise e |
219 | diff --git a/cloudinit/handlers/upstart_job.py b/cloudinit/handlers/upstart_job.py |
220 | index 83fb072..003cad6 100644 |
221 | --- a/cloudinit/handlers/upstart_job.py |
222 | +++ b/cloudinit/handlers/upstart_job.py |
223 | @@ -89,7 +89,7 @@ def _has_suitable_upstart(): |
224 | util.subp(["dpkg", "--compare-versions", dpkg_ver, "ge", good]) |
225 | return True |
226 | except util.ProcessExecutionError as e: |
227 | - if e.exit_code is 1: |
228 | + if e.exit_code == 1: |
229 | pass |
230 | else: |
231 | util.logexc(LOG, "dpkg --compare-versions failed [%s]", |
232 | diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py |
233 | index 21517fd..e54a34e 100644 |
234 | --- a/cloudinit/net/netplan.py |
235 | +++ b/cloudinit/net/netplan.py |
236 | @@ -361,7 +361,8 @@ class Renderer(renderer.Renderer): |
237 | if section: |
238 | dump = util.yaml_dumps({name: section}, |
239 | explicit_start=False, |
240 | - explicit_end=False) |
241 | + explicit_end=False, |
242 | + noalias=True) |
243 | txt = util.indent(dump, ' ' * 4) |
244 | return [txt] |
245 | return [] |
246 | diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py |
247 | index f76e508..539b76d 100644 |
248 | --- a/cloudinit/net/network_state.py |
249 | +++ b/cloudinit/net/network_state.py |
250 | @@ -706,9 +706,9 @@ class NetworkStateInterpreter(object): |
251 | """Common ipconfig extraction from v2 to v1 subnets array.""" |
252 | |
253 | subnets = [] |
254 | - if 'dhcp4' in cfg: |
255 | + if cfg.get('dhcp4'): |
256 | subnets.append({'type': 'dhcp4'}) |
257 | - if 'dhcp6' in cfg: |
258 | + if cfg.get('dhcp6'): |
259 | self.use_ipv6 = True |
260 | subnets.append({'type': 'dhcp6'}) |
261 | |
262 | diff --git a/cloudinit/net/tests/test_dhcp.py b/cloudinit/net/tests/test_dhcp.py |
263 | index 79e8842..5139024 100644 |
264 | --- a/cloudinit/net/tests/test_dhcp.py |
265 | +++ b/cloudinit/net/tests/test_dhcp.py |
266 | @@ -117,6 +117,7 @@ class TestDHCPDiscoveryClean(CiTestCase): |
267 | self.assertEqual('eth9', call[0][1]) |
268 | self.assertIn('/var/tmp/cloud-init/cloud-init-dhcp-', call[0][2]) |
269 | |
270 | + @mock.patch('time.sleep', mock.MagicMock()) |
271 | @mock.patch('cloudinit.net.dhcp.os.kill') |
272 | @mock.patch('cloudinit.net.dhcp.util.subp') |
273 | def test_dhcp_discovery_run_in_sandbox_warns_invalid_pid(self, m_subp, |
274 | diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py |
275 | index 9ff929c..e91cd26 100644 |
276 | --- a/cloudinit/netinfo.py |
277 | +++ b/cloudinit/netinfo.py |
278 | @@ -141,6 +141,9 @@ def _netdev_info_ifconfig(ifconfig_data): |
279 | res = re.match(r'.*<(\S+)>', toks[i + 1]) |
280 | if res: |
281 | devs[curdev]['ipv6'][-1]['scope6'] = res.group(1) |
282 | + else: |
283 | + devs[curdev]['ipv6'][-1]['scope6'] = toks[i + 1] |
284 | + |
285 | return devs |
286 | |
287 | |
288 | @@ -389,8 +392,8 @@ def netdev_pformat(): |
289 | addr.get('scope', empty), data["hwaddr"])) |
290 | for addr in data.get('ipv6'): |
291 | tbl.add_row( |
292 | - (dev, data["up"], addr["ip"], empty, addr["scope6"], |
293 | - data["hwaddr"])) |
294 | + (dev, data["up"], addr["ip"], empty, |
295 | + addr.get("scope6", empty), data["hwaddr"])) |
296 | if len(data.get('ipv6')) + len(data.get('ipv4')) == 0: |
297 | tbl.add_row((dev, data["up"], empty, empty, empty, |
298 | data["hwaddr"])) |
299 | diff --git a/cloudinit/safeyaml.py b/cloudinit/safeyaml.py |
300 | index 7bcf9dd..3bd5e03 100644 |
301 | --- a/cloudinit/safeyaml.py |
302 | +++ b/cloudinit/safeyaml.py |
303 | @@ -17,6 +17,13 @@ _CustomSafeLoader.add_constructor( |
304 | _CustomSafeLoader.construct_python_unicode) |
305 | |
306 | |
307 | +class NoAliasSafeDumper(yaml.dumper.SafeDumper): |
308 | + """A class which avoids constructing anchors/aliases on yaml dump""" |
309 | + |
310 | + def ignore_aliases(self, data): |
311 | + return True |
312 | + |
313 | + |
314 | def load(blob): |
315 | return(yaml.load(blob, Loader=_CustomSafeLoader)) |
316 | |
317 | diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py |
318 | index a4f998b..eccbee5 100644 |
319 | --- a/cloudinit/sources/DataSourceAzure.py |
320 | +++ b/cloudinit/sources/DataSourceAzure.py |
321 | @@ -627,9 +627,11 @@ class DataSourceAzure(sources.DataSource): |
322 | if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN: |
323 | self.bounce_network_with_azure_hostname() |
324 | |
325 | + pubkey_info = self.cfg.get('_pubkeys', None) |
326 | metadata_func = partial(get_metadata_from_fabric, |
327 | fallback_lease_file=self. |
328 | - dhclient_lease_file) |
329 | + dhclient_lease_file, |
330 | + pubkey_info=pubkey_info) |
331 | else: |
332 | metadata_func = self.get_metadata_from_agent |
333 | |
334 | @@ -642,6 +644,7 @@ class DataSourceAzure(sources.DataSource): |
335 | "Error communicating with Azure fabric; You may experience." |
336 | "connectivity issues.", exc_info=True) |
337 | return False |
338 | + |
339 | util.del_file(REPORTED_READY_MARKER_FILE) |
340 | util.del_file(REPROVISION_MARKER_FILE) |
341 | return fabric_data |
342 | @@ -909,13 +912,15 @@ def find_child(node, filter_func): |
343 | def load_azure_ovf_pubkeys(sshnode): |
344 | # This parses a 'SSH' node formatted like below, and returns |
345 | # an array of dicts. |
346 | - # [{'fp': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7', |
347 | - # 'path': 'where/to/go'}] |
348 | + # [{'fingerprint': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7', |
349 | + # 'path': '/where/to/go'}] |
350 | # |
351 | # <SSH><PublicKeys> |
352 | - # <PublicKey><Fingerprint>ABC</FingerPrint><Path>/ABC</Path> |
353 | + # <PublicKey><Fingerprint>ABC</FingerPrint><Path>/x/y/z</Path> |
354 | # ... |
355 | # </PublicKeys></SSH> |
356 | + # Under some circumstances, there may be a <Value> element along with the |
357 | + # Fingerprint and Path. Pass those along if they appear. |
358 | results = find_child(sshnode, lambda n: n.localName == "PublicKeys") |
359 | if len(results) == 0: |
360 | return [] |
361 | diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py |
362 | index 9ccf2cd..4f2f6cc 100644 |
363 | --- a/cloudinit/sources/DataSourceEc2.py |
364 | +++ b/cloudinit/sources/DataSourceEc2.py |
365 | @@ -19,6 +19,7 @@ from cloudinit import sources |
366 | from cloudinit import url_helper as uhelp |
367 | from cloudinit import util |
368 | from cloudinit import warnings |
369 | +from cloudinit.event import EventType |
370 | |
371 | LOG = logging.getLogger(__name__) |
372 | |
373 | @@ -107,6 +108,19 @@ class DataSourceEc2(sources.DataSource): |
374 | 'dynamic', {}).get('instance-identity', {}).get('document', {}) |
375 | return True |
376 | |
377 | + def is_classic_instance(self): |
378 | + """Report if this instance type is Ec2 Classic (non-vpc).""" |
379 | + if not self.metadata: |
380 | + # Can return False on inconclusive as we are also called in |
381 | + # network_config where metadata will be present. |
382 | + # Secondary call site is in packaging postinst script. |
383 | + return False |
384 | + ifaces_md = self.metadata.get('network', {}).get('interfaces', {}) |
385 | + for _mac, mac_data in ifaces_md.get('macs', {}).items(): |
386 | + if 'vpc-id' in mac_data: |
387 | + return False |
388 | + return True |
389 | + |
390 | @property |
391 | def launch_index(self): |
392 | if not self.metadata: |
393 | @@ -320,6 +334,13 @@ class DataSourceEc2(sources.DataSource): |
394 | if isinstance(net_md, dict): |
395 | result = convert_ec2_metadata_network_config( |
396 | net_md, macs_to_nics=macs_to_nics, fallback_nic=iface) |
397 | + # RELEASE_BLOCKER: Xenial debian/postinst needs to add |
398 | + # EventType.BOOT on upgrade path for classic. |
399 | + |
400 | + # Non-VPC (aka Classic) Ec2 instances need to rewrite the |
401 | + # network config file every boot due to MAC address change. |
402 | + if self.is_classic_instance(): |
403 | + self.update_events['network'].add(EventType.BOOT) |
404 | else: |
405 | LOG.warning("Metadata 'network' key not valid: %s.", net_md) |
406 | self._network_config = result |
407 | @@ -442,7 +463,7 @@ def identify_aws(data): |
408 | if (data['uuid'].startswith('ec2') and |
409 | (data['uuid_source'] == 'hypervisor' or |
410 | data['uuid'] == data['serial'])): |
411 | - return CloudNames.AWS |
412 | + return CloudNames.AWS |
413 | |
414 | return None |
415 | |
416 | diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py |
417 | index 3a3fcdf..70e7a5c 100644 |
418 | --- a/cloudinit/sources/DataSourceOVF.py |
419 | +++ b/cloudinit/sources/DataSourceOVF.py |
420 | @@ -15,6 +15,8 @@ import os |
421 | import re |
422 | import time |
423 | |
424 | +import six |
425 | + |
426 | from cloudinit import log as logging |
427 | from cloudinit import sources |
428 | from cloudinit import util |
429 | @@ -434,7 +436,7 @@ def maybe_cdrom_device(devname): |
430 | """ |
431 | if not devname: |
432 | return False |
433 | - elif not isinstance(devname, util.string_types): |
434 | + elif not isinstance(devname, six.string_types): |
435 | raise ValueError("Unexpected input for devname: %s" % devname) |
436 | |
437 | # resolve '..' and multi '/' elements |
438 | diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py |
439 | index e5696b1..2829dd2 100644 |
440 | --- a/cloudinit/sources/helpers/azure.py |
441 | +++ b/cloudinit/sources/helpers/azure.py |
442 | @@ -138,9 +138,36 @@ class OpenSSLManager(object): |
443 | self.certificate = certificate |
444 | LOG.debug('New certificate generated.') |
445 | |
446 | - def parse_certificates(self, certificates_xml): |
447 | - tag = ElementTree.fromstring(certificates_xml).find( |
448 | - './/Data') |
449 | + @staticmethod |
450 | + def _run_x509_action(action, cert): |
451 | + cmd = ['openssl', 'x509', '-noout', action] |
452 | + result, _ = util.subp(cmd, data=cert) |
453 | + return result |
454 | + |
455 | + def _get_ssh_key_from_cert(self, certificate): |
456 | + pub_key = self._run_x509_action('-pubkey', certificate) |
457 | + keygen_cmd = ['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin'] |
458 | + ssh_key, _ = util.subp(keygen_cmd, data=pub_key) |
459 | + return ssh_key |
460 | + |
461 | + def _get_fingerprint_from_cert(self, certificate): |
462 | + """openssl x509 formats fingerprints as so: |
463 | + 'SHA1 Fingerprint=07:3E:19:D1:4D:1C:79:92:24:C6:A0:FD:8D:DA:\ |
464 | + B6:A8:BF:27:D4:73\n' |
465 | + |
466 | + Azure control plane passes that fingerprint as so: |
467 | + '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473' |
468 | + """ |
469 | + raw_fp = self._run_x509_action('-fingerprint', certificate) |
470 | + eq = raw_fp.find('=') |
471 | + octets = raw_fp[eq+1:-1].split(':') |
472 | + return ''.join(octets) |
473 | + |
474 | + def _decrypt_certs_from_xml(self, certificates_xml): |
475 | + """Decrypt the certificates XML document using the our private key; |
476 | + return the list of certs and private keys contained in the doc. |
477 | + """ |
478 | + tag = ElementTree.fromstring(certificates_xml).find('.//Data') |
479 | certificates_content = tag.text |
480 | lines = [ |
481 | b'MIME-Version: 1.0', |
482 | @@ -151,32 +178,30 @@ class OpenSSLManager(object): |
483 | certificates_content.encode('utf-8'), |
484 | ] |
485 | with cd(self.tmpdir): |
486 | - with open('Certificates.p7m', 'wb') as f: |
487 | - f.write(b'\n'.join(lines)) |
488 | out, _ = util.subp( |
489 | - 'openssl cms -decrypt -in Certificates.p7m -inkey' |
490 | + 'openssl cms -decrypt -in /dev/stdin -inkey' |
491 | ' {private_key} -recip {certificate} | openssl pkcs12 -nodes' |
492 | ' -password pass:'.format(**self.certificate_names), |
493 | - shell=True) |
494 | - private_keys, certificates = [], [] |
495 | + shell=True, data=b'\n'.join(lines)) |
496 | + return out |
497 | + |
498 | + def parse_certificates(self, certificates_xml): |
499 | + """Given the Certificates XML document, return a dictionary of |
500 | + fingerprints and associated SSH keys derived from the certs.""" |
501 | + out = self._decrypt_certs_from_xml(certificates_xml) |
502 | current = [] |
503 | + keys = {} |
504 | for line in out.splitlines(): |
505 | current.append(line) |
506 | if re.match(r'[-]+END .*?KEY[-]+$', line): |
507 | - private_keys.append('\n'.join(current)) |
508 | + # ignore private_keys |
509 | current = [] |
510 | elif re.match(r'[-]+END .*?CERTIFICATE[-]+$', line): |
511 | - certificates.append('\n'.join(current)) |
512 | + certificate = '\n'.join(current) |
513 | + ssh_key = self._get_ssh_key_from_cert(certificate) |
514 | + fingerprint = self._get_fingerprint_from_cert(certificate) |
515 | + keys[fingerprint] = ssh_key |
516 | current = [] |
517 | - keys = [] |
518 | - for certificate in certificates: |
519 | - with cd(self.tmpdir): |
520 | - public_key, _ = util.subp( |
521 | - 'openssl x509 -noout -pubkey |' |
522 | - 'ssh-keygen -i -m PKCS8 -f /dev/stdin', |
523 | - data=certificate, |
524 | - shell=True) |
525 | - keys.append(public_key) |
526 | return keys |
527 | |
528 | |
529 | @@ -206,7 +231,6 @@ class WALinuxAgentShim(object): |
530 | self.dhcpoptions = dhcp_options |
531 | self._endpoint = None |
532 | self.openssl_manager = None |
533 | - self.values = {} |
534 | self.lease_file = fallback_lease_file |
535 | |
536 | def clean_up(self): |
537 | @@ -328,8 +352,9 @@ class WALinuxAgentShim(object): |
538 | LOG.debug('Azure endpoint found at %s', endpoint_ip_address) |
539 | return endpoint_ip_address |
540 | |
541 | - def register_with_azure_and_fetch_data(self): |
542 | - self.openssl_manager = OpenSSLManager() |
543 | + def register_with_azure_and_fetch_data(self, pubkey_info=None): |
544 | + if self.openssl_manager is None: |
545 | + self.openssl_manager = OpenSSLManager() |
546 | http_client = AzureEndpointHttpClient(self.openssl_manager.certificate) |
547 | LOG.info('Registering with Azure...') |
548 | attempts = 0 |
549 | @@ -347,16 +372,37 @@ class WALinuxAgentShim(object): |
550 | attempts += 1 |
551 | LOG.debug('Successfully fetched GoalState XML.') |
552 | goal_state = GoalState(response.contents, http_client) |
553 | - public_keys = [] |
554 | - if goal_state.certificates_xml is not None: |
555 | + ssh_keys = [] |
556 | + if goal_state.certificates_xml is not None and pubkey_info is not None: |
557 | LOG.debug('Certificate XML found; parsing out public keys.') |
558 | - public_keys = self.openssl_manager.parse_certificates( |
559 | + keys_by_fingerprint = self.openssl_manager.parse_certificates( |
560 | goal_state.certificates_xml) |
561 | - data = { |
562 | - 'public-keys': public_keys, |
563 | - } |
564 | + ssh_keys = self._filter_pubkeys(keys_by_fingerprint, pubkey_info) |
565 | self._report_ready(goal_state, http_client) |
566 | - return data |
567 | + return {'public-keys': ssh_keys} |
568 | + |
569 | + def _filter_pubkeys(self, keys_by_fingerprint, pubkey_info): |
570 | + """cloud-init expects a straightforward array of keys to be dropped |
571 | + into the user's authorized_keys file. Azure control plane exposes |
572 | + multiple public keys to the VM via wireserver. Select just the |
573 | + user's key(s) and return them, ignoring any other certs. |
574 | + """ |
575 | + keys = [] |
576 | + for pubkey in pubkey_info: |
577 | + if 'value' in pubkey and pubkey['value']: |
578 | + keys.append(pubkey['value']) |
579 | + elif 'fingerprint' in pubkey and pubkey['fingerprint']: |
580 | + fingerprint = pubkey['fingerprint'] |
581 | + if fingerprint in keys_by_fingerprint: |
582 | + keys.append(keys_by_fingerprint[fingerprint]) |
583 | + else: |
584 | + LOG.warning("ovf-env.xml specified PublicKey fingerprint " |
585 | + "%s not found in goalstate XML", fingerprint) |
586 | + else: |
587 | + LOG.warning("ovf-env.xml specified PublicKey with neither " |
588 | + "value nor fingerprint: %s", pubkey) |
589 | + |
590 | + return keys |
591 | |
592 | def _report_ready(self, goal_state, http_client): |
593 | LOG.debug('Reporting ready to Azure fabric.') |
594 | @@ -373,11 +419,12 @@ class WALinuxAgentShim(object): |
595 | LOG.info('Reported ready to Azure fabric.') |
596 | |
597 | |
598 | -def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None): |
599 | +def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None, |
600 | + pubkey_info=None): |
601 | shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file, |
602 | dhcp_options=dhcp_opts) |
603 | try: |
604 | - return shim.register_with_azure_and_fetch_data() |
605 | + return shim.register_with_azure_and_fetch_data(pubkey_info=pubkey_info) |
606 | finally: |
607 | shim.clean_up() |
608 | |
609 | diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py |
610 | index 9c29cea..8f06911 100644 |
611 | --- a/cloudinit/sources/helpers/openstack.py |
612 | +++ b/cloudinit/sources/helpers/openstack.py |
613 | @@ -67,7 +67,7 @@ OS_VERSIONS = ( |
614 | OS_ROCKY, |
615 | ) |
616 | |
617 | -PHYSICAL_TYPES = ( |
618 | +KNOWN_PHYSICAL_TYPES = ( |
619 | None, |
620 | 'bgpovs', # not present in OpenStack upstream but used on OVH cloud. |
621 | 'bridge', |
622 | @@ -600,9 +600,7 @@ def convert_net_json(network_json=None, known_macs=None): |
623 | subnet['ipv6'] = True |
624 | subnets.append(subnet) |
625 | cfg.update({'subnets': subnets}) |
626 | - if link['type'] in PHYSICAL_TYPES: |
627 | - cfg.update({'type': 'physical', 'mac_address': link_mac_addr}) |
628 | - elif link['type'] in ['bond']: |
629 | + if link['type'] in ['bond']: |
630 | params = {} |
631 | if link_mac_addr: |
632 | params['mac_address'] = link_mac_addr |
633 | @@ -641,8 +639,10 @@ def convert_net_json(network_json=None, known_macs=None): |
634 | curinfo.update({'mac': link['vlan_mac_address'], |
635 | 'name': name}) |
636 | else: |
637 | - raise ValueError( |
638 | - 'Unknown network_data link type: %s' % link['type']) |
639 | + if link['type'] not in KNOWN_PHYSICAL_TYPES: |
640 | + LOG.warning('Unknown network_data link type (%s); treating as' |
641 | + ' physical', link['type']) |
642 | + cfg.update({'type': 'physical', 'mac_address': link_mac_addr}) |
643 | |
644 | config.append(cfg) |
645 | link_id_info[curinfo['id']] = curinfo |
646 | diff --git a/cloudinit/stages.py b/cloudinit/stages.py |
647 | index 8a06412..da7d349 100644 |
648 | --- a/cloudinit/stages.py |
649 | +++ b/cloudinit/stages.py |
650 | @@ -548,11 +548,11 @@ class Init(object): |
651 | with events.ReportEventStack("consume-user-data", |
652 | "reading and applying user-data", |
653 | parent=self.reporter): |
654 | - self._consume_userdata(frequency) |
655 | + self._consume_userdata(frequency) |
656 | with events.ReportEventStack("consume-vendor-data", |
657 | "reading and applying vendor-data", |
658 | parent=self.reporter): |
659 | - self._consume_vendordata(frequency) |
660 | + self._consume_vendordata(frequency) |
661 | |
662 | # Perform post-consumption adjustments so that |
663 | # modules that run during the init stage reflect |
664 | diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py |
665 | index 2eb7b0c..f41180f 100644 |
666 | --- a/cloudinit/tests/helpers.py |
667 | +++ b/cloudinit/tests/helpers.py |
668 | @@ -41,26 +41,6 @@ _real_subp = util.subp |
669 | SkipTest = unittest2.SkipTest |
670 | skipIf = unittest2.skipIf |
671 | |
672 | -# Used for detecting different python versions |
673 | -PY2 = False |
674 | -PY26 = False |
675 | -PY27 = False |
676 | -PY3 = False |
677 | - |
678 | -_PY_VER = sys.version_info |
679 | -_PY_MAJOR, _PY_MINOR, _PY_MICRO = _PY_VER[0:3] |
680 | -if (_PY_MAJOR, _PY_MINOR) <= (2, 6): |
681 | - if (_PY_MAJOR, _PY_MINOR) == (2, 6): |
682 | - PY26 = True |
683 | - if (_PY_MAJOR, _PY_MINOR) >= (2, 0): |
684 | - PY2 = True |
685 | -else: |
686 | - if (_PY_MAJOR, _PY_MINOR) == (2, 7): |
687 | - PY27 = True |
688 | - PY2 = True |
689 | - if (_PY_MAJOR, _PY_MINOR) >= (3, 0): |
690 | - PY3 = True |
691 | - |
692 | |
693 | # Makes the old path start |
694 | # with new base instead of whatever |
695 | @@ -207,6 +187,7 @@ class CiTestCase(TestCase): |
696 | if self.with_logs: |
697 | # Remove the handler we setup |
698 | logging.getLogger().handlers = self.old_handlers |
699 | + logging.getLogger().level = None |
700 | util.subp = _real_subp |
701 | super(CiTestCase, self).tearDown() |
702 | |
703 | @@ -356,7 +337,7 @@ class FilesystemMockingTestCase(ResourceUsingTestCase): |
704 | |
705 | def patchOpen(self, new_root): |
706 | trap_func = retarget_many_wrapper(new_root, 1, open) |
707 | - name = 'builtins.open' if PY3 else '__builtin__.open' |
708 | + name = 'builtins.open' if six.PY3 else '__builtin__.open' |
709 | self.patched_funcs.enter_context(mock.patch(name, trap_func)) |
710 | |
711 | def patchStdoutAndStderr(self, stdout=None, stderr=None): |
712 | diff --git a/cloudinit/tests/test_netinfo.py b/cloudinit/tests/test_netinfo.py |
713 | index d76e768..1c8a791 100644 |
714 | --- a/cloudinit/tests/test_netinfo.py |
715 | +++ b/cloudinit/tests/test_netinfo.py |
716 | @@ -11,6 +11,7 @@ from cloudinit.tests.helpers import CiTestCase, mock, readResource |
717 | # Example ifconfig and route output |
718 | SAMPLE_OLD_IFCONFIG_OUT = readResource("netinfo/old-ifconfig-output") |
719 | SAMPLE_NEW_IFCONFIG_OUT = readResource("netinfo/new-ifconfig-output") |
720 | +SAMPLE_FREEBSD_IFCONFIG_OUT = readResource("netinfo/freebsd-ifconfig-output") |
721 | SAMPLE_IPADDRSHOW_OUT = readResource("netinfo/sample-ipaddrshow-output") |
722 | SAMPLE_ROUTE_OUT_V4 = readResource("netinfo/sample-route-output-v4") |
723 | SAMPLE_ROUTE_OUT_V6 = readResource("netinfo/sample-route-output-v6") |
724 | @@ -18,6 +19,7 @@ SAMPLE_IPROUTE_OUT_V4 = readResource("netinfo/sample-iproute-output-v4") |
725 | SAMPLE_IPROUTE_OUT_V6 = readResource("netinfo/sample-iproute-output-v6") |
726 | NETDEV_FORMATTED_OUT = readResource("netinfo/netdev-formatted-output") |
727 | ROUTE_FORMATTED_OUT = readResource("netinfo/route-formatted-output") |
728 | +FREEBSD_NETDEV_OUT = readResource("netinfo/freebsd-netdev-formatted-output") |
729 | |
730 | |
731 | class TestNetInfo(CiTestCase): |
732 | @@ -45,6 +47,18 @@ class TestNetInfo(CiTestCase): |
733 | |
734 | @mock.patch('cloudinit.netinfo.util.which') |
735 | @mock.patch('cloudinit.netinfo.util.subp') |
736 | + def test_netdev_freebsd_nettools_pformat(self, m_subp, m_which): |
737 | + """netdev_pformat properly rendering netdev new nettools info.""" |
738 | + m_subp.return_value = (SAMPLE_FREEBSD_IFCONFIG_OUT, '') |
739 | + m_which.side_effect = lambda x: x if x == 'ifconfig' else None |
740 | + content = netdev_pformat() |
741 | + print() |
742 | + print(content) |
743 | + print() |
744 | + self.assertEqual(FREEBSD_NETDEV_OUT, content) |
745 | + |
746 | + @mock.patch('cloudinit.netinfo.util.which') |
747 | + @mock.patch('cloudinit.netinfo.util.subp') |
748 | def test_netdev_iproute_pformat(self, m_subp, m_which): |
749 | """netdev_pformat properly rendering ip route info.""" |
750 | m_subp.return_value = (SAMPLE_IPADDRSHOW_OUT, '') |
751 | diff --git a/cloudinit/url_helper.py b/cloudinit/url_helper.py |
752 | index 396d69a..0af0d9e 100644 |
753 | --- a/cloudinit/url_helper.py |
754 | +++ b/cloudinit/url_helper.py |
755 | @@ -521,7 +521,7 @@ class OauthUrlHelper(object): |
756 | if extra_exception_cb: |
757 | ret = extra_exception_cb(msg, exception) |
758 | finally: |
759 | - self.exception_cb(msg, exception) |
760 | + self.exception_cb(msg, exception) |
761 | return ret |
762 | |
763 | def _headers_cb(self, extra_headers_cb, url): |
764 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
765 | index a8a232b..a192091 100644 |
766 | --- a/cloudinit/util.py |
767 | +++ b/cloudinit/util.py |
768 | @@ -51,11 +51,6 @@ from cloudinit import version |
769 | |
770 | from cloudinit.settings import (CFG_BUILTIN) |
771 | |
772 | -try: |
773 | - string_types = (basestring,) |
774 | -except NameError: |
775 | - string_types = (str,) |
776 | - |
777 | _DNS_REDIRECT_IP = None |
778 | LOG = logging.getLogger(__name__) |
779 | |
780 | @@ -77,7 +72,6 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'], |
781 | PROC_CMDLINE = None |
782 | |
783 | _LSB_RELEASE = {} |
784 | -PY26 = sys.version_info[0:2] == (2, 6) |
785 | |
786 | |
787 | def get_architecture(target=None): |
788 | @@ -125,7 +119,7 @@ def target_path(target, path=None): |
789 | # return 'path' inside target, accepting target as None |
790 | if target in (None, ""): |
791 | target = "/" |
792 | - elif not isinstance(target, string_types): |
793 | + elif not isinstance(target, six.string_types): |
794 | raise ValueError("Unexpected input for target: %s" % target) |
795 | else: |
796 | target = os.path.abspath(target) |
797 | @@ -1596,14 +1590,17 @@ def json_dumps(data): |
798 | separators=(',', ': '), default=json_serialize_default) |
799 | |
800 | |
801 | -def yaml_dumps(obj, explicit_start=True, explicit_end=True): |
802 | +def yaml_dumps(obj, explicit_start=True, explicit_end=True, noalias=False): |
803 | """Return data in nicely formatted yaml.""" |
804 | - return yaml.safe_dump(obj, |
805 | - line_break="\n", |
806 | - indent=4, |
807 | - explicit_start=explicit_start, |
808 | - explicit_end=explicit_end, |
809 | - default_flow_style=False) |
810 | + |
811 | + return yaml.dump(obj, |
812 | + line_break="\n", |
813 | + indent=4, |
814 | + explicit_start=explicit_start, |
815 | + explicit_end=explicit_end, |
816 | + default_flow_style=False, |
817 | + Dumper=(safeyaml.NoAliasSafeDumper |
818 | + if noalias else yaml.dumper.Dumper)) |
819 | |
820 | |
821 | def ensure_dir(path, mode=None): |
822 | @@ -2817,9 +2814,6 @@ def load_shell_content(content, add_empty=False, empty_val=None): |
823 | variables. Set their value to empty_val.""" |
824 | |
825 | def _shlex_split(blob): |
826 | - if PY26 and isinstance(blob, six.text_type): |
827 | - # Older versions don't support unicode input |
828 | - blob = blob.encode("utf8") |
829 | return shlex.split(blob, comments=True) |
830 | |
831 | data = {} |
832 | diff --git a/debian/changelog b/debian/changelog |
833 | index 70f1879..61f09d7 100644 |
834 | --- a/debian/changelog |
835 | +++ b/debian/changelog |
836 | @@ -1,3 +1,47 @@ |
837 | +cloud-init (18.5-44-g7c07af28-0ubuntu1) disco; urgency=medium |
838 | + |
839 | + * New upstream snapshot. |
840 | + - Support locking user with usermod if passwd is not available. |
841 | + [Scott Moser] |
842 | + - Example for Microsoft Azure data disk added. [Anton Olifir] |
843 | + - clean: correctly determine the path for excluding seed directory |
844 | + (LP: #1818571) |
845 | + - helpers/openstack: Treat unknown link types as physical (LP: #1639263) |
846 | + - drop Python 2.6 support and our NIH version detection |
847 | + - tip-pylint: Fix assignment-from-return-none errors |
848 | + - net: append type:dhcp[46] only if dhcp[46] is True in v2 netconfig |
849 | + [Kurt Stieger] (LP: #1818032) |
850 | + - cc_apt_pipelining: stop disabling pipelining by default (LP: #1794982) |
851 | + - tests: fix some slow tests and some leaking state |
852 | + - util: don't determine string_types ourselves |
853 | + - cc_rsyslog: Escape possible nested set (LP: #1816967) |
854 | + - Enable encrypted_data_bag_secret support for Chef |
855 | + [Eric Williams] (LP: #1817082) |
856 | + - azure: Filter list of ssh keys pulled from fabric [Jason Zions (MSFT)] |
857 | + - doc: update merging doc with fixes and some additional details/examples |
858 | + - tests: integration test failure summary to use traceback if empty error |
859 | + - This is to fix https://bugs.launchpad.net/cloud-init/+bug/1812676 |
860 | + [Vitaly Kuznetsov] |
861 | + - EC2: Rewrite network config on AWS Classic instances every boot |
862 | + [Guilherme G. Piccoli] (LP: #1802073) |
863 | + - netinfo: Adjust ifconfig output parsing for FreeBSD ipv6 entries |
864 | + (LP: #1779672) |
865 | + - netplan: Don't render yaml aliases when dumping netplan (LP: #1815051) |
866 | + - add PyCharm IDE .idea/ path to .gitignore [Dominic Schlegel] |
867 | + - correct grammar issue in instance metadata documentation |
868 | + [Dominic Schlegel] (LP: #1802188) |
869 | + - clean: cloud-init clean should not trace when run from within cloud_dir |
870 | + (LP: #1795508) |
871 | + - Resolve flake8 comparison and pycodestyle over-ident issues |
872 | + [Paride Legovini] |
873 | + * Update netplan dependency package (LP: #1813667) |
874 | + * Fix build-depends-on-obsolete-package for dh-systemd |
875 | + * Change Priority from extra to optional |
876 | + * Override lintian warnings about WantedBy=cloud-init.target |
877 | + * Change Maintainer to Ubuntu Developers |
878 | + |
879 | + -- Daniel Watkins <oddbloke@ubuntu.com> Thu, 07 Mar 2019 10:32:26 -0500 |
880 | + |
881 | cloud-init (18.5-21-g8ee294d5-0ubuntu1) disco; urgency=medium |
882 | |
883 | * New upstream snapshot. |
884 | diff --git a/debian/cloud-init.lintian-overrides b/debian/cloud-init.lintian-overrides |
885 | new file mode 100644 |
886 | index 0000000..58fac0d |
887 | --- /dev/null |
888 | +++ b/debian/cloud-init.lintian-overrides |
889 | @@ -0,0 +1,4 @@ |
890 | +cloud-init binary: systemd-service-file-refers-to-unusual-wantedby-target lib/systemd/system/cloud-config.service cloud-init.target |
891 | +cloud-init binary: systemd-service-file-refers-to-unusual-wantedby-target lib/systemd/system/cloud-final.service cloud-init.target |
892 | +cloud-init binary: systemd-service-file-refers-to-unusual-wantedby-target lib/systemd/system/cloud-init-local.service cloud-init.target |
893 | +cloud-init binary: systemd-service-file-refers-to-unusual-wantedby-target lib/systemd/system/cloud-init.service cloud-init.target |
894 | diff --git a/debian/control b/debian/control |
895 | index 282304a..b1e2e8f 100644 |
896 | --- a/debian/control |
897 | +++ b/debian/control |
898 | @@ -1,10 +1,9 @@ |
899 | Source: cloud-init |
900 | Section: admin |
901 | -Priority: extra |
902 | -Maintainer: Scott Moser <smoser@ubuntu.com> |
903 | -Build-Depends: debhelper (>= 9), |
904 | +Priority: optional |
905 | +Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
906 | +Build-Depends: debhelper (>= 9.20160709), |
907 | dh-python, |
908 | - dh-systemd, |
909 | iproute2, |
910 | pep8, |
911 | po-debconf, |
912 | @@ -36,7 +35,7 @@ Architecture: all |
913 | Depends: cloud-guest-utils | cloud-utils, |
914 | isc-dhcp-client, |
915 | iproute2, |
916 | - nplan | ifupdown, |
917 | + netplan.io | ifupdown, |
918 | procps, |
919 | python3, |
920 | python3-requests, |
921 | diff --git a/doc/examples/cloud-config-chef.txt b/doc/examples/cloud-config-chef.txt |
922 | index defc5a5..2320e01 100644 |
923 | --- a/doc/examples/cloud-config-chef.txt |
924 | +++ b/doc/examples/cloud-config-chef.txt |
925 | @@ -98,6 +98,9 @@ chef: |
926 | # to the install script |
927 | omnibus_version: "12.3.0" |
928 | |
929 | + # If encrypted data bags are used, the client needs to have a secrets file |
930 | + # configured to decrypt them |
931 | + encrypted_data_bag_secret: "/etc/chef/encrypted_data_bag_secret" |
932 | |
933 | # Capture all subprocess output into a logfile |
934 | # Useful for troubleshooting cloud-init issues |
935 | diff --git a/doc/examples/cloud-config-disk-setup.txt b/doc/examples/cloud-config-disk-setup.txt |
936 | index 43a62a2..89d9ff5 100644 |
937 | --- a/doc/examples/cloud-config-disk-setup.txt |
938 | +++ b/doc/examples/cloud-config-disk-setup.txt |
939 | @@ -17,7 +17,7 @@ fs_setup: |
940 | device: ephemeral0 |
941 | partition: auto |
942 | |
943 | -# Default disk definitions for Windows Azure |
944 | +# Default disk definitions for Microsoft Azure |
945 | # ------------------------------------------ |
946 | |
947 | device_aliases: {'ephemeral0': '/dev/sdb'} |
948 | @@ -34,6 +34,21 @@ fs_setup: |
949 | replace_fs: ntfs |
950 | |
951 | |
952 | +# Data disks definitions for Microsoft Azure |
953 | +# ------------------------------------------ |
954 | + |
955 | +disk_setup: |
956 | + /dev/disk/azure/scsi1/lun0: |
957 | + table_type: gpt |
958 | + layout: True |
959 | + overwrite: True |
960 | + |
961 | +fs_setup: |
962 | + - device: /dev/disk/azure/scsi1/lun0 |
963 | + partition: 1 |
964 | + filesystem: ext4 |
965 | + |
966 | + |
967 | # Default disk definitions for SmartOS |
968 | # ------------------------------------ |
969 | |
970 | @@ -242,7 +257,7 @@ fs_setup: |
971 | # |
972 | # "false": If an existing file system exists, skip the creation. |
973 | # |
974 | -# <REPLACE_FS>: This is a special directive, used for Windows Azure that |
975 | +# <REPLACE_FS>: This is a special directive, used for Microsoft Azure that |
976 | # instructs cloud-init to replace a file system of <FS_TYPE>. NOTE: |
977 | # unless you define a label, this requires the use of the 'any' partition |
978 | # directive. |
979 | diff --git a/doc/rtd/topics/datasources/ec2.rst b/doc/rtd/topics/datasources/ec2.rst |
980 | index 64c325d..76beca9 100644 |
981 | --- a/doc/rtd/topics/datasources/ec2.rst |
982 | +++ b/doc/rtd/topics/datasources/ec2.rst |
983 | @@ -90,4 +90,15 @@ An example configuration with the default values is provided below: |
984 | max_wait: 120 |
985 | timeout: 50 |
986 | |
987 | +Notes |
988 | +----- |
989 | + * There are 2 types of EC2 instances network-wise: VPC ones (Virtual Private |
990 | + Cloud) and Classic ones (also known as non-VPC). One major difference |
991 | + between them is that Classic instances have their MAC address changed on |
992 | + stop/restart operations, so cloud-init will recreate the network config |
993 | + file for EC2 Classic instances every boot. On VPC instances this file is |
994 | + generated only in the first boot of the instance. |
995 | + The check for the instance type is performed by is_classic_instance() |
996 | + method. |
997 | + |
998 | .. vi: textwidth=78 |
999 | diff --git a/doc/rtd/topics/instancedata.rst b/doc/rtd/topics/instancedata.rst |
1000 | index 5d2dc94..231a008 100644 |
1001 | --- a/doc/rtd/topics/instancedata.rst |
1002 | +++ b/doc/rtd/topics/instancedata.rst |
1003 | @@ -4,7 +4,7 @@ |
1004 | Instance Metadata |
1005 | ***************** |
1006 | |
1007 | -What is a instance data? |
1008 | +What is instance data? |
1009 | ======================== |
1010 | |
1011 | Instance data is the collection of all configuration data that cloud-init |
1012 | diff --git a/doc/rtd/topics/merging.rst b/doc/rtd/topics/merging.rst |
1013 | index c75ca59..5f7ca18 100644 |
1014 | --- a/doc/rtd/topics/merging.rst |
1015 | +++ b/doc/rtd/topics/merging.rst |
1016 | @@ -21,12 +21,12 @@ For example. |
1017 | .. code-block:: yaml |
1018 | |
1019 | #cloud-config (1) |
1020 | - run_cmd: |
1021 | + runcmd: |
1022 | - bash1 |
1023 | - bash2 |
1024 | |
1025 | #cloud-config (2) |
1026 | - run_cmd: |
1027 | + runcmd: |
1028 | - bash3 |
1029 | - bash4 |
1030 | |
1031 | @@ -36,7 +36,7 @@ cloud-config object that contains the following. |
1032 | .. code-block:: yaml |
1033 | |
1034 | #cloud-config (merged) |
1035 | - run_cmd: |
1036 | + runcmd: |
1037 | - bash3 |
1038 | - bash4 |
1039 | |
1040 | @@ -45,7 +45,7 @@ Typically this is not what users want; instead they would likely prefer: |
1041 | .. code-block:: yaml |
1042 | |
1043 | #cloud-config (merged) |
1044 | - run_cmd: |
1045 | + runcmd: |
1046 | - bash1 |
1047 | - bash2 |
1048 | - bash3 |
1049 | @@ -55,6 +55,45 @@ This way makes it easier to combine the various cloud-config objects you have |
1050 | into a more useful list, thus reducing duplication necessary to accomplish the |
1051 | same result with the previous method. |
1052 | |
1053 | + |
1054 | +Built-in Mergers |
1055 | +================ |
1056 | + |
1057 | +Cloud-init provides merging for the following built-in types: |
1058 | + |
1059 | +- Dict |
1060 | +- List |
1061 | +- String |
1062 | + |
1063 | +The ``Dict`` merger has the following options which control what is done with |
1064 | +values contained within the config. |
1065 | + |
1066 | +- ``allow_delete``: Existing values not present in the new value can be deleted, defaults to False |
1067 | +- ``no_replace``: Do not replace an existing value if one is already present, enabled by default. |
1068 | +- ``replace``: Overwrite existing values with new ones. |
1069 | + |
1070 | +The ``List`` merger has the following options which control what is done with |
1071 | +the values contained within the config. |
1072 | + |
1073 | +- ``append``: Add new value to the end of the list, defaults to False. |
1074 | +- ``prepend``: Add new values to the start of the list, defaults to False. |
1075 | +- ``no_replace``: Do not replace an existing value if one is already present, enabled by default. |
1076 | +- ``replace``: Overwrite existing values with new ones. |
1077 | + |
1078 | +The ``Str`` merger has the following options which control what is done with |
1079 | +the values contained within the config. |
1080 | + |
1081 | +- ``append``: Add new value to the end of the string, defaults to False. |
1082 | + |
1083 | +Common options for all merge types which control how recursive merging is |
1084 | +done on other types. |
1085 | + |
1086 | +- ``recurse_dict``: If True merge the new values of the dictionary, defaults to True. |
1087 | +- ``recurse_list``: If True merge the new values of the list, defaults to False. |
1088 | +- ``recurse_array``: Alias for ``recurse_list``. |
1089 | +- ``recurse_str``: If True merge the new values of the string, defaults to False. |
1090 | + |
1091 | + |
1092 | Customizability |
1093 | =============== |
1094 | |
1095 | @@ -164,8 +203,8 @@ string format (i.e. the second option above), for example: |
1096 | |
1097 | .. code-block:: python |
1098 | |
1099 | - {'merge_how': [{'name': 'list', 'settings': ['extend']}, |
1100 | - {'name': 'dict', 'settings': []}, |
1101 | + {'merge_how': [{'name': 'list', 'settings': ['append']}, |
1102 | + {'name': 'dict', 'settings': ['no_replace', 'recurse_list']}, |
1103 | {'name': 'str', 'settings': ['append']}]} |
1104 | |
1105 | This would be the equivalent format for default string format but in dictionary |
1106 | @@ -201,4 +240,43 @@ Note, however, that merge algorithms are not used *across* types of |
1107 | configuration. As was the case before merging was implemented, |
1108 | user-data will overwrite conf.d configuration without merging. |
1109 | |
1110 | +Example cloud-config |
1111 | +==================== |
1112 | + |
1113 | +A common request is to include multiple ``runcmd`` directives in different |
1114 | +files and merge all of the commands together. To achieve this, we must modify |
1115 | +the default merging to allow for dictionaries to join list values. |
1116 | + |
1117 | + |
1118 | +The first config |
1119 | + |
1120 | +.. code-block:: yaml |
1121 | + |
1122 | + #cloud-config |
1123 | + merge_how: |
1124 | + - name: list |
1125 | + settings: [append] |
1126 | + - name: dict |
1127 | + settings: [no_replace, recurse_list] |
1128 | + |
1129 | + runcmd: |
1130 | + - bash1 |
1131 | + - bash2 |
1132 | + |
1133 | +The second config |
1134 | + |
1135 | +.. code-block:: yaml |
1136 | + |
1137 | + #cloud-config |
1138 | + merge_how: |
1139 | + - name: list |
1140 | + settings: [append] |
1141 | + - name: dict |
1142 | + settings: [no_replace, recurse_list] |
1143 | + |
1144 | + runcmd: |
1145 | + - bash3 |
1146 | + - bash4 |
1147 | + |
1148 | + |
1149 | .. vi: textwidth=78 |
1150 | diff --git a/templates/chef_client.rb.tmpl b/templates/chef_client.rb.tmpl |
1151 | index cbb6b15..99978d3 100644 |
1152 | --- a/templates/chef_client.rb.tmpl |
1153 | +++ b/templates/chef_client.rb.tmpl |
1154 | @@ -1,6 +1,6 @@ |
1155 | ## template:jinja |
1156 | {# |
1157 | -This file is only utilized if the module 'cc_chef' is enabled in |
1158 | +This file is only utilized if the module 'cc_chef' is enabled in |
1159 | cloud-config. Specifically, in order to enable it |
1160 | you need to add the following to config: |
1161 | chef: |
1162 | @@ -56,3 +56,6 @@ pid_file "{{pid_file}}" |
1163 | {% if show_time %} |
1164 | Chef::Log::Formatter.show_time = true |
1165 | {% endif %} |
1166 | +{% if encrypted_data_bag_secret %} |
1167 | +encrypted_data_bag_secret "{{encrypted_data_bag_secret}}" |
1168 | +{% endif %} |
1169 | diff --git a/tests/cloud_tests/verify.py b/tests/cloud_tests/verify.py |
1170 | index 9911ecf..7018f4d 100644 |
1171 | --- a/tests/cloud_tests/verify.py |
1172 | +++ b/tests/cloud_tests/verify.py |
1173 | @@ -61,12 +61,17 @@ def format_test_failures(test_result): |
1174 | if not test_result['failures']: |
1175 | return '' |
1176 | failure_hdr = ' test failures:' |
1177 | - failure_fmt = ' * {module}.{class}.{function}\n {error}' |
1178 | + failure_fmt = ' * {module}.{class}.{function}\n ' |
1179 | output = [] |
1180 | for failure in test_result['failures']: |
1181 | if not output: |
1182 | output = [failure_hdr] |
1183 | - output.append(failure_fmt.format(**failure)) |
1184 | + msg = failure_fmt.format(**failure) |
1185 | + if failure.get('error'): |
1186 | + msg += failure['error'] |
1187 | + else: |
1188 | + msg += failure.get('traceback', '') |
1189 | + output.append(msg) |
1190 | return '\n'.join(output) |
1191 | |
1192 | |
1193 | diff --git a/tests/data/azure/parse_certificates_fingerprints b/tests/data/azure/parse_certificates_fingerprints |
1194 | new file mode 100644 |
1195 | index 0000000..f7293c5 |
1196 | --- /dev/null |
1197 | +++ b/tests/data/azure/parse_certificates_fingerprints |
1198 | @@ -0,0 +1,4 @@ |
1199 | +ECEDEB3B8488D31AF3BC4CCED493F64B7D27D7B1 |
1200 | +073E19D14D1C799224C6A0FD8DDAB6A8BF27D473 |
1201 | +4C16E7FAD6297D74A9B25EB8F0A12808CEBE293E |
1202 | +929130695289B450FE45DCD5F6EF0CDE69865867 |
1203 | diff --git a/tests/data/azure/parse_certificates_pem b/tests/data/azure/parse_certificates_pem |
1204 | new file mode 100644 |
1205 | index 0000000..3521ea3 |
1206 | --- /dev/null |
1207 | +++ b/tests/data/azure/parse_certificates_pem |
1208 | @@ -0,0 +1,152 @@ |
1209 | +Bag Attributes |
1210 | + localKeyID: 01 00 00 00 |
1211 | + Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0 |
1212 | +Key Attributes |
1213 | + X509v3 Key Usage: 10 |
1214 | +-----BEGIN PRIVATE KEY----- |
1215 | +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDlEe5fUqwdrQTP |
1216 | +W2oVlGK2f31q/8ULT8KmOTyUvL0RPdJQ69vvHOc5Q2CKg2eviHC2LWhF8WmpnZj6 |
1217 | +61RL0GeFGizwvU8Moebw5p3oqdcgoGpHVtxf+mr4QcWF58/Fwez0dA4hcsimVNBz |
1218 | +eNpBBUIKNBMTBG+4d6hcQBUAGKUdGRcCGEyTqXLU0MgHjxC9JgVqWJl+X2LcAGj5 |
1219 | +7J+tGYGTLzKJmeCeGVNN5ZtJ0T85MYHCKQk1/FElK+Kq5akovXffQHjlnCPcx0NJ |
1220 | +47NBjlPaFp2gjnAChn79bT4iCjOFZ9avWpqRpeU517UCnY7djOr3fuod/MSQyh3L |
1221 | +Wuem1tWBAgMBAAECggEBAM4ZXQRs6Kjmo95BHGiAEnSqrlgX+dycjcBq3QPh8KZT |
1222 | +nifqnf48XhnackENy7tWIjr3DctoUq4mOp8AHt77ijhqfaa4XSg7fwKeK9NLBGC5 |
1223 | +lAXNtAey0o2894/sKrd+LMkgphoYIUnuI4LRaGV56potkj/ZDP/GwTcG/R4SDnTn |
1224 | +C1Nb05PNTAPQtPZrgPo7TdM6gGsTnFbVrYHQLyg2Sq/osHfF15YohB01esRLCAwb |
1225 | +EF8JkRC4hWIZoV7BsyQ39232zAJQGGla7+wKFs3kObwh3VnFkQpT94KZnNiZuEfG |
1226 | +x5pW4Pn3gXgNsftscXsaNe/M9mYZqo//Qw7NvUIvAvECgYEA9AVveyK0HOA06fhh |
1227 | ++3hUWdvw7Pbrl+e06jO9+bT1RjQMbHKyI60DZyVGuAySN86iChJRoJr5c6xj+iXU |
1228 | +cR6BVJDjGH5t1tyiK2aYf6hEpK9/j8Z54UiVQ486zPP0PGfT2TO4lBLK+8AUmoaH |
1229 | +gk21ul8QeVCeCJa/o+xEoRFvzcUCgYEA8FCbbvInrUtNY+9eKaUYoNodsgBVjm5X |
1230 | +I0YPUL9D4d+1nvupHSV2NVmQl0w1RaJwrNTafrl5LkqjhQbmuWNta6QgfZzSA3LB |
1231 | +lWXo1Mm0azKdcD3qMGbvn0Q3zU+yGNEgmB/Yju3/NtgYRG6tc+FCWRbPbiCnZWT8 |
1232 | +v3C2Y0XggI0CgYEA2/jCZBgGkTkzue5kNVJlh5OS/aog+pCvL6hxCtarfBuTT3ed |
1233 | +Sje+p46cz3DVpmUpATc+Si8py7KNdYQAm/BJ2be6X+woi9Xcgo87zWgcaPCjZzId |
1234 | +0I2jsIE/Gl6XvpRCDrxnGWRPgt3GNP4szbPLrDPiH9oie8+Y9eYYf7G+PZkCgYEA |
1235 | +nRSzZOPYV4f/QDF4pVQLMykfe/iH9B/fyWjEHg3He19VQmRReIHCMMEoqBziPXAe |
1236 | +onpHj8oAkeer1wpZyhhZr6CKtFDLXgGm09bXSC/IRMHC81klORovyzU2HHfZfCtG |
1237 | +WOmIDnU2+0xpIGIP8sztJ3qnf97MTJSkOSadsWo9gwkCgYEAh5AQmJQmck88Dff2 |
1238 | +qIfJIX8d+BDw47BFJ89OmMFjGV8TNB+JO+AV4Vkodg4hxKpLqTFZTTUFgoYfy5u1 |
1239 | +1/BhAjpmCDCrzubCFhx+8VEoM2+2+MmnuQoMAm9+/mD/IidwRaARgXgvEmp7sfdt |
1240 | +RyWd+p2lYvFkC/jORQtDMY4uW1o= |
1241 | +-----END PRIVATE KEY----- |
1242 | +Bag Attributes |
1243 | + localKeyID: 02 00 00 00 |
1244 | + Microsoft CSP Name: Microsoft Strong Cryptographic Provider |
1245 | +Key Attributes |
1246 | + X509v3 Key Usage: 10 |
1247 | +-----BEGIN PRIVATE KEY----- |
1248 | +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlQhPrZwVQYFV4 |
1249 | +FBc0H1iTXYaznMpwZvEITKtXWACzTdguUderEVOkXW3HTi5HvC2rMayt0nqo3zcd |
1250 | +x1eGiqdjpZQ/wMrkz9wNEM/nNMsXntEwxk0jCVNKB/jz6vf+BOtrSI01SritAGZW |
1251 | +dpKoTUyztT8C2mA3X6D8g3m4Dd07ltnzxaDqAQIU5jBHh3f/Q14tlPNZWUIiqVTC |
1252 | +gDxgAe7MDmfs9h3CInTBX1XM5J4UsLTL23/padgeSvP5YF5qr1+0c7Tdftxr2lwA |
1253 | +N3rLkisf5EiLAToVyJJlgP/exo2I8DaIKe7DZzD3Y1CrurOpkcMKYu5kM1Htlbua |
1254 | +tDkAa2oDAgMBAAECggEAOvdueS9DyiMlCKAeQb1IQosdQOh0l0ma+FgEABC2CWhd |
1255 | +0LgjQTBRM6cGO+urcq7/jhdWQ1UuUG4tVn71z7itCi/F/Enhxc2C22d2GhFVpWsn |
1256 | +giSXJYpZ/mIjkdVfWNo6FRuRmmHwMys1p0qTOS+8qUJWhSzW75csqJZGgeUrAI61 |
1257 | +LBV5F0SGR7dR2xZfy7PeDs9xpD0QivDt5DpsZWPaPvw4QlhdLgw6/YU1h9vtm6ci |
1258 | +xLjnPRLZ7JMpcQHO8dUDl6FiEI7yQ11BDm253VQAVMddYRPQABn7SpEF8kD/aZVh |
1259 | +2Clvz61Rz80SKjPUthMPLWMCRp7zB0xDMzt3/1i+tQKBgQD6Ar1/oD3eFnRnpi4u |
1260 | +n/hdHJtMuXWNfUA4dspNjP6WGOid9sgIeUUdif1XyVJ+afITzvgpWc7nUWIqG2bQ |
1261 | +WxJ/4q2rjUdvjNXTy1voVungR2jD5WLQ9DKeaTR0yCliWlx4JgdPG7qGI5MMwsr+ |
1262 | +R/PUoUUhGeEX+o/sCSieO3iUrQKBgQDqwBEMvIdhAv/CK2sG3fsKYX8rFT55ZNX3 |
1263 | +Tix9DbUGY3wQColNuI8U1nDlxE9U6VOfT9RPqKelBLCgbzB23kdEJnjSlnqlTxrx |
1264 | +E+Hkndyf2ckdJAR3XNxoQ6SRLJNBsgoBj/z5tlfZE9/Jc+uh0mYy3e6g6XCVPBcz |
1265 | +MgoIc+ofbwKBgQCGQhZ1hR30N+bHCozeaPW9OvGDIE0qcEqeh9xYDRFilXnF6pK9 |
1266 | +SjJ9jG7KR8jPLiHb1VebDSl5O1EV/6UU2vNyTc6pw7LLCryBgkGW4aWy1WZDXNnW |
1267 | +EG1meGS9GghvUss5kmJ2bxOZmV0Mi0brisQ8OWagQf+JGvtS7BAt+Q3l+QKBgAb9 |
1268 | +8YQPmXiqPjPqVyW9Ntz4SnFeEJ5NApJ7IZgX8GxgSjGwHqbR+HEGchZl4ncE/Bii |
1269 | +qBA3Vcb0fM5KgYcI19aPzsl28fA6ivLjRLcqfIfGVNcpW3iyq13vpdctHLW4N9QU |
1270 | +FdTaOYOds+ysJziKq8CYG6NvUIshXw+HTgUybqbBAoGBAIIOqcmmtgOClAwipA17 |
1271 | +dAHsI9Sjk+J0+d4JU6o+5TsmhUfUKIjXf5+xqJkJcQZMEe5GhxcCuYkgFicvh4Hz |
1272 | +kv2H/EU35LcJTqC6KTKZOWIbGcn1cqsvwm3GQJffYDiO8fRZSwCaif2J3F2lfH4Y |
1273 | +R/fA67HXFSTT+OncdRpY1NOn |
1274 | +-----END PRIVATE KEY----- |
1275 | +Bag Attributes: <Empty Attributes> |
1276 | +subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US |
1277 | +issuer=/CN=Root Agency |
1278 | +-----BEGIN CERTIFICATE----- |
1279 | +MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290 |
1280 | +IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV |
1281 | +BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv |
1282 | +cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE |
1283 | +BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIlPjJXzrRih4C |
1284 | +k/XsoI01oqo7IUxH3dA2F7vHGXQoIpKCp8Qe6Z6cFfdD8Uj+s+B1BX6hngwzIwjN |
1285 | +jE/23X3SALVzJVWzX4Y/IEjbgsuao6sOyNyB18wIU9YzZkVGj68fmMlUw3LnhPbe |
1286 | +eWkufZaJCaLyhQOwlRMbOcn48D6Ys8fccOyXNzpq3rH1OzeQpxS2M8zaJYP4/VZ/ |
1287 | +sf6KRpI7bP+QwyFvNKfhcaO9/gj4kMo9lVGjvDU20FW6g8UVNJCV9N4GO6mOcyqo |
1288 | +OhuhVfjCNGgW7N1qi0TIVn0/MQM4l4dcT2R7Z/bV9fhMJLjGsy5A4TLAdRrhKUHT |
1289 | +bzi9HyDvAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA= |
1290 | +-----END CERTIFICATE----- |
1291 | +Bag Attributes |
1292 | + localKeyID: 01 00 00 00 |
1293 | +subject=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com |
1294 | +issuer=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com |
1295 | +-----BEGIN CERTIFICATE----- |
1296 | +MIID7TCCAtWgAwIBAgIJALQS3yMg3R41MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD |
1297 | +VQQGEwJVUzETMBEGA1UECAwKV0FTSElOR1RPTjEQMA4GA1UEBwwHU2VhdHRsZTES |
1298 | +MBAGA1UECgwJTWljcm9zb2Z0MQ4wDAYDVQQLDAVBenVyZTEOMAwGA1UEAwwFQW5o |
1299 | +Vm8xIjAgBgkqhkiG9w0BCQEWE2FuaHZvQG1pY3Jvc29mdC5jb20wHhcNMTkwMjE0 |
1300 | +MjMxMjQwWhcNMjExMTEwMjMxMjQwWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgM |
1301 | +CldBU0hJTkdUT04xEDAOBgNVBAcMB1NlYXR0bGUxEjAQBgNVBAoMCU1pY3Jvc29m |
1302 | +dDEOMAwGA1UECwwFQXp1cmUxDjAMBgNVBAMMBUFuaFZvMSIwIAYJKoZIhvcNAQkB |
1303 | +FhNhbmh2b0BtaWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB |
1304 | +CgKCAQEA5RHuX1KsHa0Ez1tqFZRitn99av/FC0/Cpjk8lLy9ET3SUOvb7xznOUNg |
1305 | +ioNnr4hwti1oRfFpqZ2Y+utUS9BnhRos8L1PDKHm8Oad6KnXIKBqR1bcX/pq+EHF |
1306 | +hefPxcHs9HQOIXLIplTQc3jaQQVCCjQTEwRvuHeoXEAVABilHRkXAhhMk6ly1NDI |
1307 | +B48QvSYFaliZfl9i3ABo+eyfrRmBky8yiZngnhlTTeWbSdE/OTGBwikJNfxRJSvi |
1308 | +quWpKL1330B45Zwj3MdDSeOzQY5T2hadoI5wAoZ+/W0+IgozhWfWr1qakaXlOde1 |
1309 | +Ap2O3Yzq937qHfzEkMody1rnptbVgQIDAQABo1AwTjAdBgNVHQ4EFgQUPvdgLiv3 |
1310 | +pAk4r0QTPZU3PFOZJvgwHwYDVR0jBBgwFoAUPvdgLiv3pAk4r0QTPZU3PFOZJvgw |
1311 | +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVUHZT+h9+uCPLTEl5IDg |
1312 | +kqd9WpzXA7PJd/V+7DeDDTkEd06FIKTWZLfxLVVDjQJnQqubQb//e0zGu1qKbXnX |
1313 | +R7xqWabGU4eyPeUFWddmt1OHhxKLU3HbJNJJdL6XKiQtpGGUQt/mqNQ/DEr6hhNF |
1314 | +im5I79iA8H/dXA2gyZrj5Rxea4mtsaYO0mfp1NrFtJpAh2Djy4B1lBXBIv4DWG9e |
1315 | +mMEwzcLCOZj2cOMA6+mdLMUjYCvIRtnn5MKUHyZX5EmX79wsqMTvVpddlVLB9Kgz |
1316 | +Qnvft9+SBWh9+F3ip7BsL6Q4Q9v8eHRbnP0ya7ddlgh64uwf9VOfZZdKCnwqudJP |
1317 | +3g== |
1318 | +-----END CERTIFICATE----- |
1319 | +Bag Attributes |
1320 | + localKeyID: 02 00 00 00 |
1321 | +subject=/CN=/subscriptions/redacted/resourcegroups/redacted/providers/Microsoft.Compute/virtualMachines/redacted |
1322 | +issuer=/CN=Microsoft.ManagedIdentity |
1323 | +-----BEGIN CERTIFICATE----- |
1324 | +MIIDnTCCAoWgAwIBAgIUB2lauSRccvFkoJybUfIwOUqBN7MwDQYJKoZIhvcNAQEL |
1325 | +BQAwJDEiMCAGA1UEAxMZTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eTAeFw0xOTAy |
1326 | +MTUxOTA5MDBaFw0xOTA4MTQxOTA5MDBaMIGUMYGRMIGOBgNVBAMTgYYvc3Vic2Ny |
1327 | +aXB0aW9ucy8yN2I3NTBjZC1lZDQzLTQyZmQtOTA0NC04ZDc1ZTEyNGFlNTUvcmVz |
1328 | +b3VyY2Vncm91cHMvYW5oZXh0cmFzc2gvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21w |
1329 | +dXRlL3ZpcnR1YWxNYWNoaW5lcy9hbmh0ZXN0Y2VydDCCASIwDQYJKoZIhvcNAQEB |
1330 | +BQADggEPADCCAQoCggEBAOVCE+tnBVBgVXgUFzQfWJNdhrOcynBm8QhMq1dYALNN |
1331 | +2C5R16sRU6RdbcdOLke8LasxrK3SeqjfNx3HV4aKp2OllD/AyuTP3A0Qz+c0yxee |
1332 | +0TDGTSMJU0oH+PPq9/4E62tIjTVKuK0AZlZ2kqhNTLO1PwLaYDdfoPyDebgN3TuW |
1333 | +2fPFoOoBAhTmMEeHd/9DXi2U81lZQiKpVMKAPGAB7swOZ+z2HcIidMFfVczknhSw |
1334 | +tMvbf+lp2B5K8/lgXmqvX7RztN1+3GvaXAA3esuSKx/kSIsBOhXIkmWA/97GjYjw |
1335 | +Nogp7sNnMPdjUKu6s6mRwwpi7mQzUe2Vu5q0OQBragMCAwEAAaNWMFQwDgYDVR0P |
1336 | +AQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYD |
1337 | +VR0jBBgwFoAUOJvzEsriQWdJBndPrK+Me1bCPjYwDQYJKoZIhvcNAQELBQADggEB |
1338 | +AFGP/g8o7Hv/to11M0UqfzJuW/AyH9RZtSRcNQFLZUndwweQ6fap8lFsA4REUdqe |
1339 | +7Quqp5JNNY1XzKLWXMPoheIDH1A8FFXdsAroArzlNs9tO3TlIHE8A7HxEVZEmR4b |
1340 | +7ZiixmkQPS2RkjEoV/GM6fheBrzuFn7X5kVZyE6cC5sfcebn8xhk3ZcXI0VmpdT0 |
1341 | +jFBsf5IvFCIXXLLhJI4KXc8VMoKFU1jT9na/jyaoGmfwovKj4ib8s2aiXGAp7Y38 |
1342 | +UCmY+bJapWom6Piy5Jzi/p/kzMVdJcSa+GqpuFxBoQYEVs2XYVl7cGu/wPM+NToC |
1343 | +pkSoWwF1QAnHn0eokR9E1rU= |
1344 | +-----END CERTIFICATE----- |
1345 | +Bag Attributes: <Empty Attributes> |
1346 | +subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US |
1347 | +issuer=/CN=Root Agency |
1348 | +-----BEGIN CERTIFICATE----- |
1349 | +MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290 |
1350 | +IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV |
1351 | +BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv |
1352 | +cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE |
1353 | +BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb |
1354 | +Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi |
1355 | +nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW |
1356 | +vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+ |
1357 | +lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y |
1358 | +WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7 |
1359 | +t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA= |
1360 | +-----END CERTIFICATE----- |
1361 | diff --git a/tests/data/azure/pubkey_extract_cert b/tests/data/azure/pubkey_extract_cert |
1362 | new file mode 100644 |
1363 | index 0000000..ce9b852 |
1364 | --- /dev/null |
1365 | +++ b/tests/data/azure/pubkey_extract_cert |
1366 | @@ -0,0 +1,13 @@ |
1367 | +-----BEGIN CERTIFICATE----- |
1368 | +MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290 |
1369 | +IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV |
1370 | +BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv |
1371 | +cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE |
1372 | +BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb |
1373 | +Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi |
1374 | +nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW |
1375 | +vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+ |
1376 | +lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y |
1377 | +WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7 |
1378 | +t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA= |
1379 | +-----END CERTIFICATE----- |
1380 | diff --git a/tests/data/azure/pubkey_extract_ssh_key b/tests/data/azure/pubkey_extract_ssh_key |
1381 | new file mode 100644 |
1382 | index 0000000..54d749e |
1383 | --- /dev/null |
1384 | +++ b/tests/data/azure/pubkey_extract_ssh_key |
1385 | @@ -0,0 +1 @@ |
1386 | +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHU9IDclbKVYVbYuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoinlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmWvwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4yWzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7t5btUyvp |
1387 | diff --git a/tests/data/netinfo/freebsd-ifconfig-output b/tests/data/netinfo/freebsd-ifconfig-output |
1388 | new file mode 100644 |
1389 | index 0000000..3de15a5 |
1390 | --- /dev/null |
1391 | +++ b/tests/data/netinfo/freebsd-ifconfig-output |
1392 | @@ -0,0 +1,17 @@ |
1393 | +vtnet0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500 |
1394 | + options=6c07bb<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,JUMBO_MTU,VLAN_HWCSUM,TSO4,TSO6,LRO,VLAN_HWTSO,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6> |
1395 | + ether fa:16:3e:14:1f:99 |
1396 | + hwaddr fa:16:3e:14:1f:99 |
1397 | + inet 10.1.80.61 netmask 0xfffff000 broadcast 10.1.95.255 |
1398 | + nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL> |
1399 | + media: Ethernet 10Gbase-T <full-duplex> |
1400 | + status: active |
1401 | +pflog0: flags=0<> metric 0 mtu 33160 |
1402 | +pfsync0: flags=0<> metric 0 mtu 1500 |
1403 | + syncpeer: 0.0.0.0 maxupd: 128 defer: off |
1404 | +lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384 |
1405 | + options=600003<RXCSUM,TXCSUM,RXCSUM_IPV6,TXCSUM_IPV6> |
1406 | + inet6 ::1 prefixlen 128 |
1407 | + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x4 |
1408 | + inet 127.0.0.1 netmask 0xff000000 |
1409 | + nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL> |
1410 | diff --git a/tests/data/netinfo/freebsd-netdev-formatted-output b/tests/data/netinfo/freebsd-netdev-formatted-output |
1411 | new file mode 100644 |
1412 | index 0000000..a9d2ac1 |
1413 | --- /dev/null |
1414 | +++ b/tests/data/netinfo/freebsd-netdev-formatted-output |
1415 | @@ -0,0 +1,11 @@ |
1416 | ++++++++++++++++++++++++++++++++Net device info+++++++++++++++++++++++++++++++ |
1417 | ++---------+-------+----------------+------------+-------+-------------------+ |
1418 | +| Device | Up | Address | Mask | Scope | Hw-Address | |
1419 | ++---------+-------+----------------+------------+-------+-------------------+ |
1420 | +| lo0 | True | 127.0.0.1 | 0xff000000 | . | . | |
1421 | +| lo0 | True | ::1/128 | . | . | . | |
1422 | +| lo0 | True | fe80::1%lo0/64 | . | 0x4 | . | |
1423 | +| pflog0 | False | . | . | . | . | |
1424 | +| pfsync0 | False | . | . | . | . | |
1425 | +| vtnet0 | True | 10.1.80.61 | 0xfffff000 | . | fa:16:3e:14:1f:99 | |
1426 | ++---------+-------+----------------+------------+-------+-------------------+ |
1427 | diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py |
1428 | index 417d86a..6b05b8f 100644 |
1429 | --- a/tests/unittests/test_datasource/test_azure.py |
1430 | +++ b/tests/unittests/test_datasource/test_azure.py |
1431 | @@ -11,7 +11,7 @@ from cloudinit.util import (b64e, decode_binary, load_file, write_file, |
1432 | from cloudinit.version import version_string as vs |
1433 | from cloudinit.tests.helpers import ( |
1434 | HttprettyTestCase, CiTestCase, populate_dir, mock, wrap_and_call, |
1435 | - ExitStack, PY26, SkipTest) |
1436 | + ExitStack) |
1437 | |
1438 | import crypt |
1439 | import httpretty |
1440 | @@ -221,8 +221,6 @@ class TestAzureDataSource(CiTestCase): |
1441 | |
1442 | def setUp(self): |
1443 | super(TestAzureDataSource, self).setUp() |
1444 | - if PY26: |
1445 | - raise SkipTest("Does not work on python 2.6") |
1446 | self.tmp = self.tmp_dir() |
1447 | |
1448 | # patch cloud_dir, so our 'seed_dir' is guaranteed empty |
1449 | @@ -1692,6 +1690,7 @@ class TestPreprovisioningPollIMDS(CiTestCase): |
1450 | self.paths = helpers.Paths({'cloud_dir': self.tmp}) |
1451 | dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d |
1452 | |
1453 | + @mock.patch('time.sleep', mock.MagicMock()) |
1454 | @mock.patch(MOCKPATH + 'EphemeralDHCPv4') |
1455 | def test_poll_imds_re_dhcp_on_timeout(self, m_dhcpv4, report_ready_func, |
1456 | fake_resp, m_media_switch, m_dhcp, |
1457 | diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py |
1458 | index 26b2b93..0255616 100644 |
1459 | --- a/tests/unittests/test_datasource/test_azure_helper.py |
1460 | +++ b/tests/unittests/test_datasource/test_azure_helper.py |
1461 | @@ -1,11 +1,13 @@ |
1462 | # This file is part of cloud-init. See LICENSE file for license information. |
1463 | |
1464 | import os |
1465 | +import unittest2 |
1466 | from textwrap import dedent |
1467 | |
1468 | from cloudinit.sources.helpers import azure as azure_helper |
1469 | from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir |
1470 | |
1471 | +from cloudinit.util import load_file |
1472 | from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim |
1473 | |
1474 | GOAL_STATE_TEMPLATE = """\ |
1475 | @@ -289,6 +291,50 @@ class TestOpenSSLManager(CiTestCase): |
1476 | self.assertEqual([mock.call(manager.tmpdir)], del_dir.call_args_list) |
1477 | |
1478 | |
1479 | +class TestOpenSSLManagerActions(CiTestCase): |
1480 | + |
1481 | + def setUp(self): |
1482 | + super(TestOpenSSLManagerActions, self).setUp() |
1483 | + |
1484 | + self.allowed_subp = True |
1485 | + |
1486 | + def _data_file(self, name): |
1487 | + path = 'tests/data/azure' |
1488 | + return os.path.join(path, name) |
1489 | + |
1490 | + @unittest2.skip("todo move to cloud_test") |
1491 | + def test_pubkey_extract(self): |
1492 | + cert = load_file(self._data_file('pubkey_extract_cert')) |
1493 | + good_key = load_file(self._data_file('pubkey_extract_ssh_key')) |
1494 | + sslmgr = azure_helper.OpenSSLManager() |
1495 | + key = sslmgr._get_ssh_key_from_cert(cert) |
1496 | + self.assertEqual(good_key, key) |
1497 | + |
1498 | + good_fingerprint = '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473' |
1499 | + fingerprint = sslmgr._get_fingerprint_from_cert(cert) |
1500 | + self.assertEqual(good_fingerprint, fingerprint) |
1501 | + |
1502 | + @unittest2.skip("todo move to cloud_test") |
1503 | + @mock.patch.object(azure_helper.OpenSSLManager, '_decrypt_certs_from_xml') |
1504 | + def test_parse_certificates(self, mock_decrypt_certs): |
1505 | + """Azure control plane puts private keys as well as certificates |
1506 | + into the Certificates XML object. Make sure only the public keys |
1507 | + from certs are extracted and that fingerprints are converted to |
1508 | + the form specified in the ovf-env.xml file. |
1509 | + """ |
1510 | + cert_contents = load_file(self._data_file('parse_certificates_pem')) |
1511 | + fingerprints = load_file(self._data_file( |
1512 | + 'parse_certificates_fingerprints') |
1513 | + ).splitlines() |
1514 | + mock_decrypt_certs.return_value = cert_contents |
1515 | + sslmgr = azure_helper.OpenSSLManager() |
1516 | + keys_by_fp = sslmgr.parse_certificates('') |
1517 | + for fp in keys_by_fp.keys(): |
1518 | + self.assertIn(fp, fingerprints) |
1519 | + for fp in fingerprints: |
1520 | + self.assertIn(fp, keys_by_fp) |
1521 | + |
1522 | + |
1523 | class TestWALinuxAgentShim(CiTestCase): |
1524 | |
1525 | def setUp(self): |
1526 | @@ -329,18 +375,31 @@ class TestWALinuxAgentShim(CiTestCase): |
1527 | |
1528 | def test_certificates_used_to_determine_public_keys(self): |
1529 | shim = wa_shim() |
1530 | - data = shim.register_with_azure_and_fetch_data() |
1531 | + """if register_with_azure_and_fetch_data() isn't passed some info about |
1532 | + the user's public keys, there's no point in even trying to parse |
1533 | + the certificates |
1534 | + """ |
1535 | + mypk = [{'fingerprint': 'fp1', 'path': 'path1'}, |
1536 | + {'fingerprint': 'fp3', 'path': 'path3', 'value': ''}] |
1537 | + certs = {'fp1': 'expected-key', |
1538 | + 'fp2': 'should-not-be-found', |
1539 | + 'fp3': 'expected-no-value-key', |
1540 | + } |
1541 | + sslmgr = self.OpenSSLManager.return_value |
1542 | + sslmgr.parse_certificates.return_value = certs |
1543 | + data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk) |
1544 | self.assertEqual( |
1545 | [mock.call(self.GoalState.return_value.certificates_xml)], |
1546 | - self.OpenSSLManager.return_value.parse_certificates.call_args_list) |
1547 | - self.assertEqual( |
1548 | - self.OpenSSLManager.return_value.parse_certificates.return_value, |
1549 | - data['public-keys']) |
1550 | + sslmgr.parse_certificates.call_args_list) |
1551 | + self.assertIn('expected-key', data['public-keys']) |
1552 | + self.assertIn('expected-no-value-key', data['public-keys']) |
1553 | + self.assertNotIn('should-not-be-found', data['public-keys']) |
1554 | |
1555 | def test_absent_certificates_produces_empty_public_keys(self): |
1556 | + mypk = [{'fingerprint': 'fp1', 'path': 'path1'}] |
1557 | self.GoalState.return_value.certificates_xml = None |
1558 | shim = wa_shim() |
1559 | - data = shim.register_with_azure_and_fetch_data() |
1560 | + data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk) |
1561 | self.assertEqual([], data['public-keys']) |
1562 | |
1563 | def test_correct_url_used_for_report_ready(self): |
1564 | diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py |
1565 | index dcdabea..520c50f 100644 |
1566 | --- a/tests/unittests/test_datasource/test_configdrive.py |
1567 | +++ b/tests/unittests/test_datasource/test_configdrive.py |
1568 | @@ -268,8 +268,7 @@ class TestConfigDriveDataSource(CiTestCase): |
1569 | exists_mock = mocks.enter_context( |
1570 | mock.patch.object(os.path, 'exists', |
1571 | side_effect=exists_side_effect())) |
1572 | - device = cfg_ds.device_name_to_device(name) |
1573 | - self.assertEqual(dev_name, device) |
1574 | + self.assertEqual(dev_name, cfg_ds.device_name_to_device(name)) |
1575 | |
1576 | find_mock.assert_called_once_with(mock.ANY) |
1577 | self.assertEqual(exists_mock.call_count, 2) |
1578 | @@ -296,8 +295,7 @@ class TestConfigDriveDataSource(CiTestCase): |
1579 | exists_mock = mocks.enter_context( |
1580 | mock.patch.object(os.path, 'exists', |
1581 | return_value=True)) |
1582 | - device = cfg_ds.device_name_to_device(name) |
1583 | - self.assertEqual(dev_name, device) |
1584 | + self.assertEqual(dev_name, cfg_ds.device_name_to_device(name)) |
1585 | |
1586 | find_mock.assert_called_once_with(mock.ANY) |
1587 | exists_mock.assert_called_once_with(mock.ANY) |
1588 | @@ -331,8 +329,7 @@ class TestConfigDriveDataSource(CiTestCase): |
1589 | yield True |
1590 | with mock.patch.object(os.path, 'exists', |
1591 | side_effect=exists_side_effect()): |
1592 | - device = cfg_ds.device_name_to_device(name) |
1593 | - self.assertEqual(dev_name, device) |
1594 | + self.assertEqual(dev_name, cfg_ds.device_name_to_device(name)) |
1595 | # We don't assert the call count for os.path.exists() because |
1596 | # not all of the entries in name_tests results in two calls to |
1597 | # that function. Specifically, 'root2k' doesn't seem to call |
1598 | @@ -359,8 +356,7 @@ class TestConfigDriveDataSource(CiTestCase): |
1599 | } |
1600 | for name, dev_name in name_tests.items(): |
1601 | with mock.patch.object(os.path, 'exists', return_value=True): |
1602 | - device = cfg_ds.device_name_to_device(name) |
1603 | - self.assertEqual(dev_name, device) |
1604 | + self.assertEqual(dev_name, cfg_ds.device_name_to_device(name)) |
1605 | |
1606 | def test_dir_valid(self): |
1607 | """Verify a dir is read as such.""" |
1608 | @@ -604,6 +600,9 @@ class TestNetJson(CiTestCase): |
1609 | |
1610 | |
1611 | class TestConvertNetworkData(CiTestCase): |
1612 | + |
1613 | + with_logs = True |
1614 | + |
1615 | def setUp(self): |
1616 | super(TestConvertNetworkData, self).setUp() |
1617 | self.tmp = self.tmp_dir() |
1618 | @@ -730,6 +729,26 @@ class TestConvertNetworkData(CiTestCase): |
1619 | 'enp0s2': 'fa:16:3e:d4:57:ad'} |
1620 | self.assertEqual(expected, config_name2mac) |
1621 | |
1622 | + def test_unknown_device_types_accepted(self): |
1623 | + # If we don't recognise a link, we should treat it as physical for a |
1624 | + # best-effort boot |
1625 | + my_netdata = deepcopy(NETWORK_DATA) |
1626 | + my_netdata['links'][0]['type'] = 'my-special-link-type' |
1627 | + |
1628 | + ncfg = openstack.convert_net_json(my_netdata, known_macs=KNOWN_MACS) |
1629 | + config_name2mac = {} |
1630 | + for n in ncfg['config']: |
1631 | + if n['type'] == 'physical': |
1632 | + config_name2mac[n['name']] = n['mac_address'] |
1633 | + |
1634 | + expected = {'nic0': 'fa:16:3e:05:30:fe', 'enp0s1': 'fa:16:3e:69:b0:58', |
1635 | + 'enp0s2': 'fa:16:3e:d4:57:ad'} |
1636 | + self.assertEqual(expected, config_name2mac) |
1637 | + |
1638 | + # We should, however, warn the user that we don't recognise the type |
1639 | + self.assertIn('Unknown network_data link type (my-special-link-type)', |
1640 | + self.logs.getvalue()) |
1641 | + |
1642 | |
1643 | def cfg_ds_from_dir(base_d, files=None): |
1644 | run = os.path.join(base_d, "run") |
1645 | diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py |
1646 | index 1a5956d..20d59bf 100644 |
1647 | --- a/tests/unittests/test_datasource/test_ec2.py |
1648 | +++ b/tests/unittests/test_datasource/test_ec2.py |
1649 | @@ -401,6 +401,30 @@ class TestEc2(test_helpers.HttprettyTestCase): |
1650 | ds.metadata = DEFAULT_METADATA |
1651 | self.assertEqual('my-identity-id', ds.get_instance_id()) |
1652 | |
1653 | + def test_classic_instance_true(self): |
1654 | + """If no vpc-id in metadata, is_classic_instance must return true.""" |
1655 | + md_copy = copy.deepcopy(DEFAULT_METADATA) |
1656 | + ifaces_md = md_copy.get('network', {}).get('interfaces', {}) |
1657 | + for _mac, mac_data in ifaces_md.get('macs', {}).items(): |
1658 | + if 'vpc-id' in mac_data: |
1659 | + del mac_data['vpc-id'] |
1660 | + |
1661 | + ds = self._setup_ds( |
1662 | + platform_data=self.valid_platform_data, |
1663 | + sys_cfg={'datasource': {'Ec2': {'strict_id': False}}}, |
1664 | + md={'md': md_copy}) |
1665 | + self.assertTrue(ds.get_data()) |
1666 | + self.assertTrue(ds.is_classic_instance()) |
1667 | + |
1668 | + def test_classic_instance_false(self): |
1669 | + """If vpc-id in metadata, is_classic_instance must return false.""" |
1670 | + ds = self._setup_ds( |
1671 | + platform_data=self.valid_platform_data, |
1672 | + sys_cfg={'datasource': {'Ec2': {'strict_id': False}}}, |
1673 | + md={'md': DEFAULT_METADATA}) |
1674 | + self.assertTrue(ds.get_data()) |
1675 | + self.assertFalse(ds.is_classic_instance()) |
1676 | + |
1677 | @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery') |
1678 | def test_valid_platform_with_strict_true(self, m_dhcp): |
1679 | """Valid platform data should return true with strict_id true.""" |
1680 | diff --git a/tests/unittests/test_distros/test_create_users.py b/tests/unittests/test_distros/test_create_users.py |
1681 | index c3f258d..4062495 100644 |
1682 | --- a/tests/unittests/test_distros/test_create_users.py |
1683 | +++ b/tests/unittests/test_distros/test_create_users.py |
1684 | @@ -240,4 +240,32 @@ class TestCreateUser(CiTestCase): |
1685 | [mock.call(set(['auth1']), user), # not disabled |
1686 | mock.call(set(['key1']), 'foouser', options=disable_prefix)]) |
1687 | |
1688 | + @mock.patch("cloudinit.distros.util.which") |
1689 | + def test_lock_with_usermod_if_no_passwd(self, m_which, m_subp, |
1690 | + m_is_snappy): |
1691 | + """Lock uses usermod --lock if no 'passwd' cmd available.""" |
1692 | + m_which.side_effect = lambda m: m in ('usermod',) |
1693 | + self.dist.lock_passwd("bob") |
1694 | + self.assertEqual( |
1695 | + [mock.call(['usermod', '--lock', 'bob'])], |
1696 | + m_subp.call_args_list) |
1697 | + |
1698 | + @mock.patch("cloudinit.distros.util.which") |
1699 | + def test_lock_with_passwd_if_available(self, m_which, m_subp, |
1700 | + m_is_snappy): |
1701 | + """Lock with only passwd will use passwd.""" |
1702 | + m_which.side_effect = lambda m: m in ('passwd',) |
1703 | + self.dist.lock_passwd("bob") |
1704 | + self.assertEqual( |
1705 | + [mock.call(['passwd', '-l', 'bob'])], |
1706 | + m_subp.call_args_list) |
1707 | + |
1708 | + @mock.patch("cloudinit.distros.util.which") |
1709 | + def test_lock_raises_runtime_if_no_commands(self, m_which, m_subp, |
1710 | + m_is_snappy): |
1711 | + """Lock with no commands available raises RuntimeError.""" |
1712 | + m_which.return_value = None |
1713 | + with self.assertRaises(RuntimeError): |
1714 | + self.dist.lock_passwd("bob") |
1715 | + |
1716 | # vi: ts=4 expandtab |
1717 | diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py |
1718 | index e986b59..e453040 100644 |
1719 | --- a/tests/unittests/test_distros/test_netconfig.py |
1720 | +++ b/tests/unittests/test_distros/test_netconfig.py |
1721 | @@ -407,7 +407,7 @@ class TestNetCfgDistroUbuntuNetplan(TestNetCfgDistroBase): |
1722 | self.assertEqual(0o644, get_mode(cfgpath, tmpd)) |
1723 | |
1724 | def netplan_path(self): |
1725 | - return '/etc/netplan/50-cloud-init.yaml' |
1726 | + return '/etc/netplan/50-cloud-init.yaml' |
1727 | |
1728 | def test_apply_network_config_v1_to_netplan_ub(self): |
1729 | expected_cfgs = { |
1730 | diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py |
1731 | index 756b4fb..d00c1b4 100644 |
1732 | --- a/tests/unittests/test_ds_identify.py |
1733 | +++ b/tests/unittests/test_ds_identify.py |
1734 | @@ -441,7 +441,7 @@ class TestDsIdentify(DsIdentifyBase): |
1735 | nova does not identify itself on platforms other than intel. |
1736 | https://bugs.launchpad.net/cloud-init/+bugs?field.tag=dsid-nova""" |
1737 | |
1738 | - data = VALID_CFG['OpenStack'].copy() |
1739 | + data = copy.deepcopy(VALID_CFG['OpenStack']) |
1740 | del data['files'][P_PRODUCT_NAME] |
1741 | data.update({'policy_dmi': POLICY_FOUND_OR_MAYBE, |
1742 | 'policy_no_dmi': POLICY_FOUND_OR_MAYBE}) |
1743 | diff --git a/tests/unittests/test_handler/test_handler_chef.py b/tests/unittests/test_handler/test_handler_chef.py |
1744 | index b16532e..f431126 100644 |
1745 | --- a/tests/unittests/test_handler/test_handler_chef.py |
1746 | +++ b/tests/unittests/test_handler/test_handler_chef.py |
1747 | @@ -145,6 +145,7 @@ class TestChef(FilesystemMockingTestCase): |
1748 | file_backup_path "/var/backups/chef" |
1749 | pid_file "/var/run/chef/client.pid" |
1750 | Chef::Log::Formatter.show_time = true |
1751 | + encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret" |
1752 | """ |
1753 | tpl_file = util.load_file('templates/chef_client.rb.tmpl') |
1754 | self.patchUtils(self.tmp) |
1755 | @@ -157,6 +158,8 @@ class TestChef(FilesystemMockingTestCase): |
1756 | 'validation_name': 'bob', |
1757 | 'validation_key': "/etc/chef/vkey.pem", |
1758 | 'validation_cert': "this is my cert", |
1759 | + 'encrypted_data_bag_secret': |
1760 | + '/etc/chef/encrypted_data_bag_secret' |
1761 | }, |
1762 | } |
1763 | cc_chef.handle('chef', cfg, self.fetch_cloud('ubuntu'), LOG, []) |
1764 | diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py |
1765 | index e041e97..e3b9e02 100644 |
1766 | --- a/tests/unittests/test_net.py |
1767 | +++ b/tests/unittests/test_net.py |
1768 | @@ -19,6 +19,7 @@ import gzip |
1769 | import io |
1770 | import json |
1771 | import os |
1772 | +import re |
1773 | import textwrap |
1774 | import yaml |
1775 | |
1776 | @@ -103,6 +104,326 @@ STATIC_EXPECTED_1 = { |
1777 | 'address': '10.0.0.2'}], |
1778 | } |
1779 | |
1780 | +V1_NAMESERVER_ALIAS = """ |
1781 | +config: |
1782 | +- id: eno1 |
1783 | + mac_address: 08:94:ef:51:ae:e0 |
1784 | + mtu: 1500 |
1785 | + name: eno1 |
1786 | + subnets: |
1787 | + - type: manual |
1788 | + type: physical |
1789 | +- id: eno2 |
1790 | + mac_address: 08:94:ef:51:ae:e1 |
1791 | + mtu: 1500 |
1792 | + name: eno2 |
1793 | + subnets: |
1794 | + - type: manual |
1795 | + type: physical |
1796 | +- id: eno3 |
1797 | + mac_address: 08:94:ef:51:ae:de |
1798 | + mtu: 1500 |
1799 | + name: eno3 |
1800 | + subnets: |
1801 | + - type: manual |
1802 | + type: physical |
1803 | +- bond_interfaces: |
1804 | + - eno1 |
1805 | + - eno3 |
1806 | + id: bondM |
1807 | + mac_address: 08:94:ef:51:ae:e0 |
1808 | + mtu: 1500 |
1809 | + name: bondM |
1810 | + params: |
1811 | + bond-downdelay: 0 |
1812 | + bond-lacp-rate: fast |
1813 | + bond-miimon: 100 |
1814 | + bond-mode: 802.3ad |
1815 | + bond-updelay: 0 |
1816 | + bond-xmit-hash-policy: layer3+4 |
1817 | + subnets: |
1818 | + - address: 10.101.10.47/23 |
1819 | + gateway: 10.101.11.254 |
1820 | + type: static |
1821 | + type: bond |
1822 | +- id: eno4 |
1823 | + mac_address: 08:94:ef:51:ae:df |
1824 | + mtu: 1500 |
1825 | + name: eno4 |
1826 | + subnets: |
1827 | + - type: manual |
1828 | + type: physical |
1829 | +- id: enp0s20f0u1u6 |
1830 | + mac_address: 0a:94:ef:51:a4:b9 |
1831 | + mtu: 1500 |
1832 | + name: enp0s20f0u1u6 |
1833 | + subnets: |
1834 | + - type: manual |
1835 | + type: physical |
1836 | +- id: enp216s0f0 |
1837 | + mac_address: 68:05:ca:81:7c:e8 |
1838 | + mtu: 9000 |
1839 | + name: enp216s0f0 |
1840 | + subnets: |
1841 | + - type: manual |
1842 | + type: physical |
1843 | +- id: enp216s0f1 |
1844 | + mac_address: 68:05:ca:81:7c:e9 |
1845 | + mtu: 9000 |
1846 | + name: enp216s0f1 |
1847 | + subnets: |
1848 | + - type: manual |
1849 | + type: physical |
1850 | +- id: enp47s0f0 |
1851 | + mac_address: 68:05:ca:64:d3:6c |
1852 | + mtu: 9000 |
1853 | + name: enp47s0f0 |
1854 | + subnets: |
1855 | + - type: manual |
1856 | + type: physical |
1857 | +- bond_interfaces: |
1858 | + - enp216s0f0 |
1859 | + - enp47s0f0 |
1860 | + id: bond0 |
1861 | + mac_address: 68:05:ca:64:d3:6c |
1862 | + mtu: 9000 |
1863 | + name: bond0 |
1864 | + params: |
1865 | + bond-downdelay: 0 |
1866 | + bond-lacp-rate: fast |
1867 | + bond-miimon: 100 |
1868 | + bond-mode: 802.3ad |
1869 | + bond-updelay: 0 |
1870 | + bond-xmit-hash-policy: layer3+4 |
1871 | + subnets: |
1872 | + - type: manual |
1873 | + type: bond |
1874 | +- id: bond0.3502 |
1875 | + mtu: 9000 |
1876 | + name: bond0.3502 |
1877 | + subnets: |
1878 | + - address: 172.20.80.4/25 |
1879 | + type: static |
1880 | + type: vlan |
1881 | + vlan_id: 3502 |
1882 | + vlan_link: bond0 |
1883 | +- id: bond0.3503 |
1884 | + mtu: 9000 |
1885 | + name: bond0.3503 |
1886 | + subnets: |
1887 | + - address: 172.20.80.129/25 |
1888 | + type: static |
1889 | + type: vlan |
1890 | + vlan_id: 3503 |
1891 | + vlan_link: bond0 |
1892 | +- id: enp47s0f1 |
1893 | + mac_address: 68:05:ca:64:d3:6d |
1894 | + mtu: 9000 |
1895 | + name: enp47s0f1 |
1896 | + subnets: |
1897 | + - type: manual |
1898 | + type: physical |
1899 | +- bond_interfaces: |
1900 | + - enp216s0f1 |
1901 | + - enp47s0f1 |
1902 | + id: bond1 |
1903 | + mac_address: 68:05:ca:64:d3:6d |
1904 | + mtu: 9000 |
1905 | + name: bond1 |
1906 | + params: |
1907 | + bond-downdelay: 0 |
1908 | + bond-lacp-rate: fast |
1909 | + bond-miimon: 100 |
1910 | + bond-mode: 802.3ad |
1911 | + bond-updelay: 0 |
1912 | + bond-xmit-hash-policy: layer3+4 |
1913 | + subnets: |
1914 | + - address: 10.101.8.65/26 |
1915 | + routes: |
1916 | + - destination: 213.119.192.0/24 |
1917 | + gateway: 10.101.8.126 |
1918 | + metric: 0 |
1919 | + type: static |
1920 | + type: bond |
1921 | +- address: |
1922 | + - 10.101.10.1 |
1923 | + - 10.101.10.2 |
1924 | + - 10.101.10.3 |
1925 | + - 10.101.10.5 |
1926 | + search: |
1927 | + - foo.bar |
1928 | + - maas |
1929 | + type: nameserver |
1930 | +version: 1 |
1931 | +""" |
1932 | + |
1933 | +NETPLAN_NO_ALIAS = """ |
1934 | +network: |
1935 | + version: 2 |
1936 | + ethernets: |
1937 | + eno1: |
1938 | + match: |
1939 | + macaddress: 08:94:ef:51:ae:e0 |
1940 | + mtu: 1500 |
1941 | + set-name: eno1 |
1942 | + eno2: |
1943 | + match: |
1944 | + macaddress: 08:94:ef:51:ae:e1 |
1945 | + mtu: 1500 |
1946 | + set-name: eno2 |
1947 | + eno3: |
1948 | + match: |
1949 | + macaddress: 08:94:ef:51:ae:de |
1950 | + mtu: 1500 |
1951 | + set-name: eno3 |
1952 | + eno4: |
1953 | + match: |
1954 | + macaddress: 08:94:ef:51:ae:df |
1955 | + mtu: 1500 |
1956 | + set-name: eno4 |
1957 | + enp0s20f0u1u6: |
1958 | + match: |
1959 | + macaddress: 0a:94:ef:51:a4:b9 |
1960 | + mtu: 1500 |
1961 | + set-name: enp0s20f0u1u6 |
1962 | + enp216s0f0: |
1963 | + match: |
1964 | + macaddress: 68:05:ca:81:7c:e8 |
1965 | + mtu: 9000 |
1966 | + set-name: enp216s0f0 |
1967 | + enp216s0f1: |
1968 | + match: |
1969 | + macaddress: 68:05:ca:81:7c:e9 |
1970 | + mtu: 9000 |
1971 | + set-name: enp216s0f1 |
1972 | + enp47s0f0: |
1973 | + match: |
1974 | + macaddress: 68:05:ca:64:d3:6c |
1975 | + mtu: 9000 |
1976 | + set-name: enp47s0f0 |
1977 | + enp47s0f1: |
1978 | + match: |
1979 | + macaddress: 68:05:ca:64:d3:6d |
1980 | + mtu: 9000 |
1981 | + set-name: enp47s0f1 |
1982 | + bonds: |
1983 | + bond0: |
1984 | + interfaces: |
1985 | + - enp216s0f0 |
1986 | + - enp47s0f0 |
1987 | + macaddress: 68:05:ca:64:d3:6c |
1988 | + mtu: 9000 |
1989 | + parameters: |
1990 | + down-delay: 0 |
1991 | + lacp-rate: fast |
1992 | + mii-monitor-interval: 100 |
1993 | + mode: 802.3ad |
1994 | + transmit-hash-policy: layer3+4 |
1995 | + up-delay: 0 |
1996 | + bond1: |
1997 | + addresses: |
1998 | + - 10.101.8.65/26 |
1999 | + interfaces: |
2000 | + - enp216s0f1 |
2001 | + - enp47s0f1 |
2002 | + macaddress: 68:05:ca:64:d3:6d |
2003 | + mtu: 9000 |
2004 | + nameservers: |
2005 | + addresses: |
2006 | + - 10.101.10.1 |
2007 | + - 10.101.10.2 |
2008 | + - 10.101.10.3 |
2009 | + - 10.101.10.5 |
2010 | + search: |
2011 | + - foo.bar |
2012 | + - maas |
2013 | + parameters: |
2014 | + down-delay: 0 |
2015 | + lacp-rate: fast |
2016 | + mii-monitor-interval: 100 |
2017 | + mode: 802.3ad |
2018 | + transmit-hash-policy: layer3+4 |
2019 | + up-delay: 0 |
2020 | + routes: |
2021 | + - metric: 0 |
2022 | + to: 213.119.192.0/24 |
2023 | + via: 10.101.8.126 |
2024 | + bondM: |
2025 | + addresses: |
2026 | + - 10.101.10.47/23 |
2027 | + gateway4: 10.101.11.254 |
2028 | + interfaces: |
2029 | + - eno1 |
2030 | + - eno3 |
2031 | + macaddress: 08:94:ef:51:ae:e0 |
2032 | + mtu: 1500 |
2033 | + nameservers: |
2034 | + addresses: |
2035 | + - 10.101.10.1 |
2036 | + - 10.101.10.2 |
2037 | + - 10.101.10.3 |
2038 | + - 10.101.10.5 |
2039 | + search: |
2040 | + - foo.bar |
2041 | + - maas |
2042 | + parameters: |
2043 | + down-delay: 0 |
2044 | + lacp-rate: fast |
2045 | + mii-monitor-interval: 100 |
2046 | + mode: 802.3ad |
2047 | + transmit-hash-policy: layer3+4 |
2048 | + up-delay: 0 |
2049 | + vlans: |
2050 | + bond0.3502: |
2051 | + addresses: |
2052 | + - 172.20.80.4/25 |
2053 | + id: 3502 |
2054 | + link: bond0 |
2055 | + mtu: 9000 |
2056 | + nameservers: |
2057 | + addresses: |
2058 | + - 10.101.10.1 |
2059 | + - 10.101.10.2 |
2060 | + - 10.101.10.3 |
2061 | + - 10.101.10.5 |
2062 | + search: |
2063 | + - foo.bar |
2064 | + - maas |
2065 | + bond0.3503: |
2066 | + addresses: |
2067 | + - 172.20.80.129/25 |
2068 | + id: 3503 |
2069 | + link: bond0 |
2070 | + mtu: 9000 |
2071 | + nameservers: |
2072 | + addresses: |
2073 | + - 10.101.10.1 |
2074 | + - 10.101.10.2 |
2075 | + - 10.101.10.3 |
2076 | + - 10.101.10.5 |
2077 | + search: |
2078 | + - foo.bar |
2079 | + - maas |
2080 | +""" |
2081 | + |
2082 | +NETPLAN_DHCP_FALSE = """ |
2083 | +version: 2 |
2084 | +ethernets: |
2085 | + ens3: |
2086 | + match: |
2087 | + macaddress: 52:54:00:ab:cd:ef |
2088 | + dhcp4: false |
2089 | + dhcp6: false |
2090 | + addresses: |
2091 | + - 192.168.42.100/24 |
2092 | + - 2001:db8::100/32 |
2093 | + gateway4: 192.168.42.1 |
2094 | + gateway6: 2001:db8::1 |
2095 | + nameservers: |
2096 | + search: [example.com] |
2097 | + addresses: [192.168.42.53, 1.1.1.1] |
2098 | +""" |
2099 | + |
2100 | # Examples (and expected outputs for various renderers). |
2101 | OS_SAMPLES = [ |
2102 | { |
2103 | @@ -2286,6 +2607,50 @@ USERCTL=no |
2104 | config = sysconfig.ConfigObj(nm_cfg) |
2105 | self.assertIn('ifcfg-rh', config['main']['plugins']) |
2106 | |
2107 | + def test_netplan_dhcp_false_disable_dhcp_in_state(self): |
2108 | + """netplan config with dhcp[46]: False should not add dhcp in state""" |
2109 | + net_config = yaml.load(NETPLAN_DHCP_FALSE) |
2110 | + ns = network_state.parse_net_config_data(net_config, |
2111 | + skip_broken=False) |
2112 | + |
2113 | + dhcp_found = [snet for iface in ns.iter_interfaces() |
2114 | + for snet in iface['subnets'] if 'dhcp' in snet['type']] |
2115 | + |
2116 | + self.assertEqual([], dhcp_found) |
2117 | + |
2118 | + def test_netplan_dhcp_false_no_dhcp_in_sysconfig(self): |
2119 | + """netplan cfg with dhcp[46]: False should not have bootproto=dhcp""" |
2120 | + |
2121 | + entry = { |
2122 | + 'yaml': NETPLAN_DHCP_FALSE, |
2123 | + 'expected_sysconfig': { |
2124 | + 'ifcfg-ens3': textwrap.dedent("""\ |
2125 | + BOOTPROTO=none |
2126 | + DEFROUTE=yes |
2127 | + DEVICE=ens3 |
2128 | + DNS1=192.168.42.53 |
2129 | + DNS2=1.1.1.1 |
2130 | + DOMAIN=example.com |
2131 | + GATEWAY=192.168.42.1 |
2132 | + HWADDR=52:54:00:ab:cd:ef |
2133 | + IPADDR=192.168.42.100 |
2134 | + IPV6ADDR=2001:db8::100/32 |
2135 | + IPV6INIT=yes |
2136 | + IPV6_DEFAULTGW=2001:db8::1 |
2137 | + NETMASK=255.255.255.0 |
2138 | + NM_CONTROLLED=no |
2139 | + ONBOOT=yes |
2140 | + STARTMODE=auto |
2141 | + TYPE=Ethernet |
2142 | + USERCTL=no |
2143 | + """), |
2144 | + } |
2145 | + } |
2146 | + |
2147 | + found = self._render_and_read(network_config=yaml.load(entry['yaml'])) |
2148 | + self._compare_files_to_expected(entry['expected_sysconfig'], found) |
2149 | + self._assert_headers(found) |
2150 | + |
2151 | |
2152 | class TestOpenSuseSysConfigRendering(CiTestCase): |
2153 | |
2154 | @@ -3065,6 +3430,38 @@ class TestNetplanRoundTrip(CiTestCase): |
2155 | entry['expected_netplan'].splitlines(), |
2156 | files['/etc/netplan/50-cloud-init.yaml'].splitlines()) |
2157 | |
2158 | + def test_render_output_has_yaml_no_aliases(self): |
2159 | + entry = { |
2160 | + 'yaml': V1_NAMESERVER_ALIAS, |
2161 | + 'expected_netplan': NETPLAN_NO_ALIAS, |
2162 | + } |
2163 | + network_config = yaml.load(entry['yaml']) |
2164 | + ns = network_state.parse_net_config_data(network_config) |
2165 | + files = self._render_and_read(state=ns) |
2166 | + # check for alias |
2167 | + content = files['/etc/netplan/50-cloud-init.yaml'] |
2168 | + |
2169 | + # test load the yaml to ensure we don't render something not loadable |
2170 | + # this allows single aliases, but not duplicate ones |
2171 | + parsed = yaml.load(files['/etc/netplan/50-cloud-init.yaml']) |
2172 | + self.assertNotEqual(None, parsed) |
2173 | + |
2174 | + # now look for any alias, avoid rendering them entirely |
2175 | + # generate the first anchor string using the template |
2176 | + # as of this writing, looks like "&id001" |
2177 | + anchor = r'&' + yaml.serializer.Serializer.ANCHOR_TEMPLATE % 1 |
2178 | + found_alias = re.search(anchor, content, re.MULTILINE) |
2179 | + if found_alias: |
2180 | + msg = "Error at: %s\nContent:\n%s" % (found_alias, content) |
2181 | + raise ValueError('Found yaml alias in rendered netplan: ' + msg) |
2182 | + |
2183 | + print(entry['expected_netplan']) |
2184 | + print('-- expected ^ | v rendered --') |
2185 | + print(files['/etc/netplan/50-cloud-init.yaml']) |
2186 | + self.assertEqual( |
2187 | + entry['expected_netplan'].splitlines(), |
2188 | + files['/etc/netplan/50-cloud-init.yaml'].splitlines()) |
2189 | + |
2190 | |
2191 | class TestEniRoundTrip(CiTestCase): |
2192 | |
2193 | diff --git a/tools/cloud-init-per b/tools/cloud-init-per |
2194 | index 7d6754b..eae3e93 100755 |
2195 | --- a/tools/cloud-init-per |
2196 | +++ b/tools/cloud-init-per |
2197 | @@ -38,7 +38,7 @@ fi |
2198 | [ "$1" = "-h" -o "$1" = "--help" ] && { Usage ; exit 0; } |
2199 | [ $# -ge 3 ] || { Usage 1>&2; exit 1; } |
2200 | freq=$1 |
2201 | -name=$2 |
2202 | +name=${2/-/_} |
2203 | shift 2; |
2204 | |
2205 | [ "${name#*/}" = "${name}" ] || fail "name cannot contain a /" |
2206 | @@ -53,6 +53,12 @@ esac |
2207 | [ -d "${sem%/*}" ] || mkdir -p "${sem%/*}" || |
2208 | fail "failed to make directory for ${sem}" |
2209 | |
2210 | +# Rename legacy sem files with dashes in their names. Do not overwrite existing |
2211 | +# sem files to prevent clobbering those which may have been created from calls |
2212 | +# outside of cloud-init. |
2213 | +sem_legacy="${sem/_/-}" |
2214 | +[ "$sem" != "$sem_legacy" -a -e "$sem_legacy" ] && mv -n "$sem_legacy" "$sem" |
2215 | + |
2216 | [ "$freq" != "always" -a -e "$sem" ] && exit 0 |
2217 | "$@" |
2218 | ret=$? |
FAILED: Continuous integration, rev:8a0521081e0 45944233b49eca1 b9fc09cffb033e /code.launchpad .net/~daniel- thewatkins/ cloud-init/ +git/cloud- init/+merge/ 364099/ +edit-commit- message
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
https:/ /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 618/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 618/rebuild
https:/