Skip to content

Commit ff3c7af

Browse files
authored
CMR-10313: Setup the bamboo builds - code changed as well as I needed to change the structure and added tests. (#2218)
* CMR-10313: Setup the bamboo builds - code changed as well as I needed to change the structure and added tests. * CMR-10313: trying to make build compatible with bamboo. * CMR-10313: trying to make build compatible with bamboo. * CMR-10313: trying to make build compatible with bamboo. * CMR-10313: trying to make build compatible with bamboo. * CMR-10313: updating PR requests. * CMR-10313: Adding dockerfile to build with the correct version of python. * CMR-10313: building testing tool. * CMR-10313: building testing tool. * CMR-10313: building testing tool. * CMR-10313: Rename the test lambda
1 parent fa71a72 commit ff3c7af

File tree

17 files changed

+387
-65
lines changed

17 files changed

+387
-65
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Use an official Python runtime as a parent image
2+
FROM python:3.13-slim
3+
4+
# Install zip utility
5+
RUN apt-get update && apt-get install -y zip
6+
7+
# Set the working directory in the container
8+
WORKDIR /app
9+
10+
# Copy the current directory contents into the container at /app
11+
COPY . /app
12+
13+
# Make sure build.sh is executable
14+
RUN chmod +x build.sh
15+
16+
# Run build.sh when the container launches
17+
CMD ["./build.sh"]
18+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
# This script is used by the bamboo build project.
3+
4+
mkdir -p package
5+
6+
pip3 install --target ./package -r requirements.txt --no-compile
7+
8+
# Zip dependencies
9+
cd package
10+
zip -r ../notify_lambda_deployment_package.zip . -x "__pycache__/*"
11+
cd ..
12+
13+
# Add contents of src directory to the zip file
14+
cd src
15+
zip -r ../notify_lambda_deployment_package.zip . -x "__pycache__/*"
16+
17+
# Return to the top directory
18+
cd ..
19+
20+
rm -r package
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
requests
2+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
3+
export PYTHONPATH=src
4+
5+
pip3 install requests
6+
python3 -m unittest discover -v -s ./test -p "*_test.py"
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import sys
44
from typing import Optional
55

6-
LOG_LEVEL: int = int(os.getenv("LOG_LEVEL"))
7-
if not LOG_LEVEL:
8-
LOG_LEVEL = logging.INFO
6+
LOG_LEVEL: int = int(os.getenv("LOG_LEVEL", logging.INFO))
97

108
def setup_logger(name: str, log_file: Optional[str] = None, level: int = logging.INFO) -> logging.Logger:
119
"""Function to setup as many loggers as you want"""
@@ -28,3 +26,4 @@ def setup_logger(name: str, log_file: Optional[str] = None, level: int = logging
2826

2927
# Create a default logger
3028
logger = setup_logger(name='default_logger', level=LOG_LEVEL)
29+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import json
2+
import requests
3+
from logger import logger
4+
5+
# This lambda is triggered through a subscription to the cmr-internal-subscription-<env> SNS topic. It processes the events which are notifications that get sent
6+
# to an external URL.
7+
8+
def handler(event, context):
9+
"""The handler is the starting point that is triggered by an SNS topic subscription with a filter that designates tha the notification sent is a URL notification.
10+
Input: event: Dict[str, Any], context: Any
11+
Returns: None"""
12+
13+
logger.debug(f"Ingest notification lambda received event: {json.dumps(event, indent=2)}")
14+
for record in event['Records']:
15+
process_message(record)
16+
17+
def process_message(record):
18+
"""Processes the record in the event.
19+
Input: record: Dict[str, Any]
20+
Returns: None"""
21+
22+
try:
23+
logger.info(f"Ingest notification lambda processing message - record: {record}")
24+
message = record['Sns']
25+
message_attributes = record['Sns']['MessageAttributes']
26+
url = message_attributes['endpoint']['Value']
27+
send_message(url, message)
28+
29+
except Exception as e:
30+
logger.error(f"Ingest notification lambda an error occurred {e} while trying to send the record: {record}")
31+
raise e
32+
33+
def send_message(url, message):
34+
"""Sends the passed message to the external URL. If not successful the message is put onto a dead letter queue.
35+
Input: url: str, message: Dict[str, Any]
36+
Returns: None"""
37+
38+
# Prepare the data to be sent
39+
40+
try:
41+
# Send a POST request to the URL with the message data
42+
headers = {'Content-Type': 'application/json'}
43+
logger.info(f"Ingest notification lambda sending message ID: {message['MessageId']} to URL: {url}")
44+
response = requests.post(url, headers=headers, json=message)
45+
46+
# Check if the request was successful
47+
if response.status_code == 200:
48+
logger.info(f"Ingest notification lambda successfully sent message ID: {message['MessageId']}")
49+
else:
50+
logger.error(f"Ingest notification lambda failed to send message ID: {message['MessageId']}. Status code: {response.status_code}. Response: {response.text}")
51+
52+
except requests.exceptions.RequestException as e:
53+
logger.error(f"Ingest notification lambda an error occurred while sending the message id {message['MessageId']} to URL: {url} {e}")
54+
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import unittest
2+
from unittest.mock import patch, MagicMock
3+
import json
4+
import requests
5+
from notification_lambda import handler, process_message, send_message
6+
7+
class TestNotificationHandler(unittest.TestCase):
8+
9+
@patch('notification_lambda.process_message')
10+
def test_handler(self, mock_process_message):
11+
event = {
12+
'Records': [
13+
{'some': 'data1'},
14+
{'some': 'data2'}
15+
]
16+
}
17+
context = {}
18+
19+
handler(event, context)
20+
21+
self.assertEqual(mock_process_message.call_count, 2)
22+
mock_process_message.assert_any_call({'some': 'data1'})
23+
mock_process_message.assert_any_call({'some': 'data2'})
24+
25+
@patch('notification_lambda.send_message')
26+
def test_process_message(self, mock_send_message):
27+
record = {
28+
'Sns': {
29+
'MessageId': '12345',
30+
'MessageAttributes': {
31+
'endpoint': {
32+
'Value': 'http://example.com'
33+
}
34+
}
35+
}
36+
}
37+
38+
process_message(record)
39+
40+
mock_send_message.assert_called_once_with('http://example.com', record['Sns'])
41+
42+
@patch('notification_lambda.send_message')
43+
def test_process_message_exception(self, mock_send_message):
44+
record = {
45+
'Sns': {
46+
'MessageId': '12345',
47+
'MessageAttributes': {} # Missing 'endpoint' to cause an exception
48+
}
49+
}
50+
51+
with self.assertRaises(Exception):
52+
process_message(record)
53+
54+
@patch('requests.post')
55+
def test_send_message_success(self, mock_post):
56+
mock_response = MagicMock()
57+
mock_response.status_code = 200
58+
mock_post.return_value = mock_response
59+
60+
url = 'http://example.com'
61+
message = {'MessageId': '12345', 'some': 'data'}
62+
63+
send_message(url, message)
64+
65+
mock_post.assert_called_once_with(url, headers={'Content-Type': 'application/json'}, json=message)
66+
67+
@patch('requests.post')
68+
def test_send_message_failure(self, mock_post):
69+
mock_response = MagicMock()
70+
mock_response.status_code = 400
71+
mock_response.text = 'Bad Request'
72+
mock_post.return_value = mock_response
73+
74+
url = 'http://example.com'
75+
message = {'MessageId': '12345', 'some': 'data'}
76+
77+
send_message(url, message)
78+
79+
mock_post.assert_called_once_with(url, headers={'Content-Type': 'application/json'}, json=message)
80+
81+
@patch('requests.post')
82+
def test_send_message_exception(self, mock_post):
83+
mock_post.side_effect = requests.exceptions.RequestException('Network error')
84+
85+
url = 'http://example.com'
86+
message = {'MessageId': '12345', 'some': 'data'}
87+
88+
send_message(url, message)
89+
90+
mock_post.assert_called_once_with(url, headers={'Content-Type': 'application/json'}, json=message)
91+
92+
if __name__ == '__main__':
93+
unittest.main()
94+

subscription/notify_lambda/Dockerfile

Lines changed: 0 additions & 13 deletions
This file was deleted.

subscription/notify_lambda/build.sh

Lines changed: 0 additions & 1 deletion
This file was deleted.

subscription/notify_lambda/notification-lambda.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)