Skip to content

Commit 394403e

Browse files
committed
Add CloudWatchLogsEvent and SecretsManagerRotationEvent (part of #48)
1 parent 017f4ae commit 394403e

4 files changed

Lines changed: 396 additions & 0 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright 2021 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package feral.lambda.events
18+
19+
import io.circe.Decoder
20+
21+
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/cloudwatch-logs.d.ts
22+
// https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-cloudwatch-logs
23+
24+
sealed abstract class CloudWatchLogsEvent {
25+
def awslogs: CloudWatchLogsEventData
26+
}
27+
28+
object CloudWatchLogsEvent {
29+
30+
def apply(awslogs: CloudWatchLogsEventData): CloudWatchLogsEvent =
31+
new Impl(awslogs)
32+
33+
implicit val decoder: Decoder[CloudWatchLogsEvent] =
34+
Decoder.forProduct1("awslogs")(CloudWatchLogsEvent.apply)
35+
36+
private final case class Impl(awslogs: CloudWatchLogsEventData)
37+
extends CloudWatchLogsEvent {
38+
override def productPrefix = "CloudWatchLogsEvent"
39+
}
40+
}
41+
42+
sealed abstract class CloudWatchLogsEventData {
43+
def data: String
44+
}
45+
46+
object CloudWatchLogsEventData {
47+
48+
def apply(data: String): CloudWatchLogsEventData =
49+
new Impl(data)
50+
51+
private[events] implicit val decoder: Decoder[CloudWatchLogsEventData] =
52+
Decoder.forProduct1("data")(CloudWatchLogsEventData.apply)
53+
54+
private final case class Impl(data: String) extends CloudWatchLogsEventData {
55+
override def productPrefix = "CloudWatchLogsEventData"
56+
}
57+
}
58+
59+
/** Decoded payload after base64-decode and gzip-decompress of `CloudWatchLogsEventData.data`. */
60+
sealed abstract class CloudWatchLogsDecodedData {
61+
def owner: String
62+
def logGroup: String
63+
def logStream: String
64+
def subscriptionFilters: List[String]
65+
def messageType: String
66+
def logEvents: List[CloudWatchLogsLogEvent]
67+
}
68+
69+
object CloudWatchLogsDecodedData {
70+
71+
def apply(
72+
owner: String,
73+
logGroup: String,
74+
logStream: String,
75+
subscriptionFilters: List[String],
76+
messageType: String,
77+
logEvents: List[CloudWatchLogsLogEvent]
78+
): CloudWatchLogsDecodedData =
79+
new Impl(
80+
owner,
81+
logGroup,
82+
logStream,
83+
subscriptionFilters,
84+
messageType,
85+
logEvents
86+
)
87+
88+
implicit val decoder: Decoder[CloudWatchLogsDecodedData] =
89+
Decoder.forProduct6(
90+
"owner",
91+
"logGroup",
92+
"logStream",
93+
"subscriptionFilters",
94+
"messageType",
95+
"logEvents"
96+
)(CloudWatchLogsDecodedData.apply)
97+
98+
private final case class Impl(
99+
owner: String,
100+
logGroup: String,
101+
logStream: String,
102+
subscriptionFilters: List[String],
103+
messageType: String,
104+
logEvents: List[CloudWatchLogsLogEvent]
105+
) extends CloudWatchLogsDecodedData {
106+
override def productPrefix = "CloudWatchLogsDecodedData"
107+
}
108+
}
109+
110+
sealed abstract class CloudWatchLogsLogEvent {
111+
def id: String
112+
def timestamp: Long
113+
def message: String
114+
def extractedFields: Option[Map[String, String]]
115+
}
116+
117+
object CloudWatchLogsLogEvent {
118+
119+
def apply(
120+
id: String,
121+
timestamp: Long,
122+
message: String,
123+
extractedFields: Option[Map[String, String]]
124+
): CloudWatchLogsLogEvent =
125+
new Impl(id, timestamp, message, extractedFields)
126+
127+
private[events] implicit val decoder: Decoder[CloudWatchLogsLogEvent] =
128+
Decoder.forProduct4("id", "timestamp", "message", "extractedFields")(
129+
CloudWatchLogsLogEvent.apply
130+
)
131+
132+
private final case class Impl(
133+
id: String,
134+
timestamp: Long,
135+
message: String,
136+
extractedFields: Option[Map[String, String]]
137+
) extends CloudWatchLogsLogEvent {
138+
override def productPrefix = "CloudWatchLogsLogEvent"
139+
}
140+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2021 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package feral.lambda.events
18+
19+
import io.circe.Decoder
20+
21+
import scala.util.Try
22+
23+
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/secretsmanager.d.ts
24+
// https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html
25+
26+
sealed abstract class SecretsManagerRotationEventStep
27+
28+
object SecretsManagerRotationEventStep {
29+
30+
case object CreateSecret extends SecretsManagerRotationEventStep
31+
case object SetSecret extends SecretsManagerRotationEventStep
32+
case object TestSecret extends SecretsManagerRotationEventStep
33+
case object FinishSecret extends SecretsManagerRotationEventStep
34+
35+
private[events] implicit val decoder: Decoder[SecretsManagerRotationEventStep] =
36+
Decoder.decodeString.emapTry {
37+
case "createSecret" => Try(CreateSecret)
38+
case "setSecret" => Try(SetSecret)
39+
case "testSecret" => Try(TestSecret)
40+
case "finishSecret" => Try(FinishSecret)
41+
case s => scala.util.Failure(new IllegalArgumentException(s"Unknown step: $s"))
42+
}
43+
}
44+
45+
sealed abstract class SecretsManagerRotationEvent {
46+
def step: SecretsManagerRotationEventStep
47+
def secretId: String
48+
def clientRequestToken: String
49+
}
50+
51+
object SecretsManagerRotationEvent {
52+
53+
def apply(
54+
step: SecretsManagerRotationEventStep,
55+
secretId: String,
56+
clientRequestToken: String
57+
): SecretsManagerRotationEvent =
58+
new Impl(step, secretId, clientRequestToken)
59+
60+
private[events] implicit val decoder: Decoder[SecretsManagerRotationEvent] =
61+
Decoder.instance(c =>
62+
for {
63+
step <- c.get[SecretsManagerRotationEventStep]("Step")
64+
secretId <- c.get[String]("SecretId")
65+
clientRequestToken <- c.get[String]("ClientRequestToken")
66+
} yield SecretsManagerRotationEvent(step, secretId, clientRequestToken)
67+
)
68+
69+
private final case class Impl(
70+
step: SecretsManagerRotationEventStep,
71+
secretId: String,
72+
clientRequestToken: String
73+
) extends SecretsManagerRotationEvent {
74+
override def productPrefix = "SecretsManagerRotationEvent"
75+
}
76+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2021 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package feral.lambda.events
18+
19+
import io.circe.literal._
20+
import munit.FunSuite
21+
22+
class CloudWatchLogsEventSuite extends FunSuite {
23+
24+
test("decoder") {
25+
assertEquals(event.as[CloudWatchLogsEvent].toTry.get, expected)
26+
}
27+
28+
test("decoded data decoder") {
29+
assertEquals(decodedDataJson.as[CloudWatchLogsDecodedData].toTry.get, expectedDecodedData)
30+
}
31+
32+
def event = json"""
33+
{
34+
"awslogs": {
35+
"data": "H4sIAAAAAAAA/6tWKkktLlGyUlAqSS0u0QHQKhZnJ2cm5+cXZOYnlQAAAP//"
36+
}
37+
}
38+
"""
39+
40+
def expected =
41+
CloudWatchLogsEvent(
42+
awslogs = CloudWatchLogsEventData(
43+
data = "H4sIAAAAAAAA/6tWKkktLlGyUlAqSS0u0QHQKhZnJ2cm5+cXZOYnlQAAAP//"
44+
)
45+
)
46+
47+
def decodedDataJson = json"""
48+
{
49+
"owner": "123456789012",
50+
"logGroup": "/aws/lambda/my-function",
51+
"logStream": "2024/01/15/[$$LATEST]abc123",
52+
"subscriptionFilters": ["filter-1"],
53+
"messageType": "DATA_MESSAGE",
54+
"logEvents": [
55+
{
56+
"id": "event-id-1",
57+
"timestamp": 1705312800000,
58+
"message": "Log line one",
59+
"extractedFields": { "field1": "value1" }
60+
}
61+
]
62+
}
63+
"""
64+
65+
def expectedDecodedData =
66+
CloudWatchLogsDecodedData(
67+
owner = "123456789012",
68+
logGroup = "/aws/lambda/my-function",
69+
logStream = "2024/01/15/[$LATEST]abc123",
70+
subscriptionFilters = List("filter-1"),
71+
messageType = "DATA_MESSAGE",
72+
logEvents = List(
73+
CloudWatchLogsLogEvent(
74+
id = "event-id-1",
75+
timestamp = 1705312800000L,
76+
message = "Log line one",
77+
extractedFields = Some(Map("field1" -> "value1"))
78+
)
79+
)
80+
)
81+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2021 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package feral.lambda.events
18+
19+
import io.circe.literal._
20+
import munit.FunSuite
21+
22+
class SecretsManagerRotationEventSuite extends FunSuite {
23+
24+
test("decoder createSecret") {
25+
assertEquals(createSecretEvent.as[SecretsManagerRotationEvent].toTry.get, expectedCreateSecret)
26+
}
27+
28+
test("decoder setSecret") {
29+
assertEquals(setSecretEvent.as[SecretsManagerRotationEvent].toTry.get, expectedSetSecret)
30+
}
31+
32+
test("decoder testSecret") {
33+
assertEquals(testSecretEvent.as[SecretsManagerRotationEvent].toTry.get, expectedTestSecret)
34+
}
35+
36+
test("decoder finishSecret") {
37+
assertEquals(finishSecretEvent.as[SecretsManagerRotationEvent].toTry.get, expectedFinishSecret)
38+
}
39+
40+
def createSecretEvent = json"""
41+
{
42+
"Step": "createSecret",
43+
"SecretId": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret",
44+
"ClientRequestToken": "token-123"
45+
}
46+
"""
47+
48+
def expectedCreateSecret =
49+
SecretsManagerRotationEvent(
50+
step = SecretsManagerRotationEventStep.CreateSecret,
51+
secretId = "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret",
52+
clientRequestToken = "token-123"
53+
)
54+
55+
def setSecretEvent = json"""
56+
{
57+
"Step": "setSecret",
58+
"SecretId": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret",
59+
"ClientRequestToken": "token-456"
60+
}
61+
"""
62+
63+
def expectedSetSecret =
64+
SecretsManagerRotationEvent(
65+
step = SecretsManagerRotationEventStep.SetSecret,
66+
secretId = "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret",
67+
clientRequestToken = "token-456"
68+
)
69+
70+
def testSecretEvent = json"""
71+
{
72+
"Step": "testSecret",
73+
"SecretId": "my-secret-id",
74+
"ClientRequestToken": "token-789"
75+
}
76+
"""
77+
78+
def expectedTestSecret =
79+
SecretsManagerRotationEvent(
80+
step = SecretsManagerRotationEventStep.TestSecret,
81+
secretId = "my-secret-id",
82+
clientRequestToken = "token-789"
83+
)
84+
85+
def finishSecretEvent = json"""
86+
{
87+
"Step": "finishSecret",
88+
"SecretId": "my-secret-id",
89+
"ClientRequestToken": "token-finish"
90+
}
91+
"""
92+
93+
def expectedFinishSecret =
94+
SecretsManagerRotationEvent(
95+
step = SecretsManagerRotationEventStep.FinishSecret,
96+
secretId = "my-secret-id",
97+
clientRequestToken = "token-finish"
98+
)
99+
}

0 commit comments

Comments
 (0)