Skip to content

Commit

Permalink
Helper for running bundle remotely, without touchdown
Browse files Browse the repository at this point in the history
  • Loading branch information
Jc2k committed Jul 21, 2021
1 parent c751ee2 commit 99b73c5
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 2 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,39 @@ Should you use fuselage? Probably not. But if you are wondering why:
* It's **simple**. It provides the absolute minimum, and tries to get out the way for the stuff where it doesn't need to have an opinion. Bring your own template engine, or don't use one at all. Bring your own control plane. Run it from a deamonset, run it via fabric or even just use scp and run it by hand.


## Using with paramiko

```python
import paramiko

from fuselage.bundle import ResourceBundle
from fuselage.resources import *
from fuselage.ssh import execute_via_ssh


bundle = ResourceBundle()

bundle.add(File(
name="/tmp/hello.txt",
contents="A test file!!",
))

transport = paramiko.Transport(("localhost", 22))
transport.connect(
username="john",
password="my super sekrit password",
)

# Compile the bundle, scp it to target server, execute it via sudo
execute_via_ssh(
transport,
bundle,
"root",
sudo_password="my super sekrit password"
)
```


## Using with fabric

You will need to install fabric explicitly. Fuselage does not depend on fabric.
Expand Down
2 changes: 1 addition & 1 deletion fuselage/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def close(self):
self.zipfile.close()


def build(bundle: ResourceBundle, name: str="payload.pex") -> io.BytesIO:
def build(bundle: ResourceBundle, name: str = "payload.pex") -> io.BytesIO:
buffer = io.BytesIO()
buffer.name = name

Expand Down
97 changes: 97 additions & 0 deletions fuselage/ssh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import os
import time
from typing import Optional

import paramiko

from .builder import build
from .bundle import ResourceBundle


def iter_chunks(channel: paramiko.Channel):
while not channel.exit_status_ready():
while channel.recv_ready():
yield channel.recv(1024)
time.sleep(0.1)

while channel.recv_ready():
yield channel.recv(1024)


def iter_lines(channel: paramiko.Channel):
buffer = bytearray()
for chunk in iter_chunks(channel):
buffer.extend(chunk)

while b"\n" in buffer:
line, buffer = buffer.split(b"\n", 1)
yield line.decode("utf-8")


def execute_via_ssh(
transport: paramiko.Transport,
bundle: ResourceBundle,
dry_run: bool = False,
username: Optional[str] = "root",
sudo_password: Optional[str] = None,
):
"""
transport = paramiko.Transport(("localhost", 22))
transport.connect(
username="ubuntu",
password="password55",
)
execute(transport, bundle, "root", "mysudopassword")
"""
payload = build(bundle)

sftp = transport.open_sftp_client()
sftp.chdir(".")

path = os.path.join(sftp.getcwd(), ".payload.pex")

command_parts = [path, "--resume"]

if dry_run:
command_parts.append("--simulate")

command = " ".join(command_parts)

channel = transport.open_session()
channel.get_pty()
channel.set_combine_stderr(1)

try:
sftp.putfo(payload, path)
sftp.chmod(path, 0o755)

try:
if username and username != transport.get_username():
command = f"sudo -u {username} {command}"
channel.exec_command(command)

if sudo_password:
first_line = channel.recv(1024)

if b"[sudo]" not in first_line and not first_line.startswith(
b"Password:"
):
raise RuntimeError(f"Expected sudo, got {first_line!r}")

channel.sendall((sudo_password + "\n").encode("utf-8"))
else:
channel.exec_command(path)

channel.shutdown_write()

try:
for line in iter_lines(channel):
print(line)

return channel.recv_exit_status()
finally:
channel.close()
finally:
sftp.remove(path)
finally:
sftp.close()
5 changes: 4 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 99b73c5

Please sign in to comment.