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.
Files changed (202) hide show
  1. {lager_cli-0.32.2/lager_cli.egg-info → lager_cli-0.32.4}/PKG-INFO +1 -1
  2. {lager_cli-0.32.2 → lager_cli-0.32.4}/__init__.py +1 -1
  3. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/uninstall.py +8 -3
  4. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/update.py +206 -22
  5. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/setup_and_deploy_box.sh +19 -15
  6. {lager_cli-0.32.2 → lager_cli-0.32.4}/gateway_auth.py +60 -12
  7. {lager_cli-0.32.2 → lager_cli-0.32.4/lager_cli.egg-info}/PKG-INFO +1 -1
  8. {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/SOURCES.txt +1 -0
  9. lager_cli-0.32.4/tests/test_update_gate.py +164 -0
  10. {lager_cli-0.32.2 → lager_cli-0.32.4}/LICENSE +0 -0
  11. {lager_cli-0.32.2 → lager_cli-0.32.4}/MANIFEST.in +0 -0
  12. {lager_cli-0.32.2 → lager_cli-0.32.4}/README.md +0 -0
  13. {lager_cli-0.32.2 → lager_cli-0.32.4}/__main__.py +0 -0
  14. {lager_cli-0.32.2 → lager_cli-0.32.4}/address_utils.py +0 -0
  15. {lager_cli-0.32.2 → lager_cli-0.32.4}/battery/__init__.py +0 -0
  16. {lager_cli-0.32.2 → lager_cli-0.32.4}/battery/battery_tui.py +0 -0
  17. {lager_cli-0.32.2 → lager_cli-0.32.4}/battery/websocket_client.py +0 -0
  18. {lager_cli-0.32.2 → lager_cli-0.32.4}/box_storage.py +0 -0
  19. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/__init__.py +0 -0
  20. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/__init__.py +0 -0
  21. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_host_ops.py +0 -0
  22. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_mount_prep.py +0 -0
  23. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_pip_validation.py +0 -0
  24. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_shim_verbs.py +0 -0
  25. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/_ssh.py +0 -0
  26. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/box_group.py +0 -0
  27. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/boxes.py +0 -0
  28. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/config.py +0 -0
  29. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/diagnose.py +0 -0
  30. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/dut.py +0 -0
  31. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/hello.py +0 -0
  32. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/instruments.py +0 -0
  33. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/labjack_pins.py +0 -0
  34. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/lock.py +0 -0
  35. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/net_tui.py +0 -0
  36. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/nets.py +0 -0
  37. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/ssh.py +0 -0
  38. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/box/ssh_setup.py +0 -0
  39. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/__init__.py +0 -0
  40. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/ble.py +0 -0
  41. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/blufi.py +0 -0
  42. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/i2c.py +0 -0
  43. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/router.py +0 -0
  44. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/spi.py +0 -0
  45. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/uart.py +0 -0
  46. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/usb.py +0 -0
  47. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/websocket_client.py +0 -0
  48. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/communication/wifi.py +0 -0
  49. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/__init__.py +0 -0
  50. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/arm.py +0 -0
  51. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/__init__.py +0 -0
  52. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/commands.py +0 -0
  53. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/gdb.py +0 -0
  54. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/net_cache.py +0 -0
  55. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/service_client.py +0 -0
  56. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/service_helper.py +0 -0
  57. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/debug/tunnel.py +0 -0
  58. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/devenv.py +0 -0
  59. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/development/python.py +0 -0
  60. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/__init__.py +0 -0
  61. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/adc.py +0 -0
  62. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/dac.py +0 -0
  63. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/energy.py +0 -0
  64. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/gpi.py +0 -0
  65. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/gpo.py +0 -0
  66. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/logic.py +0 -0
  67. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/scope.py +0 -0
  68. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/thermocouple.py +0 -0
  69. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/measurement/watt.py +0 -0
  70. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/__init__.py +0 -0
  71. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/battery.py +0 -0
  72. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/battery_model_csv.py +0 -0
  73. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/eload.py +0 -0
  74. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/solar.py +0 -0
  75. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/power/supply.py +0 -0
  76. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/__init__.py +0 -0
  77. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/binaries.py +0 -0
  78. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/defaults.py +0 -0
  79. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/exec_.py +0 -0
  80. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/install.py +0 -0
  81. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/install_wheel.py +0 -0
  82. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/login.py +0 -0
  83. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/logs.py +0 -0
  84. {lager_cli-0.32.2 → lager_cli-0.32.4}/commands/utility/webcam.py +0 -0
  85. {lager_cli-0.32.2 → lager_cli-0.32.4}/config.py +0 -0
  86. {lager_cli-0.32.2 → lager_cli-0.32.4}/context/__init__.py +0 -0
  87. {lager_cli-0.32.2 → lager_cli-0.32.4}/context/ci_detection.py +0 -0
  88. {lager_cli-0.32.2 → lager_cli-0.32.4}/context/constants.py +0 -0
  89. {lager_cli-0.32.2 → lager_cli-0.32.4}/context/core.py +0 -0
  90. {lager_cli-0.32.2 → lager_cli-0.32.4}/context/error_handlers.py +0 -0
  91. {lager_cli-0.32.2 → lager_cli-0.32.4}/context/session.py +0 -0
  92. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/__init__.py +0 -0
  93. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/group_usage.py +0 -0
  94. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/matchers.py +0 -0
  95. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/net_group.py +0 -0
  96. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/net_helpers.py +0 -0
  97. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/net_storage.py +0 -0
  98. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/param_types.py +0 -0
  99. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/ssh_utils.py +0 -0
  100. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/utils.py +0 -0
  101. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/version_skew.py +0 -0
  102. {lager_cli-0.32.2 → lager_cli-0.32.4}/core/ws_diagnose.py +0 -0
  103. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/__init__.py +0 -0
  104. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/__init__.py +0 -0
  105. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/configure_docker_dns.py +0 -0
  106. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/configure_docker_dns.sh +0 -0
  107. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/convert_to_sparse_checkout.sh +0 -0
  108. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/scripts/setup_ssh_key.sh +0 -0
  109. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/security/__init__.py +0 -0
  110. {lager_cli-0.32.2 → lager_cli-0.32.4}/deployment/security/secure_box_firewall.sh +0 -0
  111. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/__init__.py +0 -0
  112. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/__init__.py +0 -0
  113. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/construct_utils.py +0 -0
  114. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/exceptions.py +0 -0
  115. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/py3compat.py +0 -0
  116. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/common/utils.py +0 -0
  117. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/__init__.py +0 -0
  118. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/adapters.py +0 -0
  119. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/core.py +0 -0
  120. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/debug.py +0 -0
  121. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/construct/macros.py +0 -0
  122. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/__init__.py +0 -0
  123. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/abbrevtable.py +0 -0
  124. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/aranges.py +0 -0
  125. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/callframe.py +0 -0
  126. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/compileunit.py +0 -0
  127. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/constants.py +0 -0
  128. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/descriptions.py +0 -0
  129. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/die.py +0 -0
  130. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/dwarf_expr.py +0 -0
  131. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/dwarfinfo.py +0 -0
  132. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/enums.py +0 -0
  133. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/lineprogram.py +0 -0
  134. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/locationlists.py +0 -0
  135. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/namelut.py +0 -0
  136. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/ranges.py +0 -0
  137. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/dwarf/structs.py +0 -0
  138. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/__init__.py +0 -0
  139. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/constants.py +0 -0
  140. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/decoder.py +0 -0
  141. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/ehabiinfo.py +0 -0
  142. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/ehabi/structs.py +0 -0
  143. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/__init__.py +0 -0
  144. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/constants.py +0 -0
  145. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/descriptions.py +0 -0
  146. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/dynamic.py +0 -0
  147. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/elffile.py +0 -0
  148. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/enums.py +0 -0
  149. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/gnuversions.py +0 -0
  150. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/hash.py +0 -0
  151. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/notes.py +0 -0
  152. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/relocation.py +0 -0
  153. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/sections.py +0 -0
  154. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/segments.py +0 -0
  155. {lager_cli-0.32.2 → lager_cli-0.32.4}/elftools/elf/structs.py +0 -0
  156. {lager_cli-0.32.2 → lager_cli-0.32.4}/errors.py +0 -0
  157. {lager_cli-0.32.2 → lager_cli-0.32.4}/exceptions.py +0 -0
  158. {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/__init__.py +0 -0
  159. {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/box_config.py +0 -0
  160. {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/measurement/__init__.py +0 -0
  161. {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/measurement/scope.py +0 -0
  162. {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/measurement/scope_stream.py +0 -0
  163. {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/power/__init__.py +0 -0
  164. {lager_cli-0.32.2 → lager_cli-0.32.4}/impl/power/enable_disable.py +0 -0
  165. {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/dependency_links.txt +0 -0
  166. {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/entry_points.txt +0 -0
  167. {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/requires.txt +0 -0
  168. {lager_cli-0.32.2 → lager_cli-0.32.4}/lager_cli.egg-info/top_level.txt +0 -0
  169. {lager_cli-0.32.2 → lager_cli-0.32.4}/main.py +0 -0
  170. {lager_cli-0.32.2 → lager_cli-0.32.4}/pyproject.toml +0 -0
  171. {lager_cli-0.32.2 → lager_cli-0.32.4}/safe_unpickle.py +0 -0
  172. {lager_cli-0.32.2 → lager_cli-0.32.4}/setup.cfg +0 -0
  173. {lager_cli-0.32.2 → lager_cli-0.32.4}/setup.py +0 -0
  174. {lager_cli-0.32.2 → lager_cli-0.32.4}/simple_hdlc.py +0 -0
  175. {lager_cli-0.32.2 → lager_cli-0.32.4}/sort_utils.py +0 -0
  176. {lager_cli-0.32.2 → lager_cli-0.32.4}/status.py +0 -0
  177. {lager_cli-0.32.2 → lager_cli-0.32.4}/supply/__init__.py +0 -0
  178. {lager_cli-0.32.2 → lager_cli-0.32.4}/supply/supply_tui.py +0 -0
  179. {lager_cli-0.32.2 → lager_cli-0.32.4}/supply/websocket_client.py +0 -0
  180. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/__init__.py +0 -0
  181. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/core/__init__.py +0 -0
  182. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/core/executor.py +0 -0
  183. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/main.py +0 -0
  184. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/__init__.py +0 -0
  185. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/completer.py +0 -0
  186. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/display.py +0 -0
  187. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/logo.py +0 -0
  188. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/repl.py +0 -0
  189. {lager_cli-0.32.2 → lager_cli-0.32.4}/terminal/ui/themes.py +0 -0
  190. {lager_cli-0.32.2 → lager_cli-0.32.4}/tests/test_box_lager_imports.py +0 -0
  191. {lager_cli-0.32.2 → lager_cli-0.32.4}/tests/test_box_storage.py +0 -0
  192. {lager_cli-0.32.2 → lager_cli-0.32.4}/tests/test_gateway_auth.py +0 -0
  193. {lager_cli-0.32.2 → lager_cli-0.32.4}/tests/test_io_imports.py +0 -0
  194. {lager_cli-0.32.2 → lager_cli-0.32.4}/update_check.py +0 -0
  195. {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC16.py +0 -0
  196. {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC16DNP.py +0 -0
  197. {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC16Kermit.py +0 -0
  198. {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC16SICK.py +0 -0
  199. {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRC32.py +0 -0
  200. {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/CRCCCITT.py +0 -0
  201. {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/PyCRC/__init__.py +0 -0
  202. {lager_cli-0.32.2 → lager_cli-0.32.4}/vendor/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: lager-cli
3
- Version: 0.32.2
3
+ Version: 0.32.4
4
4
  Summary: Lager CLI - Box and Docker connectivity
5
5
  Home-page: https://github.com/lagerdata/lager
6
6
  Author: Lager Data
@@ -7,4 +7,4 @@ Lager CLI
7
7
  A Command Line Interface for Lager Data
8
8
  """
9
9
 
10
- __version__ = '0.32.2'
10
+ __version__ = '0.32.4'
@@ -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 (ALL unused images on the box, not only lager's)")
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 image prune -af 2>/dev/null", "Removing Docker images", allow_fail=True)
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 both)
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
- bool(_check_new_hash) and bool(_check_stored_hash)
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 = force or commits_behind != 0 or commits_ahead != 0 or deps_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
- # `--force` deliberately skips this early-exit: a box whose previous update
1577
- # failed can read as "in sync" here (git fetched fine, hash matches) even
1578
- # though its container never came up, so the user needs a way to push the
1579
- # rebuild through regardless.
1580
- if (
1581
- git_sync_confirmed
1582
- and not needs_pull
1583
- and not needs_flatten
1584
- and not hash_mismatch
1585
- and not force
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 all existing containers..."
1343
- # Update restart policy first to prevent auto-restart, then force remove all containers
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
- # Disable auto-restart on all containers first
1346
- docker update --restart=no \$(docker ps -aq) 2>/dev/null || true
1347
- # Stop all running containers
1348
- docker stop \$(docker ps -q) 2>/dev/null || true
1349
- # Remove all containers (running and stopped)
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 "All containers cleaned up"
1357
+ print_success "Lager containers cleaned up"
1357
1358
 
1358
- print_info "Cleaning up Docker images and build cache to free disk space..."
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 unused Docker images...'
1362
- docker image prune -af 2>/dev/null || true
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 images and build cache cleaned up"
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
- try:
129
- resp = requests.post(
130
- f'{url}/api/auth/refresh',
131
- cookies=cookies,
132
- timeout=AUTH_SERVER_TIMEOUT,
133
- )
134
- if resp.status_code != 200:
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 (requests.RequestException, ValueError):
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
- if access_token and _token_expires_at(access_token) > time.time() + EXPIRY_MARGIN_SECONDS:
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 _refresh_access_token(url, entry)
203
+ return None
156
204
 
157
205
 
158
206
  def auth_headers_for_box(box_ip):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: lager-cli
3
- Version: 0.32.2
3
+ Version: 0.32.4
4
4
  Summary: Lager CLI - Box and Docker connectivity
5
5
  Home-page: https://github.com/lagerdata/lager
6
6
  Author: Lager Data
@@ -374,6 +374,7 @@ tests/test_box_lager_imports.py
374
374
  tests/test_box_storage.py
375
375
  tests/test_gateway_auth.py
376
376
  tests/test_io_imports.py
377
+ tests/test_update_gate.py
377
378
  vendor/__init__.py
378
379
  vendor/PyCRC/CRC16.py
379
380
  vendor/PyCRC/CRC16DNP.py