Merge lp:~harlowja/cloud-init/yum-repo-conf-write into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Joshua Harlow
Status: Merged
Merged at revision: 712
Proposed branch: lp:~harlowja/cloud-init/yum-repo-conf-write
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 346 lines (+221/-29)
9 files modified
cloudinit/config/cc_yum_add_repo.py (+106/-0)
cloudinit/distros/__init__.py (+19/-21)
cloudinit/sources/DataSourceAltCloud.py (+1/-1)
cloudinit/util.py (+1/-2)
doc/examples/cloud-config-yum-repo.txt (+20/-0)
tests/configs/sample1.yaml (+0/-1)
tests/unittests/test_handler/test_handler_yum_add_repo.py (+68/-0)
tests/unittests/test_runs/test_simple_run.py (+4/-2)
tools/hacking.py (+2/-2)
To merge this branch: bzr merge lp:~harlowja/cloud-init/yum-repo-conf-write
Reviewer Review Type Date Requested Status
Scott Moser Needs Fixing
Review via email: mp+129998@code.launchpad.net
To post a comment you must log in.
690. By Joshua Harlow

Fix copyright.

691. By Joshua Harlow

Update comments as to why format is the way it is.

692. By Joshua Harlow

Add in a created by cloud-init header.

693. By Joshua Harlow

Make the yum value 'string' translation a function.

694. By Joshua Harlow

More cleanups, fix header function.

Revision history for this message
Scott Moser (smoser) wrote :

Pep8:
cloudinit/distros/__init__.py:442:45: W291 trailing whitespace
tests/unittests/test_datasource/test_configdrive.py:10:1: N306: imports not in alphabetical order (cloudinit.sources.datasourceconfigdrive, cloudinit.settings)
tests/unittests/test_datasource/test_configdrive.py:16:1: E303 too many blank lines (3)

Also, needs documentation in doc/examples somewhere.

review: Needs Fixing
695. By Joshua Harlow

1. Rebased with trunk
2. Added example cloud-config
3. Added functioning test for yum config

Revision history for this message
Joshua Harlow (harlowja) wrote :

> Pep8:
> cloudinit/distros/__init__.py:442:45: W291 trailing whitespace
> tests/unittests/test_datasource/test_configdrive.py:10:1: N306: imports not in
> alphabetical order (cloudinit.sources.datasourceconfigdrive,
> cloudinit.settings)
> tests/unittests/test_datasource/test_configdrive.py:16:1: E303 too many blank
> lines (3)
>
> Also, needs documentation in doc/examples somewhere.

All fixed.

Revision history for this message
Scott Moser (smoser) wrote :

It looks good now, except for some probably unintended whitespace cleanups / unrelated changes.

I went ahead and un-applied those, merged, and then re-applied them as a separate commit.

Revision history for this message
Joshua Harlow (harlowja) wrote :

Thanks much, making sure I run pep8 more often now that it doesn't warn about everything under the sun.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'cloudinit/config/cc_yum_add_repo.py'
2--- cloudinit/config/cc_yum_add_repo.py 1970-01-01 00:00:00 +0000
3+++ cloudinit/config/cc_yum_add_repo.py 2012-11-08 04:57:21 +0000
4@@ -0,0 +1,106 @@
5+# vi: ts=4 expandtab
6+#
7+# Copyright (C) 2012 Yahoo! Inc.
8+#
9+# Author: Joshua Harlow <harlowja@yahoo-inc.com>
10+#
11+# This program is free software: you can redistribute it and/or modify
12+# it under the terms of the GNU General Public License version 3, as
13+# published by the Free Software Foundation.
14+#
15+# This program is distributed in the hope that it will be useful,
16+# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+# GNU General Public License for more details.
19+#
20+# You should have received a copy of the GNU General Public License
21+# along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
23+import os
24+
25+from cloudinit import util
26+
27+import configobj
28+
29+
30+def _canonicalize_id(repo_id):
31+ repo_id = repo_id.lower().replace("-", "_")
32+ repo_id = repo_id.replace(" ", "_")
33+ return repo_id
34+
35+
36+def _format_repo_value(val):
37+ if isinstance(val, (bool)):
38+ # Seems like yum prefers 1/0
39+ return str(int(val))
40+ if isinstance(val, (list, tuple)):
41+ # Can handle 'lists' in certain cases
42+ # See: http://bit.ly/Qqrf1t
43+ return "\n ".join([_format_repo_value(v) for v in val])
44+ if not isinstance(val, (basestring, str)):
45+ return str(val)
46+ return val
47+
48+
49+## TODO(harlowja): move to distro?
50+# See man yum.conf
51+def _format_repository_config(repo_id, repo_config):
52+ to_be = configobj.ConfigObj()
53+ to_be[repo_id] = {}
54+ # Do basic translation of the items -> values
55+ for (k, v) in repo_config.items():
56+ # For now assume that people using this know
57+ # the format of yum and don't verify keys/values further
58+ to_be[repo_id][k] = _format_repo_value(v)
59+ lines = to_be.write()
60+ lines.insert(0, "# Created by cloud-init on %s" % (util.time_rfc2822()))
61+ return "\n".join(lines)
62+
63+
64+def handle(name, cfg, _cloud, log, _args):
65+ repos = cfg.get('yum_repos')
66+ if not repos:
67+ log.debug(("Skipping module named %s,"
68+ " no 'yum_repos' configuration found"), name)
69+ return
70+ repo_base_path = util.get_cfg_option_str(cfg, 'yum_repo_dir',
71+ '/etc/yum.repos.d/')
72+ repo_locations = {}
73+ repo_configs = {}
74+ for (repo_id, repo_config) in repos.items():
75+ canon_repo_id = _canonicalize_id(repo_id)
76+ repo_fn_pth = os.path.join(repo_base_path, "%s.repo" % (canon_repo_id))
77+ if os.path.exists(repo_fn_pth):
78+ log.info("Skipping repo %s, file %s already exists!",
79+ repo_id, repo_fn_pth)
80+ continue
81+ elif canon_repo_id in repo_locations:
82+ log.info("Skipping repo %s, file %s already pending!",
83+ repo_id, repo_fn_pth)
84+ continue
85+ if not repo_config:
86+ repo_config = {}
87+ # Do some basic sanity checks/cleaning
88+ n_repo_config = {}
89+ for (k, v) in repo_config.items():
90+ k = k.lower().strip().replace("-", "_")
91+ if k:
92+ n_repo_config[k] = v
93+ repo_config = n_repo_config
94+ missing_required = 0
95+ for req_field in ['baseurl']:
96+ if not req_field in repo_config:
97+ log.warn(("Repository %s does not contain a %s"
98+ " configuration 'required' entry"),
99+ repo_id, req_field)
100+ missing_required += 1
101+ if not missing_required:
102+ repo_configs[canon_repo_id] = repo_config
103+ repo_locations[canon_repo_id] = repo_fn_pth
104+ else:
105+ log.warn("Repository %s is missing %s required fields, skipping!",
106+ repo_id, missing_required)
107+ for (c_repo_id, path) in repo_locations.items():
108+ repo_blob = _format_repository_config(c_repo_id,
109+ repo_configs.get(c_repo_id))
110+ util.write_file(path, repo_blob)
111
112=== modified file 'cloudinit/distros/__init__.py'
113--- cloudinit/distros/__init__.py 2012-11-07 15:42:54 +0000
114+++ cloudinit/distros/__init__.py 2012-11-08 04:57:21 +0000
115@@ -81,7 +81,7 @@
116
117 def _get_arch_package_mirror_info(self, arch=None):
118 mirror_info = self.get_option("package_mirrors", [])
119- if arch == None:
120+ if not arch:
121 arch = self.get_primary_arch()
122 return _get_arch_package_mirror_info(mirror_info, arch)
123
124@@ -187,23 +187,23 @@
125 # inputs. If something goes wrong, we can end up with a system
126 # that nobody can login to.
127 adduser_opts = {
128- "gecos": '--comment',
129- "homedir": '--home',
130- "primary_group": '--gid',
131- "groups": '--groups',
132- "passwd": '--password',
133- "shell": '--shell',
134- "expiredate": '--expiredate',
135- "inactive": '--inactive',
136- "selinux_user": '--selinux-user',
137- }
138+ "gecos": '--comment',
139+ "homedir": '--home',
140+ "primary_group": '--gid',
141+ "groups": '--groups',
142+ "passwd": '--password',
143+ "shell": '--shell',
144+ "expiredate": '--expiredate',
145+ "inactive": '--inactive',
146+ "selinux_user": '--selinux-user',
147+ }
148
149 adduser_opts_flags = {
150- "no_user_group": '--no-user-group',
151- "system": '--system',
152- "no_log_init": '--no-log-init',
153- "no_create_home": "-M",
154- }
155+ "no_user_group": '--no-user-group',
156+ "system": '--system',
157+ "no_log_init": '--no-log-init',
158+ "no_create_home": "-M",
159+ }
160
161 # Now check the value and create the command
162 for option in kwargs:
163@@ -312,11 +312,9 @@
164 raise e
165 util.ensure_dir(path, 0755)
166
167- def write_sudo_rules(self,
168- user,
169- rules,
170- sudo_file="/etc/sudoers.d/90-cloud-init-users",
171- ):
172+ def write_sudo_rules(self, user, rules, sudo_file=None):
173+ if not sudo_file:
174+ sudo_file = "/etc/sudoers.d/90-cloud-init-users"
175
176 content_header = "# user rules for %s" % user
177 content = "%s\n%s %s\n\n" % (content_header, user, rules)
178
179=== modified file 'cloudinit/sources/DataSourceAltCloud.py'
180--- cloudinit/sources/DataSourceAltCloud.py 2012-09-21 00:56:22 +0000
181+++ cloudinit/sources/DataSourceAltCloud.py 2012-11-08 04:57:21 +0000
182@@ -47,7 +47,7 @@
183 'instance-id': 455,
184 'local-hostname': 'localhost',
185 'placement': {},
186- }
187+}
188
189
190 def read_user_data_callback(mount_dir):
191
192=== modified file 'cloudinit/util.py'
193--- cloudinit/util.py 2012-10-23 16:58:32 +0000
194+++ cloudinit/util.py 2012-11-08 04:57:21 +0000
195@@ -1193,8 +1193,7 @@
196 indent=4,
197 explicit_start=True,
198 explicit_end=True,
199- default_flow_style=False,
200- )
201+ default_flow_style=False)
202 return formatted
203
204
205
206=== added file 'doc/examples/cloud-config-yum-repo.txt'
207--- doc/examples/cloud-config-yum-repo.txt 1970-01-01 00:00:00 +0000
208+++ doc/examples/cloud-config-yum-repo.txt 2012-11-08 04:57:21 +0000
209@@ -0,0 +1,20 @@
210+#cloud-config
211+# vim: syntax=yaml
212+#
213+# Add yum repository configuration to the system
214+#
215+# The following example adds the file /etc/yum.repos.d/epel_testing.repo
216+# which can then subsequently be used by yum for later operations.
217+yum_repos:
218+ # The name of the repository
219+ epel-testing:
220+ # Any repository configuration options
221+ # See: man yum.conf
222+ #
223+ # This one is required!
224+ baseurl: http://download.fedoraproject.org/pub/epel/testing/5/$basearch
225+ enabled: false
226+ failovermethod: priority
227+ gpgcheck: true
228+ gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL
229+ name: Extra Packages for Enterprise Linux 5 - Testing
230
231=== modified file 'tests/configs/sample1.yaml'
232--- tests/configs/sample1.yaml 2012-07-03 01:43:54 +0000
233+++ tests/configs/sample1.yaml 2012-11-08 04:57:21 +0000
234@@ -50,4 +50,3 @@
235
236
237 byobu_by_default: user
238-output: {all: '| tee -a /var/log/cloud-init-output.log'}
239
240=== added file 'tests/unittests/test_handler/test_handler_yum_add_repo.py'
241--- tests/unittests/test_handler/test_handler_yum_add_repo.py 1970-01-01 00:00:00 +0000
242+++ tests/unittests/test_handler/test_handler_yum_add_repo.py 2012-11-08 04:57:21 +0000
243@@ -0,0 +1,68 @@
244+from cloudinit import helpers
245+from cloudinit import util
246+
247+from cloudinit.config import cc_yum_add_repo
248+
249+from tests.unittests import helpers
250+
251+import logging
252+
253+from StringIO import StringIO
254+
255+import configobj
256+
257+LOG = logging.getLogger(__name__)
258+
259+
260+class TestConfig(helpers.FilesystemMockingTestCase):
261+ def setUp(self):
262+ super(TestConfig, self).setUp()
263+ self.tmp = self.makeDir(prefix="unittest_")
264+
265+ def test_bad_config(self):
266+ cfg = {
267+ 'yum_repos': {
268+ 'epel-testing': {
269+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
270+ # Missing this should cause the repo not to be written
271+ #'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
272+ 'enabled': False,
273+ 'gpgcheck': True,
274+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
275+ 'failovermethod': 'priority',
276+ },
277+ },
278+ }
279+ self.patchUtils(self.tmp)
280+ cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, [])
281+ with self.assertRaises(IOError):
282+ util.load_file("/etc/yum.repos.d/epel_testing.repo")
283+
284+ def test_write_config(self):
285+ cfg = {
286+ 'yum_repos': {
287+ 'epel-testing': {
288+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
289+ 'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
290+ 'enabled': False,
291+ 'gpgcheck': True,
292+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
293+ 'failovermethod': 'priority',
294+ },
295+ },
296+ }
297+ self.patchUtils(self.tmp)
298+ cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, [])
299+ contents = util.load_file("/etc/yum.repos.d/epel_testing.repo")
300+ contents = configobj.ConfigObj(StringIO(contents))
301+ expected = {
302+ 'epel_testing': {
303+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
304+ 'failovermethod': 'priority',
305+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
306+ 'enabled': '0',
307+ 'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
308+ 'gpgcheck': '1',
309+ }
310+ }
311+ self.assertEquals(expected, dict(contents))
312
313=== modified file 'tests/unittests/test_runs/test_simple_run.py'
314--- tests/unittests/test_runs/test_simple_run.py 2012-10-28 02:25:48 +0000
315+++ tests/unittests/test_runs/test_simple_run.py 2012-11-08 04:57:21 +0000
316@@ -37,11 +37,13 @@
317 self.replicateTestRoot('simple_ubuntu', new_root)
318 cfg = {
319 'datasource_list': ['None'],
320- 'write_files': [{
321+ 'write_files': [
322+ {
323 'path': '/etc/blah.ini',
324 'content': 'blah',
325 'permissions': 0755,
326- }],
327+ },
328+ ],
329 'cloud_init_modules': ['write-files'],
330 }
331 cloud_cfg = util.yaml_dumps(cfg)
332
333=== modified file 'tools/hacking.py'
334--- tools/hacking.py 2012-08-22 18:12:32 +0000
335+++ tools/hacking.py 2012-11-08 04:57:21 +0000
336@@ -66,8 +66,8 @@
337 # handle import x
338 # use .lower since capitalization shouldn't dictate order
339 split_line = import_normalize(physical_line.strip()).lower().split()
340- split_previous = import_normalize(lines[line_number - 2]
341- ).strip().lower().split()
342+ split_previous = import_normalize(lines[line_number - 2])
343+ split_previous = split_previous.strip().lower().split()
344 # with or without "as y"
345 length = [2, 4]
346 if (len(split_line) in length and len(split_previous) in length and