Skip to content

Commit ca151e7

Browse files
committed
feat(vtalerts-ipaws): init
1 parent 2c1594d commit ca151e7

3 files changed

Lines changed: 145 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Build and push testing-environment Docker image
2+
on:
3+
push:
4+
tags:
5+
- 'vtalerts-ipaws-*'
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v1
12+
- name: Get release version
13+
id: get_version
14+
run: echo RELEASE_VERSION=$(echo ${GITHUB_REF:25}) >> $GITHUB_ENV
15+
- name: Build and push Docker image
16+
uses: docker/build-push-action@v1
17+
with:
18+
repository: wuvt/vtalerts-ipaws
19+
username: ${{ secrets.DOCKER_USERNAME }}
20+
password: ${{ secrets.DOCKER_PASSWORD }}
21+
tags: latest,${{ env.RELEASE_VERSION }}
22+
path: vtalerts-ipaws
23+
add_git_labels: true
24+
- name: Build and push Docker image to GitHub Container Registry
25+
uses: docker/build-push-action@v1
26+
with:
27+
repository: wuvt/vtalerts-ipaws
28+
username: wuvt
29+
password: ${{ secrets.GITHUB_TOKEN }}
30+
registry: ghcr.io
31+
tags: latest,${{ env.RELEASE_VERSION }}
32+
path: vtalerts-ipaws
33+
add_git_labels: true

vtalerts-ipaws/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3.11-slim
2+
3+
WORKDIR /app
4+
5+
RUN pip install --no-cache-dir requests pytz
6+
7+
COPY vt_cap_to_atom.py .
8+
9+
EXPOSE 8000
10+
11+
CMD ["python", "-u", "vt_cap_to_atom.py"]

vtalerts-ipaws/vt_cap_to_atom.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import requests
2+
import xml.etree.ElementTree as ET
3+
from http.server import BaseHTTPRequestHandler, HTTPServer
4+
from datetime import datetime, timezone, timedelta
5+
6+
VT_CAP_FEED_URL = "https://content.alerts.vt.edu/alerts-cap-inbound/api/cap"
7+
8+
SERVER_HOST = "0.0.0.0"
9+
SERVER_PORT = 8000
10+
11+
FETCH_WINDOW = timedelta(weeks=365)
12+
SERVE_WINDOW = timedelta(days=1)
13+
14+
def fetch_and_translate_feed(host_header, feed = True):
15+
window = int((datetime.now() - FETCH_WINDOW).timestamp() * 1000)
16+
response = requests.get(VT_CAP_FEED_URL + f"?after={window}")
17+
response.raise_for_status()
18+
vt_cap_xml = ET.fromstring(response.content)
19+
atom_root = ET.Element("feed", xmlns="http://www.w3.org/2005/Atom")
20+
title = ET.SubElement(atom_root, "title", type="text")
21+
title.text = "VT ALERTS IPAWS EAS FEED"
22+
23+
feed_id = ET.SubElement(atom_root, "id")
24+
feed_id.text = f"http://{host_header}/IPAWSOPEN_EAS_SERVICE/rest/feed"
25+
26+
latest_entry_time = None
27+
one_minute_ago = datetime.now(timezone.utc) - SERVE_WINDOW
28+
29+
entries = []
30+
for alert_node in vt_cap_xml:
31+
if alert_node.tag == '{urn:oasis:names:tc:emergency:cap:1.2}alert':
32+
scope_node = alert_node.find("{urn:oasis:names:tc:emergency:cap:1.2}scope")
33+
if scope_node is None or scope_node.text != "Public":
34+
continue
35+
36+
sent_node = alert_node.find("{urn:oasis:names:tc:emergency:cap:1.2}sent")
37+
38+
current_entry_time = None
39+
if sent_node is not None and sent_node.text:
40+
current_entry_time = datetime.fromisoformat(sent_node.text)
41+
else:
42+
current_entry_time = datetime.now(timezone.utc)
43+
44+
alert_key = alert_node.get("key")
45+
if not alert_key:
46+
continue
47+
48+
entry = ET.Element("entry")
49+
50+
single_alert_url = f"{VT_CAP_FEED_URL}/{alert_key}"
51+
entry_title = ET.SubElement(entry, "title", type="text")
52+
entry_title.text = f"VTALERT"
53+
ET.SubElement(entry, "link", href=single_alert_url)
54+
entry_id = ET.SubElement(entry, "id")
55+
entry_id.text = single_alert_url
56+
entry_updated = ET.SubElement(entry, "updated")
57+
58+
ET.SubElement(entry, "category", term="LAE", label="event")
59+
ET.SubElement(entry, "category", term="51", label="statefips")
60+
61+
entry_updated.text = current_entry_time.replace(tzinfo=timezone.utc).isoformat().replace('+00:00', 'Z')
62+
63+
if latest_entry_time is None or current_entry_time > latest_entry_time:
64+
latest_entry_time = current_entry_time
65+
66+
if current_entry_time < one_minute_ago:
67+
continue
68+
69+
entries.append(entry)
70+
71+
updated = ET.Element("updated")
72+
if latest_entry_time:
73+
updated.text = latest_entry_time.replace(tzinfo=timezone.utc).isoformat().replace('+00:00', 'Z')
74+
else:
75+
updated.text = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
76+
77+
atom_root.insert(1, updated)
78+
79+
if feed:
80+
entries.sort(key=lambda e: e.find('updated').text, reverse=True)
81+
82+
for entry in entries:
83+
atom_root.append(entry)
84+
85+
return ET.tostring(atom_root, encoding='unicode')
86+
87+
class IPAWSHandler(BaseHTTPRequestHandler):
88+
def do_GET(self):
89+
if self.path == '/IPAWSOPEN_EAS_SERVICE/rest/update' or \
90+
self.path == '/IPAWSOPEN_EAS_SERVICE/rest/feed':
91+
self.send_response(200)
92+
self.send_header("Content-type", "application/xml")
93+
self.end_headers()
94+
atom_feed = fetch_and_translate_feed(self.headers['Host'], feed=self.path.endswith("feed"))
95+
if atom_feed:
96+
self.wfile.write(atom_feed.encode('utf-8'))
97+
98+
server_address = (SERVER_HOST, SERVER_PORT)
99+
httpd = HTTPServer(server_address, IPAWSHandler)
100+
print(f"serving {SERVER_HOST}:{SERVER_PORT}")
101+
httpd.serve_forever()

0 commit comments

Comments
 (0)