lager-cli 0.28.0__tar.gz → 0.28.2__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.28.0/lager_cli.egg-info → lager_cli-0.28.2}/PKG-INFO +1 -1
- {lager_cli-0.28.0 → lager_cli-0.28.2}/__init__.py +1 -1
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/ssh.py +14 -1
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/debug/commands.py +13 -3
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/debug/service_client.py +7 -2
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/devenv.py +354 -15
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/exec_.py +38 -24
- {lager_cli-0.28.0 → lager_cli-0.28.2}/config.py +33 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2/lager_cli.egg-info}/PKG-INFO +1 -1
- {lager_cli-0.28.0 → lager_cli-0.28.2}/LICENSE +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/MANIFEST.in +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/README.md +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/__main__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/address_utils.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/battery/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/battery/battery_tui.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/battery/websocket_client.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/box_storage.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/_host_ops.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/_mount_prep.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/_pip_validation.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/_shim_verbs.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/_ssh.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/authorize.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/box_group.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/boxes.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/config.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/diagnose.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/dut.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/hello.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/instruments.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/labjack_pins.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/lock.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/net_tui.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/box/nets.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/ble.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/blufi.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/i2c.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/router.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/spi.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/uart.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/usb.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/websocket_client.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/communication/wifi.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/arm.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/debug/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/debug/gdb.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/debug/net_cache.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/debug/service_helper.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/debug/tunnel.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/development/python.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/adc.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/dac.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/energy.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/gpi.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/gpo.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/logic.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/scope.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/thermocouple.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/measurement/watt.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/power/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/power/battery.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/power/eload.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/power/solar.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/power/supply.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/binaries.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/defaults.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/install.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/install_wheel.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/logs.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/uninstall.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/update.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/commands/utility/webcam.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/context/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/context/ci_detection.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/context/constants.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/context/core.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/context/error_handlers.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/context/session.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/group_usage.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/matchers.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/net_group.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/net_helpers.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/net_storage.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/param_types.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/ssh_utils.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/utils.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/version_skew.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/core/ws_diagnose.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/deployment/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/deployment/scripts/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/deployment/scripts/convert_to_sparse_checkout.sh +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/deployment/scripts/setup_and_deploy_box.sh +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/deployment/scripts/setup_ssh_key.sh +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/deployment/security/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/deployment/security/secure_box_firewall.sh +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/common/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/common/construct_utils.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/common/exceptions.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/common/py3compat.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/common/utils.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/construct/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/construct/adapters.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/construct/core.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/construct/debug.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/construct/macros.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/abbrevtable.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/aranges.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/callframe.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/compileunit.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/constants.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/descriptions.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/die.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/dwarf_expr.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/dwarfinfo.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/enums.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/lineprogram.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/locationlists.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/namelut.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/ranges.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/dwarf/structs.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/ehabi/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/ehabi/constants.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/ehabi/decoder.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/ehabi/ehabiinfo.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/ehabi/structs.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/constants.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/descriptions.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/dynamic.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/elffile.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/enums.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/gnuversions.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/hash.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/notes.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/relocation.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/sections.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/segments.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/elftools/elf/structs.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/errors.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/exceptions.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/box_config.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/communication/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/communication/ble.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/communication/blufi.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/communication/i2c.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/communication/router.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/communication/spi.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/communication/uart.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/communication/wifi.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/custom_devices.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/device/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/device/arm.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/device/hello.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/device/usb.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/device/webcam.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/adc.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/dac.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/energy.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/gpio.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/scope.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/scope_stream.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/thermocouple.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/measurement/watt.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/net.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/power/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/power/battery.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/power/eload.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/power/enable_disable.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/power/solar.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/power/supply.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/impl/query_instruments.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/lager_cli.egg-info/SOURCES.txt +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/lager_cli.egg-info/dependency_links.txt +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/lager_cli.egg-info/entry_points.txt +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/lager_cli.egg-info/requires.txt +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/lager_cli.egg-info/top_level.txt +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/main.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/pyproject.toml +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/safe_unpickle.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/setup.cfg +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/setup.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/simple_hdlc.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/sort_utils.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/status.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/supply/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/supply/supply_tui.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/supply/websocket_client.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/core/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/core/executor.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/main.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/ui/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/ui/completer.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/ui/display.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/ui/logo.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/ui/repl.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/terminal/ui/themes.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/tests/test_box_lager_imports.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/tests/test_box_storage.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/tests/test_io_imports.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/update_check.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/vendor/PyCRC/CRC16.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/vendor/PyCRC/CRC16DNP.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/vendor/PyCRC/CRC16Kermit.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/vendor/PyCRC/CRC16SICK.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/vendor/PyCRC/CRC32.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/vendor/PyCRC/CRCCCITT.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/vendor/PyCRC/__init__.py +0 -0
- {lager_cli-0.28.0 → lager_cli-0.28.2}/vendor/__init__.py +0 -0
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
"""
|
|
9
9
|
import click
|
|
10
10
|
from click.exceptions import Exit
|
|
11
|
+
import os
|
|
11
12
|
import subprocess
|
|
12
13
|
import platform
|
|
13
14
|
from ...box_storage import resolve_and_validate_box
|
|
14
15
|
from ...context import get_default_box
|
|
16
|
+
from ._ssh import _LAGER_BOX_KEY
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
def _get_ssh_install_hint() -> str:
|
|
@@ -62,10 +64,21 @@ def ssh(ctx, box):
|
|
|
62
64
|
# Build SSH command
|
|
63
65
|
ssh_host = f'{username}@{resolved_box}'
|
|
64
66
|
|
|
67
|
+
# Offer the dedicated lager_box key (installed by `lager authorize`) when
|
|
68
|
+
# present. It isn't one of ssh's default identity filenames, so without
|
|
69
|
+
# -i it's never tried and an authorized box still drops to a password
|
|
70
|
+
# prompt. -i appends to the identity list rather than replacing it (no
|
|
71
|
+
# IdentitiesOnly), so default keys and the password fallback still work
|
|
72
|
+
# for boxes that haven't been authorized.
|
|
73
|
+
ssh_cmd = ['ssh']
|
|
74
|
+
if os.path.exists(_LAGER_BOX_KEY):
|
|
75
|
+
ssh_cmd.extend(['-i', _LAGER_BOX_KEY])
|
|
76
|
+
ssh_cmd.append(ssh_host)
|
|
77
|
+
|
|
65
78
|
try:
|
|
66
79
|
# Run SSH as a child process (not exec) so Click ctx.call_on_close hooks run.
|
|
67
80
|
try:
|
|
68
|
-
ret = subprocess.call(
|
|
81
|
+
ret = subprocess.call(ssh_cmd)
|
|
69
82
|
except KeyboardInterrupt:
|
|
70
83
|
ret = 130
|
|
71
84
|
ctx.exit(ret if ret is not None else 0)
|
|
@@ -1274,8 +1274,18 @@ def reset(ctx, box, halt, force_reconnect):
|
|
|
1274
1274
|
help='Halt the device during memory read (keeps debugger connected).')
|
|
1275
1275
|
@click.option('--no-halt', 'no_halt', is_flag=True, default=False,
|
|
1276
1276
|
help='Leave the target running; overrides auto-halt for DA1469 QSPI XIP.')
|
|
1277
|
-
|
|
1278
|
-
|
|
1277
|
+
@click.option('--no-reset', 'no_reset', is_flag=True, default=False,
|
|
1278
|
+
help='DA1469x only: skip the reset+halt the box performs before the read. '
|
|
1279
|
+
'A running DA1469x has SWD disabled, so without reset the read fails; '
|
|
1280
|
+
'use this only on a blank/awake part to avoid rebooting it.')
|
|
1281
|
+
def memrd(ctx, start_addr, length, box, json_output, halt, no_halt, no_reset):
|
|
1282
|
+
"""Read memory from target.
|
|
1283
|
+
|
|
1284
|
+
DA1469x QSPI XIP auto-halts unless --no-halt. For any DA1469x device the box
|
|
1285
|
+
also resets+halts the target before reading (real firmware disables SWD and
|
|
1286
|
+
deep-sleeps, so a plain read fails) — this REBOOTS the DUT. Pass --no-reset
|
|
1287
|
+
to skip that step (only safe on a blank/awake part).
|
|
1288
|
+
"""
|
|
1279
1289
|
|
|
1280
1290
|
target_box = box
|
|
1281
1291
|
|
|
@@ -1361,7 +1371,7 @@ def memrd(ctx, start_addr, length, box, json_output, halt, no_halt):
|
|
|
1361
1371
|
ctx.exit(0)
|
|
1362
1372
|
|
|
1363
1373
|
try:
|
|
1364
|
-
memory_data = client.read_memory(debug_net, start_addr, length)
|
|
1374
|
+
memory_data = client.read_memory(debug_net, start_addr, length, no_reset=no_reset)
|
|
1365
1375
|
except Exception as e:
|
|
1366
1376
|
error_msg = str(e)
|
|
1367
1377
|
if "400" in error_msg or "No debugger connection" in error_msg:
|
|
@@ -196,13 +196,18 @@ class DebugServiceClient:
|
|
|
196
196
|
return response.json()
|
|
197
197
|
|
|
198
198
|
def read_memory(self, net: Dict[str, Any], start_addr: int,
|
|
199
|
-
length: int = 256) -> bytes:
|
|
200
|
-
"""Read memory from target.
|
|
199
|
+
length: int = 256, no_reset: bool = False) -> bytes:
|
|
200
|
+
"""Read memory from target.
|
|
201
|
+
|
|
202
|
+
no_reset: DA1469x only — skip the box-side reset+halt before the read.
|
|
203
|
+
"""
|
|
201
204
|
data = {
|
|
202
205
|
'net': net,
|
|
203
206
|
'start_addr': start_addr,
|
|
204
207
|
'length': length,
|
|
205
208
|
}
|
|
209
|
+
if no_reset:
|
|
210
|
+
data['no_reset'] = True
|
|
206
211
|
|
|
207
212
|
response = self.session.post(
|
|
208
213
|
f'{self.base_url}/debug/memrd',
|
|
@@ -16,6 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
import click
|
|
17
17
|
|
|
18
18
|
from ...core.group_usage import LagerGroup
|
|
19
|
+
from ...core.param_types import EnvVarType
|
|
19
20
|
from ...sort_utils import natural_sort_key
|
|
20
21
|
from ...config import (
|
|
21
22
|
read_lager_json,
|
|
@@ -25,6 +26,8 @@ from ...config import (
|
|
|
25
26
|
make_config_path,
|
|
26
27
|
LAGER_CONFIG_FILE_NAME,
|
|
27
28
|
get_global_config_file_path,
|
|
29
|
+
devenv_config_list,
|
|
30
|
+
expand_devenv_path,
|
|
28
31
|
)
|
|
29
32
|
|
|
30
33
|
|
|
@@ -69,6 +72,50 @@ def _validate_docker_image_name(image):
|
|
|
69
72
|
return True
|
|
70
73
|
|
|
71
74
|
|
|
75
|
+
def _print_terminal_info(config_path, args):
|
|
76
|
+
"""Print the resolved docker command and a readable breakdown of it."""
|
|
77
|
+
def collect(*flags):
|
|
78
|
+
out = []
|
|
79
|
+
for i, tok in enumerate(args[:-1]):
|
|
80
|
+
if tok in flags:
|
|
81
|
+
out.append(args[i + 1])
|
|
82
|
+
return out
|
|
83
|
+
|
|
84
|
+
click.secho('Resolved docker command:', fg='green')
|
|
85
|
+
click.echo(f' {" ".join(args)}')
|
|
86
|
+
click.echo()
|
|
87
|
+
click.echo(f'Config: {config_path}')
|
|
88
|
+
click.echo(f'Image: {args[-1] if args else ""}')
|
|
89
|
+
|
|
90
|
+
mounts = collect('-v')
|
|
91
|
+
if mounts:
|
|
92
|
+
click.echo('Mounts:')
|
|
93
|
+
for item in mounts:
|
|
94
|
+
click.echo(f' {item}')
|
|
95
|
+
|
|
96
|
+
envs = collect('-e', '--env') + [t.split('=', 1)[1] for t in args if t.startswith('--env=')]
|
|
97
|
+
if envs:
|
|
98
|
+
click.echo('Environment:')
|
|
99
|
+
for item in envs:
|
|
100
|
+
click.echo(f' {item}')
|
|
101
|
+
|
|
102
|
+
ports = collect('-p')
|
|
103
|
+
if ports:
|
|
104
|
+
click.echo('Ports:')
|
|
105
|
+
for item in ports:
|
|
106
|
+
click.echo(f' {item}')
|
|
107
|
+
|
|
108
|
+
networks = [t.split('=', 1)[1] for t in args if t.startswith('--network=')]
|
|
109
|
+
if networks:
|
|
110
|
+
click.echo(f'Network: {networks[0]}')
|
|
111
|
+
platforms = collect('--platform')
|
|
112
|
+
if platforms:
|
|
113
|
+
click.echo(f'Platform: {platforms[0]}')
|
|
114
|
+
|
|
115
|
+
click.echo()
|
|
116
|
+
click.secho('(--info: nothing was launched)', fg='yellow')
|
|
117
|
+
|
|
118
|
+
|
|
72
119
|
@devenv.command()
|
|
73
120
|
@click.pass_context
|
|
74
121
|
@click.option('--image', prompt='Docker image', default='lagerdata/devenv-cortexm', show_default=True, help='Docker image name')
|
|
@@ -143,7 +190,11 @@ def create(ctx, image, mount_dir, shell):
|
|
|
143
190
|
@click.option('--platform', help='Platform', required=False)
|
|
144
191
|
@click.option('--attach', '-a', 'attach_container', help='Attach to a running container by name', required=False, default=None)
|
|
145
192
|
@click.option('--shell', '-s', help='Shell to use when attaching (default: config shell or /bin/bash)', required=False, default=None)
|
|
146
|
-
|
|
193
|
+
@click.option('--volume', '-v', 'volumes', help='Bind-mount a host path into the container (HOST:CONTAINER[:ro]). Repeatable.', required=False, multiple=True)
|
|
194
|
+
@click.option('--env', '-e', help='Set an environment variable in the container (FOO=BAR). Repeatable.', required=False, multiple=True, type=EnvVarType())
|
|
195
|
+
@click.option('--passenv', help='Pass an environment variable through from the current environment. Repeatable.', required=False, multiple=True)
|
|
196
|
+
@click.option('--info', is_flag=True, default=False, help='Show the resolved docker command and config, then exit without launching.')
|
|
197
|
+
def terminal(ctx, mount, user, group, name, detach, port, entrypoint, network, platform, attach_container, shell, volumes, env, passenv, info):
|
|
147
198
|
"""
|
|
148
199
|
Start interactive terminal
|
|
149
200
|
"""
|
|
@@ -188,25 +239,27 @@ def terminal(ctx, mount, user, group, name, detach, port, entrypoint, network, p
|
|
|
188
239
|
|
|
189
240
|
repo_root_relative_path = devenv_config.get('repo_root_relative_path')
|
|
190
241
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
hostname = None
|
|
200
|
-
if 'hostname' in devenv_config:
|
|
201
|
-
hostname = devenv_config['hostname']
|
|
242
|
+
# Precedence: CLI flag wins, else fall back to the .lager value.
|
|
243
|
+
if user is None:
|
|
244
|
+
user = devenv_config.get('user')
|
|
245
|
+
if group is None:
|
|
246
|
+
group = devenv_config.get('group')
|
|
247
|
+
macaddr = devenv_config.get('macaddr')
|
|
248
|
+
hostname = devenv_config.get('hostname')
|
|
202
249
|
|
|
250
|
+
entrypoint = entrypoint or devenv_config.get('entrypoint')
|
|
203
251
|
if entrypoint:
|
|
204
252
|
args.extend(['--entrypoint', entrypoint])
|
|
205
253
|
|
|
254
|
+
# docker run has no `--group` flag; user and group are combined into `--user user:group`
|
|
255
|
+
# (matching `lager exec`). A group with no user yields `--user :group`.
|
|
256
|
+
user_group = ''
|
|
206
257
|
if user:
|
|
207
|
-
|
|
258
|
+
user_group += user
|
|
208
259
|
if group:
|
|
209
|
-
|
|
260
|
+
user_group += f':{group}'
|
|
261
|
+
if user_group:
|
|
262
|
+
args.extend(['--user', user_group])
|
|
210
263
|
if macaddr:
|
|
211
264
|
args.extend(['--mac-address', macaddr])
|
|
212
265
|
if hostname:
|
|
@@ -229,11 +282,16 @@ def terminal(ctx, mount, user, group, name, detach, port, entrypoint, network, p
|
|
|
229
282
|
if name:
|
|
230
283
|
args.extend(['--name', name])
|
|
231
284
|
|
|
285
|
+
# network / platform: CLI flag wins, else fall back to the .lager value.
|
|
286
|
+
network = network or devenv_config.get('network')
|
|
232
287
|
if network:
|
|
233
288
|
args.append(f'--network={network}')
|
|
234
289
|
|
|
235
|
-
|
|
290
|
+
# ports: config-defined first, then any -p passed on the command line.
|
|
291
|
+
all_ports = [*devenv_config_list(devenv_config.get('ports')), *port]
|
|
292
|
+
args.extend(itertools.chain(*zip(itertools.repeat('-p'), all_ports)))
|
|
236
293
|
|
|
294
|
+
platform = platform or devenv_config.get('platform')
|
|
237
295
|
if platform:
|
|
238
296
|
args.extend(['--platform', platform])
|
|
239
297
|
|
|
@@ -250,10 +308,27 @@ def terminal(ctx, mount, user, group, name, detach, port, entrypoint, network, p
|
|
|
250
308
|
f'{global_config_path}:/lager/{LAGER_CONFIG_FILE_NAME}'
|
|
251
309
|
])
|
|
252
310
|
|
|
311
|
+
# Custom bind mounts: config-defined first, then any passed on the command line.
|
|
312
|
+
# Specs may use ~, environment variables, and ${PROJECT_ROOT} for portability.
|
|
313
|
+
project_root = os.path.dirname(path)
|
|
314
|
+
for vol in (*devenv_config_list(devenv_config.get('volumes')), *volumes):
|
|
315
|
+
args.extend(['-v', expand_devenv_path(vol, project_root)])
|
|
316
|
+
|
|
317
|
+
# Environment variables: config-defined, then --env, then --passenv passthrough.
|
|
318
|
+
for var in (*devenv_config_list(devenv_config.get('environment')), *env):
|
|
319
|
+
args.extend(['--env', var])
|
|
320
|
+
for var_name in passenv:
|
|
321
|
+
if var_name in os.environ:
|
|
322
|
+
args.extend(['--env', f'{var_name}={os.environ[var_name]}'])
|
|
323
|
+
|
|
253
324
|
args.extend(['-w', working_dir])
|
|
254
325
|
|
|
255
326
|
args.append(image)
|
|
256
327
|
|
|
328
|
+
if info:
|
|
329
|
+
_print_terminal_info(path, args)
|
|
330
|
+
ctx.exit(0)
|
|
331
|
+
|
|
257
332
|
try:
|
|
258
333
|
proc = subprocess.run(args, check=False)
|
|
259
334
|
except FileNotFoundError:
|
|
@@ -381,3 +456,267 @@ def commands():
|
|
|
381
456
|
for name, command in sorted(cmds.items(), key=lambda x: natural_sort_key(x[0])):
|
|
382
457
|
click.secho(name, fg='green', nl=False)
|
|
383
458
|
click.echo(f': {command}')
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _load_devenv_section():
|
|
462
|
+
"""Return (path, data, section) for the project DEVENV config, or exit."""
|
|
463
|
+
path, data = get_devenv_json()
|
|
464
|
+
if 'DEVENV' not in data:
|
|
465
|
+
click.secho(f'No devenv configuration found in {path}', fg='red', err=True)
|
|
466
|
+
click.echo('Please run `lager devenv create` first to set up your development environment.', err=True)
|
|
467
|
+
click.get_current_context().exit(1)
|
|
468
|
+
return path, data, data['DEVENV']
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _save_devenv(path, data):
|
|
472
|
+
"""Write devenv config back to disk with consistent error handling."""
|
|
473
|
+
try:
|
|
474
|
+
write_lager_json(data, path)
|
|
475
|
+
except PermissionError:
|
|
476
|
+
click.secho(f'Error: Permission denied writing to {path}', fg='red', err=True)
|
|
477
|
+
click.echo('Make sure the file is writable.', err=True)
|
|
478
|
+
click.get_current_context().exit(1)
|
|
479
|
+
except Exception as e:
|
|
480
|
+
click.secho(f'Error writing to {path}: {e}', fg='red', err=True)
|
|
481
|
+
click.get_current_context().exit(1)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@devenv.group(name='mount')
|
|
485
|
+
def mount_group():
|
|
486
|
+
"""
|
|
487
|
+
Manage persistent bind-mounts / volumes for the devenv container
|
|
488
|
+
"""
|
|
489
|
+
pass
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@mount_group.command(name='add')
|
|
493
|
+
@click.argument('spec')
|
|
494
|
+
def mount_add(spec):
|
|
495
|
+
"""
|
|
496
|
+
Add a volume SPEC to the DEVENV `volumes` list.
|
|
497
|
+
|
|
498
|
+
SPEC is in Docker `-v` form: HOST:CONTAINER[:ro] for a host bind-mount,
|
|
499
|
+
or NAME:CONTAINER for a named volume.
|
|
500
|
+
"""
|
|
501
|
+
spec = spec.strip()
|
|
502
|
+
if ':' not in spec:
|
|
503
|
+
click.secho(
|
|
504
|
+
f'Error: invalid volume "{spec}". Expected HOST:CONTAINER[:ro] '
|
|
505
|
+
'(e.g. /host/path:/container/path or myvol:/data).',
|
|
506
|
+
fg='red', err=True)
|
|
507
|
+
click.get_current_context().exit(1)
|
|
508
|
+
|
|
509
|
+
path, data, section = _load_devenv_section()
|
|
510
|
+
volumes = devenv_config_list(section.get('volumes'))
|
|
511
|
+
if spec in volumes:
|
|
512
|
+
click.echo(f'Volume already configured: {spec}')
|
|
513
|
+
return
|
|
514
|
+
volumes.append(spec)
|
|
515
|
+
section['volumes'] = volumes
|
|
516
|
+
_save_devenv(path, data)
|
|
517
|
+
click.echo(f'Added volume: {spec}')
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
@mount_group.command(name='remove')
|
|
521
|
+
@click.argument('spec')
|
|
522
|
+
def mount_remove(spec):
|
|
523
|
+
"""
|
|
524
|
+
Remove a volume SPEC from the DEVENV `volumes` list.
|
|
525
|
+
"""
|
|
526
|
+
spec = spec.strip()
|
|
527
|
+
path, data, section = _load_devenv_section()
|
|
528
|
+
volumes = devenv_config_list(section.get('volumes'))
|
|
529
|
+
if spec not in volumes:
|
|
530
|
+
click.secho(f'Volume not found: {spec}', fg='red', err=True)
|
|
531
|
+
click.get_current_context().exit(1)
|
|
532
|
+
volumes = [v for v in volumes if v != spec]
|
|
533
|
+
if volumes:
|
|
534
|
+
section['volumes'] = volumes
|
|
535
|
+
else:
|
|
536
|
+
section.pop('volumes', None)
|
|
537
|
+
_save_devenv(path, data)
|
|
538
|
+
click.echo(f'Removed volume: {spec}')
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
@mount_group.command(name='list')
|
|
542
|
+
def mount_list():
|
|
543
|
+
"""
|
|
544
|
+
List configured volumes.
|
|
545
|
+
"""
|
|
546
|
+
_, _, section = _load_devenv_section()
|
|
547
|
+
volumes = devenv_config_list(section.get('volumes'))
|
|
548
|
+
if not volumes:
|
|
549
|
+
click.echo('No volumes configured')
|
|
550
|
+
return
|
|
551
|
+
for vol in volumes:
|
|
552
|
+
click.echo(vol)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@devenv.group(name='env')
|
|
556
|
+
def env_group():
|
|
557
|
+
"""
|
|
558
|
+
Manage persistent environment variables for the devenv container
|
|
559
|
+
"""
|
|
560
|
+
pass
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
@env_group.command(name='set')
|
|
564
|
+
@click.argument('assignment', type=EnvVarType())
|
|
565
|
+
def env_set(assignment):
|
|
566
|
+
"""
|
|
567
|
+
Set an environment variable ASSIGNMENT (FOO=BAR) in the DEVENV
|
|
568
|
+
`environment` list. Replaces any existing value for the same variable.
|
|
569
|
+
"""
|
|
570
|
+
name = assignment.split('=', 1)[0]
|
|
571
|
+
path, data, section = _load_devenv_section()
|
|
572
|
+
environment = devenv_config_list(section.get('environment'))
|
|
573
|
+
|
|
574
|
+
new_env = []
|
|
575
|
+
replaced = False
|
|
576
|
+
for entry in environment:
|
|
577
|
+
if entry.split('=', 1)[0] == name:
|
|
578
|
+
if not replaced:
|
|
579
|
+
new_env.append(assignment)
|
|
580
|
+
replaced = True
|
|
581
|
+
# drop any duplicate entries for the same variable
|
|
582
|
+
else:
|
|
583
|
+
new_env.append(entry)
|
|
584
|
+
if not replaced:
|
|
585
|
+
new_env.append(assignment)
|
|
586
|
+
|
|
587
|
+
section['environment'] = new_env
|
|
588
|
+
_save_devenv(path, data)
|
|
589
|
+
click.echo(f'Set environment variable: {assignment}')
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@env_group.command(name='unset')
|
|
593
|
+
@click.argument('name')
|
|
594
|
+
def env_unset(name):
|
|
595
|
+
"""
|
|
596
|
+
Remove environment variable NAME from the DEVENV `environment` list.
|
|
597
|
+
"""
|
|
598
|
+
path, data, section = _load_devenv_section()
|
|
599
|
+
environment = devenv_config_list(section.get('environment'))
|
|
600
|
+
remaining = [e for e in environment if e.split('=', 1)[0] != name]
|
|
601
|
+
if len(remaining) == len(environment):
|
|
602
|
+
click.secho(f'Environment variable not found: {name}', fg='red', err=True)
|
|
603
|
+
click.get_current_context().exit(1)
|
|
604
|
+
if remaining:
|
|
605
|
+
section['environment'] = remaining
|
|
606
|
+
else:
|
|
607
|
+
section.pop('environment', None)
|
|
608
|
+
_save_devenv(path, data)
|
|
609
|
+
click.echo(f'Unset environment variable: {name}')
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
@env_group.command(name='list')
|
|
613
|
+
def env_list():
|
|
614
|
+
"""
|
|
615
|
+
List configured environment variables.
|
|
616
|
+
"""
|
|
617
|
+
_, _, section = _load_devenv_section()
|
|
618
|
+
environment = devenv_config_list(section.get('environment'))
|
|
619
|
+
if not environment:
|
|
620
|
+
click.echo('No environment variables configured')
|
|
621
|
+
return
|
|
622
|
+
for entry in environment:
|
|
623
|
+
click.echo(entry)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
# Scalar DEVENV keys settable via `lager devenv set` (replace semantics).
|
|
627
|
+
SETTABLE_SCALAR_KEYS = (
|
|
628
|
+
'image', 'mount_dir', 'shell', 'user', 'group', 'entrypoint',
|
|
629
|
+
'hostname', 'macaddr', 'network', 'platform', 'repo_root_relative_path',
|
|
630
|
+
)
|
|
631
|
+
# List keys with no dedicated editor; `set` appends to these.
|
|
632
|
+
SETTABLE_APPEND_KEYS = ('ports',)
|
|
633
|
+
# Convenience singular aliases accepted on the command line.
|
|
634
|
+
DEVENV_KEY_ALIASES = {'port': 'ports'}
|
|
635
|
+
# List keys that have richer dedicated editors.
|
|
636
|
+
LIST_KEYS_WITH_EDITORS = {
|
|
637
|
+
'volumes': 'lager devenv mount',
|
|
638
|
+
'environment': 'lager devenv env',
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@devenv.command(name='set')
|
|
643
|
+
@click.argument('key')
|
|
644
|
+
@click.argument('value')
|
|
645
|
+
def set_(key, value):
|
|
646
|
+
"""
|
|
647
|
+
Set a DEVENV configuration KEY to VALUE.
|
|
648
|
+
|
|
649
|
+
Scalar keys (image, mount_dir, shell, user, group, hostname, macaddr,
|
|
650
|
+
network, platform, repo_root_relative_path) are replaced. The `ports`
|
|
651
|
+
list key is appended to. Use `lager devenv mount` / `lager devenv env`
|
|
652
|
+
for volumes / environment.
|
|
653
|
+
"""
|
|
654
|
+
ctx = click.get_current_context()
|
|
655
|
+
key = DEVENV_KEY_ALIASES.get(key, key)
|
|
656
|
+
if key in LIST_KEYS_WITH_EDITORS:
|
|
657
|
+
click.secho(
|
|
658
|
+
f'Error: edit `{key}` with `{LIST_KEYS_WITH_EDITORS[key]}`.',
|
|
659
|
+
fg='red', err=True)
|
|
660
|
+
ctx.exit(1)
|
|
661
|
+
if key not in SETTABLE_SCALAR_KEYS and key not in SETTABLE_APPEND_KEYS:
|
|
662
|
+
valid = ', '.join(SETTABLE_SCALAR_KEYS + SETTABLE_APPEND_KEYS)
|
|
663
|
+
click.secho(f'Error: unknown devenv key "{key}". Valid keys: {valid}.', fg='red', err=True)
|
|
664
|
+
ctx.exit(1)
|
|
665
|
+
if key == 'image' and not _validate_docker_image_name(value):
|
|
666
|
+
click.secho(f"Error: Invalid Docker image name: '{value}'", fg='red', err=True)
|
|
667
|
+
ctx.exit(1)
|
|
668
|
+
|
|
669
|
+
path, data, section = _load_devenv_section()
|
|
670
|
+
if key in SETTABLE_APPEND_KEYS:
|
|
671
|
+
values = devenv_config_list(section.get(key))
|
|
672
|
+
if value in values:
|
|
673
|
+
click.echo(f'{key} already contains: {value}')
|
|
674
|
+
return
|
|
675
|
+
values.append(value)
|
|
676
|
+
section[key] = values
|
|
677
|
+
_save_devenv(path, data)
|
|
678
|
+
click.echo(f'Added {key}: {value}')
|
|
679
|
+
else:
|
|
680
|
+
section[key] = value
|
|
681
|
+
_save_devenv(path, data)
|
|
682
|
+
click.echo(f'Set {key} = {value}')
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
@devenv.command(name='unset')
|
|
686
|
+
@click.argument('key')
|
|
687
|
+
def unset(key):
|
|
688
|
+
"""
|
|
689
|
+
Remove a DEVENV configuration KEY entirely (scalar or list).
|
|
690
|
+
"""
|
|
691
|
+
key = DEVENV_KEY_ALIASES.get(key, key)
|
|
692
|
+
path, data, section = _load_devenv_section()
|
|
693
|
+
if key not in section:
|
|
694
|
+
click.secho(f'devenv key not set: {key}', fg='red', err=True)
|
|
695
|
+
click.get_current_context().exit(1)
|
|
696
|
+
del section[key]
|
|
697
|
+
_save_devenv(path, data)
|
|
698
|
+
click.echo(f'Unset {key}')
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
@devenv.command(name='show')
|
|
702
|
+
def show():
|
|
703
|
+
"""
|
|
704
|
+
Print the resolved DEVENV configuration.
|
|
705
|
+
"""
|
|
706
|
+
path, _, section = _load_devenv_section()
|
|
707
|
+
scalars = {k: v for k, v in section.items()
|
|
708
|
+
if not k.startswith('cmd.') and not isinstance(v, (list, tuple))}
|
|
709
|
+
lists = {k: v for k, v in section.items() if isinstance(v, (list, tuple))}
|
|
710
|
+
cmds = {k.split('.', 1)[1]: v for k, v in section.items() if k.startswith('cmd.')}
|
|
711
|
+
|
|
712
|
+
click.secho(f'DEVENV ({path})', fg='green')
|
|
713
|
+
for key in sorted(scalars):
|
|
714
|
+
click.echo(f' {key}: {scalars[key]}')
|
|
715
|
+
for key in sorted(lists):
|
|
716
|
+
click.echo(f' {key}:')
|
|
717
|
+
for item in devenv_config_list(lists[key]):
|
|
718
|
+
click.echo(f' - {item}')
|
|
719
|
+
if cmds:
|
|
720
|
+
click.echo(' commands:')
|
|
721
|
+
for name in sorted(cmds, key=natural_sort_key):
|
|
722
|
+
click.echo(f' {name}: {cmds[name]}')
|
|
@@ -15,11 +15,11 @@ import subprocess
|
|
|
15
15
|
import platform
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
import click
|
|
18
|
-
from ...config import get_devenv_json, write_lager_json, LAGER_CONFIG_FILE_NAME, get_global_config_file_path
|
|
18
|
+
from ...config import get_devenv_json, write_lager_json, LAGER_CONFIG_FILE_NAME, get_global_config_file_path, devenv_config_list, expand_devenv_path
|
|
19
19
|
from ...core.param_types import EnvVarType
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def _run_command_local(section, path, cmd_to_run, mount, extra_args, debug, interactive, tty, user, group, env, passenv):
|
|
22
|
+
def _run_command_local(section, path, cmd_to_run, mount, extra_args, debug, interactive, tty, user, group, env, passenv, volumes=()):
|
|
23
23
|
"""
|
|
24
24
|
Run a command locally in a Docker container
|
|
25
25
|
"""
|
|
@@ -94,8 +94,24 @@ def _run_command_local(section, path, cmd_to_run, mount, extra_args, debug, inte
|
|
|
94
94
|
if hostname:
|
|
95
95
|
base_command.append(f'--hostname={hostname}')
|
|
96
96
|
|
|
97
|
-
#
|
|
98
|
-
|
|
97
|
+
# Network / platform / ports from config (no CLI flags on exec for these).
|
|
98
|
+
network = section.get('network')
|
|
99
|
+
if network:
|
|
100
|
+
base_command.append(f'--network={network}')
|
|
101
|
+
for port in devenv_config_list(section.get('ports')):
|
|
102
|
+
base_command.extend(['-p', port])
|
|
103
|
+
platform = section.get('platform')
|
|
104
|
+
if platform:
|
|
105
|
+
base_command.extend(['--platform', platform])
|
|
106
|
+
|
|
107
|
+
# Custom bind mounts: config-defined first, then any passed on the command line.
|
|
108
|
+
# Specs may use ~, environment variables, and ${PROJECT_ROOT} for portability.
|
|
109
|
+
project_root = os.path.dirname(path)
|
|
110
|
+
for vol in (*devenv_config_list(section.get('volumes')), *volumes):
|
|
111
|
+
base_command.extend(['-v', expand_devenv_path(vol, project_root)])
|
|
112
|
+
|
|
113
|
+
# Add custom environment variables: config-defined first, then --env.
|
|
114
|
+
for env_var in (*devenv_config_list(section.get('environment')), *env):
|
|
99
115
|
base_command.append(f'--env={env_var}')
|
|
100
116
|
|
|
101
117
|
# Pass through environment variables
|
|
@@ -149,12 +165,13 @@ def _run_command_local(section, path, cmd_to_run, mount, extra_args, debug, inte
|
|
|
149
165
|
'--passenv',
|
|
150
166
|
multiple=True, help='Environment variable to inherit from current environment')
|
|
151
167
|
@click.option('--mount', '-m', help='Name of volume to mount', required=False)
|
|
168
|
+
@click.option('--volume', 'volumes', help='Bind-mount a host path into the container (HOST:CONTAINER[:ro]). Repeatable.', required=False, multiple=True)
|
|
152
169
|
@click.option('--interactive/--no-interactive', '-i', is_flag=True, help='Keep STDIN open even if not attached', default=True, show_default=True)
|
|
153
170
|
@click.option('--tty/--no-tty', '-t', is_flag=True, help='Allocate a pseudo-TTY', default=True, show_default=True)
|
|
154
171
|
@click.option('--user', '-u', help='User to run as in container', default=None)
|
|
155
172
|
@click.option('--group', '-g', help='Group to run as in container', default=None)
|
|
156
173
|
@click.option('--verbose', '-v', is_flag=True, help='Show verbose output including the full docker command')
|
|
157
|
-
def exec_(ctx, cmd_name, extra_args, command, save_as, warn, env, passenv, mount, interactive, tty, user, group, verbose):
|
|
174
|
+
def exec_(ctx, cmd_name, extra_args, command, save_as, warn, env, passenv, mount, volumes, interactive, tty, user, group, verbose):
|
|
158
175
|
"""
|
|
159
176
|
Execute COMMAND in a docker container locally. COMMAND is a named command which was previously saved using `--save-as`.
|
|
160
177
|
If COMMAND is not provided, execute the command specified by --command. If --save-as is also provided,
|
|
@@ -165,19 +182,6 @@ def exec_(ctx, cmd_name, extra_args, command, save_as, warn, env, passenv, mount
|
|
|
165
182
|
click.echo(exec_.get_help(ctx))
|
|
166
183
|
ctx.exit(0)
|
|
167
184
|
|
|
168
|
-
# Default user and group to current user if not specified
|
|
169
|
-
if user is None:
|
|
170
|
-
try:
|
|
171
|
-
user = str(os.getuid())
|
|
172
|
-
except AttributeError:
|
|
173
|
-
pass
|
|
174
|
-
|
|
175
|
-
if group is None:
|
|
176
|
-
try:
|
|
177
|
-
group = str(os.getgid())
|
|
178
|
-
except AttributeError:
|
|
179
|
-
pass
|
|
180
|
-
|
|
181
185
|
# Get DEVENV configuration
|
|
182
186
|
path, data = get_devenv_json()
|
|
183
187
|
|
|
@@ -188,11 +192,21 @@ def exec_(ctx, cmd_name, extra_args, command, save_as, warn, env, passenv, mount
|
|
|
188
192
|
|
|
189
193
|
section = data['DEVENV']
|
|
190
194
|
|
|
191
|
-
#
|
|
192
|
-
if
|
|
193
|
-
user = section
|
|
194
|
-
if
|
|
195
|
-
group = section
|
|
195
|
+
# Precedence: CLI flag wins, then .lager config, then the current uid/gid.
|
|
196
|
+
if user is None:
|
|
197
|
+
user = section.get('user')
|
|
198
|
+
if group is None:
|
|
199
|
+
group = section.get('group')
|
|
200
|
+
if user is None:
|
|
201
|
+
try:
|
|
202
|
+
user = str(os.getuid())
|
|
203
|
+
except AttributeError:
|
|
204
|
+
pass
|
|
205
|
+
if group is None:
|
|
206
|
+
try:
|
|
207
|
+
group = str(os.getgid())
|
|
208
|
+
except AttributeError:
|
|
209
|
+
pass
|
|
196
210
|
|
|
197
211
|
# Check for both command name and command string
|
|
198
212
|
if cmd_name and command:
|
|
@@ -237,6 +251,6 @@ def exec_(ctx, cmd_name, extra_args, command, save_as, warn, env, passenv, mount
|
|
|
237
251
|
# Run the command locally in Docker
|
|
238
252
|
returncode = _run_command_local(
|
|
239
253
|
section, path, cmd_to_run, mount, extra_args,
|
|
240
|
-
ctx.obj.debug or verbose, interactive, tty, user, group, env, passenv
|
|
254
|
+
ctx.obj.debug or verbose, interactive, tty, user, group, env, passenv, volumes
|
|
241
255
|
)
|
|
242
256
|
ctx.exit(returncode)
|