-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from LaiderLai/ssh
Support an IoT deployment method with a USB stick that flashed with an EFI based ISO
- Loading branch information
Showing
10 changed files
with
298 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ dependencies = [ | |
"schedule", | ||
"wrapt_timeout_decorator", | ||
"whl", | ||
"paramiko", | ||
] | ||
|
||
dynamic = ["version"] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
"""This module provides auto-ISO deployment checking""" | ||
|
||
import os | ||
import subprocess | ||
import time | ||
from sanity.agent.err import FAILED, SUCCESS | ||
|
||
|
||
class DeploymentISO: | ||
"""checking auto-deployment ISO status""" | ||
|
||
instlog = None | ||
timeout = None | ||
|
||
def __init__(self, instlog="installer-logs.tar.xz", timeout=1800): | ||
self.instlog = instlog | ||
self.timeout = timeout | ||
|
||
def infile(self, string, filename): | ||
"""check if the string in the file or not""" | ||
with open(filename, encoding="utf-8") as file: | ||
for line in file: | ||
if string in line: | ||
return True | ||
return False | ||
|
||
def chklog(self, logdir): | ||
"""check log content""" | ||
crashdir = f"{logdir}/var/crash" | ||
if os.path.exists(crashdir) and os.listdir(crashdir): | ||
return { | ||
"code": FAILED, | ||
"mesg": f"Please check the crash log under {crashdir}", | ||
} | ||
|
||
curtinlog = f"{logdir}/var/log/installer/curtin-install.log" | ||
if not self.infile("SUCCESS: curtin command extract", curtinlog): | ||
return { | ||
"code": FAILED, | ||
"mesg": f"Please check the {curtinlog}" | ||
f"to know why installer is NOT finished with dd", | ||
} | ||
print( | ||
"Found the installation process is finished" | ||
" correctly from the log" | ||
) | ||
return {"code": SUCCESS} | ||
|
||
def result(self, con): | ||
"""identify auto-installer ISO deployment result via connection""" | ||
# Checking installation status by connection | ||
con.connection() | ||
|
||
homedir = os.path.expanduser("~") | ||
savedlog = f"{homedir}/{self.instlog}" | ||
|
||
# Check the installation completed log is exist | ||
waiting = time.time() + self.timeout | ||
while True: | ||
try: | ||
con.download(f"/{self.instlog}", savedlog) | ||
except FileNotFoundError as e: | ||
print(e) | ||
if time.time() > waiting: | ||
raise TimeoutError( | ||
"TimeoutError: the installation is not complete" | ||
f"because there is no /{self.instlog}" | ||
) from e | ||
time.sleep(5) | ||
continue | ||
break | ||
con.close() | ||
print(f"The {savedlog} is saved") | ||
|
||
# Extract and check the log | ||
extractdir = f"{homedir}/a-s-tmp" | ||
subprocess.run(["mkdir", "-p", extractdir], check=False) | ||
subprocess.run( | ||
["tar", "-Jxf", savedlog, "-C", extractdir], | ||
check=False, | ||
) | ||
|
||
return self.chklog(extractdir) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
"""This module provides SSH relvent methods""" | ||
|
||
import logging | ||
import time | ||
from dataclasses import dataclass | ||
import paramiko | ||
|
||
|
||
@dataclass | ||
class SSHInfo: | ||
"""the ssh connection information""" | ||
|
||
name: str | ||
port: int | ||
uname: str | ||
passwd: str | ||
timeout: int | ||
|
||
|
||
class SSHConnection: | ||
"""handle send and receive through SSH to target""" | ||
|
||
info = None | ||
client = None | ||
sftp = None | ||
stdout = None | ||
stderr = None | ||
|
||
# pylint: disable=R0913,R0917 | ||
def __init__(self, name, port, uname, passwd, timeout=1800): | ||
self.info = SSHInfo(name, port, uname, passwd, timeout) | ||
|
||
def __del__(self): | ||
self.close() | ||
|
||
def getname(self): | ||
"""return SSH target name""" | ||
return self.info.name | ||
|
||
def isconnected(self): | ||
"""check if the connection is created""" | ||
return self.client is not None and self.sftp is not None | ||
|
||
def connection(self): | ||
"""initial the connection""" | ||
if self.client: | ||
self.close() | ||
|
||
self.client = paramiko.SSHClient() | ||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | ||
|
||
waiting = time.time() + self.info.timeout | ||
while True: | ||
try: | ||
self.client.connect( | ||
self.info.name, | ||
self.info.port, | ||
self.info.uname, | ||
self.info.passwd, | ||
) | ||
except ( | ||
paramiko.ssh_exception.NoValidConnectionsError, | ||
paramiko.ssh_exception.AuthenticationException, | ||
paramiko.ssh_exception.SSHException, | ||
) as e: | ||
print(e) | ||
if time.time() > waiting: | ||
raise TimeoutError( | ||
f"TimeoutError: the {self.info.name}:{self.info.port}" | ||
" connection is failed" | ||
) from e | ||
time.sleep(5) | ||
continue | ||
|
||
self.sftp = self.client.open_sftp() | ||
break | ||
|
||
def close(self): | ||
"""close connection""" | ||
if self.client: | ||
self.client.close() | ||
self.client = None | ||
self.sftp = None | ||
|
||
def write_con(self, cmd): | ||
"""send command and return result""" | ||
if self.client: | ||
sshcmd = f"echo {self.info.passwd} | sudo -S {cmd}" | ||
_stdin, _stdout, _stderr = self.client.exec_command(sshcmd) | ||
self.stdout = _stdout.read().decode("utf-8") | ||
self.stderr = _stderr.read().decode("utf-8") | ||
return self.read_con() | ||
|
||
def read_con(self): | ||
"""return the latest result from write_con()""" | ||
if self.stderr: | ||
logging.info(self.stderr) | ||
return self.stderr | ||
if self.stdout: | ||
logging.info(self.stdout) | ||
return self.stdout | ||
return None | ||
|
||
def download(self, remote, local): | ||
"""download from remote to local""" | ||
if self.sftp: | ||
self.sftp.get(remote, local) | ||
|
||
def upload(self, local, remote): | ||
"""upload from local to remote""" | ||
if self.sftp: | ||
self.sftp.put(local, remote) | ||
|
||
def log(self, name="ssh.log"): | ||
"""store result to a file""" | ||
root = logging.getLogger() | ||
if root.handlers: | ||
for handler in root.handlers: | ||
root.removeHandler(handler) | ||
|
||
logformat = "%(asctime)s: %(message)s" | ||
logging.basicConfig( | ||
level=logging.INFO, filename=name, filemode="a", format=logformat | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.