in asfyaml/feature/notifications.py [0:0]
def run(self):
# Test if we need to process this (only works on the default branch)
if self.instance.branch != self.repository.default_branch:
print("Saw notifications meta-data in .asf.yaml, but not in default branch of repository, not updating...")
return
self.valid_targets = {} # Set to a brand-new instance-local dict for valid scheme entries.
# Read the list of valid mailing list targets from disk
valid_lists = json.load(open(VALID_LISTS_FILE))
# For each setting in our YAML, validate and then set.
for key, value in self.yaml.items():
# commits_by_path is handled elsewhere and is a dict, so we disregard that here.
# jira_options isn't super necessary to verify yet, so ignore as well.
if key == "commits_by_path" or key == "jira_options":
continue
# if there is a '.incubator' bit in the mailing list target, crop it out. We're done with those!
value = value.replace(".incubator.apache.org", ".apache.org")
if not isinstance(value, str):
raise Exception(
f"[ERROR] Found bad value set for notifications::{key}. Notification targets must be string values."
)
# Ensure we allow this scheme to be configured
if not any(fnmatch.fnmatch(key, pattern) for pattern in VALID_NOTIFICATION_SCHEMES):
raise Exception(f"[ERROR] Found unknown notification scheme, notifications::{key}")
# Ensure this is a valid (existing) list target
if not RE_VALID_MAILING_LIST.match(value) or value not in valid_lists:
raise Exception(
f"[ERROR] The mailing list target, {value}, set in notifications::{key}, is not an existing ASF mailing list."
)
if self.repository.is_private:
if not any(fnmatch.fnmatch(value, pattern) for pattern in VALID_PRIVATE_TARGETS):
raise Exception(
f"[ERROR] The mailing list target for notifications::{key} MUST be a private mailing list."
)
# Ensure the right project is contacted, but allow for overrides
mapped_override = mappings.ML_OVERRIDES.get(self.repository.name)
if mapped_override and value == mapped_override:
pass
elif not value.endswith(f"@{self.repository.hostname}.apache.org"):
raise Exception(
f"[ERROR] Target for notifications::{key} is set to {value}, but must be a valid @{self.repository.hostname}.apache.org mailing list!"
)
# All is well??
self.valid_targets[key] = value
# Check for commits_by_path and validate if found
if "commits_by_path" in self.yaml:
if not isinstance(self.yaml.commits_by_path, dict):
raise Exception(
f"[ERROR] notifications::commits_by_path must be a dictionary, but was a {self.yaml.commits_by_path.__class__.__name__}"
)
for pattern, target in self.yaml.commits_by_path.items():
# All mail targets must be strings. Either a single target or a list of targets.
email_targets = (isinstance(target, list) and target) or [target]
if not all(isinstance(x, str) for x in email_targets):
raise Exception(
f"[ERROR] Notification target for notifications::commits_by_path::{pattern} must be either a single email address or a list of email addresses."
)
for email_address in email_targets:
if not fnmatch.fnmatch(email_address, "*@*.*"): # Super simple email address validation
raise Exception(
f"[ERROR] Notification target for notifications::commits_by_path::{pattern} must be valid email addresses, but found target: {email_address}"
)
# Update the notifications file on disk
scheme_path = os.path.join(self.repository.path, NOTIFICATION_SETTINGS_FILE)
old_yml = {}
if os.path.exists(scheme_path):
old_yml = yaml.safe_load(open(scheme_path))
if old_yml == self.yaml: # No changes, just return straight away.
return
else: # Changes made, save to disk
with open(scheme_path, "w") as fp:
yaml.dump(self.yaml_raw, fp, default_flow_style=False)
print("Dumped yaml")
print(f"Updating notification schemes for repository {self.repository.name}: ")
changes = ""
# Figure out what changed since last
all_schemes = set(list(self.yaml.keys()) + list(old_yml.keys())) # Every scheme in old + new set.
for key in all_schemes:
if key == "commits_by_path":
continue # We don't handle these just yet
if key not in old_yml and key in self.yaml:
changes += "- adding new scheme (%s): %r\n" % (key, self.yaml[key])
elif key in old_yml and key not in self.yaml:
changes += "- removing old scheme (%s) - was %r\n" % (key, old_yml[key])
elif key in old_yml and key in self.yaml and old_yml[key] != self.yaml[key]:
changes += "- updating scheme %s: %r -> %r\n" % (key, old_yml[key], self.yaml[key])
# Print the changes to the git client
print(changes)
changesets = ""
for push in self.repository.changesets:
for commit in push.commits:
if ".asf.yaml" in commit.files:
perp = commit.committer_email
if commit.committer_email != commit.author_email:
perp = f"{commit.committer_email}/{commit.author_email}"
changesets += f"{commit.sha}: [{perp}] {commit.subject}\n"
# If in test mode, bail!
if "quietmode" in self.instance.environments_enabled:
return
# Tell project what happened, on private@
msg = f"""The following notification schemes have been changed on {self.repository.name} by {self.committer.email}:
{changes}
These changes were caused by the following commits to the {self.instance.branch} branch:
{changesets}
With regards,
ASF Infra.
"""
asfpy.messaging.mail(
sender="GitBox <gitbox@apache.org>",
recipients=[f"private@{self.repository.hostname}.apache.org"],
subject=f"Notification schemes for {self.repository.name}.git updated",
message=msg,
)