def handle(name, _cfg, _cloud, log, _args): required_tools = [RMCCTRL, RECFGCT] for tool in required_tools: if not os.path.isfile(tool): log.debug('%s is not found but is required, therefore not ' 'attempting to reset RMC.' % tool) return log.debug('Attempting to reset RMC.') system_info = util.system_info() node_id_before = get_node_id(log) log.debug('Node ID at beginning of module: %s' % node_id_before) # Stop the RMC subsystem and all resource managers so that we can make # some changes to it try: util.subp([RMCCTRL, '-z']) except: util.logexc(log, 'Failed to stop the RMC subsystem.') raise if 'linux' in system_info['platform'].lower(): recycle_srcmstr_process(log) reconfigure_rsct_subsystems(log) node_id_after = get_node_id(log) log.debug('Node ID at end of module: %s' % node_id_after) if node_id_after == node_id_before: msg = 'New node ID did not get generated.' log.error(msg) raise Exception(msg)
def generate_fallback_network_config() -> dict: """Return network config V1 dict representing instance network config.""" network_v1 = { "version": 1, "config": [{ "type": "physical", "name": "eth0", "subnets": [{ "type": "dhcp", "control": "auto" }] }] } if subp.which("systemd-detect-virt"): try: virt_type, _ = subp.subp(['systemd-detect-virt']) except subp.ProcessExecutionError as err: LOG.warning( "Unable to run systemd-detect-virt: %s." " Rendering default network config.", err) return network_v1 if virt_type.strip() == "kvm": # instance.type VIRTUAL-MACHINE arch = util.system_info()["uname"][4] if arch == "ppc64le": network_v1["config"][0]["name"] = "enp0s5" elif arch == "s390x": network_v1["config"][0]["name"] = "enc9" else: network_v1["config"][0]["name"] = "enp5s0" return network_v1
def preferred_ntp_clients(self): """The preferred ntp client is dependent on the version.""" # Allow distro to determine the preferred ntp client list if not self._preferred_ntp_clients: distro_info = util.system_info()["dist"] name = distro_info[0] major_ver = int(distro_info[1].split(".")[0]) # This is horribly complicated because of a case of # "we do not care if versions should be increasing syndrome" if (major_ver >= 15 and "openSUSE" not in name) or ( major_ver >= 15 and "openSUSE" in name and major_ver != 42 ): self._preferred_ntp_clients = [ "chrony", "systemd-timesyncd", "ntp", ] else: self._preferred_ntp_clients = [ "ntp", "systemd-timesyncd", "chrony", ] return self._preferred_ntp_clients
def handle(name, _cfg, _cloud, log, _args): default_interface = 'eth0' system_info = util.system_info() if 'aix' in system_info['platform'].lower(): default_interface = 'en0' interface = util.get_cfg_option_str(_cfg, 'set_hostname_from_interface', default=default_interface) log.debug('Setting hostname based on interface %s' % interface) full_hostname = None ipv4addr = None ipv6addr = None # Look up the IP address on the interface # and then reverse lookup the hostname in DNS info = netinfo.netdev_info() if interface in info: if 'addr' in info[interface]: ipv4addr = info[interface]['addr'] if 'addr6' in info[interface]: ipv6addr = info[interface]['addr6'].split('/')[0] else: log.warning('Interface %s was not found on the system. ' 'Interfaces found on system: %s' % (interface, info.keys())) ipaddr = ipv4addr or ipv6addr try: full_hostname, alias, iplist = socket.gethostbyaddr(ipaddr) if full_hostname: log.debug('Setting hostname on VM as %s' % full_hostname) short_hostname = full_hostname.split('.')[0] _cloud.distro.set_hostname(short_hostname, fqdn=full_hostname) except socket.error: log.warning('No hostname found for IP addresses %s' % ipaddr)
def uses_systemd(self): # Fedora 18 and RHEL 7 were the first adopters in their series (dist, vers) = util.system_info()['dist'][:2] major = (int)(vers.split('.')[0]) return ((dist.startswith('Red Hat Enterprise Linux') and major >= 7) or (dist.startswith('CentOS Linux') and major >= 7) or (dist.startswith('Fedora') and major >= 18))
def persist_instance_data(self): """Process and write INSTANCE_JSON_FILE with all instance metadata. Replace any hyphens with underscores in key names for use in template processing. @return True on successful write, False otherwise. """ if hasattr(self, '_crawled_metadata'): # Any datasource with _crawled_metadata will best represent # most recent, 'raw' metadata crawled_metadata = copy.deepcopy( getattr(self, '_crawled_metadata')) crawled_metadata.pop('user-data', None) crawled_metadata.pop('vendor-data', None) instance_data = {'ds': crawled_metadata} else: instance_data = {'ds': {'meta_data': self.metadata}} if hasattr(self, 'network_json'): network_json = getattr(self, 'network_json') if network_json != UNSET: instance_data['ds']['network_json'] = network_json if hasattr(self, 'ec2_metadata'): ec2_metadata = getattr(self, 'ec2_metadata') if ec2_metadata != UNSET: instance_data['ds']['ec2_metadata'] = ec2_metadata instance_data['ds']['_doc'] = EXPERIMENTAL_TEXT # Add merged cloud.cfg and sys info for jinja templates and cli query instance_data['merged_cfg'] = copy.deepcopy(self.sys_cfg) instance_data['merged_cfg']['_doc'] = ( 'Merged cloud-init system config from /etc/cloud/cloud.cfg and' ' /etc/cloud/cloud.cfg.d/') instance_data['sys_info'] = util.system_info() instance_data.update( self._get_standardized_metadata(instance_data)) try: # Process content base64encoding unserializable values content = util.json_dumps(instance_data) # Strip base64: prefix and set base64_encoded_keys list. processed_data = process_instance_metadata( json.loads(content), sensitive_keys=self.sensitive_metadata_keys) except TypeError as e: LOG.warning('Error persisting instance-data.json: %s', str(e)) return False except UnicodeDecodeError as e: LOG.warning('Error persisting instance-data.json: %s', str(e)) return False json_sensitive_file = os.path.join(self.paths.run_dir, INSTANCE_JSON_SENSITIVE_FILE) write_json(json_sensitive_file, processed_data, mode=0o600) json_file = os.path.join(self.paths.run_dir, INSTANCE_JSON_FILE) # World readable write_json(json_file, redact_sensitive_keys(processed_data)) return True
def apply_network(self, settings, bring_up=True): (dist, vers) = util.system_info()['dist'][:2] major = (int)(vers.split('.')[0]) # RHEL 7 runs cloud-init-local before networking is started. # Attempting to bring the interfaces up hangs cloud-init-local # and causes it to timeout. Therefore for RHEL 7 we write the # network configuration and let the network service bring them up # when it is started. if dist.startswith('Red Hat Enterprise Linux') and major >= 7: bring_up = False super(Distro, self).apply_network(settings, bring_up)
def preferred_ntp_clients(self): """The preferred ntp client is dependent on the version.""" if not self._preferred_ntp_clients: (_name, _version, codename) = util.system_info()['dist'] # Xenial cloud-init only installed ntp, UbuntuCore has timesyncd. if codename == "xenial" and not util.system_is_snappy(): self._preferred_ntp_clients = ['ntp'] else: self._preferred_ntp_clients = ( copy.deepcopy(PREFERRED_NTP_CLIENTS)) return self._preferred_ntp_clients
def get_system_info(): """Collect and report system information""" info = util.system_info() evt = events.ReportingEvent( SYSTEMINFO_EVENT_TYPE, 'system information', "cloudinit_version=%s, kernel_version=%s, variant=%s, " "distro_name=%s, distro_version=%s, flavor=%s, " "python_version=%s" % (version.version_string(), info['release'], info['variant'], info['dist'][0], info['dist'][1], info['dist'][2], info['python']), events.DEFAULT_EVENT_ORIGIN) events.report_event(evt) # return the event for unit testing purpose return evt
def givecmdline(pid): # Returns the cmdline for the given process id. In Linux we can use procfs # for this but on BSD there is /usr/bin/procstat. try: # Example output from procstat -c 1 # PID COMM ARGS # 1 init /bin/init -- if util.system_info()["platform"].startswith('FreeBSD'): (output, _err) = util.subp(['procstat', '-c', str(pid)]) line = output.splitlines()[1] m = re.search('\d+ (\w|\.|-)+\s+(/\w.+)', line) return m.group(2) else: return util.load_file("/proc/%s/cmdline" % pid) except IOError: return None
def available(target=None): if not util.system_info()["variant"] in KNOWN_DISTROS: return False expected = ["ifup", "ifdown"] search = ["/sbin", "/usr/sbin"] for p in expected: if not subp.which(p, search=search, target=target): return False expected_paths = [ "etc/sysconfig/network-scripts/network-functions", "etc/sysconfig/config", ] for p in expected_paths: if os.path.isfile(subp.target_path(target, p)): return True return False
def handle(name, _cfg, _cloud, log, _args): default_interface = 'eth0' system_info = util.system_info() if 'aix' in system_info['platform'].lower(): default_interface = 'en0' interface = util.get_cfg_option_str(_cfg, 'set_hostname_from_interface', default=default_interface) log.debug('Setting hostname based on interface %s' % interface) set_hostname = False fqdn = None # Look up the IP address on the interface # and then reverse lookup the hostname in DNS info = netinfo.netdev_info() if interface in info: set_short = util.get_cfg_option_bool(_cfg, "set_dns_shortname", False) if 'addr' in info[interface] and info[interface]['addr']: # Handle IPv4 address set_hostname =_set_hostname(_cfg, _cloud, log, info[interface]['addr'], set_short) elif 'addr6' in info[interface] and info[interface]['addr6']: # Handle IPv6 addresses for ipaddr in info[interface]['addr6']: ipaddr = ipaddr.split('/')[0] set_hostname = _set_hostname(_cfg, _cloud, log, ipaddr, set_short) if set_hostname: break else: log.warning('Interface %s was not found on the system. ' 'Interfaces found on system: %s' % (interface, info.keys())) # Reverse lookup failed, fall back to cc_set_hostname way. if not set_hostname: (short_hostname, fqdn) = util.get_hostname_fqdn(_cfg, _cloud) try: log.info('Fall back to setting hostname on VM as %s' % fqdn) _cloud.distro.set_hostname(short_hostname, fqdn=fqdn) except Exception: util.logexc(log, "Failed to set the hostname to %s", fqdn) raise
def handle(name, _cfg, _cloud, log, _args): default_interface = 'eth0' system_info = util.system_info() if 'aix' in system_info['platform'].lower(): default_interface = 'en0' interface = util.get_cfg_option_str(_cfg, 'set_hostname_from_interface', default=default_interface) log.debug('Setting hostname based on interface %s' % interface) set_hostname = False fqdn = None # Look up the IP address on the interface # and then reverse lookup the hostname in DNS info = netinfo.netdev_info() if interface in info: set_short = util.get_cfg_option_bool(_cfg, "set_dns_shortname", False) if 'addr' in info[interface] and info[interface]['addr']: # Handle IPv4 address set_hostname = _set_hostname(_cfg, _cloud, log, info[interface]['addr'], set_short) elif 'addr6' in info[interface] and info[interface]['addr6']: # Handle IPv6 addresses for ipaddr in info[interface]['addr6']: ipaddr = ipaddr.split('/')[0] set_hostname = _set_hostname(_cfg, _cloud, log, ipaddr, set_short) if set_hostname: break else: log.warning('Interface %s was not found on the system. ' 'Interfaces found on system: %s' % (interface, info.keys())) # Reverse lookup failed, fall back to cc_set_hostname way. if not set_hostname: (short_hostname, fqdn) = util.get_hostname_fqdn(_cfg, _cloud) try: log.info('Fall back to setting hostname on VM as %s' % fqdn) _cloud.distro.set_hostname(short_hostname, fqdn=fqdn) except Exception: util.logexc(log, "Failed to set the hostname to %s", fqdn) raise
def dist_check_timestamp(): """ Determine which init system a particular linux distro is using. Each init system (systemd, etc) has a different way of providing timestamps. :return: timestamps of kernelboot, kernelendboot, and cloud-initstart or TIMESTAMP_UNKNOWN if the timestamps cannot be retrieved. """ if uses_systemd(): return gather_timestamps_using_systemd() # Use dmesg to get timestamps if the distro does not have systemd if util.is_FreeBSD() or "gentoo" in util.system_info()["system"].lower(): return gather_timestamps_using_dmesg() # this distro doesn't fit anything that is supported by cloud-init. just # return error codes return TIMESTAMP_UNKNOWN
def preferred_ntp_clients(self): """The preferred ntp client is dependent on the version.""" """Allow distro to determine the preferred ntp client list""" if not self._preferred_ntp_clients: distro_info = util.system_info()['dist'] name = distro_info[0] major_ver = int(distro_info[1].split('.')[0]) # This is horribly complicated because of a case of # "we do not care if versions should be increasing syndrome" if ((major_ver >= 15 and 'openSUSE' not in name) or (major_ver >= 15 and 'openSUSE' in name and major_ver != 42)): self._preferred_ntp_clients = [ 'chrony', 'systemd-timesyncd', 'ntp' ] else: self._preferred_ntp_clients = [ 'ntp', 'systemd-timesyncd', 'chrony' ] return self._preferred_ntp_clients
def preferred_ntp_clients(self): """The preferred ntp client is dependent on the version.""" """Allow distro to determine the preferred ntp client list""" if not self._preferred_ntp_clients: distro_info = util.system_info()['dist'] name = distro_info[0] major_ver = int(distro_info[1].split('.')[0]) # This is horribly complicated because of a case of # "we do not care if versions should be increasing syndrome" if ( (major_ver >= 15 and 'openSUSE' not in name) or (major_ver >= 15 and 'openSUSE' in name and major_ver != 42) ): self._preferred_ntp_clients = ['chrony', 'systemd-timesyncd', 'ntp'] else: self._preferred_ntp_clients = ['ntp', 'systemd-timesyncd', 'chrony'] return self._preferred_ntp_clients
def device_part_info(devpath): # convert an entry in /dev/ to parent disk and partition number # input of /dev/vdb or /dev/disk/by-label/foo # rpath is hopefully a real-ish path in /dev (vda, sdb..) rpath = os.path.realpath(devpath) bname = os.path.basename(rpath) syspath = "/sys/class/block/%s" % bname # FreeBSD doesn't know of sysfs so just get everything we need from # the device, like /dev/vtbd0p2. if util.system_info()["platform"].startswith('FreeBSD'): m = re.search('^(/dev/.+)p([0-9])$', devpath) return (m.group(1), m.group(2)) if not os.path.exists(syspath): raise ValueError("%s had no syspath (%s)" % (devpath, syspath)) ptpath = os.path.join(syspath, "partition") if not os.path.exists(ptpath): raise TypeError("%s not a partition" % devpath) ptnum = util.load_file(ptpath).rstrip() # for a partition, real syspath is something like: # /sys/devices/pci0000:00/0000:00:04.0/virtio1/block/vda/vda1 rsyspath = os.path.realpath(syspath) disksyspath = os.path.dirname(rsyspath) diskmajmin = util.load_file(os.path.join(disksyspath, "dev")).rstrip() diskdevpath = os.path.realpath("/dev/block/%s" % diskmajmin) # diskdevpath has something like 253:0 # and udev has put links in /dev/block/253:0 to the device name in /dev/ return (diskdevpath, ptnum)
def _dist_uses_systemd(self): # Fedora 18 and RHEL 7 were the first adopters in their series (dist, vers) = util.system_info()['dist'][:2] major = (int)(vers.split('.')[0]) return ((dist.startswith('Red Hat Enterprise Linux') and major >= 7) or (dist.startswith('Fedora') and major >= 18))
def persist_instance_data(self): """Process and write INSTANCE_JSON_FILE with all instance metadata. Replace any hyphens with underscores in key names for use in template processing. @return True on successful write, False otherwise. """ if hasattr(self, "_crawled_metadata"): # Any datasource with _crawled_metadata will best represent # most recent, 'raw' metadata crawled_metadata = copy.deepcopy(getattr(self, "_crawled_metadata")) crawled_metadata.pop("user-data", None) crawled_metadata.pop("vendor-data", None) instance_data = {"ds": crawled_metadata} else: instance_data = {"ds": {"meta_data": self.metadata}} if hasattr(self, "network_json"): network_json = getattr(self, "network_json") if network_json != UNSET: instance_data["ds"]["network_json"] = network_json if hasattr(self, "ec2_metadata"): ec2_metadata = getattr(self, "ec2_metadata") if ec2_metadata != UNSET: instance_data["ds"]["ec2_metadata"] = ec2_metadata instance_data["ds"]["_doc"] = EXPERIMENTAL_TEXT # Add merged cloud.cfg and sys info for jinja templates and cli query instance_data["merged_cfg"] = copy.deepcopy(self.sys_cfg) instance_data["merged_cfg"]["_doc"] = ( "Merged cloud-init system config from /etc/cloud/cloud.cfg and" " /etc/cloud/cloud.cfg.d/") instance_data["sys_info"] = util.system_info() instance_data.update(self._get_standardized_metadata(instance_data)) try: # Process content base64encoding unserializable values content = util.json_dumps(instance_data) # Strip base64: prefix and set base64_encoded_keys list. processed_data = process_instance_metadata( json.loads(content), sensitive_keys=self.sensitive_metadata_keys, ) except TypeError as e: LOG.warning("Error persisting instance-data.json: %s", str(e)) return False except UnicodeDecodeError as e: LOG.warning("Error persisting instance-data.json: %s", str(e)) return False json_sensitive_file = os.path.join(self.paths.run_dir, INSTANCE_JSON_SENSITIVE_FILE) cloud_id = instance_data["v1"].get("cloud_id", "none") cloud_id_file = os.path.join(self.paths.run_dir, "cloud-id") util.write_file(f"{cloud_id_file}-{cloud_id}", f"{cloud_id}\n") if os.path.exists(cloud_id_file): prev_cloud_id_file = os.path.realpath(cloud_id_file) else: prev_cloud_id_file = cloud_id_file util.sym_link(f"{cloud_id_file}-{cloud_id}", cloud_id_file, force=True) if prev_cloud_id_file != cloud_id_file: util.del_file(prev_cloud_id_file) write_json(json_sensitive_file, processed_data, mode=0o600) json_file = os.path.join(self.paths.run_dir, INSTANCE_JSON_FILE) # World readable write_json(json_file, redact_sensitive_keys(processed_data)) return True
def handle(name, _cfg, _cloud, log, _args): log.debug('Attempting to configure the boot list.') system_info = util.system_info() if 'aix' in system_info['platform'].lower(): try: boot_devices = util.subp([BOOTINFO, '-b'])[0].strip().split('\n') out = run_bootlist_command(log, mode='normal', fmt='logical', boot_devices=boot_devices, cmd_location=BOOTLIST_AIX) log.debug(out) return except util.ProcessExecutionError: util.logexc(log, 'Failed to set the bootlist.') raise if is_powerkvm(log): log.debug('Not configuring the boot list since this VM is running on ' 'PowerKVM.') return architecture = system_info['uname'][4] if 'ppc' not in architecture: return orig_normal_bootlist = run_bootlist_command(log, mode='normal', fmt='ofpath').split('\n') orig_service_bootlist = run_bootlist_command(log, mode='service', fmt='ofpath').split('\n') (dist, vers) = system_info['dist'][:2] major_release = (int)(vers.split('.')[0]) device_paths = [] if dist.startswith('Red Hat Enterprise Linux'): log.debug('RHEL version: %s' % vers) if major_release == 6: device_paths = get_device_paths_from_file(log, '/etc/yaboot.conf') else: device_paths = [get_last_booted_device(log)] elif dist.startswith('SUSE Linux Enterprise'): log.debug('SLES version: %s' % vers) if major_release == 11: device_paths = get_device_paths_from_file(log, '/etc/lilo.conf') else: device_paths = [get_last_booted_device(log)] elif dist.startswith('Ubuntu'): log.debug('Ubuntu version: %s' % vers) device_paths = [get_last_booted_device(log)] else: raise NotImplementedError('Not yet implemented for (%s, %s)' % (dist, vers)) # Running the bootlist command using the ofpath format requires ofpathname # to work properly. On RHEL 6.4, ofpathname may fail if the 'bc' package # is not installed, causing bootlist to have some strange behavior when # setting the bootlist. In order to avoid setting an invalid bootlist, we # will fail if ofpathname does not work properly. # Example: `bootlist -m both -o` returns: # ofpathname: 'bc' command not found. Please, install 'bc' package try: util.subp([OFPATHNAME]) except util.ProcessExecutionError: util.logexc( log, 'The ofpathname command returned errors. Since the ' 'bootlist command relies on ofpathname, these errors need ' 'to be resolved.') raise if len(device_paths) > 0: out = run_bootlist_command(log, mode='both', fmt='ofpath', boot_devices=device_paths) log.debug(out) successful = (verify_bootlist(log, 'normal', orig_normal_bootlist) and verify_bootlist(log, 'service', orig_service_bootlist)) if not successful: msg = 'Failed to update the bootlist properly.' log.error(msg) raise Exception(msg)
def handle(name, _cfg, _cloud, log, _args): log.debug('Attempting to configure the boot list.') system_info = util.system_info() if 'aix' in system_info['platform'].lower(): try: boot_devices = util.subp([BOOTINFO, '-b'])[0].strip().split('\n') out = run_bootlist_command(log, mode='normal', fmt='logical', boot_devices=boot_devices, cmd_location=BOOTLIST_AIX) log.debug(out) return except util.ProcessExecutionError: util.logexc(log, 'Failed to set the bootlist.') raise if is_powerkvm(log): log.debug('Not configuring the boot list since this VM is running on ' 'PowerKVM.') return architecture = system_info['uname'][4] if 'ppc' not in architecture: return orig_normal_bootlist = run_bootlist_command(log, mode='normal', fmt='ofpath').split('\n') orig_service_bootlist = run_bootlist_command(log, mode='service', fmt='ofpath').split('\n') (dist, vers) = system_info['dist'][:2] major_release = (int)(vers.split('.')[0]) device_paths = [] if dist.startswith('Red Hat Enterprise Linux'): log.debug('RHEL version: %s' % vers) if major_release == 6: device_paths = get_device_paths_from_file(log, '/etc/yaboot.conf') else: device_paths = [get_last_booted_device(log)] elif dist.startswith('SUSE Linux Enterprise'): log.debug('SLES version: %s' % vers) if major_release == 11: device_paths = get_device_paths_from_file(log, '/etc/lilo.conf') else: device_paths = [get_last_booted_device(log)] elif dist.startswith('Ubuntu'): log.debug('Ubuntu version: %s' % vers) device_paths = [get_last_booted_device(log)] else: raise NotImplementedError('Not yet implemented for (%s, %s)' % (dist, vers)) # Running the bootlist command using the ofpath format requires ofpathname # to work properly. On RHEL 6.4, ofpathname may fail if the 'bc' package # is not installed, causing bootlist to have some strange behavior when # setting the bootlist. In order to avoid setting an invalid bootlist, we # will fail if ofpathname does not work properly. # Example: `bootlist -m both -o` returns: # ofpathname: 'bc' command not found. Please, install 'bc' package try: util.subp([OFPATHNAME]) except util.ProcessExecutionError: util.logexc(log, 'The ofpathname command returned errors. Since the ' 'bootlist command relies on ofpathname, these errors need ' 'to be resolved.') raise if len(device_paths) > 0: out = run_bootlist_command(log, mode='both', fmt='ofpath', boot_devices=device_paths) log.debug(out) successful = (verify_bootlist(log, 'normal', orig_normal_bootlist) and verify_bootlist(log, 'service', orig_service_bootlist)) if not successful: msg = 'Failed to update the bootlist properly.' log.error(msg) raise Exception(msg)
import cloudinit.sources.helpers.vultr as vultr LOG = log.getLogger(__name__) BUILTIN_DS_CONFIG = { 'url': 'http://169.254.169.254', 'retries': 30, 'timeout': 2, 'wait': 2, 'user-agent': 'Cloud-Init/%s - OS: %s Variant: %s' % (version.version_string(), util.system_info()['system'], util.system_info()['variant']) } class DataSourceVultr(sources.DataSource): dsname = 'Vultr' def __init__(self, sys_cfg, distro, paths): super(DataSourceVultr, self).__init__(sys_cfg, distro, paths) self.ds_cfg = util.mergemanydict([ util.get_cfg_by_path(sys_cfg, ["datasource", "Vultr"], {}), BUILTIN_DS_CONFIG ])
def available(target=None): sysconfig = available_sysconfig(target=target) nm = available_nm(target=target) return (util.system_info()['variant'] in KNOWN_DISTROS and any([nm, sysconfig]))
from cloudinit import sources, util, version LOG = log.getLogger(__name__) BUILTIN_DS_CONFIG = { "url": "http://169.254.169.254", "retries": 30, "timeout": 10, "wait": 5, "user-agent": "Cloud-Init/%s - OS: %s Variant: %s" % ( version.version_string(), util.system_info()["system"], util.system_info()["variant"], ), } class DataSourceVultr(sources.DataSource): dsname = "Vultr" def __init__(self, sys_cfg, distro, paths): super(DataSourceVultr, self).__init__(sys_cfg, distro, paths) self.ds_cfg = util.mergemanydict([ util.get_cfg_by_path(sys_cfg, ["datasource", "Vultr"], {}), BUILTIN_DS_CONFIG, ])