okdata-aws 2.2.0__tar.gz → 3.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/PKG-INFO +1 -1
- okdata-aws-3.0.1/okdata/aws/status/model.py +98 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/status/wrapper.py +14 -9
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/PKG-INFO +1 -1
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/SOURCES.txt +1 -4
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/requires.txt +0 -1
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/setup.py +1 -2
- okdata-aws-2.2.0/okdata/aws/status/model.py +0 -72
- okdata-aws-2.2.0/tests/test_logging.py +0 -318
- okdata-aws-2.2.0/tests/test_model.py +0 -29
- okdata-aws-2.2.0/tests/test_status.py +0 -131
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/README.md +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/__init__.py +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/__init__.py +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/logging.py +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/ssm.py +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/status/__init__.py +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/status/sdk.py +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/dependency_links.txt +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/namespace_packages.txt +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/top_level.txt +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/pyproject.toml +0 -0
- {okdata-aws-2.2.0 → okdata-aws-3.0.1}/setup.cfg +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from dataclasses import asdict, dataclass, field
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from json import dumps, JSONEncoder
|
|
5
|
+
from typing import Dict, Optional, List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TraceStatus(str, Enum):
|
|
9
|
+
STARTED = "STARTED"
|
|
10
|
+
CONTINUE = "CONTINUE"
|
|
11
|
+
FINISHED = "FINISHED"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TraceEventStatus(str, Enum):
|
|
15
|
+
OK = "OK"
|
|
16
|
+
FAILED = "FAILED"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StatusJSONEncoder(JSONEncoder):
|
|
20
|
+
def default(self, obj, *args, **kwargs):
|
|
21
|
+
if isinstance(obj, Exception):
|
|
22
|
+
return str(obj)
|
|
23
|
+
if isinstance(obj, datetime):
|
|
24
|
+
return obj.isoformat()
|
|
25
|
+
return super().default(obj)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BaseModel:
|
|
29
|
+
def dict(self, exclude_none=False):
|
|
30
|
+
if exclude_none:
|
|
31
|
+
return asdict(
|
|
32
|
+
self,
|
|
33
|
+
dict_factory=lambda d: {k: v for (k, v) in d if v is not None},
|
|
34
|
+
)
|
|
35
|
+
return asdict(self)
|
|
36
|
+
|
|
37
|
+
def json(self, exclude_none=False, **kwargs):
|
|
38
|
+
return dumps(
|
|
39
|
+
self.dict(exclude_none=exclude_none),
|
|
40
|
+
cls=StatusJSONEncoder,
|
|
41
|
+
**kwargs,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def parse_obj(cls, obj):
|
|
46
|
+
return cls(**obj)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class StatusMeta(BaseModel):
|
|
51
|
+
function_name: Optional[str] = None
|
|
52
|
+
function_version: Optional[str] = None
|
|
53
|
+
function_stage: Optional[str] = None
|
|
54
|
+
function_api_id: Optional[str] = None
|
|
55
|
+
git_rev: Optional[str] = None
|
|
56
|
+
git_branch: Optional[str] = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# TODO: Rework optional vs required and defaults when currents users are updated
|
|
60
|
+
# and if to be used as a basis for the status api. Both trace_id (for new traces)
|
|
61
|
+
# and trace_event_id are currently generated in status-api.
|
|
62
|
+
@dataclass
|
|
63
|
+
class StatusData(BaseModel):
|
|
64
|
+
trace_id: Optional[str] = None # TODO: Generate here as default?
|
|
65
|
+
# trace_event_id: UUID = None # = Field(default_factory=uuid4)
|
|
66
|
+
domain: str = "N/A" # TODO: Temporary default (required)
|
|
67
|
+
domain_id: Optional[str] = None
|
|
68
|
+
start_time: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
69
|
+
end_time: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
70
|
+
trace_status: TraceStatus = TraceStatus.CONTINUE
|
|
71
|
+
trace_event_status: TraceEventStatus = TraceEventStatus.OK
|
|
72
|
+
user: Optional[str] = None
|
|
73
|
+
component: str = "N/A" # TODO: Temporary default (required)
|
|
74
|
+
operation: Optional[str] = None
|
|
75
|
+
status_body: Optional[Dict] = None
|
|
76
|
+
meta: Optional[StatusMeta] = None
|
|
77
|
+
s3_path: Optional[str] = None
|
|
78
|
+
duration: Optional[int] = None
|
|
79
|
+
exception: Optional[str] = None
|
|
80
|
+
errors: Optional[List] = None
|
|
81
|
+
|
|
82
|
+
def __setattr__(self, name, value):
|
|
83
|
+
# Validate and ensure format of errors
|
|
84
|
+
if name == "errors" and value is not None:
|
|
85
|
+
if not isinstance(value, list):
|
|
86
|
+
raise TypeError("`errors` must be provided as a list.")
|
|
87
|
+
|
|
88
|
+
for error in value:
|
|
89
|
+
if not isinstance(error, dict):
|
|
90
|
+
raise TypeError(f"{error} is not a dict.")
|
|
91
|
+
if "message" not in error:
|
|
92
|
+
raise ValueError("Missing key 'message'.")
|
|
93
|
+
if not isinstance(error["message"], dict):
|
|
94
|
+
raise TypeError("error['message'] is not a dict.")
|
|
95
|
+
if "nb" not in error["message"]:
|
|
96
|
+
raise ValueError("Missing key 'nb' in error['message'].")
|
|
97
|
+
|
|
98
|
+
super().__setattr__(name, value)
|
|
@@ -5,7 +5,12 @@ from functools import wraps
|
|
|
5
5
|
|
|
6
6
|
from requests.exceptions import HTTPError
|
|
7
7
|
|
|
8
|
-
from .model import
|
|
8
|
+
from .model import (
|
|
9
|
+
StatusData,
|
|
10
|
+
StatusMeta,
|
|
11
|
+
TraceEventStatus,
|
|
12
|
+
TraceStatus,
|
|
13
|
+
)
|
|
9
14
|
from .sdk import Status
|
|
10
15
|
|
|
11
16
|
_status_logger = None
|
|
@@ -57,13 +62,13 @@ def _status_from_lambda_context(event, context):
|
|
|
57
62
|
"trace_id": event.get("execution_name"),
|
|
58
63
|
"user": authorizer.get("principalId"),
|
|
59
64
|
"component": os.getenv("SERVICE_NAME"),
|
|
60
|
-
"meta":
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
"meta": StatusMeta(
|
|
66
|
+
function_name=getattr(context, "function_name", None),
|
|
67
|
+
function_version=getattr(context, "function_version", None),
|
|
68
|
+
function_stage=request_context.get("stage"),
|
|
69
|
+
function_api_id=request_context.get("apiId"),
|
|
70
|
+
git_rev=os.getenv("GIT_REV"),
|
|
71
|
+
git_branch=os.getenv("GIT_BRANCH"),
|
|
72
|
+
),
|
|
68
73
|
}
|
|
69
74
|
)
|
|
@@ -14,7 +14,4 @@ okdata_aws.egg-info/SOURCES.txt
|
|
|
14
14
|
okdata_aws.egg-info/dependency_links.txt
|
|
15
15
|
okdata_aws.egg-info/namespace_packages.txt
|
|
16
16
|
okdata_aws.egg-info/requires.txt
|
|
17
|
-
okdata_aws.egg-info/top_level.txt
|
|
18
|
-
tests/test_logging.py
|
|
19
|
-
tests/test_model.py
|
|
20
|
-
tests/test_status.py
|
|
17
|
+
okdata_aws.egg-info/top_level.txt
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="okdata-aws",
|
|
8
|
-
version="
|
|
8
|
+
version="3.0.1",
|
|
9
9
|
author="Oslo Origo",
|
|
10
10
|
author_email="dataplattform@oslo.kommune.no",
|
|
11
11
|
description="Collection of helpers for working with AWS",
|
|
@@ -30,7 +30,6 @@ setuptools.setup(
|
|
|
30
30
|
install_requires=[
|
|
31
31
|
"boto3",
|
|
32
32
|
"okdata-sdk>=3,<4",
|
|
33
|
-
"pydantic<2",
|
|
34
33
|
"requests",
|
|
35
34
|
"starlette>=0.25.0,<1.0.0",
|
|
36
35
|
"structlog",
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
from typing import Dict, Optional, List
|
|
3
|
-
from datetime import datetime, timezone
|
|
4
|
-
from pydantic import BaseModel, Field, validator
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class TraceStatus(str, Enum):
|
|
8
|
-
STARTED = "STARTED"
|
|
9
|
-
CONTINUE = "CONTINUE"
|
|
10
|
-
FINISHED = "FINISHED"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TraceEventStatus(str, Enum):
|
|
14
|
-
OK = "OK"
|
|
15
|
-
FAILED = "FAILED"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class StatusMeta(BaseModel):
|
|
19
|
-
function_name: Optional[str] = None
|
|
20
|
-
function_version: Optional[str] = None
|
|
21
|
-
function_stage: Optional[str] = None
|
|
22
|
-
function_api_id: Optional[str] = None
|
|
23
|
-
git_rev: Optional[str] = None
|
|
24
|
-
git_branch: Optional[str] = None
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# TODO: Rework optional vs required and defaults when currents users are updated
|
|
28
|
-
# and if to be used as a basis for the status api. Both trace_id (for new traces)
|
|
29
|
-
# and trace_event_id are currently generated in status-api.
|
|
30
|
-
class StatusData(BaseModel):
|
|
31
|
-
trace_id: Optional[str] = None # TODO: Generate here as default?
|
|
32
|
-
# trace_event_id: UUID = None # = Field(default_factory=uuid4)
|
|
33
|
-
domain: str = "N/A" # TODO: Temporary default (required)
|
|
34
|
-
domain_id: Optional[str] = None
|
|
35
|
-
start_time: datetime = Field(
|
|
36
|
-
default_factory=lambda: datetime.now(timezone.utc), str=datetime.isoformat
|
|
37
|
-
)
|
|
38
|
-
end_time: datetime = Field(
|
|
39
|
-
default_factory=lambda: datetime.now(timezone.utc), str=datetime.isoformat
|
|
40
|
-
)
|
|
41
|
-
trace_status: TraceStatus = TraceStatus.CONTINUE
|
|
42
|
-
trace_event_status: TraceEventStatus = TraceEventStatus.OK
|
|
43
|
-
user: Optional[str] = None
|
|
44
|
-
component: str = "N/A" # TODO: Temporary default (required)
|
|
45
|
-
operation: Optional[str] = None
|
|
46
|
-
status_body: Optional[Dict] = None
|
|
47
|
-
meta: Optional[StatusMeta] = None
|
|
48
|
-
s3_path: Optional[str] = None
|
|
49
|
-
duration: Optional[int] = None
|
|
50
|
-
exception: Optional[str] = None
|
|
51
|
-
errors: Optional[List] = None
|
|
52
|
-
|
|
53
|
-
class Config:
|
|
54
|
-
validate_assignment = True
|
|
55
|
-
|
|
56
|
-
@validator("exception", pre=True)
|
|
57
|
-
def ensure_exception_data_is_string(cls, v):
|
|
58
|
-
if isinstance(v, Exception):
|
|
59
|
-
return str(v)
|
|
60
|
-
return v
|
|
61
|
-
|
|
62
|
-
@validator("errors", each_item=True)
|
|
63
|
-
def ensure_format_of_errors(cls, v):
|
|
64
|
-
if not isinstance(v, dict):
|
|
65
|
-
raise TypeError(f"{v} is not a dict.")
|
|
66
|
-
if "message" not in v:
|
|
67
|
-
raise ValueError("Missing key 'message'.")
|
|
68
|
-
if not isinstance(v["message"], dict):
|
|
69
|
-
raise TypeError("error['message'] is not a dict.")
|
|
70
|
-
if "nb" not in v["message"]:
|
|
71
|
-
raise ValueError("Missing key 'nb' in error['message'].")
|
|
72
|
-
return v
|
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
from time import sleep
|
|
4
|
-
|
|
5
|
-
from okdata.aws.logging import hide_suffix, log_duration, log_dynamodb, logging_wrapper
|
|
6
|
-
|
|
7
|
-
empty_event = {}
|
|
8
|
-
empty_context = None
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def empty_handler(event, context):
|
|
12
|
-
return {}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def ok_handler(event, context):
|
|
16
|
-
return {"statusCode": 200, "body": "OK"}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def user_error_handler(event, context):
|
|
20
|
-
return {"statusCode": 400, "body": "Bad Request"}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def server_error_handler(event, context):
|
|
24
|
-
return {"statusCode": 500, "body": "Internal Server Error"}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def throwing_handler(event, context):
|
|
28
|
-
raise Exception("fail!")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def timed_operation(raise_exception=False):
|
|
32
|
-
sleep(0.01)
|
|
33
|
-
if raise_exception:
|
|
34
|
-
raise Exception("fail!")
|
|
35
|
-
else:
|
|
36
|
-
return 201
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def timing_handler(event, context):
|
|
40
|
-
result = log_duration(timed_operation, "my_timer")
|
|
41
|
-
return {"statusCode": result}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def throwing_timing_handler(event, context):
|
|
45
|
-
log_duration(lambda: timed_operation(raise_exception=True), "my_timer")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def non_rest_handler(event, context):
|
|
49
|
-
return None
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class RequestContext:
|
|
53
|
-
def __init__(
|
|
54
|
-
self, function_name, function_version, aws_request_id, memory_limit_in_mb
|
|
55
|
-
):
|
|
56
|
-
self.function_name = function_name
|
|
57
|
-
self.function_version = function_version
|
|
58
|
-
self.aws_request_id = aws_request_id
|
|
59
|
-
self.memory_limit_in_mb = memory_limit_in_mb
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def test_log_service_name_from_env(capsys):
|
|
63
|
-
os.environ["SERVICE_NAME"] = "my_other_service"
|
|
64
|
-
|
|
65
|
-
wrapper = logging_wrapper(empty_handler)
|
|
66
|
-
wrapper(empty_event, empty_context)
|
|
67
|
-
|
|
68
|
-
log = json.loads(capsys.readouterr().out)
|
|
69
|
-
|
|
70
|
-
assert log["service_name"] == "my_other_service"
|
|
71
|
-
assert log["handler_method"] == "empty_handler"
|
|
72
|
-
assert log["function_name"] == ""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def test_log_empty_event_and_context(capsys):
|
|
76
|
-
decorator = logging_wrapper(service_name="my_service")
|
|
77
|
-
wrapper = decorator(empty_handler)
|
|
78
|
-
wrapper(empty_event, empty_context)
|
|
79
|
-
|
|
80
|
-
log = json.loads(capsys.readouterr().out)
|
|
81
|
-
|
|
82
|
-
assert log["service_name"] == "my_service"
|
|
83
|
-
assert log["handler_method"] == "empty_handler"
|
|
84
|
-
assert log["function_name"] == ""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def test_log_none_headers(capsys):
|
|
88
|
-
decorator = logging_wrapper(service_name="my_service")
|
|
89
|
-
wrapper = decorator(empty_handler)
|
|
90
|
-
wrapper({"headers": None}, empty_context)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def test_legacy_wrapper(capsys):
|
|
94
|
-
decorator = logging_wrapper("my_old_service")
|
|
95
|
-
wrapper = decorator(empty_handler)
|
|
96
|
-
wrapper(empty_event, empty_context)
|
|
97
|
-
|
|
98
|
-
log = json.loads(capsys.readouterr().out)
|
|
99
|
-
|
|
100
|
-
assert log["service_name"] == "my_old_service"
|
|
101
|
-
assert log["handler_method"] == "empty_handler"
|
|
102
|
-
assert log["function_name"] == ""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def test_handler_name(capsys):
|
|
106
|
-
wrapper = logging_wrapper(empty_handler)
|
|
107
|
-
|
|
108
|
-
assert wrapper.__name__ == empty_handler.__name__
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def test_log_event_data(capsys):
|
|
112
|
-
wrapper = logging_wrapper(empty_handler)
|
|
113
|
-
event = {
|
|
114
|
-
"path": "my_path",
|
|
115
|
-
"pathParameters": {"my_path_param": "my_value"},
|
|
116
|
-
"requestContext": {
|
|
117
|
-
"accountId": "1234567890",
|
|
118
|
-
"stage": "my_stage",
|
|
119
|
-
"apiId": "my_api_id",
|
|
120
|
-
"domainName": "my_domain",
|
|
121
|
-
"identity": {"sourceIp": "1.2.3.4"},
|
|
122
|
-
},
|
|
123
|
-
"resource": "my_resource",
|
|
124
|
-
"httpMethod": "my_method",
|
|
125
|
-
"queryStringParameters": {
|
|
126
|
-
"my_query_param": "my_query_value",
|
|
127
|
-
"token": "secret-stuff",
|
|
128
|
-
"secretToken": "secret-stuff",
|
|
129
|
-
},
|
|
130
|
-
}
|
|
131
|
-
wrapper(event, empty_context)
|
|
132
|
-
|
|
133
|
-
log = json.loads(capsys.readouterr().out)
|
|
134
|
-
|
|
135
|
-
assert log["function_stage"] == "my_stage"
|
|
136
|
-
assert log["function_api_id"] == "my_api_id"
|
|
137
|
-
assert log["aws_account_id"] == "1234567890"
|
|
138
|
-
assert log["source_ip"] == "1.2.3.x"
|
|
139
|
-
assert log["request_domain_name"] == "my_domain"
|
|
140
|
-
assert log["request_resource"] == "my_resource"
|
|
141
|
-
assert log["request_path"] == "my_path"
|
|
142
|
-
assert log["request_method"] == "my_method"
|
|
143
|
-
assert log["request_path_parameters"] == {"my_path_param": "my_value"}
|
|
144
|
-
assert log["request_query_string_parameters"] == {
|
|
145
|
-
"my_query_param": "my_query_value"
|
|
146
|
-
}
|
|
147
|
-
assert event["queryStringParameters"] == {
|
|
148
|
-
"my_query_param": "my_query_value",
|
|
149
|
-
"token": "secret-stuff",
|
|
150
|
-
"secretToken": "secret-stuff",
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def test_log_headers(capsys):
|
|
155
|
-
wrapper = logging_wrapper(empty_handler)
|
|
156
|
-
|
|
157
|
-
event = {"headers": {"X-Amzn-Trace-Id": "my-trace-id"}}
|
|
158
|
-
wrapper(event, empty_context)
|
|
159
|
-
|
|
160
|
-
log = json.loads(capsys.readouterr().out)
|
|
161
|
-
|
|
162
|
-
assert log["aws_trace_id"] == "my-trace-id"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def test_log_context(capsys):
|
|
166
|
-
wrapper = logging_wrapper(empty_handler)
|
|
167
|
-
|
|
168
|
-
context = RequestContext(
|
|
169
|
-
function_name="my_function",
|
|
170
|
-
function_version="my_function_version",
|
|
171
|
-
aws_request_id="my_request_id",
|
|
172
|
-
memory_limit_in_mb=1024,
|
|
173
|
-
)
|
|
174
|
-
wrapper(empty_event, context)
|
|
175
|
-
|
|
176
|
-
log = json.loads(capsys.readouterr().out)
|
|
177
|
-
|
|
178
|
-
assert log["function_name"] == "my_function"
|
|
179
|
-
assert log["function_version"] == "my_function_version"
|
|
180
|
-
assert log["aws_request_id"] == "my_request_id"
|
|
181
|
-
assert log["memory_limit_in_mb"] == 1024
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def test_log_authenticated(capsys):
|
|
185
|
-
event = {"requestContext": {"authorizer": {"principalId": "abc123456"}}}
|
|
186
|
-
wrapper = logging_wrapper(ok_handler)
|
|
187
|
-
wrapper(event, empty_context)
|
|
188
|
-
|
|
189
|
-
log = json.loads(capsys.readouterr().out)
|
|
190
|
-
|
|
191
|
-
assert log["principal_id"] == "abc123xxx"
|
|
192
|
-
assert log["logged_in"] is True
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def test_log_unauthenticated(capsys):
|
|
196
|
-
wrapper = logging_wrapper(ok_handler)
|
|
197
|
-
wrapper(empty_event, empty_context)
|
|
198
|
-
|
|
199
|
-
log = json.loads(capsys.readouterr().out)
|
|
200
|
-
|
|
201
|
-
assert log["logged_in"] is False
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def test_log_response_ok(capsys):
|
|
205
|
-
wrapper = logging_wrapper(ok_handler)
|
|
206
|
-
wrapper(empty_event, empty_context)
|
|
207
|
-
|
|
208
|
-
log = json.loads(capsys.readouterr().out)
|
|
209
|
-
|
|
210
|
-
assert log["response_status_code"] == 200
|
|
211
|
-
assert "response_body" not in log
|
|
212
|
-
assert log["level"] == "info"
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def test_log_user_error(capsys):
|
|
216
|
-
wrapper = logging_wrapper(user_error_handler)
|
|
217
|
-
wrapper(empty_event, empty_context)
|
|
218
|
-
|
|
219
|
-
log = json.loads(capsys.readouterr().out)
|
|
220
|
-
|
|
221
|
-
assert log["response_status_code"] == 400
|
|
222
|
-
assert log["response_body"] == "Bad Request"
|
|
223
|
-
assert log["level"] == "info"
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def test_log_server_error(capsys):
|
|
227
|
-
wrapper = logging_wrapper(server_error_handler)
|
|
228
|
-
wrapper(empty_event, empty_context)
|
|
229
|
-
|
|
230
|
-
log = json.loads(capsys.readouterr().out)
|
|
231
|
-
|
|
232
|
-
assert log["response_status_code"] == 500
|
|
233
|
-
assert log["response_body"] == "Internal Server Error"
|
|
234
|
-
assert log["level"] == "error"
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def test_log_exception(capsys):
|
|
238
|
-
wrapper = logging_wrapper(throwing_handler)
|
|
239
|
-
try:
|
|
240
|
-
wrapper(empty_event, empty_context)
|
|
241
|
-
assert False
|
|
242
|
-
except Exception:
|
|
243
|
-
pass
|
|
244
|
-
|
|
245
|
-
log = json.loads(capsys.readouterr().out)
|
|
246
|
-
|
|
247
|
-
assert log["level"] == "error"
|
|
248
|
-
assert "Exception: fail!" in log["exception"]
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def test_log_duration(capsys):
|
|
252
|
-
wrapper = logging_wrapper(timing_handler)
|
|
253
|
-
wrapper(empty_event, empty_context)
|
|
254
|
-
|
|
255
|
-
log = json.loads(capsys.readouterr().out)
|
|
256
|
-
|
|
257
|
-
assert log["my_timer"] > 10.0
|
|
258
|
-
assert log["response_status_code"] == 201
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def test_log_duration_exception(capsys):
|
|
262
|
-
wrapper = logging_wrapper(throwing_timing_handler)
|
|
263
|
-
try:
|
|
264
|
-
wrapper(empty_event, empty_context)
|
|
265
|
-
assert False
|
|
266
|
-
except Exception:
|
|
267
|
-
pass
|
|
268
|
-
|
|
269
|
-
log = json.loads(capsys.readouterr().out)
|
|
270
|
-
|
|
271
|
-
assert log["my_timer"] > 10.0
|
|
272
|
-
assert log["level"] == "error"
|
|
273
|
-
assert "Exception: fail!" in log["exception"]
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def test_log_non_rest_response(capsys):
|
|
277
|
-
wrapper = logging_wrapper(non_rest_handler)
|
|
278
|
-
wrapper(empty_event, empty_context)
|
|
279
|
-
|
|
280
|
-
log = json.loads(capsys.readouterr().out)
|
|
281
|
-
|
|
282
|
-
assert log["level"] == "info"
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
def test_hide_suffix():
|
|
286
|
-
username = "jon-blund"
|
|
287
|
-
|
|
288
|
-
assert hide_suffix(username) == "jon-blxxx"
|
|
289
|
-
assert hide_suffix(username, 5) == "jon-xxxxx"
|
|
290
|
-
assert hide_suffix(username, 20) == "xxxxxxxxx"
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def dynamodb_handler(event, context):
|
|
294
|
-
dynamodb_response = {"ResponseMetadata": {"HTTPStatusCode": 200}}
|
|
295
|
-
if "Count" in event:
|
|
296
|
-
dynamodb_response["Count"] = event["Count"]
|
|
297
|
-
|
|
298
|
-
log_dynamodb(lambda: dynamodb_response)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
def test_log_dynamodb(capsys):
|
|
302
|
-
wrapper = logging_wrapper(dynamodb_handler)
|
|
303
|
-
wrapper(empty_event, empty_context)
|
|
304
|
-
|
|
305
|
-
log = json.loads(capsys.readouterr().out)
|
|
306
|
-
|
|
307
|
-
assert "dynamodb_duration_ms" in log
|
|
308
|
-
assert log["dynamodb_status_code"] == 200
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
def test_log_dynamodb_item_count(capsys):
|
|
312
|
-
event = {"Count": 123}
|
|
313
|
-
wrapper = logging_wrapper(dynamodb_handler)
|
|
314
|
-
wrapper(event, empty_context)
|
|
315
|
-
|
|
316
|
-
log = json.loads(capsys.readouterr().out)
|
|
317
|
-
|
|
318
|
-
assert log["dynamodb_item_count"] == 123
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from pydantic.error_wrappers import ValidationError
|
|
3
|
-
|
|
4
|
-
from okdata.aws.status.model import StatusData
|
|
5
|
-
|
|
6
|
-
OK_ERROR = {"message": {"nb": "Det er et problem", "en": "There is a problem"}}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TestStatusData:
|
|
10
|
-
def test_errors_entry_valid(self):
|
|
11
|
-
params = {"errors": [OK_ERROR, OK_ERROR]}
|
|
12
|
-
result = StatusData(**params)
|
|
13
|
-
assert isinstance(result, StatusData)
|
|
14
|
-
assert result.errors[0] == OK_ERROR
|
|
15
|
-
|
|
16
|
-
def test_errors_entry_not_dict(self):
|
|
17
|
-
params = {"errors": [OK_ERROR, "This is string, not a dict."]}
|
|
18
|
-
with pytest.raises(ValidationError):
|
|
19
|
-
StatusData(**params)
|
|
20
|
-
|
|
21
|
-
def test_errors_entry_no_message(self):
|
|
22
|
-
params = {"errors": [OK_ERROR, {"Bad key": {}}]}
|
|
23
|
-
with pytest.raises(ValueError):
|
|
24
|
-
StatusData(**params)
|
|
25
|
-
|
|
26
|
-
def test_errors_entry_no_nb(self):
|
|
27
|
-
params = {"errors": [OK_ERROR, {"message": {"Bad key": "foo", "en": "bar"}}]}
|
|
28
|
-
with pytest.raises(ValueError):
|
|
29
|
-
StatusData(**params)
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from copy import deepcopy
|
|
3
|
-
from unittest.mock import patch
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from freezegun import freeze_time
|
|
7
|
-
from okdata.sdk.config import Config
|
|
8
|
-
|
|
9
|
-
from okdata.aws.status.model import StatusData, TraceStatus, TraceEventStatus
|
|
10
|
-
from okdata.aws.status.sdk import Status
|
|
11
|
-
from okdata.aws.status.wrapper import _status_from_lambda_context
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
utc_now = "2020-10-10T08:55:01+00:00"
|
|
15
|
-
trace_id = "my-trace-id"
|
|
16
|
-
mock_token_response = {
|
|
17
|
-
"access_token": "access",
|
|
18
|
-
"refresh_token": "refresh",
|
|
19
|
-
"token_type": "bearer",
|
|
20
|
-
}
|
|
21
|
-
mock_status_data = {
|
|
22
|
-
"trace_id": trace_id,
|
|
23
|
-
"trace_status": "CONTINUE",
|
|
24
|
-
"trace_event_status": "OK",
|
|
25
|
-
"domain": "dataset",
|
|
26
|
-
"domain_id": "my-dataset/1",
|
|
27
|
-
"status_body": None,
|
|
28
|
-
"user": "someuser",
|
|
29
|
-
"component": "system32",
|
|
30
|
-
}
|
|
31
|
-
mock_status_response = {"trace_id": trace_id}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class MockLambdaContext:
|
|
35
|
-
function_name = "my_lambda_function"
|
|
36
|
-
function_version = 123
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@pytest.fixture(scope="function")
|
|
40
|
-
def mock_openid(requests_mock):
|
|
41
|
-
openid_matcher = re.compile("openid-connect")
|
|
42
|
-
requests_mock.register_uri(
|
|
43
|
-
"POST",
|
|
44
|
-
openid_matcher,
|
|
45
|
-
json=mock_token_response,
|
|
46
|
-
status_code=200,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@pytest.fixture(scope="function")
|
|
51
|
-
def mock_status_api(requests_mock):
|
|
52
|
-
matcher = re.compile("mock-status-api")
|
|
53
|
-
requests_mock.register_uri(
|
|
54
|
-
"POST", matcher, json=mock_status_response, status_code=200
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class TestStatusClass:
|
|
59
|
-
def test_status_data_from_trace_id(self):
|
|
60
|
-
s = Status(trace_id)
|
|
61
|
-
assert s.status_data.trace_id == trace_id
|
|
62
|
-
|
|
63
|
-
def test_status_data_from_dict(self):
|
|
64
|
-
s = Status(mock_status_data)
|
|
65
|
-
assert s.status_data.trace_id == trace_id
|
|
66
|
-
|
|
67
|
-
def test_status_data_from_object(self):
|
|
68
|
-
status_data = StatusData.parse_obj(mock_status_data)
|
|
69
|
-
s = Status(status_data)
|
|
70
|
-
assert s.status_data.trace_id == trace_id
|
|
71
|
-
|
|
72
|
-
def test_status_with_sdk_config(self):
|
|
73
|
-
config = Config(config={"foo": "bar"})
|
|
74
|
-
# Mock out the `Authenticate` class so that the SDK doesn't try to set
|
|
75
|
-
# up authentication with a malformed config.
|
|
76
|
-
with patch("okdata.sdk.sdk.Authenticate"):
|
|
77
|
-
s = Status(trace_id, config)
|
|
78
|
-
assert s._sdk.config.config == {"foo": "bar"}
|
|
79
|
-
|
|
80
|
-
def test_status_data_from_lambda(self):
|
|
81
|
-
lambda_context = MockLambdaContext()
|
|
82
|
-
s = _status_from_lambda_context(
|
|
83
|
-
event={
|
|
84
|
-
"requestContext": {"authorizer": {"principalId": "someuser"}},
|
|
85
|
-
"execution_name": trace_id,
|
|
86
|
-
},
|
|
87
|
-
context=lambda_context,
|
|
88
|
-
)
|
|
89
|
-
assert s.trace_id == trace_id
|
|
90
|
-
assert s.meta.function_name == lambda_context.function_name
|
|
91
|
-
assert s.user == "someuser"
|
|
92
|
-
|
|
93
|
-
@freeze_time(utc_now)
|
|
94
|
-
def test_status_done(self, requests_mock, mock_openid, mock_status_api):
|
|
95
|
-
requests_mock.register_uri("POST", f"/status-api/status/{trace_id}", json={})
|
|
96
|
-
s = Status(StatusData.parse_obj(mock_status_data))
|
|
97
|
-
s.done()
|
|
98
|
-
|
|
99
|
-
last_request = requests_mock.last_request
|
|
100
|
-
assert last_request.method == "POST"
|
|
101
|
-
assert last_request.url.endswith(f"/status/{trace_id}")
|
|
102
|
-
|
|
103
|
-
payload = last_request.json()
|
|
104
|
-
assert payload["trace_id"] == trace_id
|
|
105
|
-
assert payload["start_time"] == utc_now
|
|
106
|
-
assert payload["end_time"] == utc_now
|
|
107
|
-
assert payload["domain"] == "dataset"
|
|
108
|
-
assert payload["domain_id"] == "my-dataset/1"
|
|
109
|
-
assert payload["user"] == "someuser"
|
|
110
|
-
assert payload["component"] == "system32"
|
|
111
|
-
assert payload["trace_status"] == TraceStatus.CONTINUE
|
|
112
|
-
assert payload["trace_event_status"] == TraceEventStatus.OK
|
|
113
|
-
|
|
114
|
-
def test_status_optional_fields(self):
|
|
115
|
-
status_data = deepcopy(mock_status_data)
|
|
116
|
-
status_data["domain_id"] = None
|
|
117
|
-
status_data["user"] = None
|
|
118
|
-
s = StatusData.parse_obj(status_data)
|
|
119
|
-
assert s.domain_id is None
|
|
120
|
-
assert s.user is None
|
|
121
|
-
|
|
122
|
-
status_data.pop("domain_id")
|
|
123
|
-
status_data.pop("user")
|
|
124
|
-
s = StatusData.parse_obj(status_data)
|
|
125
|
-
assert s.domain_id is None
|
|
126
|
-
assert s.user is None
|
|
127
|
-
|
|
128
|
-
def test_add_status_data_payload(self, mock_openid, mock_status_api):
|
|
129
|
-
s = Status(mock_status_data)
|
|
130
|
-
s.add(domain_id="my-domain-id")
|
|
131
|
-
assert s.status_data.domain_id == "my-domain-id"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|