- Update to version 17.1

+ Version numbering scheme change now YY.NUMBER_OF_RELESE_THAT_YEAR
  + Remove cloud.cfg.suse, use generated default config file
  + Remove addopenSUSEBase.patch, included upstream
  + Remove suseIntegratedHandler.patch, included upstream
  + Remove openSUSEhostsTemplate.diff, included upstream
  + Remove cloud-init-handle-no-carrier.patch, included upstream
  + Remove cloud-init-digital-ocean-datasource.patch,
    use upstream implementation
  + Remove cloud-init-digital-ocean-datasource-enable-by-default.patch,
    use upstream implementation
  + Remove cloud-init-fix-unicode-handling-binarydecode.patch,
    included upstream
  + Remove cloud-init-no-dmidecode-on-ppc64.patch, included upstream
  + Remove dataSourceOpenNebula.patch, use upstream implementation
  + Remove setupSUSEsysVInit.diff, included upstream
  + Remove SUSEsysVInit.diff, included upstream
  + Remove cloud-init-finalbeforelogin.patch, don't block login 
  + Remove cloud-init-handle-not-implemented-query.patch, query option removed
  + Remove cloud-init-spceandtabs-clean.patch, indentation fixed upstream
  + Remove dynamicInitCmd.diff, different solution from upstream
  + Added cloud-init-more-tasks.patch, (bsc#1047363)
    replace cloud-init-finalbeforelogin.patch
  + Forward port cloud-init-python2-sigpipe.patch
  + Remove cloud-init-net-eni.patch, included upstream
  + Remove cloud-init-service.patch, included upstream
  + Forward port cloud-init-sysconfig-netpathfix.patch
  + Remove cloud-init-net-sysconfig-lp1665441.patch, included upstream
  + Remove cloud-init-python26.patch, included upstream
  + Add cloud-init-tests-set-exec.patch

OBS-URL: https://build.opensuse.org/package/show/Cloud:Tools/cloud-init?expand=0&rev=88
This commit is contained in:
Robert Schweikert 2017-10-04 15:50:46 +00:00 committed by Git OBS Bridge
parent 40312f95d8
commit 738f268769
33 changed files with 911 additions and 2155 deletions

View File

@ -1,26 +0,0 @@
Index: cloud-init-0.7.8/cloudinit/config/cc_resolv_conf.py
===================================================================
--- cloud-init-0.7.8.orig/cloudinit/config/cc_resolv_conf.py
+++ cloud-init-0.7.8/cloudinit/config/cc_resolv_conf.py
@@ -58,7 +58,7 @@ LOG = logging.getLogger(__name__)
frequency = PER_INSTANCE
-distros = ['fedora', 'rhel', 'sles']
+distros = ['fedora', 'opensuse', 'rhel', 'sles']
def generate_resolv_conf(template_fn, params, target_fname="/etc/resolv.conf"):
Index: cloud-init-0.7.8/cloudinit/distros/__init__.py
===================================================================
--- cloud-init-0.7.8.orig/cloudinit/distros/__init__.py
+++ cloud-init-0.7.8/cloudinit/distros/__init__.py
@@ -46,7 +46,7 @@ OSFAMILIES = {
'redhat': ['fedora', 'rhel'],
'gentoo': ['gentoo'],
'freebsd': ['freebsd'],
- 'suse': ['sles'],
+ 'suse': ['opensuse', 'sles'],
'arch': ['arch'],
}

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4a4f1f7fb9dd0987a02aa7cd6f609910294fce8f9724dcebc0cd88630b4f1fd6
size 508777

3
cloud-init-17.1.tar.gz Normal file
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:80f3bf5e8f57b67ac599aba2856568aeb30bd25187c7a363bed157a1e4d63e01
size 780532

View File

@ -0,0 +1,40 @@
--- config/cloud.cfg.tmpl.orig
+++ config/cloud.cfg.tmpl
@@ -79,6 +79,9 @@ cloud_config_modules:
- spacewalk
- yum-add-repo
{% endif %}
+{% if variant in ["opensuse", "suse"] %}
+ - zypper_add_repo
+{% endif %}
{% if variant in ["ubuntu", "unknown", "debian"] %}
- grub-dpkg
- apt-pipelining
@@ -127,7 +130,7 @@ cloud_final_modules:
# (not accessible to handlers/transforms)
system_info:
# This will affect which distro class gets used
-{% if variant in ["centos", "debian", "fedora", "rhel", "ubuntu", "freebsd"] %}
+{% if variant in ["centos", "debian", "fedora", "opensuse", "rhel", "suse", "ubuntu", "freebsd"] %}
distro: {{ variant }}
{% else %}
# Unknown/fallback distro.
@@ -186,4 +189,18 @@ system_info:
groups: [wheel]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/tcsh
+{% elif variant in ["opensuse", "suse"] %}
+ # Default user name + that default users groups (if added/used)
+ default_user:
+ name: {{ variant }}
+ lock_passwd: True
+ gecos: {{ variant }} Cloud User
+ groups: [cdrom, users]
+ sudo: ["ALL=(ALL) NOPASSWD:ALL"]
+ shell: /bin/bash
+ # Other config here will be given to the distro class and/or path classes
+ paths:
+ cloud_dir: /var/lib/cloud/
+ templates_dir: /etc/cloud/templates/
+ ssh_svcname: sshd
{% endif %}

View File

@ -1,23 +0,0 @@
From 7ae201166402fbf2e6c1632028be956a954835ef Mon Sep 17 00:00:00 2001
From: Scott Moser <smoser@brickies.net>
Date: Tue, 18 Oct 2016 12:30:38 -0400
Subject: DigitalOcean: enable usage of data source by default.
Just add DigitalOcean to the list of datasources that are used
if there is no 'datasource_list' provided in config.
diff --git a/cloudinit/settings.py b/cloudinit/settings.py
index 8c258ea..a968271 100644
--- a/cloudinit/settings.py
+++ b/cloudinit/settings.py
@@ -32,6 +32,7 @@ CFG_BUILTIN = {
'NoCloud',
'ConfigDrive',
'OpenNebula',
+ 'DigitalOcean',
'Azure',
'AltCloud',
'OVF',
--
cgit v0.10.2

View File

@ -1,782 +0,0 @@
From 9f83bb8e80806d3dd79ba426474dc3c696e19a41 Mon Sep 17 00:00:00 2001
From: Ben Howard <bh@digitalocean.com>
Date: Fri, 19 Aug 2016 16:28:26 -0600
Subject: DigitalOcean: use meta-data for network configruation
On DigitalOcean, Network information is provided via Meta-data.
It changes the datasource to be a local datasource, meaning it
will run before fallback networking is configured.
The advantage of that is that before networking is configured it
can bring up a network device with ipv4 link-local and hit the
metadata service that lives at 169.254.169.254 to find its networking
configuration. It then takes down the link local address and lets
cloud-init configure networking.
The configuring of a network device to go looking for a metadata
service is gated by a check of data in the smbios. This guarantees
that the code will not run on another system.
diff --git a/cloudinit/sources/DataSourceDigitalOcean.py b/cloudinit/sources/DataSourceDigitalOcean.py
index fc596e1..c5770d5 100644
--- a/cloudinit/sources/DataSourceDigitalOcean.py
+++ b/cloudinit/sources/DataSourceDigitalOcean.py
@@ -18,13 +18,12 @@
# DigitalOcean Droplet API:
# https://developers.digitalocean.com/documentation/metadata/
-import json
-
from cloudinit import log as logging
from cloudinit import sources
-from cloudinit import url_helper
from cloudinit import util
+import cloudinit.sources.helpers.digitalocean as do_helper
+
LOG = logging.getLogger(__name__)
BUILTIN_DS_CONFIG = {
@@ -36,11 +35,13 @@ BUILTIN_DS_CONFIG = {
MD_RETRIES = 30
MD_TIMEOUT = 2
MD_WAIT_RETRY = 2
+MD_USE_IPV4LL = True
class DataSourceDigitalOcean(sources.DataSource):
def __init__(self, sys_cfg, distro, paths):
sources.DataSource.__init__(self, sys_cfg, distro, paths)
+ self.distro = distro
self.metadata = dict()
self.ds_cfg = util.mergemanydict([
util.get_cfg_by_path(sys_cfg, ["datasource", "DigitalOcean"], {}),
@@ -48,80 +49,72 @@ class DataSourceDigitalOcean(sources.DataSource):
self.metadata_address = self.ds_cfg['metadata_url']
self.retries = self.ds_cfg.get('retries', MD_RETRIES)
self.timeout = self.ds_cfg.get('timeout', MD_TIMEOUT)
+ self.use_ip4LL = self.ds_cfg.get('use_ip4LL', MD_USE_IPV4LL)
self.wait_retry = self.ds_cfg.get('wait_retry', MD_WAIT_RETRY)
+ self._network_config = None
def _get_sysinfo(self):
- # DigitalOcean embeds vendor ID and instance/droplet_id in the
- # SMBIOS information
-
- LOG.debug("checking if instance is a DigitalOcean droplet")
-
- # Detect if we are on DigitalOcean and return the Droplet's ID
- vendor_name = util.read_dmi_data("system-manufacturer")
- if vendor_name != "DigitalOcean":
- return (False, None)
+ return do_helper.read_sysinfo()
- LOG.info("running on DigitalOcean")
-
- droplet_id = util.read_dmi_data("system-serial-number")
- if droplet_id:
- LOG.debug(("system identified via SMBIOS as DigitalOcean Droplet"
- "{}").format(droplet_id))
- else:
- LOG.critical(("system identified via SMBIOS as a DigitalOcean "
- "Droplet, but did not provide an ID. Please file a "
- "support ticket at: "
- "https://cloud.digitalocean.com/support/tickets/"
- "new"))
-
- return (True, droplet_id)
-
- def get_data(self, apply_filter=False):
+ def get_data(self):
(is_do, droplet_id) = self._get_sysinfo()
# only proceed if we know we are on DigitalOcean
if not is_do:
return False
- LOG.debug("reading metadata from {}".format(self.metadata_address))
- response = url_helper.readurl(self.metadata_address,
- timeout=self.timeout,
- sec_between=self.wait_retry,
- retries=self.retries)
+ LOG.info("Running on digital ocean. droplet_id=%s" % droplet_id)
- contents = util.decode_binary(response.contents)
- decoded = json.loads(contents)
+ ipv4LL_nic = None
+ if self.use_ip4LL:
+ ipv4LL_nic = do_helper.assign_ipv4_link_local()
- self.metadata = decoded
- self.metadata['instance-id'] = decoded.get('droplet_id', droplet_id)
- self.metadata['local-hostname'] = decoded.get('hostname', droplet_id)
- self.vendordata_raw = decoded.get("vendor_data", None)
- self.userdata_raw = decoded.get("user_data", None)
- return True
+ md = do_helper.read_metadata(
+ self.metadata_address, timeout=self.timeout,
+ sec_between=self.wait_retry, retries=self.retries)
- def get_public_ssh_keys(self):
- public_keys = self.metadata.get('public_keys', [])
- if isinstance(public_keys, list):
- return public_keys
- else:
- return [public_keys]
+ self.metadata_full = md
+ self.metadata['instance-id'] = md.get('droplet_id', droplet_id)
+ self.metadata['local-hostname'] = md.get('hostname', droplet_id)
+ self.metadata['interfaces'] = md.get('interfaces')
+ self.metadata['public-keys'] = md.get('public_keys')
+ self.metadata['availability_zone'] = md.get('region', 'default')
+ self.vendordata_raw = md.get("vendor_data", None)
+ self.userdata_raw = md.get("user_data", None)
- @property
- def availability_zone(self):
- return self.metadata.get('region', 'default')
+ if ipv4LL_nic:
+ do_helper.del_ipv4_link_local(ipv4LL_nic)
- @property
- def launch_index(self):
- return None
+ return True
def check_instance_id(self, sys_cfg):
return sources.instance_id_matches_system_uuid(
self.get_instance_id(), 'system-serial-number')
+ @property
+ def network_config(self):
+ """Configure the networking. This needs to be done each boot, since
+ the IP information may have changed due to snapshot and/or
+ migration.
+ """
+
+ if self._network_config:
+ return self._network_config
+
+ interfaces = self.metadata.get('interfaces')
+ LOG.debug(interfaces)
+ if not interfaces:
+ raise Exception("Unable to get meta-data from server....")
+
+ nameservers = self.metadata_full['dns']['nameservers']
+ self._network_config = do_helper.convert_network_configuration(
+ interfaces, nameservers)
+ return self._network_config
+
# Used to match classes to dependencies
datasources = [
- (DataSourceDigitalOcean, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
+ (DataSourceDigitalOcean, (sources.DEP_FILESYSTEM, )),
]
diff --git a/cloudinit/sources/helpers/digitalocean.py b/cloudinit/sources/helpers/digitalocean.py
new file mode 100644
index 0000000..b0a721c
--- /dev/null
+++ b/cloudinit/sources/helpers/digitalocean.py
@@ -0,0 +1,218 @@
+# vi: ts=4 expandtab
+#
+# Author: Ben Howard <bh@digitalocean.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 json
+import random
+
+from cloudinit import log as logging
+from cloudinit import net as cloudnet
+from cloudinit import url_helper
+from cloudinit import util
+
+NIC_MAP = {'public': 'eth0', 'private': 'eth1'}
+
+LOG = logging.getLogger(__name__)
+
+
+def assign_ipv4_link_local(nic=None):
+ """Bring up NIC using an address using link-local (ip4LL) IPs. On
+ DigitalOcean, the link-local domain is per-droplet routed, so there
+ is no risk of collisions. However, to be more safe, the ip4LL
+ address is random.
+ """
+
+ if not nic:
+ for cdev in sorted(cloudnet.get_devicelist()):
+ if cloudnet.is_physical(cdev):
+ nic = cdev
+ LOG.debug("assigned nic '%s' for link-local discovery", nic)
+ break
+
+ if not nic:
+ raise RuntimeError("unable to find interfaces to access the"
+ "meta-data server. This droplet is broken.")
+
+ addr = "169.254.{0}.{1}/16".format(random.randint(1, 168),
+ random.randint(0, 255))
+
+ ip_addr_cmd = ['ip', 'addr', 'add', addr, 'dev', nic]
+ ip_link_cmd = ['ip', 'link', 'set', 'dev', nic, 'up']
+
+ if not util.which('ip'):
+ raise RuntimeError("No 'ip' command available to configure ip4LL "
+ "address")
+
+ try:
+ (result, _err) = util.subp(ip_addr_cmd)
+ LOG.debug("assigned ip4LL address '%s' to '%s'", addr, nic)
+
+ (result, _err) = util.subp(ip_link_cmd)
+ LOG.debug("brought device '%s' up", nic)
+ except Exception:
+ util.logexc(LOG, "ip4LL address assignment of '%s' to '%s' failed."
+ " Droplet networking will be broken", addr, nic)
+ raise
+
+ return nic
+
+
+def del_ipv4_link_local(nic=None):
+ """Remove the ip4LL address. While this is not necessary, the ip4LL
+ address is extraneous and confusing to users.
+ """
+ if not nic:
+ LOG.debug("no link_local address interface defined, skipping link "
+ "local address cleanup")
+ return
+
+ LOG.debug("cleaning up ipv4LL address")
+
+ ip_addr_cmd = ['ip', 'addr', 'flush', 'dev', nic]
+
+ try:
+ (result, _err) = util.subp(ip_addr_cmd)
+ LOG.debug("removed ip4LL addresses from %s", nic)
+
+ except Exception as e:
+ util.logexc(LOG, "failed to remove ip4LL address from '%s'.", nic, e)
+
+
+def convert_network_configuration(config, dns_servers):
+ """Convert the DigitalOcean Network description into Cloud-init's netconfig
+ format.
+
+ Example JSON:
+ {'public': [
+ {'mac': '04:01:58:27:7f:01',
+ 'ipv4': {'gateway': '45.55.32.1',
+ 'netmask': '255.255.224.0',
+ 'ip_address': '45.55.50.93'},
+ 'anchor_ipv4': {
+ 'gateway': '10.17.0.1',
+ 'netmask': '255.255.0.0',
+ 'ip_address': '10.17.0.9'},
+ 'type': 'public',
+ 'ipv6': {'gateway': '....',
+ 'ip_address': '....',
+ 'cidr': 64}}
+ ],
+ 'private': [
+ {'mac': '04:01:58:27:7f:02',
+ 'ipv4': {'gateway': '10.132.0.1',
+ 'netmask': '255.255.0.0',
+ 'ip_address': '10.132.75.35'},
+ 'type': 'private'}
+ ]
+ }
+ """
+
+ def _get_subnet_part(pcfg, nameservers=None):
+ subpart = {'type': 'static',
+ 'control': 'auto',
+ 'address': pcfg.get('ip_address'),
+ 'gateway': pcfg.get('gateway')}
+
+ if nameservers:
+ subpart['dns_nameservers'] = nameservers
+
+ if ":" in pcfg.get('ip_address'):
+ subpart['address'] = "{0}/{1}".format(pcfg.get('ip_address'),
+ pcfg.get('cidr'))
+ else:
+ subpart['netmask'] = pcfg.get('netmask')
+
+ return subpart
+
+ all_nics = []
+ for k in ('public', 'private'):
+ if k in config:
+ all_nics.extend(config[k])
+
+ macs_to_nics = cloudnet.get_interfaces_by_mac()
+ nic_configs = []
+
+ for nic in all_nics:
+
+ mac_address = nic.get('mac')
+ sysfs_name = macs_to_nics.get(mac_address)
+ nic_type = nic.get('type', 'unknown')
+ # Note: the entry 'public' above contains a list, but
+ # the list will only ever have one nic inside it per digital ocean.
+ # If it ever had more than one nic, then this code would
+ # assign all 'public' the same name.
+ if_name = NIC_MAP.get(nic_type, sysfs_name)
+
+ LOG.debug("mapped %s interface to %s, assigning name of %s",
+ mac_address, sysfs_name, if_name)
+
+ ncfg = {'type': 'physical',
+ 'mac_address': mac_address,
+ 'name': if_name}
+
+ subnets = []
+ for netdef in ('ipv4', 'ipv6', 'anchor_ipv4', 'anchor_ipv6'):
+ raw_subnet = nic.get(netdef, None)
+ if not raw_subnet:
+ continue
+
+ sub_part = _get_subnet_part(raw_subnet)
+ if nic_type == 'public' and 'anchor' not in netdef:
+ # add DNS resolvers to the public interfaces only
+ sub_part = _get_subnet_part(raw_subnet, dns_servers)
+ else:
+ # remove the gateway any non-public interfaces
+ if 'gateway' in sub_part:
+ del sub_part['gateway']
+
+ subnets.append(sub_part)
+
+ ncfg['subnets'] = subnets
+ nic_configs.append(ncfg)
+ LOG.debug("nic '%s' configuration: %s", if_name, ncfg)
+
+ return {'version': 1, 'config': nic_configs}
+
+
+def read_metadata(url, timeout=2, sec_between=2, retries=30):
+ response = url_helper.readurl(url, timeout=timeout,
+ sec_between=sec_between, retries=retries)
+ if not response.ok():
+ raise RuntimeError("unable to read metadata at %s" % url)
+ return json.loads(response.contents.decode())
+
+
+def read_sysinfo():
+ # DigitalOcean embeds vendor ID and instance/droplet_id in the
+ # SMBIOS information
+
+ # Detect if we are on DigitalOcean and return the Droplet's ID
+ vendor_name = util.read_dmi_data("system-manufacturer")
+ if vendor_name != "DigitalOcean":
+ return (False, None)
+
+ droplet_id = util.read_dmi_data("system-serial-number")
+ if droplet_id:
+ LOG.debug("system identified via SMBIOS as DigitalOcean Droplet: %s",
+ droplet_id)
+ else:
+ msg = ("system identified via SMBIOS as a DigitalOcean "
+ "Droplet, but did not provide an ID. Please file a "
+ "support ticket at: "
+ "https://cloud.digitalocean.com/support/tickets/new")
+ LOG.critical(msg)
+ raise RuntimeError(msg)
+
+ return (True, droplet_id)
diff --git a/tests/unittests/test_datasource/test_digitalocean.py b/tests/unittests/test_datasource/test_digitalocean.py
index f5d2ef3..bdfe0ba 100644
--- a/tests/unittests/test_datasource/test_digitalocean.py
+++ b/tests/unittests/test_datasource/test_digitalocean.py
@@ -20,25 +20,123 @@ import json
from cloudinit import helpers
from cloudinit import settings
from cloudinit.sources import DataSourceDigitalOcean
+from cloudinit.sources.helpers import digitalocean
-from .. import helpers as test_helpers
-from ..helpers import HttprettyTestCase
-
-httpretty = test_helpers.import_httpretty()
+from ..helpers import mock, TestCase
DO_MULTIPLE_KEYS = ["ssh-rsa AAAAB3NzaC1yc2EAAAA... test1@do.co",
"ssh-rsa AAAAB3NzaC1yc2EAAAA... test2@do.co"]
DO_SINGLE_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAA... test@do.co"
-DO_META = {
- 'user_data': 'user_data_here',
- 'vendor_data': 'vendor_data_here',
- 'public_keys': DO_SINGLE_KEY,
- 'region': 'nyc3',
- 'id': '2000000',
- 'hostname': 'cloudinit-test',
+# the following JSON was taken from droplet (that's why its a string)
+DO_META = json.loads("""
+{
+ "droplet_id": "22532410",
+ "hostname": "utl-96268",
+ "vendor_data": "vendordata goes here",
+ "user_data": "userdata goes here",
+ "public_keys": "",
+ "auth_key": "authorization_key",
+ "region": "nyc3",
+ "interfaces": {
+ "private": [
+ {
+ "ipv4": {
+ "ip_address": "10.132.6.205",
+ "netmask": "255.255.0.0",
+ "gateway": "10.132.0.1"
+ },
+ "mac": "04:01:57:d1:9e:02",
+ "type": "private"
+ }
+ ],
+ "public": [
+ {
+ "ipv4": {
+ "ip_address": "192.0.0.20",
+ "netmask": "255.255.255.0",
+ "gateway": "104.236.0.1"
+ },
+ "ipv6": {
+ "ip_address": "2604:A880:0800:0000:1000:0000:0000:0000",
+ "cidr": 64,
+ "gateway": "2604:A880:0800:0000:0000:0000:0000:0001"
+ },
+ "anchor_ipv4": {
+ "ip_address": "10.0.0.5",
+ "netmask": "255.255.0.0",
+ "gateway": "10.0.0.1"
+ },
+ "mac": "04:01:57:d1:9e:01",
+ "type": "public"
+ }
+ ]
+ },
+ "floating_ip": {
+ "ipv4": {
+ "active": false
+ }
+ },
+ "dns": {
+ "nameservers": [
+ "2001:4860:4860::8844",
+ "2001:4860:4860::8888",
+ "8.8.8.8"
+ ]
+ }
+}
+""")
+
+# This has no private interface
+DO_META_2 = {
+ "droplet_id": 27223699,
+ "hostname": "smtest1",
+ "vendor_data": "\n".join([
+ ('"Content-Type: multipart/mixed; '
+ 'boundary=\"===============8645434374073493512==\"'),
+ 'MIME-Version: 1.0',
+ '',
+ '--===============8645434374073493512==',
+ 'MIME-Version: 1.0'
+ 'Content-Type: text/cloud-config; charset="us-ascii"'
+ 'Content-Transfer-Encoding: 7bit'
+ 'Content-Disposition: attachment; filename="cloud-config"'
+ '',
+ '#cloud-config',
+ 'disable_root: false',
+ 'manage_etc_hosts: true',
+ '',
+ '',
+ '--===============8645434374073493512=='
+ ]),
+ "public_keys": [
+ "ssh-rsa AAAAB3NzaN...N3NtHw== smoser@brickies"
+ ],
+ "auth_key": "88888888888888888888888888888888",
+ "region": "nyc3",
+ "interfaces": {
+ "public": [{
+ "ipv4": {
+ "ip_address": "45.55.249.133",
+ "netmask": "255.255.192.0",
+ "gateway": "45.55.192.1"
+ },
+ "anchor_ipv4": {
+ "ip_address": "10.17.0.5",
+ "netmask": "255.255.0.0",
+ "gateway": "10.17.0.1"
+ },
+ "mac": "ae:cc:08:7c:88:00",
+ "type": "public"
+ }]
+ },
+ "floating_ip": {"ipv4": {"active": True, "ip_address": "138.197.59.92"}},
+ "dns": {"nameservers": ["8.8.8.8", "8.8.4.4"]},
+ "tags": None,
}
+DO_META['public_keys'] = DO_SINGLE_KEY
+
MD_URL = 'http://169.254.169.254/metadata/v1.json'
@@ -46,69 +144,189 @@ def _mock_dmi():
return (True, DO_META.get('id'))
-def _request_callback(method, uri, headers):
- return (200, headers, json.dumps(DO_META))
-
-
-class TestDataSourceDigitalOcean(HttprettyTestCase):
+class TestDataSourceDigitalOcean(TestCase):
"""
Test reading the meta-data
"""
- def setUp(self):
- self.ds = DataSourceDigitalOcean.DataSourceDigitalOcean(
- settings.CFG_BUILTIN, None,
- helpers.Paths({}))
- self.ds._get_sysinfo = _mock_dmi
- super(TestDataSourceDigitalOcean, self).setUp()
-
- @httpretty.activate
- def test_connection(self):
- httpretty.register_uri(
- httpretty.GET, MD_URL,
- body=json.dumps(DO_META))
-
- success = self.ds.get_data()
- self.assertTrue(success)
-
- @httpretty.activate
- def test_metadata(self):
- httpretty.register_uri(
- httpretty.GET, MD_URL,
- body=_request_callback)
- self.ds.get_data()
+ def get_ds(self, get_sysinfo=_mock_dmi):
+ ds = DataSourceDigitalOcean.DataSourceDigitalOcean(
+ settings.CFG_BUILTIN, None, helpers.Paths({}))
+ ds.use_ip4LL = False
+ if get_sysinfo is not None:
+ ds._get_sysinfo = get_sysinfo
+ return ds
- self.assertEqual(DO_META.get('user_data'),
- self.ds.get_userdata_raw())
+ @mock.patch('cloudinit.sources.helpers.digitalocean.read_sysinfo')
+ def test_returns_false_not_on_docean(self, m_read_sysinfo):
+ m_read_sysinfo.return_value = (False, None)
+ ds = self.get_ds(get_sysinfo=None)
+ self.assertEqual(False, ds.get_data())
+ m_read_sysinfo.assert_called()
- self.assertEqual(DO_META.get('vendor_data'),
- self.ds.get_vendordata_raw())
+ @mock.patch('cloudinit.sources.helpers.digitalocean.read_metadata')
+ def test_metadata(self, mock_readmd):
+ mock_readmd.return_value = DO_META.copy()
- self.assertEqual(DO_META.get('region'),
- self.ds.availability_zone)
+ ds = self.get_ds()
+ ret = ds.get_data()
+ self.assertTrue(ret)
- self.assertEqual(DO_META.get('id'),
- self.ds.get_instance_id())
+ mock_readmd.assert_called()
- self.assertEqual(DO_META.get('hostname'),
- self.ds.get_hostname())
+ self.assertEqual(DO_META.get('user_data'), ds.get_userdata_raw())
+ self.assertEqual(DO_META.get('vendor_data'), ds.get_vendordata_raw())
+ self.assertEqual(DO_META.get('region'), ds.availability_zone)
+ self.assertEqual(DO_META.get('droplet_id'), ds.get_instance_id())
+ self.assertEqual(DO_META.get('hostname'), ds.get_hostname())
# Single key
self.assertEqual([DO_META.get('public_keys')],
- self.ds.get_public_ssh_keys())
+ ds.get_public_ssh_keys())
- self.assertIsInstance(self.ds.get_public_ssh_keys(), list)
+ self.assertIsInstance(ds.get_public_ssh_keys(), list)
- @httpretty.activate
- def test_multiple_ssh_keys(self):
- DO_META['public_keys'] = DO_MULTIPLE_KEYS
- httpretty.register_uri(
- httpretty.GET, MD_URL,
- body=_request_callback)
- self.ds.get_data()
+ @mock.patch('cloudinit.sources.helpers.digitalocean.read_metadata')
+ def test_multiple_ssh_keys(self, mock_readmd):
+ metadata = DO_META.copy()
+ metadata['public_keys'] = DO_MULTIPLE_KEYS
+ mock_readmd.return_value = metadata.copy()
+
+ ds = self.get_ds()
+ ret = ds.get_data()
+ self.assertTrue(ret)
+
+ mock_readmd.assert_called()
# Multiple keys
- self.assertEqual(DO_META.get('public_keys'),
- self.ds.get_public_ssh_keys())
+ self.assertEqual(metadata['public_keys'], ds.get_public_ssh_keys())
+ self.assertIsInstance(ds.get_public_ssh_keys(), list)
+
+
+class TestNetworkConvert(TestCase):
+
+ def _get_networking(self):
+ netcfg = digitalocean.convert_network_configuration(
+ DO_META['interfaces'], DO_META['dns']['nameservers'])
+ self.assertIn('config', netcfg)
+ return netcfg
+
+ def test_networking_defined(self):
+ netcfg = self._get_networking()
+ self.assertIsNotNone(netcfg)
+
+ for nic_def in netcfg.get('config'):
+ print(json.dumps(nic_def, indent=3))
+ n_type = nic_def.get('type')
+ n_subnets = nic_def.get('type')
+ n_name = nic_def.get('name')
+ n_mac = nic_def.get('mac_address')
+
+ self.assertIsNotNone(n_type)
+ self.assertIsNotNone(n_subnets)
+ self.assertIsNotNone(n_name)
+ self.assertIsNotNone(n_mac)
+
+ def _get_nic_definition(self, int_type, expected_name):
+ """helper function to return if_type (i.e. public) and the expected
+ name used by cloud-init (i.e eth0)"""
+ netcfg = self._get_networking()
+ meta_def = (DO_META.get('interfaces')).get(int_type)[0]
+
+ self.assertEqual(int_type, meta_def.get('type'))
+
+ for nic_def in netcfg.get('config'):
+ print(nic_def)
+ if nic_def.get('name') == expected_name:
+ return nic_def, meta_def
+
+ def _get_match_subn(self, subnets, ip_addr):
+ """get the matching subnet definition based on ip address"""
+ for subn in subnets:
+ address = subn.get('address')
+ self.assertIsNotNone(address)
+
+ # equals won't work because of ipv6 addressing being in
+ # cidr notation, i.e fe00::1/64
+ if ip_addr in address:
+ print(json.dumps(subn, indent=3))
+ return subn
+
+ def test_public_interface_defined(self):
+ """test that the public interface is defined as eth0"""
+ (nic_def, meta_def) = self._get_nic_definition('public', 'eth0')
+ self.assertEqual('eth0', nic_def.get('name'))
+ self.assertEqual(meta_def.get('mac'), nic_def.get('mac_address'))
+ self.assertEqual('physical', nic_def.get('type'))
+
+ def test_private_interface_defined(self):
+ """test that the private interface is defined as eth1"""
+ (nic_def, meta_def) = self._get_nic_definition('private', 'eth1')
+ self.assertEqual('eth1', nic_def.get('name'))
+ self.assertEqual(meta_def.get('mac'), nic_def.get('mac_address'))
+ self.assertEqual('physical', nic_def.get('type'))
+
+ def _check_dns_nameservers(self, subn_def):
+ self.assertIn('dns_nameservers', subn_def)
+ expected_nameservers = DO_META['dns']['nameservers']
+ nic_nameservers = subn_def.get('dns_nameservers')
+ self.assertEqual(expected_nameservers, nic_nameservers)
+
+ def test_public_interface_ipv6(self):
+ """test public ipv6 addressing"""
+ (nic_def, meta_def) = self._get_nic_definition('public', 'eth0')
+ ipv6_def = meta_def.get('ipv6')
+ self.assertIsNotNone(ipv6_def)
+
+ subn_def = self._get_match_subn(nic_def.get('subnets'),
+ ipv6_def.get('ip_address'))
+
+ cidr_notated_address = "{0}/{1}".format(ipv6_def.get('ip_address'),
+ ipv6_def.get('cidr'))
+
+ self.assertEqual(cidr_notated_address, subn_def.get('address'))
+ self.assertEqual(ipv6_def.get('gateway'), subn_def.get('gateway'))
+ self._check_dns_nameservers(subn_def)
+
+ def test_public_interface_ipv4(self):
+ """test public ipv4 addressing"""
+ (nic_def, meta_def) = self._get_nic_definition('public', 'eth0')
+ ipv4_def = meta_def.get('ipv4')
+ self.assertIsNotNone(ipv4_def)
+
+ subn_def = self._get_match_subn(nic_def.get('subnets'),
+ ipv4_def.get('ip_address'))
+
+ self.assertEqual(ipv4_def.get('netmask'), subn_def.get('netmask'))
+ self.assertEqual(ipv4_def.get('gateway'), subn_def.get('gateway'))
+ self._check_dns_nameservers(subn_def)
+
+ def test_public_interface_anchor_ipv4(self):
+ """test public ipv4 addressing"""
+ (nic_def, meta_def) = self._get_nic_definition('public', 'eth0')
+ ipv4_def = meta_def.get('anchor_ipv4')
+ self.assertIsNotNone(ipv4_def)
+
+ subn_def = self._get_match_subn(nic_def.get('subnets'),
+ ipv4_def.get('ip_address'))
+
+ self.assertEqual(ipv4_def.get('netmask'), subn_def.get('netmask'))
+ self.assertNotIn('gateway', subn_def)
+
+ def test_convert_without_private(self):
+ netcfg = digitalocean.convert_network_configuration(
+ DO_META_2['interfaces'], DO_META_2['dns']['nameservers'])
- self.assertIsInstance(self.ds.get_public_ssh_keys(), list)
+ byname = {}
+ for i in netcfg['config']:
+ if 'name' in i:
+ if i['name'] in byname:
+ raise ValueError("name '%s' in config twice: %s" %
+ (i['name'], netcfg))
+ byname[i['name']] = i
+ self.assertTrue('eth0' in byname)
+ self.assertTrue('subnets' in byname['eth0'])
+ eth0 = byname['eth0']
+ self.assertEqual(
+ sorted(['45.55.249.133', '10.17.0.5']),
+ sorted([i['address'] for i in eth0['subnets']]))
--
cgit v0.10.2

View File

@ -0,0 +1,15 @@
Index: cloud-init-17.1/systemd/cloud-final.service.tmpl
===================================================================
--- cloud-init-17.1.orig/systemd/cloud-final.service.tmpl
+++ cloud-init-17.1/systemd/cloud-final.service.tmpl
@@ -4,9 +4,9 @@ Description=Execute cloud user/final scr
After=network-online.target cloud-config.service rc-local.service
{% if variant in ["ubuntu", "unknown", "debian"] %}
After=multi-user.target
+Before=apt-daily.service
{% endif %}
Wants=network-online.target cloud-config.service
-Before=apt-daily.service
[Service]
Type=oneshot

View File

@ -1,20 +0,0 @@
Index: systemd/cloud-final.service
===================================================================
--- systemd/cloud-final.service.orig
+++ systemd/cloud-final.service
@@ -1,6 +1,7 @@
[Unit]
Description=Execute cloud user/final scripts
After=network-online.target cloud-config.service rc-local.service multi-user.target
+Before=systemd-logind.service
Wants=network-online.target cloud-config.service
[Service]
@@ -9,6 +10,7 @@ ExecStart=/usr/bin/cloud-init modules --
RemainAfterExit=yes
TimeoutSec=0
KillMode=process
+TasksMax=infinity
# Output needs to appear in instance console output
StandardOutput=journal+console

View File

@ -1,13 +0,0 @@
Index: cloudinit/util.py
===================================================================
--- cloudinit/util.py.orig
+++ cloudinit/util.py
@@ -155,7 +155,7 @@ def target_path(target, path=None):
def decode_binary(blob, encoding='utf-8'):
# Converts a binary type into a text type using given encoding.
- if isinstance(blob, six.text_type):
+ if isinstance(blob, six.string_types):
return blob
return blob.decode(encoding)

View File

@ -1,210 +0,0 @@
--- cloudinit/net/__init__.py.orig
+++ cloudinit/net/__init__.py
@@ -33,10 +33,12 @@ def sys_dev_path(devname, path=""):
def read_sys_net(devname, path, translate=None, enoent=None, keyerror=None):
+ dev_path = sys_dev_path(devname, path)
try:
- contents = util.load_file(sys_dev_path(devname, path))
+ contents = util.load_file(dev_path)
except (OSError, IOError) as e:
- if getattr(e, 'errno', None) in (errno.ENOENT, errno.ENOTDIR):
+ e_errno = getattr(e, 'errno', None)
+ if e_errno in (errno.ENOENT, errno.ENOTDIR):
if enoent is not None:
return enoent
raise
@@ -109,24 +111,9 @@ def is_disabled_cfg(cfg):
return cfg.get('config') == "disabled"
-def sys_netdev_info(name, field):
- if not os.path.exists(os.path.join(SYS_CLASS_NET, name)):
- raise OSError("%s: interface does not exist in %s" %
- (name, SYS_CLASS_NET))
- fname = os.path.join(SYS_CLASS_NET, name, field)
- if not os.path.exists(fname):
- raise OSError("%s: could not find sysfs entry: %s" % (name, fname))
- data = util.load_file(fname)
- if data[-1] == '\n':
- data = data[:-1]
- return data
-
-
def generate_fallback_config():
"""Determine which attached net dev is most likely to have a connection and
generate network state to run dhcp on that interface"""
- # by default use eth0 as primary interface
- nconf = {'config': [], 'version': 1}
# get list of interfaces that could have connections
invalid_interfaces = set(['lo'])
@@ -143,28 +130,30 @@ def generate_fallback_config():
# skip any bridges
continue
try:
- carrier = int(sys_netdev_info(interface, 'carrier'))
+ carrier = read_sys_net(interface, 'carrier', enoent=False)
if carrier:
+ carrier = int(carrier)
connected.append(interface)
continue
- except OSError:
+ except (IOError, OSError, TypeError):
pass
# check if nic is dormant or down, as this may make a nick appear to
# not have a carrier even though it could acquire one when brought
# online by dhclient
try:
- dormant = int(sys_netdev_info(interface, 'dormant'))
+ dormant = read_sys_net(interface, 'dormant', enoent=False)
if dormant:
+ domant = int(dormant)
possibly_connected.append(interface)
continue
- except OSError:
+ except (IOError, OSError, TypeError):
pass
try:
- operstate = sys_netdev_info(interface, 'operstate')
+ operstate = read_sys_net(interface, 'operstate', enoent=False)
if operstate in ['dormant', 'down', 'lowerlayerdown', 'unknown']:
possibly_connected.append(interface)
continue
- except OSError:
+ except (IOError, OSError):
pass
# don't bother with interfaces that might not be connected if there are
@@ -173,23 +162,29 @@ def generate_fallback_config():
potential_interfaces = connected
else:
potential_interfaces = possibly_connected
- # if there are no interfaces, give up
- if not potential_interfaces:
- return
+
# if eth0 exists use it above anything else, otherwise get the interface
- # that looks 'first'
- if DEFAULT_PRIMARY_INTERFACE in potential_interfaces:
- name = DEFAULT_PRIMARY_INTERFACE
+ # that we can read 'first' (using the sorted defintion of first).
+ names = [DEFAULT_PRIMARY_INTERFACE]
+ names.extend(sorted(potential_interfaces))
+ target_name = None
+ target_mac = None
+ for name in names:
+ if name not in potential_interfaces:
+ continue
+ mac = read_sys_net(name, 'address', enoent=False)
+ if mac:
+ target_name = name
+ target_mac = mac
+ break
+ if target_mac and target_name:
+ nconf = {'config': [], 'version': 1}
+ nconf['config'].append(
+ {'type': 'physical', 'name': target_name,
+ 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]})
+ return nconf
else:
- name = sorted(potential_interfaces)[0]
-
- mac = sys_netdev_info(name, 'address')
- target_name = name
-
- nconf['config'].append(
- {'type': 'physical', 'name': target_name,
- 'mac_address': mac, 'subnets': [{'type': 'dhcp'}]})
- return nconf
+ return None
def apply_network_config_names(netcfg, strict_present=True, strict_busy=True):
--- cloudinit/net/cmdline.py.orig
+++ cloudinit/net/cmdline.py
@@ -26,7 +26,7 @@ import sys
import six
from . import get_devicelist
-from . import sys_netdev_info
+from . import read_sys_net
from cloudinit import util
@@ -197,7 +197,10 @@ def read_kernel_cmdline_config(files=Non
return None
if mac_addrs is None:
- mac_addrs = dict((k, sys_netdev_info(k, 'address'))
- for k in get_devicelist())
+ mac_addrs = {}
+ for k in get_devicelist():
+ mac_addr = read_sys_net(k, 'address', enoent=False)
+ if mac_addr:
+ mac_addrs[k] = mac_addr
return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs)
--- tests/unittests/test_net.py.orig
+++ tests/unittests/test_net.py
@@ -422,7 +422,7 @@ pre-down route del -net 10.0.0.0 netmask
}
-def _setup_test(tmp_dir, mock_get_devicelist, mock_sys_netdev_info,
+def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net,
mock_sys_dev_path):
mock_get_devicelist.return_value = ['eth1000']
dev_characteristics = {
@@ -435,10 +435,10 @@ def _setup_test(tmp_dir, mock_get_device
}
}
- def netdev_info(name, field):
+ def fake_read(devname, path, translate=None, enoent=None, keyerror=None):
return dev_characteristics[name][field]
- mock_sys_netdev_info.side_effect = netdev_info
+ mock_read_sys_net.side_effect = fake_read
def sys_dev_path(devname, path=""):
return tmp_dir + devname + "/" + path
@@ -454,15 +454,15 @@ def _setup_test(tmp_dir, mock_get_device
class TestSysConfigRendering(TestCase):
@mock.patch("cloudinit.net.sys_dev_path")
- @mock.patch("cloudinit.net.sys_netdev_info")
+ @mock.patch("cloudinit.net.read_sys_net")
@mock.patch("cloudinit.net.get_devicelist")
def test_default_generation(self, mock_get_devicelist,
- mock_sys_netdev_info,
+ mock_read_sys_net,
mock_sys_dev_path):
tmp_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, tmp_dir)
_setup_test(tmp_dir, mock_get_devicelist,
- mock_sys_netdev_info, mock_sys_dev_path)
+ mock_read_sys_net, mock_sys_dev_path)
network_cfg = net.generate_fallback_config()
ns = network_state.parse_net_config_data(network_cfg,
@@ -511,15 +511,15 @@ USERCTL=no
class TestEniNetRendering(TestCase):
@mock.patch("cloudinit.net.sys_dev_path")
- @mock.patch("cloudinit.net.sys_netdev_info")
+ @mock.patch("cloudinit.net.read_sys_net")
@mock.patch("cloudinit.net.get_devicelist")
def test_default_generation(self, mock_get_devicelist,
- mock_sys_netdev_info,
+ mock_read_sys_net,
mock_sys_dev_path):
tmp_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, tmp_dir)
_setup_test(tmp_dir, mock_get_devicelist,
- mock_sys_netdev_info, mock_sys_dev_path)
+ mock_read_sys_net, mock_sys_dev_path)
network_cfg = net.generate_fallback_config()
ns = network_state.parse_net_config_data(network_cfg,

View File

@ -1,13 +0,0 @@
--- cloudinit/cmd/main.py.orig
+++ cloudinit/cmd/main.py
@@ -681,6 +681,10 @@ def main(sysv_args=None):
rname, rdesc = ("dhclient-hook",
"running dhclient-hook module")
+ elif name == 'query':
+ print('Action query is not yet implemented')
+ sys.exit(1)
+
args.reporter = events.ReportEventStack(
rname, rdesc, reporting_enabled=report_on)

View File

@ -0,0 +1,10 @@
--- systemd/cloud-final.service.tmpl.orig
+++ systemd/cloud-final.service.tmpl
@@ -14,6 +14,7 @@ ExecStart=/usr/bin/cloud-init modules --
RemainAfterExit=yes
TimeoutSec=0
KillMode=process
+TasksMax=infinity
# Output needs to appear in instance console output
StandardOutput=journal+console

View File

@ -1,18 +0,0 @@
--- cloudinit/net/eni.py.orig
+++ cloudinit/net/eni.py
@@ -338,6 +338,7 @@ class Renderer(renderer.Renderer):
up = indent + "post-up route add"
down = indent + "pre-down route del"
or_true = " || true"
+ gateway = indent + "gateway "
mapping = {
'network': '-net',
'netmask': 'netmask',
@@ -346,6 +347,7 @@ class Renderer(renderer.Renderer):
}
if route['network'] == '0.0.0.0' and route['netmask'] == '0.0.0.0':
default_gw = " default gw %s" % route['gateway']
+ content.append(gateway + route['gateway'])
content.append(up + default_gw + or_true)
content.append(down + default_gw + or_true)
elif route['network'] == '::' and route['netmask'] == 0:

View File

@ -1,15 +0,0 @@
--- cloud-init-0.7.8.orig/cloudinit/net/sysconfig.py
+++ cloud-init-0.7.8/cloudinit/net/sysconfig.py
@@ -295,10 +295,10 @@ class Renderer(renderer.Renderer):
cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0])
elif len(iface_subnets) > 1:
for i, iface_subnet in enumerate(iface_subnets,
- start=len(iface.children)):
+ start=len(iface_cfg.children)):
iface_sub_cfg = iface_cfg.copy()
iface_sub_cfg.name = "%s:%s" % (iface_name, i)
- iface.children.append(iface_sub_cfg)
+ iface_cfg.children.append(iface_sub_cfg)
cls._render_subnet(iface_sub_cfg, route_cfg, iface_subnet)
@classmethod

View File

@ -1,22 +0,0 @@
--- cloudinit/util.py.orig
+++ cloudinit/util.py
@@ -2337,7 +2337,7 @@ def read_dmi_data(key):
# running dmidecode can be problematic on some arches (LP: #1243287)
uname_arch = os.uname()[4]
- if uname_arch.startswith("arm") or uname_arch == "aarch64":
+ if uname_arch.startswith("arm") or uname_arch.startswith("ppc"):
LOG.debug("dmidata is not supported on %s", uname_arch)
return None
--- tests/unittests/test_util.py.orig
+++ tests/unittests/test_util.py
@@ -384,7 +384,7 @@ class TestReadDMIData(helpers.Filesystem
dmi_name = 'use-dmidecode'
self._configure_dmidecode_return(dmi_name, dmi_val)
- expected = {'armel': None, 'aarch64': None, 'x86_64': dmi_val}
+ expected = {'armel': None, 'aarch64': dmi_val, 'x86_64': dmi_val}
found = {}
# we do not run the 'dmi-decode' binary on some arches
# verify that anything requested that is not in the sysfs dir

View File

@ -1,20 +1,20 @@
--- cloudinit/util.py.orig
+++ cloudinit/util.py
@@ -46,6 +46,7 @@ import tempfile
import time
@@ -35,6 +35,7 @@ import time
from errno import ENOENT, ENOEXEC
from base64 import b64decode, b64encode
+from signal import signal, SIGPIPE, SIG_DFL
from six.moves.urllib import parse as urlparse
import six
@@ -1802,7 +1803,8 @@ def subp(args, data=None, rcs=None, env=
@@ -1815,7 +1816,8 @@ def subp(args, data=None, rcs=None, env=
sp = subprocess.Popen(args, stdout=stdout,
stderr=stderr, stdin=stdin,
- env=env, shell=shell)
+ env=env, shell=shell,
+ preexec_fn=lambda: signal(SIGPIPE, SIG_DFL))
+ preexec_fn=lambda: signal(SIGPIPE, SIG_DFL))
(out, err) = sp.communicate(data)
# Just ensure blank instead of none.

View File

@ -1,23 +0,0 @@
Index: cloud-init-0.7.8/cloudinit/util.py
===================================================================
--- cloud-init-0.7.8.orig/cloudinit/util.py
+++ cloud-init-0.7.8/cloudinit/util.py
@@ -283,9 +283,6 @@ class ProcessExecutionError(IOError):
'reason': self.reason,
}
IOError.__init__(self, message)
- # For backward compatibility with Python 2.
- if not hasattr(self, 'message'):
- self.message = message
class SeLinuxGuard(object):
@@ -1816,7 +1813,7 @@ def subp(args, data=None, rcs=None, env=
def ldecode(data, m='utf-8'):
if not isinstance(data, bytes):
return data
- return data.decode(m, errors=decode)
+ return data.decode(m, decode)
out = ldecode(out)
err = ldecode(err)

View File

@ -1,58 +0,0 @@
Index: systemd/cloud-init.service
===================================================================
--- systemd/cloud-init.service.orig
+++ systemd/cloud-init.service
@@ -1,9 +1,19 @@
[Unit]
Description=Initial cloud-init job (metadata service crawler)
-After=cloud-init-local.service networking.service
-Before=network-online.target sshd.service sshd-keygen.service systemd-user-sessions.service
-Requires=networking.service
-Wants=local-fs.target cloud-init-local.service sshd.service sshd-keygen.service
+DefaultDependencies=no
+Wants=cloud-init-local.service
+Wants=local-fs.target
+Wants=sshd-keygen.service
+Wants=sshd.service
+After=cloud-init-local.service
+After=dbus.service
+After=wicked.service
+Requires=wicked.service
+Before=network-online.target
+Before=sshd-keygen.service
+Before=sshd.service
+Before=systemd-user-sessions.service
+Conflicts=shutdown.target
[Service]
Type=oneshot
Index: systemd/cloud-init-local.service
===================================================================
--- systemd/cloud-init-local.service.orig
+++ systemd/cloud-init-local.service
@@ -4,9 +4,10 @@ DefaultDependencies=no
Wants=local-fs.target
Wants=network-pre.target
After=local-fs.target
-Conflicts=shutdown.target
+Before=basic.target
Before=network-pre.target
Before=shutdown.target
+Conflicts=shutdown.target
[Service]
Type=oneshot
Index: systemd/cloud-final.service
===================================================================
--- systemd/cloud-final.service.orig
+++ systemd/cloud-final.service
@@ -1,6 +1,8 @@
[Unit]
Description=Execute cloud user/final scripts
-After=network-online.target cloud-config.service rc-local.service multi-user.target
+After=cloud-config.service
+After=network-online.target
+After=rc-local.service
Before=systemd-logind.service
Wants=network-online.target cloud-config.service

View File

@ -0,0 +1,11 @@
--- cloudinit/util.py.orig
+++ cloudinit/util.py
@@ -599,6 +599,8 @@ def system_info():
var = 'ubuntu'
elif linux_dist == 'redhat':
var = 'rhel'
+ elif linux_dist == 'suse':
+ var = 'suse'
else:
var = 'linux'
elif system in ('windows', 'darwin', "freebsd"):

View File

@ -1,74 +0,0 @@
Index: cloud-init-0.7.8/cloudinit/net/__init__.py
===================================================================
--- cloud-init-0.7.8.orig/cloudinit/net/__init__.py
+++ cloud-init-0.7.8/cloudinit/net/__init__.py
@@ -38,7 +38,7 @@ def read_sys_net(devname, path, translat
contents = util.load_file(dev_path)
except (OSError, IOError) as e:
e_errno = getattr(e, 'errno', None)
- if e_errno in (errno.ENOENT, errno.ENOTDIR):
+ if e_errno in (errno.ENOENT, errno.ENOTDIR):
if enoent is not None:
return enoent
raise
@@ -173,16 +173,16 @@ def generate_fallback_config():
if name not in potential_interfaces:
continue
mac = read_sys_net(name, 'address', enoent=False)
- if mac:
+ if mac:
target_name = name
- target_mac = mac
- break
+ target_mac = mac
+ break
if target_mac and target_name:
nconf = {'config': [], 'version': 1}
- nconf['config'].append(
+ nconf['config'].append(
{'type': 'physical', 'name': target_name,
- 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]})
- return nconf
+ 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]})
+ return nconf
else:
return None
Index: cloud-init-0.7.8/cloudinit/net/eni.py
===================================================================
--- cloud-init-0.7.8.orig/cloudinit/net/eni.py
+++ cloud-init-0.7.8/cloudinit/net/eni.py
@@ -338,7 +338,7 @@ class Renderer(renderer.Renderer):
up = indent + "post-up route add"
down = indent + "pre-down route del"
or_true = " || true"
- gateway = indent + "gateway "
+ gateway = indent + "gateway "
mapping = {
'network': '-net',
'netmask': 'netmask',
@@ -347,7 +347,7 @@ class Renderer(renderer.Renderer):
}
if route['network'] == '0.0.0.0' and route['netmask'] == '0.0.0.0':
default_gw = " default gw %s" % route['gateway']
- content.append(gateway + route['gateway'])
+ content.append(gateway + route['gateway'])
content.append(up + default_gw + or_true)
content.append(down + default_gw + or_true)
elif route['network'] == '::' and route['netmask'] == 0:
Index: cloud-init-0.7.8/cloudinit/net/cmdline.py
===================================================================
--- cloud-init-0.7.8.orig/cloudinit/net/cmdline.py
+++ cloud-init-0.7.8/cloudinit/net/cmdline.py
@@ -198,9 +198,9 @@ def read_kernel_cmdline_config(files=Non
if mac_addrs is None:
mac_addrs = {}
- for k in get_devicelist():
+ for k in get_devicelist():
mac_addr = read_sys_net(k, 'address', enoent=False)
- if mac_addr:
+ if mac_addr:
mac_addrs[k] = mac_addr
return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs)

View File

@ -1,15 +1,17 @@
--- cloudinit/net/sysconfig.py.orig
+++ cloudinit/net/sysconfig.py
@@ -94,7 +94,7 @@ class ConfigMap(object):
@@ -88,8 +88,8 @@ class ConfigMap(object):
class Route(ConfigMap):
"""Represents a route configuration."""
- route_fn_tpl = '%(base)s/network-scripts/route-%(name)s'
+ route_fn_tpl = '%(base)s/network/route-%(name)s'
- route_fn_tpl_ipv4 = '%(base)s/network-scripts/route-%(name)s'
- route_fn_tpl_ipv6 = '%(base)s/network-scripts/route6-%(name)s'
+ route_fn_tpl_ipv4 = '%(base)s/network/route-%(name)s'
+ route_fn_tpl_ipv6 = '%(base)s/network/route6-%(name)s'
def __init__(self, route_name, base_sysconf_dir):
super(Route, self).__init__()
@@ -119,7 +119,7 @@ class Route(ConfigMap):
@@ -166,7 +166,7 @@ class Route(ConfigMap):
class NetInterface(ConfigMap):
"""Represents a sysconfig/networking-script (and its config + children)."""
@ -18,3 +20,14 @@
iface_types = {
'ethernet': 'Ethernet',
@@ -602,8 +602,8 @@ def available(target=None):
return False
expected_paths = [
- 'etc/sysconfig/network-scripts/network-functions',
- 'etc/sysconfig/network-scripts/ifdown-eth']
+ 'etc/sysconfig/network/network-functions',
+ 'etc/sysconfig/network/ifdown-eth']
for p in expected_paths:
if not os.path.isfile(util.target_path(target, p)):
return False

View File

@ -0,0 +1,20 @@
--- Makefile.orig
+++ Makefile
@@ -11,7 +11,7 @@ PIP_INSTALL := pip install
ifeq ($(PYVER),python3)
pyflakes = pyflakes3
- unittests = unittest3
+ unittests = unittest
yaml = yaml
else
ifeq ($(PYVER),python2)
@@ -19,7 +19,7 @@ ifeq ($(PYVER),python2)
unittests = unittest
else
pyflakes = pyflakes pyflakes3
- unittests = unittest unittest3
+ unittests = unittest unittest
endif
endif

View File

@ -1,3 +1,555 @@
-------------------------------------------------------------------
Thu Sep 21 17:32:55 EDT 2017 - rjschwei@suse.com
- Update to version 17.1
+ Version numbering scheme change now YY.NUMBER_OF_RELESE_THAT_YEAR
+ Remove cloud.cfg.suse, use generated default config file
+ Remove addopenSUSEBase.patch, included upstream
+ Remove suseIntegratedHandler.patch, included upstream
+ Remove openSUSEhostsTemplate.diff, included upstream
+ Remove cloud-init-handle-no-carrier.patch, included upstream
+ Remove cloud-init-digital-ocean-datasource.patch,
use upstream implementation
+ Remove cloud-init-digital-ocean-datasource-enable-by-default.patch,
use upstream implementation
+ Remove cloud-init-fix-unicode-handling-binarydecode.patch,
included upstream
+ Remove cloud-init-no-dmidecode-on-ppc64.patch, included upstream
+ Remove dataSourceOpenNebula.patch, use upstream implementation
+ Remove setupSUSEsysVInit.diff, included upstream
+ Remove SUSEsysVInit.diff, included upstream
+ Remove cloud-init-finalbeforelogin.patch, don't block login
+ Remove cloud-init-handle-not-implemented-query.patch, query option removed
+ Remove cloud-init-spceandtabs-clean.patch, indentation fixed upstream
+ Remove dynamicInitCmd.diff, different solution from upstream
+ Added cloud-init-more-tasks.patch, (bsc#1047363)
replace cloud-init-finalbeforelogin.patch
+ Forward port cloud-init-python2-sigpipe.patch
+ Remove cloud-init-net-eni.patch, included upstream
+ Remove cloud-init-service.patch, included upstream
+ Forward port cloud-init-sysconfig-netpathfix.patch
+ Remove cloud-init-net-sysconfig-lp1665441.patch, included upstream
+ Remove cloud-init-python26.patch, included upstream
+ Add cloud-init-tests-set-exec.patch
+ Add cloud-init-final-no-apt.patch
+ Remove skip-argparse-on-python3.patch
+ doc: document GCE datasource. [Arnd Hannemann]
+ suse: updates to templates to support openSUSE and SLES.
+ [Robert Schweikert] (LP: #1718640)
+ suse: Copy sysvinit files from redhat with slight changes.
+ [Robert Schweikert] (LP: #1718649)
+ docs: fix sphinx module schema documentation [Chad Smith]
+ tests: Add cloudinit package to all test targets [Chad Smith]
+ Makefile: No longer look for yaml files in obsolete ./bin/.
+ tests: fix ds-identify unit tests to set EC2_STRICT_ID_DEFAULT.
+ ec2: Fix maybe_perform_dhcp_discovery to use /var/tmp as a tmpdir
+ [Chad Smith] (LP: #1717627)
+ Azure: wait longer for SSH pub keys to arrive.
+ [Paul Meyer] (LP: #1717611)
+ GCE: Fix usage of user-data. (LP: #1717598)
+ cmdline: add collect-logs subcommand. [Chad Smith] (LP: #1607345)
+ CloudStack: consider dhclient lease files named with a hyphen.
+ (LP: #1717147)
+ resizefs: Drop check for read-only device file, do not warn on
+ overlayroot. [Chad Smith]
+ Do not provide systemd-fsck drop-in which could cause ordering cycles.
+ [Balint Reczey] (LP: #1717477)
+ tests: Enable the NoCloud KVM platform [Joshua Powers]
+ resizefs: pass mount point to xfs_growfs [Dusty Mabe]
+ vmware: Enable nics before sending the SUCCESS event. [Sankar Tanguturi]
+ cloud-config modules: honor distros definitions in each module
+ [Chad Smith] (LP: #1715738, #1715690)
+ chef: Add option to pin chef omnibus install version
+ [Ethan Apodaca] (LP: #1462693)
+ tests: execute: support command as string [Joshua Powers]
+ schema and docs: Add jsonschema to resizefs and bootcmd modules
+ [Chad Smith]
+ tools: Add xkvm script, wrapper around qemu-system [Joshua Powers]
+ vmware customization: return network config format
+ [Sankar Tanguturi] (LP: #1675063)
+ Ec2: only attempt to operate at local mode on known platforms.
+ (LP: #1715128)
+ Use /run/cloud-init for tempfile operations. (LP: #1707222)
+ ds-identify: Make OpenStack return maybe on arch other than intel.
+ (LP: #1715241)
+ tests: mock missed openstack metadata uri network_data.json
+ [Chad Smith] (LP: #1714376)
+ relocate tests/unittests/helpers.py to cloudinit/tests
+ [Lars Kellogg-Stedman]
+ tox: add nose timer output [Joshua Powers]
+ upstart: do not package upstart jobs, drop ubuntu-init-switch module.
+ tests: Stop leaking calls through unmocked metadata addresses
+ [Chad Smith] (LP: #1714117)
+ distro: allow distro to specify a default locale [Ryan Harper]
+ tests: fix two recently added tests for sles distro.
+ url_helper: dynamically import oauthlib import from inside oauth_headers
+ [Chad Smith]
+ tox: make xenial environment run with python3.6
+ suse: Add support for openSUSE and return SLES to a working state.
+ [Robert Schweikert]
+ GCE: Add a main to the GCE Datasource.
+ ec2: Add IPv6 dhcp support to Ec2DataSource. [Chad Smith] (LP: #1639030)
+ url_helper: fail gracefully if oauthlib is not available
+ [Lars Kellogg-Stedman] (LP: #1713760)
+ cloud-init analyze: fix issues running under python 2. [Andrew Jorgensen]
+ Configure logging module to always use UTC time.
+ [Ryan Harper] (LP: #1713158)
+ Log a helpful message if a user script does not include shebang.
+ [Andrew Jorgensen]
+ cli: Fix command line parsing of coniditionally loaded subcommands.
+ [Chad Smith] (LP: #1712676)
+ doc: Explain error behavior in user data include file format.
+ [Jason Butz]
+ cc_landscape & cc_puppet: Fix six.StringIO use in writing configs
+ [Chad Smith] (LP: #1699282, #1710932)
+ schema cli: Add schema subcommand to cloud-init cli and cc_runcmd schema
+ [Chad Smith]
+ Debian: Remove non-free repositories from apt sources template.
+ [Joonas Kylmälä] (LP: #1700091)
+ tools: Add tooling for basic cloud-init performance analysis.
+ [Chad Smith] (LP: #1709761)
+ network: add v2 passthrough and fix parsing v2 config with bonds/bridge
+ params [Ryan Harper] (LP: #1709180)
+ doc: update capabilities with features available, link doc reference,
+ cli example [Ryan Harper]
+ vcloud directory: Guest Customization support for passwords
+ [Maitreyee Saikia]
+ ec2: Allow Ec2 to run in init-local using dhclient in a sandbox.
+ [Chad Smith] (LP: #1709772)
+ cc_ntp: fallback on timesyncd configuration if ntp is not installable
+ [Ryan Harper] (LP: #1686485)
+ net: Reduce duplicate code. Have get_interfaces_by_mac use
+ get_interfaces.
+ tests: Fix build tree integration tests [Joshua Powers]
+ sysconfig: Dont repeat header when rendering resolv.conf
+ [Ryan Harper] (LP: #1701420)
+ archlinux: Fix bug with empty dns, do not render 'lo' devices.
+ (LP: #1663045, #1706593)
+ cloudinit.net: add initialize_network_device function and tests
+ [Chad Smith]
+ makefile: fix ci-deps-ubuntu target [Chad Smith]
+ tests: adjust locale integration test to parse default locale.
+ tests: remove 'yakkety' from releases as it is EOL.
+ tests: Add initial tests for EC2 and improve a docstring.
+ locale: Do not re-run locale-gen if provided locale is system default.
+ archlinux: fix set hostname usage of write_file.
+ [Joshua Powers] (LP: #1705306)
+ sysconfig: support subnet type of 'manual'.
+ tools/run-centos: make running with no argument show help.
+ Drop rand_str() usage in DNS redirection detection
+ [Bob Aman] (LP: #1088611)
+ sysconfig: use MACADDR on bonds/bridges to configure mac_address
+ [Ryan Harper] (LP: #1701417)
+ net: eni route rendering missed ipv6 default route config
+ [Ryan Harper] (LP: #1701097)
+ sysconfig: enable mtu set per subnet, including ipv6 mtu
+ [Ryan Harper] (LP: #1702513)
+ sysconfig: handle manual type subnets [Ryan Harper] (LP: #1687725)
+ sysconfig: fix ipv6 gateway routes [Ryan Harper] (LP: #1694801)
+ sysconfig: fix rendering of bond, bridge and vlan types.
+ [Ryan Harper] (LP: #1695092)
+ Templatize systemd unit files for cross distro deltas. [Ryan Harper]
+ sysconfig: ipv6 and default gateway fixes. [Ryan Harper] (LP: #1704872)
+ net: fix renaming of nics to support mac addresses written in upper
+ case. (LP: #1705147)
+ tests: fixes for issues uncovered when moving to python 3.6.
+ (LP: #1703697)
+ sysconfig: include GATEWAY value if set in subnet
+ [Ryan Harper] (LP: #1686856)
+ Scaleway: add datasource with user and vendor data for Scaleway.
+ [Julien Castets]
+ Support comments in content read by load_shell_content.
+ cloudinitlocal fail to run during boot [Hongjiang Zhang]
+ doc: fix disk setup example table_type options
+ [Sandor Zeestraten] (LP: #1703789)
+ tools: Fix exception handling. [Joonas Kylmälä] (LP: #1701527)
+ tests: fix usage of mock in GCE test.
+ test_gce: Fix invalid mock of platform_reports_gce to return False
+ [Chad Smith]
+ test: fix incorrect keyid for apt repository.
+ [Joshua Powers] (LP: #1702717)
+ tests: Update version of pylxd [Joshua Powers]
+ write_files: Remove log from helper function signatures.
+ [Andrew Jorgensen]
+ doc: document the cmdline options to NoCloud [Brian Candler]
+ read_dmi_data: always return None when inside a container. (LP: #1701325)
+ requirements.txt: remove trailing white space.
+ Azure: Add network-config, Refactor net layer to handle duplicate macs.
+ [Ryan Harper]
+ Tests: Simplify the check on ssh-import-id [Joshua Powers]
+ tests: update ntp tests after sntp added [Joshua Powers]
+ FreeBSD: Make freebsd a variant, fix unittests and
+ tools/build-on-freebsd.
+ FreeBSD: fix test failure
+ FreeBSD: replace ifdown/ifup with "ifconfig down" and "ifconfig up".
+ [Hongjiang Zhang] (LP: #1697815)
+ FreeBSD: fix cdrom mounting failure if /mnt/cdrom/secure did not exist.
+ [Hongjiang Zhang] (LP: #1696295)
+ main: Don't use templater to format the welcome message
+ [Andrew Jorgensen]
+ docs: Automatically generate module docs form schema if present.
+ [Chad Smith]
+ debian: fix path comment in /etc/hosts template.
+ [Jens Sandmann] (LP: #1606406)
+ suse: add hostname and fully qualified domain to template.
+ [Jens Sandmann]
+ write_file(s): Print permissions as octal, not decimal [Andrew Jorgensen]
+ ci deps: Add --test-distro to read-dependencies to install all deps
+ [Chad Smith]
+ tools/run-centos: cleanups and move to using read-dependencies
+ pkg build ci: Add make ci-deps-<distro> target to install pkgs
+ [Chad Smith]
+ systemd: make cloud-final.service run before apt daily services.
+ (LP: #1693361)
+ selinux: Allow restorecon to be non-fatal. [Ryan Harper] (LP: #1686751)
+ net: Allow netinfo subprocesses to return 0 or 1.
+ [Ryan Harper] (LP: #1686751)
+ net: Allow for NetworkManager configuration [Ryan McCabe] (LP: #1693251)
+ Use distro release version to determine if we use systemd in redhat spec
+ [Ryan Harper]
+ net: normalize data in network_state object
+ Integration Testing: tox env, pyxld 2.2.3, and revamp framework
+ [Wesley Wiedenmeier]
+ Chef: Update omnibus url to chef.io, minor doc changes. [JJ Asghar]
+ tools: add centos scripts to build and test [Joshua Powers]
+ Drop cheetah python module as it is not needed by trunk [Ryan Harper]
+ rhel/centos spec cleanups.
+ cloud.cfg: move to a template. setup.py changes along the way.
+ Makefile: add deb-src and srpm targets. use PYVER more places.
+ makefile: fix python 2/3 detection in the Makefile [Chad Smith]
+ snap: Removing snapcraft plug line [Joshua Powers] (LP: #1695333)
+ RHEL/CentOS: Fix default routes for IPv4/IPv6 configuration.
+ [Andreas Karis] (LP: #1696176)
+ test: Fix pyflakes complaint of unused import.
+ [Joshua Powers] (LP: #1695918)
+ NoCloud: support seed of nocloud from smbios information
+ [Vladimir Pouzanov] (LP: #1691772)
+ net: when selecting a network device, use natural sort order
+ [Marc-Aurèle Brothier]
+ fix typos and remove whitespace in various docs [Stephan Telling]
+ systemd: Fix typo in comment in cloud-init.target. [Chen-Han Hsiao]
+ Tests: Skip jsonschema related unit tests when dependency is absent.
+ [Chad Smith] (LP: #1695318)
+ azure: remove accidental duplicate line in merge.
+ azure: identify platform by well known value in chassis asset tag.
+ [Chad Smith] (LP: #1693939)
+ tools/net-convert.py: support old cloudinit versions by using kwargs.
+ ntp: Add schema definition and passive schema validation.
+ [Chad Smith] (LP: #1692916)
+ Fix eni rendering for bridge params that require repeated key for
+ values. [Ryan Harper]
+ net: remove systemd link file writing from eni renderer [Ryan Harper]
+ AliYun: Enable platform identification and enable by default.
+ [Junjie Wang] (LP: #1638931)
+ net: fix reading and rendering addresses in cidr format.
+ [Dimitri John Ledkov] (LP: #1689346, #1684349)
+ disk_setup: udev settle before attempting partitioning or fs creation.
+ (LP: #1692093)
+ GCE: Update the attribute used to find instance SSH keys.
+ [Daniel Watkins] (LP: #1693582)
+ nplan: For bonds, allow dashed or underscore names of keys.
+ [Dimitri John Ledkov] (LP: #1690480)
+ python2.6: fix unit tests usage of assertNone and format.
+ test: update docstring on test_configured_list_with_none
+ fix tools/ds-identify to not write None twice.
+ tox/build: do not package depend on style requirements.
+ cc_ntp: Restructure cc_ntp unit tests. [Chad Smith] (LP: #1692794)
+ flake8: move the pinned version of flake8 up to 3.3.0
+ tests: Apply workaround for snapd bug in test case. [Joshua Powers]
+ RHEL/CentOS: Fix dual stack IPv4/IPv6 configuration.
+ [Andreas Karis] (LP: #1679817, #1685534, #1685532)
+ disk_setup: fix several issues with gpt disk partitions. (LP: #1692087)
+ function spelling & docstring update [Joshua Powers]
+ Fixing wrong file name regression. [Joshua Powers]
+ tox: move pylint target to 1.7.1
+ Fix get_interfaces_by_mac for empty macs (LP: #1692028)
+ DigitalOcean: remove routes except for the public interface.
+ [Ben Howard] (LP: #1681531.)
+ netplan: pass macaddress, when specified, for vlans
+ [Dimitri John Ledkov] (LP: #1690388)
+ doc: various improvements for the docs on cc_users_groups.
+ [Felix Dreissig]
+ cc_ntp: write template before installing and add service restart
+ [Ryan Harper] (LP: #1645644)
+ cloudstack: fix tests to avoid accessing /var/lib/NetworkManager
+ [Lars Kellogg-Stedman]
+ tests: fix hardcoded path to mkfs.ext4 [Joshua Powers] (LP: #1691517)
+ Actually skip warnings when .skip file is present.
+ [Chris Brinker] (LP: #1691551)
+ netplan: fix netplan render_network_state signature.
+ [Dimitri John Ledkov] (LP: #1685944)
+ Azure: fix reformatting of ephemeral disks on resize to large types.
+ (LP: #1686514)
+ Revert "tools/net-convert: fix argument order for render_network_state"
+ make deb: Add devscripts dependency for make deb. Cleanup
+ packages/bddeb. [Chad Smith] (LP: #1685935)
+ tools/net-convert: fix argument order for render_network_state
+ [Ryan Harper] (LP: #1685944)
+ openstack: fix log message copy/paste typo in _get_url_settings
+ [Lars Kellogg-Stedman]
+ unittests: fix unittests run on centos [Joshua Powers]
+ Improve detection of snappy to include os-release and kernel cmdline.
+ (LP: #1689944)
+ Add address to config entry generated by _klibc_to_config_entry.
+ [Julien Castets] (LP: #1691135)
+ sysconfig: Raise ValueError when multiple default gateways are present.
+ [Chad Smith] (LP: #1687485)
+ FreeBSD: improvements and fixes for use on Azure
+ [Hongjiang Zhang] (LP: #1636345)
+ Add unit tests for ds-identify, fix Ec2 bug found.
+ fs_setup: if cmd is specified, use shell interpretation.
+ [Paul Meyer] (LP: #1687712)
+ doc: document network configuration defaults policy and formats.
+ [Ryan Harper]
+ Fix name of "uri" key in docs for "cc_apt_configure" module
+ [Felix Dreissig]
+ tests: Enable artful [Joshua Powers]
+ nova-lxd: read product_name from environment, not platform.
+ (LP: #1685810)
+ Fix yum repo config where keys contain array values
+ [Dylan Perry] (LP: #1592150)
+ template: Update debian backports template [Joshua Powers] (LP: #1627293)
+ rsyslog: replace ~ with stop [Joshua Powers] (LP: #1367899)
+ Doc: add additional RTD examples [Joshua Powers] (LP: #1459604)
+ Fix growpart for some cases when booted with root=PARTUUID.
+ (LP: #1684869)
+ pylint: update output style to parseable [Joshua Powers]
+ pylint: fix all logging warnings [Joshua Powers]
+ CloudStack: Add NetworkManager to list of supported DHCP lease dirs.
+ [Syed]
+ net: kernel lies about vlans not stealing mac addresses, when they do
+ [Dimitri John Ledkov] (LP: #1682871)
+ ds-identify: Check correct path for "latest" config drive
+ [Daniel Watkins] (LP: #1673637)
+ doc: Fix example for resolve.conf configuration.
+ [Jon Grimm] (LP: #1531582)
+ Fix examples that reference upstream chef repository.
+ [Jon Grimm] (LP: #1678145)
+ doc: correct grammar and improve clarity in merging documentation.
+ [David Tagatac]
+ doc: Add missing doc link to snap-config module. [Ryan Harper]
+ snap: allows for creating cloud-init snap [Joshua Powers]
+ DigitalOcean: assign IPv4ll address to lowest indexed interface.
+ [Ben Howard]
+ DigitalOcean: configure all NICs presented in meta-data. [Ben Howard]
+ Remove (and/or fix) URL shortener references [Jon Grimm] (LP: #1669727)
+ HACKING.rst: more info on filling out contributors agreement.
+ util: teach write_file about copy_mode option
+ [Lars Kellogg-Stedman] (LP: #1644064)
+ DigitalOcean: bind resolvers to loopback interface. [Ben Howard]
+ tests: fix AltCloud tests to not rely on blkid (LP: #1636531)
+ OpenStack: add 'dvs' to the list of physical link types. (LP: #1674946)
+ Fix bug that resulted in an attempt to rename bonds or vlans.
+ (LP: #1669860)
+ tests: update OpenNebula and Digital Ocean to not rely on host
+ interfaces.
+ net: in netplan renderer delete known image-builtin content.
+ (LP: #1675576)
+ doc: correct grammar in capabilities.rst [David Tagatac]
+ ds-identify: fix detecting of maas datasource. (LP: #1677710)
+ netplan: remove debugging prints, add debug logging [Ryan Harper]
+ ds-identify: do not write None twice to datasource_list.
+ support resizing partition and rootfs on system booted without
+ initramfs. [Steve Langasek] (LP: #1677376)
+ apt_configure: run only when needed. (LP: #1675185)
+ OpenStack: identify OpenStack by product 'OpenStack Compute'.
+ (LP: #1675349)
+ GCE: Search GCE in ds-identify, consider serial number in check.
+ (LP: #1674861)
+ Add support for setting hashed passwords [Tore S. Lonoy] (LP: #1570325)
+ Fix filesystem creation when using "partition: auto"
+ [Jonathan Ballet] (LP: #1634678)
+ ConfigDrive: support reading config drive data from /config-drive.
+ (LP: #1673411)
+ ds-identify: fix detection of Bigstep datasource. (LP: #1674766)
+ test: add running of pylint [Joshua Powers]
+ ds-identify: fix bug where filename expansion was left on.
+ advertise network config v2 support (NETWORK_CONFIG_V2) in features.
+ Bigstep: fix bug when executing in python3. [root]
+ Fix unit test when running in a system deployed with cloud-init.
+ Bounce network interface for Azure when using the built-in path.
+ [Brent Baude] (LP: #1674685)
+ cloudinit.net: add network config v2 parsing and rendering [Ryan Harper]
+ net: Fix incorrect call to isfile [Joshua Powers] (LP: #1674317)
+ net: add renderers for automatically selecting the renderer.
+ doc: fix config drive doc with regard to unpartitioned disks.
+ (LP: #1673818)
+ test: Adding integratiron test for password as list [Joshua Powers]
+ render_network_state: switch arguments around, do not require target
+ support 'loopback' as a device type.
+ Integration Testing: improve testcase subclassing [Wesley Wiedenmeier]
+ gitignore: adding doc/rtd_html [Joshua Powers]
+ doc: add instructions for running integration tests via tox.
+ [Joshua Powers]
+ test: avoid differences in 'date' output due to daylight savings.
+ Fix chef config module in omnibus install. [Jeremy Melvin] (LP: #1583837)
+ Add feature flags to cloudinit.version. [Wesley Wiedenmeier]
+ tox: add a citest environment
+ Further fix regression to support 'password' for default user.
+ fix regression when no chpasswd/list was provided.
+ Support chpasswd/list being a list in addition to a string.
+ [Sergio Lystopad] (LP: #1665694)
+ doc: Fix configuration example for cc_set_passwords module.
+ [Sergio Lystopad] (LP: #1665773)
+ net: support both ipv4 and ipv6 gateways in sysconfig.
+ [Lars Kellogg-Stedman] (LP: #1669504)
+ net: do not raise exception for > 3 nameservers
+ [Lars Kellogg-Stedman] (LP: #1670052)
+ ds-identify: report cleanups for config and exit value. (LP: #1669949)
+ ds-identify: move default setting for Ec2/strict_id to a global.
+ ds-identify: record not found in cloud.cfg and always add None.
+ Support warning if the used datasource is not in ds-identify's list.
+ tools/ds-identify: make report mode write namespaced results.
+ Move warning functionality to cloudinit/warnings.py
+ Add profile.d script for showing warnings on login.
+ Z99-cloud-locale-test.sh: install and make consistent.
+ tools/ds-identify: look at cloud.cfg when looking for ec2 strict_id.
+ tools/ds-identify: disable vmware_guest_customization by default.
+ tools/ds-identify: ovf identify vmware guest customization.
+ Identify Brightbox as an Ec2 datasource user. (LP: #1661693)
+ DatasourceEc2: add warning message when not on AWS.
+ ds-identify: add reading of datasource/Ec2/strict_id
+ tools/ds-identify: add support for found or maybe contributing config.
+ tools/ds-identify: read the seed directory on Ec2
+ tools/ds-identify: use quotes in local declarations.
+ tools/ds-identify: fix documentation of policy setting in a comment.
+ ds-identify: only run once per boot unless --force is given.
+ flake8: fix flake8 complaints in previous commit.
+ net: correct errors in cloudinit/net/sysconfig.py
+ [Lars Kellogg-Stedman] (LP: #1665441)
+ ec2_utils: fix MetadataLeafDecoder that returned bytes on empty
+ apply the runtime configuration written by ds-identify.
+ ds-identify: fix checking for filesystem label (LP: #1663735)
+ ds-identify: read ds=nocloud properly (LP: #1663723)
+ support nova-lxd by reading platform from environment of pid 1.
+ (LP: #1661797)
+ ds-identify: change aarch64 to use the default for non-dmi systems.
+ Remove style checking during build and add latest style checks to tox
+ [Joshua Powers] (LP: #1652329)
+ code-style: make master pass pycodestyle (2.3.1) cleanly, currently:
+ [Joshua Powers]
+ manual_cache_clean: When manually cleaning touch a file in instance dir.
+ Add tools/ds-identify to identify datasources available.
+ Fix small typo and change iso-filename for consistency [Robin Naundorf]
+ Fix eni rendering of multiple IPs per interface
+ [Ryan Harper] (LP: #1657940)
+ tools/mock-meta: support python2 or python3 and ipv6 in both.
+ tests: remove executable bit on test_net, so it runs, and fix it.
+ tests: No longer monkey patch httpretty for python 3.4.2
+ Add 3 ecdsa-sha2-nistp* ssh key types now that they are standardized
+ [Lars Kellogg-Stedman] (LP: #1658174)
+ reset httppretty for each test [Lars Kellogg-Stedman] (LP: #1658200)
+ build: fix running Make on a branch with tags other than master
+ EC2: Do not cache security credentials on disk
+ [Andrew Jorgensen] (LP: #1638312)
+ doc: Fix typos and clarify some aspects of the part-handler
+ [Erik M. Bray]
+ doc: add some documentation on OpenStack datasource.
+ OpenStack: Use timeout and retries from config in get_data.
+ [Lars Kellogg-Stedman] (LP: #1657130)
+ Fixed Misc issues related to VMware customization. [Sankar Tanguturi]
+ Fix minor docs typo: perserve > preserve [Jeremy Bicha]
+ Use dnf instead of yum when available
+ [Lars Kellogg-Stedman] (LP: #1647118)
+ validate-yaml: use python rather than explicitly python3
+ Get early logging logged, including failures of cmdline url.
- From 0.7.9
+ doc: adjust headers in tests documentation for consistency.
+ pep8: fix issue found in zesty build with pycodestyle.
+ integration test: initial commit of integration test framework
+ [Wesley Wiedenmeier]
+ LICENSE: Allow dual licensing GPL-3 or Apache 2.0 [Jon Grimm]
+ Fix config order of precedence, putting kernel command line over system.
+ [Wesley Wiedenmeier] (LP: #1582323)
+ pep8: whitespace fix
+ Update the list of valid ssh keys. [Michael Felt]
+ network: add ENI unit test for statically rendered routes.
+ set_hostname: avoid erroneously appending domain to fqdn
+ [Lars Kellogg-Stedman] (LP: #1647910)
+ doc: change 'nobootwait' to 'nofail' in docs [Anhad Jai Singh]
+ Replace an expired bit.ly link in code comment.
+ user-groups: fix bug when groups was provided as string and had spaces
+ (LP: #1354694)
+ mounts: use mount -a again to accomplish mounts (LP: #1647708)
+ CloudSigma: Fix bug where datasource was not loaded in local search.
+ (LP: #1648380)
+ when adding a user, strip whitespace from group list [Lars Kellogg-Stedman]
+ (LP: #1354694)
+ fix decoding of utf-8 chars in yaml test
+ Replace usage of sys_netdev_info with read_sys_net (LP: #1625766)
+ fix problems found in python2.6 test.
+ OpenStack: extend physical types to include hyperv, hw_veb, vhost_user.
+ (LP: #1642679)
+ tests: fix assumptions that expected no eth0 in system. (LP: #1644043)
+ net/cmdline: Consider ip= or ip6= on command line not only ip=
+ (LP: #1639930)
+ Just use file logging by default (LP: #1643990)
+ Improve formatting for ProcessExecutionError [Wesley Wiedenmeier]
+ flake8: fix trailing white space
+ Doc: various documentation fixes [Sean Bright]
+ cloudinit/config/cc_rh_subscription.py: Remove repos before adding
+ [Brent Baude]
+ packages/redhat: fix rpm spec file.
+ main: set TZ in environment if not already set. [Ryan Harper]
+ Azure: No longer rely on walinux agent. (LP: #1538522)
+ disk_setup: Use sectors as unit when formatting MBR disks with sfdisk.
+ [Daniel Watkins] (LP: #1460715)
+ Add activate_datasource, for datasource specific code paths. (LP: #1611074)
+ systemd: cloud-init-local use RequiresMountsFor=/var/lib/cloud
+ (LP: #1642062)
+ systemd: cloud-init remove After=systemd-networkd-wait-online
+ systemd: cloud-init-local change Before basic to sysinit
+ pep8: fix style errors reported by pycodestyle 2.1.0
+ systemd: drop both Wants and After local-fs.target
+ systemd: networking service adjustments. (LP: #1636912)
+ systemd: replace Before=basic.target, dbus.target with sysinit.target
+ (LP: #1629797)
+ doc: Add documentation on stages of boot.
+ doc: make the RST files consistently formated and other improvements.
+ Ec2: fix syntax and tox in previous commit.
+ Ec2: protect against non-dictionary in block-device-mapping.
+ doc: fixed example to not overwrite /etc/hosts [Chris Glass]
+ Doc: fix spelling / typos in ca_certs and scripts_vendor.
+ pyflakes: fix issue with pyflakes 1.3 found in ubuntu zesty-proposed.
+ net/cmdline: Further adjustments to ipv6 support [LaMont Jones]
+ (LP: #1621615)
+ Add coverage dependency to bddeb to fix package build.
+ doc: improve HACKING.rst file
+ dmidecode: Allow dmidecode to be used on aarch64 [Robert Schweikert]
+ AliYun: Add new datasource for Ali-Cloud ECS [kaihuan.pkh]
+ Add coverage collection to tox unit tests. [Joshua Powers]
+ cc_users_groups: fix remaing call to ds.normalize_user_groups [Ryan Harper]
+ disk-config: udev settle after partitioning in gpt format. (LP: #1626243)
+ unittests: do not read system /etc/cloud/cloud.cfg.d (LP: #1635350)
+ Add documentation for logging features. [Wesley Wiedenmeier]
+ Add support for snap create-user on Ubuntu Core images. [Ryan Harper]
+ Fix sshd restarts for rhel distros. [Jim Gorz]
+ OpenNebula: replace 'ip' parsing with cloudinit.net usage.
+ Fix python2.6 things found running in centos 6.
+ Move user/group functions to new ug_util file
+ DigitalOcean: enable usage of data source by default.
+ update Gentoo initscripts to run in the correct order [Matthew Thode]
+ MAAS: improve the main of datasource to look at kernel cmdline config.
+ tests: silence the Cheetah UserWarning about NameMapper C version.
+ systemd: Run cloud-init.service Before dbus.socket not dbus.target
+ [Daniel Watkins] (LP: #1629797)
+ systemd: run cloud-init.service Before dbus.service (LP: #1629797)
+ unittests: fix use of mock 2.0 'assert_called' when running make check
+ [Ryan Harper]
+ Improve module documentation and doc cleanup. [Wesley Wiedenmeier]
+ lxd: Update network config for LXD 2.3 [Stéphane Graber]
+ DigitalOcean: use meta-data for network configruation [Ben Howard]
+ ntp: move to run after apt configuration (LP: #1628337)
+ Decode unicode types in decode_binary [Robert Schweikert]
+ systemd: Ensure that cloud-init-local happens before NetworkManager
+ Allow ephemeral drive to be unpartitioned [Paul Meyer]
+ subp: add 'update_env' argument
+ net: support reading ipv6 dhcp config from initramfs [LaMont Jones]
+ (LP: #1621615, #1621507)
+ Adjust mounts and disk configuration for systemd. (LP: #1611074)
+ dmidecode: run dmidecode only on i?86 or x86_64 arch. [Robert Schweikert]
+ systemd: put cloud-init.target After multi-user.target (LP: #1623868)
-------------------------------------------------------------------
Wed Sep 20 10:11:42 UTC 2017 - dmueller@suse.com

View File

@ -18,73 +18,59 @@
%global configver 0.7
Name: cloud-init
Version: 0.7.8
Version: 17.1
Release: 0
License: GPL-3.0 and AGPL-3.0
Summary: Cloud node initialization tool
Url: http://launchpad.net/cloud-init/
Group: System/Management
Source0: %{name}-%{version}.tar.gz
Source1: cloud.cfg.suse
Source2: rsyslog-cloud-init.cfg
Patch0: suseSysVInit.diff
# FIXME addopenSUSEBase.patch proposed for upstream merge
Patch1: addopenSUSEBase.patch
# FIXME suseIntegratedHandler.patch proposed for upstream merge
Patch2: suseIntegratedHandler.patch
Patch3: setupSUSEsysVInit.diff
# FIXME openSUSEhostsTemplate.diff proposed for upstream merge
Patch5: openSUSEhostsTemplate.diff
# FIXME dynamicInitCmd.patch proposed for upstream merge
Patch6: dynamicInitCmd.diff
Patch9: cloud-init-no-dmidecode-on-ppc64.patch
Source1: rsyslog-cloud-init.cfg
# FIXME zypp_add_repos.diff needs proposed for upstream merge
Patch4: zypp_add_repos.diff
Patch10: cloud-init-no-user-lock-if-already-locked.patch
Patch11: dataSourceOpenNebula.patch
Patch12: fix-default-systemd-unit-dir.patch
Patch14: cloud-init-finalbeforelogin.patch
# FIXME cloud-init-more-tasks.patch proposed for upstream merge
Patch13: cloud-init-more-tasks.patch
# python2 disables SIGPIPE, causing broken pipe errors in shell scripts (bsc#903449)
Patch20: cloud-init-python2-sigpipe.patch
Patch21: cloud-init-net-eni.patch
Patch22: cloud-init-service.patch
Patch23: cloud-init-fix-unicode-handling-binarydecode.patch
# From upstream patch
Patch24: cloud-init-handle-no-carrier.patch
Patch25: cloud-init-digital-ocean-datasource.patch
Patch26: cloud-init-digital-ocean-datasource-enable-by-default.patch
Patch27: cloud-init-sysconfig-netpathfix.patch
Patch28: zypp_add_repos.diff
Patch29: datasourceLocalDisk.patch
Patch30: cloud-init-handle-not-implemented-query.patch
Patch32: cloud-init-net-sysconfig-lp1665441.patch
Patch33: cloud-init-spceandtabs-clean.patch
Patch34: skip-argparse-on-python3.patch
Patch34: cloud-init-tests-set-exec.patch
Patch35: cloud-init-final-no-apt.patch
BuildRequires: fdupes
BuildRequires: filesystem
%if 0%{?suse_version} && 0%{?suse_version} > 1315
BuildRequires: python3-devel
BuildRequires: python3-setuptools
%else
BuildRequires: python-devel
BuildRequires: python-setuptools
%endif
# pkg-config is needed to find correct systemd unit dir
BuildRequires: pkg-config
# needed for /lib/udev
BuildRequires: udev
%if 0%{?suse_version} > 1320
BuildRequires: python3-devel
BuildRequires: python3-setuptools
# Test requirements
#BuildRequires: python3-Cheetah
BuildRequires: python3-Jinja2
BuildRequires: python3-PrettyTable
BuildRequires: python3-PyYAML
BuildRequires: python3-configobj
#BuildRequires: python3-contextlib2
BuildRequires: python3-configobj >= 5.0.2
BuildRequires: python3-httpretty
BuildRequires: python3-jsonpatch
BuildRequires: python3-mock
BuildRequires: python3-nose
BuildRequires: python3-oauthlib
BuildRequires: python3-requests
BuildRequires: python3-testtools
%else
BuildRequires: python-devel
BuildRequires: python-Jinja2
BuildRequires: python-PyYAML
BuildRequires: python-requests
BuildRequires: python-setuptools
BuildRequires: python-six
%endif
%if 0%{?is_opensuse} || 0%{?suse_version} == 1310 || 0%{?suse_version} == 1320
BuildRequires: openSUSE-release
%else
BuildRequires: sles-release
%endif
Requires: bash
Requires: file
@ -97,9 +83,10 @@ Requires: net-tools-deprecated
Requires: openssh
%if 0%{?suse_version} > 1320
Requires: python3-boto >= 2.7
Requires: python3-configobj
Requires: python3-configobj >= 5.0.2
Requires: python3-Jinja2
Requires: python3-jsonpatch
Requires: python3-jsonschema
Requires: python3-oauthlib
Requires: python3-PrettyTable
Requires: python3-pyserial
@ -111,9 +98,10 @@ Requires: python3-xml
%else
Requires: python-argparse
Requires: python-boto >= 2.7
Requires: python-configobj
Requires: python-configobj >= 5.0.2
Requires: python-Jinja2
Requires: python-jsonpatch
Requires: python-jsonschema
Requires: python-oauthlib
Requires: python-PrettyTable
Requires: python-pyserial
@ -140,7 +128,6 @@ Requires: dmidecode
%endif
%if 0%{?suse_version} && 0%{?suse_version} <= 1210
%define initsys sysvinit_suse
Patch40: cloud-init-python26.patch
%else
%define initsys systemd
BuildRequires: systemd
@ -187,52 +174,29 @@ according to the fetched configuration data from the admin node.
Documentation and examples for cloud-init tools
%package test
Summary: Cloud node initialization tool - Testsuite
Group: System/Management
Requires: cloud-init = %{version}
%description test
Cloud-init is an init script that initializes a cloud node (VM)
according to the fetched configuration data from the admin node.
Unit tests for the cloud-init tools
#%package test
#Summary: Cloud node initialization tool - Testsuite
#Group: System/Management
#Requires: cloud-init = %{version}
#
#%description test
#Cloud-init is an init script that initializes a cloud node (VM)
#according to the fetched configuration data from the admin node.
#
#Unit tests for the cloud-init tools
%prep
%setup -q
%patch0 -p1
%patch1 -p1
%patch2
%patch3
%patch5
%patch6
%patch9
%patch4 -p0
%patch10 -p1
%patch11
%patch12
%patch14
%patch13
%patch20
%patch21
%patch22
%patch23
%patch24
%patch25 -p1
%patch26 -p1
%patch27
%patch28 -p0
%patch29 -p0
%patch30
%patch32 -p1
%patch33 -p1
%patch34
%if 0%{?suse_version} && 0%{?suse_version} <= 1210
%patch40 -p1
%endif
%patch35 -p1
%if 0%{?suse_version} <= 1130
# disable ecdsa for SLE 11 (not available)
echo "ssh_genkeytypes: ['rsa', 'dsa']" >> %{SOURCE1}
%endif
%build
%if 0%{?suse_version} && 0%{?suse_version} <= 1315
@ -247,16 +211,11 @@ python3 setup.py build
# these tests are currently failing due to suse patches
rm -v tests/unittests/test_distros/test_netconfig.py
rm -v tests/unittests/test_net.py
# Ignore test failure currently not doing anything with opennebula
rm -v tests/unittests/test_datasource/test_opennebula.py
rm -v tests/unittests/test_datasource/test_cloudstack.py
# These tests fail in python 3
rm -v tests/unittests/test_datasource/test_altcloud.py
rm -v tests/unittests/test_handler/test_handler_apt_source_v3.py
%if 0%{?suse_version} && 0%{?suse_version} <= 1315
python -m testtools.run
%else
python3 -m testtools.run
%endif
# To be investigated
rm -v tests/unittests/test_handler/test_handler_ntp.py
make test
%endif
@ -275,7 +234,10 @@ mkdir -p %{buildroot}%{_localstatedir}/lib/cloud
# move documentation
mkdir -p %{buildroot}%{_defaultdocdir}
mv %{buildroot}%{_datadir}/doc/%{name} %{buildroot}%{docdir}
cp -a %{SOURCE1} %{buildroot}/%{_sysconfdir}/cloud/cloud.cfg
%if 0%{?suse_version} <= 1130
# disable ecdsa for SLE 11 (not available)
echo "ssh_genkeytypes: ['rsa', 'dsa']" >> %{buildroot}/%{_sysconfdir}/cloud/cloud.cfg
%endif
# copy the LICENSE
cp LICENSE %{buildroot}%{docdir}
# Set the distribution indicator
@ -296,7 +258,7 @@ sed -i s/INSERT_SUSE_DISTRO/opensuse/ %{buildroot}/%{_sysconfdir}/cloud/cloud.cf
%if 0%{?suse_version} && 0%{?suse_version} > 1110
mkdir -p %{buildroot}/%{_sysconfdir}/rsyslog.d
mkdir -p %{buildroot}/usr/lib/udev/rules.d/
cp -a %{SOURCE2} %{buildroot}/%{_sysconfdir}/rsyslog.d/21-cloudinit.conf
cp -a %{SOURCE1} %{buildroot}/%{_sysconfdir}/rsyslog.d/21-cloudinit.conf
mv %{buildroot}/lib/udev/rules.d/66-azure-ephemeral.rules %{buildroot}/usr/lib/udev/rules.d/
%endif
@ -396,15 +358,11 @@ popd
%{docdir}/examples/*
%{docdir}/README
%{docdir}/*.txt
%{docdir}/*.rst
#%{docdir}/*.rst
%dir %{docdir}/examples
%files test
%defattr(-,root,root)
%if 0%{?suse_version} && 0%{?suse_version} <= 1315
%{python_sitelib}/tests
%else
%{python3_sitelib}/tests
%endif
#%files test
#%defattr(-,root,root)
#%{python_sitelib}/tests
%changelog

View File

@ -1,61 +0,0 @@
# Adapted default config for (open)SUSE systems
users:
- root
disable_root: false
preserve_hostname: false
syslog_fix_perms: root:root
# The modules that run in the 'init' stage
cloud_init_modules:
- migrator
- bootcmd
- write-files
- growpart
- resizefs
- set_hostname
- update_hostname
- update_etc_hosts
- ca-certs
- rsyslog
- users-groups
- ssh
# The modules that run in the 'config' stage
cloud_config_modules:
- mounts
- ssh-import-id
- locale
- set-passwords
- zypp_add_repo
- package-update-upgrade-install
- timezone
- puppet
- chef
- salt-minion
- mcollective
- disable-ec2-metadata
- runcmd
- byobu
# The modules that run in the 'final' stage
cloud_final_modules:
- rightscale_userdata
- scripts-per-once
- scripts-per-boot
- scripts-per-instance
- scripts-user
- ssh-authkey-fingerprints
- keys-to-console
- phone-home
- final-message
- power-state-change
# System and/or distro specific settings
system_info:
distro: INSERT_SUSE_DISTRO
paths:
cloud_dir: /var/lib/cloud/
templates_dir: /etc/cloud/templates/
ssh_svcname: sshd

View File

@ -1,98 +0,0 @@
--- cloudinit/sources/DataSourceOpenNebula.py.orig
+++ cloudinit/sources/DataSourceOpenNebula.py
@@ -121,7 +121,7 @@ class BrokenContextDiskDir(Exception):
class OpenNebulaNetwork(object):
REG_DEV_MAC = re.compile(
- r'^\d+: (eth\d+):.*?link\/ether (..:..:..:..:..:..) ?',
+ r'^\d+: (eth\d+):.*?link\/\W+ (..:..:..:..:..:..) ?',
re.MULTILINE | re.DOTALL)
def __init__(self, ip, context):
@@ -130,12 +130,24 @@ class OpenNebulaNetwork(object):
self.ifaces = self.get_ifaces()
def get_ifaces(self):
- return self.REG_DEV_MAC.findall(self.ip)
+ list = self.REG_DEV_MAC.findall(self.ip)
+ ifaces = dict()
+ for l in list:
+ ifaces[l[1]] = l[0]
+ return ifaces
def mac2ip(self, mac):
components = mac.split(':')[2:]
return [str(int(c, 16)) for c in components]
+ def get_context_interfaces(self):
+
+ def device_mac(t): return re.match(r"ETH\d+_MAC", t)
+
+ mac_vars = filter(device_mac, self.context.keys())
+ context_interfaces = [v.split('_')[0] for v in mac_vars]
+ return context_interfaces
+
def get_ip(self, dev, components):
var_name = dev.upper() + '_IP'
if var_name in self.context:
@@ -188,27 +200,39 @@ class OpenNebulaNetwork(object):
conf.append('iface lo inet loopback')
conf.append('')
- for i in self.ifaces:
- dev = i[0]
- mac = i[1]
+ context_interfaces = self.get_context_interfaces()
+
+ if len(context_interfaces):
+ try:
+ (out, _err) = util.subp(["systemctl", "stop", "NetworkManager"])
+ (out, _err) = util.subp(["systemctl", "disable", "NetworkManager"])
+ except util.ProcessExecutionError:
+ util.logexc(LOG, "Disable NetworkManager command failed")
+
+ for interface in context_interfaces:
+ mac = self.context[interface+"_MAC"]
+ dev = self.ifaces.get(mac)
+ if dev is None:
+ continue
+
ip_components = self.mac2ip(mac)
conf.append('auto ' + dev)
conf.append('iface ' + dev + ' inet static')
- conf.append(' address ' + self.get_ip(dev, ip_components))
- conf.append(' network ' + self.get_network(dev, ip_components))
- conf.append(' netmask ' + self.get_mask(dev))
+ conf.append(' address ' + self.get_ip(nterface, ip_components))
+ conf.append(' network ' + self.get_network(interface, ip_components))
+ conf.append(' netmask ' + self.get_mask(interface))
- gateway = self.get_gateway(dev)
+ gateway = self.get_gateway(interface)
if gateway:
conf.append(' gateway ' + gateway)
- domain = self.get_domain(dev)
+ domain = self.get_domain(interface)
if domain:
conf.append(' dns-search ' + domain)
# add global DNS servers to all interfaces
- dns = self.get_dns(dev)
+ dns = self.get_dns(interface)
if global_dns or dns:
all_dns = global_dns
if dns:
@@ -375,9 +399,8 @@ def read_context_disk_dir(source_dir, as
if ssh_key_var:
lines = context.get(ssh_key_var).splitlines()
- results['metadata']['public-keys'] = [l for l in lines
- if len(l) and not
- l.startswith("#")]
+ ssh_keys = [l for l in lines if len(l) and not l.startswith("#")]
+ results['metadata']['public-keys'] = ssh_keys
# custom hostname -- try hostname or leave cloud-init
# itself create hostname from IP address later

View File

@ -1,28 +0,0 @@
=== modified file 'cloudinit/config/cc_set_passwords.py'
Index: cloudinit/config/cc_set_passwords.py
===================================================================
--- cloudinit/config/cc_set_passwords.py.orig
+++ cloudinit/config/cc_set_passwords.py
@@ -147,7 +147,7 @@ def handle(_name, cfg, cloud, log, args)
util.write_file(ssh_util.DEF_SSHD_CFG, "\n".join(lines))
try:
- cmd = cloud.distro.init_cmd # Default service
+ cmd = cloud.distro.get_init_cmd()
cmd.append(cloud.distro.get_option('ssh_svcname', 'ssh'))
cmd.append('restart')
if 'systemctl' in cmd: # Switch action ordering
Index: cloudinit/distros/__init__.py
===================================================================
--- cloudinit/distros/__init__.py.orig
+++ cloudinit/distros/__init__.py
@@ -88,6 +88,9 @@ class Distro(object):
" no file found at %s") % (tz, tz_file))
return tz_file
+ def get_init_cmd(self):
+ return self.init_cmd
+
def get_option(self, opt_name, default=None):
return self._cfg.get(opt_name, default)

View File

@ -1,6 +1,6 @@
--- setup.py.orig
+++ setup.py
@@ -58,8 +58,8 @@ def tiny_p(cmd, capture=True):
@@ -49,8 +49,8 @@ def tiny_p(cmd, capture=True):
def pkg_config_read(library, var):
fallbacks = {
'systemd': {
@ -13,9 +13,9 @@
cmd = ['pkg-config', '--variable=%s' % var, library]
--- systemd/cloud-init-generator.orig
+++ systemd/cloud-init-generator
@@ -7,7 +7,7 @@ LOG_D="/run/cloud-init"
ENABLE="enabled"
DISABLE="disabled"
@@ -9,7 +9,7 @@ DISABLE="disabled"
FOUND="found"
NOTFOUND="notfound"
RUN_ENABLED_FILE="$LOG_D/$ENABLE"
-CLOUD_SYSTEM_TARGET="/lib/systemd/system/cloud-init.target"
+CLOUD_SYSTEM_TARGET="/usr/lib/systemd/system/cloud-init.target"

View File

@ -1,29 +0,0 @@
--- /dev/null
+++ templates/hosts.opensuse.tmpl
@@ -0,0 +1,26 @@
+*
+ This file /etc/cloud/templates/hosts.opensuse.tmpl is only utilized
+ if enabled in cloud-config. Specifically, in order to enable it
+ you need to add the following to config:
+ manage_etc_hosts: True
+*#
+# Your system has configured 'manage_etc_hosts' as True.
+# As a result, if you wish for changes to this file to persist
+# then you will need to either
+# a.) make changes to the master file in
+# /etc/cloud/templates/hosts.opensuse.tmpl
+# b.) change or remove the value of 'manage_etc_hosts' in
+# /etc/cloud/cloud.cfg or cloud-config from user-data
+#
+# The following lines are desirable for IPv4 capable hosts
+127.0.0.1 localhost
+
+# The following lines are desirable for IPv6 capable hosts
+::1 localhost ipv6-localhost ipv6-loopback
+fe00::0 ipv6-localnet
+
+ff00::0 ipv6-mcastprefix
+ff02::1 ipv6-allnodes
+ff02::2 ipv6-allrouters
+ff02::3 ipv6-allhosts
+

View File

@ -1,18 +0,0 @@
--- setup.py.orig
+++ setup.py
@@ -75,6 +75,7 @@ INITSYS_FILES = {
'sysvinit_freebsd': [f for f in glob('sysvinit/freebsd/*') if is_f(f)],
'sysvinit_deb': [f for f in glob('sysvinit/debian/*') if is_f(f)],
'sysvinit_openrc': [f for f in glob('sysvinit/gentoo/*') if is_f(f)],
+ 'sysvinit_suse': [f for f in glob('sysvinit/suse/*') if is_f(f)],
'systemd': [f for f in (glob('systemd/*.service') +
glob('systemd/*.target')) if is_f(f)],
'systemd.generators': [f for f in glob('systemd/*-generator') if is_f(f)],
@@ -85,6 +86,7 @@ INITSYS_ROOTS = {
'sysvinit_freebsd': '/usr/local/etc/rc.d',
'sysvinit_deb': '/etc/init.d',
'sysvinit_openrc': '/etc/init.d',
+ 'sysvinit_suse': '/etc/init.d',
'systemd': pkg_config_read('systemd', 'systemdsystemunitdir'),
'systemd.generators': pkg_config_read('systemd',
'systemdsystemgeneratordir'),

View File

@ -1,23 +0,0 @@
--- requirements.txt
+++ requirements.txt
@@ -27,9 +27,6 @@
# All new style configurations are in the yaml format
pyyaml
-# The new main entrypoint uses argparse instead of optparse
-argparse
-
# Requests handles ssl correctly!
requests
--- setup.py
+++ setup.py
@@ -198,7 +198,7 @@
requirements = read_requires()
if sys.version_info < (3,):
- requirements.append('cheetah')
+ requirements.extend(('cheetah', 'argparse'))
setuptools.setup(
name='cloud-init',

View File

@ -1,421 +0,0 @@
Index: cloudinit/distros/opensuse.py
===================================================================
--- /dev/null
+++ cloudinit/distros/opensuse.py
@@ -0,0 +1,233 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2016 SUSE LLC
+# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Author: Robert Schweikert <rjschwei@suse.com>
+# Author: Juerg Haefliger <juerg.haefliger@hp.com>
+#
+# Leaning very heavily on the RHEL and Debian implementation
+#
+# 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/>.
+
+from cloudinit import distros
+
+from cloudinit.distros.parsers.hostname import HostnameConf
+
+from cloudinit import helpers
+from cloudinit import log as logging
+from cloudinit import util
+
+from cloudinit.distros import net_util
+from cloudinit.distros import rhel_util as rhutil
+#from cloudinit.net import sysconfig
+#from cloudinit.net.network_state import parse_net_config_data
+from cloudinit.settings import PER_INSTANCE
+
+LOG = logging.getLogger(__name__)
+
+class Distro(distros.Distro):
+ clock_conf_fn = '/etc/sysconfig/clock'
+ hostname_conf_fn = '/etc/HOSTNAME'
+ init_cmd = ['service']
+ locale_conf_fn = '/etc/sysconfig/language'
+ network_conf_fn = '/etc/sysconfig/network'
+ network_script_tpl = '/etc/sysconfig/network/ifcfg-%s'
+ resolve_conf_fn = '/etc/resolv.conf'
+ route_conf_tpl = '/etc/sysconfig/network/ifroute-%s'
+ systemd_hostname_conf_fn = '/etc/hostname'
+ systemd_locale_conf_fn = '/etc/locale.conf'
+ tz_local_fn = '/etc/localtime'
+
+ def __init__(self, name, cfg, paths):
+ distros.Distro.__init__(self, name, cfg, paths)
+# self._net_renderer = sysconfig.Renderer()
+ # This will be used to restrict certain
+ # calls from repeatly happening (when they
+ # should only happen say once per instance...)
+ self._runner = helpers.Runners(paths)
+ self.osfamily = 'suse'
+ cfg['ssh_svcname'] = 'sshd'
+ self.systemdDist = util.which('systemctl')
+ if self.systemdDist:
+ self.init_cmd = ['systemctl']
+ cfg['ssh_svcname'] = 'sshd.service'
+
+ def apply_locale(self, locale, out_fn=None):
+ if self.systemdDist:
+ if not out_fn:
+ out_fn = self.systemd_locale_conf_fn
+ locale_cfg = {'LANG': locale}
+ else:
+ if not out_fn:
+ out_fn = self.locale_conf_fn
+ locale_cfg = {'RC_LANG': locale}
+ rhutil.update_sysconfig_file(out_fn, locale_cfg)
+
+ def install_packages(self, pkglist):
+ self.package_command('install', args='-l', pkgs=pkglist)
+
+ def package_command(self, command, args=None, pkgs=None):
+ if pkgs is None:
+ pkgs = []
+
+ cmd = ['zypper']
+ # No user interaction possible, enable non-interactive mode
+ cmd.append('--non-interactive')
+
+ # Comand is the operation, such as install
+ if command == 'upgrade':
+ command = 'update'
+ cmd.append(command)
+
+ # args are the arguments to the command, not global options
+ if args and isinstance(args, str):
+ cmd.append(args)
+ elif args and isinstance(args, list):
+ 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)
+
+ def set_timezone(self, tz):
+ tz_file = self._find_tz_file(tz)
+ if self.systemdDist:
+ # Currently, timedatectl complains if invoked during startup
+ # so for compatibility, create the link manually.
+ util.del_file(self.tz_local_fn)
+ util.sym_link(tz_file, self.tz_local_fn)
+ else:
+ # Adjust the sysconfig clock zone setting
+ clock_cfg = {
+ 'TIMEZONE': str(tz),
+ }
+ rhutil.update_sysconfig_file(self.clock_conf_fn, clock_cfg)
+ # This ensures that the correct tz will be used for the system
+ util.copy(tz_file, self.tz_local_fn)
+
+ def update_package_sources(self):
+ self._runner.run("update-sources", self.package_command,
+ ['refresh'], freq=PER_INSTANCE)
+
+
+ def _bring_up_interfaces(self, device_names):
+ if device_names and 'all' in device_names:
+ raise RuntimeError(('Distro %s can not translate '
+ 'the device name "all"') % (self.name))
+ return distros.Distro._bring_up_interfaces(self, device_names)
+
+
+ def _read_hostname(self, filename, default=None):
+ if self.systemdDist and filename.endswith('/previous-hostname'):
+ return util.load_file(filename).strip()
+ elif self.systemdDist:
+ (out, _err) = util.subp(['hostname'])
+ if len(out):
+ return out
+ else:
+ return default
+ else:
+ try:
+ conf = self._read_hostname_conf(filename)
+ hostname = conf.hostname
+ except IOError:
+ pass
+ if not hostname:
+ return default
+ return hostname
+
+ def _read_hostname_conf(self, filename):
+ conf = HostnameConf(util.load_file(filename))
+ conf.parse()
+ return conf
+
+ def _read_system_hostname(self):
+ if self.systemdDist:
+ host_fn = self.systemd_hostname_conf_fn
+ else:
+ host_fn = self.hostname_conf_fn
+ return (host_fn, self._read_hostname(host_fn))
+
+ def _write_hostname(self, hostname, out_fn):
+ if self.systemdDist and out_fn.endswith('/previous-hostname'):
+ util.write_file(out_fn, hostname)
+ elif self.systemdDist:
+ util.subp(['hostnamectl', 'set-hostname', str(hostname)])
+ else:
+ conf = None
+ try:
+ # Try to update the previous one
+ # so lets see if we can read it first.
+ conf = self._read_hostname_conf(out_fn)
+ except IOError:
+ pass
+ if not conf:
+ conf = HostnameConf('')
+ conf.set_hostname(hostname)
+ util.write_file(out_fn, str(conf), 0o644)
+
+ def _write_network(self, settings):
+ # Convert debian settings to ifcfg format
+ entries = net_util.translate_network(settings)
+ LOG.debug("Translated ubuntu style network settings %s into %s",
+ settings, entries)
+ # Make the intermediate format as the suse format...
+ nameservers = []
+ searchservers = []
+ dev_names = entries.keys()
+ for (dev, info) in entries.items():
+ net_fn = self.network_script_tpl % (dev)
+ route_fn = self.route_conf_tpl % (dev)
+ mode = None
+ if info.get('auto', None):
+ mode = 'auto'
+ else:
+ mode = 'manual'
+ bootproto = info.get('bootproto', None)
+ gateway = info.get('gateway', None)
+ net_cfg = {
+ 'BOOTPROTO': bootproto,
+ 'BROADCAST': info.get('broadcast'),
+ 'GATEWAY': gateway,
+ 'IPADDR': info.get('address'),
+ 'LLADDR': info.get('hwaddress'),
+ 'NETMASK': info.get('netmask'),
+ 'STARTMODE': mode,
+ 'USERCONTROL': 'no'
+ }
+ if dev != 'lo':
+ net_cfg['ETHTOOL_OPTIONS'] = ''
+ else:
+ net_cfg['FIREWALL'] = 'no'
+ rhutil.update_sysconfig_file(net_fn, net_cfg, True)
+ if gateway and bootproto == 'static':
+ default_route = 'default %s' %gateway
+ util.write_file(route_fn, default_route, 0o644)
+ if 'dns-nameservers' in info:
+ nameservers.extend(info['dns-nameservers'])
+ if 'dns-search' in info:
+ searchservers.extend(info['dns-search'])
+ if nameservers or searchservers:
+ rhutil.update_resolve_conf_file(self.resolve_conf_fn,
+ nameservers, searchservers)
+ return dev_names
+
+# New interface cannot yet be implemented/used as we have to figure out
+# how to have a distro specific renderer
+# def _write_network_config(self, netconfig):
+# ns = parse_net_config_data(netconfig)
+# self._net_renderer.render_network_state("/", ns)
+# return []
Index: cloudinit/distros/sles.py
===================================================================
--- cloudinit/distros/sles.py.orig
+++ cloudinit/distros/sles.py
@@ -1,10 +1,9 @@
# vi: ts=4 expandtab
#
-# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
-#
-# Author: Juerg Haefliger <juerg.haefliger@hp.com>
+# Copyright (C) 2014 SUSE LLC
#
# Leaning very heavily on the RHEL and Debian implementation
+# Author: Robert Schweikert <rjschwei@suse.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
@@ -18,162 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from cloudinit import distros
-
-from cloudinit.distros.parsers.hostname import HostnameConf
+from cloudinit.distros import opensuse
-from cloudinit import helpers
from cloudinit import log as logging
-from cloudinit import util
-
-from cloudinit.distros import net_util
-from cloudinit.distros import rhel_util
-from cloudinit.settings import PER_INSTANCE
LOG = logging.getLogger(__name__)
+class Distro(opensuse.Distro):
+ pass
-class Distro(distros.Distro):
- clock_conf_fn = '/etc/sysconfig/clock'
- locale_conf_fn = '/etc/sysconfig/language'
- network_conf_fn = '/etc/sysconfig/network'
- hostname_conf_fn = '/etc/HOSTNAME'
- network_script_tpl = '/etc/sysconfig/network/ifcfg-%s'
- resolve_conf_fn = '/etc/resolv.conf'
- tz_local_fn = '/etc/localtime'
-
- def __init__(self, name, cfg, paths):
- distros.Distro.__init__(self, name, cfg, paths)
- # This will be used to restrict certain
- # calls from repeatly happening (when they
- # should only happen say once per instance...)
- self._runner = helpers.Runners(paths)
- self.osfamily = 'suse'
-
- def install_packages(self, pkglist):
- self.package_command('install', args='-l', pkgs=pkglist)
-
- def _write_network(self, settings):
- # Convert debian settings to ifcfg format
- entries = net_util.translate_network(settings)
- LOG.debug("Translated ubuntu style network settings %s into %s",
- settings, entries)
- # Make the intermediate format as the suse format...
- nameservers = []
- searchservers = []
- dev_names = entries.keys()
- for (dev, info) in entries.items():
- net_fn = self.network_script_tpl % (dev)
- mode = info.get('auto')
- if mode and mode.lower() == 'true':
- mode = 'auto'
- else:
- mode = 'manual'
- net_cfg = {
- 'BOOTPROTO': info.get('bootproto'),
- 'BROADCAST': info.get('broadcast'),
- 'GATEWAY': info.get('gateway'),
- 'IPADDR': info.get('address'),
- 'LLADDR': info.get('hwaddress'),
- 'NETMASK': info.get('netmask'),
- 'STARTMODE': mode,
- 'USERCONTROL': 'no'
- }
- if dev != 'lo':
- net_cfg['ETHERDEVICE'] = dev
- net_cfg['ETHTOOL_OPTIONS'] = ''
- else:
- net_cfg['FIREWALL'] = 'no'
- rhel_util.update_sysconfig_file(net_fn, net_cfg, True)
- if 'dns-nameservers' in info:
- nameservers.extend(info['dns-nameservers'])
- if 'dns-search' in info:
- searchservers.extend(info['dns-search'])
- if nameservers or searchservers:
- rhel_util.update_resolve_conf_file(self.resolve_conf_fn,
- nameservers, searchservers)
- return dev_names
-
- def apply_locale(self, locale, out_fn=None):
- if not out_fn:
- out_fn = self.locale_conf_fn
- locale_cfg = {
- 'RC_LANG': locale,
- }
- rhel_util.update_sysconfig_file(out_fn, locale_cfg)
-
- def _write_hostname(self, hostname, out_fn):
- conf = None
- try:
- # Try to update the previous one
- # so lets see if we can read it first.
- conf = self._read_hostname_conf(out_fn)
- except IOError:
- pass
- if not conf:
- conf = HostnameConf('')
- conf.set_hostname(hostname)
- util.write_file(out_fn, str(conf), 0o644)
-
- def _read_system_hostname(self):
- host_fn = self.hostname_conf_fn
- return (host_fn, self._read_hostname(host_fn))
-
- def _read_hostname_conf(self, filename):
- conf = HostnameConf(util.load_file(filename))
- conf.parse()
- return conf
-
- def _read_hostname(self, filename, default=None):
- hostname = None
- try:
- conf = self._read_hostname_conf(filename)
- hostname = conf.hostname
- except IOError:
- pass
- if not hostname:
- return default
- return hostname
-
- def _bring_up_interfaces(self, device_names):
- if device_names and 'all' in device_names:
- raise RuntimeError(('Distro %s can not translate '
- 'the device name "all"') % (self.name))
- return distros.Distro._bring_up_interfaces(self, device_names)
-
- def set_timezone(self, tz):
- tz_file = self._find_tz_file(tz)
- # Adjust the sysconfig clock zone setting
- clock_cfg = {
- 'TIMEZONE': str(tz),
- }
- rhel_util.update_sysconfig_file(self.clock_conf_fn, clock_cfg)
- # 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, pkgs=None):
- if pkgs is None:
- pkgs = []
-
- cmd = ['zypper']
- # No user interaction possible, enable non-interactive mode
- cmd.append('--non-interactive')
-
- # Comand is the operation, such as install
- cmd.append(command)
-
- # args are the arguments to the command, not global options
- if args and isinstance(args, str):
- cmd.append(args)
- elif args and isinstance(args, list):
- 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)
-
- def update_package_sources(self):
- self._runner.run("update-sources", self.package_command,
- ['refresh'], freq=PER_INSTANCE)

View File

@ -1,39 +1,110 @@
--- cloudinit/config/cc_zypp_add_repo.py
+++ cloudinit/config/cc_zypp_add_repo.py 2016/11/23 13:10:49
@@ -0,0 +1,112 @@
+# vi: ts=4 expandtab
Index: cloudinit/config/cc_zypp_add_repo.py
===================================================================
--- /dev/null
+++ cloudinit/config/cc_zypper_add_repo.py
@@ -0,0 +1,220 @@
+#
+# Copyright (C) 2016 SUSE LLC.
+# This file is based on cc_yum_add_repo.py and was rewritten by
+# Thorsten Kukuk <kukuk@suse.com> for zypp repos.
+# Copyright (C) 2017 SUSE LLC.
+#
+# Original file was:
+#
+# 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/>.
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""zypper_add_repo: Add zyper repositories to the system"""
+
+import os
+
+import configobj
+import six
+import os
+
+
+from cloudinit import log as logging
+from cloudinit import util
+from cloudinit.config.schema import get_schema_doc
+from cloudinit.settings import PER_ALWAYS
+from six import string_types
+from textwrap import dedent
+
+distros = ['opensuse', 'sles']
+
+schema = {
+ 'id': 'cc_zypper_add_repo',
+ 'name': 'ZypperAddRepo',
+ 'title': 'Configure zypper behavior and add zypper repositories',
+ 'description': dedent("""\
+ Configure zypper behavior by modifying /etc/zypp/zypp.conf. The
+ configuration writer is "dumb" and will simply append the provided
+ configuration options to the configuration file. Option settings
+ that may be duplicate will be resolved by the way the zypp.conf file
+ is parsed. The file is in INI format.
+ Add repositories to the system. No validation is performed on the
+ repository file entries, it is assumed the user is familiar with
+ the zypper repository file format."""),
+ 'distros': distros,
+ 'examples': [dedent("""\
+ zypper:
+ repos:
+ - id: opensuse-oss
+ name: os-oss
+ baseurl: http://dl.opensuse.org/dist/leap/v/repo/oss/
+ enabled: 1
+ autorefresh: 1
+ - id: opensuse-oss-update
+ name: os-oss-up
+ baseurl: http://dl.opensuse.org/dist/leap/v/update
+ # any setting per
+ # https://en.opensuse.org/openSUSE:Standards_RepoInfo
+ # enable and autorefresh are on by default
+ config:
+ reposdir: /etc/zypp/repos.dir
+ servicesdir: /etc/zypp/services.d
+ download.use_deltarpm: true
+ # any setting in /etc/zypp/zypp.conf
+ """)],
+ 'frequency': PER_ALWAYS,
+ 'type': 'object',
+ 'properties': {
+ 'zypper': {
+ 'type': 'object',
+ 'properties': {
+ 'repos': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {
+ 'type': 'string',
+ 'description': dedent("""\
+ The unique id of the repo, used when
+ writing
+ /etc/zypp/repos.d/<id>.repo.""")
+ },
+ 'baseurl': {
+ 'type': 'string',
+ 'format': 'uri', # built-in format type
+ 'description': 'The base repositoy URL'
+ }
+ },
+ 'required': ['id', 'baseurl'],
+ 'additionalProperties': True
+ },
+ 'minItems': 1
+ },
+ 'config': {
+ 'type': 'object',
+ 'description': dedent("""\
+ Any supported zypo.conf key is written to
+ /etc/zypp/zypp.conf'""")
+ }
+ },
+ 'required': [],
+ 'minProperties': 1, # Either config or repo must be provided
+ 'additionalProperties': False, # only repos and config allowed
+ }
+ }
+}
+
+__doc__ = get_schema_doc(schema) # Supplement python help()
+
+LOG = logging.getLogger(__name__)
+
+
+def _canonicalize_id(repo_id):
+ repo_id = repo_id.replace(" ", "_")
@ -41,12 +112,12 @@
+
+
+def _format_repo_value(val):
+ if isinstance(val, (bool)):
+ if isinstance(val, bool):
+ # zypp prefers 1/0
+ return str(int(val))
+ return 1 if val else 0
+ if isinstance(val, (list, tuple)):
+ return "\n ".join([_format_repo_value(v) for v in val])
+ if not isinstance(val, six.string_types):
+ if not isinstance(val, string_types):
+ return str(val)
+ return val
+
@ -63,58 +134,99 @@
+ return "\n".join(lines)
+
+
+def handle(name, cfg, _cloud, log, _args):
+ repos = cfg.get('zypp_repos')
+def _write_repos(repos, repo_base_path):
+ """Write the user-provided repo definition files
+ @param repos: A list of repo dictionary objects provided by the user's
+ cloud config.
+ @param repo_base_path: The directory path to which repo definitions are
+ written.
+ """
+
+ if not repos:
+ log.debug(("Skipping module named %s,"
+ " no 'zypp_repos' configuration found"), name)
+ return
+ repo_base_path = util.get_cfg_option_str(cfg, 'zypp_repo_dir',
+ '/etc/zypp/repos.d/')
+ repo_locations = {}
+ repo_configs = {}
+ for (repo_id, repo_config) in repos.items():
+ valid_repos = {}
+ for index, user_repo_config in enumerate(repos):
+ # Skip on absent required keys
+ missing_keys = set(['id', 'baseurl']).difference(set(user_repo_config))
+ if missing_keys:
+ LOG.warning(
+ "Repo config at index %d is missing required config keys: %s",
+ index, ",".join(missing_keys))
+ continue
+ repo_id = user_repo_config.get('id')
+ 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!",
+ LOG.info("Skipping repo %s, file %s already exists!",
+ repo_id, repo_fn_pth)
+ continue
+ elif repo_id in repo_locations:
+ log.info("Skipping repo %s, file %s already pending!",
+ elif repo_id in valid_repos:
+ 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 req_field not in repo_config:
+ log.warn(("Repository %s does not contain a %s"
+ " configuration 'required' entry"),
+ repo_id, req_field)
+ missing_required += 1
+
+ # Do some basic key formatting
+ repo_config = dict(
+ (k.lower().strip().replace("-", "_"), v)
+ for k, v in user_repo_config.items()
+ if k and k != 'id')
+
+ # Set defaults if not present
+ for field in ['enabled', 'autorefresh']:
+ if field not in repo_config:
+ repo_config[field] = '1'
+ if not missing_required:
+ repo_configs[repo_id] = repo_config
+ repo_locations[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)
--- doc/examples/cloud-config-zypp-repo.txt
+++ doc/examples/cloud-config-zypp-repo.txt 2016/11/23 12:59:42
+
+ valid_repos[repo_id] = (repo_fn_pth, repo_config)
+
+ for (repo_id, repo_data) in valid_repos.items():
+ repo_blob = _format_repository_config(repo_id, repo_data[-1])
+ util.write_file(repo_data[0], repo_blob)
+
+
+def _write_zypp_config(zypper_config):
+ """Write to the default zypp configuration file /etc/zypp/zypp.conf"""
+ if not zypper_config:
+ return
+ zypp_config = '/etc/zypp/zypp.conf'
+ zypp_conf_content = util.load_file(zypp_config)
+ new_settings = ['# Added via cloud.cfg']
+ for setting, value in zypper_config.items():
+ if setting == 'configdir':
+ msg = 'Changing the location of the zypper configuration is '
+ msg += 'not supported, skipping "configdir" setting'
+ LOG.warning(msg)
+ continue
+ if value:
+ new_settings.append('%s=%s' % (setting, value))
+ if len(new_settings) > 1:
+ new_config = zypp_conf_content + '\n'.join(new_settings)
+ else:
+ new_config = zypp_conf_content
+ util.write_file(zypp_config, new_config)
+
+
+def handle(name, cfg, _cloud, log, _args):
+ zypper_section = cfg.get('zypper')
+ if not zypper_section:
+ LOG.debug(("Skipping module named %s,"
+ " no 'zypper' relevant configuration found"), name)
+ return
+ repos = zypper_section.get('repos')
+ if not repos:
+ LOG.debug(("Skipping module named %s,"
+ " no 'repos' configuration found"), name)
+ return
+ zypper_config = zypper_section.get('config', {})
+ repo_base_path = zypper_config.get('reposdir', '/etc/zypp/repos.d/')
+
+ _write_zypp_config(zypper_config)
+ _write_repos(repos, repo_base_path)
+
+# vi: ts=4 expandtab
Index: doc/examples/cloud-config-zypp-repo.txt
===================================================================
--- /dev/null
+++ doc/examples/cloud-config-zypp-repo.txt
@@ -0,0 +1,18 @@
+#cloud-config
+# vim: syntax=yaml