Skip to content

Commit d78e92f

Browse files
authored
feat: make Logging property for GraphQLApi (#2971)
1 parent fe958f3 commit d78e92f

39 files changed

+1272
-24
lines changed

samtranslator/internal/model/appsync.py

+8
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,27 @@ class DynamoDBConfigType(TypedDict, total=False):
2525
DeltaSyncConfig: DeltaSyncConfigType
2626

2727

28+
class LogConfigType(TypedDict, total=False):
29+
CloudWatchLogsRoleArn: Intrinsicable[str]
30+
ExcludeVerboseContent: bool
31+
FieldLogLevel: str
32+
33+
2834
class GraphQLApi(Resource):
2935
resource_type = "AWS::AppSync::GraphQLApi"
3036
property_types = {
3137
"Name": GeneratedProperty(),
3238
"Tags": GeneratedProperty(),
3339
"XrayEnabled": GeneratedProperty(),
3440
"AuthenticationType": GeneratedProperty(),
41+
"LogConfig": GeneratedProperty(),
3542
}
3643

3744
Name: str
3845
AuthenticationType: str
3946
Tags: Optional[List[Dict[str, Any]]]
4047
XrayEnabled: Optional[bool]
48+
LogConfig: Optional[LogConfigType]
4149

4250
runtime_attrs = {"api_id": lambda self: fnGetAtt(self.logical_id, "ApiId")}
4351

samtranslator/internal/schema_source/aws_serverless_graphqlapi.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from typing import Optional
1+
from typing import Optional, Union
22

33
from typing_extensions import Literal
44

5-
from samtranslator.internal.schema_source.common import BaseModel, DictStrAny, get_prop
5+
from samtranslator.internal.schema_source.common import BaseModel, DictStrAny, PassThroughProp, get_prop
66

77
properties = get_prop("sam-resource-graphqlapi")
88

@@ -12,13 +12,20 @@ class Auth(BaseModel):
1212
Type: str
1313

1414

15+
class Logging(BaseModel):
16+
CloudWatchLogsRoleArn: Optional[str]
17+
ExcludeVerboseContent: Optional[PassThroughProp]
18+
FieldLogLevel: Optional[str]
19+
20+
1521
class Properties(BaseModel):
1622
Auth: Auth
1723
Tags: Optional[DictStrAny]
1824
Name: Optional[str]
1925
XrayEnabled: Optional[bool]
2026
SchemaInline: Optional[str]
2127
SchemaUri: Optional[str]
28+
Logging: Optional[Union[Logging, bool]]
2229

2330

2431
class Resource(BaseModel):

samtranslator/model/sam_resources.py

+66-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
DynamoDBConfigType,
1717
GraphQLApi,
1818
GraphQLSchema,
19+
LogConfigType,
1920
)
2021
from samtranslator.internal.types import GetManagedPolicyMap
2122
from samtranslator.intrinsics.resolver import IntrinsicsResolver
@@ -2136,6 +2137,7 @@ class SamGraphQLApi(SamResourceMacro):
21362137
"Auth": Property(True, IS_DICT),
21372138
"SchemaInline": Property(False, IS_STR),
21382139
"SchemaUri": Property(False, IS_STR),
2140+
"Logging": Property(False, one_of(IS_DICT, is_type(bool))),
21392141
}
21402142

21412143
Auth: Dict[str, Any]
@@ -2144,16 +2146,21 @@ class SamGraphQLApi(SamResourceMacro):
21442146
Name: Optional[str]
21452147
SchemaInline: Optional[str]
21462148
SchemaUri: Optional[str]
2149+
Logging: Optional[Union[Dict[str, Any], bool]]
21472150

21482151
@cw_timer
21492152
def to_cloudformation(self, **kwargs: Any) -> List[Resource]:
2150-
appsync_api = self._construct_appsync_api()
2153+
appsync_api, cloudwatch_role = self._construct_appsync_api_resources()
21512154
appsync_schema = self._construct_appsync_schema(appsync_api.get_runtime_attr("api_id"))
2155+
21522156
resources: List[Resource] = [appsync_api, appsync_schema]
21532157

2158+
if cloudwatch_role:
2159+
resources.append(cloudwatch_role)
2160+
21542161
return resources
21552162

2156-
def _construct_appsync_api(self) -> GraphQLApi:
2163+
def _construct_appsync_api_resources(self) -> Tuple[GraphQLApi, Optional[IAMRole]]:
21572164
api = GraphQLApi(logical_id=self.logical_id, depends_on=self.depends_on, attributes=self.resource_attributes)
21582165

21592166
api.AuthenticationType = sam_expect(self.Auth.get("Type"), self.logical_id, "Auth.Type").to_be_a_string()
@@ -2163,7 +2170,63 @@ def _construct_appsync_api(self) -> GraphQLApi:
21632170
if self.Tags:
21642171
api.Tags = get_tag_list(self.Tags)
21652172

2166-
return api
2173+
# Logging has 3 possible types: dict, bool, and None.
2174+
# GraphQLApi will not include logging if and only if the user explicity sets Logging as false boolean.
2175+
# It will for every other value (including true boolean which is essentially same as None).
2176+
if isinstance(self.Logging, bool) and self.Logging is False:
2177+
return api, None
2178+
2179+
api.LogConfig, cloudwatch_role = self._parse_logging_properties()
2180+
2181+
return api, cloudwatch_role
2182+
2183+
def _create_logging_default(self) -> Tuple[LogConfigType, IAMRole]:
2184+
"""
2185+
Create a default logging configuration.
2186+
2187+
This function is used when "Logging" property is a False boolean or NoneType.
2188+
"""
2189+
log_config: LogConfigType = {}
2190+
log_config["FieldLogLevel"] = "ALL"
2191+
cloudwatch_role = self._construct_cloudwatch_role()
2192+
log_config["CloudWatchLogsRoleArn"] = cloudwatch_role.get_runtime_attr("arn")
2193+
2194+
return log_config, cloudwatch_role
2195+
2196+
def _parse_logging_properties(self) -> Tuple[LogConfigType, Optional[IAMRole]]:
2197+
"""Parse logging properties from SAM template, and use defaults if required keys dont exist."""
2198+
if not isinstance(self.Logging, dict):
2199+
return self._create_logging_default()
2200+
2201+
log_config: LogConfigType = {}
2202+
2203+
if "ExcludeVerboseContent" in self.Logging:
2204+
log_config["ExcludeVerboseContent"] = self.Logging["ExcludeVerboseContent"]
2205+
2206+
log_config["FieldLogLevel"] = self.Logging.get("FieldLogLevel", "ALL")
2207+
log_config["CloudWatchLogsRoleArn"] = self.Logging.get("CloudWatchLogsRoleArn", None)
2208+
2209+
if log_config["CloudWatchLogsRoleArn"]:
2210+
return log_config, None
2211+
2212+
cloudwatch_role = self._construct_cloudwatch_role()
2213+
log_config["CloudWatchLogsRoleArn"] = cloudwatch_role.get_runtime_attr("arn")
2214+
2215+
return log_config, cloudwatch_role
2216+
2217+
def _construct_cloudwatch_role(self) -> IAMRole:
2218+
role = IAMRole(
2219+
logical_id=f"{self.logical_id}CloudWatchRole",
2220+
depends_on=self.depends_on,
2221+
attributes=self.resource_attributes,
2222+
)
2223+
role.AssumeRolePolicyDocument = IAMRolePolicies.construct_assume_role_policy_for_service_principal(
2224+
"appsync.amazonaws.com"
2225+
)
2226+
role.ManagedPolicyArns = [
2227+
{"Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs"}
2228+
]
2229+
return role
21672230

21682231
def _construct_appsync_schema(self, api_id: Intrinsicable[str]) -> GraphQLSchema:
21692232
schema = GraphQLSchema(

samtranslator/schema/schema.json

+29
Original file line numberDiff line numberDiff line change
@@ -195464,6 +195464,24 @@
195464195464
"title": "Location",
195465195465
"type": "object"
195466195466
},
195467+
"Logging": {
195468+
"additionalProperties": false,
195469+
"properties": {
195470+
"CloudWatchLogsRoleArn": {
195471+
"title": "Cloudwatchlogsrolearn",
195472+
"type": "string"
195473+
},
195474+
"ExcludeVerboseContent": {
195475+
"$ref": "#/definitions/PassThroughProp"
195476+
},
195477+
"FieldLogLevel": {
195478+
"title": "Fieldloglevel",
195479+
"type": "string"
195480+
}
195481+
},
195482+
"title": "Logging",
195483+
"type": "object"
195484+
},
195467195485
"MQEvent": {
195468195486
"additionalProperties": false,
195469195487
"properties": {
@@ -199349,6 +199367,17 @@
199349199367
"Auth": {
199350199368
"$ref": "#/definitions/samtranslator__internal__schema_source__aws_serverless_graphqlapi__Auth"
199351199369
},
199370+
"Logging": {
199371+
"anyOf": [
199372+
{
199373+
"$ref": "#/definitions/Logging"
199374+
},
199375+
{
199376+
"type": "boolean"
199377+
}
199378+
],
199379+
"title": "Logging"
199380+
},
199352199381
"Name": {
199353199382
"title": "Name",
199354199383
"type": "string"

schema_source/sam.schema.json

+29
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,24 @@
19581958
"title": "Location",
19591959
"type": "object"
19601960
},
1961+
"Logging": {
1962+
"additionalProperties": false,
1963+
"properties": {
1964+
"CloudWatchLogsRoleArn": {
1965+
"title": "Cloudwatchlogsrolearn",
1966+
"type": "string"
1967+
},
1968+
"ExcludeVerboseContent": {
1969+
"$ref": "#/definitions/PassThroughProp"
1970+
},
1971+
"FieldLogLevel": {
1972+
"title": "Fieldloglevel",
1973+
"type": "string"
1974+
}
1975+
},
1976+
"title": "Logging",
1977+
"type": "object"
1978+
},
19611979
"MQEvent": {
19621980
"additionalProperties": false,
19631981
"properties": {
@@ -5748,6 +5766,17 @@
57485766
"Auth": {
57495767
"$ref": "#/definitions/samtranslator__internal__schema_source__aws_serverless_graphqlapi__Auth"
57505768
},
5769+
"Logging": {
5770+
"anyOf": [
5771+
{
5772+
"$ref": "#/definitions/Logging"
5773+
},
5774+
{
5775+
"type": "boolean"
5776+
}
5777+
],
5778+
"title": "Logging"
5779+
},
57515780
"Name": {
57525781
"title": "Name",
57535782
"type": "string"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Transform: AWS::Serverless-2016-10-31
2+
Resources:
3+
NoAuthAPI:
4+
Type: AWS::Serverless::GraphQLApi
5+
Properties:
6+
XrayEnabled: true
7+
Tags:
8+
Key1: Value1
9+
Key2: Value2
10+
11+
NoSchemaPropertiesAPI:
12+
Type: AWS::Serverless::GraphQLApi
13+
Properties:
14+
XrayEnabled: true
15+
Auth:
16+
Type: AWS_IAM
17+
Tags:
18+
key1: value1
19+
key2: value2
20+
21+
BothSchemaInlineAndUriAPI:
22+
Type: AWS::Serverless::GraphQLApi
23+
Properties:
24+
SchemaInline: |
25+
type Mutation {
26+
addTodo(id: ID!, name: String, description: String, priority: Int): Todo
27+
}
28+
SchemaUri: https://bucket-name.s3.region-code.amazonaws.com/key-name
29+
XrayEnabled: true
30+
Auth:
31+
Type: AWS_IAM
32+
Tags:
33+
key1: value1
34+
key2: value2

tests/translator/input/error_graphqlapi_no_auth.yaml

-9
This file was deleted.

tests/translator/input/error_graphqlapi_both_schema_inline_and_uri.yaml renamed to tests/translator/input/graphqlapi_default_logging.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Resources:
77
type Mutation {
88
addTodo(id: ID!, name: String, description: String, priority: Int): Todo
99
}
10-
SchemaUri: https://bucket-name.s3.region-code.amazonaws.com/key-name
1110
XrayEnabled: true
1211
Auth:
1312
Type: AWS_IAM
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Transform: AWS::Serverless-2016-10-31
2+
Resources:
3+
SuperCoolAPI:
4+
Type: AWS::Serverless::GraphQLApi
5+
Properties:
6+
SchemaInline: |
7+
type Mutation {
8+
addTodo(id: ID!, name: String, description: String, priority: Int): Todo
9+
}
10+
XrayEnabled: true
11+
Auth:
12+
Type: AWS_IAM
13+
Tags:
14+
key1: value1
15+
key2: value2
16+
Logging:
17+
CloudWatchLogsRoleArn: some-arn
18+
FieldLogLevel: ERROR
19+
ExcludeVerboseContent: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# This is not an expected user use case (they should just not define Logging if they want defaults), but still testing.
2+
Transform: AWS::Serverless-2016-10-31
3+
Resources:
4+
SuperCoolAPI:
5+
Type: AWS::Serverless::GraphQLApi
6+
Properties:
7+
SchemaInline: |
8+
type Mutation {
9+
addTodo(id: ID!, name: String, description: String, priority: Int): Todo
10+
}
11+
XrayEnabled: true
12+
Auth:
13+
Type: AWS_IAM
14+
Tags:
15+
key1: value1
16+
key2: value2
17+
Logging: true

tests/translator/input/error_graphqlapi_no_schema_properties.yaml renamed to tests/translator/input/graphqlapi_no_logging.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ Resources:
33
SuperCoolAPI:
44
Type: AWS::Serverless::GraphQLApi
55
Properties:
6+
SchemaInline: |
7+
type Mutation {
8+
addTodo(id: ID!, name: String, description: String, priority: Int): Todo
9+
}
610
XrayEnabled: true
711
Auth:
812
Type: AWS_IAM
913
Tags:
1014
key1: value1
1115
key2: value2
16+
Logging: false

0 commit comments

Comments
 (0)