Esempio n. 1
0
    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__()
Esempio n. 2
0
    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
                        )
Esempio n. 3
0
    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)
Esempio n. 4
0
    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
Esempio n. 5
0
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
Esempio n. 6
0
 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
Esempio n. 7
0
    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)
Esempio n. 8
0
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)
Esempio n. 9
0
    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)
Esempio n. 10
0
    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)
Esempio n. 11
0
    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
                    )
Esempio n. 12
0
    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()
Esempio n. 13
0
    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()
Esempio n. 14
0
    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
Esempio n. 15
0
    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()
Esempio n. 16
0
    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)
Esempio n. 17
0
    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
Esempio n. 18
0
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)
Esempio n. 19
0
    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
Esempio n. 20
0
    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()