datatracker/ietf/bin/aliases-from-json.py
Jennifer Richards fa56223939
feat: API to replace email alias generation commands (#7012)
* 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()
2024-02-07 10:15:50 -06:00

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,
)