def run(self): """Pull a remote and delete it. All pulls in our code follow the pattern pull + delete. :raise: PayloadInstallationError if the pull fails """ # pull requires this for some reason mainctx = create_new_context() mainctx.push_thread_default() cancellable = None # Variable substitute the ref: https://pagure.io/atomic-wg/issue/299 ref = RpmOstree.varsubst_basearch(self._data.ref) self.report_progress( _("Starting pull of {branch_name} from {source}").format( branch_name=ref, source=self._data.remote)) progress = OSTree.AsyncProgress.new() progress.connect('changed', self._pull_progress_cb) pull_opts = {'refs': Variant('as', [ref])} # If we're doing a kickstart, we can at least use the content as a reference: # See <https://github.com/rhinstaller/anaconda/issues/1117> # The first path here is used by <https://pagure.io/fedora-lorax-templates> # and the second by <https://github.com/projectatomic/rpm-ostree-toolbox/> # FIXME extend tests to cover this part of code if OSTree.check_version(2017, 8): for path in ['/ostree/repo', '/install/ostree/repo']: if os.path.isdir(path + '/objects'): pull_opts['localcache-repos'] = Variant('as', [path]) break sysroot_file = Gio.File.new_for_path(conf.target.physical_root) sysroot = OSTree.Sysroot.new(sysroot_file) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) try: repo.pull_with_options(self._data.remote, Variant('a{sv}', pull_opts), progress, cancellable) except GError as e: raise PayloadInstallationError( "Failed to pull from repository: %s" % e) from e log.info("ostree pull: %s", progress.get_status() or "") self.report_progress(_("Preparing deployment of {}").format(ref)) # Now that we have the data pulled, delete the remote for now. This will allow a remote # configuration defined in the tree (if any) to override what's in the kickstart. # Otherwise, we'll re-add it in post. Ideally, ostree would support a pull without adding # a remote, but that would get quite complex. repo.remote_delete(self._data.remote, None) mainctx.pop_thread_default()
def replace_installed_refs_remote(self, new_remote_name): """Replace remote for all the refs. :param str new_remote_name: the remote name which will be used instead of the current one """ install_path = self._installation.get_path() install_path = install_path.get_path() # unpack the Gio.File variant_type = VariantType(self.FLATPAK_DEPLOY_DATA_GVARIANT_STRING) for ref in self.get_refs_full_format(): deploy_path = os.path.join(install_path, ref, "active/deploy") with open(deploy_path, "rb") as f: content = f.read() variant = Variant.new_from_bytes(variant_type, Bytes(content), False) children = [ variant.get_child_value(i) for i in range(variant.n_children()) ] # Replace the origin children[0] = Variant('s', new_remote_name) new_variant = Variant.new_tuple(*children) serialized = new_variant.get_data_as_bytes().get_data() with open(deploy_path, "wb") as f: f.write(serialized)
def run(self): cancellable = None sysroot_file = Gio.File.new_for_path(self._root) sysroot = OSTree.Sysroot.new(sysroot_file) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) remote_options = {} if not self._data.gpg_verification_enabled: remote_options['gpg-verify'] = Variant('b', False) if not conf.payload.verify_ssl: remote_options['tls-permissive'] = Variant('b', True) if self._use_root: root = sysroot_file else: root = None repo.remote_change(root, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, self._data.remote, self._data.url, Variant('a{sv}', remote_options), cancellable)
def _gvariant_settings(settings, updated_key1, updated_key2, value, default_type_str=None): """Update setting of updated_key1, updated_key2 of settings object with value. If the value is None, the setting is removed. The type of value is determined from existing setting. If setting for key1, key2 does not exist, default_type_str is used or the type is inferred from the value supplied (string and bool only). """ type_str = default_type_str # build copy of GVariant settings as mutable python object new_settings = {} dict1 = settings.get_child_value(0) # loop over first level dict (key1) for key1_idx in range(dict1.n_children()): key_dict2 = dict1.get_child_value(key1_idx) key1 = key_dict2.get_child_value(0).unpack() new_settings[key1] = {} dict2 = key_dict2.get_child_value(1) # loop over second level dict (key2) for key2_idx in range(dict2.n_children()): key_val = dict2.get_child_value(key2_idx) key2 = key_val.get_child_value(0).unpack() val = key_val.get_child_value(1).get_child_value(0) # get type string of updated value if key1 == updated_key1 and key2 == updated_key2: type_str = val.get_type_string() # copy old value to new python object new_settings[key1][key2] = val if type_str is None: # infer the new value type for string and boolean if isinstance(value, bool): type_str = 'b' elif isinstance(value, str): type_str = 's' if type_str is not None: if updated_key1 not in new_settings: new_settings[updated_key1] = {} if value is None: new_settings[updated_key1].pop(updated_key2, None) else: new_settings[updated_key1][updated_key2] = Variant(type_str, value) return Variant(settings.get_type_string(), (new_settings, ))
def _get_remote_options(self): """Get the remote options.""" remote_options = {} if not self._data.gpg_verification_enabled: remote_options['gpg-verify'] = Variant('b', False) if not conf.payload.verify_ssl: remote_options['tls-permissive'] = Variant('b', True) return Variant('a{sv}', remote_options)
def nm_add_connection(values): """Add new connection specified by values. :param values: | list of settings with new values and its types | [[key1, key2, value, type_str], ...] | key1: first-level key of setting (eg "connection") | key2: second-level key of setting (eg "uuid") | value: new value | type_str: dbus type of new value (eg "ay") :type values: | [[key1, key2, value, type_str], ...] | key1: str | key2: str | value: object | type_str: str """ settings = {} for key1, key2, value, type_str in values: gvalue = Variant(type_str, value) if key1 not in settings: settings[key1] = {} settings[key1][key2] = gvalue proxy = _get_proxy( object_path="/org/freedesktop/NetworkManager/Settings", interface_name="org.freedesktop.NetworkManager.Settings") try: connection = proxy.AddConnection('(a{sa{sv}})', settings) except GError as e: if "bond.options: invalid option" in e.message: raise BondOptionsError(e) raise return connection
def get_property_sync(service, obj_path, iface, prop_name, connection=None): """ Get value of a given property of a given object provided by a given service. :param service: DBus service to use :type service: str :param obj_path: object path :type obj_path: str :param iface: interface to use :type iface: str :param prop_name: name of the property :type prop_name: str :param connection: connection to use (if None, a new connection is established) :type connection: Gio.DBusConnection :return: unpacked value of the property :rtype: tuple with elements that depend on the type of the property :raise DBusCallError: when the internal dbus_call_safe_sync invocation raises an exception :raise DBusPropertyError: when the given object doesn't have the given property """ args = Variant('(ss)', (iface, prop_name)) ret = call_sync(service, obj_path, DBUS_PROPS_IFACE, "Get", args, connection) if ret is None: msg = "No value for the %s object's property %s" % (obj_path, prop_name) raise DBusPropertyError(msg) return ret
def post_install(self): super().post_install() gi.require_version("OSTree", "1.0") from gi.repository import OSTree cancellable = None # Following up on the "remote delete" above, we removed the # remote from /ostree/repo/config. But we want it in /etc, so # re-add it to /etc/ostree/remotes.d, using the sysroot path. # # However, we ignore the case where the remote already exists, # which occurs when the content itself provides the remote # config file. # Note here we use the deployment as sysroot, because it's # that version of /etc that we want. sysroot_file = Gio.File.new_for_path(conf.target.system_root) sysroot = OSTree.Sysroot.new(sysroot_file) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] repo.remote_change(sysroot_file, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, self.data.ostreesetup.remote, self.data.ostreesetup.url, Variant('a{sv}', self._remoteOptions), cancellable) boot = conf.target.system_root + '/boot' # If we're using GRUB2, move its config file, also with a # compatibility symlink. boot_grub2_cfg = boot + '/grub2/grub.cfg' if os.path.isfile(boot_grub2_cfg): boot_loader = boot + '/loader' target_grub_cfg = boot_loader + '/grub.cfg' log.info("Moving %s -> %s", boot_grub2_cfg, target_grub_cfg) os.rename(boot_grub2_cfg, target_grub_cfg) os.symlink('../loader/grub.cfg', boot_grub2_cfg) # Skip kernel args setup for dirinstall, there is no bootloader or rootDevice setup. if not conf.target.is_directory: # OSTree owns the bootloader configuration, so here we give it # the argument list we computed from storage, architecture and # such. bootloader = STORAGE.get_proxy(BOOTLOADER) device_tree = STORAGE.get_proxy(DEVICE_TREE) root_device = device_tree.GetRootDevice() set_kargs_args = ["admin", "instutil", "set-kargs"] set_kargs_args.extend(bootloader.GetArguments()) set_kargs_args.append("root=" + device_tree.GetFstabSpec(root_device)) self._safe_exec_with_redirect("ostree", set_kargs_args, root=conf.target.system_root)
def uninhibit_screensaver(connection, inhibit_id): """ Re-enable the screensaver idle timer. :param connection: A handle for the session message bus :type connection: Gio.DBusConnection :param inhibit_id: The ID returned by the inhibit method :type inhibit_id: int """ try: safe_dbus.call_sync(SCREENSAVER_SERVICE, SCREENSAVER_PATH, SCREENSAVER_IFACE, SCREENSAVER_UNINHIBIT_METHOD, Variant('(u)', (inhibit_id,)), connection) except safe_dbus.DBusCallError as e: log.info("Unable to uninhibit the screensaver: %s", e)
def set_layouts(self, layouts_variants, options=None, convert=False): """ Method that sets X11 layouts and variants (for later X sessions) via systemd-localed's DBus API. :param layout_variant: list of 'layout (variant)' or 'layout' specifications of layouts and variants :type layout_variant: list of strings :param options: list of X11 options that should be set :type options: list of strings :param convert: whether the keymap should be converted to a X11 layout (see set_and_convert_keymap) :type convert: bool """ layouts = [] variants = [] for layout_variant in (nonempty for nonempty in layouts_variants if nonempty): (layout, variant) = parse_layout_variant(layout_variant) layouts.append(layout) variants.append(variant) layouts_str = ",".join(layouts) variants_str = ",".join(variants) if options: opts_str = ",".join(options) else: opts_str = "" # args: layout, model, variant, options, convert, user_interaction # where convert indicates whether the keymap should be converted # to X11 layout and user_interaction indicates whether PolicyKit # should ask for credentials or not args = Variant( "(ssssbb)", (layouts_str, "", variants_str, opts_str, convert, False)) try: safe_dbus.call_sync(LOCALED_SERVICE, LOCALED_OBJECT_PATH, LOCALED_IFACE, "SetX11Keyboard", args, self._connection) except safe_dbus.DBusCallError as e: log.error("Failed to set layouts: %s", e)
def inhibit_screensaver(connection): """ Inhibit the screensaver idle timer. :param connection: A handle for the session message bus :type connection: Gio.DBusConnection :return: The inhibit ID or None :rtype: int or None """ try: inhibit_id = safe_dbus.call_sync(SCREENSAVER_SERVICE, SCREENSAVER_PATH, SCREENSAVER_IFACE, SCREENSAVER_INHIBIT_METHOD, Variant('(ss)', (SCREENSAVER_APPLICATION, SCREENSAVER_REASON)), connection) return inhibit_id[0] except safe_dbus.DBusCallError as e: log.info("Unable to inhibit the screensaver: %s", e) return None
def set_keymap(self, keymap, convert=False): """ Method that sets VConsole keymap via systemd-localed's DBus API. :param keymap: VConsole keymap that should be set :type keymap: str :param convert: whether the keymap should be converted to a X11 layout (see set_and_convert_keymap) :type convert: bool """ # args: keymap, keymap_toggle, convert, user_interaction # where convert indicates whether the keymap should be converted # to X11 layout and user_interaction indicates whether PolicyKit # should ask for credentials or not args = Variant('(ssbb)', (keymap, "", convert, False)) try: safe_dbus.call_sync(LOCALED_SERVICE, LOCALED_OBJECT_PATH, LOCALED_IFACE, "SetVConsoleKeyboard", args, self._connection) except safe_dbus.DBusCallError as e: log.error("Failed to set keymap: %s", e)
def install(self): mainctx = create_new_context() mainctx.push_thread_default() cancellable = None gi.require_version("OSTree", "1.0") gi.require_version("RpmOstree", "1.0") from gi.repository import OSTree, RpmOstree ostreesetup = self.data.ostreesetup log.info("executing ostreesetup=%r", ostreesetup) # Initialize the filesystem - this will create the repo as well self._safe_exec_with_redirect("ostree", [ "admin", "--sysroot=" + conf.target.physical_root, "init-fs", conf.target.physical_root ]) # Here, we use the physical root as sysroot, because we haven't # yet made a deployment. sysroot_file = Gio.File.new_for_path(conf.target.physical_root) sysroot = OSTree.Sysroot.new(sysroot_file) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) self._remoteOptions = {} if hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg: self._remoteOptions['gpg-verify'] = Variant('b', False) if not conf.payload.verify_ssl: self._remoteOptions['tls-permissive'] = Variant('b', True) repo.remote_change(None, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, ostreesetup.remote, ostreesetup.url, Variant('a{sv}', self._remoteOptions), cancellable) # Variable substitute the ref: https://pagure.io/atomic-wg/issue/299 ref = RpmOstree.varsubst_basearch(ostreesetup.ref) progressQ.send_message( _("Starting pull of %(branchName)s from %(source)s") % { "branchName": ref, "source": ostreesetup.remote }) progress = OSTree.AsyncProgress.new() progress.connect('changed', self._pull_progress_cb) pull_opts = {'refs': Variant('as', [ref])} # If we're doing a kickstart, we can at least use the content as a reference: # See <https://github.com/rhinstaller/anaconda/issues/1117> # The first path here is used by <https://pagure.io/fedora-lorax-templates> # and the second by <https://github.com/projectatomic/rpm-ostree-toolbox/> if OSTree.check_version(2017, 8): for path in ['/ostree/repo', '/install/ostree/repo']: if os.path.isdir(path + '/objects'): pull_opts['localcache-repos'] = Variant('as', [path]) break try: repo.pull_with_options(ostreesetup.remote, Variant('a{sv}', pull_opts), progress, cancellable) except GError as e: exn = PayloadInstallError("Failed to pull from repository: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) util.ipmi_abort(scripts=self.data.scripts) sys.exit(1) log.info("ostree pull: %s", progress.get_status() or "") progressQ.send_message(_("Preparing deployment of %s") % (ref, )) # Now that we have the data pulled, delete the remote for now. # This will allow a remote configuration defined in the tree # (if any) to override what's in the kickstart. Otherwise, # we'll re-add it in post. Ideally, ostree would support a # pull without adding a remote, but that would get quite # complex. repo.remote_delete(self.data.ostreesetup.remote, None) self._safe_exec_with_redirect("ostree", [ "admin", "--sysroot=" + conf.target.physical_root, "os-init", ostreesetup.osname ]) admin_deploy_args = [ "admin", "--sysroot=" + conf.target.physical_root, "deploy", "--os=" + ostreesetup.osname ] admin_deploy_args.append(ostreesetup.remote + ':' + ref) log.info("ostree admin deploy starting") progressQ.send_message(_("Deployment starting: %s") % (ref, )) self._safe_exec_with_redirect("ostree", admin_deploy_args) log.info("ostree admin deploy complete") progressQ.send_message(_("Deployment complete: %s") % (ref, )) # Reload now that we've deployed, find the path to the new deployment sysroot.load(None) deployments = sysroot.get_deployments() assert len(deployments) > 0 deployment = deployments[0] deployment_path = sysroot.get_deployment_directory(deployment) util.set_system_root(deployment_path.get_path()) try: self._copy_bootloader_data() except (OSError, RuntimeError) as e: exn = PayloadInstallError("Failed to copy bootloader data: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) util.ipmi_abort(scripts=self.data.scripts) sys.exit(1) mainctx.pop_thread_default()