Fix plugin (#4594)

* change behaviour to load plugin

* add checks in _write_plugin_to_temp_file

* modify test

* fixed test
This commit is contained in:
emanu 2026-06-28 21:09:20 +02:00 committed by GitHub
parent c6a5a130a8
commit cce0c47e11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 34 deletions

View File

@ -3,6 +3,7 @@ import json
import os import os
import stat import stat
import sys import sys
import tempfile
import urllib.error import urllib.error
import urllib.parse import urllib.parse
from argparse import ArgumentParser, Namespace from argparse import ArgumentParser, Namespace
@ -60,7 +61,8 @@ class Arguments:
debug: bool = False debug: bool = False
offline: bool = False offline: bool = False
no_pkg_lookups: bool = False no_pkg_lookups: bool = False
plugin: str | None = None plugin: Path | None = None
plugin_url: str | None = None
skip_version_check: bool = False skip_version_check: bool = False
skip_wifi_check: bool = False skip_wifi_check: bool = False
advanced: bool = False advanced: bool = False
@ -614,10 +616,17 @@ class ArchConfigHandler:
parser.add_argument( parser.add_argument(
'--plugin', '--plugin',
nargs='?', nargs='?',
type=str, type=Path,
default=None, default=None,
help='File path to a plugin to load', help='File path to a plugin to load',
) )
parser.add_argument(
'--plugin-url',
type=str,
nargs='?',
default=None,
help='Url to a plugin file to load',
)
parser.add_argument( parser.add_argument(
'--skip-version-check', '--skip-version-check',
action='store_true', action='store_true',
@ -657,7 +666,11 @@ class ArchConfigHandler:
warn(f'Warning: --debug mode will write certain credentials to {logger.path}!') warn(f'Warning: --debug mode will write certain credentials to {logger.path}!')
if args.plugin: if args.plugin:
plugin_path = Path(args.plugin) load_plugin(args.plugin)
if args.plugin_url:
plugin_data = self._fetch_from_url(args.plugin_url)
plugin_path = self._write_plugin_to_temp_file(plugin_data)
load_plugin(plugin_path) load_plugin(plugin_path)
if args.creds_decryption_key is None: if args.creds_decryption_key is None:
@ -749,6 +762,27 @@ class ArchConfigHandler:
sys.exit(1) sys.exit(1)
def _write_plugin_to_temp_file(self, plugin_data: str) -> Path:
if not plugin_data.strip():
error('The downloaded plugin is empty')
sys.exit(1)
tmp_file = tempfile.NamedTemporaryFile(
mode='w',
suffix='.py',
prefix='archinstall_plugin_',
delete=False,
)
try:
with tmp_file as f:
f.write(plugin_data)
except OSError as err:
error(f'Could not write the downloaded plugin to a temporary file: {err}')
sys.exit(1)
return Path(tmp_file.name)
def _read_file(self, path: Path) -> str: def _read_file(self, path: Path) -> str:
if not path.exists(): if not path.exists():
error(f'Could not find file {path}') error(f'Could not find file {path}')

View File

@ -1,9 +1,6 @@
import hashlib
import importlib.util import importlib.util
import os import os
import sys import sys
import urllib.parse
import urllib.request
from importlib import metadata from importlib import metadata
from pathlib import Path from pathlib import Path
@ -34,23 +31,6 @@ def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
plugins[f.__name__] = f plugins[f.__name__] = f
def _localize_path(path: Path) -> Path:
"""
Support structures for load_plugin()
"""
url = urllib.parse.urlparse(str(path))
if url.scheme and url.scheme in ('https', 'http'):
converted_path = Path(f'/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py')
with open(converted_path, 'w') as temp_file:
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode('utf-8'))
return converted_path
else:
return path
def _import_via_path(path: Path, namespace: str | None = None) -> str: def _import_via_path(path: Path, namespace: str | None = None) -> str:
if not namespace: if not namespace:
namespace = os.path.basename(path) namespace = os.path.basename(path)
@ -82,17 +62,10 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str:
def load_plugin(path: Path) -> None: def load_plugin(path: Path) -> None:
namespace: str | None = None namespace: str | None = None
parsed_url = urllib.parse.urlparse(str(path)) info(f'Loading plugin from {path}')
info(f'Loading plugin from url {parsed_url}')
# The Profile was not a direct match on a remote URL if os.path.isfile(path):
if not parsed_url.scheme: namespace = _import_via_path(path)
# Path was not found in any known examples, check if it's an absolute path
if os.path.isfile(path):
namespace = _import_via_path(path)
elif parsed_url.scheme in ('https', 'http'):
localized = _localize_path(path)
namespace = _import_via_path(localized)
if namespace and namespace in sys.modules: if namespace and namespace in sys.modules:
# Version dependency via __archinstall__version__ variable (if present) in the plugin # Version dependency via __archinstall__version__ variable (if present) in the plugin

View File

@ -50,6 +50,7 @@ def test_default_args(monkeypatch: MonkeyPatch) -> None:
offline=False, offline=False,
no_pkg_lookups=False, no_pkg_lookups=False,
plugin=None, plugin=None,
plugin_url=None,
skip_version_check=False, skip_version_check=False,
advanced=False, advanced=False,
) )
@ -106,7 +107,8 @@ def test_correct_parsing_args(
debug=True, debug=True,
offline=True, offline=True,
no_pkg_lookups=True, no_pkg_lookups=True,
plugin='pytest_plugin.py', plugin=Path('pytest_plugin.py'),
plugin_url=None,
skip_version_check=True, skip_version_check=True,
advanced=True, advanced=True,
) )