Skip to content

Commit

Permalink
add progress indicators for DNS checks
Browse files Browse the repository at this point in the history
  • Loading branch information
hpk42 committed Jul 8, 2024
1 parent 0653292 commit 39fe591
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 26 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test-and-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ jobs:

- run: cmdeploy init staging2.testrun.org

- run: cmdeploy run
- run: cmdeploy run --verbose

- name: set DNS entries
run: |
ssh -o StrictHostKeyChecking=accept-new -v [email protected] chown opendkim:opendkim -R /etc/dkimkeys
cmdeploy dns --zonefile staging-generated.zone
cmdeploy dns --zonefile staging-generated.zone --verbose
cat staging-generated.zone >> .github/workflows/staging.testrun.org-default.zone
cat .github/workflows/staging.testrun.org-default.zone
scp .github/workflows/staging.testrun.org-default.zone [email protected]:/etc/nsd/staging2.testrun.org.zone
Expand All @@ -88,5 +88,5 @@ jobs:
run: CHATMAIL_DOMAIN2=nine.testrun.org cmdeploy test --slow

- name: cmdeploy dns (try 3 times)
run: cmdeploy dns || cmdeploy dns || cmdeploy dns
run: cmdeploy dns -v || cmdeploy dns -v || cmdeploy dns -v

31 changes: 19 additions & 12 deletions cmdeploy/src/cmdeploy/cmdeploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ def run_cmd(args, out):
if retcode == 0:
out.green("Deploy completed, call `cmdeploy test` next.")
elif not remote_data["acme_account_url"]:
out.red("Deploy completed but needs rerun (letsencrypt not configured)")
out("Deploy completed but letsencrypt not configured, re-checking DNS")
return dns_cmd(args, out)
else:
out.red("Deploy failed")
return retcode
Expand All @@ -88,7 +89,7 @@ def dns_cmd(args, out):
def status_cmd(args, out):
"""Display status for online chatmail instance."""

sshexec = SSHExec(args.config.mail_domain, remote_funcs)
sshexec = args.get_sshexec()

out.green(f"chatmail domain: {args.config.mail_domain}")
if args.config.privacy_mail:
Expand Down Expand Up @@ -136,14 +137,6 @@ def test_cmd(args, out):


def fmt_cmd_options(parser):
parser.add_argument(
"--verbose",
"-v",
dest="verbose",
action="store_true",
help="provide information on invocations",
)

parser.add_argument(
"--check",
"-c",
Expand Down Expand Up @@ -173,7 +166,6 @@ def fmt_cmd(args, out):

out.check_call(" ".join(format_args), quiet=not args.verbose)
out.check_call(" ".join(check_args), quiet=not args.verbose)
return 0


def bench_cmd(args, out):
Expand Down Expand Up @@ -231,6 +223,14 @@ def add_config_option(parser):
type=Path,
help="path to the chatmail.ini file",
)
parser.add_argument(
"--verbose",
"-v",
dest="verbose",
action="store_true",
default=False,
help="provide verbose logging",
)


def add_subcommand(subparsers, func):
Expand Down Expand Up @@ -270,11 +270,18 @@ def get_parser():


def main(args=None):
"""Provide main entry point for 'xdcget' CLI invocation."""
"""Provide main entry point for 'cmdeploy' CLI invocation."""
parser = get_parser()
args = parser.parse_args(args=args)
if not hasattr(args, "func"):
return parser.parse_args(["-h"])

def get_sshexec(log=None):
print(f"[ssh] login to {args.config.mail_domain}")
return SSHExec(args.config.mail_domain, remote_funcs, log=log)

args.get_sshexec = get_sshexec

out = Out()
kwargs = {}
if args.func.__name__ not in ("init_cmd", "fmt_cmd"):
Expand Down
24 changes: 18 additions & 6 deletions cmdeploy/src/cmdeploy/dns.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import datetime
import importlib
import sys

from . import remote_funcs
from .sshexec import SSHExec


def show_dns(args, out) -> int:
"""Check existing DNS records, optionally write them to zone file
and return (exitcode, remote_data) tuple."""
print("Checking DNS entries ...")
template = importlib.resources.files(__package__).joinpath("chatmail.zone.f")
mail_domain = args.config.mail_domain

sshexec = SSHExec(mail_domain, remote_funcs)
if not args.verbose:

def log(data):
sys.stdout.write(".")
sys.stdout.flush()

else:
log = print

sshexec = args.get_sshexec(log=log)
print("Checking DNS entries ", end="\n" if args.verbose else "")

remote_data = sshexec(remote_funcs.perform_initial_checks, mail_domain=mail_domain)

Expand All @@ -27,12 +36,15 @@ def show_dns(args, out) -> int:
sts_id=datetime.datetime.now().strftime("%Y%m%d%H%M"),
chatmail_domain=args.config.mail_domain,
)

to_print = sshexec(remote_funcs.check_zonefile, zonefile=zonefile)
if not args.verbose:
print()

if getattr(args, "zonefile", None):
with open(args.zonefile, "w+") as zf:
zf.write(zonefile)
print(f"DNS records successfully written to: {args.zonefile}")

to_print = sshexec(remote_funcs.check_zonefile, zonefile=zonefile)
out.green(f"DNS records successfully written to: {args.zonefile}")

if to_print:
to_print.insert(
Expand Down
8 changes: 7 additions & 1 deletion cmdeploy/src/cmdeploy/remote_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@


def shell(command, fail_ok=False):
log(f"$ {command}")
try:
return check_output(command, shell=True).decode().rstrip()
except CalledProcessError:
Expand Down Expand Up @@ -97,6 +98,11 @@ def check_zonefile(zonefile):
# and setup a simple serialized function-execution loop

if __name__ == "__channelexec__":

def log(item):
channel.send(("log", item)) # noqa

while 1:
func_name, kwargs = channel.receive() # noqa
channel.send(globals()[func_name](**kwargs)) # noqa
res = globals()[func_name](**kwargs) # noqa
channel.send(("finish", res)) # noqa
14 changes: 10 additions & 4 deletions cmdeploy/src/cmdeploy/sshexec.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
class SSHExec:
RemoteError = execnet.RemoteError

def __init__(self, host, remote_funcs, python="python3", timeout=60):
target = host if "@" in host else f"root@{host}"
self.gateway = execnet.makegateway(f"ssh={target}//python={python}")
def __init__(self, host, remote_funcs, log=None, python="python3", timeout=60):
self.ssh_target = host if "@" in host else f"root@{host}"
self.gateway = execnet.makegateway(f"ssh={self.ssh_target}//python={python}")
self._remote_cmdloop_channel = self.gateway.remote_exec(remote_funcs)
self.log = log
self.timeout = timeout

def __call__(self, func, **kwargs):
self._remote_cmdloop_channel.send((func.__name__, kwargs))
return self._remote_cmdloop_channel.receive(timeout=self.timeout)
while 1:
code, data = self._remote_cmdloop_channel.receive(timeout=self.timeout)
if code == "log" and self.log:
self.log(data)
elif code == "finish":
return data

0 comments on commit 39fe591

Please sign in to comment.