mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-09 06:56:37 +00:00
fix(oci): call the editing of an application Modify everywhere
- The option that edits an installed application is Modify for one container and for a multi-container application, in the OCI menu and on the Monitor button. Recreate is only the rebuild of a multi-container application as it is. - A stack recreation says "Recreating" and "Recreated" in its steps instead of "Updating". - The notes of the GPU, Coral, disk and share menus no longer name the option. - Spanish for the new texts; the English copies of the stack texts are removed from de, fr, pt and sv so the bot translates them.
This commit is contained in:
@@ -354,7 +354,7 @@ def candidate_contract(record, operation, proposal=None):
|
||||
raise ValueError(translate('Recreating requires a confirmed proposal'))
|
||||
if (proposal.get('base_config_sha256') is not None
|
||||
and proposal['base_config_sha256'] != record['observed']['config_sha256']):
|
||||
raise ValueError(translate('The instance changed while it was being edited; configure Recreate again'))
|
||||
raise ValueError(translate('The instance changed while it was being edited; open Modify again'))
|
||||
candidate = proposal['candidate']
|
||||
if (candidate.get('vmid') != record['vmid']
|
||||
or candidate.get('installation_id') != record['installation_id']
|
||||
@@ -872,7 +872,7 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i
|
||||
if coordinated and backup_compression != 'zstd':
|
||||
raise ValueError(translate('Coordinated backups require zstd'))
|
||||
if show:
|
||||
msg_info(translate('Preparing the update...') if update else translate('Preparing the recreation...'))
|
||||
msg_info(translate('Preparing the update...') if update else translate('Preparing the changes...'))
|
||||
record = instances.read(root, vmid)
|
||||
if coordinated and any(coordinated.get(flag) for flag in
|
||||
('nextcloud_replay', 'paperless_replay', 'tandoor_replay', 'immich_replay')):
|
||||
@@ -910,7 +910,7 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i
|
||||
required = {m['container_path'] for m in candidate['deployment'].get('mounts', [])}
|
||||
if any(not any(p == target or p.startswith(target.rstrip('/') + '/') for target in required)
|
||||
for p in set(image['defaults'].get('Volumes') or {}) - non_persistent_image_volumes(candidate['template'])):
|
||||
raise ValueError(translate('The new image requires additional persistent paths; use Recreate'))
|
||||
raise ValueError(translate('The new image requires additional persistent paths; use Modify'))
|
||||
directory = instances.location(root, vmid).parent / 'transactions' / uuid.uuid4().hex
|
||||
private_directory(directory)
|
||||
open_log(directory)
|
||||
@@ -956,7 +956,7 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i
|
||||
gpu_devices.verify(original_gpu)
|
||||
gpu_devices.verify(desired_gpu)
|
||||
if show:
|
||||
msg_ok(translate('Update prepared') if update else translate('Recreation prepared'))
|
||||
msg_ok(translate('Update prepared') if update else translate('Changes prepared'))
|
||||
msg_info(translate('Stopping the container...'))
|
||||
stop(vmid)
|
||||
if show:
|
||||
@@ -1062,7 +1062,7 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i
|
||||
cleanup_error = exc
|
||||
log(f'cleanup: {exc}')
|
||||
msg_ok(translate('Update completed. Data kept.') if update
|
||||
else translate('Recreation completed. Data kept.'))
|
||||
else translate('Changes applied. Data kept.'))
|
||||
if freed:
|
||||
msg_ok(f"{translate('Unused images removed from the cache:')} {gib(freed)}")
|
||||
if cleanup_error is not None:
|
||||
@@ -1275,7 +1275,7 @@ def phase_label(phase):
|
||||
|
||||
|
||||
def show_status(path, state):
|
||||
operations = {'update': translate('Update'), 'recreate': translate('Recreate')}
|
||||
operations = {'update': translate('Update'), 'recreate': translate('Modify')}
|
||||
msg_info2(f"{translate('Interrupted operation:')} {operations.get(state.get('operation'), state.get('operation'))} (CT {state['vmid']})")
|
||||
msg_info2(f"{translate('Stopped at:')} {phase_label(state.get('phase'))}")
|
||||
if state.get('stage'):
|
||||
|
||||
@@ -254,9 +254,9 @@ def main():
|
||||
msg_error(translate('Another OCI operation is using the instance registry. Wait for it to finish.'))
|
||||
return 1
|
||||
except (OSError, ValueError, KeyError, RuntimeError, StopIteration, subprocess.TimeoutExpired) as error:
|
||||
msg_error(f"{translate('The application could not be recreated:')} {error}")
|
||||
msg_error(f"{translate('The application could not be modified:')} {error}")
|
||||
return 1
|
||||
msg_ok(translate('The application has been recreated with the new options.'))
|
||||
msg_ok(translate('The application has been modified with the new options.'))
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -120,6 +120,7 @@ def _apply(path, plan, adapter):
|
||||
running = {str(vmid): adapter.is_running(vmid) for vmid in plan['start_order']}
|
||||
if any(type(value) is not bool for value in running.values()):
|
||||
raise ValueError(translate('Invalid running state'))
|
||||
recreate = plan['operation'] == 'recreate'
|
||||
state = {'schema_version': 1, 'id': str(uuid.uuid4()),
|
||||
'plan': copy.deepcopy(plan), 'running': running,
|
||||
'prepared': {}, 'backups': {}, 'stop_intent': False,
|
||||
@@ -152,9 +153,9 @@ def _apply(path, plan, adapter):
|
||||
state['replacement_intent'] = True
|
||||
save(path, state, 'replacing')
|
||||
for vmid in plan['start_order']:
|
||||
msg_info(f"{translate('Updating')} {member(adapter, vmid)}...")
|
||||
msg_info(f"{translate('Recreating') if recreate else translate('Updating')} {member(adapter, vmid)}...")
|
||||
adapter.replace(vmid, state['prepared'][str(vmid)], state['id'])
|
||||
msg_ok(f"{translate('Updated:')} {member(adapter, vmid)}")
|
||||
msg_ok(f"{translate('Recreated:') if recreate else translate('Updated:')} {member(adapter, vmid)}")
|
||||
save(path, state, 'starting')
|
||||
for vmid in plan['start_order']:
|
||||
msg_info(f"{translate('Starting')} {member(adapter, vmid)}...")
|
||||
@@ -162,10 +163,10 @@ def _apply(path, plan, adapter):
|
||||
adapter.healthcheck(vmid)
|
||||
msg_ok(f"{translate('Service responding:')} {member(adapter, vmid)}")
|
||||
save(path, state, 'checking')
|
||||
msg_info(translate('Checking the updated stack...'))
|
||||
msg_info(translate('Checking the recreated stack...') if recreate else translate('Checking the updated stack...'))
|
||||
adapter.validate_candidates(state)
|
||||
adapter.restore_running_state(running, plan['start_order'])
|
||||
msg_ok(translate('Updated stack checked'))
|
||||
msg_ok(translate('Recreated stack checked') if recreate else translate('Updated stack checked'))
|
||||
save(path, state, 'publishing')
|
||||
msg_info(translate('Saving the stack records...'))
|
||||
adapter.publish(state)
|
||||
|
||||
@@ -159,7 +159,7 @@ def application_name(record, vmid):
|
||||
def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=None):
|
||||
operation = 'recreate' if proposal is not None else 'update'
|
||||
msg_info(translate('Checking the container before the update...') if operation == 'update'
|
||||
else translate('Checking the container before recreating it...'))
|
||||
else translate('Checking the container before modifying it...'))
|
||||
with instances.locked(instances.ROOT):
|
||||
record = instances.read(instances.ROOT, vmid)
|
||||
if record['status'] != 'installed' or record.get('pending_transaction') or record.get('pending_stack_transaction'):
|
||||
|
||||
@@ -285,7 +285,8 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()):
|
||||
return False
|
||||
if row['stack']:
|
||||
return _manage_stack(project, ui, row, action, lifecycle_args)
|
||||
if action == 'modify':
|
||||
# Rebuilding as it is exists only for a multi-container application.
|
||||
if action == 'recreate':
|
||||
return False
|
||||
if not row['pending']:
|
||||
if row['status'] != 'installed' or row['reason'] != 'matched':
|
||||
@@ -293,7 +294,7 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()):
|
||||
return False
|
||||
if action is None:
|
||||
action = ui.choose(translate('Manage OCI'), [('update', translate('Update the image with the saved configuration')),
|
||||
('recreate', translate('Recreate: edit resources, network, paths and GPU')),
|
||||
('modify', translate('Modify: edit resources, network, paths and GPU')),
|
||||
('remove', translate('Remove: delete the application and its containers'))], 'update')
|
||||
if action is None:
|
||||
return False
|
||||
@@ -323,7 +324,7 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()):
|
||||
ui.message(str(error), translate('Review external OCI changes'))
|
||||
return False
|
||||
proposal = None
|
||||
if action == 'recreate':
|
||||
if action == 'modify':
|
||||
from .recreation import edit_recreation
|
||||
from .cli import _deployment_summary_text
|
||||
from .ui import BacktrackUI, RestartWizard
|
||||
@@ -333,8 +334,8 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()):
|
||||
try:
|
||||
proposal = edit_recreation(record, wizard)
|
||||
approved = wizard.review(_deployment_summary_text(proposal['candidate']['template'],
|
||||
proposal['candidate']['deployment']), translate('Recreate OCI'),
|
||||
question=translate('Recreate with these options?'), default=True)
|
||||
proposal['candidate']['deployment']), translate('Modify OCI'),
|
||||
question=translate('Apply these changes?'), default=True)
|
||||
break
|
||||
except RestartWizard:
|
||||
wizard.restart()
|
||||
@@ -354,7 +355,7 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()):
|
||||
# outside the container and its backup.
|
||||
if '--acknowledge-external-data' not in command:
|
||||
command.append('--acknowledge-external-data')
|
||||
title = translate('Recreate OCI') if proposal else translate('Update OCI')
|
||||
title = translate('Modify OCI') if proposal else translate('Update OCI')
|
||||
if proposal is None:
|
||||
completed = _run_lifecycle(command, title)
|
||||
else:
|
||||
@@ -598,7 +599,7 @@ def _image_too_recent(project, vmid, min_age_days):
|
||||
|
||||
def direct_management(project, vmid, action, lifecycle_args=(), unattended=False, min_image_age_days=0):
|
||||
"""One operation on one instance, without the list of the menu: the entry
|
||||
ProxMenux Monitor uses for its Update and Recreate buttons and for
|
||||
ProxMenux Monitor uses for its Update and Modify buttons and for
|
||||
scheduled updates."""
|
||||
from .ui import interactive_ui
|
||||
ui = UnattendedUI() if unattended else interactive_ui()
|
||||
|
||||
@@ -180,12 +180,12 @@ def change_recognition(project, ui, primary, member, run_lifecycle):
|
||||
choices = recognition_choices(storage)
|
||||
if [tag for tag, _, _ in choices] == ['cpu'] and current == 'cpu':
|
||||
ui.message(translate('No usable GPU was found on this host. Recognition stays on the CPU.'),
|
||||
translate('Recreate OCI'))
|
||||
translate('Modify OCI stack'))
|
||||
return False
|
||||
selected = ui.choose(translate('What runs the recognition of Immich'),
|
||||
[(tag, label) for tag, label, _ in choices], current)
|
||||
if selected is None or selected == current:
|
||||
ui.message(translate('Nothing was changed.'), translate('Recreate OCI'))
|
||||
ui.message(translate('Nothing was changed.'), translate('Modify OCI stack'))
|
||||
return False
|
||||
details = next(extra for tag, _, extra in choices if tag == selected)
|
||||
if details.get('experimental') and not confirm_experimental_rocm(ui):
|
||||
@@ -196,7 +196,7 @@ def change_recognition(project, ui, primary, member, run_lifecycle):
|
||||
'cache, the library and the database are kept. The whole application is stopped and '
|
||||
'updated, as in an update; if anything fails, the previous containers and the previous '
|
||||
'choice are restored.'))
|
||||
if not ui.review(text, translate('Recreate OCI'), question=translate('Change the recognition now?'), default=True):
|
||||
if not ui.review(text, translate('Modify OCI stack'), question=translate('Change the recognition now?'), default=True):
|
||||
return False
|
||||
command = [sys.executable, str(project / 'remote/oci_immich_recognition.py'), str(primary['vmid']),
|
||||
'--acceleration', selected]
|
||||
@@ -204,13 +204,13 @@ def change_recognition(project, ui, primary, member, run_lifecycle):
|
||||
command += ['--render-device', details['render']]
|
||||
if details.get('override'):
|
||||
command += ['--gfx-override', details['override']]
|
||||
return run_lifecycle(command, translate('Recreate OCI'))
|
||||
return run_lifecycle(command, translate('Modify OCI stack'))
|
||||
|
||||
|
||||
def modify_stack(project, ui, primary, run_lifecycle):
|
||||
learning = immich_learning(primary)
|
||||
if learning is not None:
|
||||
what = ui.choose(translate('What to recreate'),
|
||||
what = ui.choose(translate('What to modify'),
|
||||
[('paths', translate('Add or remove extra paths and devices')),
|
||||
('recognition', translate('Change what runs recognition: CPU or GPU'))], 'paths')
|
||||
if what is None:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""The steps of a coordinated operation name the operation that is running."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "remote"))
|
||||
|
||||
import oci_stack_transaction as transaction
|
||||
|
||||
|
||||
class StackOperationWording(unittest.TestCase):
|
||||
def steps(self, operation):
|
||||
plan = {"operation": operation, "primary_vmid": 138, "members": [{"vmid": 138}],
|
||||
"start_order": [138], "stop_order": [138]}
|
||||
adapter = MagicMock(spec=["validate", "is_running", "prepare", "stop", "backup", "verify_backups", "replace",
|
||||
"start", "healthcheck", "validate_candidates", "restore_running_state", "publish"])
|
||||
adapter.is_running.return_value = False
|
||||
adapter.prepare.return_value = {}
|
||||
adapter.backup.return_value = "backup"
|
||||
said = []
|
||||
with patch.object(transaction, "save"), patch.object(transaction, "translate", side_effect=lambda text: text), \
|
||||
patch.object(transaction, "msg_info", side_effect=said.append), \
|
||||
patch.object(transaction, "msg_ok", side_effect=said.append):
|
||||
transaction._apply(Path("/nonexistent/journal.json"), plan, adapter)
|
||||
return said
|
||||
|
||||
def test_a_recreation_never_says_it_is_updating(self):
|
||||
said = self.steps("recreate")
|
||||
self.assertIn("Recreating CT 138...", said)
|
||||
self.assertIn("Recreated: CT 138", said)
|
||||
self.assertIn("Recreated stack checked", said)
|
||||
self.assertEqual([line for line in said if "pdat" in line], [])
|
||||
|
||||
def test_an_update_keeps_its_wording(self):
|
||||
said = self.steps("update")
|
||||
self.assertIn("Updating CT 138...", said)
|
||||
self.assertIn("Updated: CT 138", said)
|
||||
self.assertIn("Updated stack checked", said)
|
||||
self.assertEqual([line for line in said if "ecreat" in line], [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user