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
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)