* feat: DraftAliasGenerator class Encapsulates logic from generate_draft_aliases.py * refactor: Avoid circular imports * feat: Add draft_aliases API endpoint * feat: Add @requires_api_token decorator Stolen from feat/rpc-api * feat: Add token auth to draft_aliases endpoint * feat: draft-aliases-from-json.py script Parses output from the draft_aliases API call * chore: Remove unused cruft * refactor: Avoid shadowing "draft" name * fix: Suppress empty lists from DraftAliasGenerator * refactor: Use a GET instead of POST * feat: GroupAliasGenerator class * feat: group aliases API view * fix: Handle domains array correctly * fix: Suppress empty group aliases * refactor: Generalize aliases-from-json.py script * refactor: Same output fmt for draft and group alias apis * feat: Sort addresses for stability * fix: Add "anything" virtual alias * test: Test requires_api_token decorator * feat: Harden is_valid_token against misconfig * test: Test is_valid_token * test: Test draft_aliases view * test: Test group_aliases view * test: Test DraftAliasGenerator * fix: ise group is type "ise" in test data * test: Fix logic in testManagementCommand The test was incorrect - and fails when fixed. :-( * test: Test GroupAliasGenerator Test currently fails * fix: Suppress empty -ads alias * test: Fix group acronym copy/paste error I *think* this must be what had been intended. The code does not look like it ever dealt with GroupHistory, so I'm pretty sure it wasn't meant to have the same acronym used by two different Groups at different times. * test: Check draft .notify alias generation * test: Cover get_draft_notify_emails()
100 lines
3.1 KiB
Python
100 lines
3.1 KiB
Python
# Copyright The IETF Trust 2024, All Rights Reserved
|
|
#
|
|
# Uses only Python standard lib
|
|
#
|
|
|
|
import argparse
|
|
import datetime
|
|
import json
|
|
import shutil
|
|
import stat
|
|
import sys
|
|
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
# Default options
|
|
POSTCONFIRM_PATH = "/a/postconfirm/wrapper"
|
|
VDOMAIN = "virtual.ietf.org"
|
|
|
|
# Map from domain label to dns domain
|
|
ADOMAINS = {
|
|
"ietf": "ietf.org",
|
|
"irtf": "irtf.org",
|
|
"iab": "iab.org",
|
|
}
|
|
|
|
|
|
def generate_files(records, adest, vdest, postconfirm, vdomain):
|
|
"""Generate files from an iterable of records
|
|
|
|
If adest or vdest exists as a file, it will be overwritten. If it is a directory, files
|
|
with the default names (draft-aliases and draft-virtual) will be created, but existing
|
|
files _will not_ be overwritten!
|
|
"""
|
|
with TemporaryDirectory() as tmpdir:
|
|
tmppath = Path(tmpdir)
|
|
apath = tmppath / "aliases"
|
|
vpath = tmppath / "virtual"
|
|
|
|
with apath.open("w") as afile, vpath.open("w") as vfile:
|
|
date = datetime.datetime.now(datetime.timezone.utc)
|
|
signature = f"# Generated by {Path(__file__).absolute()} at {date}\n"
|
|
afile.write(signature)
|
|
vfile.write(signature)
|
|
vfile.write(f"{vdomain} anything\n")
|
|
|
|
for item in records:
|
|
alias = item["alias"]
|
|
domains = item["domains"]
|
|
address_list = item["addresses"]
|
|
filtername = f"xfilter-{alias}"
|
|
afile.write(f'{filtername + ":":64s} "|{postconfirm} filter expand-{alias} {vdomain}"\n')
|
|
for dom in domains:
|
|
vfile.write(f"{f'{alias}@{ADOMAINS[dom]}':64s} {filtername}\n")
|
|
vfile.write(f"{f'expand-{alias}@{vdomain}':64s} {', '.join(sorted(address_list))}\n")
|
|
|
|
perms = stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
|
|
apath.chmod(perms)
|
|
vpath.chmod(perms)
|
|
shutil.move(apath, adest)
|
|
shutil.move(vpath, vdest)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(
|
|
description="Convert a JSON stream of draft alias definitions into alias / virtual alias files."
|
|
)
|
|
parser.add_argument(
|
|
"--prefix",
|
|
required=True,
|
|
help="Prefix for output files. Files will be named <prefix>-aliases and <prefix>-virtual."
|
|
)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
default="./",
|
|
type=Path,
|
|
help="Destination for output files.",
|
|
)
|
|
parser.add_argument(
|
|
"--postconfirm",
|
|
default=POSTCONFIRM_PATH,
|
|
help=f"Full path to postconfirm executable (defaults to {POSTCONFIRM_PATH}",
|
|
)
|
|
parser.add_argument(
|
|
"--vdomain",
|
|
default=VDOMAIN,
|
|
help=f"Virtual domain (defaults to {VDOMAIN}_",
|
|
)
|
|
args = parser.parse_args()
|
|
if not args.output_dir.is_dir():
|
|
sys.stderr.write("Error: output-dir must be a directory")
|
|
data = json.load(sys.stdin)
|
|
generate_files(
|
|
data["aliases"],
|
|
adest=args.output_dir / f"{args.prefix}-aliases",
|
|
vdest=args.output_dir / f"{args.prefix}-virtual",
|
|
postconfirm=args.postconfirm,
|
|
vdomain=args.vdomain,
|
|
)
|