runbooks 0.7.0__py3-none-any.whl → 0.7.6__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 (132) hide show
  1. runbooks/__init__.py +87 -37
  2. runbooks/cfat/README.md +300 -49
  3. runbooks/cfat/__init__.py +2 -2
  4. runbooks/finops/__init__.py +1 -1
  5. runbooks/finops/cli.py +1 -1
  6. runbooks/inventory/collectors/__init__.py +8 -0
  7. runbooks/inventory/collectors/aws_management.py +791 -0
  8. runbooks/inventory/collectors/aws_networking.py +3 -3
  9. runbooks/main.py +3389 -782
  10. runbooks/operate/__init__.py +207 -0
  11. runbooks/operate/base.py +311 -0
  12. runbooks/operate/cloudformation_operations.py +619 -0
  13. runbooks/operate/cloudwatch_operations.py +496 -0
  14. runbooks/operate/dynamodb_operations.py +812 -0
  15. runbooks/operate/ec2_operations.py +926 -0
  16. runbooks/operate/iam_operations.py +569 -0
  17. runbooks/operate/s3_operations.py +1211 -0
  18. runbooks/operate/tagging_operations.py +655 -0
  19. runbooks/remediation/CLAUDE.md +100 -0
  20. runbooks/remediation/DOME9.md +218 -0
  21. runbooks/remediation/README.md +26 -0
  22. runbooks/remediation/Tests/__init__.py +0 -0
  23. runbooks/remediation/Tests/update_policy.py +74 -0
  24. runbooks/remediation/__init__.py +95 -0
  25. runbooks/remediation/acm_cert_expired_unused.py +98 -0
  26. runbooks/remediation/acm_remediation.py +875 -0
  27. runbooks/remediation/api_gateway_list.py +167 -0
  28. runbooks/remediation/base.py +643 -0
  29. runbooks/remediation/cloudtrail_remediation.py +908 -0
  30. runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
  31. runbooks/remediation/cognito_active_users.py +78 -0
  32. runbooks/remediation/cognito_remediation.py +856 -0
  33. runbooks/remediation/cognito_user_password_reset.py +163 -0
  34. runbooks/remediation/commons.py +455 -0
  35. runbooks/remediation/dynamodb_optimize.py +155 -0
  36. runbooks/remediation/dynamodb_remediation.py +744 -0
  37. runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
  38. runbooks/remediation/ec2_public_ips.py +134 -0
  39. runbooks/remediation/ec2_remediation.py +892 -0
  40. runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
  41. runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
  42. runbooks/remediation/ec2_unused_security_groups.py +202 -0
  43. runbooks/remediation/kms_enable_key_rotation.py +651 -0
  44. runbooks/remediation/kms_remediation.py +717 -0
  45. runbooks/remediation/lambda_list.py +243 -0
  46. runbooks/remediation/lambda_remediation.py +971 -0
  47. runbooks/remediation/multi_account.py +569 -0
  48. runbooks/remediation/rds_instance_list.py +199 -0
  49. runbooks/remediation/rds_remediation.py +873 -0
  50. runbooks/remediation/rds_snapshot_list.py +192 -0
  51. runbooks/remediation/requirements.txt +118 -0
  52. runbooks/remediation/s3_block_public_access.py +159 -0
  53. runbooks/remediation/s3_bucket_public_access.py +143 -0
  54. runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
  55. runbooks/remediation/s3_downloader.py +215 -0
  56. runbooks/remediation/s3_enable_access_logging.py +562 -0
  57. runbooks/remediation/s3_encryption.py +526 -0
  58. runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
  59. runbooks/remediation/s3_list.py +141 -0
  60. runbooks/remediation/s3_object_search.py +201 -0
  61. runbooks/remediation/s3_remediation.py +816 -0
  62. runbooks/remediation/scan_for_phrase.py +425 -0
  63. runbooks/remediation/workspaces_list.py +220 -0
  64. runbooks/security/__init__.py +9 -10
  65. runbooks/security/security_baseline_tester.py +4 -2
  66. runbooks-0.7.6.dist-info/METADATA +608 -0
  67. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
  68. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
  69. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
  70. jupyter-agent/.env +0 -2
  71. jupyter-agent/.env.template +0 -2
  72. jupyter-agent/.gitattributes +0 -35
  73. jupyter-agent/.gradio/certificate.pem +0 -31
  74. jupyter-agent/README.md +0 -16
  75. jupyter-agent/__main__.log +0 -8
  76. jupyter-agent/app.py +0 -256
  77. jupyter-agent/cloudops-agent.png +0 -0
  78. jupyter-agent/ds-system-prompt.txt +0 -154
  79. jupyter-agent/jupyter-agent.png +0 -0
  80. jupyter-agent/llama3_template.jinja +0 -123
  81. jupyter-agent/requirements.txt +0 -9
  82. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
  83. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
  84. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
  85. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
  86. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
  87. jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
  88. jupyter-agent/utils.py +0 -409
  89. runbooks/aws/__init__.py +0 -58
  90. runbooks/aws/dynamodb_operations.py +0 -231
  91. runbooks/aws/ec2_copy_image_cross-region.py +0 -195
  92. runbooks/aws/ec2_describe_instances.py +0 -202
  93. runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
  94. runbooks/aws/ec2_run_instances.py +0 -213
  95. runbooks/aws/ec2_start_stop_instances.py +0 -212
  96. runbooks/aws/ec2_terminate_instances.py +0 -143
  97. runbooks/aws/ec2_unused_eips.py +0 -196
  98. runbooks/aws/ec2_unused_volumes.py +0 -188
  99. runbooks/aws/s3_create_bucket.py +0 -142
  100. runbooks/aws/s3_list_buckets.py +0 -152
  101. runbooks/aws/s3_list_objects.py +0 -156
  102. runbooks/aws/s3_object_operations.py +0 -183
  103. runbooks/aws/tagging_lambda_handler.py +0 -183
  104. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
  105. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
  106. runbooks/inventory/aws_organization.png +0 -0
  107. runbooks/inventory/cfn_move_stack_instances.py +0 -1526
  108. runbooks/inventory/delete_s3_buckets_objects.py +0 -169
  109. runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
  110. runbooks/inventory/update_aws_actions.py +0 -173
  111. runbooks/inventory/update_cfn_stacksets.py +0 -1215
  112. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
  113. runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
  114. runbooks/inventory/update_s3_public_access_block.py +0 -539
  115. runbooks/organizations/__init__.py +0 -12
  116. runbooks/organizations/manager.py +0 -374
  117. runbooks-0.7.0.dist-info/METADATA +0 -375
  118. /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
  119. /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
  120. /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
  121. /runbooks/inventory/{tests → Tests}/setup.py +0 -0
  122. /runbooks/inventory/{tests → Tests}/src.py +0 -0
  123. /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
  124. /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
  125. /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
  126. /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
  127. /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
  128. /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
  129. /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
  130. /runbooks/{aws → operate}/tags.json +0 -0
  131. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
  132. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
jupyter-agent/utils.py DELETED
@@ -1,409 +0,0 @@
1
- """
2
- utils.py
3
-
4
- This module provides helper functions to:
5
- - Generate and update Jupyter Notebook structures.
6
- - Execute code in a sandbox environment.
7
- - Parse and convert execution results to notebook cell outputs.
8
- - Export notebooks to HTML.
9
- """
10
-
11
- import json
12
- from pathlib import Path
13
- from typing import Any, Dict, List, Tuple
14
-
15
- import nbformat
16
- from e2b_code_interpreter import Sandbox
17
- from huggingface_hub import InferenceClient
18
- from nbconvert import HTMLExporter
19
- from nbformat.v4 import new_code_cell, new_markdown_cell, new_notebook
20
- from traitlets.config import Config
21
- from transformers import AutoTokenizer
22
-
23
- ## --- Global Configuration and Template Loading ---
24
-
25
- ## Create a configuration for nbconvert and set up the HTML exporter
26
- config = Config()
27
- html_exporter = HTMLExporter(config=config, template_name="classic")
28
-
29
- ## Load the Jinja template for the LLaMA model
30
- TEMPLATE_PATH = Path("llama3_template.jinja")
31
- try:
32
- with TEMPLATE_PATH.open("r", encoding="utf-8") as f:
33
- llama_template = f.read()
34
- except FileNotFoundError:
35
- raise FileNotFoundError(f"Template file {TEMPLATE_PATH} not found.")
36
-
37
- MAX_TURNS = 4
38
-
39
- ## --- Code Execution Functions ---
40
-
41
-
42
- def parse_exec_result_nb(execution: Any) -> List[Dict[str, Any]]:
43
- """
44
- Convert an E2B execution object to a list of Jupyter notebook cell outputs format.
45
-
46
- :param execution: Execution object from the sandbox.
47
- :return: List of output dictionaries.
48
- """
49
- outputs: List[Dict[str, Any]] = []
50
-
51
- if execution.logs.stdout:
52
- outputs.append(
53
- {
54
- "output_type": "stream",
55
- "name": "stdout",
56
- "text": "".join(execution.logs.stdout),
57
- }
58
- )
59
-
60
- if execution.logs.stderr:
61
- outputs.append(
62
- {
63
- "output_type": "stream",
64
- "name": "stderr",
65
- "text": "".join(execution.logs.stderr),
66
- }
67
- )
68
-
69
- if execution.error:
70
- outputs.append(
71
- {
72
- "output_type": "error",
73
- "ename": execution.error.name,
74
- "evalue": execution.error.value,
75
- "traceback": [line for line in execution.error.traceback.split("\n")],
76
- }
77
- )
78
-
79
- for result in execution.results:
80
- output = {
81
- "output_type": (
82
- "execute_result" if result.is_main_result else "display_data"
83
- ),
84
- "metadata": {},
85
- "data": {},
86
- }
87
-
88
- if result.text:
89
- output["data"]["text/plain"] = [result.text] # Array for text/plain
90
- if result.html:
91
- output["data"]["text/html"] = result.html
92
- if result.png:
93
- output["data"]["image/png"] = result.png
94
- if result.svg:
95
- output["data"]["image/svg+xml"] = result.svg
96
- if result.jpeg:
97
- output["data"]["image/jpeg"] = result.jpeg
98
- if result.pdf:
99
- output["data"]["application/pdf"] = result.pdf
100
- if result.latex:
101
- output["data"]["text/latex"] = result.latex
102
- if result.json:
103
- output["data"]["application/json"] = result.json
104
- if result.javascript:
105
- output["data"]["application/javascript"] = result.javascript
106
-
107
- if result.is_main_result and execution.execution_count is not None:
108
- output["execution_count"] = execution.execution_count
109
-
110
- if output["data"]:
111
- outputs.append(output)
112
-
113
- return outputs
114
-
115
-
116
- ## HTML and CSS templates for notebook cells
117
- system_template = """\
118
- <details>
119
- <summary style="display: flex; align-items: center;">
120
- <div class="alert alert-block alert-info" style="margin: 0; width: 100%;">
121
- <b>System: <span class="arrow">▶</span></b>
122
- </div>
123
- </summary>
124
- <div class="alert alert-block alert-info">
125
- {}
126
- </div>
127
- </details>
128
-
129
- <style>
130
- details > summary .arrow {{
131
- display: inline-block;
132
- transition: transform 0.2s;
133
- }}
134
- details[open] > summary .arrow {{
135
- transform: rotate(90deg);
136
- }}
137
- </style>
138
- """
139
-
140
- user_template = """<div class="alert alert-block alert-success">
141
- <b>User:</b> {}
142
- </div>
143
- """
144
-
145
- header_message = """<p align="center">
146
- <img src="cloudops-agent.png" alt="Jupyter Agent" />
147
- </p>
148
-
149
-
150
- <p style="text-align:center;">Let a LLM agent write and execute code inside a notebook!</p>"""
151
-
152
- bad_html_bad = """input[type="file"] {
153
- display: block;
154
- }"""
155
-
156
-
157
- ## --- Notebook Creation and Update Functions ---
158
-
159
-
160
- def create_base_notebook(messages: List[Dict[str, Any]]) -> Tuple[Dict[str, Any], int]:
161
- """
162
- Create the base Jupyter Notebook structure with initial cells.
163
-
164
- :param messages: List of conversation messages.
165
- :return: A tuple of the notebook data dictionary and the current code cell counter.
166
- """
167
- base_notebook = {
168
- "metadata": {
169
- "kernel_info": {"name": "python3"},
170
- "language_info": {
171
- "name": "python",
172
- "version": "3.12",
173
- },
174
- },
175
- "nbformat": 4,
176
- "nbformat_minor": 0,
177
- "cells": [],
178
- }
179
- base_notebook["cells"].append(
180
- {"cell_type": "markdown", "metadata": {}, "source": header_message}
181
- )
182
-
183
- if len(messages) == 0:
184
- base_notebook["cells"].append(
185
- {
186
- "cell_type": "code",
187
- "execution_count": None,
188
- "metadata": {},
189
- "source": "",
190
- "outputs": [],
191
- }
192
- )
193
-
194
- code_cell_counter = 0
195
-
196
- for message in messages:
197
- if message["role"] == "system":
198
- text = system_template.format(message["content"].replace("\n", "<br>"))
199
- base_notebook["cells"].append(
200
- {"cell_type": "markdown", "metadata": {}, "source": text}
201
- )
202
- elif message["role"] == "user":
203
- text = user_template.format(message["content"].replace("\n", "<br>"))
204
- base_notebook["cells"].append(
205
- {"cell_type": "markdown", "metadata": {}, "source": text}
206
- )
207
-
208
- elif message["role"] == "assistant" and "tool_calls" in message:
209
- base_notebook["cells"].append(
210
- {
211
- "cell_type": "code",
212
- "execution_count": None,
213
- "metadata": {},
214
- "source": message["content"],
215
- "outputs": [],
216
- }
217
- )
218
-
219
- elif message["role"] == "ipython":
220
- code_cell_counter += 1
221
- base_notebook["cells"][-1]["outputs"] = message["nbformat"]
222
- base_notebook["cells"][-1]["execution_count"] = code_cell_counter
223
-
224
- elif message["role"] == "assistant" and "tool_calls" not in message:
225
- base_notebook["cells"].append(
226
- {"cell_type": "markdown", "metadata": {}, "source": message["content"]}
227
- )
228
-
229
- else:
230
- raise ValueError(message)
231
-
232
- return base_notebook, code_cell_counter
233
-
234
-
235
- def execute_code(sbx: Sandbox, code: str) -> Tuple[str, Any]:
236
- """
237
- Execute the given code in the provided sandbox.
238
-
239
- :param sbx: Sandbox instance to run the code.
240
- :param code: Code to execute.
241
- :return: Tuple of aggregated output string and the raw execution object.
242
- """
243
- execution = sbx.run_code(code, on_stdout=lambda data: print("stdout:", data))
244
- output = ""
245
- if len(execution.logs.stdout) > 0:
246
- output += "\n".join(execution.logs.stdout)
247
- if len(execution.logs.stderr) > 0:
248
- output += "\n".join(execution.logs.stderr)
249
- if execution.error is not None:
250
- output += execution.error.traceback
251
- return output, execution
252
-
253
-
254
- def parse_exec_result_llm(execution: Any) -> str:
255
- """
256
- Parse the execution results and return a single concatenated output string.
257
-
258
- :param execution: Execution object from the sandbox.
259
- :return: Concatenated string of output messages.
260
- """
261
- output = ""
262
- if len(execution.logs.stdout) > 0:
263
- output += "\n".join(execution.logs.stdout)
264
- if len(execution.logs.stderr) > 0:
265
- output += "\n".join(execution.logs.stderr)
266
- if execution.error is not None:
267
- output += execution.error.traceback
268
- return output
269
-
270
-
271
- def update_notebook_display(notebook_data):
272
- notebook = nbformat.from_dict(notebook_data)
273
- notebook_body, _ = html_exporter.from_notebook_node(notebook)
274
- notebook_body = notebook_body.replace(bad_html_bad, "")
275
- return notebook_body
276
-
277
-
278
- ## --- Interactive Notebook Generation ---
279
-
280
-
281
- def run_interactive_notebook(
282
- client: InferenceClient,
283
- model: str,
284
- tokenizer: Any,
285
- messages: List[Dict[str, Any]],
286
- sbx: Sandbox,
287
- max_new_tokens: int = 512,
288
- ) -> Any:
289
- """
290
- Generator function that iteratively builds and updates the Jupyter Notebook.
291
-
292
- :param client: Hugging Face InferenceClient for text generation.
293
- :param model: Model identifier.
294
- :param tokenizer: Tokenizer corresponding to the model.
295
- :param messages: List of conversation messages.
296
- :param sbx: Sandbox instance for executing code.
297
- :param max_new_tokens: Maximum tokens to generate per turn.
298
- :yield: Tuple containing updated notebook HTML, notebook data, and messages.
299
- """
300
- notebook_data, code_cell_counter = create_base_notebook(messages)
301
- turns = 0
302
-
303
- # code_cell_counter = 0
304
- while turns <= MAX_TURNS:
305
- turns += 1
306
- input_tokens = tokenizer.apply_chat_template(
307
- messages,
308
- chat_template=llama_template,
309
- builtin_tools=["code_interpreter"],
310
- add_generation_prompt=True,
311
- )
312
- model_input = tokenizer.decode(input_tokens)
313
-
314
- print(f"Model input:\n{model_input}\n{'='*80}")
315
-
316
- response_stream = client.text_generation(
317
- model=model,
318
- prompt=model_input,
319
- details=True,
320
- stream=True,
321
- do_sample=True,
322
- repetition_penalty=1.1,
323
- temperature=0.8,
324
- max_new_tokens=max_new_tokens,
325
- )
326
-
327
- assistant_response = ""
328
- tokens = []
329
-
330
- code_cell = False
331
- for i, chunk in enumerate(response_stream):
332
- if not chunk.token.special:
333
- content = chunk.token.text
334
- else:
335
- content = ""
336
- tokens.append(chunk.token.text)
337
- assistant_response += content
338
-
339
- if len(tokens) == 1:
340
- create_cell = True
341
- code_cell = "<|python_tag|>" in tokens[0]
342
- if code_cell:
343
- code_cell_counter += 1
344
- else:
345
- create_cell = False
346
-
347
- ## Update notebook cells in real-time
348
- if create_cell:
349
- if "<|python_tag|>" in tokens[0]:
350
- notebook_data["cells"].append(
351
- {
352
- "cell_type": "code",
353
- "execution_count": None,
354
- "metadata": {},
355
- "source": assistant_response,
356
- "outputs": [],
357
- }
358
- )
359
- else:
360
- notebook_data["cells"].append(
361
- {
362
- "cell_type": "markdown",
363
- "metadata": {},
364
- "source": assistant_response,
365
- }
366
- )
367
- else:
368
- notebook_data["cells"][-1]["source"] = assistant_response
369
- if i % 16 == 0:
370
- yield update_notebook_display(notebook_data), notebook_data, messages
371
- yield update_notebook_display(notebook_data), notebook_data, messages
372
-
373
- ## If a code cell was generated, execute the code
374
- if code_cell:
375
- notebook_data["cells"][-1]["execution_count"] = code_cell_counter
376
-
377
- exec_result, execution = execute_code(sbx, assistant_response)
378
- messages.append(
379
- {
380
- "role": "assistant",
381
- "content": assistant_response,
382
- "tool_calls": [
383
- {
384
- "type": "function",
385
- "function": {
386
- "name": "code_interpreter",
387
- "arguments": {"code": assistant_response},
388
- },
389
- }
390
- ],
391
- }
392
- )
393
- messages.append(
394
- {
395
- "role": "ipython",
396
- "content": parse_exec_result_llm(execution),
397
- "nbformat": parse_exec_result_nb(execution),
398
- }
399
- )
400
-
401
- ## Update the last code cell with execution results
402
- notebook_data["cells"][-1]["outputs"] = parse_exec_result_nb(execution)
403
- update_notebook_display(notebook_data)
404
- else:
405
- messages.append({"role": "assistant", "content": assistant_response})
406
- if tokens[-1] == "<|eot_id|>":
407
- break
408
-
409
- yield update_notebook_display(notebook_data), notebook_data, messages
runbooks/aws/__init__.py DELETED
@@ -1,58 +0,0 @@
1
- ## src/runbooks/aws/__init__.py
2
- """AWS Runbooks Initialization Module."""
3
-
4
- import importlib
5
- import os
6
- import sys
7
-
8
- from runbooks.utils.logger import configure_logger
9
-
10
- logger = configure_logger(__name__)
11
-
12
-
13
- def discover_scripts():
14
- """
15
- Dynamically discovers and lists all AWS scripts in this package.
16
-
17
- Returns:
18
- dict: A mapping of script names to their main functions.
19
- """
20
- scripts = {}
21
- aws_path = os.path.dirname(__file__)
22
- for filename in os.listdir(aws_path):
23
- if filename.endswith(".py") and filename != "__init__.py":
24
- module_name = f"runbooks.aws.{filename[:-3]}"
25
- try:
26
- module = importlib.import_module(module_name)
27
- if hasattr(module, "main"):
28
- scripts[filename[:-3]] = module.main
29
- except Exception as e:
30
- logger.error(f"Error importing {module_name}: {e}")
31
- return scripts
32
-
33
-
34
- def run_script(script_name, *args):
35
- """
36
- Executes the given script by name.
37
-
38
- Args:
39
- script_name (str): The name of the script to execute.
40
- *args: Additional arguments to pass to the script.
41
- """
42
- scripts = discover_scripts()
43
- if script_name in scripts:
44
- try:
45
- scripts[script_name](*args)
46
- except Exception as e:
47
- logger.error(f"Error executing script {script_name}: {e}")
48
- else:
49
- logger.error(f"Script {script_name} not found.")
50
- sys.exit(1)
51
-
52
-
53
- if __name__ == "__main__":
54
- if len(sys.argv) < 2:
55
- logger.error("Usage: python -m runbooks.aws <script_name> [<args>]")
56
- sys.exit(1)
57
-
58
- run_script(sys.argv[1], *sys.argv[2:])
@@ -1,231 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- DynamoDB Operations: Put Item, Delete Item, and Batch Write.
5
-
6
- This script supports the following functionalities:
7
- 1. Insert or update a single item (Put Item).
8
- 2. Retrieve and delete a single item (Delete Item).
9
- 3. Batch insert multiple items efficiently (Batch Write).
10
-
11
- Designed for usage in Python, Docker, and AWS Lambda environments.
12
-
13
- Author: nnthanh101@gmail.com
14
- Date: 2025-01-09
15
- Version: 1.0.0
16
- """
17
-
18
- import json
19
- import os
20
- from typing import Dict, List
21
-
22
- import boto3
23
- from botocore.exceptions import BotoCoreError, ClientError
24
-
25
- from runbooks.utils.logger import configure_logger
26
-
27
- ## ✅ Configure Logger
28
- logger = configure_logger(__name__)
29
-
30
- # ==============================
31
- # CONFIGURATION VARIABLES
32
- # ==============================
33
- AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
34
- TABLE_NAME = os.getenv("TABLE_NAME", "employees")
35
- MAX_BATCH_ITEMS = int(os.getenv("MAX_BATCH_ITEMS", 100))
36
-
37
-
38
- # ==============================
39
- # AWS CLIENT INITIALIZATION
40
- # ==============================
41
- try:
42
- dynamodb = boto3.resource("dynamodb", region_name=AWS_REGION)
43
- table = dynamodb.Table(TABLE_NAME)
44
- logger.info(f"✅ DynamoDB Table '{TABLE_NAME}' initialized successfully.")
45
- except Exception as e:
46
- logger.error(f"❌ Failed to initialize DynamoDB table: {e}")
47
- raise
48
-
49
-
50
- # ==============================
51
- # FUNCTION: PUT ITEM
52
- # ==============================
53
- def put_item(emp_id: str, name: str, salary: int) -> None:
54
- """
55
- Inserts or updates a single item in DynamoDB.
56
-
57
- Args:
58
- emp_id (str): Employee ID.
59
- name (str): Employee name.
60
- salary (int): Employee salary.
61
-
62
- Raises:
63
- Exception: If item insertion fails.
64
- """
65
- try:
66
- logger.info(f"🚀 Inserting/Updating item in table '{TABLE_NAME}'...")
67
- table.put_item(Item={"emp_id": emp_id, "name": name, "salary": salary})
68
- logger.info(f"✅ Item added successfully: emp_id={emp_id}, name={name}, salary={salary}")
69
-
70
- except ClientError as e:
71
- logger.error(f"❌ AWS Client Error: {e}")
72
- raise
73
-
74
- except Exception as e:
75
- logger.error(f"❌ Unexpected Error: {e}")
76
- raise
77
-
78
-
79
- # ==============================
80
- # FUNCTION: DELETE ITEM
81
- # ==============================
82
- def delete_item(emp_id: str) -> Dict:
83
- """
84
- Retrieves and deletes a single item from DynamoDB.
85
-
86
- Args:
87
- emp_id (str): Employee ID.
88
-
89
- Returns:
90
- Dict: Deleted item details.
91
-
92
- Raises:
93
- Exception: If retrieval or deletion fails.
94
- """
95
- try:
96
- ## ✅ 1. Retrieve the item
97
- logger.info(f"🔍 Retrieving item with emp_id={emp_id}...")
98
- response = table.get_item(Key={"emp_id": emp_id})
99
-
100
- if "Item" not in response:
101
- raise ValueError(f"Item with emp_id={emp_id} not found.")
102
- item = response["Item"]
103
- logger.info(f"✅ Item retrieved: {item}")
104
-
105
- ## ✅ 2. Delete the item
106
- logger.info(f"🗑️ Deleting item with emp_id={emp_id}...")
107
- table.delete_item(Key={"emp_id": emp_id})
108
- logger.info(f"✅ Item deleted successfully: emp_id={emp_id}")
109
-
110
- return item
111
-
112
- except ClientError as e:
113
- logger.error(f"❌ AWS Client Error: {e}")
114
- raise
115
-
116
- except BotoCoreError as e:
117
- logger.error(f"❌ BotoCore Error: {e}")
118
- raise
119
-
120
- except Exception as e:
121
- logger.error(f"❌ Unexpected Error: {e}")
122
- raise
123
-
124
-
125
- # ==============================
126
- # FUNCTION: BATCH WRITE ITEMS
127
- # ==============================
128
- def batch_write_items(batch_size: int = MAX_BATCH_ITEMS) -> None:
129
- """
130
- Inserts multiple items into DynamoDB using batch writer.
131
-
132
- Args:
133
- batch_size (int): Number of items to write in a batch.
134
-
135
- Raises:
136
- Exception: If batch write fails.
137
- """
138
- try:
139
- logger.info(f"🚀 Starting batch write with {batch_size} items...")
140
- with table.batch_writer() as batch:
141
- for i in range(batch_size):
142
- batch.put_item(
143
- Item={
144
- "emp_id": str(i),
145
- "name": f"Name-{i}",
146
- "salary": 50000 + i * 100, ## Incremental salary
147
- }
148
- )
149
- logger.info(f"✅ Batch write completed successfully with {batch_size} items.")
150
-
151
- except ClientError as e:
152
- logger.error(f"❌ AWS Client Error: {e}")
153
- raise
154
-
155
- except BotoCoreError as e:
156
- logger.error(f"❌ BotoCore Error: {e}")
157
- raise
158
-
159
- except Exception as e:
160
- logger.error(f"❌ Unexpected Error: {e}")
161
- raise
162
-
163
-
164
- # ==============================
165
- # MAIN FUNCTION (CLI/DOCKER)
166
- # ==============================
167
- def main():
168
- """
169
- Main function for CLI/Docker execution.
170
- """
171
- try:
172
- ## Use-Case 1: Put Item
173
- put_item(emp_id="2", name="John Doe", salary=75000)
174
-
175
- ## Use-Case 2: Delete Item
176
- delete_item(emp_id="2")
177
-
178
- ## Use-Case 3: Batch Write Items
179
- batch_write_items(batch_size=MAX_BATCH_ITEMS)
180
-
181
- except Exception as e:
182
- logger.error(f"❌ Error in main execution: {e}")
183
- raise
184
-
185
-
186
- # ==============================
187
- # AWS LAMBDA HANDLER
188
- # ==============================
189
- def lambda_handler(event, context):
190
- """
191
- AWS Lambda handler for DynamoDB operations.
192
-
193
- Args:
194
- event (dict): AWS Lambda event with action details.
195
- context: AWS Lambda context object.
196
-
197
- Returns:
198
- dict: Status code and message.
199
- """
200
- try:
201
- action = event.get("action")
202
- emp_id = event.get("emp_id")
203
- name = event.get("name")
204
- salary = event.get("salary", 0)
205
- batch_size = int(event.get("batch_size", MAX_BATCH_ITEMS))
206
-
207
- if action == "put":
208
- put_item(emp_id, name, salary)
209
- return {"statusCode": 200, "body": f"Item {emp_id} inserted."}
210
-
211
- elif action == "delete":
212
- item = delete_item(emp_id)
213
- return {"statusCode": 200, "body": f"Item {item} deleted."}
214
-
215
- elif action == "batch_write":
216
- batch_write_items(batch_size)
217
- return {"statusCode": 200, "body": "Batch write completed."}
218
-
219
- else:
220
- raise ValueError("Invalid action. Use 'put', 'delete', or 'batch_write'.")
221
-
222
- except Exception as e:
223
- logger.error(f"❌ Lambda Error: {e}")
224
- return {"statusCode": 500, "body": str(e)}
225
-
226
-
227
- # ==============================
228
- # SCRIPT ENTRY POINT
229
- # ==============================
230
- if __name__ == "__main__":
231
- main()