Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions dev/archery/archery/ci/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# under the License.

import click
import email.utils

from .core import Workflow
from ..crossbow.reports import ChatReport, EmailReport, ReportUtils
Expand Down Expand Up @@ -105,13 +106,24 @@ def report_email(obj, workflow_id, sender_name, sender_email, recipient_email,
"""
output = obj['output']

email_report = EmailReport(
report=Workflow(workflow_id, repository,
ignore_job=ignore, gh_token=obj['github_token']),
sender_name=sender_name,
sender_email=sender_email,
recipient_email=recipient_email
workflow = Workflow(workflow_id, repository,
ignore_job=ignore, gh_token=obj['github_token'])
email_report = EmailReport(report=workflow)
n_errors = len(workflow.tasks_by_state['error'])
n_failures = len(workflow.tasks_by_state['failure'])
n_pendings = len(workflow.tasks_by_state['pending'])
subject = (
f'[NIGHTLY] Arrow Build Report for Job {workflow.job.branch}: '
f'{n_errors + n_failures} failed, '
f'{n_pendings} pending'
)
headers = {
'Date': email.utils.formatdate(workflow.datetime),
'From': f'{sender_name} <{sender_email}>',
'To': recipient_email,
'Subject': subject,
}
message = email_report.render('workflow_report', headers)

if send:
ReportUtils.send_email(
Expand All @@ -120,7 +132,7 @@ def report_email(obj, workflow_id, sender_name, sender_email, recipient_email,
smtp_server=smtp_server,
smtp_port=smtp_port,
recipient_email=recipient_email,
message=email_report.render("workflow_report")
message=message
)
else:
output.write(email_report.render("workflow_report"))
output.write(str(message))
47 changes: 30 additions & 17 deletions dev/archery/archery/crossbow/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# under the License.

from datetime import date
import email.utils
from pathlib import Path
import time
import sys
Expand Down Expand Up @@ -382,12 +383,20 @@ def report(obj, job_name, sender_name, sender_email, recipient_email,
queue.fetch()

job = queue.get(job_name)
email_report = EmailReport(
report=Report(job),
sender_name=sender_name,
sender_email=sender_email,
recipient_email=recipient_email
report = Report(job)
email_report = EmailReport(report=report)
date = report.datetime.strftime('%Y-%m-%d')
subject = (
f'[{date}] Arrow Build Report for {report.name}: '
f'{len(report.failed_jobs())} failed'
)
headers = {
'Date': email.utils.formatdate(report.datetime),
'From': f'{sender_name} <{sender_email}>',
'To': recipient_email,
'Subject': subject,
}
message = email_report.render('nightly_report', headers)

if poll:
job.wait_until_finished(
Expand All @@ -402,10 +411,10 @@ def report(obj, job_name, sender_name, sender_email, recipient_email,
smtp_server=smtp_server,
smtp_port=smtp_port,
recipient_email=recipient_email,
message=email_report.render("nightly_report")
message=message
)
else:
output.write(email_report.render("nightly_report"))
output.write(str(message))


@crossbow.command()
Expand Down Expand Up @@ -641,19 +650,23 @@ def notify_token_expiration(obj, days, sender_name, sender_email,
return

class TokenExpirationReport:
def __init__(self, token_expiration_date, days_left):
self.token_expiration_date = token_expiration_date
def __init__(self, days_left):
self.days_left = days_left

email_report = EmailReport(
report=TokenExpirationReport(
token_expiration_date or "ALREADY_EXPIRED", days_left),
sender_name=sender_name,
sender_email=sender_email,
recipient_email=recipient_email
if not token_expiration_date:
token_expiration_date = 'ALREADY_EXPIRED'
report = TokenExpirationReport(days_left)
email_report = EmailReport(report)
subject = (
f'[CI] Arrow Crossbow Token Expiration in {token_expiration_date}'
)
headers = {
'From': f'{sender_name} <{sender_email}>',
'To': recipient_email,
'Subject': subject,
}
message = email_report.render('token_expiration', headers)

message = email_report.render("token_expiration").strip()
if send:
ReportUtils.send_email(
smtp_user=smtp_user,
Expand All @@ -664,4 +677,4 @@ def __init__(self, token_expiration_date, days_left):
message=message
)
else:
output.write(message)
output.write(str(message))
19 changes: 15 additions & 4 deletions dev/archery/archery/crossbow/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import collections
import csv
import email.message
import email.utils
import operator
import fnmatch
import functools
Expand Down Expand Up @@ -259,7 +261,7 @@ def send_email(cls, smtp_user, smtp_password, smtp_server, smtp_port,
else:
smtp.starttls()
smtp.login(smtp_user, smtp_password)
smtp.sendmail(smtp_user, recipient_email, message)
smtp.send_message(smtp_user, recipient_email, message)

@classmethod
def write_csv(cls, report, add_headers=True):
Expand All @@ -278,11 +280,20 @@ class EmailReport(JinjaReport):
}
fields = [
'report',
'sender_name',
'sender_email',
'recipient_email',
]

def render(self, template_name, headers):
message = email.message.EmailMessage()
message.set_charset('utf-8')
if 'Message-Id' not in headers:
message['Message-Id'] = email.utils.make_msgid()
if 'Date' not in headers:
message['Date'] = email.utils.formatdate()
for (key, value) in headers.items():
message[key] = value
message.set_content(super().render(template_name))
return message


class CommentReport(Report):

Expand Down
11 changes: 8 additions & 3 deletions dev/archery/archery/crossbow/tests/fixtures/email-report.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
From: Sender Reporter <sender@arrow.com>
To: recipient@arrow.com
Subject: [NIGHTLY] Arrow Build Report for Job ursabot-1: 2 failed, 1 pending
MIME-Version: 1.0
Message-Id: <message-id>
Date: Thu, 01 Jan 2026 02:19:16 -0000
From: from@example.com
To: to@example.com
Subject: Arrow Build Report
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit

Arrow Build Report for Job ursabot-1

Expand Down
14 changes: 10 additions & 4 deletions dev/archery/archery/crossbow/tests/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,18 @@ def test_crossbow_email_report(load_fixture):
job = load_fixture('crossbow-job.yaml', decoder=yaml.load)
report = Report(job)
assert report.tasks_by_state is not None
email_report = EmailReport(report=report, sender_name="Sender Reporter",
sender_email="sender@arrow.com",
recipient_email="recipient@arrow.com")
email_report = EmailReport(report=report)
headers = {
'Message-Id': '<message-id>',
'Date': 'Thu, 01 Jan 2026 02:19:16 -0000',
'From': 'from@example.com',
'To': 'to@example.com',
'Subject': 'Arrow Build Report',
}

assert (
email_report.render("nightly_report") == textwrap.dedent(expected_msg)
str(email_report.render("nightly_report", headers)) ==
textwrap.dedent(expected_msg)
)


Expand Down
10 changes: 2 additions & 8 deletions dev/archery/archery/templates/email_nightly_report.txt.j2
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#}
{%- if True -%}
{%- endif -%}
From: {{ sender_name }} <{{ sender_email }}>
To: {{ recipient_email }}
Subject: [NIGHTLY] Arrow Build Report for Job {{report.job.branch}}: {{ (report.tasks_by_state["error"] | length) + (report.tasks_by_state["failure"] | length) }} failed, {{ report.tasks_by_state["pending"] | length }} pending

-#}
Arrow Build Report for Job {{ report.job.branch }}

See https://s3.amazonaws.com/arrow-data/index.html for more information.
Expand Down Expand Up @@ -58,4 +52,4 @@ Succeeded Tasks:
- {{ task_name }}
{{ report.task_url(task) }}
{% endfor %}
{%- endif -%}
{%- endif -%}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#}
From: {{ sender_name }} <{{ sender_email }}>
To: {{ recipient_email }}
Subject: [CI] Arrow Crossbow Token Expiration in {{ report.token_expiration_date }}

-#}
The Arrow Crossbow Token will expire in {{ report.days_left }} days.

Please generate a new Token. Send it to Apache INFRA to update the CROSSBOW_GITHUB_TOKEN.
Expand Down
10 changes: 2 additions & 8 deletions dev/archery/archery/templates/email_workflow_report.txt.j2
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#}
{%- if True -%}
{%- endif -%}
From: {{ sender_name }} <{{ sender_email }}>
To: {{ recipient_email }}
Subject: [{{ report.datetime.strftime('%Y-%m-%d') }}] Arrow Build Report for {{ report.name }}: {{ report.failed_jobs() | length }} failed

-#}
Arrow Build Report for {{ report.name }}

Workflow URL: {{ report.url }}
Expand All @@ -42,4 +36,4 @@ Succeeded Jobs:
- {{ job.name }}
{{ job.url }}
{% endfor %}
{%- endif -%}
{%- endif -%}