← Back to team overview

cloud-init-dev team mailing list archive

[Merge] lp:~craigtracey/cloud-init/package-versions into lp:cloud-init

 

Craig Tracey has proposed merging lp:~craigtracey/cloud-init/package-versions into lp:cloud-init.

Requested reviews:
  cloud init development team (cloud-init-dev)
Related bugs:
  Bug #1108047 in cloud-init: "cloud-init should have generic mechanism for installing specific package versions"
  https://bugs.launchpad.net/cloud-init/+bug/1108047

For more details, see:
https://code.launchpad.net/~craigtracey/cloud-init/package-versions/+merge/145209

Adding package versioning logic to package_command

This change adds the ability to provide specific package versions to
Distro.install_packages and subsequently Distro.package_command. In order
to effectively use Distro.install_packages, one is now able to pass a
variety of formats in order to easily manage package requirements. These
are examples of what can be passed:
- "package"
- ["package1","package2"]
- ("package",)
- ("package", "version")
- [("package1",)("package2",)]
- [("package1", "version1"),("package2","version2")]

This change also adds the option to install a specific version for the
puppet configuration module. This is especially important here as
successful puppet deployments are highly reliant on specific puppet
versions.

-- 
https://code.launchpad.net/~craigtracey/cloud-init/package-versions/+merge/145209
Your team cloud init development team is requested to review the proposed merge of lp:~craigtracey/cloud-init/package-versions into lp:cloud-init.
=== modified file 'cloudinit/config/cc_landscape.py'
--- cloudinit/config/cc_landscape.py	2012-10-28 02:25:48 +0000
+++ cloudinit/config/cc_landscape.py	2013-01-28 17:07:23 +0000
@@ -62,7 +62,7 @@
     if not ls_cloudcfg:
         return
 
-    cloud.distro.install_packages(["landscape-client"])
+    cloud.distro.install_packages(('landscape-client',))
 
     merge_data = [
         LSC_BUILTIN_CFG,

=== modified file 'cloudinit/config/cc_puppet.py'
--- cloudinit/config/cc_puppet.py	2012-12-14 02:06:32 +0000
+++ cloudinit/config/cc_puppet.py	2013-01-28 17:07:23 +0000
@@ -59,8 +59,14 @@
 
     # Start by installing the puppet package if necessary...
     install = util.get_cfg_option_bool(puppet_cfg, 'install', True)
-    if install:
-        cloud.distro.install_packages(["puppet"])
+    version = util.get_cfg_option_str(puppet_cfg, 'version', None)
+    if not install and version:
+        log.warn(("Puppet install set false but version supplied,"
+                  " doing nothing."))
+    elif install:
+        log.debug(("Attempting to install puppet %s,"),
+                   version if version else 'latest')
+        cloud.distro.install_packages(('puppet', version))
 
     # ... and then update the puppet configuration
     if 'conf' in puppet_cfg:

=== modified file 'cloudinit/config/cc_salt_minion.py'
--- cloudinit/config/cc_salt_minion.py	2012-10-28 02:25:48 +0000
+++ cloudinit/config/cc_salt_minion.py	2013-01-28 17:07:23 +0000
@@ -31,7 +31,7 @@
     salt_cfg = cfg['salt_minion']
 
     # Start by installing the salt package ...
-    cloud.distro.install_packages(["salt-minion"])
+    cloud.distro.install_packages(('salt-minion',))
 
     # Ensure we can configure files at the right dir
     config_dir = salt_cfg.get("config_dir", '/etc/salt')

=== modified file 'cloudinit/distros/debian.py'
--- cloudinit/distros/debian.py	2013-01-15 21:08:43 +0000
+++ cloudinit/distros/debian.py	2013-01-28 17:07:23 +0000
@@ -65,7 +65,7 @@
 
     def install_packages(self, pkglist):
         self.update_package_sources()
-        self.package_command('install', pkglist)
+        self.package_command('install', pkgs=pkglist)
 
     def _write_network(self, settings):
         util.write_file(self.network_conf_fn, settings)
@@ -142,15 +142,24 @@
         # This ensures that the correct tz will be used for the system
         util.copy(tz_file, self.tz_local_fn)
 
-    def package_command(self, command, args=None):
+    def package_command(self, command, args=None, pkgs=[]):
         e = os.environ.copy()
         # See: http://tiny.cc/kg91fw
         # Or: http://tiny.cc/mh91fw
         e['DEBIAN_FRONTEND'] = 'noninteractive'
         cmd = ['apt-get', '--option', 'Dpkg::Options::=--force-confold',
-               '--assume-yes', '--quiet', command]
-        if args:
+               '--assume-yes', '--quiet']
+
+        if args and isinstance(args, str):
+            cmd.append(args)
+        elif args and isinstance(args, list):
             cmd.extend(args)
+
+        cmd.append(command)
+
+        pkglist = util.expand_package_list('%s=%s', pkgs)
+        cmd.extend(pkglist)
+
         # Allow the output of this to flow outwards (ie not be captured)
         util.subp(cmd, env=e, capture=False)
 

=== modified file 'cloudinit/distros/rhel.py'
--- cloudinit/distros/rhel.py	2013-01-15 21:08:43 +0000
+++ cloudinit/distros/rhel.py	2013-01-28 17:07:23 +0000
@@ -63,7 +63,7 @@
         self.osfamily = 'redhat'
 
     def install_packages(self, pkglist):
-        self.package_command('install', pkglist)
+        self.package_command('install', pkgs=pkglist)
 
     def _adjust_resolve(self, dns_servers, search_servers):
         try:
@@ -208,7 +208,7 @@
         # This ensures that the correct tz will be used for the system
         util.copy(tz_file, self.tz_local_fn)
 
-    def package_command(self, command, args=None):
+    def package_command(self, command, args=None, pkgs=[]):
         cmd = ['yum']
         # If enabled, then yum will be tolerant of errors on the command line
         # with regard to packages.
@@ -219,9 +219,17 @@
         # Determines whether or not yum prompts for confirmation
         # of critical actions. We don't want to prompt...
         cmd.append("-y")
+
+        if args and isinstance(args, str):
+            cmd.append(args)
+        elif args and isinstance(args, list):
+            cmd.extend(args)
+
         cmd.append(command)
-        if args:
-            cmd.extend(args)
+
+        pkglist = util.expand_package_list('%s-%s', pkgs)
+        cmd.extend(pkglist)
+
         # Allow the output of this to flow outwards (ie not be captured)
         util.subp(cmd, capture=False)
 

=== modified file 'cloudinit/util.py'
--- cloudinit/util.py	2013-01-17 00:46:30 +0000
+++ cloudinit/util.py	2013-01-28 17:07:23 +0000
@@ -1560,3 +1560,26 @@
         device = device[5:]
 
     return os.path.isfile("/sys/class/block/%s/partition" % device)
+
+
+def expand_package_list(version_fmt, pkgs):
+    # we will accept tuples, lists of tuples, or just plain lists
+    if not isinstance(pkgs, list):
+        pkgs = [pkgs]
+
+    pkglist = []
+    for pkg in pkgs:
+        if isinstance(pkg, str):
+            pkglist.append(pkg)
+            continue
+
+        if len(pkg) < 1 or len(pkg) > 2:
+            raise RuntimeError("Invalid package_command tuple.")
+
+        if len(pkg) == 2 and pkg[1]:
+            pkglist.append(version_fmt % pkg)
+            continue
+
+        pkglist.append(pkg[0])
+
+    return pkglist