clear-skies-aws 1.10.2__py3-none-any.whl → 2.0.1__py3-none-any.whl

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.
Files changed (73) hide show
  1. {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.1.dist-info}/METADATA +36 -35
  2. clear_skies_aws-2.0.1.dist-info/RECORD +4 -0
  3. {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.1.dist-info}/WHEEL +1 -1
  4. clear_skies_aws-2.0.1.dist-info/licenses/LICENSE +21 -0
  5. clear_skies_aws-1.10.2.dist-info/LICENSE +0 -7
  6. clear_skies_aws-1.10.2.dist-info/RECORD +0 -71
  7. clearskies_aws/__init__.py +0 -2
  8. clearskies_aws/actions/__init__.py +0 -108
  9. clearskies_aws/actions/action_aws.py +0 -118
  10. clearskies_aws/actions/assume_role.py +0 -102
  11. clearskies_aws/actions/assume_role_test.py +0 -72
  12. clearskies_aws/actions/ses.py +0 -194
  13. clearskies_aws/actions/ses_test.py +0 -89
  14. clearskies_aws/actions/sns.py +0 -64
  15. clearskies_aws/actions/sns_test.py +0 -77
  16. clearskies_aws/actions/sqs.py +0 -82
  17. clearskies_aws/actions/sqs_test.py +0 -127
  18. clearskies_aws/actions/step_function.py +0 -66
  19. clearskies_aws/actions/step_function_test.py +0 -103
  20. clearskies_aws/backends/__init__.py +0 -12
  21. clearskies_aws/backends/dynamo_db_backend.py +0 -614
  22. clearskies_aws/backends/dynamo_db_backend_test.py +0 -300
  23. clearskies_aws/backends/dynamo_db_condition_parser.py +0 -365
  24. clearskies_aws/backends/dynamo_db_condition_parser_test.py +0 -266
  25. clearskies_aws/backends/dynamo_db_parti_ql_backend.py +0 -1123
  26. clearskies_aws/backends/dynamo_db_parti_ql_backend_test.py +0 -544
  27. clearskies_aws/backends/sqs_backend.py +0 -80
  28. clearskies_aws/backends/sqs_backend_test.py +0 -31
  29. clearskies_aws/contexts/__init__.py +0 -10
  30. clearskies_aws/contexts/cli.py +0 -19
  31. clearskies_aws/contexts/cli_websocket_mock.py +0 -33
  32. clearskies_aws/contexts/lambda_api_gateway.py +0 -30
  33. clearskies_aws/contexts/lambda_api_gateway_web_socket.py +0 -30
  34. clearskies_aws/contexts/lambda_elb.py +0 -30
  35. clearskies_aws/contexts/lambda_http_gateway.py +0 -30
  36. clearskies_aws/contexts/lambda_invocation.py +0 -48
  37. clearskies_aws/contexts/lambda_sns.py +0 -43
  38. clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py +0 -51
  39. clearskies_aws/contexts/lambda_sqs_standard_partial_batch_test.py +0 -66
  40. clearskies_aws/contexts/wsgi.py +0 -19
  41. clearskies_aws/di/__init__.py +0 -1
  42. clearskies_aws/di/standard_dependencies.py +0 -60
  43. clearskies_aws/handlers/__init__.py +0 -2
  44. clearskies_aws/handlers/secrets_manager_rotation.py +0 -174
  45. clearskies_aws/handlers/simple_body_routing.py +0 -39
  46. clearskies_aws/input_outputs/__init__.py +0 -8
  47. clearskies_aws/input_outputs/cli_websocket_mock.py +0 -12
  48. clearskies_aws/input_outputs/lambda_api_gateway.py +0 -105
  49. clearskies_aws/input_outputs/lambda_api_gateway_test.py +0 -87
  50. clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +0 -8
  51. clearskies_aws/input_outputs/lambda_elb.py +0 -21
  52. clearskies_aws/input_outputs/lambda_http_gateway.py +0 -12
  53. clearskies_aws/input_outputs/lambda_invocation.py +0 -34
  54. clearskies_aws/input_outputs/lambda_sns.py +0 -52
  55. clearskies_aws/input_outputs/lambda_sqs_standard.py +0 -54
  56. clearskies_aws/mocks/__init__.py +0 -1
  57. clearskies_aws/mocks/actions/__init__.py +0 -6
  58. clearskies_aws/mocks/actions/ses.py +0 -28
  59. clearskies_aws/mocks/actions/sns.py +0 -23
  60. clearskies_aws/mocks/actions/sqs.py +0 -23
  61. clearskies_aws/mocks/actions/step_function.py +0 -26
  62. clearskies_aws/secrets/__init__.py +0 -7
  63. clearskies_aws/secrets/additional_configs/__init__.py +0 -54
  64. clearskies_aws/secrets/additional_configs/iam_db_auth.py +0 -29
  65. clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +0 -92
  66. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +0 -81
  67. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +0 -141
  68. clearskies_aws/secrets/akeyless_with_ssm_cache.py +0 -46
  69. clearskies_aws/secrets/parameter_store.py +0 -50
  70. clearskies_aws/secrets/parameter_store_test.py +0 -18
  71. clearskies_aws/secrets/secrets_manager.py +0 -75
  72. clearskies_aws/secrets/secrets_manager_test.py +0 -18
  73. clearskies_aws/web_socket_connection_model.py +0 -43
@@ -1,19 +0,0 @@
1
- from ..di import StandardDependencies
2
- from clearskies.contexts.cli import CLI, build_context
3
- def cli(
4
- application,
5
- di_class=StandardDependencies,
6
- bindings=None,
7
- binding_classes=None,
8
- binding_modules=None,
9
- additional_configs=None,
10
- ):
11
- return build_context(
12
- CLI,
13
- application,
14
- di_class=di_class,
15
- bindings=bindings,
16
- binding_classes=binding_classes,
17
- binding_modules=binding_modules,
18
- additional_configs=additional_configs,
19
- )
@@ -1,33 +0,0 @@
1
- import clearskies
2
- import clearskies_aws
3
- from ..input_outputs import CLIWebsocketMock as CLIWebsocketMockInputOutput
4
- from clearskies_aws.contexts.cli import CLI
5
- class CLIWebsocketMock(CLI):
6
- def __init__(self, di):
7
- super().__init__(di)
8
-
9
- def __call__(self):
10
- if self.handler is None:
11
- raise ValueError("Cannot execute CLIWebsocketMock context without first configuring it")
12
-
13
- try:
14
- return self.handler(self.di.build(CLIWebsocketMockInputOutput))
15
- except clearskies.input_outputs.exceptions.CLINotFound:
16
- print("help (aka 404 not found)!")
17
- def cli_websocket_mock(
18
- application,
19
- di_class=clearskies_aws.di.StandardDependencies,
20
- bindings=None,
21
- binding_classes=None,
22
- binding_modules=None,
23
- additional_configs=None,
24
- ):
25
- return clearskies.contexts.build_context(
26
- CLIWebsocketMock,
27
- application,
28
- di_class=di_class,
29
- bindings=bindings,
30
- binding_classes=binding_classes,
31
- binding_modules=binding_modules,
32
- additional_configs=additional_configs,
33
- )
@@ -1,30 +0,0 @@
1
- from ..input_outputs import LambdaAPIGateway as LambdaAPIGatewayInputOutput
2
- from ..di import StandardDependencies
3
- from clearskies.contexts.build_context import build_context
4
- from clearskies.contexts.context import Context
5
- class LambdaAPIGateway(Context):
6
- def __init__(self, di):
7
- super().__init__(di)
8
-
9
- def __call__(self, event, context):
10
- if self.handler is None:
11
- raise ValueError("Cannot execute LambdaAPIGateway context without first configuring it")
12
-
13
- return self.handler(LambdaAPIGatewayInputOutput(event, context))
14
- def lambda_api_gateway(
15
- application,
16
- di_class=StandardDependencies,
17
- bindings=None,
18
- binding_classes=None,
19
- binding_modules=None,
20
- additional_configs=None,
21
- ):
22
- return build_context(
23
- LambdaAPIGateway,
24
- application,
25
- di_class=di_class,
26
- bindings=bindings,
27
- binding_classes=binding_classes,
28
- binding_modules=binding_modules,
29
- additional_configs=additional_configs,
30
- )
@@ -1,30 +0,0 @@
1
- from ..input_outputs import LambdaAPIGatewayWebSocket as LambdaAPIGatewayWebSocketInputOutput
2
- from ..di import StandardDependencies
3
- from clearskies.contexts.build_context import build_context
4
- from clearskies.contexts.context import Context
5
- class LambdaAPIGatewayWebSocket(Context):
6
- def __init__(self, di):
7
- super().__init__(di)
8
-
9
- def __call__(self, event, context):
10
- if self.handler is None:
11
- raise ValueError("Cannot execute LambdaAPIGatewayWebSocket context without first configuring it")
12
-
13
- return self.handler(LambdaAPIGatewayWebSocketInputOutput(event, context))
14
- def lambda_api_gateway_web_socket(
15
- application,
16
- di_class=StandardDependencies,
17
- bindings=None,
18
- binding_classes=None,
19
- binding_modules=None,
20
- additional_configs=None,
21
- ):
22
- return build_context(
23
- LambdaAPIGatewayWebSocket,
24
- application,
25
- di_class=di_class,
26
- bindings=bindings,
27
- binding_classes=binding_classes,
28
- binding_modules=binding_modules,
29
- additional_configs=additional_configs,
30
- )
@@ -1,30 +0,0 @@
1
- from ..input_outputs import LambdaELB as LambdaELBInputOutput
2
- from ..di import StandardDependencies
3
- from clearskies.contexts.build_context import build_context
4
- from clearskies.contexts.context import Context
5
- class LambdaELB(Context):
6
- def __init__(self, di):
7
- super().__init__(di)
8
-
9
- def __call__(self, event, context):
10
- if self.handler is None:
11
- raise ValueError("Cannot execute LambdaELB context without first configuring it")
12
-
13
- return self.handler(LambdaELBInputOutput(event, context))
14
- def lambda_elb(
15
- application,
16
- di_class=StandardDependencies,
17
- bindings=None,
18
- binding_classes=None,
19
- binding_modules=None,
20
- additional_configs=None,
21
- ):
22
- return build_context(
23
- LambdaELB,
24
- application,
25
- di_class=di_class,
26
- bindings=bindings,
27
- binding_classes=binding_classes,
28
- binding_modules=binding_modules,
29
- additional_configs=additional_configs,
30
- )
@@ -1,30 +0,0 @@
1
- from ..input_outputs import LambdaHTTPGateway as LambdaHTTPGatewayInputOutput
2
- from ..di import StandardDependencies
3
- from clearskies.contexts.build_context import build_context
4
- from clearskies.contexts.context import Context
5
- class LambdaHTTPGateway(Context):
6
- def __init__(self, di):
7
- super().__init__(di)
8
-
9
- def __call__(self, event, context):
10
- if self.handler is None:
11
- raise ValueError("Cannot execute LambdaHTTPGateway context without first configuring it")
12
-
13
- return self.handler(LambdaHTTPGatewayInputOutput(event, context))
14
- def lambda_http_gateway(
15
- application,
16
- di_class=StandardDependencies,
17
- bindings=None,
18
- binding_classes=None,
19
- binding_modules=None,
20
- additional_configs=None,
21
- ):
22
- return build_context(
23
- LambdaHTTPGateway,
24
- application,
25
- di_class=di_class,
26
- bindings=bindings,
27
- binding_classes=binding_classes,
28
- binding_modules=binding_modules,
29
- additional_configs=additional_configs,
30
- )
@@ -1,48 +0,0 @@
1
- from clearskies.authentication import public
2
- from ..input_outputs import LambdaInvocation as LambdaInvocationInputOutput
3
- from ..di import StandardDependencies
4
- from clearskies.contexts.build_context import build_context
5
- from clearskies.contexts.context import Context
6
- class LambdaInvocation(Context):
7
- def __init__(self, di):
8
- super().__init__(di)
9
-
10
- def finalize_handler_config(self, config):
11
- return {
12
- 'authentication': public(),
13
- **config,
14
- }
15
-
16
- def __call__(
17
- self,
18
- event,
19
- context,
20
- method=None,
21
- url=None,
22
- ):
23
- if self.handler is None:
24
- raise ValueError("Cannot execute LambdaInvocation context without first configuring it")
25
-
26
- return self.handler(LambdaInvocationInputOutput(
27
- event,
28
- context,
29
- method=method,
30
- url=url,
31
- ))
32
- def lambda_invocation(
33
- application,
34
- di_class=StandardDependencies,
35
- bindings=None,
36
- binding_classes=None,
37
- binding_modules=None,
38
- additional_configs=None,
39
- ):
40
- return build_context(
41
- LambdaInvocation,
42
- application,
43
- di_class=di_class,
44
- bindings=bindings,
45
- binding_classes=binding_classes,
46
- binding_modules=binding_modules,
47
- additional_configs=additional_configs,
48
- )
@@ -1,43 +0,0 @@
1
- from ..input_outputs import LambdaSns as LambdaSnsInputOutput
2
- from ..di import StandardDependencies
3
- from clearskies.contexts.build_context import build_context
4
- from clearskies.contexts.context import Context
5
- from clearskies.authentication import public
6
-
7
- class LambdaSns(Context):
8
- def __init__(self, di):
9
- super().__init__(di)
10
-
11
- def finalize_handler_config(self, config):
12
- return {
13
- 'authentication': public(),
14
- **config,
15
- }
16
-
17
- def __call__(self, event, context, method=None, url=None):
18
- if self.handler is None:
19
- raise ValueError("Cannot execute LambdaSnsEvent context without first configuring it")
20
-
21
- try:
22
- return self.handler(LambdaSnsInputOutput(event, context, method=method, url=url))
23
- except Exception as e:
24
- print('Failed message ' + event['Records'][0]['Sns']['MessageId'] + '. Error error: ' + str(e))
25
- raise e
26
-
27
- def lambda_sns(
28
- application,
29
- di_class=StandardDependencies,
30
- bindings=None,
31
- binding_classes=None,
32
- binding_modules=None,
33
- additional_configs=None,
34
- ):
35
- return build_context(
36
- LambdaSns,
37
- application,
38
- di_class=di_class,
39
- bindings=bindings,
40
- binding_classes=binding_classes,
41
- binding_modules=binding_modules,
42
- additional_configs=additional_configs,
43
- )
@@ -1,51 +0,0 @@
1
- import traceback
2
- from ..input_outputs import LambdaSqsStandard as LambdaSqsStandardInputOutput
3
- from ..di import StandardDependencies
4
- from clearskies.contexts.build_context import build_context
5
- from clearskies.contexts.context import Context
6
- from clearskies.authentication import public
7
- class LambdaSqsStandardPartialBatch(Context):
8
- def __init__(self, di):
9
- super().__init__(di)
10
-
11
- def finalize_handler_config(self, config):
12
- return {
13
- 'authentication': public(),
14
- **config,
15
- }
16
-
17
- def __call__(self, event, context, url=None, method=None):
18
- if self.handler is None:
19
- raise ValueError("Cannot execute LambdaELB context without first configuring it")
20
-
21
- item_failures = []
22
- for record in event['Records']:
23
- try:
24
- self.handler(LambdaSqsStandardInputOutput(record['body'], event, context, url=url, method=method))
25
- except Exception as e:
26
- print('Failed message ' + record['messageId'] + ' being returned for retry. Error error: ' + str(e))
27
- traceback.print_tb(e.__traceback__)
28
- item_failures.append({'itemIdentifier': record['messageId']})
29
-
30
- if item_failures:
31
- return {
32
- "batchItemFailures": item_failures,
33
- }
34
- return {}
35
- def lambda_sqs_standard_partial_batch(
36
- application,
37
- di_class=StandardDependencies,
38
- bindings=None,
39
- binding_classes=None,
40
- binding_modules=None,
41
- additional_configs=None,
42
- ):
43
- return build_context(
44
- LambdaSqsStandardPartialBatch,
45
- application,
46
- di_class=di_class,
47
- bindings=bindings,
48
- binding_classes=binding_classes,
49
- binding_modules=binding_modules,
50
- additional_configs=additional_configs,
51
- )
@@ -1,66 +0,0 @@
1
- import json
2
- import unittest
3
- from types import SimpleNamespace
4
- from unittest.mock import MagicMock, call
5
-
6
- from .lambda_sqs_standard_partial_batch import lambda_sqs_standard_partial_batch
7
-
8
-
9
- class LambdaSqsStandardPartialBatchTest(unittest.TestCase):
10
- def setUp(self):
11
- self.calls = []
12
-
13
- def my_callable(self, request_data):
14
- if 'boom' in request_data:
15
- raise ValueError('oops')
16
- self.calls.append(request_data)
17
-
18
- def test_simple_execution(self):
19
- sqs_handler = lambda_sqs_standard_partial_batch(self.my_callable)
20
- sqs_handler(
21
- {
22
- 'Records': [
23
- {
24
- 'messageId': '1-2-3-4',
25
- 'body': json.dumps({'hey': 'sup'}),
26
- },
27
- {
28
- 'messageId': '2-3-4-5',
29
- 'body': json.dumps({'cool': 'yo'}),
30
- },
31
- ]
32
- },
33
- {},
34
- )
35
- self.assertEqual(
36
- [
37
- {"hey": "sup"},
38
- {"cool": "yo"},
39
- ],
40
- self.calls,
41
- )
42
-
43
- def test_with_failure(self):
44
- sqs_handler = lambda_sqs_standard_partial_batch(self.my_callable)
45
- results = sqs_handler({
46
- 'Records': [
47
- {
48
- 'messageId': '1-2-3-4',
49
- 'body': json.dumps({'hey': 'sup'}),
50
- },
51
- {
52
- 'messageId': '2-3-4-5',
53
- 'body': json.dumps({'boom': 'yo'}),
54
- },
55
- ]
56
- }, {})
57
- self.assertEqual(
58
- [
59
- {"hey": "sup"},
60
- ],
61
- self.calls,
62
- )
63
- self.assertEqual(
64
- {"batchItemFailures": [{"itemIdentifier": "2-3-4-5"}]},
65
- results,
66
- )
@@ -1,19 +0,0 @@
1
- from ..di import StandardDependencies
2
- from clearskies.contexts.wsgi import WSGI, build_context
3
- def wsgi(
4
- application,
5
- di_class=StandardDependencies,
6
- bindings=None,
7
- binding_classes=None,
8
- binding_modules=None,
9
- additional_configs=None,
10
- ):
11
- return build_context(
12
- WSGI,
13
- application,
14
- di_class=di_class,
15
- bindings=bindings,
16
- binding_classes=binding_classes,
17
- binding_modules=binding_modules,
18
- additional_configs=additional_configs,
19
- )
@@ -1 +0,0 @@
1
- from .standard_dependencies import StandardDependencies
@@ -1,60 +0,0 @@
1
- from types import ModuleType
2
-
3
- import boto3 as boto3_module
4
- from boto3.session import Session as Boto3Session
5
- from clearskies import Environment
6
- from clearskies.di import StandardDependencies as DefaultStandardDependencies
7
-
8
- from ..backends import (
9
- DynamoDBBackend,
10
- DynamoDBPartiQLBackend,
11
- DynamoDBPartiQLCursor,
12
- SqsBackend,
13
- )
14
- from ..secrets import ParameterStore
15
-
16
-
17
- class StandardDependencies(DefaultStandardDependencies):
18
-
19
- def provide_dynamo_db_parti_ql_cursor(
20
- self, boto3_session: Boto3Session
21
- ) -> DynamoDBPartiQLCursor:
22
- return DynamoDBPartiQLCursor(boto3_session)
23
-
24
- def provide_dynamo_db_backend(
25
- self, boto3: ModuleType, environment: Environment
26
- ) -> DynamoDBBackend:
27
- return DynamoDBBackend(boto3, environment)
28
-
29
- def provide_dynamo_db_parti_ql_backend(
30
- self, dynamo_db_parti_ql_cursor: DynamoDBPartiQLCursor, environment: Environment
31
- ) -> DynamoDBPartiQLBackend:
32
- return DynamoDBPartiQLBackend(dynamo_db_parti_ql_cursor)
33
-
34
- def provide_sqs_backend(
35
- self, boto3: ModuleType, environment: Environment
36
- ) -> SqsBackend:
37
- return SqsBackend(boto3, environment)
38
-
39
- def provide_boto3(self) -> ModuleType:
40
- import boto3
41
- return boto3
42
-
43
- def provide_secrets(
44
- self, boto3: ModuleType, environment: Environment
45
- ) -> ParameterStore:
46
- # This is just here so that we can auto-inject the secrets into the environment without having
47
- # to force the developer to define a secrets manager
48
- return ParameterStore(boto3, environment)
49
-
50
- def provide_boto3_session(
51
- self, boto3: ModuleType, environment: Environment
52
- ) -> boto3_module.session.Session:
53
-
54
- if not environment.get("AWS_REGION", True):
55
- raise ValueError(
56
- "To use AWS Session you must use set AWS_REGION in the .env file or an environment variable"
57
- )
58
-
59
- session = boto3.session.Session(region_name=environment.get("AWS_REGION", True))
60
- return session
@@ -1,2 +0,0 @@
1
- from .simple_body_routing import SimpleBodyRouting
2
- from .secrets_manager_rotation import SecretsManagerRotation
@@ -1,174 +0,0 @@
1
- import json
2
-
3
- import clearskies
4
- from clearskies.handlers.exceptions import ClientError
5
- from clearskies.handlers.base import Base
6
- import botocore
7
-
8
- class SecretsManagerRotation(Base, clearskies.handlers.SchemaHelper):
9
- _steps = ["createSecret", "setSecret", "testSecret", "finishSecret"]
10
-
11
- current = "AWSCURRENT"
12
- pending = "AWSPENDING"
13
-
14
- _configuration_defaults = {
15
- "createSecret": None,
16
- "setSecret": None,
17
- "testSecret": None,
18
- "finishSecret": None,
19
- "schema": [],
20
- }
21
-
22
- def __init__(self, boto3, di):
23
- super().__init__(di)
24
- self.boto3 = boto3
25
-
26
- def _check_configuration(self, configuration):
27
- super()._check_configuration(configuration)
28
- class_name = self.__class__.__name__
29
- if not configuration.get("createSecret"):
30
- raise KeyError(f"Missing required configuration 'createSecret' for handler {class_name}")
31
-
32
- for config_name in self._steps:
33
- config = configuration.get(config_name)
34
- if config is None:
35
- continue
36
- if not callable(config):
37
- raise ValueError(f"Misconfiguration for handler {class_name}: configuration '{config_name}' is not callable")
38
-
39
- if configuration.get("schema") is not None:
40
- self._check_schema(configuration["schema"], None, f"Misconfiguration for handler {class_name}")
41
-
42
- def _finalize_configuration(self, configuration):
43
- if configuration.get('schema'):
44
- configuration['schema'] = self._schema_to_columns(configuration['schema'])
45
- return super()._finalize_configuration(configuration)
46
-
47
- def handle(self, input_output):
48
- request_data = input_output.json_body()
49
-
50
- arn = request_data.get('SecretId')
51
- request_token = request_data.get('ClientRequestToken')
52
- step = request_data.get('Step')
53
- secretsmanager = self.boto3.client('secretsmanager')
54
- metadata = secretsmanager.describe_secret(SecretId=arn)
55
-
56
- self._validate_secret_and_request(step, arn, metadata, request_token)
57
-
58
- current_secret_data = {}
59
- pending_secret_data = {}
60
-
61
- current_secret = secretsmanager.get_secret_value(SecretId=arn, VersionStage=self.current)
62
- current_secret_data = json.loads(current_secret['SecretString'])
63
-
64
- # validate the current secret
65
- secret_errors = {
66
- **self._extra_column_errors(current_secret_data),
67
- **self._find_input_errors(current_secret_data),
68
- }
69
- if secret_errors:
70
- raise ValueError(f"The current secret did not match the configured schema: {secret_errors}")
71
-
72
- # check for a pending secret. Note that this is not always available. In the event that we are retrying a failed
73
- # rotation it will already be set, in which case we need to skip the createSecret step.
74
- try:
75
- pending_secret = secretsmanager.get_secret_value(SecretId=arn, VersionId=request_token, VersionStage=self.pending)
76
- pending_secret_data = json.loads(pending_secret['SecretString'])
77
- except botocore.exceptions.ClientError as error:
78
- if error.response['Error']['Code'] == 'ResourceNotFoundException':
79
- pending_secret_data = None
80
- else:
81
- raise error
82
-
83
- # we can't call the createSecret step if we already have a pending secret or this will generate an error from AWS.
84
- if step == "createSecret" and pending_secret_data is not None:
85
- return
86
-
87
- # call the appropriate step and pass along *everything*.
88
- getattr(self, step)(
89
- current_secret_data=current_secret_data,
90
- pending_secret_data=pending_secret_data,
91
- secretsmanager=secretsmanager,
92
- metadata=metadata,
93
- request_token=request_token,
94
- arn=arn,
95
- )
96
-
97
- def _validate_secret_and_request(self, step, arn, metadata, request_token):
98
- """ This function does some basic checks suggested by AWS of both the request and the secret to make sure everything is on the up-and-up. """
99
- if step not in self._steps:
100
- raise ClientError(f"Invalid step: {step}")
101
-
102
- if not metadata.get('RotationEnabled'):
103
- raise ValueError("Secret %s is not enabled for rotation" % arn)
104
-
105
- versions = metadata["VersionIdsToStages"]
106
- prefix = f"Rotation config error for version '{request_token}' of secret '{arn}': "
107
- if request_token not in versions:
108
- raise ValueError(f"{prefix} we don't have a stage for rotation")
109
- if self.current in versions[request_token]:
110
- raise ValueError(f"{prefix} it's already the current version, which shouldn't happen. I'm quitting with prejudice.")
111
- elif self.pending not in versions[request_token]:
112
- raise ValueError(f"{prefix} it hasn't been set to pending yet, which makes no sense!")
113
-
114
- def createSecret(self, **kwargs):
115
- new_secret_data = self._di.call_function(self._configuration["createSecret"], **kwargs)
116
- if new_secret_data is None:
117
- raise ValueError(f"I called the configured createSecret function but it didn't return anything. It has to return the new secret data.")
118
- if not isinstance(new_secret_data, dict):
119
- raise ValueError(f"I called the configured createSecret function but it didn't return a dictionary. The createSecret function must return a dictionary.")
120
-
121
- secret_errors = {
122
- **self._extra_column_errors(new_secret_data),
123
- **self._find_input_errors(new_secret_data),
124
- }
125
- if secret_errors:
126
- raise ValueError(f"The secret data returned by the call to createSecret did not match the configured schema: {secret_errors}")
127
-
128
- # if we get this far we can store the new data
129
- secretsmanager = kwargs["secretsmanager"]
130
- request_token = kwargs["request_token"]
131
- arn = kwargs["arn"]
132
- secretsmanager.put_secret_value(
133
- SecretId=arn,
134
- SecretString=json.dumps(new_secret_data),
135
- ClientRequestToken=request_token,
136
- VersionStages=[self.pending],
137
- )
138
-
139
- def setSecret(self, **kwargs):
140
- if not self._configuration.get("setSecret"):
141
- return
142
- self._di.call_function(self._configuration["setSecret"], **kwargs)
143
-
144
- def testSecret(self, **kwargs):
145
- if not self._configuration.get("testSecret"):
146
- return
147
- self._di.call_function(self._configuration["testSecret"], **kwargs)
148
-
149
- def finishSecret(self, **kwargs):
150
- if self._configuration.get("finishSecret"):
151
- self._di.call_function(self._configuration["finishSecret"], **kwargs)
152
-
153
- secretsmanager = kwargs["secretsmanager"]
154
- request_token = kwargs["request_token"]
155
- arn = kwargs["arn"]
156
- metadata = kwargs["metadata"]
157
- current_version = None
158
- for version in metadata["VersionIdsToStages"]:
159
- if self.current not in metadata["VersionIdsToStages"][version]:
160
- continue
161
-
162
- if version == request_token:
163
- return
164
-
165
- current_version = version
166
- break
167
-
168
- # finish the rotation by taking the new version and making it current.
169
- secretsmanager.update_secret_version_stage(
170
- SecretId=arn,
171
- VersionStage=self.current,
172
- MoveToVersionId=request_token,
173
- RemoveFromVersionId=current_version
174
- )