/falk/manage.py

https://github.com/SFTtech/kevin · Python · 120 lines · 77 code · 31 blank · 12 comment · 9 complexity · 41f911be3dcbbae28746fb77b5e85031 MD5 · raw file

  1. #!/usr/bin/env python3
  2. """
  3. SSH client for a VM managed by falk.
  4. """
  5. import argparse
  6. import asyncio
  7. import logging
  8. from kevin.falk import FalkSSH, FalkSocket
  9. from kevin.process import SSHProcess
  10. from kevin.util import parse_connection_entry, log_setup
  11. async def spawn_shell(falk, vm_id, volatile, command):
  12. """
  13. Spawns an interactive shell with falk.
  14. """
  15. logging.debug("connecting to falk...")
  16. await falk.create()
  17. logging.debug("looking up machine '%s'...", vm_id)
  18. vm = await falk.create_vm(vm_id)
  19. if vm is None:
  20. raise Exception("vm '%s' was not found on falk '%s'. "
  21. "available:\n%s" % (
  22. vm_id, falk, await falk.get_vms()))
  23. manage = not volatile
  24. logging.debug("preparing and launching machine (manage=%s)..." % manage)
  25. await vm.prepare(manage=manage)
  26. await vm.launch()
  27. logging.debug("VM launched, waiting for ssh...")
  28. await vm.wait_for_ssh_port()
  29. if manage:
  30. logging.warning("please shut down the VM gracefully "
  31. "to avoid data loss (=> `sudo poweroff`)")
  32. # ssh into the machine, force tty allocation
  33. async with SSHProcess(command,
  34. vm.ssh_user, vm.ssh_host,
  35. vm.ssh_port, vm.ssh_known_host_key, pipes=False,
  36. options=["-t"]) as proc:
  37. ret = await proc.wait()
  38. # wait for the machine to exit gracefully
  39. wait_time = 30
  40. logging.warning(f"waiting {wait_time}s for machine to shut down")
  41. await vm.wait_for_shutdown(30)
  42. await vm.terminate()
  43. await vm.cleanup()
  44. return ret
  45. def main():
  46. """ Connect to a pty of some vm provided by falk """
  47. cmd = argparse.ArgumentParser()
  48. cmd.add_argument("--volatile", action="store_true",
  49. help="don't start the VM in management mode")
  50. cmd.add_argument("falk_id",
  51. help=("falk connection information: "
  52. "unix://socketpath, unix://user@socket "
  53. "or ssh://user@host:port"))
  54. cmd.add_argument("vm_id", help="machine identification")
  55. cmd.add_argument("command", nargs="*",
  56. help="command to execute. default: shell.")
  57. cmd.add_argument("-d", "--debug", action="store_true",
  58. help="enable asyncio debugging")
  59. cmd.add_argument("-v", "--verbose", action="count", default=0,
  60. help="increase program verbosity")
  61. cmd.add_argument("-q", "--quiet", action="count", default=0,
  62. help="decrease program verbosity")
  63. args = cmd.parse_args()
  64. # set up log level
  65. log_setup(args.verbose - args.quiet)
  66. loop = asyncio.get_event_loop()
  67. # enable asyncio debugging
  68. loop.set_debug(args.debug)
  69. user, connection, location, key = parse_connection_entry(
  70. "falk_id", args.falk_id, require_key=False)
  71. if connection == "ssh":
  72. host, port = location
  73. falk = FalkSSH("manage", host, port, user, key)
  74. elif connection == "unix":
  75. falk = FalkSocket("manage", location, user)
  76. else:
  77. raise Exception("unknown falk connection type: %s" % connection)
  78. ret = 1
  79. try:
  80. ret = loop.run_until_complete(
  81. spawn_shell(falk, args.vm_id, args.volatile, args.command))
  82. except KeyboardInterrupt:
  83. print("\nfalk.manage killed by keyboard interrupt\n")
  84. loop.stop()
  85. loop.run_forever()
  86. loop.close()
  87. exit(ret)
  88. if __name__ == '__main__':
  89. main()