Merge ~chad.smith/cloud-init:ubuntu/devel into cloud-init:ubuntu/devel
- Git
- lp:~chad.smith/cloud-init
- ubuntu/devel
- Merge into ubuntu/devel
Proposed by
Chad Smith
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 964c869024b422c3966975cc6d23981537084b0a | ||||||||||||
Proposed branch: | ~chad.smith/cloud-init:ubuntu/devel | ||||||||||||
Merge into: | cloud-init:ubuntu/devel | ||||||||||||
Diff against target: |
371 lines (+141/-27) 10 files modified
cloudinit/config/cc_puppet.py (+40/-14) cloudinit/config/cc_salt_minion.py (+9/-0) cloudinit/sources/DataSourceGCE.py (+7/-8) cloudinit/util.py (+7/-2) debian/changelog (+13/-0) doc/examples/cloud-config-chef.txt (+2/-2) tests/cloud_tests/testcases/modules/salt_minion.py (+5/-0) tests/cloud_tests/testcases/modules/salt_minion.yaml (+5/-0) tests/unittests/test_datasource/test_gce.py (+19/-1) tests/unittests/test_util.py (+34/-0) |
||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Scott Moser | Pending | ||
Review via email: mp+340252@code.launchpad.net |
Commit message
Description of the change
Sync tip of master to pull in GCE fix for LP: #1752711 and other changes for Bionic
To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote : | # |
review:
Approve
(continuous-integration)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/cloudinit/config/cc_puppet.py b/cloudinit/config/cc_puppet.py | |||
2 | index 28b1d56..57a170f 100644 | |||
3 | --- a/cloudinit/config/cc_puppet.py | |||
4 | +++ b/cloudinit/config/cc_puppet.py | |||
5 | @@ -21,6 +21,13 @@ under ``version``, and defaults to ``none``, which selects the latest version | |||
6 | 21 | in the repos. If the ``puppet`` config key exists in the config archive, this | 21 | in the repos. If the ``puppet`` config key exists in the config archive, this |
7 | 22 | module will attempt to start puppet even if no installation was performed. | 22 | module will attempt to start puppet even if no installation was performed. |
8 | 23 | 23 | ||
9 | 24 | The module also provides keys for configuring the new puppet 4 paths and | ||
10 | 25 | installing the puppet package from the puppetlabs repositories: | ||
11 | 26 | https://docs.puppet.com/puppet/4.2/reference/whered_it_go.html | ||
12 | 27 | The keys are ``package_name``, ``conf_file`` and ``ssl_dir``. If unset, their | ||
13 | 28 | values will default to ones that work with puppet 3.x and with distributions | ||
14 | 29 | that ship modified puppet 4.x that uses the old paths. | ||
15 | 30 | |||
16 | 24 | Puppet configuration can be specified under the ``conf`` key. The | 31 | Puppet configuration can be specified under the ``conf`` key. The |
17 | 25 | configuration is specified as a dictionary containing high-level ``<section>`` | 32 | configuration is specified as a dictionary containing high-level ``<section>`` |
18 | 26 | keys and lists of ``<key>=<value>`` pairs within each section. Each section | 33 | keys and lists of ``<key>=<value>`` pairs within each section. Each section |
19 | @@ -44,6 +51,9 @@ in pem format as a multi-line string (using the ``|`` yaml notation). | |||
20 | 44 | puppet: | 51 | puppet: |
21 | 45 | install: <true/false> | 52 | install: <true/false> |
22 | 46 | version: <version> | 53 | version: <version> |
23 | 54 | conf_file: '/etc/puppet/puppet.conf' | ||
24 | 55 | ssl_dir: '/var/lib/puppet/ssl' | ||
25 | 56 | package_name: 'puppet' | ||
26 | 47 | conf: | 57 | conf: |
27 | 48 | agent: | 58 | agent: |
28 | 49 | server: "puppetmaster.example.org" | 59 | server: "puppetmaster.example.org" |
29 | @@ -63,9 +73,17 @@ from cloudinit import helpers | |||
30 | 63 | from cloudinit import util | 73 | from cloudinit import util |
31 | 64 | 74 | ||
32 | 65 | PUPPET_CONF_PATH = '/etc/puppet/puppet.conf' | 75 | PUPPET_CONF_PATH = '/etc/puppet/puppet.conf' |
33 | 66 | PUPPET_SSL_CERT_DIR = '/var/lib/puppet/ssl/certs/' | ||
34 | 67 | PUPPET_SSL_DIR = '/var/lib/puppet/ssl' | 76 | PUPPET_SSL_DIR = '/var/lib/puppet/ssl' |
36 | 68 | PUPPET_SSL_CERT_PATH = '/var/lib/puppet/ssl/certs/ca.pem' | 77 | PUPPET_PACKAGE_NAME = 'puppet' |
37 | 78 | |||
38 | 79 | |||
39 | 80 | class PuppetConstants(object): | ||
40 | 81 | |||
41 | 82 | def __init__(self, puppet_conf_file, puppet_ssl_dir, log): | ||
42 | 83 | self.conf_path = puppet_conf_file | ||
43 | 84 | self.ssl_dir = puppet_ssl_dir | ||
44 | 85 | self.ssl_cert_dir = os.path.join(puppet_ssl_dir, "certs") | ||
45 | 86 | self.ssl_cert_path = os.path.join(self.ssl_cert_dir, "ca.pem") | ||
46 | 69 | 87 | ||
47 | 70 | 88 | ||
48 | 71 | def _autostart_puppet(log): | 89 | def _autostart_puppet(log): |
49 | @@ -92,22 +110,29 @@ def handle(name, cfg, cloud, log, _args): | |||
50 | 92 | return | 110 | return |
51 | 93 | 111 | ||
52 | 94 | puppet_cfg = cfg['puppet'] | 112 | puppet_cfg = cfg['puppet'] |
53 | 95 | |||
54 | 96 | # Start by installing the puppet package if necessary... | 113 | # Start by installing the puppet package if necessary... |
55 | 97 | install = util.get_cfg_option_bool(puppet_cfg, 'install', True) | 114 | install = util.get_cfg_option_bool(puppet_cfg, 'install', True) |
56 | 98 | version = util.get_cfg_option_str(puppet_cfg, 'version', None) | 115 | version = util.get_cfg_option_str(puppet_cfg, 'version', None) |
57 | 116 | package_name = util.get_cfg_option_str( | ||
58 | 117 | puppet_cfg, 'package_name', PUPPET_PACKAGE_NAME) | ||
59 | 118 | conf_file = util.get_cfg_option_str( | ||
60 | 119 | puppet_cfg, 'conf_file', PUPPET_CONF_PATH) | ||
61 | 120 | ssl_dir = util.get_cfg_option_str(puppet_cfg, 'ssl_dir', PUPPET_SSL_DIR) | ||
62 | 121 | |||
63 | 122 | p_constants = PuppetConstants(conf_file, ssl_dir, log) | ||
64 | 99 | if not install and version: | 123 | if not install and version: |
65 | 100 | log.warn(("Puppet install set false but version supplied," | 124 | log.warn(("Puppet install set false but version supplied," |
66 | 101 | " doing nothing.")) | 125 | " doing nothing.")) |
67 | 102 | elif install: | 126 | elif install: |
68 | 103 | log.debug(("Attempting to install puppet %s,"), | 127 | log.debug(("Attempting to install puppet %s,"), |
69 | 104 | version if version else 'latest') | 128 | version if version else 'latest') |
71 | 105 | cloud.distro.install_packages(('puppet', version)) | 129 | |
72 | 130 | cloud.distro.install_packages((package_name, version)) | ||
73 | 106 | 131 | ||
74 | 107 | # ... and then update the puppet configuration | 132 | # ... and then update the puppet configuration |
75 | 108 | if 'conf' in puppet_cfg: | 133 | if 'conf' in puppet_cfg: |
76 | 109 | # Add all sections from the conf object to puppet.conf | 134 | # Add all sections from the conf object to puppet.conf |
78 | 110 | contents = util.load_file(PUPPET_CONF_PATH) | 135 | contents = util.load_file(p_constants.conf_path) |
79 | 111 | # Create object for reading puppet.conf values | 136 | # Create object for reading puppet.conf values |
80 | 112 | puppet_config = helpers.DefaultingConfigParser() | 137 | puppet_config = helpers.DefaultingConfigParser() |
81 | 113 | # Read puppet.conf values from original file in order to be able to | 138 | # Read puppet.conf values from original file in order to be able to |
82 | @@ -116,19 +141,19 @@ def handle(name, cfg, cloud, log, _args): | |||
83 | 116 | cleaned_lines = [i.lstrip() for i in contents.splitlines()] | 141 | cleaned_lines = [i.lstrip() for i in contents.splitlines()] |
84 | 117 | cleaned_contents = '\n'.join(cleaned_lines) | 142 | cleaned_contents = '\n'.join(cleaned_lines) |
85 | 118 | puppet_config.readfp(StringIO(cleaned_contents), | 143 | puppet_config.readfp(StringIO(cleaned_contents), |
87 | 119 | filename=PUPPET_CONF_PATH) | 144 | filename=p_constants.conf_path) |
88 | 120 | for (cfg_name, cfg) in puppet_cfg['conf'].items(): | 145 | for (cfg_name, cfg) in puppet_cfg['conf'].items(): |
89 | 121 | # Cert configuration is a special case | 146 | # Cert configuration is a special case |
90 | 122 | # Dump the puppet master ca certificate in the correct place | 147 | # Dump the puppet master ca certificate in the correct place |
91 | 123 | if cfg_name == 'ca_cert': | 148 | if cfg_name == 'ca_cert': |
92 | 124 | # Puppet ssl sub-directory isn't created yet | 149 | # Puppet ssl sub-directory isn't created yet |
93 | 125 | # Create it with the proper permissions and ownership | 150 | # Create it with the proper permissions and ownership |
100 | 126 | util.ensure_dir(PUPPET_SSL_DIR, 0o771) | 151 | util.ensure_dir(p_constants.ssl_dir, 0o771) |
101 | 127 | util.chownbyname(PUPPET_SSL_DIR, 'puppet', 'root') | 152 | util.chownbyname(p_constants.ssl_dir, 'puppet', 'root') |
102 | 128 | util.ensure_dir(PUPPET_SSL_CERT_DIR) | 153 | util.ensure_dir(p_constants.ssl_cert_dir) |
103 | 129 | util.chownbyname(PUPPET_SSL_CERT_DIR, 'puppet', 'root') | 154 | util.chownbyname(p_constants.ssl_cert_dir, 'puppet', 'root') |
104 | 130 | util.write_file(PUPPET_SSL_CERT_PATH, cfg) | 155 | util.write_file(p_constants.ssl_cert_path, cfg) |
105 | 131 | util.chownbyname(PUPPET_SSL_CERT_PATH, 'puppet', 'root') | 156 | util.chownbyname(p_constants.ssl_cert_path, 'puppet', 'root') |
106 | 132 | else: | 157 | else: |
107 | 133 | # Iterate through the config items, we'll use ConfigParser.set | 158 | # Iterate through the config items, we'll use ConfigParser.set |
108 | 134 | # to overwrite or create new items as needed | 159 | # to overwrite or create new items as needed |
109 | @@ -144,8 +169,9 @@ def handle(name, cfg, cloud, log, _args): | |||
110 | 144 | puppet_config.set(cfg_name, o, v) | 169 | puppet_config.set(cfg_name, o, v) |
111 | 145 | # We got all our config as wanted we'll rename | 170 | # We got all our config as wanted we'll rename |
112 | 146 | # the previous puppet.conf and create our new one | 171 | # the previous puppet.conf and create our new one |
115 | 147 | util.rename(PUPPET_CONF_PATH, "%s.old" % (PUPPET_CONF_PATH)) | 172 | util.rename(p_constants.conf_path, "%s.old" |
116 | 148 | util.write_file(PUPPET_CONF_PATH, puppet_config.stringify()) | 173 | % (p_constants.conf_path)) |
117 | 174 | util.write_file(p_constants.conf_path, puppet_config.stringify()) | ||
118 | 149 | 175 | ||
119 | 150 | # Set it up so it autostarts | 176 | # Set it up so it autostarts |
120 | 151 | _autostart_puppet(log) | 177 | _autostart_puppet(log) |
121 | diff --git a/cloudinit/config/cc_salt_minion.py b/cloudinit/config/cc_salt_minion.py | |||
122 | index 2b38837..5112a34 100644 | |||
123 | --- a/cloudinit/config/cc_salt_minion.py | |||
124 | +++ b/cloudinit/config/cc_salt_minion.py | |||
125 | @@ -25,6 +25,9 @@ specified with ``public_key`` and ``private_key`` respectively. | |||
126 | 25 | salt_minion: | 25 | salt_minion: |
127 | 26 | conf: | 26 | conf: |
128 | 27 | master: salt.example.com | 27 | master: salt.example.com |
129 | 28 | grains: | ||
130 | 29 | role: | ||
131 | 30 | - web | ||
132 | 28 | public_key: | | 31 | public_key: | |
133 | 29 | ------BEGIN PUBLIC KEY------- | 32 | ------BEGIN PUBLIC KEY------- |
134 | 30 | <key data> | 33 | <key data> |
135 | @@ -65,6 +68,12 @@ def handle(name, cfg, cloud, log, _args): | |||
136 | 65 | minion_data = util.yaml_dumps(salt_cfg.get('conf')) | 68 | minion_data = util.yaml_dumps(salt_cfg.get('conf')) |
137 | 66 | util.write_file(minion_config, minion_data) | 69 | util.write_file(minion_config, minion_data) |
138 | 67 | 70 | ||
139 | 71 | if 'grains' in salt_cfg: | ||
140 | 72 | # add grains to /etc/salt/grains | ||
141 | 73 | grains_config = os.path.join(config_dir, 'grains') | ||
142 | 74 | grains_data = util.yaml_dumps(salt_cfg.get('grains')) | ||
143 | 75 | util.write_file(grains_config, grains_data) | ||
144 | 76 | |||
145 | 68 | # ... copy the key pair if specified | 77 | # ... copy the key pair if specified |
146 | 69 | if 'public_key' in salt_cfg and 'private_key' in salt_cfg: | 78 | if 'public_key' in salt_cfg and 'private_key' in salt_cfg: |
147 | 70 | if os.path.isdir("/etc/salt/pki/minion"): | 79 | if os.path.isdir("/etc/salt/pki/minion"): |
148 | diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py | |||
149 | index 2da34a9..bebc991 100644 | |||
150 | --- a/cloudinit/sources/DataSourceGCE.py | |||
151 | +++ b/cloudinit/sources/DataSourceGCE.py | |||
152 | @@ -213,16 +213,15 @@ def read_md(address=None, platform_check=True): | |||
153 | 213 | if md['availability-zone']: | 213 | if md['availability-zone']: |
154 | 214 | md['availability-zone'] = md['availability-zone'].split('/')[-1] | 214 | md['availability-zone'] = md['availability-zone'].split('/')[-1] |
155 | 215 | 215 | ||
158 | 216 | encoding = instance_data.get('user-data-encoding') | 216 | if 'user-data' in instance_data: |
159 | 217 | if encoding: | 217 | # instance_data was json, so values are all utf-8 strings. |
160 | 218 | ud = instance_data['user-data'].encode("utf-8") | ||
161 | 219 | encoding = instance_data.get('user-data-encoding') | ||
162 | 218 | if encoding == 'base64': | 220 | if encoding == 'base64': |
165 | 219 | md['user-data'] = b64decode(instance_data.get('user-data')) | 221 | ud = b64decode(ud) |
166 | 220 | else: | 222 | elif encoding: |
167 | 221 | LOG.warning('unknown user-data-encoding: %s, ignoring', encoding) | 223 | LOG.warning('unknown user-data-encoding: %s, ignoring', encoding) |
172 | 222 | 224 | ret['user-data'] = ud | |
169 | 223 | if 'user-data' in md: | ||
170 | 224 | ret['user-data'] = md['user-data'] | ||
171 | 225 | del md['user-data'] | ||
173 | 226 | 225 | ||
174 | 227 | ret['meta-data'] = md | 226 | ret['meta-data'] = md |
175 | 228 | ret['success'] = True | 227 | ret['success'] = True |
176 | diff --git a/cloudinit/util.py b/cloudinit/util.py | |||
177 | index 338fb97..02dc2ce 100644 | |||
178 | --- a/cloudinit/util.py | |||
179 | +++ b/cloudinit/util.py | |||
180 | @@ -1746,7 +1746,7 @@ def chmod(path, mode): | |||
181 | 1746 | def write_file(filename, content, mode=0o644, omode="wb", copy_mode=False): | 1746 | def write_file(filename, content, mode=0o644, omode="wb", copy_mode=False): |
182 | 1747 | """ | 1747 | """ |
183 | 1748 | Writes a file with the given content and sets the file mode as specified. | 1748 | Writes a file with the given content and sets the file mode as specified. |
185 | 1749 | Resotres the SELinux context if possible. | 1749 | Restores the SELinux context if possible. |
186 | 1750 | 1750 | ||
187 | 1751 | @param filename: The full path of the file to write. | 1751 | @param filename: The full path of the file to write. |
188 | 1752 | @param content: The content to write to the file. | 1752 | @param content: The content to write to the file. |
189 | @@ -1865,8 +1865,13 @@ def subp(args, data=None, rcs=None, env=None, capture=True, shell=False, | |||
190 | 1865 | if not isinstance(data, bytes): | 1865 | if not isinstance(data, bytes): |
191 | 1866 | data = data.encode() | 1866 | data = data.encode() |
192 | 1867 | 1867 | ||
193 | 1868 | # Popen converts entries in the arguments array from non-bytes to bytes. | ||
194 | 1869 | # When locale is unset it may use ascii for that encoding which can | ||
195 | 1870 | # cause UnicodeDecodeErrors. (LP: #1751051) | ||
196 | 1871 | bytes_args = [x if isinstance(x, six.binary_type) else x.encode("utf-8") | ||
197 | 1872 | for x in args] | ||
198 | 1868 | try: | 1873 | try: |
200 | 1869 | sp = subprocess.Popen(args, stdout=stdout, | 1874 | sp = subprocess.Popen(bytes_args, stdout=stdout, |
201 | 1870 | stderr=stderr, stdin=stdin, | 1875 | stderr=stderr, stdin=stdin, |
202 | 1871 | env=env, shell=shell) | 1876 | env=env, shell=shell) |
203 | 1872 | (out, err) = sp.communicate(data) | 1877 | (out, err) = sp.communicate(data) |
204 | diff --git a/debian/changelog b/debian/changelog | |||
205 | index 2552bb6..27dba2c 100644 | |||
206 | --- a/debian/changelog | |||
207 | +++ b/debian/changelog | |||
208 | @@ -1,3 +1,16 @@ | |||
209 | 1 | cloud-init (18.1-5-g40e77380-0ubuntu1) bionic; urgency=medium | ||
210 | 2 | |||
211 | 3 | * New upstream snapshot. | ||
212 | 4 | - GCE: fix reading of user-data that is not base64 encoded. (LP: #1752711) | ||
213 | 5 | - doc: fix chef install from apt packages example in RTD. | ||
214 | 6 | - Implement puppet 4 support [Romanos Skiadas] (LP: #1446804) | ||
215 | 7 | - subp: Fix subp usage with non-ascii characters when no system locale. | ||
216 | 8 | (LP: #1751051) | ||
217 | 9 | - salt: configure grains in grains file rather than in minion config. | ||
218 | 10 | [Daniel Wallace] | ||
219 | 11 | |||
220 | 12 | -- Chad Smith <chad.smith@canonical.com> Thu, 01 Mar 2018 15:47:04 -0700 | ||
221 | 13 | |||
222 | 1 | cloud-init (18.1-0ubuntu1) bionic; urgency=medium | 14 | cloud-init (18.1-0ubuntu1) bionic; urgency=medium |
223 | 2 | 15 | ||
224 | 3 | * New upstream snapshot. | 16 | * New upstream snapshot. |
225 | diff --git a/doc/examples/cloud-config-chef.txt b/doc/examples/cloud-config-chef.txt | |||
226 | index 58d5fdc..defc5a5 100644 | |||
227 | --- a/doc/examples/cloud-config-chef.txt | |||
228 | +++ b/doc/examples/cloud-config-chef.txt | |||
229 | @@ -12,8 +12,8 @@ | |||
230 | 12 | 12 | ||
231 | 13 | # Key from https://packages.chef.io/chef.asc | 13 | # Key from https://packages.chef.io/chef.asc |
232 | 14 | apt: | 14 | apt: |
235 | 15 | source1: | 15 | sources: |
236 | 16 | source: "deb http://packages.chef.io/repos/apt/stable $RELEASE main" | 16 | source1: "deb http://packages.chef.io/repos/apt/stable $RELEASE main" |
237 | 17 | key: | | 17 | key: | |
238 | 18 | -----BEGIN PGP PUBLIC KEY BLOCK----- | 18 | -----BEGIN PGP PUBLIC KEY BLOCK----- |
239 | 19 | Version: GnuPG v1.4.12 (Darwin) | 19 | Version: GnuPG v1.4.12 (Darwin) |
240 | diff --git a/tests/cloud_tests/testcases/modules/salt_minion.py b/tests/cloud_tests/testcases/modules/salt_minion.py | |||
241 | index c697db2..f13b48a 100644 | |||
242 | --- a/tests/cloud_tests/testcases/modules/salt_minion.py | |||
243 | +++ b/tests/cloud_tests/testcases/modules/salt_minion.py | |||
244 | @@ -26,4 +26,9 @@ class Test(base.CloudTestCase): | |||
245 | 26 | self.assertIn('<key data>', out) | 26 | self.assertIn('<key data>', out) |
246 | 27 | self.assertIn('------END PUBLIC KEY-------', out) | 27 | self.assertIn('------END PUBLIC KEY-------', out) |
247 | 28 | 28 | ||
248 | 29 | def test_grains(self): | ||
249 | 30 | """Test master value in config.""" | ||
250 | 31 | out = self.get_data_file('grains') | ||
251 | 32 | self.assertIn('role: web', out) | ||
252 | 33 | |||
253 | 29 | # vi: ts=4 expandtab | 34 | # vi: ts=4 expandtab |
254 | diff --git a/tests/cloud_tests/testcases/modules/salt_minion.yaml b/tests/cloud_tests/testcases/modules/salt_minion.yaml | |||
255 | index f20d24f..ab0e05b 100644 | |||
256 | --- a/tests/cloud_tests/testcases/modules/salt_minion.yaml | |||
257 | +++ b/tests/cloud_tests/testcases/modules/salt_minion.yaml | |||
258 | @@ -17,6 +17,8 @@ cloud_config: | | |||
259 | 17 | ------BEGIN PRIVATE KEY------ | 17 | ------BEGIN PRIVATE KEY------ |
260 | 18 | <key data> | 18 | <key data> |
261 | 19 | ------END PRIVATE KEY------- | 19 | ------END PRIVATE KEY------- |
262 | 20 | grains: | ||
263 | 21 | role: web | ||
264 | 20 | collect_scripts: | 22 | collect_scripts: |
265 | 21 | minion: | | 23 | minion: | |
266 | 22 | #!/bin/bash | 24 | #!/bin/bash |
267 | @@ -30,5 +32,8 @@ collect_scripts: | |||
268 | 30 | minion.pub: | | 32 | minion.pub: | |
269 | 31 | #!/bin/bash | 33 | #!/bin/bash |
270 | 32 | cat /etc/salt/pki/minion/minion.pub | 34 | cat /etc/salt/pki/minion/minion.pub |
271 | 35 | grains: | | ||
272 | 36 | #!/bin/bash | ||
273 | 37 | cat /etc/salt/grains | ||
274 | 33 | 38 | ||
275 | 34 | # vi: ts=4 expandtab | 39 | # vi: ts=4 expandtab |
276 | diff --git a/tests/unittests/test_datasource/test_gce.py b/tests/unittests/test_datasource/test_gce.py | |||
277 | index f77c2c4..eb3cec4 100644 | |||
278 | --- a/tests/unittests/test_datasource/test_gce.py | |||
279 | +++ b/tests/unittests/test_datasource/test_gce.py | |||
280 | @@ -38,11 +38,20 @@ GCE_META_ENCODING = { | |||
281 | 38 | 'instance/hostname': 'server.project-baz.local', | 38 | 'instance/hostname': 'server.project-baz.local', |
282 | 39 | 'instance/zone': 'baz/bang', | 39 | 'instance/zone': 'baz/bang', |
283 | 40 | 'instance/attributes': { | 40 | 'instance/attributes': { |
285 | 41 | 'user-data': b64encode(b'/bin/echo baz\n').decode('utf-8'), | 41 | 'user-data': b64encode(b'#!/bin/echo baz\n').decode('utf-8'), |
286 | 42 | 'user-data-encoding': 'base64', | 42 | 'user-data-encoding': 'base64', |
287 | 43 | } | 43 | } |
288 | 44 | } | 44 | } |
289 | 45 | 45 | ||
290 | 46 | GCE_USER_DATA_TEXT = { | ||
291 | 47 | 'instance/id': '12345', | ||
292 | 48 | 'instance/hostname': 'server.project-baz.local', | ||
293 | 49 | 'instance/zone': 'baz/bang', | ||
294 | 50 | 'instance/attributes': { | ||
295 | 51 | 'user-data': '#!/bin/sh\necho hi mom\ntouch /run/up-now\n', | ||
296 | 52 | } | ||
297 | 53 | } | ||
298 | 54 | |||
299 | 46 | HEADERS = {'Metadata-Flavor': 'Google'} | 55 | HEADERS = {'Metadata-Flavor': 'Google'} |
300 | 47 | MD_URL_RE = re.compile( | 56 | MD_URL_RE = re.compile( |
301 | 48 | r'http://metadata.google.internal/computeMetadata/v1/.*') | 57 | r'http://metadata.google.internal/computeMetadata/v1/.*') |
302 | @@ -135,7 +144,16 @@ class TestDataSourceGCE(test_helpers.HttprettyTestCase): | |||
303 | 135 | shostname = GCE_META_PARTIAL.get('instance/hostname').split('.')[0] | 144 | shostname = GCE_META_PARTIAL.get('instance/hostname').split('.')[0] |
304 | 136 | self.assertEqual(shostname, self.ds.get_hostname()) | 145 | self.assertEqual(shostname, self.ds.get_hostname()) |
305 | 137 | 146 | ||
306 | 147 | def test_userdata_no_encoding(self): | ||
307 | 148 | """check that user-data is read.""" | ||
308 | 149 | _set_mock_metadata(GCE_USER_DATA_TEXT) | ||
309 | 150 | self.ds.get_data() | ||
310 | 151 | self.assertEqual( | ||
311 | 152 | GCE_USER_DATA_TEXT['instance/attributes']['user-data'].encode(), | ||
312 | 153 | self.ds.get_userdata_raw()) | ||
313 | 154 | |||
314 | 138 | def test_metadata_encoding(self): | 155 | def test_metadata_encoding(self): |
315 | 156 | """user-data is base64 encoded if user-data-encoding is 'base64'.""" | ||
316 | 139 | _set_mock_metadata(GCE_META_ENCODING) | 157 | _set_mock_metadata(GCE_META_ENCODING) |
317 | 140 | self.ds.get_data() | 158 | self.ds.get_data() |
318 | 141 | 159 | ||
319 | diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py | |||
320 | index 4a92e74..89ae40f 100644 | |||
321 | --- a/tests/unittests/test_util.py | |||
322 | +++ b/tests/unittests/test_util.py | |||
323 | @@ -8,7 +8,9 @@ import shutil | |||
324 | 8 | import stat | 8 | import stat |
325 | 9 | import tempfile | 9 | import tempfile |
326 | 10 | 10 | ||
327 | 11 | import json | ||
328 | 11 | import six | 12 | import six |
329 | 13 | import sys | ||
330 | 12 | import yaml | 14 | import yaml |
331 | 13 | 15 | ||
332 | 14 | from cloudinit import importer, util | 16 | from cloudinit import importer, util |
333 | @@ -733,6 +735,38 @@ class TestSubp(helpers.CiTestCase): | |||
334 | 733 | self.assertEqual("/target/my/path/", | 735 | self.assertEqual("/target/my/path/", |
335 | 734 | util.target_path("/target/", "///my/path/")) | 736 | util.target_path("/target/", "///my/path/")) |
336 | 735 | 737 | ||
337 | 738 | def test_c_lang_can_take_utf8_args(self): | ||
338 | 739 | """Independent of system LC_CTYPE, args can contain utf-8 strings. | ||
339 | 740 | |||
340 | 741 | When python starts up, its default encoding gets set based on | ||
341 | 742 | the value of LC_CTYPE. If no system locale is set, the default | ||
342 | 743 | encoding for both python2 and python3 in some paths will end up | ||
343 | 744 | being ascii. | ||
344 | 745 | |||
345 | 746 | Attempts to use setlocale or patching (or changing) os.environ | ||
346 | 747 | in the current environment seem to not be effective. | ||
347 | 748 | |||
348 | 749 | This test starts up a python with LC_CTYPE set to C so that | ||
349 | 750 | the default encoding will be set to ascii. In such an environment | ||
350 | 751 | Popen(['command', 'non-ascii-arg']) would cause a UnicodeDecodeError. | ||
351 | 752 | """ | ||
352 | 753 | python_prog = '\n'.join([ | ||
353 | 754 | 'import json, sys', | ||
354 | 755 | 'from cloudinit.util import subp', | ||
355 | 756 | 'data = sys.stdin.read()', | ||
356 | 757 | 'cmd = json.loads(data)', | ||
357 | 758 | 'subp(cmd, capture=False)', | ||
358 | 759 | '']) | ||
359 | 760 | cmd = [BASH, '-c', 'echo -n "$@"', '--', | ||
360 | 761 | self.utf8_valid.decode("utf-8")] | ||
361 | 762 | python_subp = [sys.executable, '-c', python_prog] | ||
362 | 763 | |||
363 | 764 | out, _err = util.subp( | ||
364 | 765 | python_subp, update_env={'LC_CTYPE': 'C'}, | ||
365 | 766 | data=json.dumps(cmd).encode("utf-8"), | ||
366 | 767 | decode=False) | ||
367 | 768 | self.assertEqual(self.utf8_valid, out) | ||
368 | 769 | |||
369 | 736 | 770 | ||
370 | 737 | class TestEncode(helpers.TestCase): | 771 | class TestEncode(helpers.TestCase): |
371 | 738 | """Test the encoding functions""" | 772 | """Test the encoding functions""" |
PASSED: Continuous integration, rev:964c869024b 422c3966975cc6d 23981537084b0a /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 804/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 804/rebuild
https:/