summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloudinit/config/cc_yum_add_repo.py106
-rw-r--r--cloudinit/distros/__init__.py38
-rw-r--r--cloudinit/sources/DataSourceAltCloud.py2
-rw-r--r--cloudinit/util.py3
-rw-r--r--doc/examples/cloud-config-yum-repo.txt20
-rw-r--r--tests/unittests/test_handler/test_handler_yum_add_repo.py68
-rw-r--r--tests/unittests/test_runs/test_simple_run.py6
-rwxr-xr-xtools/hacking.py4
8 files changed, 220 insertions, 27 deletions
diff --git a/cloudinit/config/cc_yum_add_repo.py b/cloudinit/config/cc_yum_add_repo.py
new file mode 100644
index 00000000..5c273825
--- /dev/null
+++ b/cloudinit/config/cc_yum_add_repo.py
@@ -0,0 +1,106 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2012 Yahoo! Inc.
+#
+# Author: Joshua Harlow <harlowja@yahoo-inc.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3, as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from cloudinit import util
+
+import configobj
+
+
+def _canonicalize_id(repo_id):
+ repo_id = repo_id.lower().replace("-", "_")
+ repo_id = repo_id.replace(" ", "_")
+ return repo_id
+
+
+def _format_repo_value(val):
+ if isinstance(val, (bool)):
+ # Seems like yum prefers 1/0
+ return str(int(val))
+ if isinstance(val, (list, tuple)):
+ # Can handle 'lists' in certain cases
+ # See: http://bit.ly/Qqrf1t
+ return "\n ".join([_format_repo_value(v) for v in val])
+ if not isinstance(val, (basestring, str)):
+ return str(val)
+ return val
+
+
+## TODO(harlowja): move to distro?
+# See man yum.conf
+def _format_repository_config(repo_id, repo_config):
+ to_be = configobj.ConfigObj()
+ to_be[repo_id] = {}
+ # Do basic translation of the items -> values
+ for (k, v) in repo_config.items():
+ # For now assume that people using this know
+ # the format of yum and don't verify keys/values further
+ to_be[repo_id][k] = _format_repo_value(v)
+ lines = to_be.write()
+ lines.insert(0, "# Created by cloud-init on %s" % (util.time_rfc2822()))
+ return "\n".join(lines)
+
+
+def handle(name, cfg, _cloud, log, _args):
+ repos = cfg.get('yum_repos')
+ if not repos:
+ log.debug(("Skipping module named %s,"
+ " no 'yum_repos' configuration found"), name)
+ return
+ repo_base_path = util.get_cfg_option_str(cfg, 'yum_repo_dir',
+ '/etc/yum.repos.d/')
+ repo_locations = {}
+ repo_configs = {}
+ for (repo_id, repo_config) in repos.items():
+ canon_repo_id = _canonicalize_id(repo_id)
+ repo_fn_pth = os.path.join(repo_base_path, "%s.repo" % (canon_repo_id))
+ if os.path.exists(repo_fn_pth):
+ log.info("Skipping repo %s, file %s already exists!",
+ repo_id, repo_fn_pth)
+ continue
+ elif canon_repo_id in repo_locations:
+ log.info("Skipping repo %s, file %s already pending!",
+ repo_id, repo_fn_pth)
+ continue
+ if not repo_config:
+ repo_config = {}
+ # Do some basic sanity checks/cleaning
+ n_repo_config = {}
+ for (k, v) in repo_config.items():
+ k = k.lower().strip().replace("-", "_")
+ if k:
+ n_repo_config[k] = v
+ repo_config = n_repo_config
+ missing_required = 0
+ for req_field in ['baseurl']:
+ if not req_field in repo_config:
+ log.warn(("Repository %s does not contain a %s"
+ " configuration 'required' entry"),
+ repo_id, req_field)
+ missing_required += 1
+ if not missing_required:
+ repo_configs[canon_repo_id] = repo_config
+ repo_locations[canon_repo_id] = repo_fn_pth
+ else:
+ log.warn("Repository %s is missing %s required fields, skipping!",
+ repo_id, missing_required)
+ for (c_repo_id, path) in repo_locations.items():
+ repo_blob = _format_repository_config(c_repo_id,
+ repo_configs.get(c_repo_id))
+ util.write_file(path, repo_blob)
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index d2cb0a8b..8a98e334 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -187,23 +187,23 @@ class Distro(object):
# inputs. If something goes wrong, we can end up with a system
# that nobody can login to.
adduser_opts = {
- "gecos": '--comment',
- "homedir": '--home',
- "primary_group": '--gid',
- "groups": '--groups',
- "passwd": '--password',
- "shell": '--shell',
- "expiredate": '--expiredate',
- "inactive": '--inactive',
- "selinux_user": '--selinux-user',
- }
+ "gecos": '--comment',
+ "homedir": '--home',
+ "primary_group": '--gid',
+ "groups": '--groups',
+ "passwd": '--password',
+ "shell": '--shell',
+ "expiredate": '--expiredate',
+ "inactive": '--inactive',
+ "selinux_user": '--selinux-user',
+ }
adduser_opts_flags = {
- "no_user_group": '--no-user-group',
- "system": '--system',
- "no_log_init": '--no-log-init',
- "no_create_home": "-M",
- }
+ "no_user_group": '--no-user-group',
+ "system": '--system',
+ "no_log_init": '--no-log-init',
+ "no_create_home": "-M",
+ }
# Now check the value and create the command
for option in kwargs:
@@ -320,11 +320,9 @@ class Distro(object):
raise e
util.ensure_dir(path, 0750)
- def write_sudo_rules(self,
- user,
- rules,
- sudo_file="/etc/sudoers.d/90-cloud-init-users",
- ):
+ def write_sudo_rules(self, user, rules, sudo_file=None):
+ if not sudo_file:
+ sudo_file = "/etc/sudoers.d/90-cloud-init-users"
content_header = "# user rules for %s" % user
content = "%s\n%s %s\n\n" % (content_header, user, rules)
diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
index d7e1204f..9812bdcb 100644
--- a/cloudinit/sources/DataSourceAltCloud.py
+++ b/cloudinit/sources/DataSourceAltCloud.py
@@ -47,7 +47,7 @@ META_DATA_NOT_SUPPORTED = {
'instance-id': 455,
'local-hostname': 'localhost',
'placement': {},
- }
+}
def read_user_data_callback(mount_dir):
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 7890a3d6..4f5b15ee 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -1193,8 +1193,7 @@ def yaml_dumps(obj):
indent=4,
explicit_start=True,
explicit_end=True,
- default_flow_style=False,
- )
+ default_flow_style=False)
return formatted
diff --git a/doc/examples/cloud-config-yum-repo.txt b/doc/examples/cloud-config-yum-repo.txt
new file mode 100644
index 00000000..ab2c031e
--- /dev/null
+++ b/doc/examples/cloud-config-yum-repo.txt
@@ -0,0 +1,20 @@
+#cloud-config
+# vim: syntax=yaml
+#
+# Add yum repository configuration to the system
+#
+# The following example adds the file /etc/yum.repos.d/epel_testing.repo
+# which can then subsequently be used by yum for later operations.
+yum_repos:
+ # The name of the repository
+ epel-testing:
+ # Any repository configuration options
+ # See: man yum.conf
+ #
+ # This one is required!
+ baseurl: http://download.fedoraproject.org/pub/epel/testing/5/$basearch
+ enabled: false
+ failovermethod: priority
+ gpgcheck: true
+ gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL
+ name: Extra Packages for Enterprise Linux 5 - Testing
diff --git a/tests/unittests/test_handler/test_handler_yum_add_repo.py b/tests/unittests/test_handler/test_handler_yum_add_repo.py
new file mode 100644
index 00000000..c7fcddc4
--- /dev/null
+++ b/tests/unittests/test_handler/test_handler_yum_add_repo.py
@@ -0,0 +1,68 @@
+from cloudinit import helpers
+from cloudinit import util
+
+from cloudinit.config import cc_yum_add_repo
+
+from tests.unittests import helpers
+
+import logging
+
+from StringIO import StringIO
+
+import configobj
+
+LOG = logging.getLogger(__name__)
+
+
+class TestConfig(helpers.FilesystemMockingTestCase):
+ def setUp(self):
+ super(TestConfig, self).setUp()
+ self.tmp = self.makeDir(prefix="unittest_")
+
+ def test_bad_config(self):
+ cfg = {
+ 'yum_repos': {
+ 'epel-testing': {
+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
+ # Missing this should cause the repo not to be written
+ #'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
+ 'enabled': False,
+ 'gpgcheck': True,
+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
+ 'failovermethod': 'priority',
+ },
+ },
+ }
+ self.patchUtils(self.tmp)
+ cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, [])
+ with self.assertRaises(IOError):
+ util.load_file("/etc/yum.repos.d/epel_testing.repo")
+
+ def test_write_config(self):
+ cfg = {
+ 'yum_repos': {
+ 'epel-testing': {
+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
+ 'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
+ 'enabled': False,
+ 'gpgcheck': True,
+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
+ 'failovermethod': 'priority',
+ },
+ },
+ }
+ self.patchUtils(self.tmp)
+ cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, [])
+ contents = util.load_file("/etc/yum.repos.d/epel_testing.repo")
+ contents = configobj.ConfigObj(StringIO(contents))
+ expected = {
+ 'epel_testing': {
+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
+ 'failovermethod': 'priority',
+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
+ 'enabled': '0',
+ 'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
+ 'gpgcheck': '1',
+ }
+ }
+ self.assertEquals(expected, dict(contents))
diff --git a/tests/unittests/test_runs/test_simple_run.py b/tests/unittests/test_runs/test_simple_run.py
index 22d6cf2c..60ef812a 100644
--- a/tests/unittests/test_runs/test_simple_run.py
+++ b/tests/unittests/test_runs/test_simple_run.py
@@ -37,11 +37,13 @@ class TestSimpleRun(helpers.FilesystemMockingTestCase):
self.replicateTestRoot('simple_ubuntu', new_root)
cfg = {
'datasource_list': ['None'],
- 'write_files': [{
+ 'write_files': [
+ {
'path': '/etc/blah.ini',
'content': 'blah',
'permissions': 0755,
- }],
+ },
+ ],
'cloud_init_modules': ['write-files'],
}
cloud_cfg = util.yaml_dumps(cfg)
diff --git a/tools/hacking.py b/tools/hacking.py
index 11163df3..26a07c53 100755
--- a/tools/hacking.py
+++ b/tools/hacking.py
@@ -66,8 +66,8 @@ def cloud_import_alphabetical(physical_line, line_number, lines):
# handle import x
# use .lower since capitalization shouldn't dictate order
split_line = import_normalize(physical_line.strip()).lower().split()
- split_previous = import_normalize(lines[line_number - 2]
- ).strip().lower().split()
+ split_previous = import_normalize(lines[line_number - 2])
+ split_previous = split_previous.strip().lower().split()
# with or without "as y"
length = [2, 4]
if (len(split_line) in length and len(split_previous) in length and