def __init__( self, silent=False, callback=None, migrate=False, reset_cache=False, ): self.reset_cache = reset_cache if reset_cache: cache.reset() self.pool = iocage_lib.ioc_json.IOCJson( silent=silent, checking_datasets=True ).json_get_value("pool") self.callback = callback self.silent = silent self.__check_fd_mount__() self.__check_datasets__() self.pool_root_dataset = Dataset(self.pool, cache=reset_cache) self.iocage_dataset = Dataset( os.path.join(self.pool, 'iocage'), cache=reset_cache ) if migrate: self.__check_migrations__() self.__clean_files__()
def __destroy_dataset__(self, dataset): """Destroys the given datasets and snapshots.""" ds = Dataset(dataset) ds.destroy(recursive=True, force=True) if dataset.endswith('jails'): # We need to make sure we remove the snapshots from the RELEASES # We are purposely not using -R as those will hit templates # and we are not using IOCSnapshot for perfomance for snap in SnapshotListableResource().release_snapshots: snap.destroy(recursive=True, force=True) if 'templates' in dataset: if dataset.endswith('/root/root'): # They named their jail root... uuid = 'root' else: uuid = dataset.rsplit('/', 1)[1] jail_datasets = Dataset( f'{self.pool}/iocage/jails' ).get_dependents() for jail in jail_datasets: with iocage_lib.ioc_exceptions.ignore_exceptions( BaseException): j_conf = iocage_lib.ioc_json.IOCJson( self.path, suppress_log=True ).json_get_value('all') source_template = j_conf['source_template'] if source_template == uuid: self.__destroy_parse_datasets__( jail.name, clean=True )
def __destroy_parse_datasets__(self, path, clean=False, stop=True): """ Parses the datasets before calling __destroy_dataset__ with each entry. """ try: datasets = [ d.name for d in Dataset(path).get_dependents(depth=None) ] except (Exception, SystemExit): # Dataset can't be found, we don't care return if "templates" in path or "release" in path: # This will tell __stop_jails__ to actually try stopping on # a /root root = True else: # Otherwise we only stop when the uuid is the last entry in # the jails path. root = False if stop: try: self.__stop_jails__(datasets, path, root) except (RuntimeError, FileNotFoundError, SystemExit): # If a bad or missing configuration for a jail, this will # get in the way. pass single = True if len(datasets) == 1 else False if single: # Is actually a single dataset. self.__destroy_dataset__(datasets[0]) else: datasets.reverse() for dataset in datasets: ds = Dataset(dataset) if not ds.exists: continue self.path = ds.path try: self.j_conf = iocage_lib.ioc_json.IOCJson( self.path, suppress_log=True ).json_get_value('all') except BaseException: # Isn't a jail, iocage will throw a variety of # exceptions or SystemExit pass self.__destroy_dataset__(dataset) self.__destroy_leftovers__(dataset, clean=clean)
def list_datasets(self): """Lists the datasets of given type.""" if self.list_type == "base": ds = Dataset(f"{self.pool}/iocage/releases").get_dependents() elif self.list_type == "template": ds = Dataset(f"{self.pool}/iocage/templates").get_dependents() else: ds = Dataset(f"{self.pool}/iocage/jails").get_dependents() ds = list(ds) if self.list_type in ('all', 'basejail', 'template'): if self.quick: _all = self.list_all_quick(ds) else: _all = self.list_all(ds) return _all elif self.list_type == "uuid": jails = {} for jail in ds: uuid = jail.name.rsplit("/", 1)[-1] try: jails[uuid] = jail.properties["mountpoint"] except KeyError: iocage_lib.ioc_common.logit( { 'level': 'ERROR', 'message': f'{jail.name} mountpoint is ' 'misconfigured. Please correct this.' }, _callback=self.callback, silent=self.silent) template_datasets = Dataset( f'{self.pool}/iocage/templates').get_dependents() for template in template_datasets: uuid = template.name.rsplit("/", 1)[-1] jails[uuid] = template.properties['mountpoint'] return jails elif self.list_type == "base": bases = self.list_bases(ds) return bases
def child_test(iocroot, name, _type, force=False, recursive=False): """Tests for dependent children""" path = None children = [] paths = [ f"{iocroot}/jails/{name}/root", f"{iocroot}/releases/{name}", f"{iocroot}/templates/{name}/root" ] for p in paths: if os.path.isdir(p): path = p children = Dataset(path).snapshots_recursive() break if path is None: if not force: ioc_common.logit({ "level": "WARNING", "message": "Partial UUID/NAME supplied, cannot check for " "dependant jails." }) if not click.confirm("\nProceed?"): exit() else: return _children = [] for child in children: _name = child.name _children.append(f" {_name}\n") sort = ioc_common.ioc_sort("", "name", data=_children) _children.sort(key=sort) if len(_children) != 0: if not force and not recursive: ioc_common.logit({ "level": "WARNING", "message": f"\n{name} has dependent jails" " (who may also have dependents)," " use --recursive to destroy: " }) ioc_common.logit({ "level": "WARNING", "message": "".join(_children) }) exit(1) else: return _children
def __init__(self, callback=None): self.pool = iocage_lib.ioc_json.IOCJson().json_get_value('pool') self.iocroot = iocage_lib.ioc_json.IOCJson( self.pool).json_get_value('iocroot') self.callback = callback self.iocroot_datasets = [ d.name for d in Dataset(os.path.join(self.pool, 'iocage')).get_dependents() ] self.path = None self.j_conf = None
def clean_images(self): """Destroys the images dataset""" iocage_lib.ioc_common.logit( { 'level': 'INFO', 'message': 'Cleaning iocage/images' }, _callback=self.callback, silent=self.silent) Dataset(f'{self.pool}/iocage/images').destroy(True, True)
def tmp_dataset_checks(_callback, silent): tmp_dataset = Dataset('/tmp', cache=False) if tmp_dataset.exists: tmp_val = tmp_dataset.properties['exec'] if tmp_val == 'off': iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': f'{tmp_dataset.name} needs exec=on!' }, _callback=_callback, silent=silent)
def run_debug(self): os.makedirs(self.path, exist_ok=True) self.run_host_debug() jails = Dataset( os.path.join(self.pool, 'iocage/jails') ).get_dependents() templates = Dataset( os.path.join(self.pool, 'iocage/templates') ).get_dependents() for jail in jails: jail_path = jail.path jail = jail.name.rsplit('/', 1)[-1] self.run_jail_debug(jail, jail_path) for template in templates: template_path = template.path template = template.name.rsplit('/', 1)[-1] self.run_jail_debug(template, template_path)
def __check_datasets__(self): """ Loops through the required datasets and if there is root privilege will then create them. """ datasets = ("iocage", "iocage/download", "iocage/images", "iocage/jails", "iocage/log", "iocage/releases", "iocage/templates") for dataset in datasets: zfs_dataset_name = f"{self.pool}/{dataset}" try: ds = Dataset(zfs_dataset_name) if not ds.exists: raise ZFSException(-1, 'Dataset does not exist') elif not ds.path: iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": f'Please set a mountpoint on {ds.name}' }, _callback=self.callback) except ZFSException: # Doesn't exist if os.geteuid() != 0: raise RuntimeError("Run as root to create missing" " datasets!") iocage_lib.ioc_common.logit( { "level": "INFO", "message": f"Creating {self.pool}/{dataset}" }, _callback=self.callback, silent=self.silent) dataset_options = { "compression": "lz4", "aclmode": "passthrough", "aclinherit": "passthrough" } ds = Dataset(zfs_dataset_name) ds.create({'properties': dataset_options}) prop = ds.properties.get("exec") if prop != "on": iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": f"Dataset \"{dataset}\" has " f"exec={prop} (should be on)" }, _callback=self.callback)
def __stop_jails__(self, datasets, path=None, root=False): for dataset in datasets: if 'jails' not in dataset: continue dataset = Dataset(dataset) if not dataset.exists: # Keeping old behavior, retrieving props safely continue if dataset.properties['type'] != 'filesystem': continue mountpoint = dataset.path if mountpoint == 'legacy': continue # This is just to setup a replacement. path = path.replace('templates', 'jails') try: uuid = dataset.name.partition(path)[2].lstrip('/') if not uuid: # jails dataset # This will trigger a false IndexError if we don't continue continue if uuid.endswith('/root/root'): # They named their jail root... uuid = 'root' elif uuid.endswith('/root'): uuid = uuid.rsplit('/root', 1)[0] except IndexError: # A RELEASE dataset return # We want the real path now. _path = mountpoint.replace('/root', '', 1) if (dataset.name.endswith(uuid) or root) and _path is not None: with iocage_lib.ioc_exceptions.ignore_exceptions( BaseException): # Can be missing/corrupt/whatever configuration # Since it's being nuked anyways, we don't care. iocage_lib.ioc_stop.IOCStop( uuid, _path, silent=True, suppress_exception=True )
def fetch_update(self, cli=False, uuid=None): """This calls 'freebsd-update' to update the fetched RELEASE.""" tmp_dataset = Dataset('/tmp') if tmp_dataset.exists: tmp_val = tmp_dataset.properties['exec'] if tmp_val == 'off': iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': f'{tmp_dataset.name} needs exec=on!' }, _callback=self.callback, silent=self.silent) if cli: cmd = [ "mount", "-t", "devfs", "devfs", f"{self.iocroot}/jails/{uuid}/root/dev" ] mount = f'{self.iocroot}/jails/{uuid}' mount_root = f'{mount}/root' iocage_lib.ioc_common.logit( { "level": "INFO", "message": f"\n* Updating {uuid} to the latest patch" " level... " }, _callback=self.callback, silent=self.silent) else: cmd = [ "mount", "-t", "devfs", "devfs", f"{self.iocroot}/releases/{self.release}/root/dev" ] mount = f'{self.iocroot}/releases/{self.release}' mount_root = f'{mount}/root' iocage_lib.ioc_common.logit( { "level": "INFO", "message": f"\n* Updating {self.release} to the latest patch" " level... " }, _callback=self.callback, silent=self.silent) su.Popen(cmd).communicate() shutil.copy("/etc/resolv.conf", f"{mount_root}/etc/resolv.conf") path = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:'\ '/usr/local/bin:/root/bin' fetch_env = { 'UNAME_r': self.release, 'PAGER': '/bin/cat', 'PATH': path, 'PWD': '/', 'HOME': '/', 'TERM': 'xterm-256color' } if os.path.isfile(f"{mount_root}/etc/freebsd-update.conf"): if self.verify: f = "https://raw.githubusercontent.com/freebsd/freebsd" \ "/master/usr.sbin/freebsd-update/freebsd-update.sh" tmp = tempfile.NamedTemporaryFile(delete=False) with urllib.request.urlopen(f) as fbsd_update: tmp.write(fbsd_update.read()) tmp.close() os.chmod(tmp.name, 0o755) fetch_name = tmp.name else: fetch_name = f"{mount_root}/usr/sbin/freebsd-update" fetch_cmd = [ fetch_name, "-b", mount_root, "-d", f"{mount_root}/var/db/freebsd-update/", "-f", f"{mount_root}/etc/freebsd-update.conf", "--not-running-from-cron", "fetch" ] with iocage_lib.ioc_exec.IOCExec(fetch_cmd, f"{self.iocroot}/jails/{uuid}", uuid=uuid, unjailed=True, callback=self.callback, su_env=fetch_env) as _exec: try: iocage_lib.ioc_common.consume_and_log( _exec, callback=self.callback) except iocage_lib.ioc_exceptions.CommandFailed as e: iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': b''.join(e.message) }, _callback=self.callback, silent=self.silent) try: fetch_install_cmd = [ fetch_name, "-b", mount_root, "-d", f"{mount_root}/var/db/freebsd-update/", "-f", f"{mount_root}/etc/freebsd-update.conf", "install" ] with iocage_lib.ioc_exec.IOCExec( fetch_install_cmd, f"{self.iocroot}/jails/{uuid}", uuid=uuid, unjailed=True, callback=self.callback, su_env=fetch_env) as _exec: try: iocage_lib.ioc_common.consume_and_log( _exec, callback=self.callback) except iocage_lib.ioc_exceptions.CommandFailed as e: iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': b''.join(e.message) }, _callback=self.callback, silent=self.silent) finally: new_release = iocage_lib.ioc_common.get_jail_freebsd_version( mount_root, self.release) if self.release != new_release: jails = iocage_lib.ioc_list.IOCList( 'uuid', hdr=False).list_datasets() if not cli: for jail, path in jails.items(): _json = iocage_lib.ioc_json.IOCJson(path) props = _json.json_get_value('all') if props['basejail'] and self.release.rsplit( '-', 1)[0] in props['release']: props['release'] = new_release _json.json_write(props) else: _json = iocage_lib.ioc_json.IOCJson(jails[uuid]) props = _json.json_get_value('all') props['release'] = new_release _json.json_write(props) if self.verify: # tmp only exists if they verify SSL certs if not tmp.closed: tmp.close() os.remove(tmp.name) try: if not cli: # Why this sometimes doesn't exist, we may never know. os.remove(f"{mount_root}/etc/resolv.conf") except OSError: pass su.Popen(["umount", f"{mount_root}/dev"]).communicate()
def list_all(self, jails): """List all jails.""" self.full = True if self.plugin else self.full jail_list = [] plugin_index_data = {} for jail in jails: try: mountpoint = jail.properties['mountpoint'] except KeyError: iocage_lib.ioc_common.logit( { 'level': 'ERROR', 'message': f'{jail.name} mountpoint is misconfigured. ' 'Please correct this.' }, _callback=self.callback, silent=self.silent) continue try: conf = iocage_lib.ioc_json.IOCJson(mountpoint).json_get_value( 'all') state = '' except (Exception, SystemExit): # Jail is corrupt, we want all the keys to exist. # So we will take the defaults and let the user # know that they are not correct. def_props = iocage_lib.ioc_json.IOCJson().json_get_value( 'all', default=True) conf = {x: 'N/A' for x in def_props} conf['host_hostuuid'] = \ f'{jail.name.split("/")[-1]}' conf['release'] = 'N/A' state = 'CORRUPT' jid = '-' if self.basejail_only and not iocage_lib.ioc_common.check_truthy( conf.get('basejail', 0)): continue uuid_full = conf["host_hostuuid"] uuid = uuid_full if not self.full: # We only want to show the first 8 characters of a UUID, # if it's not a UUID, we will show the whole name no matter # what. try: uuid = str(_uuid.UUID(uuid, version=4))[:8] except ValueError: # We leave the "uuid" untouched, as it's not a valid # UUID, but instead a named jail. pass full_ip4 = conf["ip4_addr"] ip6 = conf["ip6_addr"] try: short_ip4 = ",".join([ item.split("|")[1].split("/")[0] for item in full_ip4.split(",") ]) except IndexError: short_ip4 = full_ip4 if full_ip4 != "none" else "-" boot = 'on' if iocage_lib.ioc_common.check_truthy( conf.get('boot', 0)) else 'off' jail_type = conf["type"] full_release = conf["release"] basejail = 'yes' if iocage_lib.ioc_common.check_truthy( conf.get('basejail', 0)) else 'no' if "HBSD" in full_release: full_release = re.sub(r"\W\w.", "-", full_release) full_release = full_release.replace("--SD", "-STABLE-HBSD") short_release = full_release.rstrip("-HBSD") else: short_release = "-".join(full_release.rsplit("-")[:2]) if full_ip4 == "none": full_ip4 = "-" if ip6 == "none": ip6 = "-" # Will be set already by a corrupt jail status = False if state != 'CORRUPT': status, jid = self.list_get_jid(uuid_full) if status: state = "up" else: state = "down" if conf["type"] == "template": template = "-" else: jail_root = Dataset(f'{jail.name}/root') _origin_property = jail_root.properties.get('origin') if _origin_property: template = _origin_property template = template.rsplit("/root@", 1)[0].rsplit("/", 1)[-1] else: template = "-" if "release" in template.lower() or "stable" in template.lower(): template = "-" if iocage_lib.ioc_common.check_truthy( conf['dhcp']) and status and os.geteuid() == 0: interface = conf["interfaces"].split(",")[0].split(":")[0] if interface == "vnet0": # Inside jails they are epairNb interface = f"{interface.replace('vnet', 'epair')}b" short_ip4 = "DHCP" full_ip4_cmd = [ "jexec", f"ioc-{uuid_full.replace('.', '_')}", "ifconfig", interface, "inet" ] try: out = su.check_output(full_ip4_cmd) full_ip4 = f'{interface}|' \ f'{out.splitlines()[2].split()[1].decode()}' except (su.CalledProcessError, IndexError) as e: short_ip4 += '(Network Issue)' if isinstance(e, su.CalledProcessError): full_ip4 = f'DHCP - Network Issue: {e}' else: full_ip4 = f'DHCP - Failed Parsing: {e}' elif iocage_lib.ioc_common.check_truthy( conf['dhcp']) and not status: short_ip4 = "DHCP" full_ip4 = "DHCP (not running)" elif iocage_lib.ioc_common.check_truthy( conf['dhcp']) and os.geteuid() != 0: short_ip4 = "DHCP" full_ip4 = "DHCP (running -- address requires root)" # Append the JID and the NAME to the table if self.full and self.plugin: if jail_type != "plugin" and jail_type != "pluginv2": # We only want plugin type jails to be apart of the # list continue try: with open(f"{mountpoint}/plugin/ui.json", "r") as u: # We want to ensure that we show the correct NAT # ports for nat based plugins and when NAT isn't # desired, we don't show them at all. In all these # variable values, what persists across NAT/DHCP/Static # ip based plugins is that the internal ports of the # jail don't change. For example if a plugin jail has # nginx running on port 4000, it will still want to # have it running on 4000 regardless of the fact # how user configures to start the plugin jail. We take # this fact, and search for an explicit specified port # number in the admin portal, if none is found, that # means that it is ( 80 - default for http ). nat_forwards_dict = {} nat_forwards = conf.get('nat_forwards', 'none') for rule in nat_forwards.split( ',') if nat_forwards != 'none' else (): # Rule can be proto(port), proto(in/out), port if rule.isdigit(): jail = host = rule else: rule = rule.split('(')[-1].strip(')') if ':' in rule: jail, host = rule.split(':') else: # only one port provided jail = host = rule nat_forwards_dict[int(jail)] = int(host) if not conf.get('nat'): all_ips = map( lambda v: 'DHCP' if 'dhcp' in v.lower() else v, [ i.split('|')[-1].split('/')[0].strip() for i in full_ip4.split(',') ]) else: default_gateways = \ iocage_lib.ioc_common.get_host_gateways() all_ips = [ f['addr'] for k in default_gateways if default_gateways[k]['interface'] for f in netifaces.ifaddresses( default_gateways[k]['interface']) [netifaces.AF_INET if k == 'ipv4' else netifaces.AF_INET6] ] ui_data = json.load(u) admin_portal = ui_data["adminportal"] admin_portals = [] for portal in admin_portal.split(','): if conf.get('nat'): portal_uri = urllib.parse.urlparse(portal) portal_port = portal_uri.port or 80 # We do this safely as it's possible # dev hasn't added it to plugin's json yet nat_port = nat_forwards_dict.get(portal_port) if nat_port: uri = portal_uri._replace( netloc=f'{portal_uri._hostinfo[0]}:' f'{nat_port}').geturl() else: uri = portal else: uri = portal admin_portals.append(','.join( map(lambda v: uri.replace('%%IP%%', v), all_ips))) admin_portal = ','.join(admin_portals) try: ph = ui_data["adminportal_placeholders"].items() if ph and not status: admin_portal = f"{uuid} is not running!" else: for placeholder, prop in ph: admin_portal = admin_portal.replace( placeholder, iocage_lib.ioc_json.IOCJson( mountpoint).json_plugin_get_value( prop.split("."))) except KeyError: pass except iocage_lib.ioc_exceptions.CommandNeedsRoot: admin_portal = "Admin Portal requires root" except iocage_lib.ioc_exceptions.CommandFailed as e: admin_portal = b' '.join(e.message).decode() doc_url = ui_data.get('docurl', '-') except FileNotFoundError: # They just didn't set a admin portal. admin_portal = doc_url = '-' jail_list.append([ jid, uuid, boot, state, jail_type, full_release, full_ip4, ip6, template, admin_portal, doc_url ]) if self.plugin_data: if conf['plugin_repository'] not in plugin_index_data: repo_obj = iocage_lib.ioc_plugin.IOCPlugin( git_repository=conf['plugin_repository']) if not os.path.exists(repo_obj.git_destination): repo_obj.pull_clone_git_repo() with open( os.path.join(repo_obj.git_destination, 'INDEX')) as f: plugin_index_data[conf['plugin_repository']] = \ json.loads(f.read()) index_plugin_conf = plugin_index_data[ conf['plugin_repository']].get(conf['plugin_name'], {}) jail_list[-1].extend([ conf['plugin_name'], conf['plugin_repository'], index_plugin_conf.get('primary_pkg'), index_plugin_conf.get('category'), ]) elif self.full: jail_list.append([ jid, uuid, boot, state, jail_type, full_release, full_ip4, ip6, template, basejail ]) else: jail_list.append([jid, uuid, state, short_release, short_ip4]) list_type = "list_full" if self.full else "list_short" sort = iocage_lib.ioc_common.ioc_sort(list_type, self.sort, data=jail_list) jail_list.sort(key=sort) # return the list... if not self.header: flat_jail = [j for j in jail_list] return flat_jail # Prints the table table = texttable.Texttable(max_width=0) if self.full: if self.plugin: table.set_cols_dtype( ["t", "t", "t", "t", "t", "t", "t", "t", "t", "t", "t"]) jail_list.insert(0, [ "JID", "NAME", "BOOT", "STATE", "TYPE", "RELEASE", "IP4", "IP6", "TEMPLATE", "PORTAL", "DOC_URL" ]) else: # We get an infinite float otherwise. table.set_cols_dtype( ["t", "t", "t", "t", "t", "t", "t", "t", "t", "t"]) jail_list.insert(0, [ "JID", "NAME", "BOOT", "STATE", "TYPE", "RELEASE", "IP4", "IP6", "TEMPLATE", 'BASEJAIL' ]) else: # We get an infinite float otherwise. table.set_cols_dtype(["t", "t", "t", "t", "t"]) jail_list.insert(0, ["JID", "NAME", "STATE", "RELEASE", "IP4"]) table.add_rows(jail_list) return table.draw()
def upgrade_jail(self): tmp_dataset = Dataset('/tmp') if tmp_dataset.exists: tmp_val = tmp_dataset.properties['exec'] if tmp_val == 'off': iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': f'{tmp_dataset.name} needs exec=on!' }, _callback=self.callback, silent=self.silent) if "HBSD" in self.freebsd_version: su.Popen(["hbsd-upgrade", "-j", self.jid]).communicate() return if not os.path.isfile(f"{self.path}/etc/freebsd-update.conf"): return self.__upgrade_check_conf__() f_rel = f'{self.new_release.rsplit("-RELEASE")[0]}.0' f = 'https://raw.githubusercontent.com/freebsd/freebsd' \ f'/release/{f_rel}/usr.sbin/freebsd-update/freebsd-update.sh' tmp = None try: tmp = tempfile.NamedTemporaryFile(delete=False) with urllib.request.urlopen(f) as fbsd_update: tmp.write(fbsd_update.read()) tmp.close() os.chmod(tmp.name, 0o755) fetch_cmd = [ tmp.name, "-b", self.path, "-d", f"{self.path}/var/db/freebsd-update/", "-f", f"{self.path}/etc/freebsd-update.conf", "--not-running-from-cron", "--currently-running " f"{self.jail_release}", "-r", self.new_release, "upgrade" ] # FreeNAS MW/Others, this is a best effort as things may require # stdin input, in which case dropping to a tty is the best solution if not self.interactive: with iocage_lib.ioc_exec.IOCExec( fetch_cmd, self.path.replace('/root', ''), uuid=self.uuid, unjailed=True, stdin_bytestring=b'y\n', callback=self.callback, ) as _exec: iocage_lib.ioc_common.consume_and_log( _exec, callback=self.callback ) else: iocage_lib.ioc_exec.InteractiveExec( fetch_cmd, self.path.replace('/root', ''), uuid=self.uuid, unjailed=True ) if not os.path.islink(self.freebsd_install_link): msg = f'Upgrade failed, nothing to install after fetch!' iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': msg }, _callback=self.callback, silent=self.silent ) for _ in range(50): # up to 50 invocations to prevent runaway if os.path.islink(self.freebsd_install_link): self.__upgrade_install__(tmp.name) else: break if os.path.islink(self.freebsd_install_link): msg = f'Upgrade failed, freebsd-update won\'t finish!' iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': msg }, _callback=self.callback, silent=self.silent ) new_release = iocage_lib.ioc_common.get_jail_freebsd_version( self.path, self.new_release ) finally: if tmp: if not tmp.closed: tmp.close() os.remove(tmp.name) iocage_lib.ioc_json.IOCJson( self.path.replace('/root', ''), silent=True).json_set_value(f"release={new_release}") return new_release
def fetch_download(self, _list, missing=False): """Creates the download dataset and then downloads the RELEASE.""" dataset = f"{self.iocroot}/download/{self.release}" fresh = False if not os.path.isdir(dataset): fresh = True dataset = f"{self.pool}/iocage/download/{self.release}" ds = Dataset(dataset) if not ds.exists: ds.create({'properties': {'compression': 'lz4'}}) if not ds.mounted: ds.mount() if missing or fresh: release_download_path = os.path.join(self.iocroot, 'download', self.release) for f in _list: if self.hardened: _file = f"{self.server}/{self.root_dir}/{f}" if f == "lib32.txz": continue else: _file = f"{self.server}/{self.root_dir}/" \ f"{self.release}/{f}" if self.auth == "basic": r = requests.get(_file, auth=(self.user, self.password), verify=self.verify, stream=True) elif self.auth == "digest": r = requests.get(_file, auth=requests.auth.HTTPDigestAuth( self.user, self.password), verify=self.verify, stream=True) else: r = requests.get(_file, verify=self.verify, stream=True) status = r.status_code == requests.codes.ok if not status: r.raise_for_status() with open(os.path.join(release_download_path, f), 'wb') as txz: file_size = int(r.headers['Content-Length']) chunk_size = 1024 * 1024 total = file_size / chunk_size start = time.time() dl_progress = 0 last_progress = 0 for i, chunk in enumerate( r.iter_content(chunk_size=chunk_size), 1): if chunk: elapsed = time.time() - start dl_progress += len(chunk) txz.write(chunk) progress = float(i) / float(total) if progress >= 1.: progress = 1 progress = round(progress * 100, 0) if progress != last_progress: text = self.update_progress( progress, f'Downloading: {f}', elapsed, chunk_size) if progress % 10 == 0: # Not for user output, but for callback # heartbeats iocage_lib.ioc_common.logit( { 'level': 'INFO', 'message': text.rstrip() }, _callback=self.callback, silent=True) last_progress = progress start = time.time()
def fetch_release(self, _list=False): """Small wrapper to choose the right fetch.""" if self.http and not self._file: if self.eol and self.verify: eol = self.__fetch_eol_check__() else: eol = [] if self.release: iocage_lib.ioc_common.check_release_newer( self.release, callback=self.callback, silent=self.silent, major_only=True, ) rel = self.fetch_http_release(eol, _list=_list) if _list: return rel elif self._file: # Format for file directory should be: root-dir/RELEASE/*.txz if not self.root_dir: iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": "Please supply --root-dir or -d." }, _callback=self.callback, silent=self.silent) if self.release is None: iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": "Please supply a RELEASE!" }, _callback=self.callback, silent=self.silent) dataset = f"{self.iocroot}/download/{self.release}" pool_dataset = f"{self.pool}/iocage/download/{self.release}" if os.path.isdir(dataset): pass else: self.zpool.create_dataset({ 'name': pool_dataset, 'properties': { 'compression': 'lz4' } }) for f in self.files: file_path = os.path.join(self.root_dir, self.release, f) if not os.path.isfile(file_path): ds = Dataset(pool_dataset) ds.destroy(recursive=True, force=True) if f == "MANIFEST": error = f"{f} is a required file!" \ f"\nPlease place it in {self.root_dir}/" \ f"{self.release}" else: error = f"{f}.txz is a required file!" \ f"\nPlease place it in {self.root_dir}/" \ f"{self.release}" iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": error }, _callback=self.callback, silent=self.silent) iocage_lib.ioc_common.logit( { "level": "INFO", "message": f"Copying: {f}... " }, _callback=self.callback, silent=self.silent) shutil.copy(file_path, dataset) if f != "MANIFEST": iocage_lib.ioc_common.logit( { "level": "INFO", "message": f"Extracting: {f}... " }, _callback=self.callback, silent=self.silent) self.fetch_extract(f)
def __destroy_leftovers__(self, dataset, clean=False): """Removes parent datasets and logs.""" uuid = dataset.rsplit('/root', 1)[0].rsplit('/')[-1] if self.path is not None and self.path.endswith('/root'): umount_path = self.path.rsplit('/root', 1)[0] else: umount_path = self.path ds = Dataset(dataset) ds_properties = ds.properties if ds.exists else {} if self.path == '-' or ds_properties.get('type') == 'snapshot': # This is either not mounted or doesn't exist anymore, # we don't care either way. self.path = None if self.path is not None: try: os.remove(f'{self.iocroot}/log/{uuid}-console.log') except FileNotFoundError: pass # Dangling mounts are bad...mmkay? for command in [ ['umount', '-afF', f'{umount_path}/fstab'], ['umount', '-f', f'{umount_path}/root/dev/fd'], ['umount', '-f', f'{umount_path}/root/dev'], ['umount', '-f', f'{umount_path}/root/proc'], ['umount', '-f', f'{umount_path}/root/compat/linux/proc'] ]: su.run(command, stderr=su.PIPE) if self.j_conf is not None: try: release = self.j_conf['cloned_release'] except KeyError: # Thick jails release = self.j_conf['release'] release_snap = Snapshot( f'{self.pool}/iocage/releases/{release}/root@{uuid}' ) if release_snap.exists: release_snap.destroy() else: try: temp = self.j_conf['source_template'] temp_snap = Snapshot( f'{self.pool}/iocage/templates/{temp}@{uuid}' ) if temp_snap.exists: temp_snap.destroy() except KeyError: # Not all jails have this, using slow way of finding this for dataset in self.iocroot_datasets: if 'templates' in dataset: temp_snap = Snapshot( f'{dataset}@{uuid}' ) if temp_snap.exists: temp_snap.destroy() break
class IOCCheck(object): """Checks if the required iocage datasets are present""" def __init__( self, silent=False, callback=None, migrate=False, reset_cache=False, ): self.reset_cache = reset_cache if reset_cache: cache.reset() self.pool = iocage_lib.ioc_json.IOCJson( silent=silent, checking_datasets=True ).json_get_value("pool") self.callback = callback self.silent = silent self.__check_fd_mount__() self.__check_datasets__() self.pool_root_dataset = Dataset(self.pool, cache=reset_cache) self.iocage_dataset = Dataset( os.path.join(self.pool, 'iocage'), cache=reset_cache ) if migrate: self.__check_migrations__() self.__clean_files__() def __clean_files__(self): shutil.rmtree( os.path.join(self.iocage_dataset.path, '.plugin_index'), ignore_errors=True ) def __check_migrations__(self): if not self.iocage_dataset.path.startswith( self.pool_root_dataset.path ): self.iocage_dataset.inherit_property('mountpoint') def __check_datasets__(self): """ Loops through the required datasets and if there is root privilege will then create them. """ datasets = ("iocage", "iocage/download", "iocage/images", "iocage/jails", "iocage/log", "iocage/releases", "iocage/templates") for dataset in datasets: zfs_dataset_name = f"{self.pool}/{dataset}" try: ds = Dataset(zfs_dataset_name, cache=self.reset_cache) if not ds.exists: raise ZFSException(-1, 'Dataset does not exist') elif not ds.path: iocage_lib.ioc_common.logit({ "level": "EXCEPTION", "message": f'Please set a mountpoint on {ds.name}' }, _callback=self.callback) except ZFSException: # Doesn't exist if os.geteuid() != 0: raise RuntimeError("Run as root to create missing" " datasets!") iocage_lib.ioc_common.logit({ "level": "INFO", "message": f"Creating {self.pool}/{dataset}" }, _callback=self.callback, silent=self.silent) dataset_options = { "compression": "lz4", "aclmode": "passthrough", "aclinherit": "passthrough" } with DATASET_CREATION_LOCK: ds = Dataset(zfs_dataset_name, cache=self.reset_cache) if not ds.exists: ds.create({'properties': dataset_options}) prop = ds.properties.get("exec") if prop != "on": iocage_lib.ioc_common.logit({ "level": "EXCEPTION", "message": f"Dataset \"{dataset}\" has " f"exec={prop} (should be on)" }, _callback=self.callback) def __check_fd_mount__(self): """ Checks if /dev/fd is mounted, and if not, give the user a warning. """ if os.path.ismount("/dev/fd"): # all good! return messages = collections.OrderedDict([ ("1-NOTICE", "*" * 80), ("2-WARNING", "fdescfs(5) is not mounted, performance" " may suffer. Please run:"), ("3-INFO", "mount -t fdescfs null /dev/fd"), ("4-WARNING", "You can also permanently mount it in" " /etc/fstab with the following entry:"), ("5-INFO", "fdescfs /dev/fd fdescfs rw 0 0"), ("6-NOTICE", f"{'*' * 80}\n") ]) for level, msg in messages.items(): level = level.partition("-")[2] iocage_lib.ioc_common.logit({ "level": level, "message": msg }, _callback=self.callback, silent=self.silent)
def _create_jail(self, jail_uuid, location): """ Create a snapshot of the user specified RELEASE dataset and clone a jail from that. The user can also specify properties to override the defaults. """ import iocage_lib.ioc_destroy # Circular dep start = False is_template = False source_template = None rtsold_enable = 'NO' if iocage_lib.ioc_common.match_to_dir(self.iocroot, jail_uuid): iocage_lib.ioc_common.logit({ 'level': 'EXCEPTION', 'message': f'Jail: {jail_uuid} already exists!' }) if self.migrate: config = self.config else: try: if self.clone and self.template: iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': 'You cannot clone a template, ' 'use create -t instead.' }, _callback=self.callback, silent=self.silent) elif self.template: _type = "templates" temp_path = f"{self.iocroot}/{_type}/{self.release}" template_config = iocage_lib.ioc_json.IOCJson( temp_path).json_get_value try: cloned_release = template_config('cloned_release') except KeyError: # Thick jails won't have this cloned_release = None source_template = self.release elif self.clone: _type = "jails" clone_path = f"{self.iocroot}/{_type}/{self.release}" clone_config = iocage_lib.ioc_json.IOCJson( clone_path).json_get_value try: cloned_release = clone_config('cloned_release') except KeyError: # Thick jails won't have this cloned_release = None clone_uuid = clone_config("host_hostuuid") else: _type = "releases" rel_path = f"{self.iocroot}/{_type}/{self.release}" if not self.empty: cloned_release = \ iocage_lib.ioc_common.get_jail_freebsd_version( f'{rel_path}/root', self.release ) else: cloned_release = "EMPTY" except (IOError, OSError, FileNotFoundError, UnboundLocalError): # Unintuitevly a missing template will throw a # UnboundLocalError as the missing file will kick the # migration routine for zfs props. We don't need that :) if self.template: raise RuntimeError(f"Template: {self.release} not found!") elif self.clone: if os.path.isdir(f"{self.iocroot}/templates/" f"{self.release}"): iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": "You cannot clone a template, " "use create -t instead." }, _callback=self.callback, silent=self.silent) else: # Yep, self.release is actually the source jail. iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": f"Jail: {self.release} not found!" }, _callback=self.callback, silent=self.silent) else: iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": f"RELEASE: {self.release} not found!" }, _callback=self.callback, silent=self.silent) if not self.clone: if cloned_release is None: cloned_release = self.release config = self.create_config(jail_uuid, cloned_release, source_template) else: clone_config = f"{self.iocroot}/jails/{jail_uuid}/config.json" clone_fstab = f"{self.iocroot}/jails/{jail_uuid}/fstab" clone_etc_hosts = \ f"{self.iocroot}/jails/{jail_uuid}/root/etc/hosts" jail = f"{self.pool}/iocage/jails/{jail_uuid}/root" if self.template: source = f'{self.pool}/iocage/templates/{self.release}@{jail_uuid}' snap_cmd = ['zfs', 'snapshot', '-r', source] if self.thickjail: source = f'{source.split("@")[0]}/root@{jail_uuid}' snap_cmd = ['zfs', 'snapshot', source] try: su.check_call(snap_cmd, stderr=su.PIPE) except su.CalledProcessError: raise RuntimeError(f'Template: {jail_uuid} not found!') if not self.thickjail: su.Popen([ 'zfs', 'clone', '-p', f'{source.split("@")[0]}/root' f'@{jail_uuid}', jail ], stdout=su.PIPE).communicate() else: self.create_thickjail(jail_uuid, source.split('@')[0]) del config['cloned_release'] # self.release is actually the templates name config['release'] = iocage_lib.ioc_json.IOCJson( f'{self.iocroot}/templates/{self.release}').json_get_value( 'release') try: config['cloned_release'] = iocage_lib.ioc_json.IOCJson( f'{self.iocroot}/templates/{self.release}').json_get_value( 'cloned_release') except KeyError: # Thick jails won't have this pass elif self.clone: source = f'{self.pool}/iocage/jails/{self.release}@{jail_uuid}' snap_cmd = ['zfs', 'snapshot', '-r', source] if self.thickjail: source = f'{source.split("@")[0]}/root@{jail_uuid}' snap_cmd = ['zfs', 'snapshot', source] try: su.check_call(snap_cmd, stderr=su.PIPE) except su.CalledProcessError: raise RuntimeError(f'Jail: {jail_uuid} not found!') if not self.thickjail: su.Popen(['zfs', 'clone', source, jail.replace('/root', '')], stdout=su.PIPE).communicate() su.Popen([ 'zfs', 'clone', f'{source.split("@")[0]}/root@' f'{jail_uuid}', jail ], stdout=su.PIPE).communicate() else: self.create_thickjail(jail_uuid, source.split('@')[0]) shutil.copyfile( f'{self.iocroot}/jails/{self.release}/config.json', f'{self.iocroot}/jails/{jail_uuid}/config.json') shutil.copyfile(f'{self.iocroot}/jails/{self.release}/fstab', f'{self.iocroot}/jails/{jail_uuid}/fstab') with open(clone_config, 'r') as _clone_config: config = json.load(_clone_config) # self.release is actually the clones name config['release'] = iocage_lib.ioc_json.IOCJson( f'{self.iocroot}/jails/{self.release}').json_get_value( 'release') try: config['cloned_release'] = iocage_lib.ioc_json.IOCJson( f'{self.iocroot}/jails/{self.release}').json_get_value( 'cloned_release') except KeyError: # Thick jails won't have this pass # Clones are expected to be as identical as possible. for k, v in config.items(): try: v = v.replace(clone_uuid, jail_uuid) if '_mac' in k: # They want a unique mac on start config[k] = 'none' except AttributeError: # Bool props pass config[k] = v else: if not self.empty: dataset = f'{self.pool}/iocage/releases/{self.release}/' \ f'root@{jail_uuid}' try: su.check_call(['zfs', 'snapshot', dataset], stderr=su.PIPE) except su.CalledProcessError: release = os.path.join(self.pool, 'iocage/releases', self.release) if not Dataset(release).exists: raise RuntimeError( f'RELEASE: {self.release} not found!') else: iocage_lib.ioc_common.logit( { 'level': 'EXCEPTION', 'message': f'Snapshot: {dataset} exists!\n' 'Please manually run zfs destroy' f' {dataset} if you wish to ' 'destroy it.' }, _callback=self.callback, silent=self.silent) if not self.thickjail: su.Popen(['zfs', 'clone', '-p', dataset, jail], stdout=su.PIPE).communicate() else: self.create_thickjail(jail_uuid, dataset.split('@')[0]) del config['cloned_release'] else: try: iocage_lib.ioc_common.checkoutput( ['zfs', 'create', '-p', jail], stderr=su.PIPE) except su.CalledProcessError as err: raise RuntimeError(err.output.decode('utf-8').rstrip()) cache.reset() iocjson = iocage_lib.ioc_json.IOCJson(location, silent=True) # This test is to avoid the same warnings during install_packages. if jail_uuid == "default" or jail_uuid == "help": iocage_lib.ioc_destroy.IOCDestroy().__destroy_parse_datasets__( f"{self.pool}/iocage/jails/{jail_uuid}") iocage_lib.ioc_common.logit( { "level": "EXCEPTION", "message": f"You cannot name a jail {jail_uuid}, " "that is a reserved name." }, _callback=self.callback, silent=self.silent) disable_localhost = False for prop in self.props: key, _, value = prop.partition("=") is_true = iocage_lib.ioc_common.check_truthy(value) if key == "boot" and is_true and not self.empty: start = True elif self.plugin and key == "type" and value == "pluginv2": config["type"] = value elif key == 'template' and is_true: iocjson.json_write(config) # Set counts on this. location = location.replace("/jails/", "/templates/") iocjson.json_set_value("type=template") iocjson.json_set_value("template=1") Dataset( os.path.join(self.pool, 'iocage', 'templates', jail_uuid)).set_property('readonly', 'off') # If you supply pkglist and templates without setting the # config's type, you will end up with a type of jail # instead of template like we want. config["type"] = "template" start = False is_template = True elif key == 'ip6_addr': if 'accept_rtadv' in value: if not iocage_lib.ioc_common.lowercase_set( iocage_lib.ioc_common.construct_truthy('vnet') ) & iocage_lib.ioc_common.lowercase_set(self.props): iocage_lib.ioc_common.logit( { 'level': 'WARNING', 'message': 'accept_rtadv requires vnet,' ' enabling!' }, _callback=self.callback, silent=self.silent) config['vnet'] = 1 rtsold_enable = 'YES' elif (key == 'dhcp' and is_true) or (key == 'ip4_addr' and 'DHCP' in value.upper()): if not iocage_lib.ioc_common.lowercase_set( iocage_lib.ioc_common.construct_truthy('vnet') ) & iocage_lib.ioc_common.lowercase_set(self.props): iocage_lib.ioc_common.logit( { 'level': 'WARNING', 'message': 'dhcp requires vnet, enabling!' }, _callback=self.callback, silent=self.silent) config['vnet'] = 1 if not iocage_lib.ioc_common.lowercase_set( iocage_lib.ioc_common.construct_truthy('bpf') ) & iocage_lib.ioc_common.lowercase_set(self.props): iocage_lib.ioc_common.logit( { 'level': 'WARNING', 'message': 'dhcp requires bpf, enabling!' }, _callback=self.callback, silent=self.silent) config['bpf'] = 1 elif key == 'bpf' and is_true: if not iocage_lib.ioc_common.lowercase_set( iocage_lib.ioc_common.construct_truthy('vnet') ) & iocage_lib.ioc_common.lowercase_set(self.props): iocage_lib.ioc_common.logit( { 'level': 'WARNING', 'message': 'bpf requires vnet, enabling!' }, _callback=self.callback, silent=self.silent) config['vnet'] = 1 elif key == 'assign_localhost' and is_true: if iocage_lib.ioc_common.lowercase_set( iocage_lib.ioc_common.construct_truthy('vnet') ) & iocage_lib.ioc_common.lowercase_set(self.props): iocage_lib.ioc_common.logit( { 'level': 'WARNING', 'message': 'assign_localhost only applies to shared' ' IP jails, disabling!' }, _callback=self.callback, silent=self.silent) disable_localhost = True if disable_localhost: self.props = [ p for p in self.props if not p.startswith('assign_localhost') and not p.startswith('localhost_ip') ] if not self.thickconfig: try: del config['assign_localhost'] except KeyError: # They may not have specified this pass try: del config['localhost_ip'] except KeyError: # They may not have specified this pass else: config['assign_localhost'] = 0 config['localhost_ip'] = 0 try: value, config = iocjson.json_check_prop(key, value, config) config[key] = value except RuntimeError as err: iocjson.json_write(config) # Destroy counts on this. iocage_lib.ioc_destroy.IOCDestroy().destroy_jail(location) raise RuntimeError(f"***\n{err}\n***\n") except SystemExit: iocjson.json_write(config) # Destroy counts on this. iocage_lib.ioc_destroy.IOCDestroy().destroy_jail(location) exit(1) # We want these to represent reality on the FS iocjson.fix_properties(config) if not self.plugin: # TODO: Should we probably only write once and maybe at the end # of the function ? iocjson.json_write(config) # Just "touch" the fstab file, since it won't exist and write # /etc/hosts try: etc_hosts_ip_addr = config["ip4_addr"].split("|", 1)[-1].rsplit('/', 1)[0] except KeyError: # No ip4_addr specified during creation pass try: jail_uuid_short = jail_uuid.rsplit(".")[-2] jail_hostname = \ f"{jail_uuid}\t{jail_uuid_short}" except IndexError: # They supplied just a normal tag jail_uuid_short = jail_uuid jail_hostname = jail_uuid # If jail is template, the dataset would be readonly at this point if is_template: Dataset(os.path.join(self.pool, 'iocage/templates', jail_uuid)).set_property('readonly', 'off') if self.empty: open(f"{location}/fstab", "wb").close() config["release"] = "EMPTY" config["cloned_release"] = "EMPTY" iocjson.json_write(config) elif not self.clone: open(f"{location}/fstab", "wb").close() with open( f"{self.iocroot}/" f"{'releases' if not self.template else 'templates'}/" f"{self.release}/root/etc/hosts", "r") as _etc_hosts: with iocage_lib.ioc_common.open_atomic( f"{location}/root/etc/hosts", "w") as etc_hosts: # open_atomic will empty the file, we need these still. for line in _etc_hosts.readlines(): if line.startswith("127.0.0.1"): if config.get('assign_localhost' ) and not config.get('vnet'): l_ip = config.get('localhost_ip', 'none') l_ip = l_ip if l_ip != 'none' else \ iocage_lib.ioc_common.gen_unused_lo_ip() config['localhost_ip'] = l_ip iocjson.json_write(config) # If they are creating multiple jails, we want # this aliased before starting the jail su.run( ['ifconfig', 'lo0', 'alias', f'{l_ip}/32']) line = f'{l_ip}\t\tlocalhost' \ ' localhost.my.domain' \ f' {jail_uuid_short}\n' else: line = f'{line.rstrip()} {jail_uuid_short}\n' etc_hosts.write(line) else: # We want their IP to have the hostname at the end try: if config["ip4_addr"] != "none": final_line =\ f'{etc_hosts_ip_addr}\t{jail_hostname}\n' etc_hosts.write(final_line) except KeyError: # No ip4_addr specified during creation pass else: with open(clone_fstab, "r") as _clone_fstab: with iocage_lib.ioc_common.open_atomic(clone_fstab, "w") as _fstab: # open_atomic will empty the file, we need these still. for line in _clone_fstab.readlines(): _fstab.write(line.replace(clone_uuid, jail_uuid)) with open(clone_etc_hosts, "r") as _clone_etc_hosts: with iocage_lib.ioc_common.open_atomic(clone_etc_hosts, "w") as etc_hosts: # open_atomic will empty the file, we need these still. for line in _clone_etc_hosts.readlines(): etc_hosts.write(line.replace(clone_uuid, jail_uuid)) if not self.empty: self.create_rc(location, config["host_hostname"], config.get('basejail', 0)) if rtsold_enable == 'YES': iocage_lib.ioc_common.set_rcconf(location, "rtsold_enable", rtsold_enable) if self.basejail or self.plugin: basedirs = [ "bin", "boot", "lib", "libexec", "rescue", "sbin", "usr/bin", "usr/include", "usr/lib", "usr/libexec", "usr/sbin", "usr/share", "usr/libdata", "usr/lib32" ] if "-STABLE" in self.release: # HardenedBSD does not have this. basedirs.remove("usr/lib32") for bdir in basedirs: if "-RELEASE" not in self.release and "-STABLE" not in \ self.release: _type = "templates" else: _type = "releases" source = f"{self.iocroot}/{_type}/{self.release}/root/{bdir}" destination = f"{self.iocroot}/jails/{jail_uuid}/root/{bdir}" # This reduces the REFER of the basejail. # Just much faster by almost a factor of 2 than the builtins. su.Popen(["rm", "-r", "-f", destination]).communicate() os.mkdir(destination) iocage_lib.ioc_fstab.IOCFstab(jail_uuid, "add", source, destination, "nullfs", "ro", "0", "0", silent=True) config["basejail"] = 1 iocjson.json_write(config) if not self.plugin: if self.clone: msg = f"{jail_uuid} successfully cloned!" else: msg = f"{jail_uuid} successfully created!" iocage_lib.ioc_common.logit({ "level": "INFO", "message": msg }, _callback=self.callback, silent=self.silent) if self.pkglist: auto_config = config.get('dhcp') or \ config.get('ip_hostname') or \ config.get('nat') if config.get('ip4_addr', 'none') == "none" and \ config.get('ip6_addr', 'none') == "none" and \ not auto_config: iocage_lib.ioc_common.logit( { "level": "WARNING", "message": "You need an IP address for the jail to" " install packages!\n" }, _callback=self.callback, silent=self.silent) else: self.create_install_packages(jail_uuid, location) if start: iocage_lib.ioc_start.IOCStart(jail_uuid, location, silent=self.silent) if is_template: # We have to set readonly back, since we're done with our tasks Dataset(os.path.join(self.pool, 'iocage/templates', jail_uuid)).set_property('readonly', 'on') return jail_uuid
def list_all(self, jails): """List all jails.""" self.full = True if self.plugin else self.full jail_list = [] plugin_index_data = {} for jail in jails: try: mountpoint = jail.properties['mountpoint'] except KeyError: iocage_lib.ioc_common.logit( { 'level': 'ERROR', 'message': f'{jail.name} mountpoint is misconfigured. ' 'Please correct this.' }, _callback=self.callback, silent=self.silent) continue try: conf = iocage_lib.ioc_json.IOCJson(mountpoint).json_get_value( 'all') state = '' except (Exception, SystemExit): # Jail is corrupt, we want all the keys to exist. # So we will take the defaults and let the user # know that they are not correct. def_props = iocage_lib.ioc_json.IOCJson().json_get_value( 'all', default=True) conf = {x: 'N/A' for x in def_props} conf['host_hostuuid'] = \ f'{jail.name.split("/")[-1]}' conf['release'] = 'N/A' state = 'CORRUPT' jid = '-' if self.basejail_only and not iocage_lib.ioc_common.check_truthy( conf.get('basejail', 0)): continue uuid_full = conf["host_hostuuid"] uuid = uuid_full if not self.full: # We only want to show the first 8 characters of a UUID, # if it's not a UUID, we will show the whole name no matter # what. try: uuid = str(_uuid.UUID(uuid, version=4))[:8] except ValueError: # We leave the "uuid" untouched, as it's not a valid # UUID, but instead a named jail. pass full_ip4 = conf["ip4_addr"] ip6 = conf["ip6_addr"] try: short_ip4 = ",".join([ item.split("|")[1].split("/")[0] for item in full_ip4.split(",") ]) except IndexError: short_ip4 = full_ip4 if full_ip4 != "none" else "-" boot = 'on' if iocage_lib.ioc_common.check_truthy( conf.get('boot', 0)) else 'off' jail_type = conf["type"] full_release = conf["release"] basejail = 'yes' if iocage_lib.ioc_common.check_truthy( conf.get('basejail', 0)) else 'no' if "HBSD" in full_release: full_release = re.sub(r"\W\w.", "-", full_release) full_release = full_release.replace("--SD", "-STABLE-HBSD") short_release = full_release.rstrip("-HBSD") else: short_release = "-".join(full_release.rsplit("-")[:2]) if full_ip4 == "none": full_ip4 = "-" if ip6 == "none": ip6 = "-" # Will be set already by a corrupt jail status = False if state != 'CORRUPT': status, jid = self.list_get_jid(uuid_full) if status: state = "up" else: state = "down" if conf["type"] == "template": template = "-" else: jail_root = Dataset(f'{jail.name}/root') if jail_root.exists: _origin_property = jail_root.properties.get('origin') else: _origin_property = None if _origin_property: template = _origin_property template = template.rsplit("/root@", 1)[0].rsplit("/", 1)[-1] else: template = "-" if "release" in template.lower() or "stable" in template.lower(): template = "-" ip_dict = iocage_lib.ioc_common.retrieve_ip4_for_jail(conf, status) full_ip4 = ip_dict['full_ip4'] or full_ip4 short_ip4 = ip_dict['short_ip4'] or short_ip4 # Append the JID and the NAME to the table if self.full and self.plugin: if jail_type != "plugin" and jail_type != "pluginv2": # We only want plugin type jails to be apart of the # list continue try: with open(f"{mountpoint}/plugin/ui.json", "r") as u: ui_data = json.load(u) admin_portal = ','.join( iocage_lib.ioc_common.retrieve_admin_portals( conf, status, ui_data['adminportal'])) try: ph = ui_data["adminportal_placeholders"].items() if ph and not status: admin_portal = f"{uuid} is not running!" else: for placeholder, prop in ph: admin_portal = admin_portal.replace( placeholder, iocage_lib.ioc_json.IOCJson( mountpoint).json_plugin_get_value( prop.split("."))) except KeyError: pass except iocage_lib.ioc_exceptions.CommandNeedsRoot: admin_portal = "Admin Portal requires root" except iocage_lib.ioc_exceptions.CommandFailed as e: admin_portal = b' '.join(e.message).decode() doc_url = ui_data.get('docurl', '-') except FileNotFoundError: # They just didn't set a admin portal. admin_portal = doc_url = '-' jail_list.append([ jid, uuid, boot, state, jail_type, full_release, full_ip4, ip6, template, admin_portal, doc_url ]) if self.plugin_data: if conf['plugin_repository'] not in plugin_index_data: repo_obj = iocage_lib.ioc_plugin.IOCPlugin( git_repository=conf['plugin_repository']) if not os.path.exists(repo_obj.git_destination): try: repo_obj.pull_clone_git_repo() except Exception as e: iocage_lib.ioc_common.logit( { 'level': 'ERROR', 'message': 'Failed to clone ' f'{conf["plugin_repository"]} ' f'for {uuid_full}: {e}' }, _callback=self.callback, silent=self.silent) index_path = os.path.join(repo_obj.git_destination, 'INDEX') if not os.path.exists(index_path): iocage_lib.ioc_common.logit( { 'level': 'ERROR', 'message': f'{index_path} does not exist ' f'for {uuid_full} plugin.' }, _callback=self.callback, silent=self.silent) plugin_index_data[conf['plugin_repository']] = {} else: with open(index_path) as f: plugin_index_data[ conf['plugin_repository']] = json.loads( f.read()) elif not plugin_index_data[conf['plugin_repository']]: iocage_lib.ioc_common.logit( { 'level': 'ERROR', 'message': 'Unable to retrieve INDEX from ' f'{conf["plugin_repository"]} for ' f'{uuid_full}' }, _callback=self.callback, silent=self.silent) index_plugin_conf = plugin_index_data[ conf['plugin_repository']].get(conf['plugin_name'], {}) jail_list[-1].extend([ conf['plugin_name'], conf['plugin_repository'], index_plugin_conf.get('primary_pkg'), index_plugin_conf.get('category'), index_plugin_conf.get('maintainer'), ]) elif self.full: jail_list.append([ jid, uuid, boot, state, jail_type, full_release, full_ip4, ip6, template, basejail ]) else: jail_list.append([jid, uuid, state, short_release, short_ip4]) list_type = "list_full" if self.full else "list_short" sort = iocage_lib.ioc_common.ioc_sort(list_type, self.sort, data=jail_list) jail_list.sort(key=sort) # return the list... if not self.header: flat_jail = [j for j in jail_list] return flat_jail # Prints the table table = texttable.Texttable(max_width=0) if self.full: if self.plugin: table.set_cols_dtype( ["t", "t", "t", "t", "t", "t", "t", "t", "t", "t", "t"]) jail_list.insert(0, [ "JID", "NAME", "BOOT", "STATE", "TYPE", "RELEASE", "IP4", "IP6", "TEMPLATE", "PORTAL", "DOC_URL" ]) else: # We get an infinite float otherwise. table.set_cols_dtype( ["t", "t", "t", "t", "t", "t", "t", "t", "t", "t"]) jail_list.insert(0, [ "JID", "NAME", "BOOT", "STATE", "TYPE", "RELEASE", "IP4", "IP6", "TEMPLATE", 'BASEJAIL' ]) else: # We get an infinite float otherwise. table.set_cols_dtype(["t", "t", "t", "t", "t"]) jail_list.insert(0, ["JID", "NAME", "STATE", "RELEASE", "IP4"]) table.add_rows(jail_list) return table.draw()