Skip to content
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

Add event definitions for CloudFormation custom resources #695

Merged
merged 3 commits into from
Sep 11, 2023
Merged
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
2 changes: 2 additions & 0 deletions lambda-events/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ default = [
"autoscaling",
"chime_bot",
"clientvpn",
"cloudformation",
"cloudwatch_events",
"cloudwatch_logs",
"code_commit",
Expand Down Expand Up @@ -81,6 +82,7 @@ appsync = []
autoscaling = ["chrono"]
chime_bot = ["chrono"]
clientvpn = []
cloudformation = []
cloudwatch_events = ["chrono"]
cloudwatch_logs = ["flate2"]
code_commit = ["chrono"]
Expand Down
17 changes: 9 additions & 8 deletions lambda-events/src/custom_serde/codebuild_time.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use chrono::{DateTime, TimeZone, Utc};
use chrono::{DateTime, NaiveDateTime, Utc};
use serde::ser::Serializer;
use serde::{
de::{Deserializer, Error as DeError, Visitor},
Expand All @@ -18,7 +18,8 @@ impl<'de> Visitor<'de> for TimeVisitor {
}

fn visit_str<E: DeError>(self, val: &str) -> Result<Self::Value, E> {
Utc.datetime_from_str(val, CODEBUILD_TIME_FORMAT)
NaiveDateTime::parse_from_str(val, CODEBUILD_TIME_FORMAT)
.map(|naive| naive.and_utc())
.map_err(|e| DeError::custom(format!("Parse error {} for {}", e, val)))
}
}
Expand Down Expand Up @@ -81,9 +82,9 @@ mod tests {
"date": "Sep 1, 2017 4:12:29 PM"
});

let expected = Utc
.datetime_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT)
.unwrap();
let expected = NaiveDateTime::parse_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT)
.unwrap()
.and_utc();
let decoded: Test = serde_json::from_value(data).unwrap();
assert_eq!(expected, decoded.date);
}
Expand All @@ -99,9 +100,9 @@ mod tests {
"date": "Sep 1, 2017 4:12:29 PM"
});

let expected = Utc
.datetime_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT)
.unwrap();
let expected = NaiveDateTime::parse_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT)
.unwrap()
.and_utc();
let decoded: Test = serde_json::from_value(data).unwrap();
assert_eq!(Some(expected), decoded.date);
}
Expand Down
143 changes: 143 additions & 0 deletions lambda-events/src/event/cloudformation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "RequestType")]
pub enum CloudFormationCustomResourceRequest<P1 = Value, P2 = Value>
where
P1: DeserializeOwned + Serialize,
P2: DeserializeOwned + Serialize,
{
#[serde(bound = "")]
Create(CreateRequest<P2>),
#[serde(bound = "")]
Update(UpdateRequest<P1, P2>),
#[serde(bound = "")]
Delete(DeleteRequest<P2>),
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(deny_unknown_fields)]
pub struct CreateRequest<P2 = Value>
where
P2: DeserializeOwned + Serialize,
{
#[serde(default)]
pub service_token: Option<String>,
pub request_id: String,
#[serde(rename = "ResponseURL")]
pub response_url: String,
pub stack_id: String,
pub resource_type: String,
pub logical_resource_id: String,
#[serde(bound = "")]
pub resource_properties: P2,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(deny_unknown_fields)]
pub struct UpdateRequest<P1 = Value, P2 = Value>
where
P1: DeserializeOwned + Serialize,
P2: DeserializeOwned + Serialize,
{
#[serde(default)]
pub service_token: Option<String>,
pub request_id: String,
#[serde(rename = "ResponseURL")]
pub response_url: String,
pub stack_id: String,
pub resource_type: String,
pub logical_resource_id: String,
pub physical_resource_id: String,
#[serde(bound = "")]
pub resource_properties: P2,
#[serde(bound = "")]
pub old_resource_properties: P1,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(deny_unknown_fields)]
pub struct DeleteRequest<P2 = Value>
where
P2: DeserializeOwned + Serialize,
{
#[serde(default)]
pub service_token: Option<String>,
pub request_id: String,
#[serde(rename = "ResponseURL")]
pub response_url: String,
pub stack_id: String,
pub resource_type: String,
pub logical_resource_id: String,
pub physical_resource_id: String,
#[serde(bound = "")]
pub resource_properties: P2,
}

#[cfg(test)]
mod test {
use std::collections::HashMap;

use super::CloudFormationCustomResourceRequest::*;
use super::*;

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(deny_unknown_fields)]
struct TestProperties {
key_1: String,
key_2: Vec<String>,
key_3: HashMap<String, String>,
}

type TestRequest = CloudFormationCustomResourceRequest<TestProperties, TestProperties>;

#[test]
fn example_cloudformation_custom_resource_create_request() {
let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-create-request.json");
let parsed: TestRequest = serde_json::from_slice(data).unwrap();

match parsed {
Create(_) => (),
_ => panic!("expected Create request"),
}

let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
}

#[test]
fn example_cloudformation_custom_resource_update_request() {
let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-update-request.json");
let parsed: TestRequest = serde_json::from_slice(data).unwrap();

match parsed {
Update(_) => (),
_ => panic!("expected Update request"),
}

let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
}

#[test]
fn example_cloudformation_custom_resource_delete_request() {
let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-delete-request.json");
let parsed: TestRequest = serde_json::from_slice(data).unwrap();

match parsed {
Delete(_) => (),
_ => panic!("expected Delete request"),
}

let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
}
}
4 changes: 4 additions & 0 deletions lambda-events/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub mod chime_bot;
#[cfg(feature = "clientvpn")]
pub mod clientvpn;

/// AWS Lambda event definitions for cloudformation.
#[cfg(feature = "cloudformation")]
pub mod cloudformation;

/// CloudWatch Events payload
#[cfg(feature = "cloudwatch_events")]
pub mod cloudwatch_events;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler",
"RequestType" : "Create",
"RequestId" : "82304eb2-bdda-469f-a33b-a3f1406d0a52",
"ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values",
"StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da",
"ResourceType" : "Custom::MyCustomResourceType",
"LogicalResourceId" : "CustomResource",
"ResourceProperties" : {
"Key1" : "string",
"Key2" : [ "list" ],
"Key3" : { "Key4" : "map" }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler",
"RequestType" : "Delete",
"RequestId" : "ef70561d-d4ba-42a4-801b-33ad88dafc37",
"ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values",
"StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da",
"ResourceType" : "Custom::MyCustomResourceType",
"LogicalResourceId" : "CustomResource",
"PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647",
"ResourceProperties" : {
"Key1" : "string",
"Key2" : [ "list" ],
"Key3" : { "Key4" : "map" }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler",
"RequestType" : "Update",
"RequestId" : "49347ca5-c603-44e5-a34b-10cf1854a887",
"ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values",
"StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da",
"ResourceType" : "Custom::MyCustomResourceType",
"LogicalResourceId" : "CustomResource",
"PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647",
"ResourceProperties" : {
"Key1" : "new-string",
"Key2" : [ "new-list" ],
"Key3" : { "Key4" : "new-map" }
},
"OldResourceProperties" : {
"Key1" : "string",
"Key2" : [ "list" ],
"Key3" : { "Key4" : "map" }
}
}
5 changes: 5 additions & 0 deletions lambda-events/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub use event::activemq;
/// AWS Lambda event definitions for alb.
#[cfg(feature = "alb")]
pub use event::alb;

/// AWS Lambda event definitions for apigw.
#[cfg(feature = "apigw")]
pub use event::apigw;
Expand All @@ -40,6 +41,10 @@ pub use event::chime_bot;
#[cfg(feature = "clientvpn")]
pub use event::clientvpn;

/// AWS Lambda event definitions for cloudformation
#[cfg(feature = "cloudformation")]
pub use event::cloudformation;

/// CloudWatch Events payload
#[cfg(feature = "cloudwatch_events")]
pub use event::cloudwatch_events;
Expand Down