diff -Nru cloud-init-0.6.3/debian/changelog cloud-init-0.6.3/debian/changelog --- cloud-init-0.6.3/debian/changelog 2015-11-17 17:02:08.000000000 +0000 +++ cloud-init-0.6.3/debian/changelog 2016-04-07 14:10:09.000000000 +0100 @@ -1,3 +1,14 @@ +cloud-init (0.6.3-0ubuntu1.25) precise; urgency=medium + + * Microsoft Azure: Use stable VM instance ID over SharedConfig.xml + (LP: #1506187) + - d/patches/lp-1506187-azure_use_unique_vm_id.patch: use DMI data for + the stable VM instance ID + - d/cloud-init.preinst: migrate existing instances to stable VM instance + ID on upgrade from prior versions of cloud-init. + + -- Daniel Watkins Thu, 07 Apr 2016 13:13:47 +0100 + cloud-init (0.6.3-0ubuntu1.24) precise; urgency=medium * d/patches/lp-1506244-azure-ssh-key-values.patch: AZURE: Add support diff -Nru cloud-init-0.6.3/debian/cloud-init.preinst cloud-init-0.6.3/debian/cloud-init.preinst --- cloud-init-0.6.3/debian/cloud-init.preinst 2014-09-18 23:24:36.000000000 +0100 +++ cloud-init-0.6.3/debian/cloud-init.preinst 2016-04-07 14:42:22.000000000 +0100 @@ -105,6 +105,73 @@ return 0 } +azure_apply_new_instance_id_1506187() { + # With LP: #1506187, the Azure instance ID detection method was changed + # to use the DMI data. In order to prevent existing instances from thinking + # they are new instances, the instance ID needs to be updated here. + + if grep DataSourceAzure /var/lib/cloud/instance/datasource > /dev/null 2>&1; then + + product_id_f="/sys/devices/virtual/dmi/id/product_uuid" + instance_id_link="/var/lib/cloud/instance" + + if [ ! -e "${product_id_f}" -o ! -e "${instance_id_link}" ]; then + return 0 + fi + + # Get the current instance ID's (new and old) + new_instance_id="$(cat ${product_id_f})" + old_instance_id="$(basename "$(readlink -f "${instance_id_link}")")" + + if [ "${new_instance_id}" = "${old_instance_id}" ]; then + # this may have been applied for a prior version, i.e. upgrading + # from 14.04 to 16.04 + return 0 + + elif [ -z "${new_instance_id}" -o -z "${old_instance_id}" ]; then + cat < /var/lib/cloud/data/instance-id + + # Remove the symlink for the instance + rm /var/lib/cloud/instance + + # Rename the old instance id to the new one + mv /var/lib/cloud/instances/${old_instance_id} \ + /var/lib/cloud/instances/${new_instance_id} + + # Link the old id to the new one, just incase + ln -s /var/lib/cloud/instances/${new_instance_id} \ + /var/lib/cloud/instances/${old_instance_id} + + # Make the active instance the new id + ln -s /var/lib/cloud/instances/${new_instance_id} \ + /var/lib/cloud/instance + fi +fi +} + case "$1" in install|upgrade) # removing obsolete conffiles from the 'ec2-init' package @@ -160,6 +227,11 @@ ln -sf user-scripts /var/lib/cloud/instance/sem/config-scripts-user fi + # 0.6.3-0ubuntu1.25 introduced new instance ID source for Azure + if dpkg --compare-versions "$2" le "0.6.3-0ubuntu1.25"; then + azure_apply_new_instance_id_1506187 + fi + d=/etc/cloud/ if [ -f "$d/distro.cfg" ] && [ ! -f "$d/cloud.cfg.d/90_dpkg.cfg" ]; then echo "moving $d/distro.cfg -> $d/cloud.cfg.d/90_dpkg.cfg" diff -Nru cloud-init-0.6.3/debian/patches/lp-1506187-azure_use_unique_vm_id.patch cloud-init-0.6.3/debian/patches/lp-1506187-azure_use_unique_vm_id.patch --- cloud-init-0.6.3/debian/patches/lp-1506187-azure_use_unique_vm_id.patch 1970-01-01 01:00:00.000000000 +0100 +++ cloud-init-0.6.3/debian/patches/lp-1506187-azure_use_unique_vm_id.patch 2016-04-25 16:16:11.000000000 +0100 @@ -0,0 +1,241 @@ +Author: Daniel Watkins +Origin: upstream +Bug: https://launchpad.net/bugs/1506187 +Description: Handle new Azure instance IDs +--- a/cloudinit/DataSourceAzure.py ++++ b/cloudinit/DataSourceAzure.py +@@ -41,7 +41,6 @@ + AGENT_START = ['service', 'walinuxagent', 'start'] + BOUNCE_COMMAND = ['sh', '-xc', + "i=$interface; x=0; ifdown $i || x=$?; ifup $i || x=$?; exit $x"] +-DATA_DIR_CLEAN_LIST = ['SharedConfig.xml'] + + BUILTIN_DS_CONFIG = { + 'agent_command': AGENT_START, +@@ -139,21 +138,6 @@ + mycfg = self.ds_cfg + ddir = mycfg['data_dir'] + +- if found != ddir: +- cached_ovfenv = util.load_file( +- os.path.join(ddir, 'ovf-env.xml'), quiet=True) +- if cached_ovfenv != files['ovf-env.xml']: +- # source was not walinux-agent's datadir, so we have to clean +- # up so 'wait_for_files' doesn't return early due to stale data +- cleaned = [] +- for f in [os.path.join(ddir, f) for f in DATA_DIR_CLEAN_LIST]: +- if os.path.exists(f): +- futil.del_file(f) +- cleaned.append(f) +- if cleaned: +- LOG.info("removed stale file(s) in '%s': %s", +- ddir, str(cleaned)) +- + # walinux agent writes files world readable, but expects + # the directory to be protected. + write_files(ddir, files, dirmode=0700) +@@ -185,9 +169,6 @@ + LOG.warn("agent command '%s' failed.", mycfg['agent_command']) + util.logexc(LOG) + +- shcfgxml = os.path.join(mycfg['data_dir'], "SharedConfig.xml") +- wait_for = [shcfgxml] +- + fp_files = [] + key_value = None + for pk in self.cfg.get('_pubkeys', []): +@@ -200,24 +181,17 @@ + LOG.debug("ssh authentication: using fingerprint from fabric") + + start = time.time() +- missing = wait_for_files(wait_for + fp_files) ++ missing = wait_for_files(fp_files) + if len(missing): + LOG.warn("Did not find files, but going on: %s", missing) + else: +- LOG.debug("waited %.3f seconds for %d files to appear", +- time.time() - start, len(wait_for)) +- +- if shcfgxml in missing: +- LOG.warn("SharedConfig.xml missing, using static instance-id") +- else: +- try: +- self.metadata['instance-id'] = iid_from_shared_config(shcfgxml) +- except ValueError as e: +- LOG.warn("failed to get instance id in %s: %s" % (shcfgxml, e)) ++ LOG.debug("waited %.3f seconds for files to appear", ++ time.time() - start) + + pubkeys = key_value or pubkeys_from_crt_files(fp_files) + + self.metadata['public-keys'] = pubkeys ++ self.metadata['instance-id'] = get_instance_id() + + found_ephemeral = find_fabric_formatted_ephemeral_disk() + if found_ephemeral: +@@ -239,6 +213,14 @@ + def count_files(mp): + return len(fnmatch.filter(os.listdir(mp), '*[!cdrom]*')) + ++ ++def get_instance_id(): ++ """ ++ Read the instance ID from dmi data ++ """ ++ return util.read_dmi_data('system-uuid') ++ ++ + def find_fabric_formatted_ephemeral_part(): + """ + Locate the first fabric formatted ephemeral device. +@@ -683,25 +665,6 @@ + return (md, ud, cfg, {'ovf-env.xml': contents}) + + +-def iid_from_shared_config(path): +- with open(path, "rb") as fp: +- content = fp.read() +- return iid_from_shared_config_content(content) +- +- +-def iid_from_shared_config_content(content): +- """ +- find INSTANCE_ID in: +- +- +- +- +- """ +- dom = minidom.parseString(content) +- depnode = single_node_at_path(dom, ["SharedConfig", "Deployment"]) +- return depnode.attributes.get('name').value +- +- + class BrokenAzureDataSource(Exception): + pass + +--- a/cloudinit/util.py ++++ b/cloudinit/util.py +@@ -46,6 +46,31 @@ + except ImportError: + HAVE_LIBSELINUX = False + ++# Path for DMI Data ++DMI_SYS_PATH = "/sys/class/dmi/id" ++ ++# dmidecode and /sys/class/dmi/id/* use different names for the same value, ++# this allows us to refer to them by one canonical name ++DMIDECODE_TO_DMI_SYS_MAPPING = { ++ 'baseboard-asset-tag': 'board_asset_tag', ++ 'baseboard-manufacturer': 'board_vendor', ++ 'baseboard-product-name': 'board_name', ++ 'baseboard-serial-number': 'board_serial', ++ 'baseboard-version': 'board_version', ++ 'bios-release-date': 'bios_date', ++ 'bios-vendor': 'bios_vendor', ++ 'bios-version': 'bios_version', ++ 'chassis-asset-tag': 'chassis_asset_tag', ++ 'chassis-manufacturer': 'chassis_vendor', ++ 'chassis-serial-number': 'chassis_serial', ++ 'chassis-version': 'chassis_version', ++ 'system-manufacturer': 'sys_vendor', ++ 'system-product-name': 'product_name', ++ 'system-serial-number': 'product_serial', ++ 'system-uuid': 'product_uuid', ++ 'system-version': 'product_version', ++} ++ + _DNS_REDIRECT_IP = None + LOG = logging.getLogger("cloudinit") + +@@ -982,3 +1007,90 @@ + + return os.path.isfile("/sys/class/block/%s/partition" % device) + ++ ++def _read_dmi_syspath(key): ++ """ ++ Reads dmi data with from /sys/class/dmi/id ++ """ ++ if key not in DMIDECODE_TO_DMI_SYS_MAPPING: ++ return None ++ mapped_key = DMIDECODE_TO_DMI_SYS_MAPPING[key] ++ dmi_key_path = "{0}/{1}".format(DMI_SYS_PATH, mapped_key) ++ LOG.debug("querying dmi data %s", dmi_key_path) ++ try: ++ if not os.path.exists(dmi_key_path): ++ LOG.debug("did not find %s", dmi_key_path) ++ return None ++ ++ key_data = load_file(dmi_key_path) ++ if not key_data: ++ LOG.debug("%s did not return any data", dmi_key_path) ++ return None ++ ++ LOG.debug("dmi data %s returned %s", dmi_key_path, key_data) ++ return key_data.strip() ++ ++ except Exception: ++ LOG.warn("failed read of %s", dmi_key_path) ++ logexc(LOG) ++ return None ++ ++ ++def _call_dmidecode(key, dmidecode_path): ++ """ ++ Calls out to dmidecode to get the data out. This is mostly for supporting ++ OS's without /sys/class/dmi/id support. ++ """ ++ try: ++ cmd = [dmidecode_path, "--string", key] ++ (result, _err) = subp(cmd) ++ LOG.debug("dmidecode returned '%s' for '%s'", result, key) ++ return result ++ except (IOError, OSError) as _err: ++ LOG.debug('failed dmidecode cmd: %s\n%s', cmd, _err.message) ++ return None ++ ++ ++def which(program): ++ # Return path of program for execution if found in path ++ def is_exe(fpath): ++ return os.path.isfile(fpath) and os.access(fpath, os.X_OK) ++ ++ _fpath, _ = os.path.split(program) ++ if _fpath: ++ if is_exe(program): ++ return program ++ else: ++ for path in os.environ.get("PATH", "").split(os.pathsep): ++ path = path.strip('"') ++ exe_file = os.path.join(path, program) ++ if is_exe(exe_file): ++ return exe_file ++ ++ return None ++ ++ ++def read_dmi_data(key): ++ """ ++ Wrapper for reading DMI data. ++ ++ This will do the following (returning the first that produces a ++ result): ++ 1) Use a mapping to translate `key` from dmidecode naming to ++ sysfs naming and look in /sys/class/dmi/... for a value. ++ 2) Use `key` as a sysfs key directly and look in /sys/class/dmi/... ++ 3) Fall-back to passing `key` to `dmidecode --string`. ++ ++ If all of the above fail to find a value, None will be returned. ++ """ ++ syspath_value = _read_dmi_syspath(key) ++ if syspath_value is not None: ++ return syspath_value ++ ++ dmidecode_path = which('dmidecode') ++ if dmidecode_path: ++ return _call_dmidecode(key, dmidecode_path) ++ ++ LOG.warn("did not find either path %s or dmidecode command", ++ DMI_SYS_PATH) ++ return None diff -Nru cloud-init-0.6.3/debian/patches/series cloud-init-0.6.3/debian/patches/series --- cloud-init-0.6.3/debian/patches/series 2015-11-17 17:02:08.000000000 +0000 +++ cloud-init-0.6.3/debian/patches/series 2016-04-07 12:58:59.000000000 +0100 @@ -40,3 +40,4 @@ lp-1382481-cloudstack-vr.patch lp-1177432-same-archives-as-ubuntu-server.patch lp-1506244-azure-ssh-key-values.patch +lp-1506187-azure_use_unique_vm_id.patch