Esempio n. 1
0
    def _checkout_system_container(self,
                                   repo,
                                   name,
                                   img,
                                   deployment,
                                   upgrade,
                                   values=None,
                                   destination=None,
                                   extract_only=False):
        if not values:
            values = {}
        imagebranch = SystemContainers._get_ostree_image_branch(img)

        destination = destination or "%s/%s.%d" % (
            self._get_system_checkout_path(), name, deployment)
        exports = os.path.join(destination, "rootfs/exports")
        unitfile = os.path.join(exports, "service.template")
        if self.user:
            home = os.path.expanduser("~")
            unitfileout = os.path.join(SYSTEMD_UNIT_FILES_DEST_USER % home,
                                       "%s.service" % name)
        else:
            unitfileout = os.path.join(SYSTEMD_UNIT_FILES_DEST,
                                       "%s.service" % name)

        if not upgrade and os.path.exists(unitfileout):
            raise ValueError("The file %s already exists." % unitfileout)

        util.write_out("Extracting to %s" % destination)

        if hasattr(self.args, 'display') and self.args.display:
            return

        if self.user:
            rootfs = os.path.join(destination, "rootfs")
        elif extract_only:
            rootfs = destination
        else:
            # Under Atomic, get the real deployment location.  It is needed to create the hard links.
            try:
                sysroot = OSTree.Sysroot()
                sysroot.load()
                osname = sysroot.get_booted_deployment().get_osname()
                destination = os.path.join("/ostree/deploy/", osname,
                                           os.path.relpath(destination, "/"))
                destination = os.path.realpath(destination)
            except:  #pylint: disable=bare-except
                pass
            rootfs = os.path.join(destination, "rootfs")

        if os.path.exists(destination):
            shutil.rmtree(destination)

        os.makedirs(rootfs)

        rev = repo.resolve_rev(imagebranch, False)[1]
        manifest = self._image_manifest(repo, rev)

        rootfs_fd = None
        try:
            rootfs_fd = os.open(rootfs, os.O_DIRECTORY)
            if manifest is None:
                self._checkout_layer(repo, rootfs_fd, rootfs, rev)
            else:
                layers = SystemContainers.get_layers_from_manifest(
                    json.loads(manifest))
                for layer in layers:
                    rev_layer = repo.resolve_rev(
                        "%s%s" %
                        (OSTREE_OCIIMAGE_PREFIX, layer.replace("sha256:", "")),
                        False)[1]
                    self._checkout_layer(repo, rootfs_fd, rootfs, rev_layer)
            self._do_syncfs(rootfs, rootfs_fd)
        finally:
            if rootfs_fd:
                os.close(rootfs_fd)

        if extract_only:
            return

        # When installing a new system container, set values in this order:
        #
        # 1) What comes from manifest.json, if present, as default value.
        # 2) What the user sets explictly as --set
        # 3) Values for DESTDIR and NAME
        manifest_file = os.path.join(exports, "manifest.json")
        if os.path.exists(manifest_file):
            with open(manifest_file, "r") as f:
                manifest = json.loads(f.read())
                if "defaultValues" in manifest:
                    for key, val in manifest["defaultValues"].items():
                        if key not in values:
                            values[key] = val

        if self.args.setvalues is not None:
            for i in self.args.setvalues:
                split = i.find("=")
                if split < 0:
                    raise ValueError(
                        "Invalid value '%s'.  Expected form NAME=VALUE" % i)
                key, val = i[:split], i[split + 1:]
                values[key] = val

        values["DESTDIR"] = destination
        values["NAME"] = name
        values["EXEC_START"], values[
            "EXEC_STOP"] = self._generate_systemd_startstop_directives(name)

        def _write_template(inputfilename, data, values, outfile):
            template = Template(data)
            result = template.safe_substitute(values)
            if '$' in result.replace("$$", ""):
                missing = {
                    x[1]
                    for x in template.pattern.findall(data, template.flags)
                    if len(x[1]) > 0 and x[1] not in values
                }  # pylint: disable=no-member
                raise ValueError("The template file %s still contains unreplaced values for: %s" % \
                                 (inputfilename, ", ".join(missing)))

            outfile.write(result)

        src = os.path.join(exports, "config.json")
        destination_path = os.path.join(destination, "config.json")
        if os.path.exists(src):
            shutil.copyfile(src, destination_path)
        elif os.path.exists(src + ".template"):
            with open(src + ".template",
                      'r') as infile, open(destination_path, "w") as outfile:
                _write_template(src + ".template", infile.read(), values,
                                outfile)
        else:
            self._generate_default_oci_configuration(destination)

        self._check_oci_configuration_file(destination_path)

        image_manifest = self._image_manifest(repo, rev)
        image_id = rev
        if image_manifest:
            image_manifest = json.loads(image_manifest)
            if 'Digest' in image_manifest:
                image_id = image_manifest['Digest'].replace("sha256:", "")

        with open(os.path.join(destination, "info"), 'w') as info_file:
            info = {
                "image": img,
                "revision": image_id,
                "ostree-commit": rev,
                'created': calendar.timegm(time.gmtime()),
                "values": values
            }
            info_file.write(json.dumps(info, indent=4))

        if os.path.exists(unitfile):
            with open(unitfile, 'r') as infile:
                systemd_template = infile.read()
        else:
            systemd_template = SYSTEMD_UNIT_FILE_DEFAULT_TEMPLATE

        try:
            os.makedirs(os.path.dirname(unitfileout))
        except OSError:
            pass
        with open(unitfileout, "w") as outfile:
            _write_template(unitfile, systemd_template, values, outfile)

        sym = "%s/%s" % (self._get_system_checkout_path(), name)
        if os.path.exists(sym):
            os.unlink(sym)
        os.symlink(destination, sym)

        self._systemctl_command("enable", name)
        if upgrade:
            self._systemctl_command("restart", name)
        else:
            self._systemctl_command("start", name)
        return
Esempio n. 2
0
    def _do_checkout(self, repo, name, img, upgrade, values, destination, unitfileout, tmpfilesout, extract_only, remote):
        if not values:
            values = {}

        remote_path = self._resolve_remote_path(remote)

        _, rev = self._resolve_image(repo, img)
        if rev is None:
            raise ValueError("Image %s not found" % img)

        if remote_path:
            remote_rootfs = os.path.join(remote_path, "rootfs")
            if os.path.exists(remote_rootfs):
                util.write_out("The remote rootfs for this container is set to be %s" % remote_rootfs)
            elif os.path.exists(os.path.join(remote, "usr")): # Assume that the user directly gave the location of the rootfs
                remote_rootfs = remote
                remote_path = os.path.dirname(remote_path) # Use the parent directory as the "container location"
            else:
                raise ValueError("--remote was specified but the given location does not contain a rootfs")
            exports = os.path.join(remote_path, "rootfs/exports")
        else:
            exports = os.path.join(destination, "rootfs/exports")

        unitfile = os.path.join(exports, "service.template")
        tmpfiles = os.path.join(exports, "tmpfiles.template")

        util.write_out("Extracting to %s" % destination)

        # upgrade will not restart the service if it was not already running
        was_service_active = self._is_service_active(name)

        if self.display:
            return

        if self.user:
            rootfs = os.path.join(destination, "rootfs")
        elif extract_only:
            rootfs = destination
        elif remote_path:
            rootfs = os.path.join(remote_path, "rootfs")
        else:
            # Under Atomic, get the real deployment location if we're using the
            # system repo. It is needed to create the hard links.
            if self.get_ostree_repo_location() == '/ostree/repo':
                try:
                    sysroot = OSTree.Sysroot()
                    sysroot.load()
                    osname = sysroot.get_booted_deployment().get_osname()
                    destination = os.path.join("/ostree/deploy/", osname, os.path.relpath(destination, "/"))
                    destination = os.path.realpath(destination)
                except: #pylint: disable=bare-except
                    pass
            rootfs = os.path.join(destination, "rootfs")

        if os.path.exists(destination):
            shutil.rmtree(destination)

        if remote_path:
            os.makedirs(destination)
        else:
            os.makedirs(rootfs)

        manifest = self._image_manifest(repo, rev)

        if not remote_path:
            rootfs_fd = None
            try:
                rootfs_fd = os.open(rootfs, os.O_DIRECTORY)
                if manifest is None:
                    self._checkout_layer(repo, rootfs_fd, rootfs, rev)
                else:
                    layers = SystemContainers.get_layers_from_manifest(json.loads(manifest))
                    for layer in layers:
                        rev_layer = repo.resolve_rev("%s%s" % (OSTREE_OCIIMAGE_PREFIX, layer.replace("sha256:", "")), True)[1]
                        if not rev_layer:
                            raise ValueError("Layer not found: %s.  Please pull again the image" % layer.replace("sha256:", ""))

                        self._checkout_layer(repo, rootfs_fd, rootfs, rev_layer)
                self._do_syncfs(rootfs, rootfs_fd)
            finally:
                if rootfs_fd:
                    os.close(rootfs_fd)

        if extract_only:
            return

        if self.user:
            values["RUN_DIRECTORY"] = os.environ.get("XDG_RUNTIME_DIR", "/run/user/%s" % (os.getuid()))
            values["STATE_DIRECTORY"] = "%s/.data" % HOME
        else:
            values["RUN_DIRECTORY"] = "/run"
            values["STATE_DIRECTORY"] = "/var/lib"

        # When installing a new system container, set values in this order:
        #
        # 1) What comes from manifest.json, if present, as default value.
        # 2) What the user sets explictly as --set
        # 3) Values for DESTDIR and NAME
        manifest_file = os.path.join(exports, "manifest.json")
        if os.path.exists(manifest_file):
            with open(manifest_file, "r") as f:
                manifest = json.loads(f.read())
                if "defaultValues" in manifest:
                    for key, val in manifest["defaultValues"].items():
                        if key not in values:
                            values[key] = val

        if self.args.setvalues is not None:
            for i in self.args.setvalues:
                split = i.find("=")
                if split < 0:
                    raise ValueError("Invalid value '%s'.  Expected form NAME=VALUE" % i)
                key, val = i[:split], i[split+1:]
                values[key] = val

        values["DESTDIR"] = destination
        values["NAME"] = name
        values["EXEC_START"], values["EXEC_STOP"] = self._generate_systemd_startstop_directives(name)
        values["HOST_UID"] = os.getuid()
        values["HOST_GID"] = os.getgid()

        def _write_template(inputfilename, data, values, destination):
            try:
                os.makedirs(os.path.dirname(destination))
            except OSError:
                pass
            with open(destination, "w") as outfile:
                template = Template(data)
                result = template.safe_substitute(values)
                missing = {"".join(x) for x in template.pattern.findall(data) if "".join(x) not in values} # pylint: disable=no-member
                if len(missing):
                    raise ValueError("The template file '%s' still contains unreplaced values for: %s" % \
                                     (inputfilename, ", ".join(missing)))
                outfile.write(result)

        src = os.path.join(exports, "config.json")
        destination_path = os.path.join(destination, "config.json")
        if os.path.exists(src):
            shutil.copyfile(src, destination_path)
        elif os.path.exists(src + ".template"):
            with open(src + ".template", 'r') as infile:
                _write_template(src + ".template", infile.read(), values, destination_path)
        else:
            self._generate_default_oci_configuration(destination)

        if remote_path:
            with open(destination_path, 'r') as config_file:
                config = json.loads(config_file.read())
                config['root']['path'] = remote_rootfs
            with open(destination_path, 'w') as config_file:
                config_file.write(json.dumps(config, indent=4))

        # When upgrading, stop the service and remove previously installed
        # tmpfiles, before restarting the service.
        if upgrade:
            if was_service_active:
                self._systemctl_command("stop", name)
            if os.path.exists(tmpfilesout):
                try:
                    self._systemd_tmpfiles("--remove", tmpfilesout)
                except subprocess.CalledProcessError:
                    pass

        missing_bind_paths = self._check_oci_configuration_file(destination_path, remote_path)

        image_manifest = self._image_manifest(repo, rev)
        image_id = rev
        if image_manifest:
            image_manifest = json.loads(image_manifest)
            if 'Digest' in image_manifest:
                image_id = image_manifest['Digest'].replace("sha256:", "")

        with open(os.path.join(destination, "info"), 'w') as info_file:
            info = {"image" : img,
                    "revision" : image_id,
                    "ostree-commit": rev,
                    'created' : calendar.timegm(time.gmtime()),
                    "values" : values,
                    "remote" : remote}
            info_file.write(json.dumps(info, indent=4))

        if os.path.exists(unitfile):
            with open(unitfile, 'r') as infile:
                systemd_template = infile.read()
        else:
            systemd_template = SYSTEMD_UNIT_FILE_DEFAULT_TEMPLATE

        if os.path.exists(tmpfiles):
            with open(tmpfiles, 'r') as infile:
                tmpfiles_template = infile.read()
        else:
            tmpfiles_template = SystemContainers._generate_tmpfiles_data(missing_bind_paths, values["STATE_DIRECTORY"])

        _write_template(unitfile, systemd_template, values, unitfileout)
        shutil.copyfile(unitfileout, os.path.join(destination, "%s.service" % name))
        if (tmpfiles_template):
            _write_template(unitfile, tmpfiles_template, values, tmpfilesout)
            shutil.copyfile(unitfileout, os.path.join(destination, "tmpfiles-%s.conf" % name))

        sym = "%s/%s" % (self._get_system_checkout_path(), name)
        if os.path.exists(sym):
            os.unlink(sym)
        os.symlink(destination, sym)

        self._systemctl_command("daemon-reload")
        if (tmpfiles_template):
            self._systemd_tmpfiles("--create", tmpfilesout)

        if not upgrade:
            self._systemctl_command("enable", name)
        elif was_service_active:
            self._systemctl_command("start", name)