lager-cli 0.32.2__tar.gz → 0.32.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lager_cli-0.32.2/lager_cli.egg-info → lager_cli-0.32.4}/PKG-INFO +1 -1
- {lager_cli-0.32.2 → lager_cli-0.32.4}/__init__.py +1 -1
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/uninstall.py +8 -3
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/update.py +206 -22
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/setup_and_deploy_box.sh +19 -15
- {lager_cli-0.32.2 → lager_cli-0.32.4}/gateway_auth.py +60 -12
- {lager_cli-0.32.2 → lager_cli-0.32.4/lager_cli.egg-info}/PKG-INFO +1 -1
- {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/SOURCES.txt +1 -0
- lager_cli-0.32.4/tests/test_update_gate.py +164 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/LICENSE +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/MANIFEST.in +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/README.md +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/__main__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/address_utils.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/battery/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/battery/battery_tui.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/battery/websocket_client.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/box_storage.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_host_ops.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_mount_prep.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_pip_validation.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_shim_verbs.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_ssh.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/box_group.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/boxes.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/config.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/diagnose.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/dut.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/hello.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/instruments.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/labjack_pins.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/lock.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/net_tui.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/nets.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/ssh.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/ssh_setup.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/ble.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/blufi.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/i2c.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/router.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/spi.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/uart.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/usb.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/websocket_client.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/wifi.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/arm.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/commands.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/gdb.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/net_cache.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/service_client.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/service_helper.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/tunnel.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/devenv.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/python.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/adc.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/dac.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/energy.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/gpi.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/gpo.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/logic.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/scope.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/thermocouple.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/watt.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/battery.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/battery_model_csv.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/eload.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/solar.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/supply.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/binaries.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/defaults.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/exec_.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/install.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/install_wheel.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/login.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/logs.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/webcam.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/config.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/context/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/context/ci_detection.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/context/constants.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/context/core.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/context/error_handlers.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/context/session.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/group_usage.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/matchers.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/net_group.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/net_helpers.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/net_storage.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/param_types.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/ssh_utils.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/utils.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/version_skew.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/core/ws_diagnose.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/configure_docker_dns.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/configure_docker_dns.sh +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/convert_to_sparse_checkout.sh +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/setup_ssh_key.sh +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/security/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/security/secure_box_firewall.sh +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/construct_utils.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/exceptions.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/py3compat.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/utils.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/adapters.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/core.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/debug.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/macros.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/abbrevtable.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/aranges.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/callframe.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/compileunit.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/constants.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/descriptions.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/die.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/dwarf_expr.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/dwarfinfo.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/enums.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/lineprogram.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/locationlists.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/namelut.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/ranges.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/structs.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/constants.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/decoder.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/ehabiinfo.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/structs.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/constants.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/descriptions.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/dynamic.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/elffile.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/enums.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/gnuversions.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/hash.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/notes.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/relocation.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/sections.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/segments.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/structs.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/errors.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/exceptions.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/box_config.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/measurement/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/measurement/scope.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/measurement/scope_stream.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/power/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/power/enable_disable.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/dependency_links.txt +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/entry_points.txt +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/requires.txt +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/top_level.txt +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/main.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/pyproject.toml +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/safe_unpickle.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/setup.cfg +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/setup.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/simple_hdlc.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/sort_utils.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/status.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/supply/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/supply/supply_tui.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/supply/websocket_client.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/core/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/core/executor.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/main.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/completer.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/display.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/logo.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/repl.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/themes.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/tests/test_box_lager_imports.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/tests/test_box_storage.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/tests/test_gateway_auth.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/tests/test_io_imports.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/update_check.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC16.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC16DNP.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC16Kermit.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC16SICK.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC32.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRCCCITT.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/__init__.py +0 -0
- {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/__init__.py +0 -0
|
@@ -603,7 +603,7 @@ def uninstall(ctx, box, ip, user, keep_config, keep_docker_images, remove_all, y
|
|
|
603
603
|
click.echo(" - Docker containers (lager, pigpio)")
|
|
604
604
|
click.echo(" - Docker network (lagernet)")
|
|
605
605
|
if not keep_docker_images:
|
|
606
|
-
click.echo(" - Docker images (
|
|
606
|
+
click.echo(" - Docker images (the lager image and dangling layers only)")
|
|
607
607
|
click.echo(" - ~/box directory")
|
|
608
608
|
if not keep_config:
|
|
609
609
|
click.echo(" - /etc/lager directory (saved nets)")
|
|
@@ -707,10 +707,15 @@ def uninstall(ctx, box, ip, user, keep_config, keep_docker_images, remove_all, y
|
|
|
707
707
|
run_ssh("docker network rm lagernet 2>/dev/null", "Removing lagernet network", allow_fail=True)
|
|
708
708
|
click.echo()
|
|
709
709
|
|
|
710
|
-
# Remove Docker images (unless --keep-docker-images)
|
|
710
|
+
# Remove Docker images (unless --keep-docker-images). Scoped to the
|
|
711
|
+
# lager image plus dangling layers: 'prune -af' would also delete
|
|
712
|
+
# images belonging to any OTHER stopped container on the box (a
|
|
713
|
+
# management agent, a user's own services) — infrastructure this
|
|
714
|
+
# command cannot restore.
|
|
711
715
|
click.secho("[Step 2/5] Cleaning Docker...", fg='cyan')
|
|
712
716
|
if not keep_docker_images:
|
|
713
|
-
run_ssh("docker
|
|
717
|
+
run_ssh("docker rmi -f lager 2>/dev/null", "Removing lager image", allow_fail=True)
|
|
718
|
+
run_ssh("docker image prune -f 2>/dev/null", "Removing dangling images", allow_fail=True)
|
|
714
719
|
run_ssh("docker builder prune -af 2>/dev/null", "Clearing Docker build cache", allow_fail=True)
|
|
715
720
|
else:
|
|
716
721
|
click.echo(" Skipping Docker image removal (--keep-docker-images)")
|
|
@@ -229,6 +229,15 @@ if test -f /etc/lager/.boxcfg-sudoers-v2 && sudo -n DEBIAN_FRONTEND=noninteracti
|
|
|
229
229
|
else
|
|
230
230
|
echo "LAGER_PROBE_BOXCFG_SUDOERS_OK=0"
|
|
231
231
|
fi
|
|
232
|
+
# Container liveness: 1 = the `lager` container is running, 0 = docker
|
|
233
|
+
# answered and it is not, empty = docker itself didn't answer (unknown;
|
|
234
|
+
# callers fail open). `docker ps` lists running containers only, and its
|
|
235
|
+
# name filter is a substring match, so exact-match the name on our side.
|
|
236
|
+
if _dps=$(docker ps --filter name=lager --format '{{.Names}}' 2>/dev/null); then
|
|
237
|
+
echo "LAGER_PROBE_LAGER_RUNNING=$(printf '%s\n' "$_dps" | grep -cx lager)"
|
|
238
|
+
else
|
|
239
|
+
echo "LAGER_PROBE_LAGER_RUNNING="
|
|
240
|
+
fi
|
|
232
241
|
echo "LAGER_PROBE_ETC_VERSION=$(cat /etc/lager/version 2>/dev/null)"
|
|
233
242
|
'''
|
|
234
243
|
return script.replace('__BUILD_HASH_CMD__', _build_hash_shell_cmd())
|
|
@@ -249,6 +258,60 @@ def _parse_probe_output(stdout):
|
|
|
249
258
|
return facts
|
|
250
259
|
|
|
251
260
|
|
|
261
|
+
def _build_hash_mismatch(new_hash, stored_hash):
|
|
262
|
+
"""True when the docker-build inputs changed relative to the last
|
|
263
|
+
successful build.
|
|
264
|
+
|
|
265
|
+
Both values must be non-empty: an empty new hash means the probe couldn't
|
|
266
|
+
measure the inputs, an empty stored hash means no successful build has
|
|
267
|
+
recorded one — in either case auto-invalidation is skipped rather than
|
|
268
|
+
guessed. A failed build stores the sentinel value 'FAILED' (never a real
|
|
269
|
+
sha256), which forces a mismatch — and therefore a clean rebuild — on the
|
|
270
|
+
next run.
|
|
271
|
+
"""
|
|
272
|
+
return bool(new_hash) and bool(stored_hash) and new_hash != stored_hash
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _rebuild_gate_verdict(facts, *, git_sync_confirmed, needs_pull,
|
|
276
|
+
needs_flatten, hash_mismatch, force):
|
|
277
|
+
"""Decide whether the update can stop before the container rebuild.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
'rebuild' — source state demands the full stop/build/start path.
|
|
281
|
+
'container-down' — source is in sync but the lager container is not
|
|
282
|
+
running (e.g. a previous update failed after the
|
|
283
|
+
containers were removed): rebuild so the box comes
|
|
284
|
+
back up, instead of falsely reporting success.
|
|
285
|
+
'skip' — in sync and running; safe to early-exit.
|
|
286
|
+
|
|
287
|
+
The probe's LAGER_RUNNING fact is tri-state: '1' running, '0' definitely
|
|
288
|
+
not running, ''/absent unknown (the docker probe itself failed). Only a
|
|
289
|
+
definite '0' blocks the skip — unknown fails open, same philosophy as the
|
|
290
|
+
BuildKit preflight.
|
|
291
|
+
"""
|
|
292
|
+
if not git_sync_confirmed or needs_pull or needs_flatten or hash_mismatch or force:
|
|
293
|
+
return 'rebuild'
|
|
294
|
+
if facts.get('LAGER_RUNNING', '') == '0':
|
|
295
|
+
return 'container-down'
|
|
296
|
+
return 'skip'
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _deployed_version_stale(tree_version, etc_version_raw):
|
|
300
|
+
"""True when the box tree's version differs from the last version a
|
|
301
|
+
successful update actually deployed (/etc/lager/version, `box|cli`
|
|
302
|
+
format — written only after a successful rebuild+restart).
|
|
303
|
+
|
|
304
|
+
Catches the tree-ahead-of-deploy state: an update that pulled and then
|
|
305
|
+
exited before the container stop (BuildKit preflight failure, an
|
|
306
|
+
interrupt in the pull->stop window) leaves the container serving the
|
|
307
|
+
previous build while git reads as in sync and the container as running.
|
|
308
|
+
Unknown on either side fails open (returns False) so legacy boxes with a
|
|
309
|
+
missing/empty version file don't rebuild forever.
|
|
310
|
+
"""
|
|
311
|
+
deployed = (etc_version_raw or '').split('|', 1)[0].strip()
|
|
312
|
+
return bool(tree_version) and bool(deployed) and deployed != tree_version
|
|
313
|
+
|
|
314
|
+
|
|
252
315
|
# Progress-bar denominator. 15 steps always run; 3 are conditional (flatten,
|
|
253
316
|
# cached-image wipe, J-Link install). We use the max so the denominator never
|
|
254
317
|
# jumps mid-flight — light paths simply finish below 18/18 and `finish()`
|
|
@@ -1082,7 +1145,7 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1082
1145
|
# any mutation (no git checkout, no udev install, no docker stop).
|
|
1083
1146
|
# Exit codes:
|
|
1084
1147
|
# 0 — already in sync, nothing to do
|
|
1085
|
-
# 1 — would update (code, deps, or
|
|
1148
|
+
# 1 — would update (code, deps, or a stopped container to bring back up)
|
|
1086
1149
|
# 2 — could not determine state (network error, etc.)
|
|
1087
1150
|
if check:
|
|
1088
1151
|
if progress:
|
|
@@ -1096,15 +1159,24 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1096
1159
|
# reflect the tree that would be built — no extra round-trip needed.
|
|
1097
1160
|
_check_new_hash = facts.get('BUILD_HASH_NEW', '')
|
|
1098
1161
|
_check_stored_hash = facts.get('BUILD_HASH_STORED', '')
|
|
1099
|
-
deps_will_change = (
|
|
1100
|
-
|
|
1101
|
-
and _check_new_hash != _check_stored_hash
|
|
1102
|
-
)
|
|
1162
|
+
deps_will_change = _build_hash_mismatch(_check_new_hash, _check_stored_hash)
|
|
1163
|
+
container_down = facts.get('LAGER_RUNNING', '') == '0'
|
|
1103
1164
|
|
|
1104
1165
|
if not git_sync_confirmed:
|
|
1105
1166
|
click.secho('Could not determine update state (git rev-list failed).', fg='red', err=True)
|
|
1106
1167
|
ctx.exit(2)
|
|
1107
1168
|
|
|
1169
|
+
# Only worth a look when everything else reads clean — any other
|
|
1170
|
+
# divergence already forces the rebuild that would fix it. (Local
|
|
1171
|
+
# import: a later `import re` in this function makes `re` local to
|
|
1172
|
+
# the whole function, so the module-level binding is shadowed here.)
|
|
1173
|
+
deploy_stale = False
|
|
1174
|
+
if not container_down and commits_behind == 0 and commits_ahead == 0:
|
|
1175
|
+
import re
|
|
1176
|
+
_vp = re.match(r'^v?(\d+\.\d+\.\d+)$', target_version)
|
|
1177
|
+
_tree_v = _vp.group(1) if _vp else _read_box_source_version(run_ssh_command_with_output)
|
|
1178
|
+
deploy_stale = _deployed_version_stale(_tree_v, current_version_raw)
|
|
1179
|
+
|
|
1108
1180
|
if commits_behind == 0 and commits_ahead == 0:
|
|
1109
1181
|
code_status = 'in sync'
|
|
1110
1182
|
elif is_rollback:
|
|
@@ -1131,6 +1203,17 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1131
1203
|
if force:
|
|
1132
1204
|
container_status = 'will restart (forced clean rebuild)'
|
|
1133
1205
|
est = '~6 min (fresh build)'
|
|
1206
|
+
elif container_down:
|
|
1207
|
+
# Code may be fully in sync, but there is nothing serving it — a
|
|
1208
|
+
# previous update likely failed after removing the containers.
|
|
1209
|
+
container_status = 'NOT RUNNING (will rebuild and restart)'
|
|
1210
|
+
est = '~6 min (fresh build possible)' if deps_will_change else '~90s (cached build)'
|
|
1211
|
+
elif deploy_stale:
|
|
1212
|
+
# In sync and running, but the container was deployed from an
|
|
1213
|
+
# older version — a prior update pulled and stopped before the
|
|
1214
|
+
# rebuild.
|
|
1215
|
+
container_status = 'running a STALE build (will rebuild and restart)'
|
|
1216
|
+
est = '~90s (cached build)'
|
|
1134
1217
|
elif commits_behind == 0 and commits_ahead == 0 and not deps_will_change:
|
|
1135
1218
|
container_status = 'no restart needed'
|
|
1136
1219
|
est = '~5s'
|
|
@@ -1153,7 +1236,10 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1153
1236
|
click.echo(f' Estimated: {est}')
|
|
1154
1237
|
click.echo()
|
|
1155
1238
|
|
|
1156
|
-
will_change =
|
|
1239
|
+
will_change = (
|
|
1240
|
+
force or commits_behind != 0 or commits_ahead != 0
|
|
1241
|
+
or deps_will_change or container_down or deploy_stale
|
|
1242
|
+
)
|
|
1157
1243
|
if will_change:
|
|
1158
1244
|
click.echo('Run without --check to apply.')
|
|
1159
1245
|
ctx.exit(1)
|
|
@@ -1256,10 +1342,7 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1256
1342
|
new_build_hash = _read_build_hash(run_ssh_command_with_output)
|
|
1257
1343
|
else:
|
|
1258
1344
|
new_build_hash = facts.get('BUILD_HASH_NEW', '')
|
|
1259
|
-
hash_mismatch = (
|
|
1260
|
-
bool(new_build_hash) and bool(stored_build_hash)
|
|
1261
|
-
and new_build_hash != stored_build_hash
|
|
1262
|
-
)
|
|
1345
|
+
hash_mismatch = _build_hash_mismatch(new_build_hash, stored_build_hash)
|
|
1263
1346
|
# `--force` requests a clean rebuild: wipe the cached image and the
|
|
1264
1347
|
# cargo/npm volumes so a prior failed/partial run can't leave a stale layer
|
|
1265
1348
|
# or half-installed toolchain behind on the retry.
|
|
@@ -1573,17 +1656,24 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1573
1656
|
# but code is in sync); previously this branch ignored the hash and the
|
|
1574
1657
|
# rebuild silently never happened.
|
|
1575
1658
|
#
|
|
1576
|
-
#
|
|
1577
|
-
# failed
|
|
1578
|
-
#
|
|
1579
|
-
#
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1659
|
+
# Source state alone is not enough to declare success: an update that
|
|
1660
|
+
# failed after Step 8 (containers stopped and REMOVED) leaves a box that
|
|
1661
|
+
# reads as "in sync" here — git pulled fine, the stored hash predates the
|
|
1662
|
+
# failure — but has nothing running at all. Early-exiting then reports
|
|
1663
|
+
# success on a dead box, so the verdict also requires the lager container
|
|
1664
|
+
# to be running: 'container-down' falls through to the rebuild path below
|
|
1665
|
+
# and brings the box back up. `--force` still skips the early-exit
|
|
1666
|
+
# unconditionally.
|
|
1667
|
+
_gate = _rebuild_gate_verdict(
|
|
1668
|
+
facts,
|
|
1669
|
+
git_sync_confirmed=git_sync_confirmed,
|
|
1670
|
+
needs_pull=needs_pull,
|
|
1671
|
+
needs_flatten=needs_flatten,
|
|
1672
|
+
hash_mismatch=hash_mismatch,
|
|
1673
|
+
force=force,
|
|
1674
|
+
)
|
|
1675
|
+
_box_v = '' # assigned for real below whenever _gate stays 'skip'
|
|
1676
|
+
if _gate == 'skip':
|
|
1587
1677
|
import re as _re
|
|
1588
1678
|
|
|
1589
1679
|
# Prefer the box's source-declared `__version__` over the CLI
|
|
@@ -1594,10 +1684,26 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1594
1684
|
_vp = _re.match(r'^v?(\d+\.\d+\.\d+)$', target_version)
|
|
1595
1685
|
if _vp:
|
|
1596
1686
|
_box_v = _vp.group(1)
|
|
1687
|
+
_tree_v = _box_v
|
|
1597
1688
|
else:
|
|
1598
1689
|
_src_v = _read_box_source_version(run_ssh_command_with_output)
|
|
1599
1690
|
_box_v = _src_v if _src_v else cli_version
|
|
1600
|
-
|
|
1691
|
+
# For the staleness check, only trust an actually-read source
|
|
1692
|
+
# version — the cli_version fallback is a display default, and
|
|
1693
|
+
# comparing it against the box would manufacture mismatches.
|
|
1694
|
+
_tree_v = _src_v
|
|
1695
|
+
|
|
1696
|
+
# Liveness alone can't tell whether the RUNNING container was built
|
|
1697
|
+
# from the code now in the tree: an update that pulled and then
|
|
1698
|
+
# exited before the container stop (BuildKit preflight failure, an
|
|
1699
|
+
# interrupt in the pull->stop window) leaves the old build serving
|
|
1700
|
+
# while git reads in sync. The deployed-version record catches that;
|
|
1701
|
+
# fall through and rebuild instead of stamping the new version onto
|
|
1702
|
+
# a box still running the old one.
|
|
1703
|
+
if _deployed_version_stale(_tree_v, facts.get('ETC_VERSION', '')):
|
|
1704
|
+
_gate = 'stale-deploy'
|
|
1705
|
+
|
|
1706
|
+
if _gate == 'skip':
|
|
1601
1707
|
# Reconcile /etc/lager/version on the box with the local cache.
|
|
1602
1708
|
# Previously this branch only updated the local ~/.lager file, so if
|
|
1603
1709
|
# the box's /etc/lager/version was stale (e.g. an earlier update
|
|
@@ -1624,6 +1730,25 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1624
1730
|
click.echo()
|
|
1625
1731
|
ctx.exit(0)
|
|
1626
1732
|
|
|
1733
|
+
if _gate in ('container-down', 'stale-deploy'):
|
|
1734
|
+
if _gate == 'container-down':
|
|
1735
|
+
_fall_msg = (
|
|
1736
|
+
'Code is in sync but the lager container is not running '
|
|
1737
|
+
'(a previous update may have failed mid-rebuild) — rebuilding '
|
|
1738
|
+
'and restarting.'
|
|
1739
|
+
)
|
|
1740
|
+
else:
|
|
1741
|
+
_fall_msg = (
|
|
1742
|
+
'Code is in sync but the running container was deployed from '
|
|
1743
|
+
'a previous version (a prior update may have stopped before '
|
|
1744
|
+
'the rebuild) — rebuilding and restarting.'
|
|
1745
|
+
)
|
|
1746
|
+
if progress:
|
|
1747
|
+
progress.pause()
|
|
1748
|
+
click.echo(_fall_msg)
|
|
1749
|
+
if progress:
|
|
1750
|
+
progress.resume()
|
|
1751
|
+
|
|
1627
1752
|
# BuildKit preflight — run BEFORE the lock and the container stop below so a
|
|
1628
1753
|
# box whose Docker can't build the image fails fast WITHOUT being taken
|
|
1629
1754
|
# offline. box.Dockerfile uses a `# syntax=` directive and `RUN
|
|
@@ -1813,12 +1938,71 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
|
|
|
1813
1938
|
"`sudo apt-get install -y docker-buildx`), then re-run `lager update`.",
|
|
1814
1939
|
fg='yellow', err=True,
|
|
1815
1940
|
)
|
|
1941
|
+
elif "failed to prepare extraction snapshot" in full_output or (
|
|
1942
|
+
"parent snapshot" in full_output and "does not exist" in full_output
|
|
1943
|
+
):
|
|
1944
|
+
click.secho(
|
|
1945
|
+
"Hint: the box's Docker build cache is corrupted (a cached snapshot "
|
|
1946
|
+
"references a parent that no longer exists). Clear it with: "
|
|
1947
|
+
f"ssh {ssh_host} 'docker builder prune -af' and re-run `lager update`. "
|
|
1948
|
+
"The next build runs cold and can take 10-15 minutes.",
|
|
1949
|
+
fg='yellow', err=True,
|
|
1950
|
+
)
|
|
1816
1951
|
elif "No space left on device" in full_output:
|
|
1817
1952
|
click.secho("Hint: Disk space is full on the box. Run: ssh lagerdata@[BOX_NAME] 'docker system prune -af'", fg='yellow', err=True)
|
|
1818
1953
|
elif "network" in full_output.lower() and ("timeout" in full_output.lower() or "error" in full_output.lower()):
|
|
1819
1954
|
click.secho("Hint: Network issue during build. Check box internet connectivity.", fg='yellow', err=True)
|
|
1820
1955
|
elif "permission denied" in full_output.lower():
|
|
1821
1956
|
click.secho("Hint: Permission issue. Check Docker daemon is running and user has access.", fg='yellow', err=True)
|
|
1957
|
+
|
|
1958
|
+
# Poison the stored build-inputs hash so the next run can't read this
|
|
1959
|
+
# box as up to date: the sentinel never equals a recomputed sha256, so
|
|
1960
|
+
# hash_mismatch fires on the retry, blocking the "already at version"
|
|
1961
|
+
# early-exit and forcing a clean image wipe + rebuild. Deleting the
|
|
1962
|
+
# file would NOT do this — _build_hash_mismatch requires both values
|
|
1963
|
+
# non-empty. Best-effort: if the write fails, the liveness gate still
|
|
1964
|
+
# catches the dead-container case.
|
|
1965
|
+
try:
|
|
1966
|
+
store_build_hash('FAILED')
|
|
1967
|
+
except (subprocess.SubprocessError, OSError):
|
|
1968
|
+
pass
|
|
1969
|
+
|
|
1970
|
+
# Step 8 already stopped and removed the containers, so exiting here
|
|
1971
|
+
# would strand the box with no services — the state that previously
|
|
1972
|
+
# made the next run report "already at version" on a dead box. Unless
|
|
1973
|
+
# this run wiped the image (hash change / --force), the previous image
|
|
1974
|
+
# is still tagged `lager` and its baked-in code matches what was
|
|
1975
|
+
# running before the pull, so restart it: the box stays usable on its
|
|
1976
|
+
# prior version while the operator deals with the build failure.
|
|
1977
|
+
restarted = False
|
|
1978
|
+
if not must_wipe_image:
|
|
1979
|
+
click.echo()
|
|
1980
|
+
click.secho('Restarting the box on its previous version...', fg='yellow', err=True)
|
|
1981
|
+
try:
|
|
1982
|
+
recover = run_ssh_command_with_output(
|
|
1983
|
+
'cd ~/box && chmod +x start_box.sh && LAGER_SKIP_BUILD=1 ./start_box.sh',
|
|
1984
|
+
timeout_secs=600,
|
|
1985
|
+
)
|
|
1986
|
+
# Exit 3 = container up but post-start installs failed; the
|
|
1987
|
+
# box is serving, which is all this recovery promises.
|
|
1988
|
+
restarted = recover.returncode in (0, 3)
|
|
1989
|
+
except (subprocess.SubprocessError, OSError):
|
|
1990
|
+
restarted = False
|
|
1991
|
+
if restarted:
|
|
1992
|
+
restarted = wait_for_box_ready(resolved_box, timeout_s=60)
|
|
1993
|
+
if restarted:
|
|
1994
|
+
click.secho(
|
|
1995
|
+
'The update FAILED and was not applied. The box has been '
|
|
1996
|
+
'restarted on its previous version.',
|
|
1997
|
+
fg='yellow', err=True,
|
|
1998
|
+
)
|
|
1999
|
+
else:
|
|
2000
|
+
click.secho(
|
|
2001
|
+
'The update FAILED and the box was left with its services '
|
|
2002
|
+
'stopped. Bring it back up manually with:',
|
|
2003
|
+
fg='red', err=True,
|
|
2004
|
+
)
|
|
2005
|
+
click.echo(f' ssh {ssh_host} "cd ~/box && ./start_box.sh"', err=True)
|
|
1822
2006
|
ctx.exit(1)
|
|
1823
2007
|
if verbose:
|
|
1824
2008
|
click.secho(' Build complete', fg='green')
|
|
@@ -1339,33 +1339,37 @@ print_info "Ensuring Docker service is enabled (for auto-start on boot)..."
|
|
|
1339
1339
|
ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" "sudo systemctl enable docker >/dev/null 2>&1 || true"
|
|
1340
1340
|
print_success "Docker service enabled"
|
|
1341
1341
|
|
|
1342
|
-
print_info "Stopping and removing
|
|
1343
|
-
#
|
|
1342
|
+
print_info "Stopping and removing lager containers..."
|
|
1343
|
+
# Scoped to the containers this deployment owns (lager, pigpio, and the
|
|
1344
|
+
# legacy controller). A box may run third-party containers alongside lager
|
|
1345
|
+
# — a management agent, a user's own services — and removing containers we
|
|
1346
|
+
# did not create takes down infrastructure this script cannot restore.
|
|
1347
|
+
# Update restart policy first to prevent auto-restart, then force remove.
|
|
1344
1348
|
ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" "
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
docker rm -f \$(docker ps -aq) 2>/dev/null || true
|
|
1351
|
-
# Prune any leftover containers
|
|
1352
|
-
docker container prune -f 2>/dev/null || true
|
|
1349
|
+
for c in lager pigpio controller; do
|
|
1350
|
+
docker update --restart=no \$c 2>/dev/null || true
|
|
1351
|
+
docker stop \$c 2>/dev/null || true
|
|
1352
|
+
docker rm -f \$c 2>/dev/null || true
|
|
1353
|
+
done
|
|
1353
1354
|
# Wait a moment for Docker to clean up
|
|
1354
1355
|
sleep 2
|
|
1355
1356
|
" 2>/dev/null || true
|
|
1356
|
-
print_success "
|
|
1357
|
+
print_success "Lager containers cleaned up"
|
|
1357
1358
|
|
|
1358
|
-
print_info "Cleaning up Docker
|
|
1359
|
+
print_info "Cleaning up Docker build cache and dangling images..."
|
|
1359
1360
|
echo ""
|
|
1361
|
+
# Dangling-only (no -a): 'prune -af' would also delete images belonging to
|
|
1362
|
+
# third-party containers stopped at this moment, which cannot be re-pulled
|
|
1363
|
+
# by this script.
|
|
1360
1364
|
ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" "
|
|
1361
|
-
echo 'Removing
|
|
1362
|
-
docker image prune -
|
|
1365
|
+
echo 'Removing dangling Docker images...'
|
|
1366
|
+
docker image prune -f 2>/dev/null || true
|
|
1363
1367
|
echo 'Removing Docker build cache...'
|
|
1364
1368
|
docker builder prune -af 2>/dev/null || true
|
|
1365
1369
|
echo 'Cleanup complete'
|
|
1366
1370
|
" 2>/dev/null || true
|
|
1367
1371
|
echo ""
|
|
1368
|
-
print_success "Docker
|
|
1372
|
+
print_success "Docker build cache and dangling images cleaned up"
|
|
1369
1373
|
|
|
1370
1374
|
print_info "Checking available disk space..."
|
|
1371
1375
|
ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" "df -h / | tail -n 1 | awk '{print \"Available: \" \$4 \" (\" \$5 \" used)\"}'" 2>/dev/null || true
|
|
@@ -99,6 +99,27 @@ def _token_expires_at(access_token):
|
|
|
99
99
|
return 0.0
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
def _refresh_margin(access_token):
|
|
103
|
+
"""Refresh-ahead margin, capped at a quarter of the token's lifetime.
|
|
104
|
+
|
|
105
|
+
A fixed margin at least as large as the token lifetime would classify
|
|
106
|
+
every freshly issued token as already stale, turning each request into
|
|
107
|
+
a refresh round-trip — a refresh storm against servers issuing
|
|
108
|
+
short-lived tokens, with each refresh rotating the refresh-token
|
|
109
|
+
family and multiplying the chances of losing the session.
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
payload_b64 = access_token.split('.')[1]
|
|
113
|
+
payload_b64 += '=' * (-len(payload_b64) % 4)
|
|
114
|
+
payload = json.loads(base64.urlsafe_b64decode(payload_b64))
|
|
115
|
+
lifetime = float(payload['exp']) - float(payload['iat'])
|
|
116
|
+
except (IndexError, KeyError, ValueError, TypeError):
|
|
117
|
+
return EXPIRY_MARGIN_SECONDS
|
|
118
|
+
if lifetime <= 0:
|
|
119
|
+
return EXPIRY_MARGIN_SECONDS
|
|
120
|
+
return min(EXPIRY_MARGIN_SECONDS, max(lifetime / 4.0, 1.0))
|
|
121
|
+
|
|
122
|
+
|
|
102
123
|
def save_login(url, access_token, cookies):
|
|
103
124
|
"""Store a session: the bearer token plus whatever cookies the auth
|
|
104
125
|
server set at login (typically an httpOnly refresh token)."""
|
|
@@ -121,20 +142,35 @@ def clear_login(url=None):
|
|
|
121
142
|
|
|
122
143
|
|
|
123
144
|
def _refresh_access_token(url, entry):
|
|
124
|
-
"""Ask the auth server for a fresh access token, or return None.
|
|
145
|
+
"""Ask the auth server for a fresh access token, or return None.
|
|
146
|
+
|
|
147
|
+
Retries once, but ONLY when the request never reached the server
|
|
148
|
+
(connection error): the server rotates the refresh token on every
|
|
149
|
+
successful refresh, so replaying the request after an ambiguous
|
|
150
|
+
failure (e.g. a read timeout) could trip the server's replay
|
|
151
|
+
detection and burn the whole session.
|
|
152
|
+
"""
|
|
125
153
|
cookies = entry.get('cookies') or {}
|
|
126
154
|
if not cookies:
|
|
127
155
|
return None
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
156
|
+
resp = None
|
|
157
|
+
for _ in range(2):
|
|
158
|
+
try:
|
|
159
|
+
resp = requests.post(
|
|
160
|
+
f'{url}/api/auth/refresh',
|
|
161
|
+
cookies=cookies,
|
|
162
|
+
timeout=AUTH_SERVER_TIMEOUT,
|
|
163
|
+
)
|
|
164
|
+
break
|
|
165
|
+
except requests.ConnectionError:
|
|
166
|
+
continue
|
|
167
|
+
except requests.RequestException:
|
|
135
168
|
return None
|
|
169
|
+
if resp is None or resp.status_code != 200:
|
|
170
|
+
return None
|
|
171
|
+
try:
|
|
136
172
|
access_token = resp.json().get('accessToken')
|
|
137
|
-
except
|
|
173
|
+
except ValueError:
|
|
138
174
|
return None
|
|
139
175
|
if not access_token:
|
|
140
176
|
return None
|
|
@@ -145,14 +181,26 @@ def _refresh_access_token(url, entry):
|
|
|
145
181
|
|
|
146
182
|
|
|
147
183
|
def access_token_for(url):
|
|
148
|
-
"""Stored access token for an auth server, refreshed if near expiry.
|
|
184
|
+
"""Stored access token for an auth server, refreshed if near expiry.
|
|
185
|
+
|
|
186
|
+
A failed refresh is not immediately fatal: if the stored token has not
|
|
187
|
+
actually expired, return it anyway and let the gateway judge it — a
|
|
188
|
+
transient auth-server error should not fail a command whose token is
|
|
189
|
+
still valid.
|
|
190
|
+
"""
|
|
149
191
|
entry = _load_store().get('authServers', {}).get(url)
|
|
150
192
|
if not entry:
|
|
151
193
|
return None
|
|
152
194
|
access_token = entry.get('accessToken')
|
|
153
|
-
|
|
195
|
+
now = time.time()
|
|
196
|
+
if access_token and _token_expires_at(access_token) > now + _refresh_margin(access_token):
|
|
197
|
+
return access_token
|
|
198
|
+
refreshed = _refresh_access_token(url, entry)
|
|
199
|
+
if refreshed:
|
|
200
|
+
return refreshed
|
|
201
|
+
if access_token and _token_expires_at(access_token) > now:
|
|
154
202
|
return access_token
|
|
155
|
-
return
|
|
203
|
+
return None
|
|
156
204
|
|
|
157
205
|
|
|
158
206
|
def auth_headers_for_box(box_ip):
|