mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-25 07:47:17 +00:00
add backup and restore subcommands
This commit is contained in:
parent
6551c8d983
commit
cb63bc08ff
36
docs/faq.md
36
docs/faq.md
@ -242,7 +242,7 @@ You can pass any of these variables by using `-e` argument, e.g.:
|
|||||||
docker run -e AHRIMAN_PORT=8080 arcan1s/ahriman:latest
|
docker run -e AHRIMAN_PORT=8080 arcan1s/ahriman:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Working with web service
|
### Web service setup
|
||||||
|
|
||||||
Well for that you would need to have web container instance running forever; it can be achieved by the following command:
|
Well for that you would need to have web container instance running forever; it can be achieved by the following command:
|
||||||
|
|
||||||
@ -519,6 +519,36 @@ curl 'https://api.telegram.org/bot${CHAT_ID}/sendMessage?chat_id=${API_KEY}&text
|
|||||||
5. Create end-user `sudo -u ahriman ahriman user-add -r write my-first-user`. When it will ask for the password leave it blank.
|
5. Create end-user `sudo -u ahriman ahriman user-add -r write my-first-user`. When it will ask for the password leave it blank.
|
||||||
6. Restart web service `systemctl restart ahriman-web@x86_64`.
|
6. Restart web service `systemctl restart ahriman-web@x86_64`.
|
||||||
|
|
||||||
|
## Backup and restore
|
||||||
|
|
||||||
|
The service provides several commands aim to do easy repository backup and restore. If you would like to move repository from the server `server1.example.com` to another `server2.example.com` you have to perform the following steps:
|
||||||
|
|
||||||
|
1. On the source server `server1.example.com` run `repo-backup` command, e.g.:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo ahriman repo-backup /tmp/repo.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will pack all configuration files together with database file into the archive specified as command line argument (i.e. `/tmp/repo.tar.gz`). In addition it will also archive `cache` directory (the one which contains local clones used by e.g. local packages) and `.gnupg` of the `ahriman` user.
|
||||||
|
|
||||||
|
2. Copy created archive from source server `server1.example.com` to target `server2.example.com`.
|
||||||
|
|
||||||
|
3. Install ahriman as usual on the target server `server2.example.com` if you didn't yet.
|
||||||
|
|
||||||
|
4. Extract archive e.g. by using subcommand:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo ahriman repo-restore /tmp/repo.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
An additional argument `-o`/`--output` can be used to specify extraction root (`/` by default).
|
||||||
|
|
||||||
|
5. Rebuild repository:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo -u ahriman ahriman repo-rebuild --from-database
|
||||||
|
```
|
||||||
|
|
||||||
## Other topics
|
## Other topics
|
||||||
|
|
||||||
### How does it differ from %another-manager%?
|
### How does it differ from %another-manager%?
|
||||||
@ -558,6 +588,10 @@ Though originally I've created ahriman by trying to improve the project, it stil
|
|||||||
|
|
||||||
`repo-scripts` also have bad architecture and bad quality code and uses out-of-dated `yaourt` and `package-query`.
|
`repo-scripts` also have bad architecture and bad quality code and uses out-of-dated `yaourt` and `package-query`.
|
||||||
|
|
||||||
|
#### [toolbox](https://github.com/chaotic-aur/toolbox)
|
||||||
|
|
||||||
|
It is automation tools for `repoctl` mentioned above. Except for using shell it looks pretty cool and also offers some additional features like patches, remote synchronization (isn't it?) and reporting.
|
||||||
|
|
||||||
### I would like to check service logs
|
### I would like to check service logs
|
||||||
|
|
||||||
By default, the service writes logs to `/dev/log` which can be accessed by using `journalctl` command (logs are written to the journal of the user under which command is run).
|
By default, the service writes logs to `/dev/log` which can be accessed by using `journalctl` command (logs are written to the journal of the user under which command is run).
|
||||||
|
@ -83,12 +83,14 @@ def _parser() -> argparse.ArgumentParser:
|
|||||||
_set_patch_add_parser(subparsers)
|
_set_patch_add_parser(subparsers)
|
||||||
_set_patch_list_parser(subparsers)
|
_set_patch_list_parser(subparsers)
|
||||||
_set_patch_remove_parser(subparsers)
|
_set_patch_remove_parser(subparsers)
|
||||||
|
_set_repo_backup_parser(subparsers)
|
||||||
_set_repo_check_parser(subparsers)
|
_set_repo_check_parser(subparsers)
|
||||||
_set_repo_clean_parser(subparsers)
|
_set_repo_clean_parser(subparsers)
|
||||||
_set_repo_config_parser(subparsers)
|
_set_repo_config_parser(subparsers)
|
||||||
_set_repo_rebuild_parser(subparsers)
|
_set_repo_rebuild_parser(subparsers)
|
||||||
_set_repo_remove_unknown_parser(subparsers)
|
_set_repo_remove_unknown_parser(subparsers)
|
||||||
_set_repo_report_parser(subparsers)
|
_set_repo_report_parser(subparsers)
|
||||||
|
_set_repo_restore_parser(subparsers)
|
||||||
_set_repo_setup_parser(subparsers)
|
_set_repo_setup_parser(subparsers)
|
||||||
_set_repo_sign_parser(subparsers)
|
_set_repo_sign_parser(subparsers)
|
||||||
_set_repo_status_update_parser(subparsers)
|
_set_repo_status_update_parser(subparsers)
|
||||||
@ -314,6 +316,19 @@ def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
|
"""
|
||||||
|
add parser for repository backup subcommand
|
||||||
|
:param root: subparsers for the commands
|
||||||
|
:return: created argument parser
|
||||||
|
"""
|
||||||
|
parser = root.add_parser("repo-backup", help="backup repository data",
|
||||||
|
description="backup settings and database", formatter_class=_formatter)
|
||||||
|
parser.add_argument("path", help="path of the output archive", type=Path)
|
||||||
|
parser.set_defaults(handler=handlers.Backup, architecture=[""], lock=None, no_report=True, unsafe=True)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
"""
|
"""
|
||||||
add parser for repository check subcommand
|
add parser for repository check subcommand
|
||||||
@ -415,6 +430,20 @@ def _set_repo_report_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
|
"""
|
||||||
|
add parser for repository restore subcommand
|
||||||
|
:param root: subparsers for the commands
|
||||||
|
:return: created argument parser
|
||||||
|
"""
|
||||||
|
parser = root.add_parser("repo-restore", help="restore repository data",
|
||||||
|
description="restore settings and database", formatter_class=_formatter)
|
||||||
|
parser.add_argument("path", help="path of the input archive", type=Path)
|
||||||
|
parser.add_argument("-o", "--output", help="root path of the extracted files", type=Path, default=Path("/"))
|
||||||
|
parser.set_defaults(handler=handlers.Restore, architecture=[""], lock=None, no_report=True, unsafe=True)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
"""
|
"""
|
||||||
add parser for setup subcommand
|
add parser for setup subcommand
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
from ahriman.application.handlers.handler import Handler
|
from ahriman.application.handlers.handler import Handler
|
||||||
|
|
||||||
from ahriman.application.handlers.add import Add
|
from ahriman.application.handlers.add import Add
|
||||||
|
from ahriman.application.handlers.backup import Backup
|
||||||
from ahriman.application.handlers.clean import Clean
|
from ahriman.application.handlers.clean import Clean
|
||||||
from ahriman.application.handlers.dump import Dump
|
from ahriman.application.handlers.dump import Dump
|
||||||
from ahriman.application.handlers.help import Help
|
from ahriman.application.handlers.help import Help
|
||||||
@ -29,6 +30,7 @@ from ahriman.application.handlers.rebuild import Rebuild
|
|||||||
from ahriman.application.handlers.remove import Remove
|
from ahriman.application.handlers.remove import Remove
|
||||||
from ahriman.application.handlers.remove_unknown import RemoveUnknown
|
from ahriman.application.handlers.remove_unknown import RemoveUnknown
|
||||||
from ahriman.application.handlers.report import Report
|
from ahriman.application.handlers.report import Report
|
||||||
|
from ahriman.application.handlers.restore import Restore
|
||||||
from ahriman.application.handlers.search import Search
|
from ahriman.application.handlers.search import Search
|
||||||
from ahriman.application.handlers.setup import Setup
|
from ahriman.application.handlers.setup import Setup
|
||||||
from ahriman.application.handlers.sign import Sign
|
from ahriman.application.handlers.sign import Sign
|
||||||
|
80
src/ahriman/application/handlers/backup.py
Normal file
80
src/ahriman/application/handlers/backup.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2022 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
import argparse
|
||||||
|
import pwd
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from tarfile import TarFile
|
||||||
|
from typing import Set, Type
|
||||||
|
|
||||||
|
from ahriman.application.handlers.handler import Handler
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.database.sqlite import SQLite
|
||||||
|
|
||||||
|
|
||||||
|
class Backup(Handler):
|
||||||
|
"""
|
||||||
|
backup packages handler
|
||||||
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
|
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
|
||||||
|
"""
|
||||||
|
callback for command line
|
||||||
|
:param args: command line args
|
||||||
|
:param architecture: repository architecture
|
||||||
|
:param configuration: configuration instance
|
||||||
|
:param no_report: force disable reporting
|
||||||
|
:param unsafe: if set no user check will be performed before path creation
|
||||||
|
"""
|
||||||
|
backup_paths = Backup.get_paths(configuration)
|
||||||
|
with TarFile(args.path, mode="w") as archive: # well we don't actually use compression
|
||||||
|
for backup_path in backup_paths:
|
||||||
|
archive.add(backup_path)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_paths(configuration: Configuration) -> Set[Path]:
|
||||||
|
"""
|
||||||
|
extract paths to backup
|
||||||
|
:param configuration: configuration instance
|
||||||
|
:return: map of the filesystem paths
|
||||||
|
"""
|
||||||
|
paths = set(configuration.include.glob("*.ini"))
|
||||||
|
|
||||||
|
root, _ = configuration.check_loaded()
|
||||||
|
paths.add(root) # the configuration itself
|
||||||
|
paths.add(SQLite.database_path(configuration)) # database
|
||||||
|
|
||||||
|
# local caches
|
||||||
|
repository_paths = configuration.repository_paths
|
||||||
|
if repository_paths.cache.is_dir():
|
||||||
|
paths.add(repository_paths.cache)
|
||||||
|
|
||||||
|
# gnupg home with imported keys
|
||||||
|
uid, _ = repository_paths.root_owner
|
||||||
|
system_user = pwd.getpwuid(uid)
|
||||||
|
gnupg_home = Path(system_user.pw_dir) / ".gnupg"
|
||||||
|
if gnupg_home.is_dir():
|
||||||
|
paths.add(gnupg_home)
|
||||||
|
|
||||||
|
return paths
|
48
src/ahriman/application/handlers/restore.py
Normal file
48
src/ahriman/application/handlers/restore.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2022 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from typing import Type
|
||||||
|
from tarfile import TarFile
|
||||||
|
|
||||||
|
from ahriman.application.handlers.handler import Handler
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
|
||||||
|
|
||||||
|
class Restore(Handler):
|
||||||
|
"""
|
||||||
|
restore packages handler
|
||||||
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
|
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
|
||||||
|
"""
|
||||||
|
callback for command line
|
||||||
|
:param args: command line args
|
||||||
|
:param architecture: repository architecture
|
||||||
|
:param configuration: configuration instance
|
||||||
|
:param no_report: force disable reporting
|
||||||
|
:param unsafe: if set no user check will be performed before path creation
|
||||||
|
"""
|
||||||
|
with TarFile(args.path) as archive:
|
||||||
|
archive.extractall(path=args.output)
|
58
tests/ahriman/application/handlers/test_handler_backup.py
Normal file
58
tests/ahriman/application/handlers/test_handler_backup.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from ahriman.application.handlers import Backup
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
|
|
||||||
|
|
||||||
|
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||||
|
"""
|
||||||
|
default arguments for these test cases
|
||||||
|
:param args: command line arguments fixture
|
||||||
|
:return: generated arguments for these test cases
|
||||||
|
"""
|
||||||
|
args.path = Path("result.tar.gz")
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must run command
|
||||||
|
"""
|
||||||
|
args = _default_args(args)
|
||||||
|
mocker.patch("ahriman.application.handlers.Backup.get_paths", return_value=[Path("path")])
|
||||||
|
tarfile = MagicMock()
|
||||||
|
add_mock = tarfile.__enter__.return_value = MagicMock()
|
||||||
|
mocker.patch("tarfile.TarFile.__new__", return_value=tarfile)
|
||||||
|
|
||||||
|
Backup.run(args, "x86_64", configuration, True, False)
|
||||||
|
add_mock.add.assert_called_once_with(Path("path"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_paths(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must get paths to be archived
|
||||||
|
"""
|
||||||
|
# gnupg export mock
|
||||||
|
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||||
|
mocker.patch.object(RepositoryPaths, "root_owner", (42, 42))
|
||||||
|
getpwuid_mock = mocker.patch("pwd.getpwuid", return_value=MagicMock())
|
||||||
|
# well database does not exist so we override it
|
||||||
|
database_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.database_path", return_value=configuration.path)
|
||||||
|
|
||||||
|
paths = Backup.get_paths(configuration)
|
||||||
|
getpwuid_mock.assert_called_once_with(42)
|
||||||
|
database_mock.assert_called_once_with(configuration)
|
||||||
|
assert configuration.path in paths
|
||||||
|
assert all(path.exists() for path in paths if path.name not in (".gnupg", "cache"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not Backup.ALLOW_AUTO_ARCHITECTURE_RUN
|
@ -50,6 +50,7 @@ def test_run_extract_packages(args: argparse.Namespace, configuration: Configura
|
|||||||
"""
|
"""
|
||||||
args = _default_args(args)
|
args = _default_args(args)
|
||||||
args.from_database = True
|
args.from_database = True
|
||||||
|
args.dry_run = True
|
||||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||||
mocker.patch("ahriman.application.application.Application.add")
|
mocker.patch("ahriman.application.application.Application.add")
|
||||||
extract_mock = mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[])
|
extract_mock = mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[])
|
||||||
|
39
tests/ahriman/application/handlers/test_handler_restore.py
Normal file
39
tests/ahriman/application/handlers/test_handler_restore.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from ahriman.application.handlers import Restore
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
|
||||||
|
|
||||||
|
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||||
|
"""
|
||||||
|
default arguments for these test cases
|
||||||
|
:param args: command line arguments fixture
|
||||||
|
:return: generated arguments for these test cases
|
||||||
|
"""
|
||||||
|
args.path = Path("result.tar.gz")
|
||||||
|
args.output = Path.cwd()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must run command
|
||||||
|
"""
|
||||||
|
args = _default_args(args)
|
||||||
|
tarfile = MagicMock()
|
||||||
|
extract_mock = tarfile.__enter__.return_value = MagicMock()
|
||||||
|
mocker.patch("tarfile.TarFile.__new__", return_value=tarfile)
|
||||||
|
|
||||||
|
Restore.run(args, "x86_64", configuration, True, False)
|
||||||
|
extract_mock.extractall.assert_called_once_with(path=args.output)
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not Restore.ALLOW_AUTO_ARCHITECTURE_RUN
|
@ -258,6 +258,25 @@ def test_subparsers_patch_remove_architecture(parser: argparse.ArgumentParser) -
|
|||||||
assert args.architecture == [""]
|
assert args.architecture == [""]
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_repo_backup(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
repo-backup command must imply architecture list, lock, no-report and unsafe
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["repo-backup", "output.zip"])
|
||||||
|
assert args.architecture == [""]
|
||||||
|
assert args.lock is None
|
||||||
|
assert args.no_report
|
||||||
|
assert args.unsafe
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_repo_backup_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
repo-backup command must correctly parse architecture list
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "repo-backup", "output.zip"])
|
||||||
|
assert args.architecture == [""]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_repo_check(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_repo_check(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
repo-check command must imply dry-run, no-aur and no-manual
|
repo-check command must imply dry-run, no-aur and no-manual
|
||||||
@ -339,6 +358,25 @@ def test_subparsers_repo_report_architecture(parser: argparse.ArgumentParser) ->
|
|||||||
assert args.architecture == ["x86_64"]
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_repo_restore(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
repo-restore command must imply architecture list, lock, no-report and unsafe
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["repo-restore", "output.zip"])
|
||||||
|
assert args.architecture == [""]
|
||||||
|
assert args.lock is None
|
||||||
|
assert args.no_report
|
||||||
|
assert args.unsafe
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_repo_restore_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
repo-restore command must correctly parse architecture list
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "repo-restore", "output.zip"])
|
||||||
|
assert args.architecture == [""]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_repo_setup(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_repo_setup(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
repo-setup command must imply lock, no-report, quiet and unsafe
|
repo-setup command must imply lock, no-report, quiet and unsafe
|
||||||
|
Loading…
Reference in New Issue
Block a user