Skip to content

terraform module for the lambda_function #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 38 commits into from
Apr 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8c5a02b
terraform module for the lambda_function
Jan 17, 2018
6f91fba
json.loads of os env METADATA
Jan 17, 2018
9b10ec6
order those imports!
aeikenberry Jan 17, 2018
2c51912
merge fix
aeikenberry Jan 17, 2018
5c95a95
cleaning up
aeikenberry Jan 17, 2018
84be834
support log_tags variable to supply CUSTOM_TAGS env variable and add …
Jan 17, 2018
886bfc2
[Logs] Improve retry logic to catch exceptions in the socket
tmichelet Jan 18, 2018
75768b7
[Logs] Print more information when catching an exception
tmichelet Jan 18, 2018
19fd398
[Logs] Avoid logging an error when we successfully retry submission
tmichelet Jan 23, 2018
d9f4726
Merge pull request #24 from DataDog/tristan/fix-logs-lambda-retry
tmichelet Jan 24, 2018
06c2498
should be awslogS not singular
aldegoeij Jan 25, 2018
5afe7fd
Merge pull request #25 from aldegoeij/master
Feb 21, 2018
88e0a0b
Add source auto detection for mysql and mariadb in RDS
Feb 22, 2018
a659fde
Refactor the Parse_event_source function
Feb 23, 2018
bc24709
add apigateway automatic source detection
Feb 23, 2018
004b562
Fix gzip decompression
Feb 26, 2018
da93f30
fix formatting
Feb 26, 2018
6ad517d
Fix elb source issue after refactoring
Feb 26, 2018
7d2ef76
Merge pull request #27 from DataDog/nils/rds
Feb 26, 2018
6ba484b
Merge pull request #28 from tinyclues/fix-gzip-decompress
Mar 5, 2018
2a2c157
update yaml files to add key parameter
DanielLanger Mar 8, 2018
d2385bd
lower case kms
DanielLanger Mar 8, 2018
686df82
add type
DanielLanger Mar 8, 2018
a5b1881
lower case arn
DanielLanger Mar 8, 2018
4f3e6ab
Merge pull request #29 from DataDog/daniel/samtemplateupdate
DanielLanger Mar 8, 2018
706ab14
rename custom tag
DanielLanger Mar 12, 2018
d0152ac
fix typo
DanielLanger Mar 12, 2018
55e976b
Merge pull request #31 from DataDog/daniel/samtemplateupdate
DanielLanger Mar 12, 2018
1b8fb3d
Merge pull request #30 from DataDog/daniel/awslogfunction
DanielLanger Mar 13, 2018
f83ecd8
Lowercase on the lambda function name to match infrastructure tags
Mar 27, 2018
86e6e11
Merge pull request #33 from DataDog/nils/logs-lambda-lowercase
Mar 28, 2018
be3d38e
Add support for Cloudwatch Events
Mar 30, 2018
2d3779c
Merge pull request #35 from DataDog/nils/cloudwatch-events
Apr 3, 2018
4dd76c2
[Logs] Update endpoint for logs-submitter lambda function
tmichelet Apr 20, 2018
f73db17
Merge pull request #37 from DataDog/tristan/logs
tmichelet Apr 20, 2018
0c04d66
Merge remote-tracking branch 'base/master' into feature/terraform-app…
Apr 27, 2018
75215f9
adding METADATA env variable json to log lines
Apr 27, 2018
ebd1387
Terraform Usage comments
Apr 27, 2018
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ env
base.zip
*/gen
*/env
*.tfvars
*.tfstate*
.terraform
*.zip
14 changes: 14 additions & 0 deletions Log/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,17 @@ You can optionally change the structured metadata. The metadata is merged to all
You are all set!

The test should be quite natural if the pointed bucket(s) are filling up. There may be some latency between the time a S3 log file is posted and the Lambda function wakes up.

# Terraform Usage

```
module "dd_appender" {
source = "github.com/Placester/dd-aws-lambda-functions//Log"
DD_API_KEY = {}
role = ""

metadata = {
service = "my_service"
}
}
```
111 changes: 73 additions & 38 deletions Log/lambda_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
import socket
import ssl
import re
import zlib
import StringIO
import gzip

# Parameters
# ddApiKey: Datadog API Key
Expand All @@ -30,8 +31,13 @@
"ddsourcecategory": "aws",
}

try:
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aeikenberry thinking of having Terraform map variable impact the default metadata on each of these logs...is this the right python code to support that?

metadata = merge_dicts(metadata, json.loads(os.environ.get('METADATA', '{}')))
except Exception:
pass

host = "intake.logs.datadoghq.com"

host = "lambda-intake.logs.datadoghq.com"
ssl_port = 10516
cloudtrail_regex = re.compile('\d+_CloudTrail_\w{2}-\w{4,9}-\d_\d{8}T\d{4}Z.+.json.gz$', re.I)

Expand All @@ -47,11 +53,7 @@ def lambda_handler(event, context):
)

# Attach Datadog's Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

port = ssl_port
s = ssl.wrap_socket(s)
s.connect((host, port))
s = connect_to_datadog(host, ssl_port)

# Add the context to meta
if "aws" not in metadata:
Expand All @@ -60,30 +62,47 @@ def lambda_handler(event, context):
aws_meta["function_version"] = context.function_version
aws_meta["invoked_function_arn"] = context.invoked_function_arn
#Add custom tags here by adding new value with the following format "key1:value1, key2:value2" - might be subject to modifications
metadata[DD_CUSTOM_TAGS] = "functionname:" + context.function_name+ ",memorysize:"+ context.memory_limit_in_mb
metadata[DD_CUSTOM_TAGS] = "forwardername:" + context.function_name.lower()+ ",memorysize:"+ context.memory_limit_in_mb

try:
logs = generate_logs(event)
for log in logs:
s = safe_submit_log(s, log)
except Exception as e:
print('Unexpected exception: {} for event {}'.format(str(e), event))
finally:
s.close()

def connect_to_datadog(host, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s = ssl.wrap_socket(s)
s.connect((host, port))
return s

def generate_logs(event):
try:
# Route to the corresponding parser
event_type = parse_event_type(event)

if event_type == "s3":
logs = s3_handler(s, event)

logs = s3_handler(event)
elif event_type == "awslogs":
logs = awslogs_handler(s, event)

for log in logs:
send_entry(s, log)

logs = awslogs_handler(event)
elif event_type == "events":
logs = cwevent_handler(event)
except Exception as e:
# Logs through the socket the error
err_message = 'Error parsing the object. Exception: {}'.format(str(e))
send_entry(s, err_message)
raise e
finally:
s.close()
err_message = 'Error parsing the object. Exception: {} for event {}'.format(str(e), event)
logs = [err_message]
return logs

def safe_submit_log(s, log):
try:
send_entry(s, log)
except Exception as e:
# retry once
s = connect_to_datadog(host, ssl_port)
send_entry(s, log)
return s

# Utility functions

Expand All @@ -95,11 +114,13 @@ def parse_event_type(event):
elif "awslogs" in event:
return "awslogs"

elif "detail" in event:
return "events"
raise Exception("Event type not supported (see #Event supported section)")


# Handle S3 events
def s3_handler(s, event):
def s3_handler(event):
s3 = boto3.client('s3')

# Get the object from the event and show its content type
Expand All @@ -117,7 +138,8 @@ def s3_handler(s, event):

# If the name has a .gz extension, then decompress the data
if key[-3:] == '.gz':
data = zlib.decompress(data, 16 + zlib.MAX_WBITS)
with gzip.GzipFile(fileobj=StringIO.StringIO(data)) as decompress_stream:
data = decompress_stream.read()

if is_cloudtrail(str(key)):
cloud_trail = json.loads(data)
Expand All @@ -135,10 +157,11 @@ def s3_handler(s, event):
return structured_logs


# Handle CloudWatch events and logs
def awslogs_handler(s, event):
# Handle CloudWatch logs
def awslogs_handler(event):
# Get logs
data = zlib.decompress(base64.b64decode(event["awslogs"]["data"]), 16 + zlib.MAX_WBITS)
with gzip.GzipFile(fileobj=StringIO.StringIO(base64.b64decode(event["awslogs"]["data"]))) as decompress_stream:
data = decompress_stream.read()
logs = json.loads(str(data))
#Set the source on the logs
source = logs.get("logGroup", "cloudwatch")
Expand All @@ -162,6 +185,23 @@ def awslogs_handler(s, event):

return structured_logs

#Handle Cloudwatch Events
def cwevent_handler(event):

data = event

#Set the source on the log
source = data.get("source", "cloudwatch")
service = source.split(".")
if len(service)>1:
metadata[DD_SOURCE] = service[1]
else:
metadata[DD_SOURCE] = "cloudwatch"

structured_logs = []
structured_logs.append(data)

return structured_logs

def send_entry(s, log_entry):
# The log_entry can only be a string or a dict
Expand Down Expand Up @@ -205,21 +245,16 @@ def is_cloudtrail(key):
return bool(match)

def parse_event_source(event, key):
if "lambda" in key:
return "lambda"
if is_cloudtrail(str(key)):
return "cloudtrail"
for source in ["lambda", "redshift", "cloudfront", "kinesis", "mariadb", "mysql", "apigateway", "route53", "vpc", "rds"]:
if source in key:
return source
if "elasticloadbalancing" in key:
return "elb"
if "redshift" in key:
return "redshift"
if "cloudfront" in key:
return "cloudfront"
if "kinesis" in key:
return "kinesis"
if "awslog" in event:
if is_cloudtrail(str(key)):
return "cloudtrail"
if "awslogs" in event:
return "cloudwatch"
if "Records" in event and len(event["Records"]) > 0:
if "s3" in event["Records"][0]:
return "s3"
return "aws"
return "aws"
83 changes: 83 additions & 0 deletions Log/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
variable "aws_region" {
default = "us-east-1"
}

provider "aws" {
region = "${var.aws_region}"
}

variable "DD_API_KEY" {
description = "This token is used to associate AWS CloudWatch logs to a log in your Logentries account."
}

variable "function_name" {
default = "dd_log_appender"
}

variable "role" {
description = "arn of the lambda role"
}

variable "memory_size" {
default = 1024
}

variable "timeout" {
default = 300
}

variable "metadata" {
type = "map"
default = {}
description = "map of custom key:value entries on each log statement"
}

variable "tags" {
type = "map"
default = {}
description = "lambda function tags"
}

data "archive_file" "fn" {
type = "zip"
source_file = "${path.module}/lambda_function.py"
output_path = "${path.module}/lambda_function.py.zip"
}

resource "aws_lambda_function" "fn" {
function_name = "${var.function_name}"
role = "${var.role}"
handler = "lambda_function.lambda_handler"
filename = "${path.module}/lambda_function.py.zip"
source_code_hash = "${data.archive_file.fn.output_base64sha256}"

# Set memory to 128 MB
memory_size = "${var.memory_size}"

# Set timeout to ~2 minutes (script only runs for seconds at a time)
timeout = "${var.timeout}"
runtime = "python2.7"

environment {
variables = {
DD_API_KEY = "${var.DD_API_KEY}"
METADATA = "${jsonencode(var.metadata)}"
}
}

tags = "${var.tags}"

depends_on = ["data.archive_file.fn"]
}

output "function_arn" {
value = "${aws_lambda_function.fn.arn}"
}

output "function_name" {
value = "${aws_lambda_function.fn.function_name}"
}

output "function_version" {
value = "${aws_lambda_function.fn.version}"
}
15 changes: 12 additions & 3 deletions rds_enhanced_monitoring/rds-enhanced-sam-template.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Pushes RDS Enhanced metrics to Datadog.
Parameters:
KMSKeyId:
Type: String
Description: The id (final part of the key's ARN) of a KMS key used to encrypt and decrypt your Datadog API and App keys.
Resources:
rdslambdaddfunction:
Type: 'AWS::Serverless::Function'
Properties:
Description: Pushes RDS Enhanced metrics to Datadog.
Environment:
Variables:
kmsEncryptedKeys: YOUR_KEY
kmsEncryptedKeys: 'YOUR_KEY'
Copy link

@aeikenberry aeikenberry Apr 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrapptambour

is this right?

Handler: lambda_function.lambda_handler
MemorySize: 128
Policies:
KMSDecryptPolicy:
KeyId: YOUR_KEY
KeyId: !Ref KMSKeyId
Runtime: python2.7
Timeout: 10
KmsKeyArn:
!Sub
- 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${keyId}'
- {keyId: !Ref KMSKeyId}
Type: AWS::Serverless::Function
Transform: AWS::Serverless-2016-10-31
15 changes: 12 additions & 3 deletions vpc_flow_log_monitoring/vpc-flow-log-sam-template.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Pushes VPC Flow Log metrics to Datadog.
Parameters:
KMSKeyId:
Type: String
Description: The id (final part of the key's ARN) of a KMS key used to encrypt and decrypt your Datadog API and App keys.
Resources:
vpcflowlambdaddfunction:
Type: 'AWS::Serverless::Function'
Properties:
Description: Pushes VPC Flow Log metrics to Datadog.
Environment:
Variables:
kmsEncryptedKeys: YOUR_KEY
kmsEncryptedKeys: 'YOUR_KEY'
Handler: lambda_function.lambda_handler
MemorySize: 128
Policies:
KMSDecryptPolicy:
KeyId: YOUR_KEY
KeyId: !Ref KMSKeyId
Runtime: python2.7
Timeout: 10
KmsKeyArn:
!Sub
- 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${keyId}'
- {keyId: !Ref KMSKeyId}
Type: AWS::Serverless::Function
Transform: AWS::Serverless-2016-10-31