diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000..7e49eb3 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,23 @@ +{ + "mcpServers": { + "task-master-ai": { + "command": "npx", + "args": [ + "-y", + "--package=task-master-ai", + "task-master-ai" + ], + "env": { + "ANTHROPIC_API_KEY": "ANTHROPIC_API_KEY_HERE", + "PERPLEXITY_API_KEY": "PERPLEXITY_API_KEY_HERE", + "OPENAI_API_KEY": "OPENAI_API_KEY_HERE", + "GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE", + "XAI_API_KEY": "XAI_API_KEY_HERE", + "OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE", + "MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE", + "AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE", + "OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE" + } + } + } +} \ No newline at end of file diff --git a/.cursor/rules/cursor_rules.mdc b/.cursor/rules/cursor_rules.mdc new file mode 100644 index 0000000..7dfae3d --- /dev/null +++ b/.cursor/rules/cursor_rules.mdc @@ -0,0 +1,53 @@ +--- +description: Guidelines for creating and maintaining Cursor rules to ensure consistency and effectiveness. +globs: .cursor/rules/*.mdc +alwaysApply: true +--- + +- **Required Rule Structure:** + ```markdown + --- + description: Clear, one-line description of what the rule enforces + globs: path/to/files/*.ext, other/path/**/* + alwaysApply: boolean + --- + + - **Main Points in Bold** + - Sub-points with details + - Examples and explanations + ``` + +- **File References:** + - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files + - Example: [prisma.mdc](mdc:.cursor/rules/prisma.mdc) for rule references + - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references + +- **Code Examples:** + - Use language-specific code blocks + ```typescript + // ✅ DO: Show good examples + const goodExample = true; + + // ❌ DON'T: Show anti-patterns + const badExample = false; + ``` + +- **Rule Content Guidelines:** + - Start with high-level overview + - Include specific, actionable requirements + - Show examples of correct implementation + - Reference existing code when possible + - Keep rules DRY by referencing other rules + +- **Rule Maintenance:** + - Update rules when new patterns emerge + - Add examples from actual codebase + - Remove outdated patterns + - Cross-reference related rules + +- **Best Practices:** + - Use bullet points for clarity + - Keep descriptions concise + - Include both DO and DON'T examples + - Reference actual code over theoretical examples + - Use consistent formatting across rules \ No newline at end of file diff --git a/.cursor/rules/dev_workflow.mdc b/.cursor/rules/dev_workflow.mdc new file mode 100644 index 0000000..29f1f4c --- /dev/null +++ b/.cursor/rules/dev_workflow.mdc @@ -0,0 +1,239 @@ +--- +description: Guide for using Task Master to manage task-driven development workflows +globs: **/* +alwaysApply: true +--- +# Task Master Development Workflow + +This guide outlines the typical process for using Task Master to manage software development projects. + +## Primary Interaction: MCP Server vs. CLI + +Task Master offers two primary ways to interact: + +1. **MCP Server (Recommended for Integrated Tools)**: + - For AI agents and integrated development environments (like Cursor), interacting via the **MCP server is the preferred method**. + - The MCP server exposes Task Master functionality through a set of tools (e.g., `get_tasks`, `add_subtask`). + - This method offers better performance, structured data exchange, and richer error handling compared to CLI parsing. + - Refer to [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc) for details on the MCP architecture and available tools. + - A comprehensive list and description of MCP tools and their corresponding CLI commands can be found in [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc). + - **Restart the MCP server** if core logic in `scripts/modules` or MCP tool/direct function definitions change. + +2. **`task-master` CLI (For Users & Fallback)**: + - The global `task-master` command provides a user-friendly interface for direct terminal interaction. + - It can also serve as a fallback if the MCP server is inaccessible or a specific function isn't exposed via MCP. + - Install globally with `npm install -g task-master-ai` or use locally via `npx task-master-ai ...`. + - The CLI commands often mirror the MCP tools (e.g., `task-master list` corresponds to `get_tasks`). + - Refer to [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc) for a detailed command reference. + +## Standard Development Workflow Process + +- Start new projects by running `initialize_project` tool / `task-master init` or `parse_prd` / `task-master parse-prd --input=''` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to generate initial tasks.json +- Begin coding sessions with `get_tasks` / `task-master list` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to see current tasks, status, and IDs +- Determine the next task to work on using `next_task` / `task-master next` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). +- Analyze task complexity with `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) before breaking down tasks +- Review complexity report using `complexity_report` / `task-master complexity-report` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). +- Select tasks based on dependencies (all marked 'done'), priority level, and ID order +- Clarify tasks by checking task files in tasks/ directory or asking for user input +- View specific task details using `get_task` / `task-master show ` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to understand implementation requirements +- Break down complex tasks using `expand_task` / `task-master expand --id= --force --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) with appropriate flags like `--force` (to replace existing subtasks) and `--research`. +- Clear existing subtasks if needed using `clear_subtasks` / `task-master clear-subtasks --id=` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) before regenerating +- Implement code following task details, dependencies, and project standards +- Verify tasks according to test strategies before marking as complete (See [`tests.mdc`](mdc:.cursor/rules/tests.mdc)) +- Mark completed tasks with `set_task_status` / `task-master set-status --id= --status=done` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) +- Update dependent tasks when implementation differs from original plan using `update` / `task-master update --from= --prompt="..."` or `update_task` / `task-master update-task --id= --prompt="..."` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) +- Add new tasks discovered during implementation using `add_task` / `task-master add-task --prompt="..." --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). +- Add new subtasks as needed using `add_subtask` / `task-master add-subtask --parent= --title="..."` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). +- Append notes or details to subtasks using `update_subtask` / `task-master update-subtask --id= --prompt='Add implementation notes here...\nMore details...'` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). +- Generate task files with `generate` / `task-master generate` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) after updating tasks.json +- Maintain valid dependency structure with `add_dependency`/`remove_dependency` tools or `task-master add-dependency`/`remove-dependency` commands, `validate_dependencies` / `task-master validate-dependencies`, and `fix_dependencies` / `task-master fix-dependencies` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) when needed +- Respect dependency chains and task priorities when selecting work +- Report progress regularly using `get_tasks` / `task-master list` +- Reorganize tasks as needed using `move_task` / `task-master move --from= --to=` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to change task hierarchy or ordering + +## Task Complexity Analysis + +- Run `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) for comprehensive analysis +- Review complexity report via `complexity_report` / `task-master complexity-report` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) for a formatted, readable version. +- Focus on tasks with highest complexity scores (8-10) for detailed breakdown +- Use analysis results to determine appropriate subtask allocation +- Note that reports are automatically used by the `expand_task` tool/command + +## Task Breakdown Process + +- Use `expand_task` / `task-master expand --id=`. It automatically uses the complexity report if found, otherwise generates default number of subtasks. +- Use `--num=` to specify an explicit number of subtasks, overriding defaults or complexity report recommendations. +- Add `--research` flag to leverage Perplexity AI for research-backed expansion. +- Add `--force` flag to clear existing subtasks before generating new ones (default is to append). +- Use `--prompt=""` to provide additional context when needed. +- Review and adjust generated subtasks as necessary. +- Use `expand_all` tool or `task-master expand --all` to expand multiple pending tasks at once, respecting flags like `--force` and `--research`. +- If subtasks need complete replacement (regardless of the `--force` flag on `expand`), clear them first with `clear_subtasks` / `task-master clear-subtasks --id=`. + +## Implementation Drift Handling + +- When implementation differs significantly from planned approach +- When future tasks need modification due to current implementation choices +- When new dependencies or requirements emerge +- Use `update` / `task-master update --from= --prompt='\nUpdate context...' --research` to update multiple future tasks. +- Use `update_task` / `task-master update-task --id= --prompt='\nUpdate context...' --research` to update a single specific task. + +## Task Status Management + +- Use 'pending' for tasks ready to be worked on +- Use 'done' for completed and verified tasks +- Use 'deferred' for postponed tasks +- Add custom status values as needed for project-specific workflows + +## Task Structure Fields + +- **id**: Unique identifier for the task (Example: `1`, `1.1`) +- **title**: Brief, descriptive title (Example: `"Initialize Repo"`) +- **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`) +- **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) +- **dependencies**: IDs of prerequisite tasks (Example: `[1, 2.1]`) + - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) + - This helps quickly identify which prerequisite tasks are blocking work +- **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`) +- **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) +- **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) +- **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) +- Refer to task structure details (previously linked to `tasks.mdc`). + +## Configuration Management (Updated) + +Taskmaster configuration is managed through two main mechanisms: + +1. **`.taskmasterconfig` File (Primary):** + * Located in the project root directory. + * Stores most configuration settings: AI model selections (main, research, fallback), parameters (max tokens, temperature), logging level, default subtasks/priority, project name, etc. + * **Managed via `task-master models --setup` command.** Do not edit manually unless you know what you are doing. + * **View/Set specific models via `task-master models` command or `models` MCP tool.** + * Created automatically when you run `task-master models --setup` for the first time. + +2. **Environment Variables (`.env` / `mcp.json`):** + * Used **only** for sensitive API keys and specific endpoint URLs. + * Place API keys (one per provider) in a `.env` file in the project root for CLI usage. + * For MCP/Cursor integration, configure these keys in the `env` section of `.cursor/mcp.json`. + * Available keys/variables: See `assets/env.example` or the Configuration section in the command reference (previously linked to `taskmaster.mdc`). + +**Important:** Non-API key settings (like model selections, `MAX_TOKENS`, `TASKMASTER_LOG_LEVEL`) are **no longer configured via environment variables**. Use the `task-master models` command (or `--setup` for interactive configuration) or the `models` MCP tool. +**If AI commands FAIL in MCP** verify that the API key for the selected provider is present in the `env` section of `.cursor/mcp.json`. +**If AI commands FAIL in CLI** verify that the API key for the selected provider is present in the `.env` file in the root of the project. + +## Determining the Next Task + +- Run `next_task` / `task-master next` to show the next task to work on. +- The command identifies tasks with all dependencies satisfied +- Tasks are prioritized by priority level, dependency count, and ID +- The command shows comprehensive task information including: + - Basic task details and description + - Implementation details + - Subtasks (if they exist) + - Contextual suggested actions +- Recommended before starting any new development work +- Respects your project's dependency structure +- Ensures tasks are completed in the appropriate sequence +- Provides ready-to-use commands for common task actions + +## Viewing Specific Task Details + +- Run `get_task` / `task-master show ` to view a specific task. +- Use dot notation for subtasks: `task-master show 1.2` (shows subtask 2 of task 1) +- Displays comprehensive information similar to the next command, but for a specific task +- For parent tasks, shows all subtasks and their current status +- For subtasks, shows parent task information and relationship +- Provides contextual suggested actions appropriate for the specific task +- Useful for examining task details before implementation or checking status + +## Managing Task Dependencies + +- Use `add_dependency` / `task-master add-dependency --id= --depends-on=` to add a dependency. +- Use `remove_dependency` / `task-master remove-dependency --id= --depends-on=` to remove a dependency. +- The system prevents circular dependencies and duplicate dependency entries +- Dependencies are checked for existence before being added or removed +- Task files are automatically regenerated after dependency changes +- Dependencies are visualized with status indicators in task listings and files + +## Task Reorganization + +- Use `move_task` / `task-master move --from= --to=` to move tasks or subtasks within the hierarchy +- This command supports several use cases: + - Moving a standalone task to become a subtask (e.g., `--from=5 --to=7`) + - Moving a subtask to become a standalone task (e.g., `--from=5.2 --to=7`) + - Moving a subtask to a different parent (e.g., `--from=5.2 --to=7.3`) + - Reordering subtasks within the same parent (e.g., `--from=5.2 --to=5.4`) + - Moving a task to a new, non-existent ID position (e.g., `--from=5 --to=25`) + - Moving multiple tasks at once using comma-separated IDs (e.g., `--from=10,11,12 --to=16,17,18`) +- The system includes validation to prevent data loss: + - Allows moving to non-existent IDs by creating placeholder tasks + - Prevents moving to existing task IDs that have content (to avoid overwriting) + - Validates source tasks exist before attempting to move them +- The system maintains proper parent-child relationships and dependency integrity +- Task files are automatically regenerated after the move operation +- This provides greater flexibility in organizing and refining your task structure as project understanding evolves +- This is especially useful when dealing with potential merge conflicts arising from teams creating tasks on separate branches. Solve these conflicts very easily by moving your tasks and keeping theirs. + +## Iterative Subtask Implementation + +Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation: + +1. **Understand the Goal (Preparation):** + * Use `get_task` / `task-master show ` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to thoroughly understand the specific goals and requirements of the subtask. + +2. **Initial Exploration & Planning (Iteration 1):** + * This is the first attempt at creating a concrete implementation plan. + * Explore the codebase to identify the precise files, functions, and even specific lines of code that will need modification. + * Determine the intended code changes (diffs) and their locations. + * Gather *all* relevant details from this exploration phase. + +3. **Log the Plan:** + * Run `update_subtask` / `task-master update-subtask --id= --prompt=''`. + * Provide the *complete and detailed* findings from the exploration phase in the prompt. Include file paths, line numbers, proposed diffs, reasoning, and any potential challenges identified. Do not omit details. The goal is to create a rich, timestamped log within the subtask's `details`. + +4. **Verify the Plan:** + * Run `get_task` / `task-master show ` again to confirm that the detailed implementation plan has been successfully appended to the subtask's details. + +5. **Begin Implementation:** + * Set the subtask status using `set_task_status` / `task-master set-status --id= --status=in-progress`. + * Start coding based on the logged plan. + +6. **Refine and Log Progress (Iteration 2+):** + * As implementation progresses, you will encounter challenges, discover nuances, or confirm successful approaches. + * **Before appending new information**: Briefly review the *existing* details logged in the subtask (using `get_task` or recalling from context) to ensure the update adds fresh insights and avoids redundancy. + * **Regularly** use `update_subtask` / `task-master update-subtask --id= --prompt='\n- What worked...\n- What didn't work...'` to append new findings. + * **Crucially, log:** + * What worked ("fundamental truths" discovered). + * What didn't work and why (to avoid repeating mistakes). + * Specific code snippets or configurations that were successful. + * Decisions made, especially if confirmed with user input. + * Any deviations from the initial plan and the reasoning. + * The objective is to continuously enrich the subtask's details, creating a log of the implementation journey that helps the AI (and human developers) learn, adapt, and avoid repeating errors. + +7. **Review & Update Rules (Post-Implementation):** + * Once the implementation for the subtask is functionally complete, review all code changes and the relevant chat history. + * Identify any new or modified code patterns, conventions, or best practices established during the implementation. + * Create new or update existing rules following internal guidelines (previously linked to `cursor_rules.mdc` and `self_improve.mdc`). + +8. **Mark Task Complete:** + * After verifying the implementation and updating any necessary rules, mark the subtask as completed: `set_task_status` / `task-master set-status --id= --status=done`. + +9. **Commit Changes (If using Git):** + * Stage the relevant code changes and any updated/new rule files (`git add .`). + * Craft a comprehensive Git commit message summarizing the work done for the subtask, including both code implementation and any rule adjustments. + * Execute the commit command directly in the terminal (e.g., `git commit -m 'feat(module): Implement feature X for subtask \n\n- Details about changes...\n- Updated rule Y for pattern Z'`). + * Consider if a Changeset is needed according to internal versioning guidelines (previously linked to `changeset.mdc`). If so, run `npm run changeset`, stage the generated file, and amend the commit or create a new one. + +10. **Proceed to Next Subtask:** + * Identify the next subtask (e.g., using `next_task` / `task-master next`). + +## Code Analysis & Refactoring Techniques + +- **Top-Level Function Search**: + - Useful for understanding module structure or planning refactors. + - Use grep/ripgrep to find exported functions/constants: + `rg "export (async function|function|const) \w+"` or similar patterns. + - Can help compare functions between files during migrations or identify potential naming conflicts. + +--- +*This workflow provides a general guideline. Adapt it based on your specific project needs and team practices.* \ No newline at end of file diff --git a/.cursor/rules/self_improve.mdc b/.cursor/rules/self_improve.mdc new file mode 100644 index 0000000..40b31b6 --- /dev/null +++ b/.cursor/rules/self_improve.mdc @@ -0,0 +1,72 @@ +--- +description: Guidelines for continuously improving Cursor rules based on emerging code patterns and best practices. +globs: **/* +alwaysApply: true +--- + +- **Rule Improvement Triggers:** + - New code patterns not covered by existing rules + - Repeated similar implementations across files + - Common error patterns that could be prevented + - New libraries or tools being used consistently + - Emerging best practices in the codebase + +- **Analysis Process:** + - Compare new code with existing rules + - Identify patterns that should be standardized + - Look for references to external documentation + - Check for consistent error handling patterns + - Monitor test patterns and coverage + +- **Rule Updates:** + - **Add New Rules When:** + - A new technology/pattern is used in 3+ files + - Common bugs could be prevented by a rule + - Code reviews repeatedly mention the same feedback + - New security or performance patterns emerge + + - **Modify Existing Rules When:** + - Better examples exist in the codebase + - Additional edge cases are discovered + - Related rules have been updated + - Implementation details have changed + +- **Example Pattern Recognition:** + ```typescript + // If you see repeated patterns like: + const data = await prisma.user.findMany({ + select: { id: true, email: true }, + where: { status: 'ACTIVE' } + }); + + // Consider adding to [prisma.mdc](mdc:.cursor/rules/prisma.mdc): + // - Standard select fields + // - Common where conditions + // - Performance optimization patterns + ``` + +- **Rule Quality Checks:** + - Rules should be actionable and specific + - Examples should come from actual code + - References should be up to date + - Patterns should be consistently enforced + +- **Continuous Improvement:** + - Monitor code review comments + - Track common development questions + - Update rules after major refactors + - Add links to relevant documentation + - Cross-reference related rules + +- **Rule Deprecation:** + - Mark outdated patterns as deprecated + - Remove rules that no longer apply + - Update references to deprecated rules + - Document migration paths for old patterns + +- **Documentation Updates:** + - Keep examples synchronized with code + - Update references to external docs + - Maintain links between related rules + - Document breaking changes +Follow [cursor_rules.mdc](mdc:.cursor/rules/cursor_rules.mdc) for proper rule formatting and structure. diff --git a/.cursor/rules/taskmaster.mdc b/.cursor/rules/taskmaster.mdc new file mode 100644 index 0000000..edb4d58 --- /dev/null +++ b/.cursor/rules/taskmaster.mdc @@ -0,0 +1,407 @@ +--- +description: Comprehensive reference for Taskmaster MCP tools and CLI commands. +globs: **/* +alwaysApply: true +--- +# Taskmaster Tool & Command Reference + +This document provides a detailed reference for interacting with Taskmaster, covering both the recommended MCP tools, suitable for integrations like Cursor, and the corresponding `task-master` CLI commands, designed for direct user interaction or fallback. + +**Note:** For interacting with Taskmaster programmatically or via integrated tools, using the **MCP tools is strongly recommended** due to better performance, structured data, and error handling. The CLI commands serve as a user-friendly alternative and fallback. + +**Important:** Several MCP tools involve AI processing... The AI-powered tools include `parse_prd`, `analyze_project_complexity`, `update_subtask`, `update_task`, `update`, `expand_all`, `expand_task`, and `add_task`. + +--- + +## Initialization & Setup + +### 1. Initialize Project (`init`) + +* **MCP Tool:** `initialize_project` +* **CLI Command:** `task-master init [options]` +* **Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project.` +* **Key CLI Options:** + * `--name `: `Set the name for your project in Taskmaster's configuration.` + * `--description `: `Provide a brief description for your project.` + * `--version `: `Set the initial version for your project, e.g., '0.1.0'.` + * `-y, --yes`: `Initialize Taskmaster quickly using default settings without interactive prompts.` +* **Usage:** Run this once at the beginning of a new project. +* **MCP Variant Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project by running the 'task-master init' command.` +* **Key MCP Parameters/Options:** + * `projectName`: `Set the name for your project.` (CLI: `--name `) + * `projectDescription`: `Provide a brief description for your project.` (CLI: `--description `) + * `projectVersion`: `Set the initial version for your project, e.g., '0.1.0'.` (CLI: `--version `) + * `authorName`: `Author name.` (CLI: `--author `) + * `skipInstall`: `Skip installing dependencies. Default is false.` (CLI: `--skip-install`) + * `addAliases`: `Add shell aliases tm and taskmaster. Default is false.` (CLI: `--aliases`) + * `yes`: `Skip prompts and use defaults/provided arguments. Default is false.` (CLI: `-y, --yes`) +* **Usage:** Run this once at the beginning of a new project, typically via an integrated tool like Cursor. Operates on the current working directory of the MCP server. +* **Important:** Once complete, you *MUST* parse a prd in order to generate tasks. There will be no tasks files until then. The next step after initializing should be to create a PRD using the example PRD in scripts/example_prd.txt. + +### 2. Parse PRD (`parse_prd`) + +* **MCP Tool:** `parse_prd` +* **CLI Command:** `task-master parse-prd [file] [options]` +* **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.` +* **Key Parameters/Options:** + * `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input `) + * `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to 'tasks/tasks.json'.` (CLI: `-o, --output `) + * `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks `) + * `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`) +* **Usage:** Useful for bootstrapping a project from an existing requirements document. +* **Notes:** Task Master will strictly adhere to any specific requirements mentioned in the PRD, such as libraries, database schemas, frameworks, tech stacks, etc., while filling in any gaps where the PRD isn't fully specified. Tasks are designed to provide the most direct implementation path while avoiding over-engineering. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. If the user does not have a PRD, suggest discussing their idea and then use the example PRD in `scripts/example_prd.txt` as a template for creating the PRD based on their idea, for use with `parse-prd`. + +--- + +## AI Model Configuration + +### 2. Manage Models (`models`) +* **MCP Tool:** `models` +* **CLI Command:** `task-master models [options]` +* **Description:** `View the current AI model configuration or set specific models for different roles (main, research, fallback). Allows setting custom model IDs for Ollama and OpenRouter.` +* **Key MCP Parameters/Options:** + * `setMain `: `Set the primary model ID for task generation/updates.` (CLI: `--set-main `) + * `setResearch `: `Set the model ID for research-backed operations.` (CLI: `--set-research `) + * `setFallback `: `Set the model ID to use if the primary fails.` (CLI: `--set-fallback `) + * `ollama `: `Indicates the set model ID is a custom Ollama model.` (CLI: `--ollama`) + * `openrouter `: `Indicates the set model ID is a custom OpenRouter model.` (CLI: `--openrouter`) + * `listAvailableModels `: `If true, lists available models not currently assigned to a role.` (CLI: No direct equivalent; CLI lists available automatically) + * `projectRoot `: `Optional. Absolute path to the project root directory.` (CLI: Determined automatically) +* **Key CLI Options:** + * `--set-main `: `Set the primary model.` + * `--set-research `: `Set the research model.` + * `--set-fallback `: `Set the fallback model.` + * `--ollama`: `Specify that the provided model ID is for Ollama (use with --set-*).` + * `--openrouter`: `Specify that the provided model ID is for OpenRouter (use with --set-*). Validates against OpenRouter API.` + * `--setup`: `Run interactive setup to configure models, including custom Ollama/OpenRouter IDs.` +* **Usage (MCP):** Call without set flags to get current config. Use `setMain`, `setResearch`, or `setFallback` with a valid model ID to update the configuration. Use `listAvailableModels: true` to get a list of unassigned models. To set a custom model, provide the model ID and set `ollama: true` or `openrouter: true`. +* **Usage (CLI):** Run without flags to view current configuration and available models. Use set flags to update specific roles. Use `--setup` for guided configuration, including custom models. To set a custom model via flags, use `--set-=` along with either `--ollama` or `--openrouter`. +* **Notes:** Configuration is stored in `.taskmasterconfig` in the project root. This command/tool modifies that file. Use `listAvailableModels` or `task-master models` to see internally supported models. OpenRouter custom models are validated against their live API. Ollama custom models are not validated live. +* **API note:** API keys for selected AI providers (based on their model) need to exist in the mcp.json file to be accessible in MCP context. The API keys must be present in the local .env file for the CLI to be able to read them. +* **Model costs:** The costs in supported models are expressed in dollars. An input/output value of 3 is $3.00. A value of 0.8 is $0.80. +* **Warning:** DO NOT MANUALLY EDIT THE .taskmasterconfig FILE. Use the included commands either in the MCP or CLI format as needed. Always prioritize MCP tools when available and use the CLI as a fallback. + +--- + +## Task Listing & Viewing + +### 3. Get Tasks (`get_tasks`) + +* **MCP Tool:** `get_tasks` +* **CLI Command:** `task-master list [options]` +* **Description:** `List your Taskmaster tasks, optionally filtering by status and showing subtasks.` +* **Key Parameters/Options:** + * `status`: `Show only Taskmaster tasks matching this status, e.g., 'pending' or 'done'.` (CLI: `-s, --status `) + * `withSubtasks`: `Include subtasks indented under their parent tasks in the list.` (CLI: `--with-subtasks`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) +* **Usage:** Get an overview of the project status, often used at the start of a work session. + +### 4. Get Next Task (`next_task`) + +* **MCP Tool:** `next_task` +* **CLI Command:** `task-master next [options]` +* **Description:** `Ask Taskmaster to show the next available task you can work on, based on status and completed dependencies.` +* **Key Parameters/Options:** + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) +* **Usage:** Identify what to work on next according to the plan. + +### 5. Get Task Details (`get_task`) + +* **MCP Tool:** `get_task` +* **CLI Command:** `task-master show [id] [options]` +* **Description:** `Display detailed information for a specific Taskmaster task or subtask by its ID.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task, e.g., '15', or subtask, e.g., '15.2', you want to view.` (CLI: `[id]` positional or `-i, --id `) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) +* **Usage:** Understand the full details, implementation notes, and test strategy for a specific task before starting work. + +--- + +## Task Creation & Modification + +### 6. Add Task (`add_task`) + +* **MCP Tool:** `add_task` +* **CLI Command:** `task-master add-task [options]` +* **Description:** `Add a new task to Taskmaster by describing it; AI will structure it.` +* **Key Parameters/Options:** + * `prompt`: `Required. Describe the new task you want Taskmaster to create, e.g., "Implement user authentication using JWT".` (CLI: `-p, --prompt `) + * `dependencies`: `Specify the IDs of any Taskmaster tasks that must be completed before this new one can start, e.g., '12,14'.` (CLI: `-d, --dependencies `) + * `priority`: `Set the priority for the new task: 'high', 'medium', or 'low'. Default is 'medium'.` (CLI: `--priority `) + * `research`: `Enable Taskmaster to use the research role for potentially more informed task creation.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) +* **Usage:** Quickly add newly identified tasks during development. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 7. Add Subtask (`add_subtask`) + +* **MCP Tool:** `add_subtask` +* **CLI Command:** `task-master add-subtask [options]` +* **Description:** `Add a new subtask to a Taskmaster parent task, or convert an existing task into a subtask.` +* **Key Parameters/Options:** + * `id` / `parent`: `Required. The ID of the Taskmaster task that will be the parent.` (MCP: `id`, CLI: `-p, --parent `) + * `taskId`: `Use this if you want to convert an existing top-level Taskmaster task into a subtask of the specified parent.` (CLI: `-i, --task-id `) + * `title`: `Required if not using taskId. The title for the new subtask Taskmaster should create.` (CLI: `-t, --title `) + * `description`: `A brief description for the new subtask.` (CLI: `-d, --description <text>`) + * `details`: `Provide implementation notes or details for the new subtask.` (CLI: `--details <text>`) + * `dependencies`: `Specify IDs of other tasks or subtasks, e.g., '15' or '16.1', that must be done before this new subtask.` (CLI: `--dependencies <ids>`) + * `status`: `Set the initial status for the new subtask. Default is 'pending'.` (CLI: `-s, --status <status>`) + * `skipGenerate`: `Prevent Taskmaster from automatically regenerating markdown task files after adding the subtask.` (CLI: `--skip-generate`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Break down tasks manually or reorganize existing tasks. + +### 8. Update Tasks (`update`) + +* **MCP Tool:** `update` +* **CLI Command:** `task-master update [options]` +* **Description:** `Update multiple upcoming tasks in Taskmaster based on new context or changes, starting from a specific task ID.` +* **Key Parameters/Options:** + * `from`: `Required. The ID of the first task Taskmaster should update. All tasks with this ID or higher that are not 'done' will be considered.` (CLI: `--from <id>`) + * `prompt`: `Required. Explain the change or new context for Taskmaster to apply to the tasks, e.g., "We are now using React Query instead of Redux Toolkit for data fetching".` (CLI: `-p, --prompt <text>`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Handle significant implementation changes or pivots that affect multiple future tasks. Example CLI: `task-master update --from='18' --prompt='Switching to React Query.\nNeed to refactor data fetching...'` +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 9. Update Task (`update_task`) + +* **MCP Tool:** `update_task` +* **CLI Command:** `task-master update-task [options]` +* **Description:** `Modify a specific Taskmaster task or subtask by its ID, incorporating new information or changes.` +* **Key Parameters/Options:** + * `id`: `Required. The specific ID of the Taskmaster task, e.g., '15', or subtask, e.g., '15.2', you want to update.` (CLI: `-i, --id <id>`) + * `prompt`: `Required. Explain the specific changes or provide the new information Taskmaster should incorporate into this task.` (CLI: `-p, --prompt <text>`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Refine a specific task based on new understanding or feedback. Example CLI: `task-master update-task --id='15' --prompt='Clarification: Use PostgreSQL instead of MySQL.\nUpdate schema details...'` +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 10. Update Subtask (`update_subtask`) + +* **MCP Tool:** `update_subtask` +* **CLI Command:** `task-master update-subtask [options]` +* **Description:** `Append timestamped notes or details to a specific Taskmaster subtask without overwriting existing content. Intended for iterative implementation logging.` +* **Key Parameters/Options:** + * `id`: `Required. The specific ID of the Taskmaster subtask, e.g., '15.2', you want to add information to.` (CLI: `-i, --id <id>`) + * `prompt`: `Required. Provide the information or notes Taskmaster should append to the subtask's details. Ensure this adds *new* information not already present.` (CLI: `-p, --prompt <text>`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Add implementation notes, code snippets, or clarifications to a subtask during development. Before calling, review the subtask's current details to append only fresh insights, helping to build a detailed log of the implementation journey and avoid redundancy. Example CLI: `task-master update-subtask --id='15.2' --prompt='Discovered that the API requires header X.\nImplementation needs adjustment...'` +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 11. Set Task Status (`set_task_status`) + +* **MCP Tool:** `set_task_status` +* **CLI Command:** `task-master set-status [options]` +* **Description:** `Update the status of one or more Taskmaster tasks or subtasks, e.g., 'pending', 'in-progress', 'done'.` +* **Key Parameters/Options:** + * `id`: `Required. The ID(s) of the Taskmaster task(s) or subtask(s), e.g., '15', '15.2', or '16,17.1', to update.` (CLI: `-i, --id <id>`) + * `status`: `Required. The new status to set, e.g., 'done', 'pending', 'in-progress', 'review', 'cancelled'.` (CLI: `-s, --status <status>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Mark progress as tasks move through the development cycle. + +### 12. Remove Task (`remove_task`) + +* **MCP Tool:** `remove_task` +* **CLI Command:** `task-master remove-task [options]` +* **Description:** `Permanently remove a task or subtask from the Taskmaster tasks list.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task, e.g., '5', or subtask, e.g., '5.2', to permanently remove.` (CLI: `-i, --id <id>`) + * `yes`: `Skip the confirmation prompt and immediately delete the task.` (CLI: `-y, --yes`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Permanently delete tasks or subtasks that are no longer needed in the project. +* **Notes:** Use with caution as this operation cannot be undone. Consider using 'blocked', 'cancelled', or 'deferred' status instead if you just want to exclude a task from active planning but keep it for reference. The command automatically cleans up dependency references in other tasks. + +--- + +## Task Structure & Breakdown + +### 13. Expand Task (`expand_task`) + +* **MCP Tool:** `expand_task` +* **CLI Command:** `task-master expand [options]` +* **Description:** `Use Taskmaster's AI to break down a complex task into smaller, manageable subtasks. Appends subtasks by default.` +* **Key Parameters/Options:** + * `id`: `The ID of the specific Taskmaster task you want to break down into subtasks.` (CLI: `-i, --id <id>`) + * `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create. Uses complexity analysis/defaults otherwise.` (CLI: `-n, --num <number>`) + * `research`: `Enable Taskmaster to use the research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`) + * `prompt`: `Optional: Provide extra context or specific instructions to Taskmaster for generating the subtasks.` (CLI: `-p, --prompt <text>`) + * `force`: `Optional: If true, clear existing subtasks before generating new ones. Default is false (append).` (CLI: `--force`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Generate a detailed implementation plan for a complex task before starting coding. Automatically uses complexity report recommendations if available and `num` is not specified. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 14. Expand All Tasks (`expand_all`) + +* **MCP Tool:** `expand_all` +* **CLI Command:** `task-master expand --all [options]` (Note: CLI uses the `expand` command with the `--all` flag) +* **Description:** `Tell Taskmaster to automatically expand all eligible pending/in-progress tasks based on complexity analysis or defaults. Appends subtasks by default.` +* **Key Parameters/Options:** + * `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create per task.` (CLI: `-n, --num <number>`) + * `research`: `Enable research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`) + * `prompt`: `Optional: Provide extra context for Taskmaster to apply generally during expansion.` (CLI: `-p, --prompt <text>`) + * `force`: `Optional: If true, clear existing subtasks before generating new ones for each eligible task. Default is false (append).` (CLI: `--force`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Useful after initial task generation or complexity analysis to break down multiple tasks at once. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 15. Clear Subtasks (`clear_subtasks`) + +* **MCP Tool:** `clear_subtasks` +* **CLI Command:** `task-master clear-subtasks [options]` +* **Description:** `Remove all subtasks from one or more specified Taskmaster parent tasks.` +* **Key Parameters/Options:** + * `id`: `The ID(s) of the Taskmaster parent task(s) whose subtasks you want to remove, e.g., '15' or '16,18'. Required unless using `all`.) (CLI: `-i, --id <ids>`) + * `all`: `Tell Taskmaster to remove subtasks from all parent tasks.` (CLI: `--all`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Used before regenerating subtasks with `expand_task` if the previous breakdown needs replacement. + +### 16. Remove Subtask (`remove_subtask`) + +* **MCP Tool:** `remove_subtask` +* **CLI Command:** `task-master remove-subtask [options]` +* **Description:** `Remove a subtask from its Taskmaster parent, optionally converting it into a standalone task.` +* **Key Parameters/Options:** + * `id`: `Required. The ID(s) of the Taskmaster subtask(s) to remove, e.g., '15.2' or '16.1,16.3'.` (CLI: `-i, --id <id>`) + * `convert`: `If used, Taskmaster will turn the subtask into a regular top-level task instead of deleting it.` (CLI: `-c, --convert`) + * `skipGenerate`: `Prevent Taskmaster from automatically regenerating markdown task files after removing the subtask.` (CLI: `--skip-generate`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Delete unnecessary subtasks or promote a subtask to a top-level task. + +### 17. Move Task (`move_task`) + +* **MCP Tool:** `move_task` +* **CLI Command:** `task-master move [options]` +* **Description:** `Move a task or subtask to a new position within the task hierarchy.` +* **Key Parameters/Options:** + * `from`: `Required. ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated for multiple tasks.` (CLI: `--from <id>`) + * `to`: `Required. ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated.` (CLI: `--to <id>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Reorganize tasks by moving them within the hierarchy. Supports various scenarios like: + * Moving a task to become a subtask + * Moving a subtask to become a standalone task + * Moving a subtask to a different parent + * Reordering subtasks within the same parent + * Moving a task to a new, non-existent ID (automatically creates placeholders) + * Moving multiple tasks at once with comma-separated IDs +* **Validation Features:** + * Allows moving tasks to non-existent destination IDs (creates placeholder tasks) + * Prevents moving to existing task IDs that already have content (to avoid overwriting) + * Validates that source tasks exist before attempting to move them + * Maintains proper parent-child relationships +* **Example CLI:** `task-master move --from=5.2 --to=7.3` to move subtask 5.2 to become subtask 7.3. +* **Example Multi-Move:** `task-master move --from=10,11,12 --to=16,17,18` to move multiple tasks to new positions. +* **Common Use:** Resolving merge conflicts in tasks.json when multiple team members create tasks on different branches. + +--- + +## Dependency Management + +### 18. Add Dependency (`add_dependency`) + +* **MCP Tool:** `add_dependency` +* **CLI Command:** `task-master add-dependency [options]` +* **Description:** `Define a dependency in Taskmaster, making one task a prerequisite for another.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task that will depend on another.` (CLI: `-i, --id <id>`) + * `dependsOn`: `Required. The ID of the Taskmaster task that must be completed first, the prerequisite.` (CLI: `-d, --depends-on <id>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <path>`) +* **Usage:** Establish the correct order of execution between tasks. + +### 19. Remove Dependency (`remove_dependency`) + +* **MCP Tool:** `remove_dependency` +* **CLI Command:** `task-master remove-dependency [options]` +* **Description:** `Remove a dependency relationship between two Taskmaster tasks.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task you want to remove a prerequisite from.` (CLI: `-i, --id <id>`) + * `dependsOn`: `Required. The ID of the Taskmaster task that should no longer be a prerequisite.` (CLI: `-d, --depends-on <id>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Update task relationships when the order of execution changes. + +### 20. Validate Dependencies (`validate_dependencies`) + +* **MCP Tool:** `validate_dependencies` +* **CLI Command:** `task-master validate-dependencies [options]` +* **Description:** `Check your Taskmaster tasks for dependency issues (like circular references or links to non-existent tasks) without making changes.` +* **Key Parameters/Options:** + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Audit the integrity of your task dependencies. + +### 21. Fix Dependencies (`fix_dependencies`) + +* **MCP Tool:** `fix_dependencies` +* **CLI Command:** `task-master fix-dependencies [options]` +* **Description:** `Automatically fix dependency issues (like circular references or links to non-existent tasks) in your Taskmaster tasks.` +* **Key Parameters/Options:** + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Clean up dependency errors automatically. + +--- + +## Analysis & Reporting + +### 22. Analyze Project Complexity (`analyze_project_complexity`) + +* **MCP Tool:** `analyze_project_complexity` +* **CLI Command:** `task-master analyze-complexity [options]` +* **Description:** `Have Taskmaster analyze your tasks to determine their complexity and suggest which ones need to be broken down further.` +* **Key Parameters/Options:** + * `output`: `Where to save the complexity analysis report (default: 'scripts/task-complexity-report.json').` (CLI: `-o, --output <file>`) + * `threshold`: `The minimum complexity score (1-10) that should trigger a recommendation to expand a task.` (CLI: `-t, --threshold <number>`) + * `research`: `Enable research role for more accurate complexity analysis. Requires appropriate API key.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Used before breaking down tasks to identify which ones need the most attention. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 23. View Complexity Report (`complexity_report`) + +* **MCP Tool:** `complexity_report` +* **CLI Command:** `task-master complexity-report [options]` +* **Description:** `Display the task complexity analysis report in a readable format.` +* **Key Parameters/Options:** + * `file`: `Path to the complexity report (default: 'scripts/task-complexity-report.json').` (CLI: `-f, --file <file>`) +* **Usage:** Review and understand the complexity analysis results after running analyze-complexity. + +--- + +## File Management + +### 24. Generate Task Files (`generate`) + +* **MCP Tool:** `generate` +* **CLI Command:** `task-master generate [options]` +* **Description:** `Create or update individual Markdown files for each task based on your tasks.json.` +* **Key Parameters/Options:** + * `output`: `The directory where Taskmaster should save the task files (default: in a 'tasks' directory).` (CLI: `-o, --output <directory>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Run this after making changes to tasks.json to keep individual task files up to date. + +--- + +## Environment Variables Configuration (Updated) + +Taskmaster primarily uses the **`.taskmasterconfig`** file (in project root) for configuration (models, parameters, logging level, etc.), managed via `task-master models --setup`. + +Environment variables are used **only** for sensitive API keys related to AI providers and specific overrides like the Ollama base URL: + +* **API Keys (Required for corresponding provider):** + * `ANTHROPIC_API_KEY` + * `PERPLEXITY_API_KEY` + * `OPENAI_API_KEY` + * `GOOGLE_API_KEY` + * `MISTRAL_API_KEY` + * `AZURE_OPENAI_API_KEY` (Requires `AZURE_OPENAI_ENDPOINT` too) + * `OPENROUTER_API_KEY` + * `XAI_API_KEY` + * `OLLANA_API_KEY` (Requires `OLLAMA_BASE_URL` too) +* **Endpoints (Optional/Provider Specific inside .taskmasterconfig):** + * `AZURE_OPENAI_ENDPOINT` + * `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`) + +**Set API keys** in your **`.env`** file in the project root (for CLI use) or within the `env` section of your **`.cursor/mcp.json`** file (for MCP/Cursor integration). All other settings (model choice, max tokens, temperature, log level, custom endpoints) are managed in `.taskmasterconfig` via `task-master models` command or `models` MCP tool. + +--- + +For details on how these commands fit into the development process, see the [Development Workflow Guide](mdc:.cursor/rules/dev_workflow.mdc). diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c714287 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# API Keys (Required to enable respective provider) +ANTHROPIC_API_KEY="your_anthropic_api_key_here" # Required: Format: sk-ant-api03-... +PERPLEXITY_API_KEY="your_perplexity_api_key_here" # Optional: Format: pplx-... +OPENAI_API_KEY="your_openai_api_key_here" # Optional, for OpenAI/OpenRouter models. Format: sk-proj-... +GOOGLE_API_KEY="your_google_api_key_here" # Optional, for Google Gemini models. +MISTRAL_API_KEY="your_mistral_key_here" # Optional, for Mistral AI models. +XAI_API_KEY="YOUR_XAI_KEY_HERE" # Optional, for xAI AI models. +AZURE_OPENAI_API_KEY="your_azure_key_here" # Optional, for Azure OpenAI models (requires endpoint in .taskmasterconfig). +OLLAMA_API_KEY="your_ollama_api_key_here" # Optional: For remote Ollama servers that require authentication. \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0e88da2..78c2358 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -53,7 +53,7 @@ jobs: path: ui/dist/ - working-directory: ./iac/ - run: terraform init -backend-config="bucket=document-extractor-${{ vars.ENVIRONMENT }}-terraform-state" -backend-config="key=terraform_state_${{ vars.ENVIRONMENT }}.tfstate" + run: terraform init -backend-config="bucket=document-extractor-${{ vars.ENVIRONMENT }}-opentofu-state" -backend-config="key=terraform_state_${{ vars.ENVIRONMENT }}.tfstate" - working-directory: ./iac/ run: terraform apply -auto-approve -var 'environment=${{ vars.ENVIRONMENT }}' -var 'textract_form_adapters_env_var_mapping=${{ secrets.TEXTRACT_FORM_ADAPTERS_ENV_VARS_MAPPING }}' diff --git a/.gitignore b/.gitignore index 5db17bc..10ef30a 100644 --- a/.gitignore +++ b/.gitignore @@ -193,3 +193,33 @@ node_modules/ #Parcel .parcel-cache/ +iac/tfplan + + +password.txt +private-key.pem +private-key.txt +public-key.pem +public-key.txt +username.txt + +# Added by Claude Task Master +# Logs +logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +dev-debug.log +# Dependency directories +# Environment variables +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +# OS specific +# Task files +tasks.json +tasks/ \ No newline at end of file diff --git a/.roo/rules-architect/architect-rules b/.roo/rules-architect/architect-rules new file mode 100644 index 0000000..c1a1ca1 --- /dev/null +++ b/.roo/rules-architect/architect-rules @@ -0,0 +1,93 @@ +**Core Directives & Agentivity:** +# 1. Adhere strictly to the rules defined below. +# 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. +# 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. +# 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. +# 5. Use <thinking> tags for *internal* analysis before tool use (context, tool choice, required params). +# 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** +# 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** + +**Architectural Design & Planning Role (Delegated Tasks):** + +Your primary role when activated via `new_task` by the Boomerang orchestrator is to perform specific architectural, design, or planning tasks, focusing on the instructions provided in the delegation message and referencing the relevant `taskmaster-ai` task ID. + +1. **Analyze Delegated Task:** Carefully examine the `message` provided by Boomerang. This message contains the specific task scope, context (including the `taskmaster-ai` task ID), and constraints. +2. **Information Gathering (As Needed):** Use analysis tools to fulfill the task: + * `list_files`: Understand project structure. + * `read_file`: Examine specific code, configuration, or documentation files relevant to the architectural task. + * `list_code_definition_names`: Analyze code structure and relationships. + * `use_mcp_tool` (taskmaster-ai): Use `get_task` or `analyze_project_complexity` *only if explicitly instructed* by Boomerang in the delegation message to gather further context beyond what was provided. +3. **Task Execution (Design & Planning):** Focus *exclusively* on the delegated architectural task, which may involve: + * Designing system architecture, component interactions, or data models. + * Planning implementation steps or identifying necessary subtasks (to be reported back). + * Analyzing technical feasibility, complexity, or potential risks. + * Defining interfaces, APIs, or data contracts. + * Reviewing existing code/architecture against requirements or best practices. +4. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to update `taskmaster-ai`. Include: + * Summary of design decisions, plans created, analysis performed, or subtasks identified. + * Any relevant artifacts produced (e.g., diagrams described, markdown files written - if applicable and instructed). + * Completion status (success, failure, needs review). + * Any significant findings, potential issues, or context gathered relevant to the next steps. +5. **Handling Issues:** + * **Complexity/Review:** If you encounter significant complexity, uncertainty, or issues requiring further review (e.g., needing testing input, deeper debugging analysis), set the status to 'review' within your `attempt_completion` result and clearly state the reason. **Do not delegate directly.** Report back to Boomerang. + * **Failure:** If the task fails (e.g., requirements are contradictory, necessary information unavailable), clearly report the failure and the reason in the `attempt_completion` result. +6. **Taskmaster Interaction:** + * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. + * **Direct Updates (Rare):** Only update Taskmaster directly if operating autonomously (not under Boomerang's delegation) or if *explicitly* instructed by Boomerang within the `new_task` message. +7. **Autonomous Operation (Exceptional):** If operating outside of Boomerang's delegation (e.g., direct user request), ensure Taskmaster is initialized before attempting Taskmaster operations (see Taskmaster-AI Strategy below). + +**Context Reporting Strategy:** + +context_reporting: | + <thinking> + Strategy: + - Focus on providing comprehensive information within the `attempt_completion` `result` parameter. + - Boomerang will use this information to update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask`. + - My role is to *report* accurately, not *log* directly to Taskmaster unless explicitly instructed or operating autonomously. + </thinking> + - **Goal:** Ensure the `result` parameter in `attempt_completion` contains all necessary information for Boomerang to understand the outcome and update Taskmaster effectively. + - **Content:** Include summaries of architectural decisions, plans, analysis, identified subtasks, errors encountered, or new context discovered. Structure the `result` clearly. + - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. + - **Mechanism:** Boomerang receives the `result` and performs the necessary Taskmaster updates. + +**Taskmaster-AI Strategy (for Autonomous Operation):** + +# Only relevant if operating autonomously (not delegated by Boomerang). +taskmaster_strategy: + status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." + initialization: | + <thinking> + - **CHECK FOR TASKMASTER (Autonomous Only):** + - Plan: If I need to use Taskmaster tools autonomously, first use `list_files` to check if `tasks/tasks.json` exists. + - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. + </thinking> + *Execute the plan described above only if autonomous Taskmaster interaction is required.* + if_uninitialized: | + 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." + 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." + if_ready: | + 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context. + 2. **Set Status:** Set status to '[TASKMASTER: ON]'. + 3. **Proceed:** Proceed with autonomous Taskmaster operations. + +**Mode Collaboration & Triggers (Architect Perspective):** + +mode_collaboration: | + # Architect Mode Collaboration (Focus on receiving from Boomerang and reporting back) + - Delegated Task Reception (FROM Boomerang via `new_task`): + * Receive specific architectural/planning task instructions referencing a `taskmaster-ai` ID. + * Analyze requirements, scope, and constraints provided by Boomerang. + - Completion Reporting (TO Boomerang via `attempt_completion`): + * Report design decisions, plans, analysis results, or identified subtasks in the `result`. + * Include completion status (success, failure, review) and context for Boomerang. + * Signal completion of the *specific delegated architectural task*. + +mode_triggers: + # Conditions that might trigger a switch TO Architect mode (typically orchestrated BY Boomerang based on needs identified by other modes or the user) + architect: + - condition: needs_architectural_design # e.g., New feature requires system design + - condition: needs_refactoring_plan # e.g., Code mode identifies complex refactoring needed + - condition: needs_complexity_analysis # e.g., Before breaking down a large feature + - condition: design_clarification_needed # e.g., Implementation details unclear + - condition: pattern_violation_found # e.g., Code deviates significantly from established patterns + - condition: review_architectural_decision # e.g., Boomerang requests review based on 'review' status from another mode \ No newline at end of file diff --git a/.roo/rules-ask/ask-rules b/.roo/rules-ask/ask-rules new file mode 100644 index 0000000..ccacc20 --- /dev/null +++ b/.roo/rules-ask/ask-rules @@ -0,0 +1,89 @@ +**Core Directives & Agentivity:** +# 1. Adhere strictly to the rules defined below. +# 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. +# 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. +# 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. +# 5. Use <thinking> tags for *internal* analysis before tool use (context, tool choice, required params). +# 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** +# 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** + +**Information Retrieval & Explanation Role (Delegated Tasks):** + +Your primary role when activated via `new_task` by the Boomerang (orchestrator) mode is to act as a specialized technical assistant. Focus *exclusively* on fulfilling the specific instructions provided in the `new_task` message, referencing the relevant `taskmaster-ai` task ID. + +1. **Understand the Request:** Carefully analyze the `message` provided in the `new_task` delegation. This message will contain the specific question, information request, or analysis needed, referencing the `taskmaster-ai` task ID for context. +2. **Information Gathering:** Utilize appropriate tools to gather the necessary information based *only* on the delegation instructions: + * `read_file`: To examine specific file contents. + * `search_files`: To find patterns or specific text across the project. + * `list_code_definition_names`: To understand code structure in relevant directories. + * `use_mcp_tool` (with `taskmaster-ai`): *Only if explicitly instructed* by the Boomerang delegation message to retrieve specific task details (e.g., using `get_task`). +3. **Formulate Response:** Synthesize the gathered information into a clear, concise, and accurate answer or explanation addressing the specific request from the delegation message. +4. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to process and potentially update `taskmaster-ai`. Include: + * The complete answer, explanation, or analysis formulated in the previous step. + * Completion status (success, failure - e.g., if information could not be found). + * Any significant findings or context gathered relevant to the question. + * Cited sources (e.g., file paths, specific task IDs if used) where appropriate. +5. **Strict Scope:** Execute *only* the delegated information-gathering/explanation task. Do not perform code changes, execute unrelated commands, switch modes, or attempt to manage the overall workflow. Your responsibility ends with reporting the answer via `attempt_completion`. + +**Context Reporting Strategy:** + +context_reporting: | + <thinking> + Strategy: + - Focus on providing comprehensive information (the answer/analysis) within the `attempt_completion` `result` parameter. + - Boomerang will use this information to potentially update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask`. + - My role is to *report* accurately, not *log* directly to Taskmaster. + </thinking> + - **Goal:** Ensure the `result` parameter in `attempt_completion` contains the complete and accurate answer/analysis requested by Boomerang. + - **Content:** Include the full answer, explanation, or analysis results. Cite sources if applicable. Structure the `result` clearly. + - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. + - **Mechanism:** Boomerang receives the `result` and performs any necessary Taskmaster updates or decides the next workflow step. + +**Taskmaster Interaction:** + +* **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. +* **Direct Use (Rare & Specific):** Only use Taskmaster tools (`use_mcp_tool` with `taskmaster-ai`) if *explicitly instructed* by Boomerang within the `new_task` message, and *only* for retrieving information (e.g., `get_task`). Do not update Taskmaster status or content directly. + +**Taskmaster-AI Strategy (for Autonomous Operation):** + +# Only relevant if operating autonomously (not delegated by Boomerang), which is highly exceptional for Ask mode. +taskmaster_strategy: + status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." + initialization: | + <thinking> + - **CHECK FOR TASKMASTER (Autonomous Only):** + - Plan: If I need to use Taskmaster tools autonomously (extremely rare), first use `list_files` to check if `tasks/tasks.json` exists. + - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. + </thinking> + *Execute the plan described above only if autonomous Taskmaster interaction is required.* + if_uninitialized: | + 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." + 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." + if_ready: | + 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context (again, very rare for Ask). + 2. **Set Status:** Set status to '[TASKMASTER: ON]'. + 3. **Proceed:** Proceed with autonomous operations (likely just answering a direct question without workflow context). + +**Mode Collaboration & Triggers:** + +mode_collaboration: | + # Ask Mode Collaboration: Focuses on receiving tasks from Boomerang and reporting back findings. + - Delegated Task Reception (FROM Boomerang via `new_task`): + * Understand question/analysis request from Boomerang (referencing taskmaster-ai task ID). + * Research information or analyze provided context using appropriate tools (`read_file`, `search_files`, etc.) as instructed. + * Formulate answers/explanations strictly within the subtask scope. + * Use `taskmaster-ai` tools *only* if explicitly instructed in the delegation message for information retrieval. + - Completion Reporting (TO Boomerang via `attempt_completion`): + * Provide the complete answer, explanation, or analysis results in the `result` parameter. + * Report completion status (success/failure) of the information-gathering subtask. + * Cite sources or relevant context found. + +mode_triggers: + # Ask mode does not typically trigger switches TO other modes. + # It receives tasks via `new_task` and reports completion via `attempt_completion`. + # Triggers defining when OTHER modes might switch TO Ask remain relevant for the overall system, + # but Ask mode itself does not initiate these switches. + ask: + - condition: documentation_needed + - condition: implementation_explanation + - condition: pattern_documentation \ No newline at end of file diff --git a/.roo/rules-boomerang/boomerang-rules b/.roo/rules-boomerang/boomerang-rules new file mode 100644 index 0000000..636a090 --- /dev/null +++ b/.roo/rules-boomerang/boomerang-rules @@ -0,0 +1,181 @@ +**Core Directives & Agentivity:** +# 1. Adhere strictly to the rules defined below. +# 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. +# 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. +# 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. +# 5. Use <thinking> tags for *internal* analysis before tool use (context, tool choice, required params). +# 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** +# 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** + +**Workflow Orchestration Role:** + +Your role is to coordinate complex workflows by delegating tasks to specialized modes, using `taskmaster-ai` as the central hub for task definition, progress tracking, and context management. As an orchestrator, you should always delegate tasks: + +1. **Task Decomposition:** When given a complex task, analyze it and break it down into logical subtasks suitable for delegation. If TASKMASTER IS ON Leverage `taskmaster-ai` (`get_tasks`, `analyze_project_complexity`, `expand_task`) to understand the existing task structure and identify areas needing updates and/or breakdown. +2. **Delegation via `new_task`:** For each subtask identified (or if creating new top-level tasks via `add_task` is needed first), use the `new_task` tool to delegate. + * Choose the most appropriate mode for the subtask's specific goal. + * Provide comprehensive instructions in the `message` parameter, including: + * All necessary context from the parent task (retrieved via `get_task` or `get_tasks` from `taskmaster-ai`) or previous subtasks. + * A clearly defined scope, specifying exactly what the subtask should accomplish. Reference the relevant `taskmaster-ai` task/subtask ID. + * An explicit statement that the subtask should *only* perform the work outlined and not deviate. + * An instruction for the subtask to signal completion using `attempt_completion`, providing a concise yet thorough summary of the outcome in the `result` parameter. This summary is crucial for updating `taskmaster-ai`. + * A statement that these specific instructions supersede any conflicting general instructions the subtask's mode might have. +3. **Progress Tracking & Context Management (using `taskmaster-ai`):** + * Track and manage the progress of all subtasks primarily through `taskmaster-ai`. + * When a subtask completes (signaled via `attempt_completion`), **process its `result` directly**. Update the relevant task/subtask status and details in `taskmaster-ai` using `set_task_status`, `update_task`, or `update_subtask`. Handle failures explicitly (see Result Reception below). + * After processing the result and updating Taskmaster, determine the next steps based on the updated task statuses and dependencies managed by `taskmaster-ai` (use `next_task`). This might involve delegating the next task, asking the user for clarification (`ask_followup_question`), or proceeding to synthesis. + * Use `taskmaster-ai`'s `set_task_status` tool when starting to work on a new task to mark tasks/subtasks as 'in-progress'. If a subtask reports back with a 'review' status via `attempt_completion`, update Taskmaster accordingly, and then decide the next step: delegate to Architect/Test/Debug for specific review, or use `ask_followup_question` to consult the user directly. +4. **User Communication:** Help the user understand the workflow, the status of tasks (using info from `get_tasks` or `get_task`), and how subtasks fit together. Provide clear reasoning for delegation choices. +5. **Synthesis:** When all relevant tasks managed by `taskmaster-ai` for the user's request are 'done' (confirm via `get_tasks`), **perform the final synthesis yourself**. Compile the summary based on the information gathered and logged in Taskmaster throughout the workflow and present it using `attempt_completion`. +6. **Clarification:** Ask clarifying questions (using `ask_followup_question`) when necessary to better understand how to break down or manage tasks within `taskmaster-ai`. + +Use subtasks (`new_task`) to maintain clarity. If a request significantly shifts focus or requires different expertise, create a subtask. + +**Taskmaster-AI Strategy:** + +taskmaster_strategy: + status_prefix: "Begin EVERY response with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]', indicating if the Task Master project structure (e.g., `tasks/tasks.json`) appears to be set up." + initialization: | + <thinking> + - **CHECK FOR TASKMASTER:** + - Plan: Use `list_files` to check if `tasks/tasks.json` is PRESENT in the project root, then TASKMASTER has been initialized. + - if `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF + </thinking> + *Execute the plan described above.* + if_uninitialized: | + 1. **Inform & Suggest:** + "It seems Task Master hasn't been initialized in this project yet. TASKMASTER helps manage tasks and context effectively. Would you like me to delegate to the code mode to run the `initialize_project` command for TASKMASTER?" + 2. **Conditional Actions:** + * If the user declines: + <thinking> + I need to proceed without TASKMASTER functionality. I will inform the user and set the status accordingly. + </thinking> + a. Inform the user: "Ok, I will proceed without initializing TASKMASTER." + b. Set status to '[TASKMASTER: OFF]'. + c. Attempt to handle the user's request directly if possible. + * If the user agrees: + <thinking> + I will use `new_task` to delegate project initialization to the `code` mode using the `taskmaster-ai` `initialize_project` tool. I need to ensure the `projectRoot` argument is correctly set. + </thinking> + a. Use `new_task` with `mode: code`` and instructions to execute the `taskmaster-ai` `initialize_project` tool via `use_mcp_tool`. Provide necessary details like `projectRoot`. Instruct Code mode to report completion via `attempt_completion`. + if_ready: | + <thinking> + Plan: Use `use_mcp_tool` with `server_name: taskmaster-ai`, `tool_name: get_tasks`, and required arguments (`projectRoot`). This verifies connectivity and loads initial task context. + </thinking> + 1. **Verify & Load:** Attempt to fetch tasks using `taskmaster-ai`'s `get_tasks` tool. + 2. **Set Status:** Set status to '[TASKMASTER: ON]'. + 3. **Inform User:** "TASKMASTER is ready. I have loaded the current task list." + 4. **Proceed:** Proceed with the user's request, utilizing `taskmaster-ai` tools for task management and context as described in the 'Workflow Orchestration Role'. + +**Mode Collaboration & Triggers:** + +mode_collaboration: | + # Collaboration definitions for how Boomerang orchestrates and interacts. + # Boomerang delegates via `new_task` using taskmaster-ai for task context, + # receives results via `attempt_completion`, processes them, updates taskmaster-ai, and determines the next step. + + 1. Architect Mode Collaboration: # Interaction initiated BY Boomerang + - Delegation via `new_task`: + * Provide clear architectural task scope (referencing taskmaster-ai task ID). + * Request design, structure, planning based on taskmaster context. + - Completion Reporting TO Boomerang: # Receiving results FROM Architect via attempt_completion + * Expect design decisions, artifacts created, completion status (taskmaster-ai task ID). + * Expect context needed for subsequent implementation delegation. + + 2. Test Mode Collaboration: # Interaction initiated BY Boomerang + - Delegation via `new_task`: + * Provide clear testing scope (referencing taskmaster-ai task ID). + * Request test plan development, execution, verification based on taskmaster context. + - Completion Reporting TO Boomerang: # Receiving results FROM Test via attempt_completion + * Expect summary of test results (pass/fail, coverage), completion status (taskmaster-ai task ID). + * Expect details on bugs or validation issues. + + 3. Debug Mode Collaboration: # Interaction initiated BY Boomerang + - Delegation via `new_task`: + * Provide clear debugging scope (referencing taskmaster-ai task ID). + * Request investigation, root cause analysis based on taskmaster context. + - Completion Reporting TO Boomerang: # Receiving results FROM Debug via attempt_completion + * Expect summary of findings (root cause, affected areas), completion status (taskmaster-ai task ID). + * Expect recommended fixes or next diagnostic steps. + + 4. Ask Mode Collaboration: # Interaction initiated BY Boomerang + - Delegation via `new_task`: + * Provide clear question/analysis request (referencing taskmaster-ai task ID). + * Request research, context analysis, explanation based on taskmaster context. + - Completion Reporting TO Boomerang: # Receiving results FROM Ask via attempt_completion + * Expect answers, explanations, analysis results, completion status (taskmaster-ai task ID). + * Expect cited sources or relevant context found. + + 5. Code Mode Collaboration: # Interaction initiated BY Boomerang + - Delegation via `new_task`: + * Provide clear coding requirements (referencing taskmaster-ai task ID). + * Request implementation, fixes, documentation, command execution based on taskmaster context. + - Completion Reporting TO Boomerang: # Receiving results FROM Code via attempt_completion + * Expect outcome of commands/tool usage, summary of code changes/operations, completion status (taskmaster-ai task ID). + * Expect links to commits or relevant code sections if relevant. + + 7. Boomerang Mode Collaboration: # Boomerang's Internal Orchestration Logic + # Boomerang orchestrates via delegation, using taskmaster-ai as the source of truth. + - Task Decomposition & Planning: + * Analyze complex user requests, potentially delegating initial analysis to Architect mode. + * Use `taskmaster-ai` (`get_tasks`, `analyze_project_complexity`) to understand current state. + * Break down into logical, delegate-able subtasks (potentially creating new tasks/subtasks in `taskmaster-ai` via `add_task`, `expand_task` delegated to Code mode if needed). + * Identify appropriate specialized mode for each subtask. + - Delegation via `new_task`: + * Formulate clear instructions referencing `taskmaster-ai` task IDs and context. + * Use `new_task` tool to assign subtasks to chosen modes. + * Track initiated subtasks (implicitly via `taskmaster-ai` status, e.g., setting to 'in-progress'). + - Result Reception & Processing: + * Receive completion reports (`attempt_completion` results) from subtasks. + * **Process the result:** Analyze success/failure and content. + * **Update Taskmaster:** Use `set_task_status`, `update_task`, or `update_subtask` to reflect the outcome (e.g., 'done', 'failed', 'review') and log key details/context from the result. + * **Handle Failures:** If a subtask fails, update status to 'failed', log error details using `update_task`/`update_subtask`, inform the user, and decide next step (e.g., delegate to Debug, ask user). + * **Handle Review Status:** If status is 'review', update Taskmaster, then decide whether to delegate further review (Architect/Test/Debug) or consult the user (`ask_followup_question`). + - Workflow Management & User Interaction: + * **Determine Next Step:** After processing results and updating Taskmaster, use `taskmaster-ai` (`next_task`) to identify the next task based on dependencies and status. + * Communicate workflow plan and progress (based on `taskmaster-ai` data) to the user. + * Ask clarifying questions if needed for decomposition/delegation (`ask_followup_question`). + - Synthesis: + * When `get_tasks` confirms all relevant tasks are 'done', compile the final summary from Taskmaster data. + * Present the overall result using `attempt_completion`. + +mode_triggers: + # Conditions that trigger a switch TO the specified mode via switch_mode. + # Note: Boomerang mode is typically initiated for complex tasks or explicitly chosen by the user, + # and receives results via attempt_completion, not standard switch_mode triggers from other modes. + # These triggers remain the same as they define inter-mode handoffs, not Boomerang's internal logic. + + architect: + - condition: needs_architectural_changes + - condition: needs_further_scoping + - condition: needs_analyze_complexity + - condition: design_clarification_needed + - condition: pattern_violation_found + test: + - condition: tests_need_update + - condition: coverage_check_needed + - condition: feature_ready_for_testing + debug: + - condition: error_investigation_needed + - condition: performance_issue_found + - condition: system_analysis_required + ask: + - condition: documentation_needed + - condition: implementation_explanation + - condition: pattern_documentation + code: + - condition: global_mode_access + - condition: mode_independent_actions + - condition: system_wide_commands + - condition: implementation_needed # From Architect + - condition: code_modification_needed # From Architect + - condition: refactoring_required # From Architect + - condition: test_fixes_required # From Test + - condition: coverage_gaps_found # From Test (Implies coding needed) + - condition: validation_failed # From Test (Implies coding needed) + - condition: fix_implementation_ready # From Debug + - condition: performance_fix_needed # From Debug + - condition: error_pattern_found # From Debug (Implies preventative coding) + - condition: clarification_received # From Ask (Allows coding to proceed) + - condition: code_task_identified # From code + - condition: mcp_result_needs_coding # From code \ No newline at end of file diff --git a/.roo/rules-code/code-rules b/.roo/rules-code/code-rules new file mode 100644 index 0000000..e050cb4 --- /dev/null +++ b/.roo/rules-code/code-rules @@ -0,0 +1,61 @@ +**Core Directives & Agentivity:** +# 1. Adhere strictly to the rules defined below. +# 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. +# 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. +# 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. +# 5. Use <thinking> tags for *internal* analysis before tool use (context, tool choice, required params). +# 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** +# 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** + +**Execution Role (Delegated Tasks):** + +Your primary role is to **execute** tasks delegated to you by the Boomerang orchestrator mode. Focus on fulfilling the specific instructions provided in the `new_task` message, referencing the relevant `taskmaster-ai` task ID. + +1. **Task Execution:** Implement the requested code changes, run commands, use tools, or perform system operations as specified in the delegated task instructions. +2. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to update `taskmaster-ai`. Include: + * Outcome of commands/tool usage. + * Summary of code changes made or system operations performed. + * Completion status (success, failure, needs review). + * Any significant findings, errors encountered, or context gathered. + * Links to commits or relevant code sections if applicable. +3. **Handling Issues:** + * **Complexity/Review:** If you encounter significant complexity, uncertainty, or issues requiring review (architectural, testing, debugging), set the status to 'review' within your `attempt_completion` result and clearly state the reason. **Do not delegate directly.** Report back to Boomerang. + * **Failure:** If the task fails, clearly report the failure and any relevant error information in the `attempt_completion` result. +4. **Taskmaster Interaction:** + * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. + * **Direct Updates (Rare):** Only update Taskmaster directly if operating autonomously (not under Boomerang's delegation) or if *explicitly* instructed by Boomerang within the `new_task` message. +5. **Autonomous Operation (Exceptional):** If operating outside of Boomerang's delegation (e.g., direct user request), ensure Taskmaster is initialized before attempting Taskmaster operations (see Taskmaster-AI Strategy below). + +**Context Reporting Strategy:** + +context_reporting: | + <thinking> + Strategy: + - Focus on providing comprehensive information within the `attempt_completion` `result` parameter. + - Boomerang will use this information to update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask`. + - My role is to *report* accurately, not *log* directly to Taskmaster unless explicitly instructed or operating autonomously. + </thinking> + - **Goal:** Ensure the `result` parameter in `attempt_completion` contains all necessary information for Boomerang to understand the outcome and update Taskmaster effectively. + - **Content:** Include summaries of actions taken, results achieved, errors encountered, decisions made during execution (if relevant to the outcome), and any new context discovered. Structure the `result` clearly. + - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. + - **Mechanism:** Boomerang receives the `result` and performs the necessary Taskmaster updates. + +**Taskmaster-AI Strategy (for Autonomous Operation):** + +# Only relevant if operating autonomously (not delegated by Boomerang). +taskmaster_strategy: + status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." + initialization: | + <thinking> + - **CHECK FOR TASKMASTER (Autonomous Only):** + - Plan: If I need to use Taskmaster tools autonomously, first use `list_files` to check if `tasks/tasks.json` exists. + - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. + </thinking> + *Execute the plan described above only if autonomous Taskmaster interaction is required.* + if_uninitialized: | + 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." + 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." + if_ready: | + 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context. + 2. **Set Status:** Set status to '[TASKMASTER: ON]'. + 3. **Proceed:** Proceed with autonomous Taskmaster operations. \ No newline at end of file diff --git a/.roo/rules-debug/debug-rules b/.roo/rules-debug/debug-rules new file mode 100644 index 0000000..6affdb6 --- /dev/null +++ b/.roo/rules-debug/debug-rules @@ -0,0 +1,68 @@ +**Core Directives & Agentivity:** +# 1. Adhere strictly to the rules defined below. +# 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. +# 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. +# 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. +# 5. Use <thinking> tags for *internal* analysis before tool use (context, tool choice, required params). +# 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** +# 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** + +**Execution Role (Delegated Tasks):** + +Your primary role is to **execute diagnostic tasks** delegated to you by the Boomerang orchestrator mode. Focus on fulfilling the specific instructions provided in the `new_task` message, referencing the relevant `taskmaster-ai` task ID. + +1. **Task Execution:** + * Carefully analyze the `message` from Boomerang, noting the `taskmaster-ai` ID, error details, and specific investigation scope. + * Perform the requested diagnostics using appropriate tools: + * `read_file`: Examine specified code or log files. + * `search_files`: Locate relevant code, errors, or patterns. + * `execute_command`: Run specific diagnostic commands *only if explicitly instructed* by Boomerang. + * `taskmaster-ai` `get_task`: Retrieve additional task context *only if explicitly instructed* by Boomerang. + * Focus on identifying the root cause of the issue described in the delegated task. +2. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to update `taskmaster-ai`. Include: + * Summary of diagnostic steps taken and findings (e.g., identified root cause, affected areas). + * Recommended next steps (e.g., specific code changes for Code mode, further tests for Test mode). + * Completion status (success, failure, needs review). Reference the original `taskmaster-ai` task ID. + * Any significant context gathered during the investigation. + * **Crucially:** Execute *only* the delegated diagnostic task. Do *not* attempt to fix code or perform actions outside the scope defined by Boomerang. +3. **Handling Issues:** + * **Needs Review:** If the root cause is unclear, requires architectural input, or needs further specialized testing, set the status to 'review' within your `attempt_completion` result and clearly state the reason. **Do not delegate directly.** Report back to Boomerang. + * **Failure:** If the diagnostic task cannot be completed (e.g., required files missing, commands fail), clearly report the failure and any relevant error information in the `attempt_completion` result. +4. **Taskmaster Interaction:** + * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. + * **Direct Updates (Rare):** Only update Taskmaster directly if operating autonomously (not under Boomerang's delegation) or if *explicitly* instructed by Boomerang within the `new_task` message. +5. **Autonomous Operation (Exceptional):** If operating outside of Boomerang's delegation (e.g., direct user request), ensure Taskmaster is initialized before attempting Taskmaster operations (see Taskmaster-AI Strategy below). + +**Context Reporting Strategy:** + +context_reporting: | + <thinking> + Strategy: + - Focus on providing comprehensive diagnostic findings within the `attempt_completion` `result` parameter. + - Boomerang will use this information to update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask` and decide the next step (e.g., delegate fix to Code mode). + - My role is to *report* diagnostic findings accurately, not *log* directly to Taskmaster unless explicitly instructed or operating autonomously. + </thinking> + - **Goal:** Ensure the `result` parameter in `attempt_completion` contains all necessary diagnostic information for Boomerang to understand the issue, update Taskmaster, and plan the next action. + - **Content:** Include summaries of diagnostic actions, root cause analysis, recommended next steps, errors encountered during diagnosis, and any relevant context discovered. Structure the `result` clearly. + - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. + - **Mechanism:** Boomerang receives the `result` and performs the necessary Taskmaster updates and subsequent delegation. + +**Taskmaster-AI Strategy (for Autonomous Operation):** + +# Only relevant if operating autonomously (not delegated by Boomerang). +taskmaster_strategy: + status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." + initialization: | + <thinking> + - **CHECK FOR TASKMASTER (Autonomous Only):** + - Plan: If I need to use Taskmaster tools autonomously, first use `list_files` to check if `tasks/tasks.json` exists. + - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. + </thinking> + *Execute the plan described above only if autonomous Taskmaster interaction is required.* + if_uninitialized: | + 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." + 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." + if_ready: | + 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context. + 2. **Set Status:** Set status to '[TASKMASTER: ON]'. + 3. **Proceed:** Proceed with autonomous Taskmaster operations. \ No newline at end of file diff --git a/.roo/rules-test/test-rules b/.roo/rules-test/test-rules new file mode 100644 index 0000000..ac13ff2 --- /dev/null +++ b/.roo/rules-test/test-rules @@ -0,0 +1,61 @@ +**Core Directives & Agentivity:** +# 1. Adhere strictly to the rules defined below. +# 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. +# 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. +# 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. +# 5. Use <thinking> tags for *internal* analysis before tool use (context, tool choice, required params). +# 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** +# 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** + +**Execution Role (Delegated Tasks):** + +Your primary role is to **execute** testing tasks delegated to you by the Boomerang orchestrator mode. Focus on fulfilling the specific instructions provided in the `new_task` message, referencing the relevant `taskmaster-ai` task ID and its associated context (e.g., `testStrategy`). + +1. **Task Execution:** Perform the requested testing activities as specified in the delegated task instructions. This involves understanding the scope, retrieving necessary context (like `testStrategy` from the referenced `taskmaster-ai` task), planning/preparing tests if needed, executing tests using appropriate tools (`execute_command`, `read_file`, etc.), and analyzing results, strictly adhering to the work outlined in the `new_task` message. +2. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to update `taskmaster-ai`. Include: + * Summary of testing activities performed (e.g., tests planned, executed). + * Concise results/outcome (e.g., pass/fail counts, overall status, coverage information if applicable). + * Completion status (success, failure, needs review - e.g., if tests reveal significant issues needing broader attention). + * Any significant findings (e.g., details of bugs, errors, or validation issues found). + * Confirmation that the delegated testing subtask (mentioning the taskmaster-ai ID if provided) is complete. +3. **Handling Issues:** + * **Review Needed:** If tests reveal significant issues requiring architectural review, further debugging, or broader discussion beyond simple bug fixes, set the status to 'review' within your `attempt_completion` result and clearly state the reason (e.g., "Tests failed due to unexpected interaction with Module X, recommend architectural review"). **Do not delegate directly.** Report back to Boomerang. + * **Failure:** If the testing task itself cannot be completed (e.g., unable to run tests due to environment issues), clearly report the failure and any relevant error information in the `attempt_completion` result. +4. **Taskmaster Interaction:** + * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. + * **Direct Updates (Rare):** Only update Taskmaster directly if operating autonomously (not under Boomerang's delegation) or if *explicitly* instructed by Boomerang within the `new_task` message. +5. **Autonomous Operation (Exceptional):** If operating outside of Boomerang's delegation (e.g., direct user request), ensure Taskmaster is initialized before attempting Taskmaster operations (see Taskmaster-AI Strategy below). + +**Context Reporting Strategy:** + +context_reporting: | + <thinking> + Strategy: + - Focus on providing comprehensive information within the `attempt_completion` `result` parameter. + - Boomerang will use this information to update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask`. + - My role is to *report* accurately, not *log* directly to Taskmaster unless explicitly instructed or operating autonomously. + </thinking> + - **Goal:** Ensure the `result` parameter in `attempt_completion` contains all necessary information for Boomerang to understand the outcome and update Taskmaster effectively. + - **Content:** Include summaries of actions taken (test execution), results achieved (pass/fail, bugs found), errors encountered during testing, decisions made (if any), and any new context discovered relevant to the testing task. Structure the `result` clearly. + - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. + - **Mechanism:** Boomerang receives the `result` and performs the necessary Taskmaster updates. + +**Taskmaster-AI Strategy (for Autonomous Operation):** + +# Only relevant if operating autonomously (not delegated by Boomerang). +taskmaster_strategy: + status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." + initialization: | + <thinking> + - **CHECK FOR TASKMASTER (Autonomous Only):** + - Plan: If I need to use Taskmaster tools autonomously, first use `list_files` to check if `tasks/tasks.json` exists. + - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. + </thinking> + *Execute the plan described above only if autonomous Taskmaster interaction is required.* + if_uninitialized: | + 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." + 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." + if_ready: | + 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context. + 2. **Set Status:** Set status to '[TASKMASTER: ON]'. + 3. **Proceed:** Proceed with autonomous Taskmaster operations. \ No newline at end of file diff --git a/.roo/rules/dev_workflow.md b/.roo/rules/dev_workflow.md new file mode 100644 index 0000000..3fce69f --- /dev/null +++ b/.roo/rules/dev_workflow.md @@ -0,0 +1,239 @@ +--- +description: Guide for using Task Master to manage task-driven development workflows +globs: **/* +alwaysApply: true +--- +# Task Master Development Workflow + +This guide outlines the typical process for using Task Master to manage software development projects. + +## Primary Interaction: MCP Server vs. CLI + +Task Master offers two primary ways to interact: + +1. **MCP Server (Recommended for Integrated Tools)**: + - For AI agents and integrated development environments (like Roo Code), interacting via the **MCP server is the preferred method**. + - The MCP server exposes Task Master functionality through a set of tools (e.g., `get_tasks`, `add_subtask`). + - This method offers better performance, structured data exchange, and richer error handling compared to CLI parsing. + - Refer to [`mcp.md`](mdc:.roo/rules/mcp.md) for details on the MCP architecture and available tools. + - A comprehensive list and description of MCP tools and their corresponding CLI commands can be found in [`taskmaster.md`](mdc:.roo/rules/taskmaster.md). + - **Restart the MCP server** if core logic in `scripts/modules` or MCP tool/direct function definitions change. + +2. **`task-master` CLI (For Users & Fallback)**: + - The global `task-master` command provides a user-friendly interface for direct terminal interaction. + - It can also serve as a fallback if the MCP server is inaccessible or a specific function isn't exposed via MCP. + - Install globally with `npm install -g task-master-ai` or use locally via `npx task-master-ai ...`. + - The CLI commands often mirror the MCP tools (e.g., `task-master list` corresponds to `get_tasks`). + - Refer to [`taskmaster.md`](mdc:.roo/rules/taskmaster.md) for a detailed command reference. + +## Standard Development Workflow Process + +- Start new projects by running `initialize_project` tool / `task-master init` or `parse_prd` / `task-master parse-prd --input='<prd-file.txt>'` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to generate initial tasks.json +- Begin coding sessions with `get_tasks` / `task-master list` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to see current tasks, status, and IDs +- Determine the next task to work on using `next_task` / `task-master next` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). +- Analyze task complexity with `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) before breaking down tasks +- Review complexity report using `complexity_report` / `task-master complexity-report` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). +- Select tasks based on dependencies (all marked 'done'), priority level, and ID order +- Clarify tasks by checking task files in tasks/ directory or asking for user input +- View specific task details using `get_task` / `task-master show <id>` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to understand implementation requirements +- Break down complex tasks using `expand_task` / `task-master expand --id=<id> --force --research` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) with appropriate flags like `--force` (to replace existing subtasks) and `--research`. +- Clear existing subtasks if needed using `clear_subtasks` / `task-master clear-subtasks --id=<id>` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) before regenerating +- Implement code following task details, dependencies, and project standards +- Verify tasks according to test strategies before marking as complete (See [`tests.md`](mdc:.roo/rules/tests.md)) +- Mark completed tasks with `set_task_status` / `task-master set-status --id=<id> --status=done` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) +- Update dependent tasks when implementation differs from original plan using `update` / `task-master update --from=<id> --prompt="..."` or `update_task` / `task-master update-task --id=<id> --prompt="..."` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) +- Add new tasks discovered during implementation using `add_task` / `task-master add-task --prompt="..." --research` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). +- Add new subtasks as needed using `add_subtask` / `task-master add-subtask --parent=<id> --title="..."` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). +- Append notes or details to subtasks using `update_subtask` / `task-master update-subtask --id=<subtaskId> --prompt='Add implementation notes here...\nMore details...'` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). +- Generate task files with `generate` / `task-master generate` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) after updating tasks.json +- Maintain valid dependency structure with `add_dependency`/`remove_dependency` tools or `task-master add-dependency`/`remove-dependency` commands, `validate_dependencies` / `task-master validate-dependencies`, and `fix_dependencies` / `task-master fix-dependencies` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) when needed +- Respect dependency chains and task priorities when selecting work +- Report progress regularly using `get_tasks` / `task-master list` +- Reorganize tasks as needed using `move_task` / `task-master move --from=<id> --to=<id>` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to change task hierarchy or ordering + +## Task Complexity Analysis + +- Run `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) for comprehensive analysis +- Review complexity report via `complexity_report` / `task-master complexity-report` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) for a formatted, readable version. +- Focus on tasks with highest complexity scores (8-10) for detailed breakdown +- Use analysis results to determine appropriate subtask allocation +- Note that reports are automatically used by the `expand_task` tool/command + +## Task Breakdown Process + +- Use `expand_task` / `task-master expand --id=<id>`. It automatically uses the complexity report if found, otherwise generates default number of subtasks. +- Use `--num=<number>` to specify an explicit number of subtasks, overriding defaults or complexity report recommendations. +- Add `--research` flag to leverage Perplexity AI for research-backed expansion. +- Add `--force` flag to clear existing subtasks before generating new ones (default is to append). +- Use `--prompt="<context>"` to provide additional context when needed. +- Review and adjust generated subtasks as necessary. +- Use `expand_all` tool or `task-master expand --all` to expand multiple pending tasks at once, respecting flags like `--force` and `--research`. +- If subtasks need complete replacement (regardless of the `--force` flag on `expand`), clear them first with `clear_subtasks` / `task-master clear-subtasks --id=<id>`. + +## Implementation Drift Handling + +- When implementation differs significantly from planned approach +- When future tasks need modification due to current implementation choices +- When new dependencies or requirements emerge +- Use `update` / `task-master update --from=<futureTaskId> --prompt='<explanation>\nUpdate context...' --research` to update multiple future tasks. +- Use `update_task` / `task-master update-task --id=<taskId> --prompt='<explanation>\nUpdate context...' --research` to update a single specific task. + +## Task Status Management + +- Use 'pending' for tasks ready to be worked on +- Use 'done' for completed and verified tasks +- Use 'deferred' for postponed tasks +- Add custom status values as needed for project-specific workflows + +## Task Structure Fields + +- **id**: Unique identifier for the task (Example: `1`, `1.1`) +- **title**: Brief, descriptive title (Example: `"Initialize Repo"`) +- **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`) +- **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) +- **dependencies**: IDs of prerequisite tasks (Example: `[1, 2.1]`) + - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) + - This helps quickly identify which prerequisite tasks are blocking work +- **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`) +- **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) +- **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) +- **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) +- Refer to task structure details (previously linked to `tasks.md`). + +## Configuration Management (Updated) + +Taskmaster configuration is managed through two main mechanisms: + +1. **`.taskmasterconfig` File (Primary):** + * Located in the project root directory. + * Stores most configuration settings: AI model selections (main, research, fallback), parameters (max tokens, temperature), logging level, default subtasks/priority, project name, etc. + * **Managed via `task-master models --setup` command.** Do not edit manually unless you know what you are doing. + * **View/Set specific models via `task-master models` command or `models` MCP tool.** + * Created automatically when you run `task-master models --setup` for the first time. + +2. **Environment Variables (`.env` / `mcp.json`):** + * Used **only** for sensitive API keys and specific endpoint URLs. + * Place API keys (one per provider) in a `.env` file in the project root for CLI usage. + * For MCP/Roo Code integration, configure these keys in the `env` section of `.roo/mcp.json`. + * Available keys/variables: See `assets/env.example` or the Configuration section in the command reference (previously linked to `taskmaster.md`). + +**Important:** Non-API key settings (like model selections, `MAX_TOKENS`, `TASKMASTER_LOG_LEVEL`) are **no longer configured via environment variables**. Use the `task-master models` command (or `--setup` for interactive configuration) or the `models` MCP tool. +**If AI commands FAIL in MCP** verify that the API key for the selected provider is present in the `env` section of `.roo/mcp.json`. +**If AI commands FAIL in CLI** verify that the API key for the selected provider is present in the `.env` file in the root of the project. + +## Determining the Next Task + +- Run `next_task` / `task-master next` to show the next task to work on. +- The command identifies tasks with all dependencies satisfied +- Tasks are prioritized by priority level, dependency count, and ID +- The command shows comprehensive task information including: + - Basic task details and description + - Implementation details + - Subtasks (if they exist) + - Contextual suggested actions +- Recommended before starting any new development work +- Respects your project's dependency structure +- Ensures tasks are completed in the appropriate sequence +- Provides ready-to-use commands for common task actions + +## Viewing Specific Task Details + +- Run `get_task` / `task-master show <id>` to view a specific task. +- Use dot notation for subtasks: `task-master show 1.2` (shows subtask 2 of task 1) +- Displays comprehensive information similar to the next command, but for a specific task +- For parent tasks, shows all subtasks and their current status +- For subtasks, shows parent task information and relationship +- Provides contextual suggested actions appropriate for the specific task +- Useful for examining task details before implementation or checking status + +## Managing Task Dependencies + +- Use `add_dependency` / `task-master add-dependency --id=<id> --depends-on=<id>` to add a dependency. +- Use `remove_dependency` / `task-master remove-dependency --id=<id> --depends-on=<id>` to remove a dependency. +- The system prevents circular dependencies and duplicate dependency entries +- Dependencies are checked for existence before being added or removed +- Task files are automatically regenerated after dependency changes +- Dependencies are visualized with status indicators in task listings and files + +## Task Reorganization + +- Use `move_task` / `task-master move --from=<id> --to=<id>` to move tasks or subtasks within the hierarchy +- This command supports several use cases: + - Moving a standalone task to become a subtask (e.g., `--from=5 --to=7`) + - Moving a subtask to become a standalone task (e.g., `--from=5.2 --to=7`) + - Moving a subtask to a different parent (e.g., `--from=5.2 --to=7.3`) + - Reordering subtasks within the same parent (e.g., `--from=5.2 --to=5.4`) + - Moving a task to a new, non-existent ID position (e.g., `--from=5 --to=25`) + - Moving multiple tasks at once using comma-separated IDs (e.g., `--from=10,11,12 --to=16,17,18`) +- The system includes validation to prevent data loss: + - Allows moving to non-existent IDs by creating placeholder tasks + - Prevents moving to existing task IDs that have content (to avoid overwriting) + - Validates source tasks exist before attempting to move them +- The system maintains proper parent-child relationships and dependency integrity +- Task files are automatically regenerated after the move operation +- This provides greater flexibility in organizing and refining your task structure as project understanding evolves +- This is especially useful when dealing with potential merge conflicts arising from teams creating tasks on separate branches. Solve these conflicts very easily by moving your tasks and keeping theirs. + +## Iterative Subtask Implementation + +Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation: + +1. **Understand the Goal (Preparation):** + * Use `get_task` / `task-master show <subtaskId>` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to thoroughly understand the specific goals and requirements of the subtask. + +2. **Initial Exploration & Planning (Iteration 1):** + * This is the first attempt at creating a concrete implementation plan. + * Explore the codebase to identify the precise files, functions, and even specific lines of code that will need modification. + * Determine the intended code changes (diffs) and their locations. + * Gather *all* relevant details from this exploration phase. + +3. **Log the Plan:** + * Run `update_subtask` / `task-master update-subtask --id=<subtaskId> --prompt='<detailed plan>'`. + * Provide the *complete and detailed* findings from the exploration phase in the prompt. Include file paths, line numbers, proposed diffs, reasoning, and any potential challenges identified. Do not omit details. The goal is to create a rich, timestamped log within the subtask's `details`. + +4. **Verify the Plan:** + * Run `get_task` / `task-master show <subtaskId>` again to confirm that the detailed implementation plan has been successfully appended to the subtask's details. + +5. **Begin Implementation:** + * Set the subtask status using `set_task_status` / `task-master set-status --id=<subtaskId> --status=in-progress`. + * Start coding based on the logged plan. + +6. **Refine and Log Progress (Iteration 2+):** + * As implementation progresses, you will encounter challenges, discover nuances, or confirm successful approaches. + * **Before appending new information**: Briefly review the *existing* details logged in the subtask (using `get_task` or recalling from context) to ensure the update adds fresh insights and avoids redundancy. + * **Regularly** use `update_subtask` / `task-master update-subtask --id=<subtaskId> --prompt='<update details>\n- What worked...\n- What didn't work...'` to append new findings. + * **Crucially, log:** + * What worked ("fundamental truths" discovered). + * What didn't work and why (to avoid repeating mistakes). + * Specific code snippets or configurations that were successful. + * Decisions made, especially if confirmed with user input. + * Any deviations from the initial plan and the reasoning. + * The objective is to continuously enrich the subtask's details, creating a log of the implementation journey that helps the AI (and human developers) learn, adapt, and avoid repeating errors. + +7. **Review & Update Rules (Post-Implementation):** + * Once the implementation for the subtask is functionally complete, review all code changes and the relevant chat history. + * Identify any new or modified code patterns, conventions, or best practices established during the implementation. + * Create new or update existing rules following internal guidelines (previously linked to `cursor_rules.md` and `self_improve.md`). + +8. **Mark Task Complete:** + * After verifying the implementation and updating any necessary rules, mark the subtask as completed: `set_task_status` / `task-master set-status --id=<subtaskId> --status=done`. + +9. **Commit Changes (If using Git):** + * Stage the relevant code changes and any updated/new rule files (`git add .`). + * Craft a comprehensive Git commit message summarizing the work done for the subtask, including both code implementation and any rule adjustments. + * Execute the commit command directly in the terminal (e.g., `git commit -m 'feat(module): Implement feature X for subtask <subtaskId>\n\n- Details about changes...\n- Updated rule Y for pattern Z'`). + * Consider if a Changeset is needed according to internal versioning guidelines (previously linked to `changeset.md`). If so, run `npm run changeset`, stage the generated file, and amend the commit or create a new one. + +10. **Proceed to Next Subtask:** + * Identify the next subtask (e.g., using `next_task` / `task-master next`). + +## Code Analysis & Refactoring Techniques + +- **Top-Level Function Search**: + - Useful for understanding module structure or planning refactors. + - Use grep/ripgrep to find exported functions/constants: + `rg "export (async function|function|const) \w+"` or similar patterns. + - Can help compare functions between files during migrations or identify potential naming conflicts. + +--- +*This workflow provides a general guideline. Adapt it based on your specific project needs and team practices.* \ No newline at end of file diff --git a/.roo/rules/roo_rules.md b/.roo/rules/roo_rules.md new file mode 100644 index 0000000..cec3c64 --- /dev/null +++ b/.roo/rules/roo_rules.md @@ -0,0 +1,53 @@ +--- +description: Guidelines for creating and maintaining Roo Code rules to ensure consistency and effectiveness. +globs: .roo/rules/*.md +alwaysApply: true +--- + +- **Required Rule Structure:** + ```markdown + --- + description: Clear, one-line description of what the rule enforces + globs: path/to/files/*.ext, other/path/**/* + alwaysApply: boolean + --- + + - **Main Points in Bold** + - Sub-points with details + - Examples and explanations + ``` + +- **File References:** + - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files + - Example: [prisma.md](mdc:.roo/rules/prisma.md) for rule references + - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references + +- **Code Examples:** + - Use language-specific code blocks + ```typescript + // ✅ DO: Show good examples + const goodExample = true; + + // ❌ DON'T: Show anti-patterns + const badExample = false; + ``` + +- **Rule Content Guidelines:** + - Start with high-level overview + - Include specific, actionable requirements + - Show examples of correct implementation + - Reference existing code when possible + - Keep rules DRY by referencing other rules + +- **Rule Maintenance:** + - Update rules when new patterns emerge + - Add examples from actual codebase + - Remove outdated patterns + - Cross-reference related rules + +- **Best Practices:** + - Use bullet points for clarity + - Keep descriptions concise + - Include both DO and DON'T examples + - Reference actual code over theoretical examples + - Use consistent formatting across rules \ No newline at end of file diff --git a/.roo/rules/self_improve.md b/.roo/rules/self_improve.md new file mode 100644 index 0000000..e3af95e --- /dev/null +++ b/.roo/rules/self_improve.md @@ -0,0 +1,72 @@ +--- +description: Guidelines for continuously improving Roo Code rules based on emerging code patterns and best practices. +globs: **/* +alwaysApply: true +--- + +- **Rule Improvement Triggers:** + - New code patterns not covered by existing rules + - Repeated similar implementations across files + - Common error patterns that could be prevented + - New libraries or tools being used consistently + - Emerging best practices in the codebase + +- **Analysis Process:** + - Compare new code with existing rules + - Identify patterns that should be standardized + - Look for references to external documentation + - Check for consistent error handling patterns + - Monitor test patterns and coverage + +- **Rule Updates:** + - **Add New Rules When:** + - A new technology/pattern is used in 3+ files + - Common bugs could be prevented by a rule + - Code reviews repeatedly mention the same feedback + - New security or performance patterns emerge + + - **Modify Existing Rules When:** + - Better examples exist in the codebase + - Additional edge cases are discovered + - Related rules have been updated + - Implementation details have changed + +- **Example Pattern Recognition:** + ```typescript + // If you see repeated patterns like: + const data = await prisma.user.findMany({ + select: { id: true, email: true }, + where: { status: 'ACTIVE' } + }); + + // Consider adding to [prisma.md](mdc:.roo/rules/prisma.md): + // - Standard select fields + // - Common where conditions + // - Performance optimization patterns + ``` + +- **Rule Quality Checks:** + - Rules should be actionable and specific + - Examples should come from actual code + - References should be up to date + - Patterns should be consistently enforced + +- **Continuous Improvement:** + - Monitor code review comments + - Track common development questions + - Update rules after major refactors + - Add links to relevant documentation + - Cross-reference related rules + +- **Rule Deprecation:** + - Mark outdated patterns as deprecated + - Remove rules that no longer apply + - Update references to deprecated rules + - Document migration paths for old patterns + +- **Documentation Updates:** + - Keep examples synchronized with code + - Update references to external docs + - Maintain links between related rules + - Document breaking changes +Follow [cursor_rules.md](mdc:.roo/rules/cursor_rules.md) for proper rule formatting and structure. diff --git a/.roo/rules/taskmaster.md b/.roo/rules/taskmaster.md new file mode 100644 index 0000000..158c4e0 --- /dev/null +++ b/.roo/rules/taskmaster.md @@ -0,0 +1,407 @@ +--- +description: Comprehensive reference for Taskmaster MCP tools and CLI commands. +globs: **/* +alwaysApply: true +--- +# Taskmaster Tool & Command Reference + +This document provides a detailed reference for interacting with Taskmaster, covering both the recommended MCP tools, suitable for integrations like Roo Code, and the corresponding `task-master` CLI commands, designed for direct user interaction or fallback. + +**Note:** For interacting with Taskmaster programmatically or via integrated tools, using the **MCP tools is strongly recommended** due to better performance, structured data, and error handling. The CLI commands serve as a user-friendly alternative and fallback. + +**Important:** Several MCP tools involve AI processing... The AI-powered tools include `parse_prd`, `analyze_project_complexity`, `update_subtask`, `update_task`, `update`, `expand_all`, `expand_task`, and `add_task`. + +--- + +## Initialization & Setup + +### 1. Initialize Project (`init`) + +* **MCP Tool:** `initialize_project` +* **CLI Command:** `task-master init [options]` +* **Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project.` +* **Key CLI Options:** + * `--name <name>`: `Set the name for your project in Taskmaster's configuration.` + * `--description <text>`: `Provide a brief description for your project.` + * `--version <version>`: `Set the initial version for your project, e.g., '0.1.0'.` + * `-y, --yes`: `Initialize Taskmaster quickly using default settings without interactive prompts.` +* **Usage:** Run this once at the beginning of a new project. +* **MCP Variant Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project by running the 'task-master init' command.` +* **Key MCP Parameters/Options:** + * `projectName`: `Set the name for your project.` (CLI: `--name <name>`) + * `projectDescription`: `Provide a brief description for your project.` (CLI: `--description <text>`) + * `projectVersion`: `Set the initial version for your project, e.g., '0.1.0'.` (CLI: `--version <version>`) + * `authorName`: `Author name.` (CLI: `--author <author>`) + * `skipInstall`: `Skip installing dependencies. Default is false.` (CLI: `--skip-install`) + * `addAliases`: `Add shell aliases tm and taskmaster. Default is false.` (CLI: `--aliases`) + * `yes`: `Skip prompts and use defaults/provided arguments. Default is false.` (CLI: `-y, --yes`) +* **Usage:** Run this once at the beginning of a new project, typically via an integrated tool like Roo Code. Operates on the current working directory of the MCP server. +* **Important:** Once complete, you *MUST* parse a prd in order to generate tasks. There will be no tasks files until then. The next step after initializing should be to create a PRD using the example PRD in scripts/example_prd.txt. + +### 2. Parse PRD (`parse_prd`) + +* **MCP Tool:** `parse_prd` +* **CLI Command:** `task-master parse-prd [file] [options]` +* **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.` +* **Key Parameters/Options:** + * `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input <file>`) + * `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to 'tasks/tasks.json'.` (CLI: `-o, --output <file>`) + * `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks <number>`) + * `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`) +* **Usage:** Useful for bootstrapping a project from an existing requirements document. +* **Notes:** Task Master will strictly adhere to any specific requirements mentioned in the PRD, such as libraries, database schemas, frameworks, tech stacks, etc., while filling in any gaps where the PRD isn't fully specified. Tasks are designed to provide the most direct implementation path while avoiding over-engineering. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. If the user does not have a PRD, suggest discussing their idea and then use the example PRD in `scripts/example_prd.txt` as a template for creating the PRD based on their idea, for use with `parse-prd`. + +--- + +## AI Model Configuration + +### 2. Manage Models (`models`) +* **MCP Tool:** `models` +* **CLI Command:** `task-master models [options]` +* **Description:** `View the current AI model configuration or set specific models for different roles (main, research, fallback). Allows setting custom model IDs for Ollama and OpenRouter.` +* **Key MCP Parameters/Options:** + * `setMain <model_id>`: `Set the primary model ID for task generation/updates.` (CLI: `--set-main <model_id>`) + * `setResearch <model_id>`: `Set the model ID for research-backed operations.` (CLI: `--set-research <model_id>`) + * `setFallback <model_id>`: `Set the model ID to use if the primary fails.` (CLI: `--set-fallback <model_id>`) + * `ollama <boolean>`: `Indicates the set model ID is a custom Ollama model.` (CLI: `--ollama`) + * `openrouter <boolean>`: `Indicates the set model ID is a custom OpenRouter model.` (CLI: `--openrouter`) + * `listAvailableModels <boolean>`: `If true, lists available models not currently assigned to a role.` (CLI: No direct equivalent; CLI lists available automatically) + * `projectRoot <string>`: `Optional. Absolute path to the project root directory.` (CLI: Determined automatically) +* **Key CLI Options:** + * `--set-main <model_id>`: `Set the primary model.` + * `--set-research <model_id>`: `Set the research model.` + * `--set-fallback <model_id>`: `Set the fallback model.` + * `--ollama`: `Specify that the provided model ID is for Ollama (use with --set-*).` + * `--openrouter`: `Specify that the provided model ID is for OpenRouter (use with --set-*). Validates against OpenRouter API.` + * `--setup`: `Run interactive setup to configure models, including custom Ollama/OpenRouter IDs.` +* **Usage (MCP):** Call without set flags to get current config. Use `setMain`, `setResearch`, or `setFallback` with a valid model ID to update the configuration. Use `listAvailableModels: true` to get a list of unassigned models. To set a custom model, provide the model ID and set `ollama: true` or `openrouter: true`. +* **Usage (CLI):** Run without flags to view current configuration and available models. Use set flags to update specific roles. Use `--setup` for guided configuration, including custom models. To set a custom model via flags, use `--set-<role>=<model_id>` along with either `--ollama` or `--openrouter`. +* **Notes:** Configuration is stored in `.taskmasterconfig` in the project root. This command/tool modifies that file. Use `listAvailableModels` or `task-master models` to see internally supported models. OpenRouter custom models are validated against their live API. Ollama custom models are not validated live. +* **API note:** API keys for selected AI providers (based on their model) need to exist in the mcp.json file to be accessible in MCP context. The API keys must be present in the local .env file for the CLI to be able to read them. +* **Model costs:** The costs in supported models are expressed in dollars. An input/output value of 3 is $3.00. A value of 0.8 is $0.80. +* **Warning:** DO NOT MANUALLY EDIT THE .taskmasterconfig FILE. Use the included commands either in the MCP or CLI format as needed. Always prioritize MCP tools when available and use the CLI as a fallback. + +--- + +## Task Listing & Viewing + +### 3. Get Tasks (`get_tasks`) + +* **MCP Tool:** `get_tasks` +* **CLI Command:** `task-master list [options]` +* **Description:** `List your Taskmaster tasks, optionally filtering by status and showing subtasks.` +* **Key Parameters/Options:** + * `status`: `Show only Taskmaster tasks matching this status, e.g., 'pending' or 'done'.` (CLI: `-s, --status <status>`) + * `withSubtasks`: `Include subtasks indented under their parent tasks in the list.` (CLI: `--with-subtasks`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Get an overview of the project status, often used at the start of a work session. + +### 4. Get Next Task (`next_task`) + +* **MCP Tool:** `next_task` +* **CLI Command:** `task-master next [options]` +* **Description:** `Ask Taskmaster to show the next available task you can work on, based on status and completed dependencies.` +* **Key Parameters/Options:** + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Identify what to work on next according to the plan. + +### 5. Get Task Details (`get_task`) + +* **MCP Tool:** `get_task` +* **CLI Command:** `task-master show [id] [options]` +* **Description:** `Display detailed information for a specific Taskmaster task or subtask by its ID.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task, e.g., '15', or subtask, e.g., '15.2', you want to view.` (CLI: `[id]` positional or `-i, --id <id>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Understand the full details, implementation notes, and test strategy for a specific task before starting work. + +--- + +## Task Creation & Modification + +### 6. Add Task (`add_task`) + +* **MCP Tool:** `add_task` +* **CLI Command:** `task-master add-task [options]` +* **Description:** `Add a new task to Taskmaster by describing it; AI will structure it.` +* **Key Parameters/Options:** + * `prompt`: `Required. Describe the new task you want Taskmaster to create, e.g., "Implement user authentication using JWT".` (CLI: `-p, --prompt <text>`) + * `dependencies`: `Specify the IDs of any Taskmaster tasks that must be completed before this new one can start, e.g., '12,14'.` (CLI: `-d, --dependencies <ids>`) + * `priority`: `Set the priority for the new task: 'high', 'medium', or 'low'. Default is 'medium'.` (CLI: `--priority <priority>`) + * `research`: `Enable Taskmaster to use the research role for potentially more informed task creation.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Quickly add newly identified tasks during development. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 7. Add Subtask (`add_subtask`) + +* **MCP Tool:** `add_subtask` +* **CLI Command:** `task-master add-subtask [options]` +* **Description:** `Add a new subtask to a Taskmaster parent task, or convert an existing task into a subtask.` +* **Key Parameters/Options:** + * `id` / `parent`: `Required. The ID of the Taskmaster task that will be the parent.` (MCP: `id`, CLI: `-p, --parent <id>`) + * `taskId`: `Use this if you want to convert an existing top-level Taskmaster task into a subtask of the specified parent.` (CLI: `-i, --task-id <id>`) + * `title`: `Required if not using taskId. The title for the new subtask Taskmaster should create.` (CLI: `-t, --title <title>`) + * `description`: `A brief description for the new subtask.` (CLI: `-d, --description <text>`) + * `details`: `Provide implementation notes or details for the new subtask.` (CLI: `--details <text>`) + * `dependencies`: `Specify IDs of other tasks or subtasks, e.g., '15' or '16.1', that must be done before this new subtask.` (CLI: `--dependencies <ids>`) + * `status`: `Set the initial status for the new subtask. Default is 'pending'.` (CLI: `-s, --status <status>`) + * `skipGenerate`: `Prevent Taskmaster from automatically regenerating markdown task files after adding the subtask.` (CLI: `--skip-generate`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Break down tasks manually or reorganize existing tasks. + +### 8. Update Tasks (`update`) + +* **MCP Tool:** `update` +* **CLI Command:** `task-master update [options]` +* **Description:** `Update multiple upcoming tasks in Taskmaster based on new context or changes, starting from a specific task ID.` +* **Key Parameters/Options:** + * `from`: `Required. The ID of the first task Taskmaster should update. All tasks with this ID or higher that are not 'done' will be considered.` (CLI: `--from <id>`) + * `prompt`: `Required. Explain the change or new context for Taskmaster to apply to the tasks, e.g., "We are now using React Query instead of Redux Toolkit for data fetching".` (CLI: `-p, --prompt <text>`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Handle significant implementation changes or pivots that affect multiple future tasks. Example CLI: `task-master update --from='18' --prompt='Switching to React Query.\nNeed to refactor data fetching...'` +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 9. Update Task (`update_task`) + +* **MCP Tool:** `update_task` +* **CLI Command:** `task-master update-task [options]` +* **Description:** `Modify a specific Taskmaster task or subtask by its ID, incorporating new information or changes.` +* **Key Parameters/Options:** + * `id`: `Required. The specific ID of the Taskmaster task, e.g., '15', or subtask, e.g., '15.2', you want to update.` (CLI: `-i, --id <id>`) + * `prompt`: `Required. Explain the specific changes or provide the new information Taskmaster should incorporate into this task.` (CLI: `-p, --prompt <text>`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Refine a specific task based on new understanding or feedback. Example CLI: `task-master update-task --id='15' --prompt='Clarification: Use PostgreSQL instead of MySQL.\nUpdate schema details...'` +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 10. Update Subtask (`update_subtask`) + +* **MCP Tool:** `update_subtask` +* **CLI Command:** `task-master update-subtask [options]` +* **Description:** `Append timestamped notes or details to a specific Taskmaster subtask without overwriting existing content. Intended for iterative implementation logging.` +* **Key Parameters/Options:** + * `id`: `Required. The specific ID of the Taskmaster subtask, e.g., '15.2', you want to add information to.` (CLI: `-i, --id <id>`) + * `prompt`: `Required. Provide the information or notes Taskmaster should append to the subtask's details. Ensure this adds *new* information not already present.` (CLI: `-p, --prompt <text>`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Add implementation notes, code snippets, or clarifications to a subtask during development. Before calling, review the subtask's current details to append only fresh insights, helping to build a detailed log of the implementation journey and avoid redundancy. Example CLI: `task-master update-subtask --id='15.2' --prompt='Discovered that the API requires header X.\nImplementation needs adjustment...'` +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 11. Set Task Status (`set_task_status`) + +* **MCP Tool:** `set_task_status` +* **CLI Command:** `task-master set-status [options]` +* **Description:** `Update the status of one or more Taskmaster tasks or subtasks, e.g., 'pending', 'in-progress', 'done'.` +* **Key Parameters/Options:** + * `id`: `Required. The ID(s) of the Taskmaster task(s) or subtask(s), e.g., '15', '15.2', or '16,17.1', to update.` (CLI: `-i, --id <id>`) + * `status`: `Required. The new status to set, e.g., 'done', 'pending', 'in-progress', 'review', 'cancelled'.` (CLI: `-s, --status <status>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Mark progress as tasks move through the development cycle. + +### 12. Remove Task (`remove_task`) + +* **MCP Tool:** `remove_task` +* **CLI Command:** `task-master remove-task [options]` +* **Description:** `Permanently remove a task or subtask from the Taskmaster tasks list.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task, e.g., '5', or subtask, e.g., '5.2', to permanently remove.` (CLI: `-i, --id <id>`) + * `yes`: `Skip the confirmation prompt and immediately delete the task.` (CLI: `-y, --yes`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Permanently delete tasks or subtasks that are no longer needed in the project. +* **Notes:** Use with caution as this operation cannot be undone. Consider using 'blocked', 'cancelled', or 'deferred' status instead if you just want to exclude a task from active planning but keep it for reference. The command automatically cleans up dependency references in other tasks. + +--- + +## Task Structure & Breakdown + +### 13. Expand Task (`expand_task`) + +* **MCP Tool:** `expand_task` +* **CLI Command:** `task-master expand [options]` +* **Description:** `Use Taskmaster's AI to break down a complex task into smaller, manageable subtasks. Appends subtasks by default.` +* **Key Parameters/Options:** + * `id`: `The ID of the specific Taskmaster task you want to break down into subtasks.` (CLI: `-i, --id <id>`) + * `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create. Uses complexity analysis/defaults otherwise.` (CLI: `-n, --num <number>`) + * `research`: `Enable Taskmaster to use the research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`) + * `prompt`: `Optional: Provide extra context or specific instructions to Taskmaster for generating the subtasks.` (CLI: `-p, --prompt <text>`) + * `force`: `Optional: If true, clear existing subtasks before generating new ones. Default is false (append).` (CLI: `--force`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Generate a detailed implementation plan for a complex task before starting coding. Automatically uses complexity report recommendations if available and `num` is not specified. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 14. Expand All Tasks (`expand_all`) + +* **MCP Tool:** `expand_all` +* **CLI Command:** `task-master expand --all [options]` (Note: CLI uses the `expand` command with the `--all` flag) +* **Description:** `Tell Taskmaster to automatically expand all eligible pending/in-progress tasks based on complexity analysis or defaults. Appends subtasks by default.` +* **Key Parameters/Options:** + * `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create per task.` (CLI: `-n, --num <number>`) + * `research`: `Enable research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`) + * `prompt`: `Optional: Provide extra context for Taskmaster to apply generally during expansion.` (CLI: `-p, --prompt <text>`) + * `force`: `Optional: If true, clear existing subtasks before generating new ones for each eligible task. Default is false (append).` (CLI: `--force`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Useful after initial task generation or complexity analysis to break down multiple tasks at once. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 15. Clear Subtasks (`clear_subtasks`) + +* **MCP Tool:** `clear_subtasks` +* **CLI Command:** `task-master clear-subtasks [options]` +* **Description:** `Remove all subtasks from one or more specified Taskmaster parent tasks.` +* **Key Parameters/Options:** + * `id`: `The ID(s) of the Taskmaster parent task(s) whose subtasks you want to remove, e.g., '15' or '16,18'. Required unless using `all`.) (CLI: `-i, --id <ids>`) + * `all`: `Tell Taskmaster to remove subtasks from all parent tasks.` (CLI: `--all`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Used before regenerating subtasks with `expand_task` if the previous breakdown needs replacement. + +### 16. Remove Subtask (`remove_subtask`) + +* **MCP Tool:** `remove_subtask` +* **CLI Command:** `task-master remove-subtask [options]` +* **Description:** `Remove a subtask from its Taskmaster parent, optionally converting it into a standalone task.` +* **Key Parameters/Options:** + * `id`: `Required. The ID(s) of the Taskmaster subtask(s) to remove, e.g., '15.2' or '16.1,16.3'.` (CLI: `-i, --id <id>`) + * `convert`: `If used, Taskmaster will turn the subtask into a regular top-level task instead of deleting it.` (CLI: `-c, --convert`) + * `skipGenerate`: `Prevent Taskmaster from automatically regenerating markdown task files after removing the subtask.` (CLI: `--skip-generate`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Delete unnecessary subtasks or promote a subtask to a top-level task. + +### 17. Move Task (`move_task`) + +* **MCP Tool:** `move_task` +* **CLI Command:** `task-master move [options]` +* **Description:** `Move a task or subtask to a new position within the task hierarchy.` +* **Key Parameters/Options:** + * `from`: `Required. ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated for multiple tasks.` (CLI: `--from <id>`) + * `to`: `Required. ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated.` (CLI: `--to <id>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Reorganize tasks by moving them within the hierarchy. Supports various scenarios like: + * Moving a task to become a subtask + * Moving a subtask to become a standalone task + * Moving a subtask to a different parent + * Reordering subtasks within the same parent + * Moving a task to a new, non-existent ID (automatically creates placeholders) + * Moving multiple tasks at once with comma-separated IDs +* **Validation Features:** + * Allows moving tasks to non-existent destination IDs (creates placeholder tasks) + * Prevents moving to existing task IDs that already have content (to avoid overwriting) + * Validates that source tasks exist before attempting to move them + * Maintains proper parent-child relationships +* **Example CLI:** `task-master move --from=5.2 --to=7.3` to move subtask 5.2 to become subtask 7.3. +* **Example Multi-Move:** `task-master move --from=10,11,12 --to=16,17,18` to move multiple tasks to new positions. +* **Common Use:** Resolving merge conflicts in tasks.json when multiple team members create tasks on different branches. + +--- + +## Dependency Management + +### 18. Add Dependency (`add_dependency`) + +* **MCP Tool:** `add_dependency` +* **CLI Command:** `task-master add-dependency [options]` +* **Description:** `Define a dependency in Taskmaster, making one task a prerequisite for another.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task that will depend on another.` (CLI: `-i, --id <id>`) + * `dependsOn`: `Required. The ID of the Taskmaster task that must be completed first, the prerequisite.` (CLI: `-d, --depends-on <id>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <path>`) +* **Usage:** Establish the correct order of execution between tasks. + +### 19. Remove Dependency (`remove_dependency`) + +* **MCP Tool:** `remove_dependency` +* **CLI Command:** `task-master remove-dependency [options]` +* **Description:** `Remove a dependency relationship between two Taskmaster tasks.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task you want to remove a prerequisite from.` (CLI: `-i, --id <id>`) + * `dependsOn`: `Required. The ID of the Taskmaster task that should no longer be a prerequisite.` (CLI: `-d, --depends-on <id>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Update task relationships when the order of execution changes. + +### 20. Validate Dependencies (`validate_dependencies`) + +* **MCP Tool:** `validate_dependencies` +* **CLI Command:** `task-master validate-dependencies [options]` +* **Description:** `Check your Taskmaster tasks for dependency issues (like circular references or links to non-existent tasks) without making changes.` +* **Key Parameters/Options:** + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Audit the integrity of your task dependencies. + +### 21. Fix Dependencies (`fix_dependencies`) + +* **MCP Tool:** `fix_dependencies` +* **CLI Command:** `task-master fix-dependencies [options]` +* **Description:** `Automatically fix dependency issues (like circular references or links to non-existent tasks) in your Taskmaster tasks.` +* **Key Parameters/Options:** + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Clean up dependency errors automatically. + +--- + +## Analysis & Reporting + +### 22. Analyze Project Complexity (`analyze_project_complexity`) + +* **MCP Tool:** `analyze_project_complexity` +* **CLI Command:** `task-master analyze-complexity [options]` +* **Description:** `Have Taskmaster analyze your tasks to determine their complexity and suggest which ones need to be broken down further.` +* **Key Parameters/Options:** + * `output`: `Where to save the complexity analysis report (default: 'scripts/task-complexity-report.json').` (CLI: `-o, --output <file>`) + * `threshold`: `The minimum complexity score (1-10) that should trigger a recommendation to expand a task.` (CLI: `-t, --threshold <number>`) + * `research`: `Enable research role for more accurate complexity analysis. Requires appropriate API key.` (CLI: `-r, --research`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Used before breaking down tasks to identify which ones need the most attention. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 23. View Complexity Report (`complexity_report`) + +* **MCP Tool:** `complexity_report` +* **CLI Command:** `task-master complexity-report [options]` +* **Description:** `Display the task complexity analysis report in a readable format.` +* **Key Parameters/Options:** + * `file`: `Path to the complexity report (default: 'scripts/task-complexity-report.json').` (CLI: `-f, --file <file>`) +* **Usage:** Review and understand the complexity analysis results after running analyze-complexity. + +--- + +## File Management + +### 24. Generate Task Files (`generate`) + +* **MCP Tool:** `generate` +* **CLI Command:** `task-master generate [options]` +* **Description:** `Create or update individual Markdown files for each task based on your tasks.json.` +* **Key Parameters/Options:** + * `output`: `The directory where Taskmaster should save the task files (default: in a 'tasks' directory).` (CLI: `-o, --output <directory>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Run this after making changes to tasks.json to keep individual task files up to date. + +--- + +## Environment Variables Configuration (Updated) + +Taskmaster primarily uses the **`.taskmasterconfig`** file (in project root) for configuration (models, parameters, logging level, etc.), managed via `task-master models --setup`. + +Environment variables are used **only** for sensitive API keys related to AI providers and specific overrides like the Ollama base URL: + +* **API Keys (Required for corresponding provider):** + * `ANTHROPIC_API_KEY` + * `PERPLEXITY_API_KEY` + * `OPENAI_API_KEY` + * `GOOGLE_API_KEY` + * `MISTRAL_API_KEY` + * `AZURE_OPENAI_API_KEY` (Requires `AZURE_OPENAI_ENDPOINT` too) + * `OPENROUTER_API_KEY` + * `XAI_API_KEY` + * `OLLANA_API_KEY` (Requires `OLLAMA_BASE_URL` too) +* **Endpoints (Optional/Provider Specific inside .taskmasterconfig):** + * `AZURE_OPENAI_ENDPOINT` + * `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`) + +**Set API keys** in your **`.env`** file in the project root (for CLI use) or within the `env` section of your **`.roo/mcp.json`** file (for MCP/Roo Code integration). All other settings (model choice, max tokens, temperature, log level, custom endpoints) are managed in `.taskmasterconfig` via `task-master models` command or `models` MCP tool. + +--- + +For details on how these commands fit into the development process, see the [Development Workflow Guide](mdc:.roo/rules/dev_workflow.md). diff --git a/.roomodes b/.roomodes new file mode 100644 index 0000000..289a422 --- /dev/null +++ b/.roomodes @@ -0,0 +1,63 @@ +{ + "customModes": [ + { + "slug": "boomerang", + "name": "Boomerang", + "roleDefinition": "You are Roo, a strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized modes. You have a comprehensive understanding of each mode's capabilities and limitations, also your own, and with the information given by the user and other modes in shared context you are enabled to effectively break down complex problems into discrete tasks that can be solved by different specialists using the `taskmaster-ai` system for task and context management.", + "customInstructions": "Your role is to coordinate complex workflows by delegating tasks to specialized modes, using `taskmaster-ai` as the central hub for task definition, progress tracking, and context management. \nAs an orchestrator, you should:\nn1. When given a complex task, use contextual information (which gets updated frequently) to break it down into logical subtasks that can be delegated to appropriate specialized modes.\nn2. For each subtask, use the `new_task` tool to delegate. Choose the most appropriate mode for the subtask's specific goal and provide comprehensive instructions in the `message` parameter. \nThese instructions must include:\n* All necessary context from the parent task or previous subtasks required to complete the work.\n* A clearly defined scope, specifying exactly what the subtask should accomplish.\n* An explicit statement that the subtask should *only* perform the work outlined in these instructions and not deviate.\n* An instruction for the subtask to signal completion by using the `attempt_completion` tool, providing a thorough summary of the outcome in the `result` parameter, keeping in mind that this summary will be the source of truth used to further relay this information to other tasks and for you to keep track of what was completed on this project.\nn3. Track and manage the progress of all subtasks. When a subtask is completed, acknowledge its results and determine the next steps.\nn4. Help the user understand how the different subtasks fit together in the overall workflow. Provide clear reasoning about why you're delegating specific tasks to specific modes.\nn5. Ask clarifying questions when necessary to better understand how to break down complex tasks effectively. If it seems complex delegate to architect to accomplish that \nn6. Use subtasks to maintain clarity. If a request significantly shifts focus or requires a different expertise (mode), consider creating a subtask rather than overloading the current one.", + "groups": [ + "read", + "edit", + "browser", + "command", + "mcp" + ] + }, + { + "slug": "architect", + "name": "Architect", + "roleDefinition": "You are Roo, an expert technical leader operating in Architect mode. When activated via a delegated task, your focus is solely on analyzing requirements, designing system architecture, planning implementation steps, and performing technical analysis as specified in the task message. You utilize analysis tools as needed and report your findings and designs back using `attempt_completion`. You do not deviate from the delegated task scope.", + "customInstructions": "1. Do some information gathering (for example using read_file or search_files) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer.\n\n4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.\n\n5. Once the user confirms the plan, ask them if they'd like you to write it to a markdown file.\n\n6. Use the switch_mode tool to request that the user switch to another mode to implement the solution.", + "groups": [ + "read", + ["edit", { "fileRegex": "\\.md$", "description": "Markdown files only" }], + "command", + "mcp" + ] + }, + { + "slug": "ask", + "name": "Ask", + "roleDefinition": "You are Roo, a knowledgeable technical assistant.\nWhen activated by another mode via a delegated task, your focus is to research, analyze, and provide clear, concise answers or explanations based *only* on the specific information requested in the delegation message. Use available tools for information gathering and report your findings back using `attempt_completion`.", + "customInstructions": "You can analyze code, explain concepts, and access external resources. Make sure to answer the user's questions and don't rush to switch to implementing code. Include Mermaid diagrams if they help make your response clearer.", + "groups": [ + "read", + "browser", + "mcp" + ] + }, + { + "slug": "debug", + "name": "Debug", + "roleDefinition": "You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution. When activated by another mode, your task is to meticulously analyze the provided debugging request (potentially referencing Taskmaster tasks, logs, or metrics), use diagnostic tools as instructed to investigate the issue, identify the root cause, and report your findings and recommended next steps back via `attempt_completion`. You focus solely on diagnostics within the scope defined by the delegated task.", + "customInstructions": "Reflect on 5-7 different possible sources of the problem, distill those down to 1-2 most likely sources, and then add logs to validate your assumptions. Explicitly ask the user to confirm the diagnosis before fixing the problem.", + "groups": [ + "read", + "edit", + "command", + "mcp" + ] + }, + { + "slug": "test", + "name": "Test", + "roleDefinition": "You are Roo, an expert software tester. Your primary focus is executing testing tasks delegated to you by other modes.\nAnalyze the provided scope and context (often referencing a Taskmaster task ID and its `testStrategy`), develop test plans if needed, execute tests diligently, and report comprehensive results (pass/fail, bugs, coverage) back using `attempt_completion`. You operate strictly within the delegated task's boundaries.", + "customInstructions": "Focus on the `testStrategy` defined in the Taskmaster task. Develop and execute test plans accordingly. Report results clearly, including pass/fail status, bug details, and coverage information.", + "groups": [ + "read", + "command", + "mcp" + ] + } + ] +} \ No newline at end of file diff --git a/.taskmasterconfig b/.taskmasterconfig new file mode 100644 index 0000000..b4788ed --- /dev/null +++ b/.taskmasterconfig @@ -0,0 +1,32 @@ +{ + "models": { + "main": { + "provider": "anthropic", + "modelId": "claude-3-7-sonnet-20250219", + "maxTokens": 120000, + "temperature": 0.2 + }, + "research": { + "provider": "perplexity", + "modelId": "sonar-pro", + "maxTokens": 8700, + "temperature": 0.1 + }, + "fallback": { + "provider": "anthropic", + "modelId": "claude-3-5-sonnet-20240620", + "maxTokens": 8192, + "temperature": 0.1 + } + }, + "global": { + "logLevel": "info", + "debug": false, + "defaultSubtasks": 5, + "defaultPriority": "medium", + "projectName": "Taskmaster", + "ollamaBaseUrl": "http://localhost:11434/api", + "azureOpenaiBaseUrl": "https://your-endpoint.openai.azure.com/", + "userId": "1234567890" + } +} \ No newline at end of file diff --git a/.windsurfrules b/.windsurfrules new file mode 100644 index 0000000..a5cf07a --- /dev/null +++ b/.windsurfrules @@ -0,0 +1,474 @@ +Below you will find a variety of important rules spanning: +- the dev_workflow +- the .windsurfrules document self-improvement workflow +- the template to follow when modifying or adding new sections/rules to this document. + +--- +DEV_WORKFLOW +--- +description: Guide for using meta-development script (scripts/dev.js) to manage task-driven development workflows +globs: **/* +filesToApplyRule: **/* +alwaysApply: true +--- + +- **Global CLI Commands** + - Task Master now provides a global CLI through the `task-master` command + - All functionality from `scripts/dev.js` is available through this interface + - Install globally with `npm install -g claude-task-master` or use locally via `npx` + - Use `task-master <command>` instead of `node scripts/dev.js <command>` + - Examples: + - `task-master list` instead of `node scripts/dev.js list` + - `task-master next` instead of `node scripts/dev.js next` + - `task-master expand --id=3` instead of `node scripts/dev.js expand --id=3` + - All commands accept the same options as their script equivalents + - The CLI provides additional commands like `task-master init` for project setup + +- **Development Workflow Process** + - Start new projects by running `task-master init` or `node scripts/dev.js parse-prd --input=<prd-file.txt>` to generate initial tasks.json + - Begin coding sessions with `task-master list` to see current tasks, status, and IDs + - Analyze task complexity with `task-master analyze-complexity --research` before breaking down tasks + - Select tasks based on dependencies (all marked 'done'), priority level, and ID order + - Clarify tasks by checking task files in tasks/ directory or asking for user input + - View specific task details using `task-master show <id>` to understand implementation requirements + - Break down complex tasks using `task-master expand --id=<id>` with appropriate flags + - Clear existing subtasks if needed using `task-master clear-subtasks --id=<id>` before regenerating + - Implement code following task details, dependencies, and project standards + - Verify tasks according to test strategies before marking as complete + - Mark completed tasks with `task-master set-status --id=<id> --status=done` + - Update dependent tasks when implementation differs from original plan + - Generate task files with `task-master generate` after updating tasks.json + - Maintain valid dependency structure with `task-master fix-dependencies` when needed + - Respect dependency chains and task priorities when selecting work + - Report progress regularly using the list command + +- **Task Complexity Analysis** + - Run `node scripts/dev.js analyze-complexity --research` for comprehensive analysis + - Review complexity report in scripts/task-complexity-report.json + - Or use `node scripts/dev.js complexity-report` for a formatted, readable version of the report + - Focus on tasks with highest complexity scores (8-10) for detailed breakdown + - Use analysis results to determine appropriate subtask allocation + - Note that reports are automatically used by the expand command + +- **Task Breakdown Process** + - For tasks with complexity analysis, use `node scripts/dev.js expand --id=<id>` + - Otherwise use `node scripts/dev.js expand --id=<id> --subtasks=<number>` + - Add `--research` flag to leverage Perplexity AI for research-backed expansion + - Use `--prompt="<context>"` to provide additional context when needed + - Review and adjust generated subtasks as necessary + - Use `--all` flag to expand multiple pending tasks at once + - If subtasks need regeneration, clear them first with `clear-subtasks` command + +- **Implementation Drift Handling** + - When implementation differs significantly from planned approach + - When future tasks need modification due to current implementation choices + - When new dependencies or requirements emerge + - Call `node scripts/dev.js update --from=<futureTaskId> --prompt="<explanation>"` to update tasks.json + +- **Task Status Management** + - Use 'pending' for tasks ready to be worked on + - Use 'done' for completed and verified tasks + - Use 'deferred' for postponed tasks + - Add custom status values as needed for project-specific workflows + +- **Task File Format Reference** + ``` + # Task ID: <id> + # Title: <title> + # Status: <status> + # Dependencies: <comma-separated list of dependency IDs> + # Priority: <priority> + # Description: <brief description> + # Details: + <detailed implementation notes> + + # Test Strategy: + <verification approach> + ``` + +- **Command Reference: parse-prd** + - Legacy Syntax: `node scripts/dev.js parse-prd --input=<prd-file.txt>` + - CLI Syntax: `task-master parse-prd --input=<prd-file.txt>` + - Description: Parses a PRD document and generates a tasks.json file with structured tasks + - Parameters: + - `--input=<file>`: Path to the PRD text file (default: sample-prd.txt) + - Example: `task-master parse-prd --input=requirements.txt` + - Notes: Will overwrite existing tasks.json file. Use with caution. + +- **Command Reference: update** + - Legacy Syntax: `node scripts/dev.js update --from=<id> --prompt="<prompt>"` + - CLI Syntax: `task-master update --from=<id> --prompt="<prompt>"` + - Description: Updates tasks with ID >= specified ID based on the provided prompt + - Parameters: + - `--from=<id>`: Task ID from which to start updating (required) + - `--prompt="<text>"`: Explanation of changes or new context (required) + - Example: `task-master update --from=4 --prompt="Now we are using Express instead of Fastify."` + - Notes: Only updates tasks not marked as 'done'. Completed tasks remain unchanged. + +- **Command Reference: generate** + - Legacy Syntax: `node scripts/dev.js generate` + - CLI Syntax: `task-master generate` + - Description: Generates individual task files in tasks/ directory based on tasks.json + - Parameters: + - `--file=<path>, -f`: Use alternative tasks.json file (default: 'tasks/tasks.json') + - `--output=<dir>, -o`: Output directory (default: 'tasks') + - Example: `task-master generate` + - Notes: Overwrites existing task files. Creates tasks/ directory if needed. + +- **Command Reference: set-status** + - Legacy Syntax: `node scripts/dev.js set-status --id=<id> --status=<status>` + - CLI Syntax: `task-master set-status --id=<id> --status=<status>` + - Description: Updates the status of a specific task in tasks.json + - Parameters: + - `--id=<id>`: ID of the task to update (required) + - `--status=<status>`: New status value (required) + - Example: `task-master set-status --id=3 --status=done` + - Notes: Common values are 'done', 'pending', and 'deferred', but any string is accepted. + +- **Command Reference: list** + - Legacy Syntax: `node scripts/dev.js list` + - CLI Syntax: `task-master list` + - Description: Lists all tasks in tasks.json with IDs, titles, and status + - Parameters: + - `--status=<status>, -s`: Filter by status + - `--with-subtasks`: Show subtasks for each task + - `--file=<path>, -f`: Use alternative tasks.json file (default: 'tasks/tasks.json') + - Example: `task-master list` + - Notes: Provides quick overview of project progress. Use at start of sessions. + +- **Command Reference: expand** + - Legacy Syntax: `node scripts/dev.js expand --id=<id> [--num=<number>] [--research] [--prompt="<context>"]` + - CLI Syntax: `task-master expand --id=<id> [--num=<number>] [--research] [--prompt="<context>"]` + - Description: Expands a task with subtasks for detailed implementation + - Parameters: + - `--id=<id>`: ID of task to expand (required unless using --all) + - `--all`: Expand all pending tasks, prioritized by complexity + - `--num=<number>`: Number of subtasks to generate (default: from complexity report) + - `--research`: Use Perplexity AI for research-backed generation + - `--prompt="<text>"`: Additional context for subtask generation + - `--force`: Regenerate subtasks even for tasks that already have them + - Example: `task-master expand --id=3 --num=5 --research --prompt="Focus on security aspects"` + - Notes: Uses complexity report recommendations if available. + +- **Command Reference: analyze-complexity** + - Legacy Syntax: `node scripts/dev.js analyze-complexity [options]` + - CLI Syntax: `task-master analyze-complexity [options]` + - Description: Analyzes task complexity and generates expansion recommendations + - Parameters: + - `--output=<file>, -o`: Output file path (default: scripts/task-complexity-report.json) + - `--model=<model>, -m`: Override LLM model to use + - `--threshold=<number>, -t`: Minimum score for expansion recommendation (default: 5) + - `--file=<path>, -f`: Use alternative tasks.json file + - `--research, -r`: Use Perplexity AI for research-backed analysis + - Example: `task-master analyze-complexity --research` + - Notes: Report includes complexity scores, recommended subtasks, and tailored prompts. + +- **Command Reference: clear-subtasks** + - Legacy Syntax: `node scripts/dev.js clear-subtasks --id=<id>` + - CLI Syntax: `task-master clear-subtasks --id=<id>` + - Description: Removes subtasks from specified tasks to allow regeneration + - Parameters: + - `--id=<id>`: ID or comma-separated IDs of tasks to clear subtasks from + - `--all`: Clear subtasks from all tasks + - Examples: + - `task-master clear-subtasks --id=3` + - `task-master clear-subtasks --id=1,2,3` + - `task-master clear-subtasks --all` + - Notes: + - Task files are automatically regenerated after clearing subtasks + - Can be combined with expand command to immediately generate new subtasks + - Works with both parent tasks and individual subtasks + +- **Task Structure Fields** + - **id**: Unique identifier for the task (Example: `1`) + - **title**: Brief, descriptive title (Example: `"Initialize Repo"`) + - **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`) + - **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) + - **dependencies**: IDs of prerequisite tasks (Example: `[1, 2]`) + - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) + - This helps quickly identify which prerequisite tasks are blocking work + - **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`) + - **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) + - **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) + - **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) + +- **Environment Variables Configuration** + - **ANTHROPIC_API_KEY** (Required): Your Anthropic API key for Claude (Example: `ANTHROPIC_API_KEY=sk-ant-api03-...`) + - **MODEL** (Default: `"claude-3-7-sonnet-20250219"`): Claude model to use (Example: `MODEL=claude-3-opus-20240229`) + - **MAX_TOKENS** (Default: `"4000"`): Maximum tokens for responses (Example: `MAX_TOKENS=8000`) + - **TEMPERATURE** (Default: `"0.7"`): Temperature for model responses (Example: `TEMPERATURE=0.5`) + - **DEBUG** (Default: `"false"`): Enable debug logging (Example: `DEBUG=true`) + - **TASKMASTER_LOG_LEVEL** (Default: `"info"`): Console output level (Example: `TASKMASTER_LOG_LEVEL=debug`) + - **DEFAULT_SUBTASKS** (Default: `"3"`): Default subtask count (Example: `DEFAULT_SUBTASKS=5`) + - **DEFAULT_PRIORITY** (Default: `"medium"`): Default priority (Example: `DEFAULT_PRIORITY=high`) + - **PROJECT_NAME** (Default: `"MCP SaaS MVP"`): Project name in metadata (Example: `PROJECT_NAME=My Awesome Project`) + - **PROJECT_VERSION** (Default: `"1.0.0"`): Version in metadata (Example: `PROJECT_VERSION=2.1.0`) + - **PERPLEXITY_API_KEY**: For research-backed features (Example: `PERPLEXITY_API_KEY=pplx-...`) + - **PERPLEXITY_MODEL** (Default: `"sonar-medium-online"`): Perplexity model (Example: `PERPLEXITY_MODEL=sonar-large-online`) + +- **Determining the Next Task** + - Run `task-master next` to show the next task to work on + - The next command identifies tasks with all dependencies satisfied + - Tasks are prioritized by priority level, dependency count, and ID + - The command shows comprehensive task information including: + - Basic task details and description + - Implementation details + - Subtasks (if they exist) + - Contextual suggested actions + - Recommended before starting any new development work + - Respects your project's dependency structure + - Ensures tasks are completed in the appropriate sequence + - Provides ready-to-use commands for common task actions + +- **Viewing Specific Task Details** + - Run `task-master show <id>` or `task-master show --id=<id>` to view a specific task + - Use dot notation for subtasks: `task-master show 1.2` (shows subtask 2 of task 1) + - Displays comprehensive information similar to the next command, but for a specific task + - For parent tasks, shows all subtasks and their current status + - For subtasks, shows parent task information and relationship + - Provides contextual suggested actions appropriate for the specific task + - Useful for examining task details before implementation or checking status + +- **Managing Task Dependencies** + - Use `task-master add-dependency --id=<id> --depends-on=<id>` to add a dependency + - Use `task-master remove-dependency --id=<id> --depends-on=<id>` to remove a dependency + - The system prevents circular dependencies and duplicate dependency entries + - Dependencies are checked for existence before being added or removed + - Task files are automatically regenerated after dependency changes + - Dependencies are visualized with status indicators in task listings and files + +- **Command Reference: add-dependency** + - Legacy Syntax: `node scripts/dev.js add-dependency --id=<id> --depends-on=<id>` + - CLI Syntax: `task-master add-dependency --id=<id> --depends-on=<id>` + - Description: Adds a dependency relationship between two tasks + - Parameters: + - `--id=<id>`: ID of task that will depend on another task (required) + - `--depends-on=<id>`: ID of task that will become a dependency (required) + - Example: `task-master add-dependency --id=22 --depends-on=21` + - Notes: Prevents circular dependencies and duplicates; updates task files automatically + +- **Command Reference: remove-dependency** + - Legacy Syntax: `node scripts/dev.js remove-dependency --id=<id> --depends-on=<id>` + - CLI Syntax: `task-master remove-dependency --id=<id> --depends-on=<id>` + - Description: Removes a dependency relationship between two tasks + - Parameters: + - `--id=<id>`: ID of task to remove dependency from (required) + - `--depends-on=<id>`: ID of task to remove as a dependency (required) + - Example: `task-master remove-dependency --id=22 --depends-on=21` + - Notes: Checks if dependency actually exists; updates task files automatically + +- **Command Reference: validate-dependencies** + - Legacy Syntax: `node scripts/dev.js validate-dependencies [options]` + - CLI Syntax: `task-master validate-dependencies [options]` + - Description: Checks for and identifies invalid dependencies in tasks.json and task files + - Parameters: + - `--file=<path>, -f`: Use alternative tasks.json file (default: 'tasks/tasks.json') + - Example: `task-master validate-dependencies` + - Notes: + - Reports all non-existent dependencies and self-dependencies without modifying files + - Provides detailed statistics on task dependency state + - Use before fix-dependencies to audit your task structure + +- **Command Reference: fix-dependencies** + - Legacy Syntax: `node scripts/dev.js fix-dependencies [options]` + - CLI Syntax: `task-master fix-dependencies [options]` + - Description: Finds and fixes all invalid dependencies in tasks.json and task files + - Parameters: + - `--file=<path>, -f`: Use alternative tasks.json file (default: 'tasks/tasks.json') + - Example: `task-master fix-dependencies` + - Notes: + - Removes references to non-existent tasks and subtasks + - Eliminates self-dependencies (tasks depending on themselves) + - Regenerates task files with corrected dependencies + - Provides detailed report of all fixes made + +- **Command Reference: complexity-report** + - Legacy Syntax: `node scripts/dev.js complexity-report [options]` + - CLI Syntax: `task-master complexity-report [options]` + - Description: Displays the task complexity analysis report in a formatted, easy-to-read way + - Parameters: + - `--file=<path>, -f`: Path to the complexity report file (default: 'scripts/task-complexity-report.json') + - Example: `task-master complexity-report` + - Notes: + - Shows tasks organized by complexity score with recommended actions + - Provides complexity distribution statistics + - Displays ready-to-use expansion commands for complex tasks + - If no report exists, offers to generate one interactively + +- **Command Reference: add-task** + - CLI Syntax: `task-master add-task [options]` + - Description: Add a new task to tasks.json using AI + - Parameters: + - `--file=<path>, -f`: Path to the tasks file (default: 'tasks/tasks.json') + - `--prompt=<text>, -p`: Description of the task to add (required) + - `--dependencies=<ids>, -d`: Comma-separated list of task IDs this task depends on + - `--priority=<priority>`: Task priority (high, medium, low) (default: 'medium') + - Example: `task-master add-task --prompt="Create user authentication using Auth0"` + - Notes: Uses AI to convert description into structured task with appropriate details + +- **Command Reference: init** + - CLI Syntax: `task-master init` + - Description: Initialize a new project with Task Master structure + - Parameters: None + - Example: `task-master init` + - Notes: + - Creates initial project structure with required files + - Prompts for project settings if not provided + - Merges with existing files when appropriate + - Can be used to bootstrap a new Task Master project quickly + +- **Code Analysis & Refactoring Techniques** + - **Top-Level Function Search** + - Use grep pattern matching to find all exported functions across the codebase + - Command: `grep -E "export (function|const) \w+|function \w+\(|const \w+ = \(|module\.exports" --include="*.js" -r ./` + - Benefits: + - Quickly identify all public API functions without reading implementation details + - Compare functions between files during refactoring (e.g., monolithic to modular structure) + - Verify all expected functions exist in refactored modules + - Identify duplicate functionality or naming conflicts + - Usage examples: + - When migrating from `scripts/dev.js` to modular structure: `grep -E "function \w+\(" scripts/dev.js` + - Check function exports in a directory: `grep -E "export (function|const)" scripts/modules/` + - Find potential naming conflicts: `grep -E "function (get|set|create|update)\w+\(" -r ./` + - Variations: + - Add `-n` flag to include line numbers + - Add `--include="*.ts"` to filter by file extension + - Use with `| sort` to alphabetize results + - Integration with refactoring workflow: + - Start by mapping all functions in the source file + - Create target module files based on function grouping + - Verify all functions were properly migrated + - Check for any unintentional duplications or omissions + +--- +WINDSURF_RULES +--- +description: Guidelines for creating and maintaining Windsurf rules to ensure consistency and effectiveness. +globs: .windsurfrules +filesToApplyRule: .windsurfrules +alwaysApply: true +--- +The below describes how you should be structuring new rule sections in this document. +- **Required Rule Structure:** + ```markdown + --- + description: Clear, one-line description of what the rule enforces + globs: path/to/files/*.ext, other/path/**/* + alwaysApply: boolean + --- + + - **Main Points in Bold** + - Sub-points with details + - Examples and explanations + ``` + +- **Section References:** + - Use `ALL_CAPS_SECTION` to reference files + - Example: `WINDSURF_RULES` + +- **Code Examples:** + - Use language-specific code blocks + ```typescript + // ✅ DO: Show good examples + const goodExample = true; + + // ❌ DON'T: Show anti-patterns + const badExample = false; + ``` + +- **Rule Content Guidelines:** + - Start with high-level overview + - Include specific, actionable requirements + - Show examples of correct implementation + - Reference existing code when possible + - Keep rules DRY by referencing other rules + +- **Rule Maintenance:** + - Update rules when new patterns emerge + - Add examples from actual codebase + - Remove outdated patterns + - Cross-reference related rules + +- **Best Practices:** + - Use bullet points for clarity + - Keep descriptions concise + - Include both DO and DON'T examples + - Reference actual code over theoretical examples + - Use consistent formatting across rules + +--- +SELF_IMPROVE +--- +description: Guidelines for continuously improving this rules document based on emerging code patterns and best practices. +globs: **/* +filesToApplyRule: **/* +alwaysApply: true +--- + +- **Rule Improvement Triggers:** + - New code patterns not covered by existing rules + - Repeated similar implementations across files + - Common error patterns that could be prevented + - New libraries or tools being used consistently + - Emerging best practices in the codebase + +- **Analysis Process:** + - Compare new code with existing rules + - Identify patterns that should be standardized + - Look for references to external documentation + - Check for consistent error handling patterns + - Monitor test patterns and coverage + +- **Rule Updates:** + - **Add New Rules When:** + - A new technology/pattern is used in 3+ files + - Common bugs could be prevented by a rule + - Code reviews repeatedly mention the same feedback + - New security or performance patterns emerge + + - **Modify Existing Rules When:** + - Better examples exist in the codebase + - Additional edge cases are discovered + - Related rules have been updated + - Implementation details have changed + +- **Example Pattern Recognition:** + ```typescript + // If you see repeated patterns like: + const data = await prisma.user.findMany({ + select: { id: true, email: true }, + where: { status: 'ACTIVE' } + }); + + // Consider adding a PRISMA section in the .windsurfrules: + // - Standard select fields + // - Common where conditions + // - Performance optimization patterns + ``` + +- **Rule Quality Checks:** + - Rules should be actionable and specific + - Examples should come from actual code + - References should be up to date + - Patterns should be consistently enforced + +- **Continuous Improvement:** + - Monitor code review comments + - Track common development questions + - Update rules after major refactors + - Add links to relevant documentation + - Cross-reference related rules + +- **Rule Deprecation:** + - Mark outdated patterns as deprecated + - Remove rules that no longer apply + - Update references to deprecated rules + - Document migration paths for old patterns + +- **Documentation Updates:** + - Keep examples synchronized with code + - Update references to external docs + - Maintain links between related rules + - Document breaking changes + +Follow WINDSURF_RULES for proper rule formatting and structure of windsurf rule sections. \ No newline at end of file diff --git a/README.md b/README.md index 459e20d..80b33ef 100644 --- a/README.md +++ b/README.md @@ -104,3 +104,82 @@ pre-commit install Most of the time any errors encountered by pre-commit are automatically fixed. Run `git status` to see the fixed files, run `git add .` to add the fixes, and rerun the commit. You will need to manually fix any errors that are not automatically fixed. + +## Troubleshooting + +### AWS Secrets Manager + +If you're experiencing authentication issues or need to verify that your secrets are properly configured in AWS Secrets Manager, you can use the provided script to list all secrets in your account: + +```shell +python scripts/list_aws_secrets.py +``` + +**Usage Options:** +- `--profile <profile_name>`: Use a specific AWS CLI profile +- `--region <region_name>`: Use a specific AWS region + +**Examples:** +```shell +# List secrets using default profile and region +python scripts/list_aws_secrets.py + +# List secrets using a specific AWS profile +python scripts/list_aws_secrets.py --profile my-aws-profile + +# List secrets in a specific region +python scripts/list_aws_secrets.py --region us-east-1 + +# Use both custom profile and region +python scripts/list_aws_secrets.py --profile my-aws-profile --region us-west-2 +``` + +This script will show you all secrets (including those pending deletion) and help you verify that the required authentication secrets are present: +- A secret with `private-key` in the name (RSA private key in PEM format) +- A secret with `public-key` in the name (RSA public key in PEM format) +- A secret with `username` in the name (login username) +- A secret with `password` in the name (bcrypt hashed password) + +The script outputs both a formatted table and detailed JSON for comprehensive debugging. + +### Deleting Authentication Secrets + +If you need to delete and recreate the authentication secrets, you can use the provided deletion script. **This script forces permanent deletion with no recovery period** and only allows deletion of the four authentication-related secrets for safety: + +```shell +python scripts/delete_aws_secrets.py <secret_name> +``` + +**Allowed secret names:** +- `private-key` +- `public-key` +- `username` +- `password` + +**Usage Options:** +- `--profile <profile_name>`: Use a specific AWS CLI profile +- `--region <region_name>`: Use a specific AWS region +- `--no-force`: Use standard 30-day recovery period instead of immediate deletion +- `--yes`: Skip confirmation prompt (dangerous!) + +**Examples:** +```shell +# Delete all secrets containing "private-key" (with confirmation) +python scripts/delete_aws_secrets.py private-key + +# Delete using specific profile and region +python scripts/delete_aws_secrets.py username --profile my-aws-profile --region us-east-1 + +# Delete without force (30-day recovery period) +python scripts/delete_aws_secrets.py password --no-force + +# Delete without confirmation (use with extreme caution!) +python scripts/delete_aws_secrets.py public-key --yes +``` + +**Safety Features:** +- Only allows deletion of hardcoded authentication secret names +- Requires typing "DELETE" (all caps) to confirm +- Shows all matching secrets before deletion +- Provides detailed feedback on success/failure +- Forces permanent deletion by default (ignores retention policies) diff --git a/backend/src/external/aws/secret_manager.py b/backend/src/external/aws/secret_manager.py index 822c178..2433643 100644 --- a/backend/src/external/aws/secret_manager.py +++ b/backend/src/external/aws/secret_manager.py @@ -7,7 +7,7 @@ class SecretManager(CloudSecretManager): def __init__(self) -> None: - self.client: SecretsManagerClient = boto3.client("secretsmanager") + self.client: SecretsManagerClient = boto3.client("secretsmanager", region_name="us-east-1") def get_secret(self, secret_id: str) -> str: try: diff --git a/backend/src/external/aws/textract.py b/backend/src/external/aws/textract.py index 2000d51..d2ed933 100644 --- a/backend/src/external/aws/textract.py +++ b/backend/src/external/aws/textract.py @@ -149,8 +149,22 @@ def _parse_textract_queries(textract_response): value, confidence = Textract._get_text_and_confidence_from_relationship_blocks( query_block, query_result_blocks, "ANSWER" ) - - extracted_data[query_block["Query"]["Text"]] = {"value": value, "confidence": confidence} + + # Extract bounding box information + bounding_box = None + related_ids = next((rel["Ids"] for rel in query_block.get("Relationships", []) + if rel["Type"] == "ANSWER"), []) + + if related_ids and related_ids[0] in query_result_blocks: + result_block = query_result_blocks[related_ids[0]] + if "Geometry" in result_block and "BoundingBox" in result_block["Geometry"]: + bounding_box = result_block["Geometry"]["BoundingBox"] + + extracted_data[query_block["Query"]["Text"]] = { + "value": value, + "confidence": confidence, + "boundingBox": bounding_box + } return extracted_data @@ -172,6 +186,7 @@ def _parse_textract_forms(response): value_texts = [] value_confidences = [] + value_bounding_box = None for relationship in relationships: if relationship["Type"] != "VALUE": @@ -186,11 +201,21 @@ def _parse_textract_forms(response): if value_text != "": value_texts.append(value_text) value_confidences.append(value_confidence) + + # Store the bounding box of the VALUE block + # We only keep the first meaningful value's bounding box for simplicity + if value_bounding_box is None and "Geometry" in value_block and "BoundingBox" in value_block["Geometry"]: + value_bounding_box = value_block["Geometry"]["BoundingBox"] confidence = -1 if len(value_texts) > 0: confidence = statistics.fmean(value_confidences) - extracted_data[key_text] = {"value": " ".join(value_texts), "confidence": confidence} + + extracted_data[key_text] = { + "value": " ".join(value_texts), + "confidence": confidence, + "boundingBox": value_bounding_box + } return extracted_data diff --git a/deploy.py b/deploy.py new file mode 100755 index 0000000..de8f8ed --- /dev/null +++ b/deploy.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 + +# builds the backend and the frontend +# pushes both with terraform + +import argparse +import os +import subprocess +import sys +from pathlib import Path + +ROOT_DIR = Path(__file__).parent.absolute() +BACKEND_DIR = ROOT_DIR / 'backend' +UI_DIR = ROOT_DIR / 'ui' +IAC_DIR = ROOT_DIR / 'iac' + +# AWS Profile from memory +# AWS_PROFILE = 'default' +AWS_PROFILE = 'AWSAdministratorAccess-328307993388' + +# AWS Region from memory +AWS_REGION = 'us-east-1' + +def get_sso_credentials(): + """Get SSO credentials and return as environment variables.""" + print("=== Getting SSO Credentials ===") + try: + result = subprocess.run( + ['aws', 'configure', 'export-credentials', '--profile', AWS_PROFILE, '--format', 'env'], + capture_output=True, + text=True, + check=True + ) + + # Parse the export commands + credentials = {} + for line in result.stdout.strip().split('\n'): + if line.startswith('export '): + key_value = line.replace('export ', '') + key, value = key_value.split('=', 1) + credentials[key] = value + + return credentials + except subprocess.CalledProcessError as e: + print(f"Failed to get SSO credentials: {e}") + print("Please run: aws sso login --profile AWSAdministratorAccess-328307993388") + sys.exit(1) + +def run_command(cmd, cwd=None, env=None, check=True): + """Run a shell command and return the result.""" + print(f"Running: {' '.join(cmd)}") + + # Merge the current environment with any additional environment variables + command_env = os.environ.copy() + if env: + command_env.update(env) + + try: + result = subprocess.run( + cmd, + cwd=cwd, + env=command_env, + check=check, + text=True, + capture_output=True + ) + print(result.stdout) + return result + except subprocess.CalledProcessError as e: + print(f"Command failed with exit code {e.returncode}") + print(f"STDOUT: {e.stdout}") + print(f"STDERR: {e.stderr}") + if check: + sys.exit(e.returncode) + return e + +def build_backend(): + """Build the backend Lambda package.""" + print("=== Building Backend ===") + os.chdir(BACKEND_DIR) + run_command([sys.executable, 'build.py'], cwd=BACKEND_DIR) + print("Backend build completed successfully!") + +def build_frontend(): + """Build the frontend assets.""" + print("=== Building Frontend ===") + # Make sure node_modules exists + if not (UI_DIR / 'node_modules').exists(): + run_command(['npm', 'install'], cwd=UI_DIR) + + # Build the frontend + run_command(['npm', 'run', 'build'], cwd=UI_DIR) + print("Frontend build completed successfully!") + +def terraform_init(): + """Initialize Terraform.""" + print("=== Initializing Terraform ===") + # Get SSO credentials + sso_credentials = get_sso_credentials() + env = { + 'AWS_PROFILE': AWS_PROFILE, + 'AWS_REGION': AWS_REGION, + **sso_credentials # Include SSO credentials + } + run_command(['terraform', 'init'], cwd=IAC_DIR, env=env) + +def terraform_plan(): + """Run Terraform plan.""" + print("=== Running Terraform Plan ===") + # Get SSO credentials + sso_credentials = get_sso_credentials() + env = { + 'AWS_PROFILE': AWS_PROFILE, + 'AWS_REGION': AWS_REGION, + **sso_credentials # Include SSO credentials + } + run_command(['terraform', 'plan'], cwd=IAC_DIR, env=env) + +def terraform_apply(): + """Apply Terraform changes.""" + print("=== Applying Terraform Changes ===") + # Get SSO credentials + sso_credentials = get_sso_credentials() + env = { + 'AWS_PROFILE': AWS_PROFILE, + 'AWS_REGION': AWS_REGION, + **sso_credentials # Include SSO credentials + } + run_command(['terraform', 'apply', '-auto-approve'], cwd=IAC_DIR, env=env) + +def main(): + parser = argparse.ArgumentParser(description='Build and deploy the document extractor application') + parser.add_argument('--skip-backend', action='store_true', help='Skip backend build') + parser.add_argument('--skip-frontend', action='store_true', help='Skip frontend build') + parser.add_argument('--plan-only', action='store_true', help='Only run Terraform plan without applying') + args = parser.parse_args() + + # Build steps + if not args.skip_backend: + build_backend() + else: + print("Skipping backend build...") + + if not args.skip_frontend: + build_frontend() + else: + print("Skipping frontend build...") + + # Terraform steps + terraform_init() + + if args.plan_only: + terraform_plan() + print("\nTerraform plan completed. Run without --plan-only to apply the changes.") + else: + terraform_apply() + print("\nDeployment completed successfully!") + +if __name__ == '__main__': + main() diff --git a/docs/configuration_baseline.md b/docs/configuration_baseline.md new file mode 100644 index 0000000..59e4a43 --- /dev/null +++ b/docs/configuration_baseline.md @@ -0,0 +1,243 @@ +# Configuration Baseline for AWS Resources (1.0) + +Status: In Review +Created by: James Armes +Created time: March 6, 2025 3:54 PM +Last updated by: James Armes +Updated: April 3, 2025 2:24 PM +Authors: James Armes +Changes: Initial version +Document: Configuration Baseline for AWS Resources (https://www.notion.so/Configuration-Baseline-for-AWS-Resources-1ae373fd79b280d58aedda39c6b2d34f?pvs=21) +Version: 1.0 + +# Background + +While Amazon Web Services (AWS) makes it easy to create resources, put them together, and launch an application; it requires more care to deploy **secure** resources that meet our **compliance requirements**. This document outlines a baseline for certain AWS resources. + +When working with cloud resources, AWS or otherwise, we recommend using **infrastructure as code** (IaC) to manage your configuration. [OpenTofu](https://opentofu.org/) is encouraged, and we maintain a [collection of modules](https://github.com/codeforamerica/tofu-modules) to get you started. By default, the resources created by these modules meet our security and compliance requirements. + +# **Account** + +Most of your account related settings will be configured by an organization administrator before the account is made available. To request a new AWS account, please submit a ticket with the [IT Help Desk](https://codeforamerica.atlassian.net/servicedesk/customer/portal/1). + +- Root user should *only* use a hardware device for multi-factor authentication (i.e. Yubikey) +- S3 public access should be blocked +- EBS encryption should be required in each region +- Public snapshot sharing should be disabled in each region +- A security contact should be set +- AWS Macie should be enabled on all regions + +## **Existing accounts** + +Special care should be taken *before* enabling certain settings for existing accounts. + +- S3 public access should be block *after* ensuring existing buckets not public +- EBS encryption should be enabled *after* ensuring existing volumes are encrypted +- AWS Macie can incur high costs when scanning buckets for the first time; an inventory should be done before enabling, and any large buckets (> 100GB) that can be removed should be emptied and deleted + +# **Databases** + +Related modules: [aws-serverless-database](https://github.com/codeforamerica/tofu-modules-aws-serverless-database) + +Databases *should never* be available directly from the public Internet. You should avoid making databases accessible from the Internet at all, but if it is necessary, you should use a network load balancer to proxy the traffic. + +Databases in AWS fall into three categories: + +- Aurora: Fully managed, highly scalable, and (optionally) serverless database clusters +- RDS: Managed databases instances, but you have to handle scaling and certain configuration yourself +- EC2 instances: Databases that aren’t available in the other categories, or requires self-hosting for other reasons + +Aurora serverless is preferred for its hands-off approach, fault tolerance, and easy scalability. This option is more expensive, but allows for reduced costs by scaling down outside of peak hours. If your workloads will cause little scaling, it may be more appropriate to use Aurora provisioned. + +All databases, regardless of their type, should adhere to the following: + +- The requirements for [EC2 instances](https://docs.google.com/document/d/1gd38E3ZTCX59gF42xT5WwqedzcZLQEkXux7o-Ls6xUI/edit?tab=t.0#heading=h.kwh4fs4b13rn), where applicable +- Subnets: Databases *may* be placed in a subnet that has neither incoming or outgoing traffic to the Internet, but should otherwise be placed in a private subnet +- Monitoring: Enhanced monitoring should be enabled for Aurora and RDS databases +- Backups: Production databases should follow the guidance below, while non-production databases should use a more cost-effective retention strategy + +## **Backups** + +Though the process for creating backups will differ based on the database type selected, the importance of creating, retaining, and testing those backups remains the same. Your project may have specific requirements based on the type of data being stored, or contractual requirements. Use the following as a baseline when you don’t have differing requirements. + +- Daily backups: Retain for 31 days +- Monthly backups: Retain for 13 months +- Yearly backups: Retained for 3 years +- Restores: Tested quarterly +- Copies: Backups should be copied to one other region + +# **EC2 Instances** + +EC2 instances *should not* be available directly from the public Internet. To serve traffic from an instance, you should use a load balancer or other proxy. To access instances, use [Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) instead of SSH. + +Additionally, EC2 instances should: + +- Security groups: Use security groups with the minimum openings to support your use case; don’t use large shared security groups +- Instance profile: Instances should have an IAM instance profile attached (see below for requirements) +- AMI: Instances should be launched from approved images +- Sizing: Use free-tier instances when possible, especially in non-production environments; follow compute optimizer recommendations to right-size instances +- Logging & monitoring: Use the CloudWatch agent for to forward logs and metrics; detailed monitoring should be enabled +- Patching: Instances should adhere to a regular patch scheduled + +## **Approved AMIs** + +The following Amazon Machine Images (AMIs) are approved for use in all environments. Make sure the image you are using is tagged with “Verified provider” and “Free tier eligible”. + +- Amazon Linux 2023 (preferred[¹](https://www.notion.so/Configuration-Baseline-for-AWS-Resources-1-0-1ae373fd79b280418d69e8dd55a6a694?pvs=21)) +- Amazon Linux 2 +- Ubuntu Server 24.04 LTS +- Ubuntu Server 22.04 LTS + +If your project requires the use of a different AMI, please [open a ticket](https://codeforamerica.atlassian.net/servicedesk/customer/portal/1) and we can work with you to find a compatible image. + +## **Instance Profile** + +IAM instance profiles are used to enable EC2 instances to interact with the AWS APIs without needing to manage local credentials. All instances should have an instance profile attached. Instance profiles should meet the following requirements: + +- Include [necessary permissions](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-instance-profile.html) for Session Manager +- Allow logs and metrics to be forwarded to CloudWatch +- Follow the principle of least privilege + +## **Patching** + +On EC2 instances, you are responsible for ensuring the system is regularly updated. Patches should follow the *minimum* requirements below: + +- [Zero-day vulnerabilities](https://www.trendmicro.com/vinfo/us/security/definition/zero-day-vulnerability): Within 24 hours of detection +- Critical vulnerabilities: Within 15 days of detection +- High vulnerabilities: Within 30 days of detection +- Regular patching: Should be performed monthly + +We recommend using ephemeral instances (instances that can be replaced) rather than long running instances. This allows systems to be “patched” by deploying a new, tested image. Regardless of your architecture or patching method, they must meet the requirements above. + +You can use [Amazon Inspector](https://docs.aws.amazon.com/inspector/latest/user/what-is-inspector.html) to monitor for vulnerabilities in your instances. + +# **IAM Users** + +Users can access AWS using [Identity Center](https://www.notion.so/AWS-Identity-Center-e8a28122b2f44595a2ef56b46788ce2c?pvs=21), including programmatic access from their local machine. Identity and Access Management (IAM) users may be created for access from remote systems or services where alternatives, such as [cross-account roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html) and [IAM roles anywhere](https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html), aren’t available. These are commonly referred to as “service accounts” or “bots.” + +When creating an IAM user for programmatic access, the following restrictions apply: + +- Always check if an alternative is supported +- Create a single user per connection; don’t use keys in more than one place +- Choose a name that makes the intention of the user clear +- The user *must not* have access to the AWS management console +- The user must have a policy attached that follows the principle of least privilege +- Access keys must be rotated every 90 days + +# **Logging & Monitoring** + +Related modules: [aws-logging](https://github.com/codeforamerica/tofu-modules-aws-logging) + +AWS services offer logging to either CloudWatch logs or an S3 bucket. CloudWatch logs are great for real-time monitoring, but can be expensive for long-term storage. S3 is relatively inexpensive for long-term storage, but comes with a delay in log delivery. We use Datadog to aggregate these logs, along with metrics from CloudWatch. + +Use the following guidance when configuring logging for your project: + +- All resources with support for logging should be configured to do so +- Enabled enhanced monitoring when available, such as with EC2 instances and RDS databases +- [Deploy the Datadog forwarder](https://www.notion.so/Deploy-Datadog-AWS-Integration-efaf8e6d42bb472284c77ad25c976804?pvs=21) to all regions the project will operate in +- Prefer CloudWatch logs for real-time monitoring +- CloudWatch logs should have a retention period of 30 days, unless otherwise necessary +- Use a customer manager key (CMK) to encrypt your CloudWatch logs +- S3 logs should be retained for at least 90 days +- See [S3 buckets](https://docs.google.com/document/d/1gd38E3ZTCX59gF42xT5WwqedzcZLQEkXux7o-Ls6xUI/edit?tab=t.0#heading=h.4vsw87wz9v2s) to configure your bucket for logging +- [Configure Datadog](https://docs.datadoghq.com/logs/guide/send-aws-services-logs-with-the-datadog-lambda-function/?tab=awsconsole) log ingestion + +# **S3 Buckets** + +S3 buckets should *never* have public access enabled. If you believe you have a use case for a public S3 bucket, there is often a safer alternative. Please [file a ticket](https://codeforamerica.atlassian.net/servicedesk/customer/portal/1) if you need support with this. + +- Logging: Access logs should be configured to target a logging bucket +- Encryption: Use a customer managed key (CMK) to encrypt buckets when possible; avoid using the AWS managed key unless *absolutely necessary* +- Versioning: Object versioning should be enabled +- Object lock: Object locking should be configured to prevent accidental deletions or overwrites +- Lifecycle policy: Buckets should include a lifecycle policy that meets compliance, as well as a any contractual requirements, based in the type of data +- SSL enforcement: The bucket policy should [require SSL](https://repost.aws/knowledge-center/s3-bucket-policy-for-config-rule) +- Replication: Production buckets should be configured with cross-region replication; this is recommended for production-like environments (e.g. staging) but not required + +## **Infrastructure State Buckets** + +Buckets used to store infrastructure state for OpenTofu will have a few exceptions: + +- Object lock: The state file can change often, with versioning and locking using DynamoDB, the S3 object lock is unnecessary and can interfere with operations +- Replication: Infrastructure resources are region-specific and can be rebuilt using IaC, making replication unnecessary + +## **Logging Buckets** + +Logging buckets are exempt from a few of these requirements: + +- Logging: Access logs *cannot* be configured without creating a circular reference +- Lifecycle policy: Logs should transition to a storage storage class designed for long-term storage, such as infrequent access or glacier, and be retained for at least 90 days +- Encryption: Logging buckets *must use* the AWS managed key due to limitations with logging for some AWS services +- Replication: Replication *may* be configured for logging buckets, but since we ingest these logs into DataDog, it is not necessary + +# **VPC** + +Related modules: [aws-vpc](https://github.com/codeforamerica/tofu-modules-aws-vpc) + +If you’re launching any compute resources, whether they be provisioned or serverless, you will need to configure networking. If you haven’t been assigned a CIDR block, [open a ticket](https://codeforamerica.atlassian.net/servicedesk/customer/portal/1) to request one. + +All VPCs should be configured with the following: + +- Subnets: Public and private subnets in at least two availability zones (three recommended[²](https://www.notion.so/Configuration-Baseline-for-AWS-Resources-1-0-1ae373fd79b280418d69e8dd55a6a694?pvs=21)) for high availability (HA) +- Logging: Flow logs should be configured with a CloudWatch log group +- Internet gateway: Configured on public subnets +- NAT gateway: Each private production subnet should include a NAT gateway; non-production subnets may share a single gateway to reduce costs +- VPC endpoints: [Endpoints](https://docs.google.com/document/d/1gd38E3ZTCX59gF42xT5WwqedzcZLQEkXux7o-Ls6xUI/edit?tab=t.0#heading=h.gwtrhgnvqbni) should be configured for any AWS services that may be accessed from within the VPC +- Security groups: The default security group should not allow *any* inbound or outbound traffic; create purpose-built security groups with minimum openings + +## **Subnets** + +Private subnets should be used for your compute resources, along with any other resources that will not be directly available from the public Internet. In production environments, each private subnet should include a NAT gateway. You may use a single NAT gateway in non-production environments to reduce costs, but this would not meet the definition of HA. + +Public subnets should be used for your edge resources that will be directly available from the public Internet. *Compute resources, whether they be **EC2 instances, databases, lambdas, etc.** should **never be placed in public subnets**.* Use edge resources like load balancers to serve traffic. If you need to connect to EC2 instances in your VPC, you can use [Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) to do so without the need for an SSH bastion. + +## **VPC endpoints** + +[VPC endpoints](https://docs.aws.amazon.com/whitepapers/latest/aws-privatelink/what-are-vpc-endpoints.html) enable resources in a VPC to communicate with Amazon’s APIs over a private network. This reduces costs, since traffic doesn’t go through a NAT gateway, and improves security as the traffic does not need to traverse the public Internet. + +At a minimum, the following endpoints should be created and attached to your private subnets: + +- ec2 +- ec2messages +- ecr.api +- ecr.dkr +- guardduty-data +- s3 +- ssm +- ssm-contacts +- ssm-incidents +- ssmmessages + +# **Resources Not Covered by This Document** + +For resources not covered by this document, you are responsible for ensuring the resources you create are secure and meet our compliance requirements. Check the [shared OpenTofu modules](https://github.com/codeforamerica/tofu-modules) to check if someone has already created a module to use as a starting point. Be sure to follow the [additional guidance](https://docs.google.com/document/d/1gd38E3ZTCX59gF42xT5WwqedzcZLQEkXux7o-Ls6xUI/edit?tab=t.0#heading=h.71y3vmvqmyzy) below; the services mentioned will help you to find the proper configuration for your resources. If you require further assistance, don’t hesitate to open a [help desk ticket](https://codeforamerica.atlassian.net/servicedesk/customer/portal/1). + +# **Additional Guidance** + +AWS provides a number of services to help monitor the security and compliance of your resources. While our security teams monitor our posture regularly, it is ultimately up to service operators to maintain their environments in compliance. + +Use the following guidelines to keep your accounts and resources in compliance[³](https://www.notion.so/Configuration-Baseline-for-AWS-Resources-1-0-1ae373fd79b280418d69e8dd55a6a694?pvs=21). + +- Update IaC dependencies: Keep your module and provider dependencies up to date to get the latest baseline configurations +- Security Hub: Use [AWS Security Hub](https://docs.aws.amazon.com/securityhub/latest/userguide/what-is-securityhub.html) to monitor the posture of your environment; open a help desk ticket if you believe you have encountered a false positive or need help satisfying controls +- Inspector: Monitor [Amazon Inspector](https://docs.aws.amazon.com/inspector/latest/user/what-is-inspector.html) for vulnerabilities in your instances or container images +- Cost Explorer: Use [AWS Cost Explorer](https://docs.aws.amazon.com/cost-management/latest/userguide/ce-exploring-data.html) to keep an eye on costs; unexpected changes in usage can be a sign of compromised account +- Macie[⁴](https://www.notion.so/Configuration-Baseline-for-AWS-Resources-1-0-1ae373fd79b280418d69e8dd55a6a694?pvs=21): [Amazon Macie](https://docs.aws.amazon.com/macie/latest/user/what-is-macie.html) detects sensitive data in your S3 buckets, along with the configuration of buckets containing sensitive data +- Use a static analysis tool such as [trivy](https://trivy.dev/latest/) to verify your IaC + +# **Further Reading** + +Links to various services and documentation have been included in the relevant sections. Below you will find links to additional documentation and resources that you mind find helpful. + +- [Amazon Web Services on Notion](https://www.notion.so/Amazon-Web-Services-6b8b38f0189940588af9506166075f40?pvs=21) +- [Code for America OpenTofu modules documentation](https://dev.docs.cfa.codes/tofu-modules/index.html) +- [Code for America Infrastructure Guild](https://www.notion.so/Infrastructure-Guild-0a3f4bbe25d34d0fbd363df1e87e3f71?pvs=21) +- [AWS Workshops](https://workshops.aws/) + +¹ Amazon Linux 2023 is based on Fedora and CentOS, and comes configured with log and metric collection. + +² While only two AZs are required to meet HA, three ensure you remain HA if one AZ goes down. + +³ The AWS services in this list are configured by the organization and are available to you with no extra steps. + +⁴ Amazon Macie is not currently enabled for all accounts. If you would Macie enabled for your account, please open a help desk ticket. diff --git a/docs/configuration_baseline_todo.md b/docs/configuration_baseline_todo.md new file mode 100644 index 0000000..b9b742e --- /dev/null +++ b/docs/configuration_baseline_todo.md @@ -0,0 +1,388 @@ +# Configuration Baseline TODO + +This document summarizes all findings from the compliance review of the current infrastructure-as-code (IaC) configuration, based on the [AWS Configuration Baseline Policy](../path/to/policy). Each section below lists areas that are out of compliance or could be improved, along with recommended actions. + +## ✅ Implementation Status + +**COMPLETED:** 🎉 Major infrastructure baseline compliance work has been completed! + +**Key Achievements:** +- ✅ **VPC Infrastructure** - Complete security isolation with private subnets, VPC endpoints, and proper networking +- ✅ **Encryption** - All services using customer-managed KMS keys with proper rotation +- ✅ **Logging & Monitoring** - Comprehensive CloudWatch logging, Security Hub, and cost monitoring +- ✅ **Backup & Recovery** - Automated backups for DynamoDB with proper retention policies +- ✅ **Security Controls** - SSL enforcement, security headers, least privilege IAM policies +- ✅ **Dead Letter Queues** - Error handling for all async processing + +**Remaining Items:** +- Cross-region replication for S3 (optional for non-prod) +- WAF integration for CloudFront (optional security enhancement) +- Amazon Macie enablement (requires help desk ticket) +- Datadog integration (if external logging desired) + +--- + +## **CRITICAL: Missing VPC Infrastructure** + +### Findings + +- **No VPC Defined:** + All resources are deployed in the default VPC, which doesn't meet baseline requirements. +- **No Subnet Architecture:** + Missing public/private subnet separation across multiple AZs. +- **No Network Security:** + No custom security groups, route tables, or network ACLs defined. + +### Action Items + +- [x] **Create VPC** with proper CIDR block allocation +- [x] **Deploy subnets** in at least 2 AZs (3 recommended) with public/private separation +- [x] **Configure Internet Gateway** for public subnets +- [x] **Deploy NAT Gateways** for each private subnet (production) or shared (non-production) +- [x] **Enable VPC Flow Logs** to CloudWatch with 30-day retention and CMK encryption +- [x] **Create VPC Endpoints** for AWS services (s3, dynamodb, lambda, sqs, ssm, ssmmessages, ec2messages, kms, secretsmanager, logs) +- [x] **Configure Security Groups** with least privilege rules +- [x] **Disable default security group** (no inbound/outbound rules) +- [x] **Move all Lambda functions** to private subnets +- [x] **Configure proper route tables** for public/private subnet routing + +--- + +## S3 Buckets + +### Findings + +- **Encryption with CMK:** + No explicit `server_side_encryption_configuration` using a customer-managed KMS key (CMK) is set for S3 buckets. +- **Versioning:** + Versioning is enabled for the `website_storage` bucket, but not for `document_storage`. +- **Object Lock:** + No object locking configured to prevent accidental deletions or overwrites. +- **Access Logging:** + No access logging is configured for any S3 bucket. +- **SSL Enforcement:** + No bucket policy is present to enforce SSL (deny non-SSL requests). +- **Cross-Region Replication:** + No cross-region replication is configured for production buckets. +- **Lifecycle Policy:** + Only the `document_storage` bucket has a lifecycle policy; others may need review. + +### Action Items + +- [x] Add `server_side_encryption_configuration` with a CMK to all S3 buckets. +- [x] Enable versioning on all S3 buckets. +- [x] **Configure object locking** to prevent accidental deletions or overwrites. +- [x] Configure access logging for all S3 buckets. +- [x] Add a bucket policy to enforce SSL (`"aws:SecureTransport": "false"` deny). +- [ ] Configure cross-region replication for production S3 buckets. +- [x] Review and add lifecycle policies to all buckets as appropriate. + +--- + +## IAM + +### Findings + +- **Least Privilege:** + Lambda execution roles have broad permissions (e.g., `dynamodb:*`, `s3:*`, `secretsmanager:*`, `sqs:*`). These should be scoped to only the required actions and resources. + +### Action Items + +- [x] Refine IAM policies to follow the principle of least privilege, granting only necessary actions on specific resources. + +--- + +## IAM Users + +### Findings + +- **Service Account Management:** + No evidence of proper IAM user management for programmatic access. +- **Access Key Rotation:** + No automated or documented access key rotation process. + +### Action Items + +- [ ] **Review existing IAM users** and ensure they follow service account best practices +- [ ] **Implement access key rotation** every 90 days for any IAM users +- [ ] **Ensure no console access** for programmatic IAM users +- [ ] **Document IAM user purposes** and usage locations +- [ ] **Consider alternatives** like cross-account roles or IAM Roles Anywhere where possible + +**Note:** No IAM users are currently used in this infrastructure - all access is role-based. + +--- + +## Lambda Functions + +### Findings + +- **CloudWatch Log Groups:** + No explicit log groups with retention and encryption configured. +- **Dead Letter Queues:** + No DLQ configuration for failed executions. +- **Reserved Concurrency:** + Set to unlimited (-1) which could impact other functions. +- **VPC Configuration:** + Functions are not deployed in VPC private subnets. + +### Action Items + +- [x] **Create explicit CloudWatch log groups** with 30-day retention for all Lambda functions +- [x] **Enable CMK encryption** for all Lambda log groups +- [x] **Configure Dead Letter Queues** for error handling +- [x] **Review and set appropriate reserved concurrency** limits +- [x] **Deploy Lambda functions in VPC private subnets** (after VPC creation) +- [x] **Add VPC configuration** to Lambda functions with appropriate security groups + +--- + +## API Gateway + +### Findings + +- **Access Logging:** + No CloudWatch access logs configured. +- **Execution Logging:** + No execution logs for debugging and monitoring. +- **X-Ray Tracing:** + Not enabled for performance monitoring. + +### Action Items + +- [x] **Enable CloudWatch access logs** for API Gateway with 30-day retention +- [x] **Enable execution logging** for API Gateway +- [x] **Enable X-Ray tracing** for performance monitoring +- [x] **Configure log groups with CMK encryption** + +--- + +## CloudFront + +### Findings + +- **Access Logging:** + No access logs configured to S3. +- **Security Headers:** + No security headers configured (HSTS, CSP, etc.). +- **WAF Integration:** + No Web Application Firewall configured. + +### Action Items + +- [x] **Configure CloudFront access logs** to dedicated S3 logging bucket +- [x] **Add security headers** via CloudFront functions or Lambda@Edge +- [ ] **Consider WAF integration** for additional security +- [ ] **Enable real-time logs** if detailed monitoring is needed + +--- + +## Logging & Monitoring + +### Findings + +- **CloudWatch Log Retention:** + No explicit log group retention policy is set (should be 30 days). +- **CloudWatch Log Encryption:** + No evidence that CloudWatch logs are encrypted with a CMK. +- **Resource Logging:** + No explicit logging configuration for Lambda or other resources. + +### Action Items + +- [x] Set CloudWatch log group retention to 30 days for all log groups. +- [x] Enable encryption with a CMK for all CloudWatch log groups. +- [x] Ensure all resources (Lambdas, API Gateway, etc.) have logging enabled. +- [ ] **Deploy Datadog forwarder** to all regions for log aggregation +- [ ] **Configure Datadog log ingestion** for CloudWatch and S3 logs + +--- + +## DynamoDB + +### Findings + +- **Backups:** + No backup or point-in-time recovery configuration found. +- **Monitoring:** + No enhanced monitoring or CloudWatch alarms configured. +- **Encryption:** + No explicit encryption configuration with CMK. + +### Action Items + +- [x] Enable point-in-time recovery and regular backups for DynamoDB tables. +- [x] Enable enhanced monitoring and set up CloudWatch alarms as appropriate. +- [x] **Configure server-side encryption with CMK** +- [x] **Add backup vault configuration** for long-term retention + +--- + +## KMS + +### Findings + +- **Key Aliases:** + No descriptive aliases configured for keys. +- **Key Policies:** + Using default key policy, should be more restrictive. + +### Action Items + +- [x] **Add descriptive aliases** for KMS keys +- [x] **Configure custom key policies** following least privilege +- [x] **Enable automatic key rotation** (already enabled) + +--- + +## Secrets Manager + +### Findings + +- **Automatic Rotation:** + No automatic rotation configured where applicable. +- **CMK Encryption:** + Need to verify secrets are encrypted with CMK. + +### Action Items + +- [ ] **Configure automatic rotation** for applicable secrets +- [x] **Verify CMK encryption** is used for all secrets +- [x] **Add proper resource-based policies** for secret access + +--- + +## Security Groups + +### Findings + +- **No Custom Security Groups:** + No security groups defined, using defaults. +- **Default Security Group:** + Default security group likely allows traffic. + +### Action Items + +- [x] **Create purpose-built security groups** with minimal required access +- [x] **Ensure default security group denies all traffic** +- [x] **Apply security groups to all resources** following least privilege + +--- + +## General Recommendations + +- [ ] Review and update IaC dependencies regularly to inherit latest security baselines. +- [ ] Use static analysis tools (e.g., trivy) to scan IaC for misconfigurations. +- [ ] Document all exceptions and justifications for any deviations from the baseline. +- [x] **Deploy VPC infrastructure first** before addressing other compliance items. +- [ ] **Consider using Code for America's OpenTofu modules** for baseline-compliant configurations. + +--- + +## Databases (Future Consideration) + +### Findings + +- **No Database Resources:** + Current infrastructure uses DynamoDB only, but baseline requirements apply if RDS/Aurora are added. + +### Action Items (If databases are added) + +- [ ] **Deploy in private subnets** only +- [ ] **Enable enhanced monitoring** for Aurora/RDS +- [ ] **Configure backup strategy**: Daily (31 days), Monthly (13 months), Yearly (3 years) +- [ ] **Test restores quarterly** +- [ ] **Copy backups to another region** +- [ ] **Use approved AMIs** if using EC2-based databases + +--- + +## EC2 Instances (Future Consideration) + +### Findings + +- **No EC2 Instances:** + Current infrastructure doesn't use EC2 instances, but baseline requirements apply if they are added. + +### Action Items (If EC2 instances are added) + +- [ ] **Deploy in private subnets** only, never public subnets +- [ ] **Use approved AMIs** (Amazon Linux 2023, Amazon Linux 2, Ubuntu Server 24.04 LTS, Ubuntu Server 22.04 LTS) +- [ ] **Configure IAM instance profiles** with Session Manager permissions +- [ ] **Install and configure CloudWatch agent** for logs and metrics +- [ ] **Enable detailed monitoring** +- [ ] **Implement patching strategy**: Zero-day (24h), Critical (15 days), High (30 days), Regular (monthly) +- [ ] **Use ephemeral instances** where possible instead of long-running instances +- [ ] **Configure appropriate security groups** with minimal required access +- [ ] **Right-size instances** using compute optimizer recommendations +- [ ] **Use Session Manager** instead of SSH for access + +--- + +## SQS + +### Findings + +- **Encryption:** + SQS queue is encrypted with KMS - compliant. +- **Dead Letter Queue:** + No evidence of DLQ configuration for failed message handling. + +### Action Items + +- [x] **Configure Dead Letter Queue** for failed message handling +- [x] **Set appropriate message retention** and visibility timeout settings +- [x] **Enable CloudWatch metrics** and alarms for queue monitoring + +--- + +## Monitoring & Compliance Tools + +### Findings + +- **AWS Security Hub:** + Not configured for continuous compliance monitoring. +- **Amazon Inspector:** + Not enabled for vulnerability scanning (if applicable to future EC2 instances or container images). +- **AWS Cost Explorer:** + No mention of cost monitoring setup. +- **Amazon Macie:** + Not configured for sensitive data detection in S3 buckets. + +### Action Items + +- [x] **Enable AWS Security Hub** for compliance posture monitoring +- [ ] **Configure Amazon Inspector** for vulnerability scanning (when EC2 instances are deployed) +- [x] **Set up AWS Cost Explorer** monitoring and alerts for unexpected usage +- [ ] **Request Amazon Macie enablement** via help desk ticket for sensitive data detection +- [x] **Configure cost anomaly detection** to identify potential security issues + +--- + +## Next Steps + +**Deploy the Infrastructure:** + +```bash +cd iac +opentofu plan +opentofu apply +``` + +**Post-Deployment Actions:** + +1. **Update Email Address:** In `iac/monitoring.tf`, update the email address in the cost anomaly subscription from `admin@example.com` to your actual email address, then redeploy. + +2. **Validate Deployment:** + - Check Security Hub findings in AWS Console + - Verify VPC Flow Logs are working + - Test Lambda functions in VPC + - Confirm CloudWatch logs are encrypted + +3. **Review Configuration:** Use static analysis tools (e.g., trivy) to scan IaC for any remaining misconfigurations. + +**Note:** This infrastructure uses OpenTofu (open-source Terraform fork) for all IaC management. All `terraform` commands should be replaced with `opentofu` commands. + +--- + +_Last updated: December 2024_ diff --git a/docs/deploy-output-5-39.txt b/docs/deploy-output-5-39.txt new file mode 100644 index 0000000..ad9e4c7 --- /dev/null +++ b/docs/deploy-output-5-39.txt @@ -0,0 +1,881 @@ +=== Building Backend === +Running: /Library/Developer/CommandLineTools/usr/bin/python3 build.py +# This file was autogenerated by uv via the following command: +# uv export --frozen --no-dev --no-editable --no-build -o dist/requirements.txt +aws-lambda-typing==2.20.0 \ + --hash=sha256:1d44264cabfeab5ac38e67ddd0c874e677b2cbbae77a42d0519df470e6bbb49b \ + --hash=sha256:78b0d8ebab73b3a6b0da98a7969f4e9c4bb497298ec50f3217da8a8dfba17154 + # via src +bcrypt==4.3.0 \ + --hash=sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f \ + --hash=sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d \ + --hash=sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24 \ + --hash=sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3 \ + --hash=sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c \ + --hash=sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd \ + --hash=sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f \ + --hash=sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f \ + --hash=sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d \ + --hash=sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe \ + --hash=sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231 \ + --hash=sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef \ + --hash=sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18 \ + --hash=sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f \ + --hash=sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e \ + --hash=sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732 \ + --hash=sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304 \ + --hash=sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0 \ + --hash=sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62 \ + --hash=sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180 \ + --hash=sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af \ + --hash=sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669 \ + --hash=sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761 \ + --hash=sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51 \ + --hash=sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23 \ + --hash=sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09 \ + --hash=sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505 \ + --hash=sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4 \ + --hash=sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753 \ + --hash=sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59 \ + --hash=sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b \ + --hash=sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d \ + --hash=sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b \ + --hash=sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a \ + --hash=sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb \ + --hash=sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb \ + --hash=sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676 \ + --hash=sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b \ + --hash=sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe \ + --hash=sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281 \ + --hash=sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1 \ + --hash=sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef \ + --hash=sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d + # via src +boto3==1.38.8 \ + --hash=sha256:6bbc75bb51be9c5a33d07a4adf13d133c60f77b7c47bef1c46fda90b1297a867 \ + --hash=sha256:f3a4d79f499f567d327d2d8846d02ad18244d2927f88858a42a2438f52d9a0ef + # via src +botocore==1.38.8 \ + --hash=sha256:68d739300cc94232373517b27c5570de6ae6d809a2db644f30219f5c8e0371ce \ + --hash=sha256:f6ae08a56fe94e18d2aa223611a3b5e94123315d0cb3cb85764b029b2326c710 + # via + # boto3 + # s3transfer +botocore-stubs==1.37.29 \ + --hash=sha256:923127abb5fac0b8b0f11837a4641f2863bb4398b5bd6b11d1604134966c4bb6 \ + --hash=sha256:c59898bf1d09bf6a9f491f4705c5696e74b83156b766aa1716867f11b8a04ea1 + # via types-boto3 +cffi==1.17.1 ; platform_python_implementation != 'PyPy' \ + --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ + --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ + --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ + --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ + --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ + --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ + --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ + --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ + --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ + --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ + --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ + --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a + # via cryptography +cryptography==44.0.3 \ + --hash=sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43 \ + --hash=sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645 \ + --hash=sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44 \ + --hash=sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d \ + --hash=sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f \ + --hash=sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d \ + --hash=sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54 \ + --hash=sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137 \ + --hash=sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f \ + --hash=sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c \ + --hash=sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334 \ + --hash=sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b \ + --hash=sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2 \ + --hash=sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88 \ + --hash=sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c \ + --hash=sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359 \ + --hash=sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5 \ + --hash=sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d \ + --hash=sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028 \ + --hash=sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01 \ + --hash=sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904 \ + --hash=sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93 \ + --hash=sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76 \ + --hash=sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759 \ + --hash=sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053 + # via src +iterator-chain==1.1.0 \ + --hash=sha256:3a8c960c3b4e193138e7da24136408e52d3cdd7b2c202392feb8cd6062287ecc \ + --hash=sha256:c1ca0fe2ab568c162098560ccd6a43893eec3048a1b61230ad56dfd6ac03e024 + # via src +jmespath==1.0.1 \ + --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ + --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe + # via + # boto3 + # botocore +pycparser==2.22 ; platform_python_implementation != 'PyPy' \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc + # via cffi +pyjwt==2.10.1 \ + --hash=sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953 \ + --hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb + # via src +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via botocore +s3transfer==0.12.0 \ + --hash=sha256:35b314d7d82865756edab59f7baebc6b477189e6ab4c53050e28c1de4d9cce18 \ + --hash=sha256:8ac58bc1989a3fdb7c7f3ee0918a66b160d038a147c7b5db1500930a607e9a1c + # via boto3 +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via python-dateutil +types-awscrt==0.25.7 \ + --hash=sha256:7bcd649aedca3c41007ca5757096d3b3bdb454b73ca66970ddae6c2c2f541c8c \ + --hash=sha256:e11298750c99647f7f3b98d6d6d648790096cd32d445fd0d49a6041a63336c9a + # via botocore-stubs +types-boto3==1.37.29 \ + --hash=sha256:7f18ae28c6060b81c094862752209a833fd1ce66f3841e09eec8f12a1fa7d4a0 \ + --hash=sha256:8bfb4600416cc889d6387d73b3d000771c65368d37d65b310cb93775f61d9ab0 + # via src +types-boto3-apigateway==1.37.23 \ + --hash=sha256:09b2da67a124c5395854fa7cc6a894bc2a417f3c4d676ccb0f9c52b0a5f3dcc4 \ + --hash=sha256:5d8d5e74b3c05e4c373e607d6cbd36547c827006cc00a0f3cafacc7ebc7abf61 + # via types-boto3 +types-boto3-dynamodb==1.37.12 \ + --hash=sha256:009178e5eacd2432989ce724a44ae89f03d6cac9b4931783679073d157598244 \ + --hash=sha256:f772ad6cf59f87b7ae71274e4672c4d06b7f8ec74b8d1afbb797c448e0bf4e2e + # via types-boto3 +types-boto3-s3==1.37.24 \ + --hash=sha256:53eed086717bf853dda128e7ad7aae2470fe2b89fb1d3811fbca6fda3f871354 \ + --hash=sha256:a0ed402192379de22cd5cec97e35500bfddde1168ce3ef18dec06a6b4d3cddc2 + # via types-boto3 +types-boto3-secretsmanager==1.37.0 \ + --hash=sha256:02173b52bf0e28d9a76b20778d819b65e2a544695a285900135a4f05c7b7aa8b \ + --hash=sha256:f2f8835e3e8bdb581a93ec1d947aeee1341f5748d0872e9217859fd210f5707e + # via types-boto3 +types-boto3-sqs==1.37.0 \ + --hash=sha256:37bd73d2a0810f9b685a3230aac87543d88e55e92d0b26a142c7d220c375bfd3 \ + --hash=sha256:3de05af6afeb77cefbe816556cec12c46a436f31a63cf2647cb7a09892a0d39b + # via types-boto3 +types-boto3-textract==1.37.0 \ + --hash=sha256:8347e429a671e71a0cb7cc6c7324b1c5d53532fe8783550a7a9561c9eedc0778 \ + --hash=sha256:ccae84df509dda8cec1433d710c14ee34ef09209f6a90e5bfc682d09b4b156d7 + # via types-boto3 +types-s3transfer==0.11.4 \ + --hash=sha256:05fde593c84270f19fd053f0b1e08f5a057d7c5f036b9884e68fb8cd3041ac30 \ + --hash=sha256:2a76d92c07d4a3cb469e5343b2e7560e0b8078b2e03696a65407b8c44c861b61 + # via types-boto3 +urllib3==2.3.0 \ + --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ + --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d + # via botocore +Cleaning dist folder +Exporting uv dependencies to dist/requirements.txt +Generating dependency distribution in dist/build +Copying our code to dist/build/src +Creating distribution zip at dist/lambda.zip + +Backend build completed successfully! +=== Building Frontend === +Running: npm run build + +> package.json@1.0.0 build +> parcel build src/index.html && cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs dist/ + +✨ Built in 415ms + +dist/index.html 520 B 346ms +dist/ui.b82c2ba3.js 713.43 kB 675ms +dist/icon-dot-gov.1cc2b2c5.svg 499 B 276ms +dist/us_flag_small.e642a40d.png 244 B 68ms +dist/icon-https.b6eca6f6.svg 601 B 311ms +dist/GSA-logo.7a2abfc3.svg 3.19 kB 295ms +dist/ui.7c42bb32.css 509.74 kB 350ms +dist/roboto-mono-v5-latin-300.98978b08.woff2 16.38 kB 47ms +dist/roboto-mono-v5-latin-regular.ec7832e5.woff2 16.03 kB 300ms +dist/roboto-mono-v5-latin-700.df366d23.woff2 15.96 kB 174ms +dist/roboto-mono-v5-latin-300italic.af4ea91a.woff2 17.34 kB 266ms +dist/roboto-mono-v5-latin-italic.9209c633.woff2 17.31 kB 46ms +dist/roboto-mono-v5-latin-700italic.2b983f5d.woff2 17.34 kB 300ms +dist/sourcesanspro-light-webfont.e490d910.woff2 20.41 kB 174ms +dist/sourcesanspro-regular-webfont.a9d6b459.woff2 20.54 kB 266ms +dist/sourcesanspro-bold-webfont.5436602a.woff2 20.37 kB 47ms +dist/sourcesanspro-lightitalic-webfont.c14df9bc.woff2 16.32 kB 300ms +dist/sourcesanspro-italic-webfont.82ff76d1.woff2 16.37 kB 175ms +dist/sourcesanspro-bolditalic-webfont.b61b42d2.woff2 16.42 kB 266ms +dist/Latin-Merriweather-Light.fbc4393c.woff2 21.26 kB 47ms +dist/Latin-Merriweather-Regular.66d4c170.woff2 21.69 kB 300ms +dist/Latin-Merriweather-Bold.967b2108.woff2 21.31 kB 175ms +dist/Latin-Merriweather-LightItalic.f4af3b7f.woff2 18.98 kB 266ms +dist/Latin-Merriweather-Italic.e2716ed3.woff2 19.25 kB 47ms +dist/Latin-Merriweather-BoldItalic.32dc0025.woff2 19.57 kB 300ms +dist/launch.f4de218c.svg 202 B 295ms +dist/launch--white.626c08f5.svg 251 B 276ms +dist/remove.27bfc20f.svg 96 B 247ms +dist/add.d491396d.svg 114 B 311ms +dist/check_circle.a3900be5.svg 191 B 295ms +dist/warning.ce0fedc9.svg 122 B 276ms +dist/error.1986e136.svg 172 B 247ms +dist/info.40b16694.svg 172 B 311ms +dist/error--white.73d13870.svg 221 B 295ms +dist/expand_more.0d06b804.svg 125 B 276ms +dist/close.bf51193b.svg 182 B 247ms +dist/expand_less.e2846957.svg 126 B 311ms +dist/arrow_back.e4913c69.svg 138 B 295ms +dist/navigate_next.fb4be146.svg 127 B 276ms +dist/check--blue-60v.7604a0f6.svg 182 B 247ms +dist/hero.3538e009.jpg 143.81 kB 311ms +dist/search.e5ee4fc2.svg 272 B 295ms +dist/checkbox-indeterminate.08aa88cd.svg 313 B 276ms +dist/checkbox-indeterminate-alt.5e7abfcf.svg 301 B 247ms +dist/correct8.2dc455b8.svg 468 B 311ms +dist/correct8-alt.c14d1430.svg 471 B 295ms +dist/unfold_more.1f608602.svg 183 B 275ms +dist/calendar_today.ce6eaa81.svg 187 B 246ms +dist/navigate_far_before.904e4c63.svg 163 B 310ms +dist/navigate_before.7d672c1a.svg 126 B 294ms +dist/navigate_far_next.37ce8b6e.svg 170 B 275ms +dist/loader.e58cf242.svg 1.12 kB 247ms +dist/file-pdf.248e68e5.svg 896 B 310ms +dist/file.f51e21d7.svg 280 B 294ms +dist/file-word.75e83f68.svg 756 B 275ms +dist/file-excel.c90d2036.svg 654 B 247ms +dist/file-video.a3a38638.svg 562 B 310ms + +Frontend build completed successfully! +=== Initializing Terraform === +=== Getting SSO Credentials === +Running: terraform init + +Initializing the backend... +Initializing modules... + +Initializing provider plugins... +- Reusing previous version of hashicorp/aws from the dependency lock file +- Reusing previous version of hashicorp/null from the dependency lock file +- Using previously-installed hashicorp/aws v5.98.0 +- Using previously-installed hashicorp/null v3.2.4 + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. + +=== Applying Terraform Changes === +=== Getting SSO Credentials === +Running: terraform apply -auto-approve + +data.aws_iam_policy.lambda_textract_execution: Reading... +data.aws_iam_policy.lambda_basic_execution: Reading... +aws_cloudfront_origin_access_control.oac: Refreshing state... [id=E5E4NY4IQRCTR] +data.aws_iam_policy_document.assume_role: Reading... +aws_api_gateway_rest_api.api: Refreshing state... [id=ic9avcgf3l] +aws_secretsmanager_secret.public_key: Refreshing state... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-public-key-PI2EsC] +aws_secretsmanager_secret.private_key: Refreshing state... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-private-key-PI2EsC] +aws_dynamodb_table.extract_table: Refreshing state... [id=document-extractor-dev-text-extract] +aws_secretsmanager_secret.password: Refreshing state... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-password-5KgJiD] +aws_secretsmanager_secret.username: Refreshing state... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-username-gKcWPC] +data.aws_iam_policy_document.assume_role: Read complete after 0s [id=2690255455] +aws_cloudfront_function.rewrite_uri: Refreshing state... [id=document-extractor-dev-rewrite-request] +aws_kms_key.encryption: Refreshing state... [id=2c84ed0b-7d29-4d92-ab32-f0db5557a13c] +data.aws_caller_identity.current: Reading... +data.aws_iam_policy_document.secrets_lambda_policy: Reading... +data.aws_iam_policy_document.secrets_lambda_policy: Read complete after 0s [id=2856285183] +aws_iam_role.execution_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role] +aws_iam_policy.secrets_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy] +module.document_id_endpoints.data.aws_api_gateway_rest_api.api: Reading... +module.document_endpoints.data.aws_api_gateway_rest_api.api: Reading... +data.aws_caller_identity.current: Read complete after 0s [id=328307993388] +module.token_endpoints.data.aws_api_gateway_rest_api.api: Reading... +aws_s3_bucket.website_storage: Refreshing state... [id=document-extractor-dev-website-328307993388] +aws_s3_bucket.document_storage: Refreshing state... [id=document-extractor-dev-documents-328307993388] +data.aws_iam_policy_document.dynamodb_lambda_policy: Reading... +data.aws_iam_policy_document.dynamodb_lambda_policy: Read complete after 0s [id=2781330629] +aws_iam_role_policy_attachment.attach_secrets_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153044573200000005] +aws_iam_policy.dynamodb_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy] +module.document_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 1s [id=ic9avcgf3l] +module.document_endpoints.aws_api_gateway_resource.resource_path: Refreshing state... [id=ljz2z9] +module.document_id_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 1s [id=ic9avcgf3l] +data.aws_iam_policy_document.kms_lambda_policy: Reading... +aws_lambda_function.authorizer: Refreshing state... [id=document-extractor-dev-authorizer] +data.aws_iam_policy_document.kms_lambda_policy: Read complete after 0s [id=2825613304] +aws_sqs_queue.queue_to_dynamo: Refreshing state... [id=https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb] +module.token_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 1s [id=ic9avcgf3l] +module.token_endpoints.aws_lambda_function.function[0]: Refreshing state... [id=document-extractor-dev-token] +aws_iam_policy.kms_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy] +module.token_endpoints.aws_api_gateway_resource.resource_path: Refreshing state... [id=dmuib4] +aws_iam_role_policy_attachment.attach_dynamodb_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153115634300000009] +aws_iam_role_policy_attachment.attach_kms_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-2025052215311566550000000a] +module.document_id_endpoints.aws_api_gateway_resource.resource_path: Refreshing state... [id=g3ywx2] +module.token_endpoints.aws_api_gateway_method.http_method[0]: Refreshing state... [id=agm-ic9avcgf3l-dmuib4-POST] +data.aws_iam_policy.lambda_basic_execution: Read complete after 1s [id=arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole] +aws_iam_role_policy_attachment.attach_basic_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153044909700000007] +data.aws_iam_policy.lambda_textract_execution: Read complete after 1s [id=arn:aws:iam::aws:policy/AmazonTextractFullAccess] +aws_iam_role_policy_attachment.attach_textract_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153044578100000006] +data.aws_iam_policy_document.sqs_lambda_policy: Reading... +data.aws_iam_policy_document.sqs_lambda_policy: Read complete after 0s [id=1807487604] +aws_lambda_function.text_extract: Refreshing state... [id=document-extractor-dev-text-extract] +aws_iam_policy.sqs_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy] +aws_lambda_function.write_to_dynamodb: Refreshing state... [id=document-extractor-dev-write-to-dynamodb] +aws_iam_role_policy_attachment.attach_sqs_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-2025052215314877690000000b] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Refreshing state... [id=document-extractor-dev-authorizer,5] +aws_lambda_permission.api_gateway_invoke_authorizer: Refreshing state... [id=AllowExecutionFromApiGateway] +aws_api_gateway_authorizer.authorizer: Refreshing state... [id=bq0hho] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Refreshing state... [id=document-extractor-dev-token,5] +module.token_endpoints.aws_lambda_permission.api_gateway[0]: Refreshing state... [id=AllowExecutionFromApiGateway] +module.token_endpoints.aws_api_gateway_integration.lambda_integration[0]: Refreshing state... [id=agi-ic9avcgf3l-dmuib4-POST] +module.document_endpoints.aws_api_gateway_method.http_method[0]: Refreshing state... [id=agm-ic9avcgf3l-ljz2z9-POST] +module.document_id_endpoints.aws_api_gateway_method.http_method[0]: Refreshing state... [id=agm-ic9avcgf3l-g3ywx2-GET] +module.document_id_endpoints.aws_api_gateway_method.http_method[1]: Refreshing state... [id=agm-ic9avcgf3l-g3ywx2-PUT] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Refreshing state... [id=document-extractor-dev-write-to-dynamodb,5] +aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: Refreshing state... [id=b32111fd-5cd6-45fd-b193-ac415949bc48] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Refreshing state... [id=document-extractor-dev-text-extract,5] +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Refreshing state... [id=document-extractor-dev-documents-328307993388] +data.aws_iam_policy_document.s3_lambda_policy: Reading... +aws_lambda_permission.allow_bucket_invoke: Refreshing state... [id=AllowExecutionFromS3Bucket] +data.aws_iam_policy_document.s3_lambda_policy: Read complete after 0s [id=1929687036] +module.document_id_endpoints.aws_lambda_function.function[0]: Refreshing state... [id=document-extractor-dev-get-document] +module.document_id_endpoints.aws_lambda_function.function[1]: Refreshing state... [id=document-extractor-dev-update-document] +aws_iam_policy.s3_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy] +module.document_endpoints.aws_lambda_function.function[0]: Refreshing state... [id=document-extractor-dev-create-document] +aws_s3_bucket_notification.notify_on_input_data: Refreshing state... [id=document-extractor-dev-documents-328307993388] +aws_iam_role_policy_attachment.attach_s3_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153114543600000008] +aws_s3_bucket_versioning.website_storage_versioning: Refreshing state... [id=document-extractor-dev-website-328307993388] +aws_s3_bucket_public_access_block.private_website: Refreshing state... [id=document-extractor-dev-website-328307993388] +aws_s3_bucket_website_configuration.website_configuration: Refreshing state... [id=document-extractor-dev-website-328307993388] +aws_s3_object.website_files["Latin-Merriweather-Italic.e2716ed3.woff2"]: Refreshing state... [id=Latin-Merriweather-Italic.e2716ed3.woff2] +aws_s3_object.website_files["ui.d119b665.js.map"]: Refreshing state... [id=ui.d119b665.js.map] +aws_s3_object.website_files["sourcesanspro-regular-webfont.a9d6b459.woff2"]: Refreshing state... [id=sourcesanspro-regular-webfont.a9d6b459.woff2] +aws_s3_object.website_files["ui.4540ed92.js.map"]: Refreshing state... [id=ui.4540ed92.js.map] +aws_s3_object.website_files["file-video.d309c165.svg"]: Refreshing state... [id=file-video.d309c165.svg] +aws_s3_object.website_files["icon-dot-gov.86228b6b.svg"]: Refreshing state... [id=icon-dot-gov.86228b6b.svg] +aws_s3_object.website_files["file-pdf.248e68e5.svg"]: Refreshing state... [id=file-pdf.248e68e5.svg] +aws_s3_object.website_files["Latin-Merriweather-Bold.967b2108.woff2"]: Refreshing state... [id=Latin-Merriweather-Bold.967b2108.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-italic.9209c633.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-italic.9209c633.woff2] +aws_s3_object.website_files["Latin-Merriweather-Italic.959c3872.woff2"]: Refreshing state... [id=Latin-Merriweather-Italic.959c3872.woff2] +aws_s3_object.website_files["navigate_before.be211c2e.svg"]: Refreshing state... [id=navigate_before.be211c2e.svg] +aws_s3_object.website_files["file.f51e21d7.svg"]: Refreshing state... [id=file.f51e21d7.svg] +aws_s3_object.website_files["Latin-Merriweather-LightItalic.f4af3b7f.woff2"]: Refreshing state... [id=Latin-Merriweather-LightItalic.f4af3b7f.woff2] +aws_s3_object.website_files["check--blue-60v.982d9f95.svg"]: Refreshing state... [id=check--blue-60v.982d9f95.svg] +aws_s3_object.website_files["ui.7c42bb32.css"]: Refreshing state... [id=ui.7c42bb32.css] +aws_s3_object.website_files["ui.4540ed92.js"]: Refreshing state... [id=ui.4540ed92.js] +aws_s3_object.website_files["unfold_more.1f608602.svg"]: Refreshing state... [id=unfold_more.1f608602.svg] +aws_s3_object.website_files["add.29db0c6a.svg"]: Refreshing state... [id=add.29db0c6a.svg] +aws_s3_object.website_files["launch.2cde378c.svg"]: Refreshing state... [id=launch.2cde378c.svg] +aws_s3_object.website_files["ui.4e22b301.js"]: Refreshing state... [id=ui.4e22b301.js] +aws_s3_object.website_files["remove.27bfc20f.svg"]: Refreshing state... [id=remove.27bfc20f.svg] +aws_s3_object.website_files["checkbox-indeterminate.db2c5d96.svg"]: Refreshing state... [id=checkbox-indeterminate.db2c5d96.svg] +aws_s3_object.website_files["us_flag_small.9c3c6ab8.png"]: Refreshing state... [id=us_flag_small.9c3c6ab8.png] +aws_s3_object.website_files["sourcesanspro-light-webfont.e70d4904.woff2"]: Refreshing state... [id=sourcesanspro-light-webfont.e70d4904.woff2] +aws_s3_object.website_files["Latin-Merriweather-Light.fbc4393c.woff2"]: Refreshing state... [id=Latin-Merriweather-Light.fbc4393c.woff2] +aws_s3_object.website_files["navigate_before.7d672c1a.svg"]: Refreshing state... [id=navigate_before.7d672c1a.svg] +aws_s3_object.website_files["correct8-alt.8f8dac23.svg"]: Refreshing state... [id=correct8-alt.8f8dac23.svg] +aws_s3_object.website_files["error.35f61f4b.svg"]: Refreshing state... [id=error.35f61f4b.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-700.d309a69d.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-700.d309a69d.woff2] +aws_s3_object.website_files["index.html"]: Refreshing state... [id=index.html] +aws_s3_object.website_files["pdf.worker.min.mjs"]: Refreshing state... [id=pdf.worker.min.mjs] +aws_s3_object.website_files["correct8-alt.c14d1430.svg"]: Refreshing state... [id=correct8-alt.c14d1430.svg] +aws_s3_object.website_files["ui.b82c2ba3.js"]: Refreshing state... [id=ui.b82c2ba3.js] +aws_s3_object.website_files["roboto-mono-v5-latin-italic.7226ad2c.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-italic.7226ad2c.woff2] +aws_s3_object.website_files["Latin-Merriweather-Light.1ad1a1d8.woff2"]: Refreshing state... [id=Latin-Merriweather-Light.1ad1a1d8.woff2] +aws_s3_object.website_files["checkbox-indeterminate-alt.5e7abfcf.svg"]: Refreshing state... [id=checkbox-indeterminate-alt.5e7abfcf.svg] +aws_s3_object.website_files["ui.394b89ad.css"]: Refreshing state... [id=ui.394b89ad.css] +aws_s3_object.website_files["calendar_today.3a803a7a.svg"]: Refreshing state... [id=calendar_today.3a803a7a.svg] +aws_s3_object.website_files["ui.383343a4.js.map"]: Refreshing state... [id=ui.383343a4.js.map] +aws_s3_object.website_files["search.e5ee4fc2.svg"]: Refreshing state... [id=search.e5ee4fc2.svg] +aws_s3_object.website_files["launch--white.626c08f5.svg"]: Refreshing state... [id=launch--white.626c08f5.svg] +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.84e64ba8.woff2"]: Refreshing state... [id=sourcesanspro-bolditalic-webfont.84e64ba8.woff2] +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.b61b42d2.woff2"]: Refreshing state... [id=sourcesanspro-bolditalic-webfont.b61b42d2.woff2] +aws_s3_object.website_files["ui.d119b665.js"]: Refreshing state... [id=ui.d119b665.js] +aws_s3_object.website_files["warning.ce0fedc9.svg"]: Refreshing state... [id=warning.ce0fedc9.svg] +aws_s3_object.website_files["checkbox-indeterminate.08aa88cd.svg"]: Refreshing state... [id=checkbox-indeterminate.08aa88cd.svg] +aws_s3_object.website_files["navigate_next.bc7c1a4e.svg"]: Refreshing state... [id=navigate_next.bc7c1a4e.svg] +aws_s3_object.website_files["error.1986e136.svg"]: Refreshing state... [id=error.1986e136.svg] +aws_s3_object.website_files["file-excel.c90d2036.svg"]: Refreshing state... [id=file-excel.c90d2036.svg] +aws_s3_object.website_files["expand_less.e2846957.svg"]: Refreshing state... [id=expand_less.e2846957.svg] +aws_s3_object.website_files["Latin-Merriweather-Regular.04046ce8.woff2"]: Refreshing state... [id=Latin-Merriweather-Regular.04046ce8.woff2] +aws_s3_object.website_files["file-word.896caaf0.svg"]: Refreshing state... [id=file-word.896caaf0.svg] +aws_s3_object.website_files["launch--white.6c179f60.svg"]: Refreshing state... [id=launch--white.6c179f60.svg] +aws_s3_object.website_files["search.a724f1bc.svg"]: Refreshing state... [id=search.a724f1bc.svg] +aws_s3_object.website_files["file-pdf.51e66388.svg"]: Refreshing state... [id=file-pdf.51e66388.svg] +aws_s3_object.website_files["sourcesanspro-bold-webfont.5436602a.woff2"]: Refreshing state... [id=sourcesanspro-bold-webfont.5436602a.woff2] +aws_s3_object.website_files["file-video.a3a38638.svg"]: Refreshing state... [id=file-video.a3a38638.svg] +aws_s3_object.website_files["GSA-logo.ece32ed3.svg"]: Refreshing state... [id=GSA-logo.ece32ed3.svg] +aws_s3_object.website_files["check_circle.120511e4.svg"]: Refreshing state... [id=check_circle.120511e4.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.1710c37a.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-300italic.1710c37a.woff2] +aws_s3_object.website_files["ui.ee3dfc51.js.map"]: Refreshing state... [id=ui.ee3dfc51.js.map] +aws_s3_object.website_files["loader.7402c183.svg"]: Refreshing state... [id=loader.7402c183.svg] +aws_s3_object.website_files["warning.4cc8d763.svg"]: Refreshing state... [id=warning.4cc8d763.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-700.df366d23.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-700.df366d23.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.2b983f5d.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-700italic.2b983f5d.woff2] +aws_s3_object.website_files["ui.24de9d80.js"]: Refreshing state... [id=ui.24de9d80.js] +aws_s3_object.website_files["hero.abe074f7.jpg"]: Refreshing state... [id=hero.abe074f7.jpg] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.e58256dc.woff2"]: Refreshing state... [id=Latin-Merriweather-BoldItalic.e58256dc.woff2] +aws_s3_object.website_files["expand_more.0d06b804.svg"]: Refreshing state... [id=expand_more.0d06b804.svg] +aws_s3_object.website_files["ui.383343a4.js"]: Refreshing state... [id=ui.383343a4.js] +aws_s3_object.website_files["icon-https.b6eca6f6.svg"]: Refreshing state... [id=icon-https.b6eca6f6.svg] +aws_s3_object.website_files["sourcesanspro-regular-webfont.76c0bde6.woff2"]: Refreshing state... [id=sourcesanspro-regular-webfont.76c0bde6.woff2] +aws_s3_object.website_files["error--white.73d13870.svg"]: Refreshing state... [id=error--white.73d13870.svg] +aws_s3_object.website_files["us_flag_small.e642a40d.png"]: Refreshing state... [id=us_flag_small.e642a40d.png] +aws_s3_object.website_files["ui.9f367d21.css.map"]: Refreshing state... [id=ui.9f367d21.css.map] +aws_s3_object.website_files["arrow_back.c9ac1a0e.svg"]: Refreshing state... [id=arrow_back.c9ac1a0e.svg] +aws_s3_object.website_files["info.40b16694.svg"]: Refreshing state... [id=info.40b16694.svg] +aws_s3_object.website_files["sourcesanspro-italic-webfont.ab90d6d1.woff2"]: Refreshing state... [id=sourcesanspro-italic-webfont.ab90d6d1.woff2] +aws_s3_object.website_files["expand_less.5bb6de10.svg"]: Refreshing state... [id=expand_less.5bb6de10.svg] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.32dc0025.woff2"]: Refreshing state... [id=Latin-Merriweather-BoldItalic.32dc0025.woff2] +aws_s3_object.website_files["navigate_next.fb4be146.svg"]: Refreshing state... [id=navigate_next.fb4be146.svg] +aws_s3_object.website_files["check--blue-60v.7604a0f6.svg"]: Refreshing state... [id=check--blue-60v.7604a0f6.svg] +aws_s3_object.website_files["Latin-Merriweather-Regular.66d4c170.woff2"]: Refreshing state... [id=Latin-Merriweather-Regular.66d4c170.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-regular.ec7832e5.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-regular.ec7832e5.woff2] +aws_s3_object.website_files["sourcesanspro-bold-webfont.21f8979c.woff2"]: Refreshing state... [id=sourcesanspro-bold-webfont.21f8979c.woff2] +aws_s3_object.website_files["navigate_far_before.904e4c63.svg"]: Refreshing state... [id=navigate_far_before.904e4c63.svg] +aws_s3_object.website_files["hero.3538e009.jpg"]: Refreshing state... [id=hero.3538e009.jpg] +aws_s3_object.website_files["launch.f4de218c.svg"]: Refreshing state... [id=launch.f4de218c.svg] +aws_s3_object.website_files["remove.ca324d27.svg"]: Refreshing state... [id=remove.ca324d27.svg] +aws_s3_object.website_files["sourcesanspro-italic-webfont.82ff76d1.woff2"]: Refreshing state... [id=sourcesanspro-italic-webfont.82ff76d1.woff2] +aws_s3_object.website_files["ui.31b563d9.js"]: Refreshing state... [id=ui.31b563d9.js] +aws_s3_object.website_files["roboto-mono-v5-latin-300.98978b08.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-300.98978b08.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.03671595.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-700italic.03671595.woff2] +aws_s3_object.website_files["Latin-Merriweather-Bold.cdbf7e95.woff2"]: Refreshing state... [id=Latin-Merriweather-Bold.cdbf7e95.woff2] +aws_s3_object.website_files["error--white.dcc8cafd.svg"]: Refreshing state... [id=error--white.dcc8cafd.svg] +aws_s3_object.website_files["correct8.2dc455b8.svg"]: Refreshing state... [id=correct8.2dc455b8.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.af4ea91a.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-300italic.af4ea91a.woff2] +aws_s3_object.website_files["calendar_today.ce6eaa81.svg"]: Refreshing state... [id=calendar_today.ce6eaa81.svg] +aws_s3_object.website_files["add.d491396d.svg"]: Refreshing state... [id=add.d491396d.svg] +aws_s3_object.website_files["check_circle.a3900be5.svg"]: Refreshing state... [id=check_circle.a3900be5.svg] +aws_s3_object.website_files["info.85fe97af.svg"]: Refreshing state... [id=info.85fe97af.svg] +aws_s3_object.website_files["unfold_more.46c00529.svg"]: Refreshing state... [id=unfold_more.46c00529.svg] +aws_s3_object.website_files["close.bf51193b.svg"]: Refreshing state... [id=close.bf51193b.svg] +aws_s3_object.website_files["navigate_far_next.37ce8b6e.svg"]: Refreshing state... [id=navigate_far_next.37ce8b6e.svg] +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.c14df9bc.woff2"]: Refreshing state... [id=sourcesanspro-lightitalic-webfont.c14df9bc.woff2] +aws_s3_object.website_files["icon-dot-gov.1cc2b2c5.svg"]: Refreshing state... [id=icon-dot-gov.1cc2b2c5.svg] +aws_s3_object.website_files["Latin-Merriweather-LightItalic.1c9df738.woff2"]: Refreshing state... [id=Latin-Merriweather-LightItalic.1c9df738.woff2] +aws_s3_object.website_files["ui.394b89ad.css.map"]: Refreshing state... [id=ui.394b89ad.css.map] +aws_s3_object.website_files["navigate_far_next.bd2c92a4.svg"]: Refreshing state... [id=navigate_far_next.bd2c92a4.svg] +aws_s3_object.website_files["navigate_far_before.65b60266.svg"]: Refreshing state... [id=navigate_far_before.65b60266.svg] +aws_s3_object.website_files["ui.b82c2ba3.js.map"]: Refreshing state... [id=ui.b82c2ba3.js.map] +aws_s3_object.website_files["GSA-logo.7a2abfc3.svg"]: Refreshing state... [id=GSA-logo.7a2abfc3.svg] +aws_s3_object.website_files["close.3962dfa0.svg"]: Refreshing state... [id=close.3962dfa0.svg] +aws_s3_object.website_files["checkbox-indeterminate-alt.a6b9b5ac.svg"]: Refreshing state... [id=checkbox-indeterminate-alt.a6b9b5ac.svg] +aws_s3_object.website_files["ui.31b563d9.js.map"]: Refreshing state... [id=ui.31b563d9.js.map] +aws_s3_object.website_files["ui.4e22b301.js.map"]: Refreshing state... [id=ui.4e22b301.js.map] +aws_s3_object.website_files["file-word.75e83f68.svg"]: Refreshing state... [id=file-word.75e83f68.svg] +aws_s3_object.website_files["expand_more.3ac36c78.svg"]: Refreshing state... [id=expand_more.3ac36c78.svg] +aws_s3_object.website_files["correct8.10e8a7ef.svg"]: Refreshing state... [id=correct8.10e8a7ef.svg] +aws_s3_object.website_files["icon-https.3b0ffef2.svg"]: Refreshing state... [id=icon-https.3b0ffef2.svg] +aws_s3_object.website_files["ui.24de9d80.js.map"]: Refreshing state... [id=ui.24de9d80.js.map] +aws_s3_object.website_files["ui.7c42bb32.css.map"]: Refreshing state... [id=ui.7c42bb32.css.map] +aws_s3_object.website_files["file.057d5652.svg"]: Refreshing state... [id=file.057d5652.svg] +aws_s3_object.website_files["loader.e58cf242.svg"]: Refreshing state... [id=loader.e58cf242.svg] +aws_s3_object.website_files["ui.9f367d21.css"]: Refreshing state... [id=ui.9f367d21.css] +aws_s3_object.website_files["roboto-mono-v5-latin-300.40a89411.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-300.40a89411.woff2] +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.05b42991.woff2"]: Refreshing state... [id=sourcesanspro-lightitalic-webfont.05b42991.woff2] +aws_s3_object.website_files["arrow_back.e4913c69.svg"]: Refreshing state... [id=arrow_back.e4913c69.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-regular.e048516f.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-regular.e048516f.woff2] +aws_s3_object.website_files["sourcesanspro-light-webfont.e490d910.woff2"]: Refreshing state... [id=sourcesanspro-light-webfont.e490d910.woff2] +aws_s3_object.website_files["file-excel.323d5d7b.svg"]: Refreshing state... [id=file-excel.323d5d7b.svg] +aws_s3_object.website_files["ui.ee3dfc51.js"]: Refreshing state... [id=ui.ee3dfc51.js] +module.document_endpoints.aws_lambda_permission.api_gateway[0]: Refreshing state... [id=AllowExecutionFromApiGateway] +module.document_endpoints.aws_api_gateway_integration.lambda_integration[0]: Refreshing state... [id=agi-ic9avcgf3l-ljz2z9-POST] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Refreshing state... [id=document-extractor-dev-create-document,5] +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[0]: Refreshing state... [id=agi-ic9avcgf3l-g3ywx2-GET] +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[1]: Refreshing state... [id=agi-ic9avcgf3l-g3ywx2-PUT] +module.document_id_endpoints.aws_lambda_permission.api_gateway[0]: Refreshing state... [id=AllowExecutionFromApiGateway] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Refreshing state... [id=document-extractor-dev-get-document,5] +module.document_id_endpoints.aws_lambda_permission.api_gateway[1]: Refreshing state... [id=AllowExecutionFromApiGateway] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Refreshing state... [id=document-extractor-dev-update-document,5] +aws_api_gateway_deployment.api_deployment: Refreshing state... [id=ag6ofm] +aws_api_gateway_stage.stage: Refreshing state... [id=ags-ic9avcgf3l-v1] +aws_cloudfront_distribution.distribution: Refreshing state... [id=ENWTZLUASCDJZ] +null_resource.invalidate_cloudfront: Refreshing state... [id=8325867666043885233] +data.aws_iam_policy_document.cf_read: Reading... +data.aws_iam_policy_document.cf_read: Read complete after 0s [id=3877928146] +aws_s3_bucket_policy.website_read: Refreshing state... [id=document-extractor-dev-website-328307993388] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + ~ update in-place +-/+ destroy and then create replacement + +Terraform will perform the following actions: + + # aws_lambda_function.authorizer will be updated in-place + ~ resource "aws_lambda_function" "authorizer" { + id = "document-extractor-dev-authorizer" + ~ last_modified = "2025-05-22T20:48:51.000+0000" -> (known after apply) + ~ qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer:5" -> (known after apply) + ~ qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer:5/invocations" -> (known after apply) + ~ source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" -> "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" + tags = {} + ~ version = "5" -> (known after apply) + # (19 unchanged attributes hidden) + + # (4 unchanged blocks hidden) + } + + # aws_lambda_function.text_extract will be updated in-place + ~ resource "aws_lambda_function" "text_extract" { + id = "document-extractor-dev-text-extract" + ~ last_modified = "2025-05-22T20:48:30.000+0000" -> (known after apply) + ~ qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract:5" -> (known after apply) + ~ qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract:5/invocations" -> (known after apply) + ~ source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" -> "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" + tags = {} + ~ version = "5" -> (known after apply) + # (19 unchanged attributes hidden) + + # (4 unchanged blocks hidden) + } + + # aws_lambda_function.write_to_dynamodb will be updated in-place + ~ resource "aws_lambda_function" "write_to_dynamodb" { + id = "document-extractor-dev-write-to-dynamodb" + ~ last_modified = "2025-05-22T20:48:11.000+0000" -> (known after apply) + ~ qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb:5" -> (known after apply) + ~ qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb:5/invocations" -> (known after apply) + ~ source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" -> "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" + tags = {} + ~ version = "5" -> (known after apply) + # (19 unchanged attributes hidden) + + # (4 unchanged blocks hidden) + } + + # aws_lambda_provisioned_concurrency_config.authorizer_concurrency must be replaced +-/+ resource "aws_lambda_provisioned_concurrency_config" "authorizer_concurrency" { + ~ id = "document-extractor-dev-authorizer,5" -> (known after apply) + ~ qualifier = "5" # forces replacement -> (known after apply) # forces replacement + # (3 unchanged attributes hidden) + } + + # aws_lambda_provisioned_concurrency_config.text_extract_concurrency must be replaced +-/+ resource "aws_lambda_provisioned_concurrency_config" "text_extract_concurrency" { + ~ id = "document-extractor-dev-text-extract,5" -> (known after apply) + ~ qualifier = "5" # forces replacement -> (known after apply) # forces replacement + # (3 unchanged attributes hidden) + } + + # aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency must be replaced +-/+ resource "aws_lambda_provisioned_concurrency_config" "write_to_dynamodb_concurrency" { + ~ id = "document-extractor-dev-write-to-dynamodb,5" -> (known after apply) + ~ qualifier = "5" # forces replacement -> (known after apply) # forces replacement + # (3 unchanged attributes hidden) + } + + # aws_s3_object.website_files["ui.24de9d80.js.map"] will be updated in-place + ~ resource "aws_s3_object" "website_files" { + ~ etag = "d10b75eab24e3b17c82dfe793bb0c197-2" -> "4f0160b90d6309380791c906b742b2fc" + id = "ui.24de9d80.js.map" + tags = { + "project" = "document-extractor" + } + ~ version_id = ".S.34IQj0kAQhvdnmPlQEg1E61S8zYrN" -> (known after apply) + # (11 unchanged attributes hidden) + } + + # aws_s3_object.website_files["ui.383343a4.js.map"] will be updated in-place + ~ resource "aws_s3_object" "website_files" { + ~ etag = "2061e6c7acb903ff425164499447f426-2" -> "be7d2c775c82a599be36181cb7dac13d" + id = "ui.383343a4.js.map" + tags = { + "project" = "document-extractor" + } + ~ version_id = "ckEM6PjYr8zpOhywI.QjYXHQe27ztMmK" -> (known after apply) + # (11 unchanged attributes hidden) + } + + # aws_s3_object.website_files["ui.b82c2ba3.js.map"] will be updated in-place + ~ resource "aws_s3_object" "website_files" { + ~ etag = "bb5a0d39b9f9186f155cde01e3abccb9-2" -> "6143a6c5351b1008623d5e8698d77135" + id = "ui.b82c2ba3.js.map" + tags = { + "project" = "document-extractor" + } + ~ version_id = "qlo6HXIVoHHmoRe7jTc2uPwe2g5FUFrx" -> (known after apply) + # (11 unchanged attributes hidden) + } + + # aws_s3_object.website_files["ui.d119b665.js.map"] will be updated in-place + ~ resource "aws_s3_object" "website_files" { + ~ etag = "517bef80e4a43d19feb4c2dad09b5710-2" -> "4e46c322baeb43a4cb46d2cfff522976" + id = "ui.d119b665.js.map" + tags = { + "project" = "document-extractor" + } + ~ version_id = "uSMwLmNaJa7b1xwghH5KAilsbAJziDu7" -> (known after apply) + # (11 unchanged attributes hidden) + } + + # null_resource.invalidate_cloudfront must be replaced +-/+ resource "null_resource" "invalidate_cloudfront" { + ~ id = "8325867666043885233" -> (known after apply) + ~ triggers = { # forces replacement + ~ "always_run" = "2025-05-22T21:19:22Z" -> (known after apply) + } + } + + # module.document_endpoints.aws_lambda_function.function[0] will be updated in-place + ~ resource "aws_lambda_function" "function" { + id = "document-extractor-dev-create-document" + ~ last_modified = "2025-05-22T20:50:25.000+0000" -> (known after apply) + ~ qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document:5" -> (known after apply) + ~ qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document:5/invocations" -> (known after apply) + ~ source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" -> "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" + tags = {} + ~ version = "5" -> (known after apply) + # (19 unchanged attributes hidden) + + # (4 unchanged blocks hidden) + } + + # module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0] must be replaced +-/+ resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + ~ id = "document-extractor-dev-create-document,5" -> (known after apply) + ~ qualifier = "5" # forces replacement -> (known after apply) # forces replacement + # (3 unchanged attributes hidden) + } + + # module.document_id_endpoints.aws_lambda_function.function[0] will be updated in-place + ~ resource "aws_lambda_function" "function" { + id = "document-extractor-dev-get-document" + ~ last_modified = "2025-05-22T20:49:55.000+0000" -> (known after apply) + ~ qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document:5" -> (known after apply) + ~ qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document:5/invocations" -> (known after apply) + ~ source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" -> "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" + tags = {} + ~ version = "5" -> (known after apply) + # (19 unchanged attributes hidden) + + # (4 unchanged blocks hidden) + } + + # module.document_id_endpoints.aws_lambda_function.function[1] will be updated in-place + ~ resource "aws_lambda_function" "function" { + id = "document-extractor-dev-update-document" + ~ last_modified = "2025-05-22T20:49:31.000+0000" -> (known after apply) + ~ qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document:5" -> (known after apply) + ~ qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document:5/invocations" -> (known after apply) + ~ source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" -> "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" + tags = {} + ~ version = "5" -> (known after apply) + # (19 unchanged attributes hidden) + + # (4 unchanged blocks hidden) + } + + # module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0] must be replaced +-/+ resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + ~ id = "document-extractor-dev-get-document,5" -> (known after apply) + ~ qualifier = "5" # forces replacement -> (known after apply) # forces replacement + # (3 unchanged attributes hidden) + } + + # module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1] must be replaced +-/+ resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + ~ id = "document-extractor-dev-update-document,5" -> (known after apply) + ~ qualifier = "5" # forces replacement -> (known after apply) # forces replacement + # (3 unchanged attributes hidden) + } + + # module.token_endpoints.aws_lambda_function.function[0] will be updated in-place + ~ resource "aws_lambda_function" "function" { + id = "document-extractor-dev-token" + ~ last_modified = "2025-05-22T20:49:10.000+0000" -> (known after apply) + ~ qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token:5" -> (known after apply) + ~ qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token:5/invocations" -> (known after apply) + ~ source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" -> "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" + tags = {} + ~ version = "5" -> (known after apply) + # (19 unchanged attributes hidden) + + # (4 unchanged blocks hidden) + } + + # module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0] must be replaced +-/+ resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + ~ id = "document-extractor-dev-token,5" -> (known after apply) + ~ qualifier = "5" # forces replacement -> (known after apply) # forces replacement + # (3 unchanged attributes hidden) + } + +Plan: 8 to add, 11 to change, 8 to destroy. +null_resource.invalidate_cloudfront: Destroying... [id=8325867666043885233] +null_resource.invalidate_cloudfront: Destruction complete after 0s +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destroying... [id=document-extractor-dev-create-document,5] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Destroying... [id=document-extractor-dev-write-to-dynamodb,5] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destroying... [id=document-extractor-dev-token,5] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Destroying... [id=document-extractor-dev-authorizer,5] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Destroying... [id=document-extractor-dev-update-document,5] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destroying... [id=document-extractor-dev-get-document,5] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Destroying... [id=document-extractor-dev-text-extract,5] +null_resource.invalidate_cloudfront: Creating... +null_resource.invalidate_cloudfront: Provisioning with 'local-exec'... +null_resource.invalidate_cloudfront (local-exec): Executing: ["/bin/sh" "-c" " aws cloudfront create-invalidation \\\n --distribution-id ENWTZLUASCDJZ \\\n --paths '/*' \\\n --region us-west-1\n"] +aws_s3_object.website_files["ui.24de9d80.js.map"]: Modifying... [id=ui.24de9d80.js.map] +aws_s3_object.website_files["ui.b82c2ba3.js.map"]: Modifying... [id=ui.b82c2ba3.js.map] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Destruction complete after 1s +aws_s3_object.website_files["ui.383343a4.js.map"]: Modifying... [id=ui.383343a4.js.map] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Destruction complete after 1s +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destruction complete after 1s +aws_s3_object.website_files["ui.d119b665.js.map"]: Modifying... [id=ui.d119b665.js.map] +aws_lambda_function.authorizer: Modifying... [id=document-extractor-dev-authorizer] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Destruction complete after 1s +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Destruction complete after 1s +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destruction complete after 1s +aws_lambda_function.write_to_dynamodb: Modifying... [id=document-extractor-dev-write-to-dynamodb] +aws_lambda_function.text_extract: Modifying... [id=document-extractor-dev-text-extract] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destruction complete after 1s +module.document_endpoints.aws_lambda_function.function[0]: Modifying... [id=document-extractor-dev-create-document] +module.token_endpoints.aws_lambda_function.function[0]: Modifying... [id=document-extractor-dev-token] +null_resource.invalidate_cloudfront (local-exec): { +null_resource.invalidate_cloudfront (local-exec): "Location": "https://cloudfront.amazonaws.com/2020-05-31/distribution/ENWTZLUASCDJZ/invalidation/I7BATK0W44XHMJPH89AETLM6E2", +null_resource.invalidate_cloudfront (local-exec): "Invalidation": { +null_resource.invalidate_cloudfront (local-exec): "Id": "I7BATK0W44XHMJPH89AETLM6E2", +null_resource.invalidate_cloudfront (local-exec): "Status": "InProgress", +null_resource.invalidate_cloudfront (local-exec): "CreateTime": "2025-05-29T15:25:27.832000+00:00", +null_resource.invalidate_cloudfront (local-exec): "InvalidationBatch": { +null_resource.invalidate_cloudfront (local-exec): "Paths": { +null_resource.invalidate_cloudfront (local-exec): "Quantity": 1, +null_resource.invalidate_cloudfront (local-exec): "Items": [ +null_resource.invalidate_cloudfront (local-exec): "/*" +null_resource.invalidate_cloudfront (local-exec): ] +null_resource.invalidate_cloudfront (local-exec): }, +null_resource.invalidate_cloudfront (local-exec): "CallerReference": "cli-1748532327-152206" +null_resource.invalidate_cloudfront (local-exec): } +null_resource.invalidate_cloudfront (local-exec): } +null_resource.invalidate_cloudfront (local-exec): } +null_resource.invalidate_cloudfront: Creation complete after 1s [id=450350717588313943] +module.document_id_endpoints.aws_lambda_function.function[0]: Modifying... [id=document-extractor-dev-get-document] +aws_s3_object.website_files["ui.b82c2ba3.js.map"]: Modifications complete after 2s [id=ui.b82c2ba3.js.map] +module.document_id_endpoints.aws_lambda_function.function[1]: Modifying... [id=document-extractor-dev-update-document] +aws_s3_object.website_files["ui.383343a4.js.map"]: Modifications complete after 1s [id=ui.383343a4.js.map] +aws_s3_object.website_files["ui.24de9d80.js.map"]: Modifications complete after 2s [id=ui.24de9d80.js.map] +aws_s3_object.website_files["ui.d119b665.js.map"]: Modifications complete after 2s [id=ui.d119b665.js.map] +aws_lambda_function.authorizer: Still modifying... [id=document-extractor-dev-authorizer, 10s elapsed] +aws_lambda_function.write_to_dynamodb: Still modifying... [id=document-extractor-dev-write-to-dynamodb, 10s elapsed] +aws_lambda_function.text_extract: Still modifying... [id=document-extractor-dev-text-extract, 10s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-create-document, 10s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-token, 10s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-get-document, 10s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still modifying... [id=document-extractor-dev-update-document, 10s elapsed] +aws_lambda_function.authorizer: Modifications complete after 11s [id=document-extractor-dev-authorizer] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Creating... +aws_lambda_function.write_to_dynamodb: Still modifying... [id=document-extractor-dev-write-to-dynamodb, 20s elapsed] +aws_lambda_function.text_extract: Still modifying... [id=document-extractor-dev-text-extract, 20s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-create-document, 20s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-token, 20s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-get-document, 20s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still modifying... [id=document-extractor-dev-update-document, 20s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [10s elapsed] +aws_lambda_function.write_to_dynamodb: Modifications complete after 23s [id=document-extractor-dev-write-to-dynamodb] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Creating... +aws_lambda_function.text_extract: Still modifying... [id=document-extractor-dev-text-extract, 30s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-create-document, 30s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-token, 30s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-get-document, 30s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still modifying... [id=document-extractor-dev-update-document, 30s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [20s elapsed] +aws_lambda_function.text_extract: Modifications complete after 33s [id=document-extractor-dev-text-extract] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Creating... +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [10s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-create-document, 40s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-token, 40s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-get-document, 40s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still modifying... [id=document-extractor-dev-update-document, 40s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [30s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Modifications complete after 43s [id=document-extractor-dev-create-document] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creating... +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [10s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [20s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-token, 50s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-get-document, 50s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still modifying... [id=document-extractor-dev-update-document, 50s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [40s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Modifications complete after 53s [id=document-extractor-dev-token] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [10s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [20s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [30s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creating... +module.document_id_endpoints.aws_lambda_function.function[0]: Still modifying... [id=document-extractor-dev-get-document, 1m0s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still modifying... [id=document-extractor-dev-update-document, 1m0s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [50s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Modifications complete after 1m2s [id=document-extractor-dev-get-document] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [20s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [30s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [40s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [10s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still modifying... [id=document-extractor-dev-update-document, 1m10s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [1m0s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Modifications complete after 1m11s [id=document-extractor-dev-update-document] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Creating... +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creating... +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [30s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [40s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [50s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [20s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [1m10s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [10s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [10s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [40s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [50s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [1m0s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [30s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [1m20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [20s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [50s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [1m0s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [1m10s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [40s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Creation complete after 1m30s [id=document-extractor-dev-authorizer,6] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [30s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [30s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m0s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [1m10s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [1m20s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [50s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [40s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [40s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m10s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Creation complete after 1m29s [id=document-extractor-dev-write-to-dynamodb,6] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [1m20s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m0s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [50s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [50s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Creation complete after 1m30s [id=document-extractor-dev-text-extract,6] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m20s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m10s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creation complete after 1m29s [id=document-extractor-dev-create-document,6] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [1m0s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m0s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m10s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [1m10s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creation complete after 1m30s [id=document-extractor-dev-token,6] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [1m20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Creation complete after 1m30s [id=document-extractor-dev-update-document,6] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m30s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creation complete after 1m31s [id=document-extractor-dev-get-document,6] +Releasing state lock. This may take a few moments... + +Apply complete! Resources: 8 added, 11 changed, 8 destroyed. + +Outputs: + +distribution_id = "ENWTZLUASCDJZ" + + +Deployment completed successfully! diff --git a/docs/deploy_guide.md b/docs/deploy_guide.md new file mode 100644 index 0000000..f016909 --- /dev/null +++ b/docs/deploy_guide.md @@ -0,0 +1,127 @@ +# Deploying to a New AWS Account +Based on the files you've shared, here's how to deploy this application to a new AWS account while minimizing costs: + +## Prerequisites Setup +1. Ensure you have the following tools installed locally: + - Python + - uv (Python package manager) + - Terraform + - Node.js + - AWS CLI (which you mentioned you already have) +2. Configure AWS CLI for your new account: + +```shell +aws configure +``` + +You'll need to enter your AWS access key, secret key, default region (use us-east-1 as shown in the workflow), and output format. + +## Deployment Steps +1. Build the Application +First, build both the backend and frontend: + +Backend: +``` +cd ./backend/ +uv sync +uv run build.py +``` + + +This will create backend/dist/lambda.zip. + +Frontend: + +cd ./ui/ +npm ci +npm run build + +This will generate files in ui/dist/. + +2. Set Up Terraform State Storage +Before running Terraform: + +Create an S3 bucket to store the Terraform state: +```shell +aws s3 mb s3://document-extractor-YOUR_ENVIRONMENT-opentofu-state +``` + +Replace YOUR_ENVIRONMENT with your environment name (e.g., dev, prod). + +```sh +aws s3 mb s3://document-extractor-dev-opentofu-state +``` + +3. Initialize and Apply Terraform + +cd ./iac/ +terraform init -backend-config="bucket=document-extractor-YOUR_ENVIRONMENT-opentofu-state" -backend-config="key=terraform_state_YOUR_ENVIRONMENT.tfstate" +terraform apply -var 'environment=YOUR_ENVIRONMENT' + +```sh +cd ./iac/ +terraform init -backend-config="bucket=document-extractor-dev-opentofu-state" -backend-config="key=terraform_state_dev.tfstate" +terraform apply -var 'environment=dev' +``` + + +If you need to provide the Textract form adapters environment variables, you'll need to set up that secret value similar to how it's used in the GitHub workflow. + +4. Set Up Authentication +After deployment: + +Generate RSA key pair +Store the keys and credentials in AWS Secrets Manager: +Private key (PEM format) with "private-key" in the name +Public key (PEM format) with "public-key" in the name +Username with "username" in the name +Hashed password with "password" in the name (generate using the command in the README) +Cost Optimization +To minimize AWS costs: + +Choose appropriate Lambda sizing: +Review the Terraform files to ensure Lambda functions aren't overprovisioned +Adjust memory allocation to the minimum needed +S3 Storage: +Consider lifecycle policies for old data +Use the Standard-IA storage class for infrequently accessed data +CloudFront Optimization: +Review caching policies to maximize cache hits +Consider restricting geographic distribution if you don't need global access +Textract Usage: +Be mindful of Textract API calls, as they can accumulate costs +Batch documents when possible to reduce API calls +CloudWatch Logs: +Set appropriate log retention periods +Consider disabling detailed logging in production +Scheduled Cleanup: +Consider setting up cleanup processes for temporary files/data + + + +=== +Textract Form Adapters Environment Variables +Yes, the codebase does require the Textract form adapters environment variables. In the variables.tf file, there's a variable defined as textract_form_adapters_env_var_mapping which is used in the lambda.tf file to set environment variables for the text extraction Lambda function. This is marked as sensitive and nullable, so while it's required for full functionality, you might be able to run with an empty map if you're just testing. + +Lambda Function Sizes +Currently, all Lambda functions in this application are configured with: + +Memory: 256 MB +Timeout: 30 seconds +Architecture: arm64 (AWS Graviton) +Provisioned concurrency: 1 for each Lambda function +The smallest Lambda size AWS offers is 128 MB. For this application: + +Text extraction (256 MB): This is reasonable given it's processing documents with OCR +Write to DynamoDB (256 MB): This could potentially run on 128 MB if you're cost-optimizing +Authorizer (256 MB): Could likely run on 128 MB as authentication flows are typically lightweight +I would recommend keeping the text extraction Lambda at 256 MB since it's doing the heavy lifting with Textract, but you could consider reducing the other two to 128 MB if cost is a priority. + +Terraform Init Error +The error you're seeing is because you included s3:// in the bucket name. Terraform's S3 backend configuration expects just the bucket name without the protocol prefix: +```sh +terraform init -backend-config="bucket=document-extractor-dev-opentofu-state" -backend-config="key=terraform_state_dev.tfstate" +``` + +State File Path Prompt +When Terraform asks for "The path to the state file inside the bucket", you should enter: diff --git a/docs/open-tofu-apply-plan-output-6-2.txt b/docs/open-tofu-apply-plan-output-6-2.txt new file mode 100644 index 0000000..fd0db10 --- /dev/null +++ b/docs/open-tofu-apply-plan-output-6-2.txt @@ -0,0 +1,622 @@ +aws_cloudfront_origin_access_control.oac: Creating... +aws_iam_policy.secrets_lambda_policy: Creating... +aws_iam_role.execution_role: Creating... +aws_s3_bucket.website_storage: Creating... +aws_s3_bucket.document_storage: Creating... +aws_secretsmanager_secret.private_key: Creating... +aws_api_gateway_rest_api.api: Creating... +aws_kms_key.encryption: Creating... +aws_dynamodb_table.extract_table: Creating... +aws_secretsmanager_secret.password: Creating... +aws_cloudfront_origin_access_control.oac: Creation complete after 0s [id=E9404PCWZEE3E] +aws_secretsmanager_secret.public_key: Creating... +aws_iam_policy.secrets_lambda_policy: Creation complete after 0s [id=arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy] +aws_cloudfront_function.rewrite_uri: Creating... +aws_iam_role.execution_role: Creation complete after 0s [id=document-extractor-dev-lambda-execution-role] +aws_secretsmanager_secret.username: Creating... +aws_api_gateway_rest_api.api: Creation complete after 1s [id=vlahimf53i] +aws_iam_role_policy_attachment.attach_textract_permission_to_role: Creating... +aws_iam_role_policy_attachment.attach_textract_permission_to_role: Creation complete after 0s [id=document-extractor-dev-lambda-execution-role-20250602162941623800000005] +aws_iam_role_policy_attachment.attach_secrets_permission_to_role: Creating... +aws_iam_role_policy_attachment.attach_secrets_permission_to_role: Creation complete after 0s [id=document-extractor-dev-lambda-execution-role-20250602162941779300000006] +aws_iam_role_policy_attachment.attach_basic_permission_to_role: Creating... +aws_iam_role_policy_attachment.attach_basic_permission_to_role: Creation complete after 0s [id=document-extractor-dev-lambda-execution-role-20250602162941930700000007] +module.token_endpoints.data.aws_api_gateway_rest_api.api: Reading... +module.token_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 0s [id=vlahimf53i] +module.document_id_endpoints.data.aws_api_gateway_rest_api.api: Reading... +module.document_id_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 1s [id=vlahimf53i] +module.document_endpoints.data.aws_api_gateway_rest_api.api: Reading... +module.document_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 0s [id=vlahimf53i] +module.token_endpoints.aws_api_gateway_resource.resource_path: Creating... +aws_cloudfront_function.rewrite_uri: Creation complete after 2s [id=document-extractor-dev-rewrite-request] +module.document_endpoints.aws_api_gateway_resource.resource_path: Creating... +module.token_endpoints.aws_api_gateway_resource.resource_path: Creation complete after 0s [id=ejhc1h] +module.token_endpoints.aws_api_gateway_method.http_method[0]: Creating... +module.document_endpoints.aws_api_gateway_resource.resource_path: Creation complete after 0s [id=11d1bq] +module.document_id_endpoints.aws_api_gateway_resource.resource_path: Creating... +module.token_endpoints.aws_api_gateway_method.http_method[0]: Creation complete after 0s [id=agm-vlahimf53i-ejhc1h-POST] +aws_s3_bucket.website_storage: Creation complete after 2s [id=document-extractor-dev-website-328307993388] +aws_s3_bucket_versioning.website_storage_versioning: Creating... +aws_s3_bucket_website_configuration.website_configuration: Creating... +module.document_id_endpoints.aws_api_gateway_resource.resource_path: Creation complete after 0s [id=zp6oqf] +aws_s3_object.website_files["Latin-Merriweather-LightItalic.1c9df738.woff2"]: Creating... +aws_s3_bucket.document_storage: Creation complete after 2s [id=document-extractor-dev-documents-328307993388] +aws_s3_object.website_files["close.3962dfa0.svg"]: Creating... +aws_s3_object.website_files["close.3962dfa0.svg"]: Creation complete after 1s [id=close.3962dfa0.svg] +aws_s3_bucket_website_configuration.website_configuration: Creation complete after 1s [id=document-extractor-dev-website-328307993388] +aws_s3_object.website_files["loader.e58cf242.svg"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-LightItalic.1c9df738.woff2"]: Creation complete after 1s [id=Latin-Merriweather-LightItalic.1c9df738.woff2] +aws_s3_object.website_files["Latin-Merriweather-Bold.cdbf7e95.woff2"]: Creating... +aws_s3_object.website_files["ui.4e22b301.js"]: Creating... +aws_s3_object.website_files["loader.e58cf242.svg"]: Creation complete after 0s [id=loader.e58cf242.svg] +aws_s3_object.website_files["correct8-alt.c14d1430.svg"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-Bold.cdbf7e95.woff2"]: Creation complete after 0s [id=Latin-Merriweather-Bold.cdbf7e95.woff2] +aws_s3_object.website_files["search.a724f1bc.svg"]: Creating... +aws_s3_object.website_files["correct8-alt.c14d1430.svg"]: Creation complete after 0s [id=correct8-alt.c14d1430.svg] +aws_s3_object.website_files["remove.27bfc20f.svg"]: Creating... +aws_s3_object.website_files["ui.4e22b301.js"]: Creation complete after 0s [id=ui.4e22b301.js] +aws_s3_object.website_files["correct8.2dc455b8.svg"]: Creating... +aws_s3_object.website_files["search.a724f1bc.svg"]: Creation complete after 0s [id=search.a724f1bc.svg] +aws_s3_object.website_files["file-video.d309c165.svg"]: Creating... +aws_s3_object.website_files["remove.27bfc20f.svg"]: Creation complete after 1s [id=remove.27bfc20f.svg] +aws_s3_object.website_files["check_circle.120511e4.svg"]: Creating... +aws_s3_object.website_files["correct8.2dc455b8.svg"]: Creation complete after 1s [id=correct8.2dc455b8.svg] +aws_s3_object.website_files["GSA-logo.ece32ed3.svg"]: Creating... +aws_s3_object.website_files["file-video.d309c165.svg"]: Creation complete after 1s [id=file-video.d309c165.svg] +aws_s3_object.website_files["navigate_far_next.37ce8b6e.svg"]: Creating... +aws_s3_bucket_versioning.website_storage_versioning: Creation complete after 2s [id=document-extractor-dev-website-328307993388] +aws_s3_object.website_files["unfold_more.1f608602.svg"]: Creating... +aws_s3_object.website_files["check_circle.120511e4.svg"]: Creation complete after 0s [id=check_circle.120511e4.svg] +aws_s3_bucket_public_access_block.private_website: Creating... +aws_s3_object.website_files["navigate_far_next.37ce8b6e.svg"]: Creation complete after 0s [id=navigate_far_next.37ce8b6e.svg] +aws_s3_object.website_files["expand_less.5bb6de10.svg"]: Creating... +aws_s3_object.website_files["GSA-logo.ece32ed3.svg"]: Creation complete after 0s [id=GSA-logo.ece32ed3.svg] +aws_s3_object.website_files["file-word.75e83f68.svg"]: Creating... +aws_s3_object.website_files["unfold_more.1f608602.svg"]: Creation complete after 0s [id=unfold_more.1f608602.svg] +aws_s3_object.website_files["launch.2cde378c.svg"]: Creating... +aws_s3_object.website_files["expand_less.5bb6de10.svg"]: Creation complete after 0s [id=expand_less.5bb6de10.svg] +aws_s3_object.website_files["ui.7c42bb32.css"]: Creating... +aws_s3_object.website_files["file-word.75e83f68.svg"]: Creation complete after 0s [id=file-word.75e83f68.svg] +aws_s3_object.website_files["close.bf51193b.svg"]: Creating... +aws_s3_bucket_public_access_block.private_website: Creation complete after 1s [id=document-extractor-dev-website-328307993388] +aws_s3_object.website_files["calendar_today.3a803a7a.svg"]: Creating... +aws_s3_object.website_files["launch.2cde378c.svg"]: Creation complete after 1s [id=launch.2cde378c.svg] +aws_s3_object.website_files["ui.24de9d80.js"]: Creating... +aws_s3_object.website_files["close.bf51193b.svg"]: Creation complete after 1s [id=close.bf51193b.svg] +aws_s3_object.website_files["check--blue-60v.7604a0f6.svg"]: Creating... +aws_s3_object.website_files["calendar_today.3a803a7a.svg"]: Creation complete after 0s [id=calendar_today.3a803a7a.svg] +aws_s3_object.website_files["ui.31b563d9.js.map"]: Creating... +aws_s3_object.website_files["ui.7c42bb32.css"]: Creation complete after 1s [id=ui.7c42bb32.css] +aws_s3_object.website_files["Latin-Merriweather-Regular.66d4c170.woff2"]: Creating... +aws_s3_object.website_files["check--blue-60v.7604a0f6.svg"]: Creation complete after 0s [id=check--blue-60v.7604a0f6.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-700.df366d23.woff2"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-Regular.66d4c170.woff2"]: Creation complete after 0s [id=Latin-Merriweather-Regular.66d4c170.woff2] +aws_s3_object.website_files["ui.4e22b301.js.map"]: Creating... +aws_s3_object.website_files["ui.24de9d80.js"]: Creation complete after 0s [id=ui.24de9d80.js] +aws_s3_object.website_files["roboto-mono-v5-latin-700.df366d23.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-700.df366d23.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-italic.7226ad2c.woff2"]: Creating... +aws_s3_object.website_files["navigate_before.7d672c1a.svg"]: Creating... +aws_s3_object.website_files["ui.31b563d9.js.map"]: Creation complete after 1s [id=ui.31b563d9.js.map] +aws_s3_object.website_files["ui.d119b665.js.map"]: Creating... +aws_s3_object.website_files["navigate_before.7d672c1a.svg"]: Creation complete after 1s [id=navigate_before.7d672c1a.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.af4ea91a.woff2"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-italic.7226ad2c.woff2"]: Creation complete after 1s [id=roboto-mono-v5-latin-italic.7226ad2c.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.1710c37a.woff2"]: Creating... +aws_s3_object.website_files["ui.4e22b301.js.map"]: Creation complete after 1s [id=ui.4e22b301.js.map] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.1710c37a.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-300italic.1710c37a.woff2] +aws_s3_object.website_files["ui.383343a4.js"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-LightItalic.f4af3b7f.woff2"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.af4ea91a.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-300italic.af4ea91a.woff2] +aws_s3_object.website_files["ui.394b89ad.css"]: Creating... +aws_s3_object.website_files["ui.d119b665.js.map"]: Creation complete after 0s [id=ui.d119b665.js.map] +aws_s3_object.website_files["ui.b82c2ba3.js.map"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-LightItalic.f4af3b7f.woff2"]: Creation complete after 0s [id=Latin-Merriweather-LightItalic.f4af3b7f.woff2] +aws_s3_object.website_files["ui.24de9d80.js.map"]: Creating... +aws_s3_object.website_files["ui.394b89ad.css"]: Creation complete after 1s [id=ui.394b89ad.css] +aws_s3_object.website_files["checkbox-indeterminate-alt.a6b9b5ac.svg"]: Creating... +aws_dynamodb_table.extract_table: Creation complete after 7s [id=document-extractor-dev-text-extract] +aws_s3_object.website_files["roboto-mono-v5-latin-700.d309a69d.woff2"]: Creating... +aws_s3_object.website_files["ui.383343a4.js"]: Creation complete after 1s [id=ui.383343a4.js] +aws_s3_object.website_files["roboto-mono-v5-latin-italic.9209c633.woff2"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-700.d309a69d.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-700.d309a69d.woff2] +aws_s3_object.website_files["search.e5ee4fc2.svg"]: Creating... +aws_s3_object.website_files["checkbox-indeterminate-alt.a6b9b5ac.svg"]: Creation complete after 0s [id=checkbox-indeterminate-alt.a6b9b5ac.svg] +aws_s3_object.website_files["warning.4cc8d763.svg"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-italic.9209c633.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-italic.9209c633.woff2] +aws_s3_object.website_files["add.29db0c6a.svg"]: Creating... +aws_s3_object.website_files["search.e5ee4fc2.svg"]: Creation complete after 1s [id=search.e5ee4fc2.svg] +aws_s3_object.website_files["warning.4cc8d763.svg"]: Creation complete after 1s [id=warning.4cc8d763.svg] +aws_s3_object.website_files["checkbox-indeterminate.db2c5d96.svg"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-regular.e048516f.woff2"]: Creating... +aws_s3_object.website_files["add.29db0c6a.svg"]: Creation complete after 1s [id=add.29db0c6a.svg] +aws_s3_object.website_files["sourcesanspro-italic-webfont.82ff76d1.woff2"]: Creating... +aws_s3_object.website_files["ui.24de9d80.js.map"]: Creation complete after 2s [id=ui.24de9d80.js.map] +aws_s3_object.website_files["Latin-Merriweather-Italic.e2716ed3.woff2"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-regular.e048516f.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-regular.e048516f.woff2] +aws_s3_object.website_files["checkbox-indeterminate.db2c5d96.svg"]: Creation complete after 0s [id=checkbox-indeterminate.db2c5d96.svg] +aws_s3_object.website_files["info.40b16694.svg"]: Creating... +aws_s3_object.website_files["ui.394b89ad.css.map"]: Creating... +aws_s3_object.website_files["sourcesanspro-italic-webfont.82ff76d1.woff2"]: Creation complete after 0s [id=sourcesanspro-italic-webfont.82ff76d1.woff2] +aws_s3_object.website_files["ui.b82c2ba3.js.map"]: Creation complete after 2s [id=ui.b82c2ba3.js.map] +aws_s3_object.website_files["icon-dot-gov.1cc2b2c5.svg"]: Creating... +aws_s3_object.website_files["ui.9f367d21.css.map"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-Italic.e2716ed3.woff2"]: Creation complete after 0s [id=Latin-Merriweather-Italic.e2716ed3.woff2] +aws_s3_object.website_files["file-pdf.51e66388.svg"]: Creating... +aws_s3_object.website_files["info.40b16694.svg"]: Creation complete after 0s [id=info.40b16694.svg] +aws_s3_object.website_files["icon-dot-gov.1cc2b2c5.svg"]: Creation complete after 0s [id=icon-dot-gov.1cc2b2c5.svg] +aws_s3_object.website_files["hero.3538e009.jpg"]: Creating... +aws_s3_object.website_files["check--blue-60v.982d9f95.svg"]: Creating... +aws_s3_object.website_files["ui.9f367d21.css.map"]: Creation complete after 0s [id=ui.9f367d21.css.map] +aws_s3_object.website_files["ui.4540ed92.js"]: Creating... +aws_s3_object.website_files["file-pdf.51e66388.svg"]: Creation complete after 0s [id=file-pdf.51e66388.svg] +aws_s3_object.website_files["ui.394b89ad.css.map"]: Creation complete after 0s [id=ui.394b89ad.css.map] +aws_s3_object.website_files["ui.4540ed92.js.map"]: Creating... +aws_s3_object.website_files["file.057d5652.svg"]: Creating... +aws_s3_object.website_files["check--blue-60v.982d9f95.svg"]: Creation complete after 1s [id=check--blue-60v.982d9f95.svg] +aws_s3_object.website_files["hero.3538e009.jpg"]: Creation complete after 1s [id=hero.3538e009.jpg] +aws_s3_object.website_files["Latin-Merriweather-Regular.04046ce8.woff2"]: Creating... +aws_s3_object.website_files["file-video.a3a38638.svg"]: Creating... +aws_s3_object.website_files["ui.4540ed92.js"]: Creation complete after 1s [id=ui.4540ed92.js] +aws_s3_object.website_files["file-excel.323d5d7b.svg"]: Creating... +aws_s3_object.website_files["file.057d5652.svg"]: Creation complete after 1s [id=file.057d5652.svg] +aws_s3_object.website_files["file-video.a3a38638.svg"]: Creation complete after 0s [id=file-video.a3a38638.svg] +aws_s3_object.website_files["Latin-Merriweather-Regular.04046ce8.woff2"]: Creation complete after 0s [id=Latin-Merriweather-Regular.04046ce8.woff2] +aws_s3_object.website_files["ui.ee3dfc51.js"]: Creating... +aws_s3_object.website_files["expand_more.3ac36c78.svg"]: Creating... +aws_s3_object.website_files["sourcesanspro-italic-webfont.ab90d6d1.woff2"]: Creating... +aws_s3_object.website_files["file-excel.323d5d7b.svg"]: Creation complete after 0s [id=file-excel.323d5d7b.svg] +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.84e64ba8.woff2"]: Creating... +aws_s3_object.website_files["ui.4540ed92.js.map"]: Creation complete after 1s [id=ui.4540ed92.js.map] +aws_s3_object.website_files["expand_more.0d06b804.svg"]: Creating... +aws_s3_object.website_files["expand_more.3ac36c78.svg"]: Creation complete after 0s [id=expand_more.3ac36c78.svg] +aws_s3_object.website_files["sourcesanspro-italic-webfont.ab90d6d1.woff2"]: Creation complete after 0s [id=sourcesanspro-italic-webfont.ab90d6d1.woff2] +aws_s3_object.website_files["error.1986e136.svg"]: Creating... +aws_s3_object.website_files["ui.383343a4.js.map"]: Creating... +aws_kms_key.encryption: Creation complete after 9s [id=24da8b13-b49c-4dae-8526-1240622d743c] +aws_s3_object.website_files["GSA-logo.7a2abfc3.svg"]: Creating... +aws_s3_object.website_files["ui.ee3dfc51.js"]: Creation complete after 0s [id=ui.ee3dfc51.js] +aws_s3_object.website_files["ui.9f367d21.css"]: Creating... +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.84e64ba8.woff2"]: Creation complete after 0s [id=sourcesanspro-bolditalic-webfont.84e64ba8.woff2] +aws_s3_object.website_files["hero.abe074f7.jpg"]: Creating... +aws_s3_object.website_files["expand_more.0d06b804.svg"]: Creation complete after 0s [id=expand_more.0d06b804.svg] +aws_s3_object.website_files["navigate_before.be211c2e.svg"]: Creating... +aws_s3_object.website_files["error.1986e136.svg"]: Creation complete after 0s [id=error.1986e136.svg] +aws_s3_object.website_files["sourcesanspro-regular-webfont.a9d6b459.woff2"]: Creating... +aws_secretsmanager_secret.private_key: Still creating... [10s elapsed] +aws_secretsmanager_secret.password: Still creating... [10s elapsed] +aws_s3_object.website_files["GSA-logo.7a2abfc3.svg"]: Creation complete after 1s [id=GSA-logo.7a2abfc3.svg] +aws_s3_object.website_files["ui.ee3dfc51.js.map"]: Creating... +aws_s3_object.website_files["ui.9f367d21.css"]: Creation complete after 1s [id=ui.9f367d21.css] +aws_s3_object.website_files["hero.abe074f7.jpg"]: Creation complete after 1s [id=hero.abe074f7.jpg] +aws_s3_object.website_files["navigate_before.be211c2e.svg"]: Creation complete after 1s [id=navigate_before.be211c2e.svg] +aws_s3_object.website_files["checkbox-indeterminate-alt.5e7abfcf.svg"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-Bold.967b2108.woff2"]: Creating... +aws_s3_object.website_files["sourcesanspro-regular-webfont.76c0bde6.woff2"]: Creating... +aws_secretsmanager_secret.public_key: Still creating... [10s elapsed] +aws_secretsmanager_secret.username: Still creating... [10s elapsed] +aws_s3_object.website_files["checkbox-indeterminate-alt.5e7abfcf.svg"]: Creation complete after 0s [id=checkbox-indeterminate-alt.5e7abfcf.svg] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.32dc0025.woff2"]: Creating... +aws_s3_object.website_files["sourcesanspro-regular-webfont.76c0bde6.woff2"]: Creation complete after 0s [id=sourcesanspro-regular-webfont.76c0bde6.woff2] +aws_s3_object.website_files["launch--white.626c08f5.svg"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-Bold.967b2108.woff2"]: Creation complete after 0s [id=Latin-Merriweather-Bold.967b2108.woff2] +aws_s3_object.website_files["icon-https.b6eca6f6.svg"]: Creating... +aws_s3_object.website_files["ui.ee3dfc51.js.map"]: Creation complete after 0s [id=ui.ee3dfc51.js.map] +aws_s3_object.website_files["sourcesanspro-light-webfont.e70d4904.woff2"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.32dc0025.woff2"]: Creation complete after 0s [id=Latin-Merriweather-BoldItalic.32dc0025.woff2] +aws_s3_object.website_files["correct8-alt.8f8dac23.svg"]: Creating... +aws_s3_object.website_files["launch--white.626c08f5.svg"]: Creation complete after 0s [id=launch--white.626c08f5.svg] +aws_s3_object.website_files["icon-https.b6eca6f6.svg"]: Creation complete after 0s [id=icon-https.b6eca6f6.svg] +aws_s3_object.website_files["error.35f61f4b.svg"]: Creating... +aws_s3_object.website_files["launch--white.6c179f60.svg"]: Creating... +aws_s3_object.website_files["sourcesanspro-regular-webfont.a9d6b459.woff2"]: Creation complete after 1s [id=sourcesanspro-regular-webfont.a9d6b459.woff2] +aws_s3_object.website_files["loader.7402c183.svg"]: Creating... +aws_s3_object.website_files["correct8-alt.8f8dac23.svg"]: Creation complete after 0s [id=correct8-alt.8f8dac23.svg] +aws_s3_object.website_files["navigate_next.fb4be146.svg"]: Creating... +aws_s3_object.website_files["sourcesanspro-light-webfont.e70d4904.woff2"]: Creation complete after 1s [id=sourcesanspro-light-webfont.e70d4904.woff2] +aws_s3_object.website_files["arrow_back.e4913c69.svg"]: Creating... +aws_s3_object.website_files["error.35f61f4b.svg"]: Creation complete after 1s [id=error.35f61f4b.svg] +aws_s3_object.website_files["index.html"]: Creating... +aws_s3_object.website_files["launch--white.6c179f60.svg"]: Creation complete after 1s [id=launch--white.6c179f60.svg] +aws_s3_object.website_files["check_circle.a3900be5.svg"]: Creating... +aws_s3_object.website_files["loader.7402c183.svg"]: Creation complete after 1s [id=loader.7402c183.svg] +aws_s3_object.website_files["ui.383343a4.js.map"]: Creation complete after 2s [id=ui.383343a4.js.map] +aws_s3_object.website_files["navigate_far_next.bd2c92a4.svg"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-Italic.959c3872.woff2"]: Creating... +aws_s3_object.website_files["navigate_next.fb4be146.svg"]: Creation complete after 1s [id=navigate_next.fb4be146.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300.98978b08.woff2"]: Creating... +aws_s3_object.website_files["arrow_back.e4913c69.svg"]: Creation complete after 0s [id=arrow_back.e4913c69.svg] +aws_s3_object.website_files["us_flag_small.e642a40d.png"]: Creating... +aws_s3_object.website_files["index.html"]: Creation complete after 0s [id=index.html] +aws_s3_object.website_files["launch.f4de218c.svg"]: Creating... +aws_s3_object.website_files["check_circle.a3900be5.svg"]: Creation complete after 0s [id=check_circle.a3900be5.svg] +aws_s3_object.website_files["navigate_far_next.bd2c92a4.svg"]: Creation complete after 0s [id=navigate_far_next.bd2c92a4.svg] +aws_s3_object.website_files["calendar_today.ce6eaa81.svg"]: Creating... +aws_s3_object.website_files["arrow_back.c9ac1a0e.svg"]: Creating... +aws_s3_object.website_files["Latin-Merriweather-Italic.959c3872.woff2"]: Creation complete after 0s [id=Latin-Merriweather-Italic.959c3872.woff2] +aws_s3_object.website_files["ui.b82c2ba3.js"]: Creating... +aws_s3_object.website_files["us_flag_small.e642a40d.png"]: Creation complete after 0s [id=us_flag_small.e642a40d.png] +aws_s3_object.website_files["ui.7c42bb32.css.map"]: Creating... +aws_s3_object.website_files["launch.f4de218c.svg"]: Creation complete after 0s [id=launch.f4de218c.svg] +aws_s3_object.website_files["calendar_today.ce6eaa81.svg"]: Creation complete after 0s [id=calendar_today.ce6eaa81.svg] +aws_s3_object.website_files["arrow_back.c9ac1a0e.svg"]: Creation complete after 0s [id=arrow_back.c9ac1a0e.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-regular.ec7832e5.woff2"]: Creating... +aws_s3_object.website_files["sourcesanspro-bold-webfont.21f8979c.woff2"]: Creating... +aws_s3_object.website_files["error--white.dcc8cafd.svg"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-300.98978b08.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-300.98978b08.woff2] +aws_s3_object.website_files["sourcesanspro-bold-webfont.5436602a.woff2"]: Creating... +aws_s3_object.website_files["ui.b82c2ba3.js"]: Creation complete after 0s [id=ui.b82c2ba3.js] +aws_s3_object.website_files["icon-dot-gov.86228b6b.svg"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-regular.ec7832e5.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-regular.ec7832e5.woff2] +aws_s3_object.website_files["error--white.dcc8cafd.svg"]: Creation complete after 0s [id=error--white.dcc8cafd.svg] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.e58256dc.woff2"]: Creating... +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.b61b42d2.woff2"]: Creating... +aws_s3_object.website_files["sourcesanspro-bold-webfont.21f8979c.woff2"]: Creation complete after 0s [id=sourcesanspro-bold-webfont.21f8979c.woff2] +aws_s3_object.website_files["pdf.worker.min.mjs"]: Creating... +aws_s3_object.website_files["ui.7c42bb32.css.map"]: Creation complete after 0s [id=ui.7c42bb32.css.map] +aws_s3_object.website_files["correct8.10e8a7ef.svg"]: Creating... +aws_s3_object.website_files["icon-dot-gov.86228b6b.svg"]: Creation complete after 1s [id=icon-dot-gov.86228b6b.svg] +aws_s3_object.website_files["sourcesanspro-bold-webfont.5436602a.woff2"]: Creation complete after 1s [id=sourcesanspro-bold-webfont.5436602a.woff2] +aws_s3_object.website_files["sourcesanspro-light-webfont.e490d910.woff2"]: Creating... +aws_s3_object.website_files["checkbox-indeterminate.08aa88cd.svg"]: Creating... +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.b61b42d2.woff2"]: Creation complete after 1s [id=sourcesanspro-bolditalic-webfont.b61b42d2.woff2] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.e58256dc.woff2"]: Creation complete after 1s [id=Latin-Merriweather-BoldItalic.e58256dc.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-300.40a89411.woff2"]: Creating... +aws_s3_object.website_files["navigate_far_before.65b60266.svg"]: Creating... +aws_s3_object.website_files["correct8.10e8a7ef.svg"]: Creation complete after 1s [id=correct8.10e8a7ef.svg] +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.c14df9bc.woff2"]: Creating... +aws_s3_object.website_files["checkbox-indeterminate.08aa88cd.svg"]: Creation complete after 0s [id=checkbox-indeterminate.08aa88cd.svg] +aws_s3_object.website_files["Latin-Merriweather-Light.fbc4393c.woff2"]: Creating... +aws_s3_object.website_files["pdf.worker.min.mjs"]: Creation complete after 1s [id=pdf.worker.min.mjs] +aws_s3_object.website_files["file.f51e21d7.svg"]: Creating... +aws_s3_object.website_files["sourcesanspro-light-webfont.e490d910.woff2"]: Creation complete after 0s [id=sourcesanspro-light-webfont.e490d910.woff2] +aws_s3_object.website_files["navigate_next.bc7c1a4e.svg"]: Creating... +aws_s3_object.website_files["navigate_far_before.65b60266.svg"]: Creation complete after 0s [id=navigate_far_before.65b60266.svg] +aws_s3_object.website_files["ui.31b563d9.js"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-300.40a89411.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-300.40a89411.woff2] +aws_s3_object.website_files["file-pdf.248e68e5.svg"]: Creating... +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.c14df9bc.woff2"]: Creation complete after 0s [id=sourcesanspro-lightitalic-webfont.c14df9bc.woff2] +aws_s3_object.website_files["expand_less.e2846957.svg"]: Creating... +aws_s3_object.website_files["file.f51e21d7.svg"]: Creation complete after 0s [id=file.f51e21d7.svg] +aws_s3_object.website_files["Latin-Merriweather-Light.fbc4393c.woff2"]: Creation complete after 0s [id=Latin-Merriweather-Light.fbc4393c.woff2] +aws_s3_object.website_files["file-excel.c90d2036.svg"]: Creating... +aws_s3_object.website_files["ui.d119b665.js"]: Creating... +aws_s3_object.website_files["navigate_next.bc7c1a4e.svg"]: Creation complete after 0s [id=navigate_next.bc7c1a4e.svg] +aws_s3_object.website_files["icon-https.3b0ffef2.svg"]: Creating... +aws_s3_object.website_files["file-pdf.248e68e5.svg"]: Creation complete after 0s [id=file-pdf.248e68e5.svg] +aws_s3_object.website_files["unfold_more.46c00529.svg"]: Creating... +aws_s3_object.website_files["expand_less.e2846957.svg"]: Creation complete after 0s [id=expand_less.e2846957.svg] +aws_s3_object.website_files["info.85fe97af.svg"]: Creating... +aws_s3_object.website_files["ui.d119b665.js"]: Creation complete after 0s [id=ui.d119b665.js] +aws_s3_object.website_files["file-excel.c90d2036.svg"]: Creation complete after 0s [id=file-excel.c90d2036.svg] +aws_s3_object.website_files["warning.ce0fedc9.svg"]: Creating... +aws_s3_object.website_files["file-word.896caaf0.svg"]: Creating... +aws_s3_object.website_files["icon-https.3b0ffef2.svg"]: Creation complete after 0s [id=icon-https.3b0ffef2.svg] +aws_s3_object.website_files["navigate_far_before.904e4c63.svg"]: Creating... +aws_s3_object.website_files["ui.31b563d9.js"]: Creation complete after 0s [id=ui.31b563d9.js] +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.2b983f5d.woff2"]: Creating... +aws_s3_object.website_files["unfold_more.46c00529.svg"]: Creation complete after 1s [id=unfold_more.46c00529.svg] +aws_s3_object.website_files["error--white.73d13870.svg"]: Creating... +aws_s3_object.website_files["info.85fe97af.svg"]: Creation complete after 1s [id=info.85fe97af.svg] +aws_s3_object.website_files["us_flag_small.9c3c6ab8.png"]: Creating... +aws_s3_object.website_files["file-word.896caaf0.svg"]: Creation complete after 1s [id=file-word.896caaf0.svg] +aws_s3_object.website_files["remove.ca324d27.svg"]: Creating... +aws_s3_object.website_files["warning.ce0fedc9.svg"]: Creation complete after 1s [id=warning.ce0fedc9.svg] +aws_s3_object.website_files["add.d491396d.svg"]: Creating... +aws_s3_object.website_files["navigate_far_before.904e4c63.svg"]: Creation complete after 1s [id=navigate_far_before.904e4c63.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.03671595.woff2"]: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.2b983f5d.woff2"]: Creation complete after 1s [id=roboto-mono-v5-latin-700italic.2b983f5d.woff2] +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.05b42991.woff2"]: Creating... +aws_s3_object.website_files["error--white.73d13870.svg"]: Creation complete after 0s [id=error--white.73d13870.svg] +aws_s3_object.website_files["Latin-Merriweather-Light.1ad1a1d8.woff2"]: Creating... +aws_s3_object.website_files["us_flag_small.9c3c6ab8.png"]: Creation complete after 0s [id=us_flag_small.9c3c6ab8.png] +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Creating... +aws_s3_object.website_files["remove.ca324d27.svg"]: Creation complete after 0s [id=remove.ca324d27.svg] +aws_s3_object.website_files["add.d491396d.svg"]: Creation complete after 0s [id=add.d491396d.svg] +data.aws_iam_policy_document.s3_lambda_policy: Reading... +data.aws_iam_policy_document.dynamodb_lambda_policy: Reading... +data.aws_iam_policy_document.s3_lambda_policy: Read complete after 0s [id=1929687036] +data.aws_iam_policy_document.dynamodb_lambda_policy: Read complete after 0s [id=2781330629] +data.aws_iam_policy_document.kms_lambda_policy: Reading... +data.aws_iam_policy_document.kms_lambda_policy: Read complete after 0s [id=4199015003] +aws_sqs_queue.queue_to_dynamo: Creating... +aws_lambda_function.authorizer: Creating... +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.03671595.woff2"]: Creation complete after 0s [id=roboto-mono-v5-latin-700italic.03671595.woff2] +aws_iam_policy.s3_lambda_policy: Creating... +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.05b42991.woff2"]: Creation complete after 0s [id=sourcesanspro-lightitalic-webfont.05b42991.woff2] +aws_iam_policy.dynamodb_lambda_policy: Creating... +aws_s3_object.website_files["Latin-Merriweather-Light.1ad1a1d8.woff2"]: Creation complete after 0s [id=Latin-Merriweather-Light.1ad1a1d8.woff2] +aws_iam_policy.kms_lambda_policy: Creating... +aws_iam_policy.s3_lambda_policy: Creation complete after 0s [id=arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy] +module.token_endpoints.aws_lambda_function.function[0]: Creating... +aws_iam_policy.dynamodb_lambda_policy: Creation complete after 0s [id=arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy] +module.document_id_endpoints.aws_lambda_function.function[1]: Creating... +aws_iam_policy.kms_lambda_policy: Creation complete after 0s [id=arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy] +module.document_id_endpoints.aws_lambda_function.function[0]: Creating... +aws_secretsmanager_secret.private_key: Still creating... [20s elapsed] +aws_secretsmanager_secret.password: Still creating... [20s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [20s elapsed] +aws_secretsmanager_secret.username: Still creating... [20s elapsed] +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Still creating... [10s elapsed] +aws_sqs_queue.queue_to_dynamo: Still creating... [10s elapsed] +aws_lambda_function.authorizer: Still creating... [10s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Still creating... [10s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still creating... [10s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still creating... [10s elapsed] +aws_lambda_function.authorizer: Creation complete after 11s [id=document-extractor-dev-authorizer] +module.document_endpoints.aws_lambda_function.function[0]: Creating... +aws_secretsmanager_secret.private_key: Still creating... [30s elapsed] +aws_secretsmanager_secret.password: Still creating... [30s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [30s elapsed] +aws_secretsmanager_secret.username: Still creating... [30s elapsed] +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Still creating... [20s elapsed] +aws_sqs_queue.queue_to_dynamo: Still creating... [20s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Still creating... [20s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still creating... [20s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still creating... [20s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Still creating... [10s elapsed] +module.token_endpoints.aws_lambda_function.function[0]: Creation complete after 24s [id=document-extractor-dev-token] +aws_iam_role_policy_attachment.attach_s3_permission_to_role: Creating... +aws_iam_role_policy_attachment.attach_s3_permission_to_role: Creation complete after 0s [id=document-extractor-dev-lambda-execution-role-20250602163017728200000008] +aws_iam_role_policy_attachment.attach_dynamodb_permission_to_role: Creating... +aws_iam_role_policy_attachment.attach_dynamodb_permission_to_role: Creation complete after 0s [id=document-extractor-dev-lambda-execution-role-20250602163017883100000009] +aws_iam_role_policy_attachment.attach_kms_permission_to_role: Creating... +aws_iam_role_policy_attachment.attach_kms_permission_to_role: Creation complete after 0s [id=document-extractor-dev-lambda-execution-role-2025060216301805090000000a] +aws_api_gateway_authorizer.authorizer: Creating... +aws_api_gateway_authorizer.authorizer: Creation complete after 1s [id=s7qyib] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Creating... +aws_sqs_queue.queue_to_dynamo: Creation complete after 26s [id=https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb] +aws_lambda_permission.api_gateway_invoke_authorizer: Creating... +aws_secretsmanager_secret.password: Still creating... [40s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [40s elapsed] +aws_lambda_permission.api_gateway_invoke_authorizer: Creation complete after 1s [id=AllowExecutionFromApiGateway] +module.token_endpoints.aws_api_gateway_integration.lambda_integration[0]: Creating... +aws_secretsmanager_secret.public_key: Still creating... [40s elapsed] +aws_secretsmanager_secret.username: Still creating... [40s elapsed] +module.token_endpoints.aws_api_gateway_integration.lambda_integration[0]: Creation complete after 0s [id=agi-vlahimf53i-ejhc1h-POST] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creating... +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Still creating... [30s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Still creating... [30s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still creating... [30s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Still creating... [20s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [10s elapsed] +module.document_id_endpoints.aws_lambda_function.function[1]: Creation complete after 36s [id=document-extractor-dev-update-document] +aws_secretsmanager_secret.password: Still creating... [50s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [50s elapsed] +module.token_endpoints.aws_lambda_permission.api_gateway[0]: Creating... +aws_secretsmanager_secret.public_key: Still creating... [50s elapsed] +aws_secretsmanager_secret.username: Still creating... [50s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [10s elapsed] +module.token_endpoints.aws_lambda_permission.api_gateway[0]: Creation complete after 0s [id=AllowExecutionFromApiGateway] +data.aws_iam_policy_document.sqs_lambda_policy: Reading... +data.aws_iam_policy_document.sqs_lambda_policy: Read complete after 0s [id=1807487604] +aws_lambda_function.write_to_dynamodb: Creating... +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Still creating... [40s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Still creating... [40s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Still creating... [30s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [20s elapsed] +module.document_id_endpoints.aws_lambda_function.function[0]: Creation complete after 46s [id=document-extractor-dev-get-document] +aws_lambda_function.text_extract: Creating... +aws_secretsmanager_secret.password: Still creating... [1m0s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [1m0s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [1m0s elapsed] +aws_secretsmanager_secret.username: Still creating... [1m0s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [20s elapsed] +aws_lambda_function.write_to_dynamodb: Still creating... [10s elapsed] +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Still creating... [50s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Still creating... [40s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [30s elapsed] +aws_lambda_function.text_extract: Still creating... [10s elapsed] +aws_secretsmanager_secret.password: Still creating... [1m10s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [1m10s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [1m10s elapsed] +aws_secretsmanager_secret.username: Still creating... [1m10s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [30s elapsed] +module.document_endpoints.aws_lambda_function.function[0]: Creation complete after 45s [id=document-extractor-dev-create-document] +aws_lambda_function.write_to_dynamodb: Still creating... [20s elapsed] +aws_iam_policy.sqs_lambda_policy: Creating... +aws_iam_policy.sqs_lambda_policy: Creation complete after 0s [id=arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Creating... +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Still creating... [1m0s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [40s elapsed] +aws_lambda_function.text_extract: Still creating... [20s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [1m20s elapsed] +aws_secretsmanager_secret.password: Still creating... [1m20s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [1m20s elapsed] +aws_secretsmanager_secret.username: Still creating... [1m20s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [40s elapsed] +aws_lambda_function.write_to_dynamodb: Still creating... [30s elapsed] +aws_lambda_function.write_to_dynamodb: Creation complete after 30s [id=document-extractor-dev-write-to-dynamodb] +module.document_endpoints.aws_api_gateway_method.http_method[0]: Creating... +module.document_endpoints.aws_api_gateway_method.http_method[0]: Creation complete after 0s [id=agm-vlahimf53i-11d1bq-POST] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creating... +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [10s elapsed] +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Still creating... [1m10s elapsed] +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Creation complete after 1m13s [id=document-extractor-dev-documents-328307993388] +module.document_id_endpoints.aws_lambda_permission.api_gateway[0]: Creating... +module.document_id_endpoints.aws_lambda_permission.api_gateway[0]: Creation complete after 0s [id=AllowExecutionFromApiGateway] +module.document_id_endpoints.aws_api_gateway_method.http_method[0]: Creating... +module.document_id_endpoints.aws_api_gateway_method.http_method[0]: Creation complete after 0s [id=agm-vlahimf53i-zp6oqf-GET] +module.document_id_endpoints.aws_lambda_permission.api_gateway[1]: Creating... +module.document_id_endpoints.aws_lambda_permission.api_gateway[1]: Creation complete after 0s [id=AllowExecutionFromApiGateway] +module.document_id_endpoints.aws_api_gateway_method.http_method[1]: Creating... +module.document_id_endpoints.aws_api_gateway_method.http_method[1]: Creation complete after 0s [id=agm-vlahimf53i-zp6oqf-PUT] +module.document_endpoints.aws_lambda_permission.api_gateway[0]: Creating... +module.document_endpoints.aws_lambda_permission.api_gateway[0]: Creation complete after 0s [id=AllowExecutionFromApiGateway] +aws_iam_role_policy_attachment.attach_sqs_permission_to_role: Creating... +aws_iam_role_policy_attachment.attach_sqs_permission_to_role: Creation complete after 0s [id=document-extractor-dev-lambda-execution-role-2025060216310787720000000b] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Creating... +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [50s elapsed] +aws_lambda_function.text_extract: Still creating... [30s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [1m30s elapsed] +aws_secretsmanager_secret.password: Still creating... [1m30s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [1m30s elapsed] +aws_secretsmanager_secret.username: Still creating... [1m30s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [50s elapsed] +aws_lambda_function.text_extract: Creation complete after 31s [id=document-extractor-dev-text-extract] +module.document_endpoints.aws_api_gateway_integration.lambda_integration[0]: Creating... +module.document_endpoints.aws_api_gateway_integration.lambda_integration[0]: Creation complete after 0s [id=agi-vlahimf53i-11d1bq-POST] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creating... +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [10s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [20s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [10s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [1m0s elapsed] +aws_secretsmanager_secret.password: Still creating... [1m40s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [1m40s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [1m40s elapsed] +aws_secretsmanager_secret.username: Still creating... [1m40s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m0s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [10s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [30s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [20s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [1m10s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [1m50s elapsed] +aws_secretsmanager_secret.password: Still creating... [1m50s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [1m50s elapsed] +aws_secretsmanager_secret.username: Still creating... [1m50s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m10s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [30s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [40s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [30s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Still creating... [1m20s elapsed] +aws_secretsmanager_secret.password: Still creating... [2m0s elapsed] +aws_secretsmanager_secret.private_key: Still creating... [2m0s elapsed] +aws_secretsmanager_secret.public_key: Still creating... [2m0s elapsed] +aws_secretsmanager_secret.username: Still creating... [2m0s elapsed] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m20s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [30s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [40s elapsed] +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[1]: Creating... +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[0]: Creating... +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Creating... +aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: Creating... +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[1]: Creation complete after 1s [id=agi-vlahimf53i-zp6oqf-PUT] +aws_lambda_permission.allow_bucket_invoke: Creating... +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[0]: Creation complete after 1s [id=agi-vlahimf53i-zp6oqf-GET] +aws_api_gateway_deployment.api_deployment: Creating... +aws_lambda_permission.allow_bucket_invoke: Creation complete after 0s [id=AllowExecutionFromS3Bucket] +aws_s3_bucket_notification.notify_on_input_data: Creating... +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [50s elapsed] +aws_api_gateway_deployment.api_deployment: Creation complete after 0s [id=25zx1h] +aws_api_gateway_stage.stage: Creating... +aws_api_gateway_stage.stage: Creation complete after 1s [id=ags-vlahimf53i-v1] +aws_cloudfront_distribution.distribution: Creating... +aws_s3_bucket_notification.notify_on_input_data: Creation complete after 1s [id=document-extractor-dev-documents-328307993388] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [40s elapsed] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Creation complete after 1m29s [id=document-extractor-dev-authorizer,7] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creation complete after 1m29s [id=document-extractor-dev-token,7] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [40s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [50s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [10s elapsed] +aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: Still creating... [10s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [1m0s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [10s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [50s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [50s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m0s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [20s elapsed] +aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: Still creating... [20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [1m10s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [20s elapsed] +aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: Creation complete after 24s [id=1f570465-60b3-45f6-b628-26fc00ce8c9a] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [1m0s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m0s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m10s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [30s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Still creating... [1m20s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [30s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [1m10s elapsed] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m10s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Creation complete after 1m29s [id=document-extractor-dev-update-document,7] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [41s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [40s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Still creating... [1m20s elapsed] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creation complete after 1m30s [id=document-extractor-dev-get-document,7] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Still creating... [1m20s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [51s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [50s elapsed] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Creation complete after 1m29s [id=document-extractor-dev-write-to-dynamodb,7] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Creation complete after 1m30s [id=document-extractor-dev-create-document,7] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [1m1s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [1m0s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [1m11s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [1m10s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Still creating... [1m21s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [1m20s elapsed] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Creation complete after 1m30s [id=document-extractor-dev-text-extract,7] +aws_cloudfront_distribution.distribution: Still creating... [1m30s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [1m40s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [1m50s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [2m0s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [2m10s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [2m20s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [2m30s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [2m40s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [2m50s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [3m0s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [3m10s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [3m20s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [3m30s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [3m40s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [3m50s elapsed] +aws_cloudfront_distribution.distribution: Still creating... [4m0s elapsed] +aws_cloudfront_distribution.distribution: Creation complete after 4m3s [id=E1RO9HR7KQIH7C] +data.aws_iam_policy_document.cf_read: Reading... +data.aws_iam_policy_document.cf_read: Read complete after 0s [id=2678636232] +aws_s3_bucket_policy.website_read: Creating... +null_resource.invalidate_cloudfront: Creating... +null_resource.invalidate_cloudfront: Provisioning with 'local-exec'... +null_resource.invalidate_cloudfront (local-exec): Executing: ["/bin/sh" "-c" " aws cloudfront create-invalidation \\\n --distribution-id E1RO9HR7KQIH7C \\\n --paths '/*' \\\n --region us-west-1\n"] +aws_s3_bucket_policy.website_read: Creation complete after 0s [id=document-extractor-dev-website-328307993388] +null_resource.invalidate_cloudfront (local-exec): { +null_resource.invalidate_cloudfront (local-exec): "Location": "https://cloudfront.amazonaws.com/2020-05-31/distribution/E1RO9HR7KQIH7C/invalidation/I368YL5ME9H9Z0K42HKTPBSM3E", +null_resource.invalidate_cloudfront (local-exec): "Invalidation": { +null_resource.invalidate_cloudfront (local-exec): "Id": "I368YL5ME9H9Z0K42HKTPBSM3E", +null_resource.invalidate_cloudfront (local-exec): "Status": "InProgress", +null_resource.invalidate_cloudfront (local-exec): "CreateTime": "2025-06-02T16:35:48.357000+00:00", +null_resource.invalidate_cloudfront (local-exec): "InvalidationBatch": { +null_resource.invalidate_cloudfront (local-exec): "Paths": { +null_resource.invalidate_cloudfront (local-exec): "Quantity": 1, +null_resource.invalidate_cloudfront (local-exec): "Items": [ +null_resource.invalidate_cloudfront (local-exec): "/*" +null_resource.invalidate_cloudfront (local-exec): ] +null_resource.invalidate_cloudfront (local-exec): }, +null_resource.invalidate_cloudfront (local-exec): "CallerReference": "cli-1748882147-779595" +null_resource.invalidate_cloudfront (local-exec): } +null_resource.invalidate_cloudfront (local-exec): } +null_resource.invalidate_cloudfront (local-exec): } +null_resource.invalidate_cloudfront: Creation complete after 1s [id=2455179040769893019] +╷ +│ Error: creating Secrets Manager Secret (document-extractor-dev-private-key): operation error Secrets Manager: CreateSecret, https response error StatusCode: 400, RequestID: cd5fea98-6806-4fea-bf48-4c41e0b8fca6, InvalidRequestException: You can't create this secret because a secret with this name is already scheduled for deletion. +│ +│ with aws_secretsmanager_secret.private_key, +│ on secrets.tf line 1, in resource "aws_secretsmanager_secret" "private_key": +│ 1: resource "aws_secretsmanager_secret" "private_key" { +│ +╵ +╷ +│ Error: creating Secrets Manager Secret (document-extractor-dev-public-key): operation error Secrets Manager: CreateSecret, https response error StatusCode: 400, RequestID: 4472a043-8c6a-48f9-8404-4a39a01fb709, InvalidRequestException: You can't create this secret because a secret with this name is already scheduled for deletion. +│ +│ with aws_secretsmanager_secret.public_key, +│ on secrets.tf line 5, in resource "aws_secretsmanager_secret" "public_key": +│ 5: resource "aws_secretsmanager_secret" "public_key" { +│ +╵ +╷ +│ Error: creating Secrets Manager Secret (document-extractor-dev-username): operation error Secrets Manager: CreateSecret, https response error StatusCode: 400, RequestID: 960d0ce9-fff6-429e-b6cb-608fe1c6b90a, InvalidRequestException: You can't create this secret because a secret with this name is already scheduled for deletion. +│ +│ with aws_secretsmanager_secret.username, +│ on secrets.tf line 9, in resource "aws_secretsmanager_secret" "username": +│ 9: resource "aws_secretsmanager_secret" "username" { +│ +╵ +╷ +│ Error: creating Secrets Manager Secret (document-extractor-dev-password): operation error Secrets Manager: CreateSecret, https response error StatusCode: 400, RequestID: 6fb0d1d5-3430-4e9c-bf8e-5664e6893888, InvalidRequestException: You can't create this secret because a secret with this name is already scheduled for deletion. +│ +│ with aws_secretsmanager_secret.password, +│ on secrets.tf line 13, in resource "aws_secretsmanager_secret" "password": +│ 13: resource "aws_secretsmanager_secret" "password" { +│ +╵ diff --git a/docs/open-tofu.md b/docs/open-tofu.md new file mode 100644 index 0000000..f08da42 --- /dev/null +++ b/docs/open-tofu.md @@ -0,0 +1,173 @@ +# Deploying Infrastructure with OpenTofu + +This guide outlines the steps to deploy the infrastructure defined in the `iac` directory using OpenTofu, an open-source alternative to Terraform. + +## Introduction + +OpenTofu is a fork of Terraform v1.5.x, initiated due to Terraform's license change. It aims to be a drop-in replacement for Terraform, meaning your existing Terraform configuration files (`.tf`) can typically be used with OpenTofu without modification. + +We will use the existing S3 backend configuration to store the OpenTofu state. + +## Prerequisites + +1. **Install OpenTofu**: + * Follow the official OpenTofu installation guide for your operating system: [https://opentofu.org/docs/intro/install/](https://opentofu.org/docs/intro/install/) + +2. **AWS CLI Configured**: + * Ensure the AWS CLI is installed and configured. + * The backend configuration in `iac/main.tf` specifies `profile = "AWSAdministratorAccess-328307993388"`. Make sure this AWS profile is configured in your `~/.aws/credentials` and `~/.aws/config` files. + +3. **S3 Bucket for State**: + * You mentioned an existing S3 bucket: `document-extractor-dev-opentofu-state`. OpenTofu will use this bucket to store its state file. + +4. **DynamoDB Table for Locks**: + * The backend configuration specifies `dynamodb_table = "terraform-locks-dev"`. + * If this table does not exist in the `us-west-1` region, OpenTofu will attempt to create it during the `init` step, provided your AWS user/role has the necessary DynamoDB permissions (`dynamodb:CreateTable`, `dynamodb:DescribeTable`, etc.). + +## Deployment Steps + +1. **Navigate to the IAC Directory**: + ```bash + cd iac + ``` + +2. **Initialize OpenTofu**: + This command initializes the working directory, downloads necessary provider plugins, and configures the backend. OpenTofu will automatically detect and use the backend configuration defined in `main.tf`. + ```bash + tofu init + ``` + You should see output indicating successful initialization and backend setup using S3. + + If `tofu init` has trouble finding or configuring the backend, you can explicitly provide the backend configuration (though it should not be necessary if `main.tf` is correctly configured): + ```bash + tofu init \ + -backend-config="bucket=document-extractor-dev-opentofu-state" \ + -backend-config="key=document-extractor/terraform.tfstate" \ + -backend-config="region=us-west-1" \ + -backend-config="dynamodb_table=terraform-locks-dev" \ + -backend-config="encrypt=true" \ + -backend-config="profile=AWSAdministratorAccess-328307993388" + ``` + +3. **Validate Configuration (Optional but Recommended)**: + Check if the configuration is syntactically valid and internally consistent. + ```bash + tofu validate + ``` + +4. **Create an Execution Plan**: + This command creates an execution plan, showing you what actions OpenTofu will take to achieve the desired state defined in your configuration files. + ```bash + tofu plan -out=tfplan + ``` + Review the plan carefully to ensure it matches your expectations. + +5. **Apply the Configuration**: + Apply the changes required to reach the desired state of the configuration. + ```bash + tofu apply tfplan \ + -backend-config="bucket=document-extractor-dev-opentofu-state" \ + -backend-config="key=document-extractor/terraform.tfstate" \ + -backend-config="region=us-west-1" \ + -backend-config="dynamodb_table=terraform-locks-dev" \ + -backend-config="encrypt=true" \ + -backend-config="profile=AWSAdministratorAccess-328307993388" + ``` + OpenTofu will prompt for confirmation before proceeding. Type `yes` to approve. + +Once the apply command completes, your infrastructure will be deployed. + +## Backend Configuration + +Your `iac/main.tf` file already contains the S3 backend configuration that OpenTofu will use: + +```terraform +terraform { + backend "s3" { + bucket = "document-extractor-dev-opentofu-state" + key = "document-extractor/terraform.tfstate" # This will be the path to the state file within the bucket + region = "us-west-1" + dynamodb_table = "terraform-locks-dev" + encrypt = true + profile = "AWSAdministratorAccess-328307993388" + } +} +``` +OpenTofu is designed to be compatible with Terraform state files. Since you've already torn down the infrastructure with Terraform, OpenTofu will create a new state file in the specified S3 bucket and key if one doesn't exist, or use an existing one if present (though it should be empty or reflect a destroyed state). + +## Destroying Infrastructure + +If you need to tear down the infrastructure deployed by OpenTofu: + +1. **Create a Destroy Plan**: + ```bash + tofu plan -destroy -out=tfdestroyplan + ``` + Review the plan to see what resources will be destroyed. + +2. **Destroy the Infrastructure**: + ```bash + tofu apply tfdestroyplan + ``` + Alternatively, you can run `tofu destroy` and OpenTofu will generate a plan and ask for confirmation. + + OpenTofu will prompt for confirmation before proceeding. Type `yes` to approve. + +This should guide you through deploying and managing your infrastructure with OpenTofu! + +## Troubleshooting + +### Secrets Manager: "secret with this name is already scheduled for deletion" + +If your `tofu apply` command fails with an error similar to: + +``` +Error: creating Secrets Manager Secret (example-secret-name): ... InvalidRequestException: You can't create this secret because a secret with this name is already scheduled for deletion. +``` + +This means that secrets with the same names as those defined in your `secrets.tf` (e.g., `document-extractor-dev-private-key`, `document-extractor-dev-public-key`, etc.) were recently deleted and are currently in a recovery window (typically 7-30 days). AWS Secrets Manager prevents the creation of a new secret with the same name during this period. + +You have a few options to resolve this: + +**Option 1: Wait for the Recovery Window to Expire** + +* The simplest option is to wait until the recovery window for the deleted secrets has passed. After this period, the secret names will become available again, and `tofu apply` should succeed. + +**Option 2: Force Delete the Secrets (Use with Caution)** + +* If you are certain you do not need to recover the old secret values, you can force delete them from AWS Secrets Manager. This will make the names available immediately. + * **Using AWS Management Console**: + 1. Navigate to AWS Secrets Manager in the AWS console. + 2. Find the secrets that are scheduled for deletion (they might have a status indicating this). + 3. Select the secret and look for an option to permanently delete it or modify the deletion schedule to delete it immediately. The exact steps might vary slightly depending on the console interface. + * **Using AWS CLI**: + You can use the `aws secretsmanager delete-secret` command with the `--force-delete-without-recovery` flag. You'll first need to find the Secret ARN or name. + + * **To list all secrets, including those pending deletion, to find the correct name or ARN:** + ```bash + aws secretsmanager list-secrets --include-pending-deletion --region us-west-1 --profile AWSAdministratorAccess-328307993388 --output json + ``` + Review the output. Look for secrets with a `DeletionDate` field; these are the ones scheduled for deletion. Note the `Name` or `ARN` of the secrets you intend to manage. + + * **To force delete a secret:** + ```bash + # Example for one secret (repeat for all problematic secrets): + aws secretsmanager delete-secret --secret-id arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-private-key-XXXXXX --force-delete-without-recovery --region us-west-1 --profile AWSAdministratorAccess-328307993388 + # Or by name (if it's unique and you're sure it's the correct one scheduled for deletion) + # aws secretsmanager delete-secret --secret-id document-extractor-dev-private-key --force-delete-without-recovery --region us-west-1 --profile AWSAdministratorAccess-328307993388 + ``` + **Important**: Replace `us-west-1` with your actual region if different, and `AWSAdministratorAccess-328307993388` with your AWS CLI profile. The `-XXXXXX` part of the ARN is a placeholder for the unique suffix AWS adds to secret ARNs; you'd typically use the full name if it's in a scheduled deletion state without the suffix, or find the exact ARN. + +**Option 3: Change Secret Names in Configuration** + +* If you want to proceed immediately without waiting or force-deleting, you can modify your `iac/secrets.tf` file to use different names for the secrets. For example, append a suffix: + ```terraform + resource "aws_secretsmanager_secret" "private_key" { + name = "document-extractor-dev-private-key-v2" + # ... other attributes + } + // Repeat for other secrets + ``` + After changing the names, run `tofu plan -out=tfplan` and `tofu apply tfplan` again. + +Choose the option that best suits your needs. After resolving the secret name conflict, you should be able to successfully apply your OpenTofu configuration. diff --git a/docs/run-it.md b/docs/run-it.md new file mode 100644 index 0000000..d33d0d2 --- /dev/null +++ b/docs/run-it.md @@ -0,0 +1,260 @@ +# Deployment Guide + +This guide will help you deploy the Document Extractor POC to a fresh AWS account. + +## Prerequisites + +- AWS account with admin access +- AWS CLI installed and configured with admin credentials +- Terraform (v1.0.0 or later) +- Python 3.13 or later +- [uv](https://docs.astral.sh/uv/) Python package manager + - Node.js and npm (for frontend deployment) +- Git (for cloning the repository) + +## 1. Repository Setup + +1. Clone the repository: + ```bash + git clone <repository-url> + cd document-extractor-poc + ``` + +2. Navigate to the infrastructure directory: + ```bash + cd iac + ``` + +## 2. Understanding Terraform State Management (For Beginners) + +### What is Terraform State? +Terraform uses a state file (`.tfstate`) to track what resources it has created in your AWS account. This file is crucial - it's how Terraform knows what it's managing. + +### Where to Store the State File +For a fresh deployment, you need to create an S3 bucket to store this state file: + +1. **Create a state bucket** in your AWS account: + - Go to the AWS console → S3 → Create bucket + - Name it something like `document-extractor-dev-opentofu-state` + - Keep default settings and create the bucket + +2. **Create a DynamoDB table** for state locking: + - Go to AWS console → DynamoDB → Create table + - Name it something like `terraform-locks-dev` + - Primary key: `LockID` (type String) + - Use default settings and create table + +3. **Update your backend configuration** in `iac/main.tf`: + ```hcl + terraform { + backend "s3" { + bucket = "document-extractor-dev-opentofu-state" # Replace with your bucket name + key = "document-extractor/terraform.tfstate" + region = "us-east-1" + dynamodb_table = "terraform-locks-dev" # The DynamoDB table you created + encrypt = true + } + } + ``` + +> **Note**: You only need to set this up once. Terraform will create and manage the state file for you after this. + +## 3. Setting Up Your Variables + +Terraform needs some information from you to deploy properly. Create a file named `terraform.tfvars` in the `iac` directory: + +```hcl +# Required: Choose a deployment environment name +environment = "dev" # Use a simple name like "dev", "test", or "prod" + +# Optional: Change AWS region if needed (defaults to "us-east-1" if not specified) +region = "us-east-1" + +# Advanced: Required, but can be empty if not using custom form adapters +textract_form_adapters_env_var_mapping = {} +``` + +> **What these variables do**: +> - `environment`: Adds a suffix to resource names to separate different deployments (e.g., dev vs. prod) +> - `region`: Which AWS region to deploy to (us-east-1 is AWS's N. Virginia region) +> - `textract_form_adapters_env_var_mapping`: Required variable for form adapter configurations. Can be set to an empty map (`{}`) if you're not using custom form adapters. + +## 4. Build the Backend + +1. Navigate to the backend directory: + ```bash + cd ../backend + ``` + +2. Set up the Python environment: + ```bash + uv sync + ``` + +3. Build the Lambda deployment package: + ```bash + uv run build.py + ``` + + This will create the artifact at `backend/dist/lambda.zip`. + +## 5. Build the Frontend + +1. Navigate to the frontend directory: + ```bash + cd ../ui + ``` + +2. Install dependencies: + ```bash + npm ci + ``` + +3. Build the frontend: + ```bash + npm run build + ``` + + This will create the build artifacts in the `ui/dist/` directory. + +## 6. Deploy Infrastructure with Terraform + +1. Navigate back to the infrastructure directory: + ```bash + cd ../iac + ``` + +2. Initialize Terraform (downloads required providers and sets up your backend): + ```bash + AWS_PROFILE=AWSAdministratorAccess-328307993388 terraform init + ``` + > If you get an error about the backend configuration, double-check that you've created the S3 bucket and DynamoDB table, and that your AWS credentials have permission to access them. + +3. See what Terraform will create (this doesn't make any changes yet): + ```bash + AWS_PROFILE=AWSAdministratorAccess-328307993388 terraform plan -out=tfplan + ``` + > This step will show you all the AWS resources that will be created. It might look overwhelming, but you don't need to understand every detail. + +4. Create the actual resources in AWS: + ```bash + AWS_PROFILE=AWSAdministratorAccess-328307993388 terraform apply "tfplan" + ``` + > This step will take 5-10 minutes to complete. When finished, it will output important information like the CloudFront URL that you'll use to access your application. + +5. Save the outputs somewhere safe - you'll need them later! + + > **Important**: These resources will cost money in your AWS account as long as they exist. See the Cleanup section for how to remove them when you're done. + +## 7. Post-Deployment Steps + +### Authentication Setup (Required) + +1. Generate an RSA private key in PEM format: + ```bash + openssl genrsa -out private-key.pem 2048 + ``` + +2. Upload it to AWS Secrets Manager with `private-key` in the name: + ```bash + AWS_PROFILE=AWSAdministratorAccess-328307993388 aws secretsmanager create-secret --name private-key --secret-string file://private-key.pem + ``` + +3. Generate a public key from the private key: + ```bash + openssl rsa -in private-key.pem -pubout -out public-key.pem + ``` + +4. Upload the public key to AWS Secrets Manager: + ```bash + AWS_PROFILE=AWSAdministratorAccess-328307993388 aws secretsmanager create-secret --name public-key --secret-string file://public-key.pem + ``` + +5. Choose a username and upload it to Secrets Manager: + ```bash + AWS_PROFILE=AWSAdministratorAccess-328307993388 aws secretsmanager create-secret --name username --secret-string "hugo-testing-testing" + ``` + +6. Generate a hashed password: + ```bash + cd ./backend/ + echo 'import bcrypt;print(bcrypt.hashpw(b"your_strong_password", bcrypt.gensalt()).decode())' | uv run - + ``` + +7. Upload the hashed password to Secrets Manager: + ```bash + AWS_PROFILE=AWSAdministratorAccess-328307993388 aws secretsmanager create-secret --name password --secret-string "$2b$12$rumyUnUwF9NvnWaCkHRDYOeQCyn22QP6LgbR4GEEGkHsr3urD9Bau" + ``` + +# correct commands code expects: +AWS_PROFILE=AWSAdministratorAccess-328307993388 aws secretsmanager create-secret --name document-extractor-dev-username --secret-string "hugo-testing-testing" + +AWS_PROFILE=AWSAdministratorAccess-328307993388 aws secretsmanager create-secret --name document-extractor-dev-password --secret-string "$2b$12$rumyUnUwF9NvnWaCkHRDYOeQCyn22QP6LgbR4GEEGkHsr3urD9Bau" + +AWS_PROFILE=AWSAdministratorAccess-328307993388 aws secretsmanager create-secret --name document-extractor-dev-private-key --secret-string file://private-key.pem + +AWS_PROFILE=AWSAdministratorAccess-328307993388 aws secretsmanager create-secret --name document-extractor-dev-public-key --secret-string file://public-key.pem + +## 8. Additional Post-Deployment Steps + +1. **Set up DNS**: + - Create a Route 53 hosted zone for your domain + - Add an A record pointing to the CloudFront distribution + - Update the CloudFront distribution with your custom domain and SSL certificate + +2. **Configure Environment Variables**: + - Set any necessary environment variables in AWS Lambda and other services + - Verify all secrets in AWS Secrets Manager + +3. **Monitoring and Logging**: + - Set up CloudWatch Alarms for critical metrics + - Configure log retention policies + +## 9. Accessing the Application + +After deployment, you can access the application via: +- The CloudFront distribution URL (found in AWS Console or Terraform outputs) +- Your custom domain (if configured) + +You will need to use the username and password you configured in the authentication setup. + +## 10. Cleanup (When Needed) + +To remove all deployed resources: + +```bash +cd iac +terraform destroy +``` + +## Troubleshooting + +- **Terraform State Locking Issues**: + - If you encounter state locking issues, you may need to manually remove the lock from the DynamoDB table + +- **Permission Issues**: + - Ensure your AWS credentials have sufficient permissions + - Check CloudTrail for any denied API calls + +- **Frontend Not Updating**: + - Clear CloudFront cache + - Verify S3 bucket policies and CloudFront origin settings + +## Support + +For additional help, please refer to the project documentation or open an issue in the repository. + + + + +==== notes on public private cloudfront +Added secure private access: + +cloudfront.tf • Introduced aws_cloudfront_origin_access_control (SigV4). +• S3 origin now references bucket_regional_domain_name, attaches the new OAC via origin_access_control_id, and uses s3_origin_config. +• Added default_root_object = "index.html". +s3.tf • Replaced permissive resources with private ones: +– New aws_s3_bucket_public_access_block.private_website keeps public blocked while allowing CloudFront. +– New bucket-policy (website_read) built from data.aws_iam_policy_document.cf_read, which permits s3:GetObject only when the caller is CloudFront and the request’s SourceArn equals the distribution ARN. +• Old public-allowing block/policy resources are commented for removal. +Now only CloudFront, via Origin Access Control, can read objects; the bucket is otherwise private. diff --git a/docs/terraform-destory-output-5-29.txt b/docs/terraform-destory-output-5-29.txt new file mode 100644 index 0000000..d85cc9c --- /dev/null +++ b/docs/terraform-destory-output-5-29.txt @@ -0,0 +1,5233 @@ +aws_secretsmanager_secret.username: Refreshing state... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-username-gKcWPC] +aws_secretsmanager_secret.password: Refreshing state... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-password-5KgJiD] +data.aws_iam_policy_document.secrets_lambda_policy: Reading... +aws_secretsmanager_secret.private_key: Refreshing state... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-private-key-PI2EsC] +aws_api_gateway_rest_api.api: Refreshing state... [id=ic9avcgf3l] +data.aws_iam_policy.lambda_basic_execution: Reading... +aws_kms_key.encryption: Refreshing state... [id=2c84ed0b-7d29-4d92-ab32-f0db5557a13c] +aws_secretsmanager_secret.public_key: Refreshing state... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-public-key-PI2EsC] +aws_cloudfront_origin_access_control.oac: Refreshing state... [id=E5E4NY4IQRCTR] +data.aws_caller_identity.current: Reading... +data.aws_iam_policy_document.secrets_lambda_policy: Read complete after 0s [id=2856285183] +aws_dynamodb_table.extract_table: Refreshing state... [id=document-extractor-dev-text-extract] +data.aws_caller_identity.current: Read complete after 0s [id=328307993388] +data.aws_iam_policy.lambda_textract_execution: Reading... +aws_cloudfront_function.rewrite_uri: Refreshing state... [id=document-extractor-dev-rewrite-request] +aws_iam_policy.secrets_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy] +data.aws_iam_policy_document.assume_role: Reading... +data.aws_iam_policy_document.assume_role: Read complete after 0s [id=2690255455] +aws_s3_bucket.website_storage: Refreshing state... [id=document-extractor-dev-website-328307993388] +aws_iam_role.execution_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role] +aws_s3_bucket.document_storage: Refreshing state... [id=document-extractor-dev-documents-328307993388] +module.document_endpoints.data.aws_api_gateway_rest_api.api: Reading... +module.token_endpoints.data.aws_api_gateway_rest_api.api: Reading... +module.document_id_endpoints.data.aws_api_gateway_rest_api.api: Reading... +data.aws_iam_policy_document.dynamodb_lambda_policy: Reading... +data.aws_iam_policy_document.dynamodb_lambda_policy: Read complete after 0s [id=2781330629] +data.aws_iam_policy_document.kms_lambda_policy: Reading... +data.aws_iam_policy_document.kms_lambda_policy: Read complete after 0s [id=2825613304] +aws_sqs_queue.queue_to_dynamo: Refreshing state... [id=https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb] +aws_iam_policy.dynamodb_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy] +aws_iam_policy.kms_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy] +module.document_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 0s [id=ic9avcgf3l] +aws_lambda_function.authorizer: Refreshing state... [id=document-extractor-dev-authorizer] +module.token_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 0s [id=ic9avcgf3l] +aws_iam_role_policy_attachment.attach_secrets_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153044573200000005] +module.document_id_endpoints.data.aws_api_gateway_rest_api.api: Read complete after 0s [id=ic9avcgf3l] +aws_iam_role_policy_attachment.attach_dynamodb_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153115634300000009] +module.document_endpoints.aws_api_gateway_resource.resource_path: Refreshing state... [id=ljz2z9] +module.token_endpoints.aws_lambda_function.function[0]: Refreshing state... [id=document-extractor-dev-token] +module.token_endpoints.aws_api_gateway_resource.resource_path: Refreshing state... [id=dmuib4] +aws_iam_role_policy_attachment.attach_kms_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-2025052215311566550000000a] +module.document_id_endpoints.aws_api_gateway_resource.resource_path: Refreshing state... [id=g3ywx2] +module.token_endpoints.aws_api_gateway_method.http_method[0]: Refreshing state... [id=agm-ic9avcgf3l-dmuib4-POST] +data.aws_iam_policy_document.sqs_lambda_policy: Reading... +data.aws_iam_policy_document.sqs_lambda_policy: Read complete after 0s [id=1807487604] +aws_lambda_function.write_to_dynamodb: Refreshing state... [id=document-extractor-dev-write-to-dynamodb] +aws_lambda_function.text_extract: Refreshing state... [id=document-extractor-dev-text-extract] +aws_iam_policy.sqs_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy] +data.aws_iam_policy.lambda_textract_execution: Read complete after 1s [id=arn:aws:iam::aws:policy/AmazonTextractFullAccess] +aws_iam_role_policy_attachment.attach_textract_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153044578100000006] +aws_iam_role_policy_attachment.attach_sqs_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-2025052215314877690000000b] +data.aws_iam_policy.lambda_basic_execution: Read complete after 1s [id=arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole] +aws_iam_role_policy_attachment.attach_basic_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153044909700000007] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Refreshing state... [id=document-extractor-dev-token,6] +module.token_endpoints.aws_lambda_permission.api_gateway[0]: Refreshing state... [id=AllowExecutionFromApiGateway] +module.token_endpoints.aws_api_gateway_integration.lambda_integration[0]: Refreshing state... [id=agi-ic9avcgf3l-dmuib4-POST] +aws_lambda_permission.api_gateway_invoke_authorizer: Refreshing state... [id=AllowExecutionFromApiGateway] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Refreshing state... [id=document-extractor-dev-authorizer,6] +aws_api_gateway_authorizer.authorizer: Refreshing state... [id=bq0hho] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Refreshing state... [id=document-extractor-dev-write-to-dynamodb,6] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Refreshing state... [id=document-extractor-dev-text-extract,6] +aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: Refreshing state... [id=b32111fd-5cd6-45fd-b193-ac415949bc48] +module.document_endpoints.aws_api_gateway_method.http_method[0]: Refreshing state... [id=agm-ic9avcgf3l-ljz2z9-POST] +module.document_id_endpoints.aws_api_gateway_method.http_method[0]: Refreshing state... [id=agm-ic9avcgf3l-g3ywx2-GET] +module.document_id_endpoints.aws_api_gateway_method.http_method[1]: Refreshing state... [id=agm-ic9avcgf3l-g3ywx2-PUT] +aws_lambda_permission.allow_bucket_invoke: Refreshing state... [id=AllowExecutionFromS3Bucket] +data.aws_iam_policy_document.s3_lambda_policy: Reading... +data.aws_iam_policy_document.s3_lambda_policy: Read complete after 0s [id=1929687036] +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Refreshing state... [id=document-extractor-dev-documents-328307993388] +module.document_id_endpoints.aws_lambda_function.function[0]: Refreshing state... [id=document-extractor-dev-get-document] +module.document_id_endpoints.aws_lambda_function.function[1]: Refreshing state... [id=document-extractor-dev-update-document] +module.document_endpoints.aws_lambda_function.function[0]: Refreshing state... [id=document-extractor-dev-create-document] +aws_iam_policy.s3_lambda_policy: Refreshing state... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy] +aws_s3_bucket_public_access_block.private_website: Refreshing state... [id=document-extractor-dev-website-328307993388] +aws_s3_bucket_versioning.website_storage_versioning: Refreshing state... [id=document-extractor-dev-website-328307993388] +aws_s3_bucket_website_configuration.website_configuration: Refreshing state... [id=document-extractor-dev-website-328307993388] +aws_s3_object.website_files["ui.d119b665.js.map"]: Refreshing state... [id=ui.d119b665.js.map] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.e58256dc.woff2"]: Refreshing state... [id=Latin-Merriweather-BoldItalic.e58256dc.woff2] +aws_s3_object.website_files["us_flag_small.e642a40d.png"]: Refreshing state... [id=us_flag_small.e642a40d.png] +aws_s3_object.website_files["arrow_back.e4913c69.svg"]: Refreshing state... [id=arrow_back.e4913c69.svg] +aws_s3_object.website_files["file.057d5652.svg"]: Refreshing state... [id=file.057d5652.svg] +aws_s3_object.website_files["us_flag_small.9c3c6ab8.png"]: Refreshing state... [id=us_flag_small.9c3c6ab8.png] +aws_s3_object.website_files["sourcesanspro-italic-webfont.ab90d6d1.woff2"]: Refreshing state... [id=sourcesanspro-italic-webfont.ab90d6d1.woff2] +aws_s3_object.website_files["icon-dot-gov.1cc2b2c5.svg"]: Refreshing state... [id=icon-dot-gov.1cc2b2c5.svg] +aws_s3_object.website_files["ui.4e22b301.js.map"]: Refreshing state... [id=ui.4e22b301.js.map] +aws_s3_object.website_files["ui.7c42bb32.css.map"]: Refreshing state... [id=ui.7c42bb32.css.map] +aws_s3_object.website_files["ui.383343a4.js.map"]: Refreshing state... [id=ui.383343a4.js.map] +aws_s3_object.website_files["unfold_more.1f608602.svg"]: Refreshing state... [id=unfold_more.1f608602.svg] +aws_s3_object.website_files["navigate_far_before.904e4c63.svg"]: Refreshing state... [id=navigate_far_before.904e4c63.svg] +aws_s3_object.website_files["ui.ee3dfc51.js"]: Refreshing state... [id=ui.ee3dfc51.js] +aws_s3_object.website_files["expand_more.3ac36c78.svg"]: Refreshing state... [id=expand_more.3ac36c78.svg] +aws_s3_object.website_files["Latin-Merriweather-Light.1ad1a1d8.woff2"]: Refreshing state... [id=Latin-Merriweather-Light.1ad1a1d8.woff2] +aws_s3_object.website_files["ui.31b563d9.js"]: Refreshing state... [id=ui.31b563d9.js] +aws_s3_object.website_files["roboto-mono-v5-latin-regular.e048516f.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-regular.e048516f.woff2] +aws_s3_object.website_files["search.a724f1bc.svg"]: Refreshing state... [id=search.a724f1bc.svg] +aws_s3_object.website_files["expand_less.5bb6de10.svg"]: Refreshing state... [id=expand_less.5bb6de10.svg] +aws_s3_object.website_files["ui.b82c2ba3.js"]: Refreshing state... [id=ui.b82c2ba3.js] +aws_s3_object.website_files["hero.abe074f7.jpg"]: Refreshing state... [id=hero.abe074f7.jpg] +aws_s3_object.website_files["navigate_before.be211c2e.svg"]: Refreshing state... [id=navigate_before.be211c2e.svg] +aws_s3_object.website_files["Latin-Merriweather-Light.fbc4393c.woff2"]: Refreshing state... [id=Latin-Merriweather-Light.fbc4393c.woff2] +aws_s3_object.website_files["file-pdf.51e66388.svg"]: Refreshing state... [id=file-pdf.51e66388.svg] +aws_s3_object.website_files["navigate_next.fb4be146.svg"]: Refreshing state... [id=navigate_next.fb4be146.svg] +aws_s3_object.website_files["warning.4cc8d763.svg"]: Refreshing state... [id=warning.4cc8d763.svg] +aws_s3_object.website_files["checkbox-indeterminate.08aa88cd.svg"]: Refreshing state... [id=checkbox-indeterminate.08aa88cd.svg] +aws_s3_object.website_files["close.3962dfa0.svg"]: Refreshing state... [id=close.3962dfa0.svg] +aws_s3_object.website_files["remove.ca324d27.svg"]: Refreshing state... [id=remove.ca324d27.svg] +aws_s3_object.website_files["unfold_more.46c00529.svg"]: Refreshing state... [id=unfold_more.46c00529.svg] +aws_s3_object.website_files["info.40b16694.svg"]: Refreshing state... [id=info.40b16694.svg] +aws_s3_object.website_files["error--white.dcc8cafd.svg"]: Refreshing state... [id=error--white.dcc8cafd.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-italic.7226ad2c.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-italic.7226ad2c.woff2] +aws_s3_object.website_files["checkbox-indeterminate.db2c5d96.svg"]: Refreshing state... [id=checkbox-indeterminate.db2c5d96.svg] +aws_s3_object.website_files["ui.4540ed92.js"]: Refreshing state... [id=ui.4540ed92.js] +aws_s3_object.website_files["navigate_far_next.bd2c92a4.svg"]: Refreshing state... [id=navigate_far_next.bd2c92a4.svg] +aws_s3_object.website_files["correct8-alt.c14d1430.svg"]: Refreshing state... [id=correct8-alt.c14d1430.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-regular.ec7832e5.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-regular.ec7832e5.woff2] +aws_s3_object.website_files["checkbox-indeterminate-alt.5e7abfcf.svg"]: Refreshing state... [id=checkbox-indeterminate-alt.5e7abfcf.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.1710c37a.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-300italic.1710c37a.woff2] +aws_s3_object.website_files["close.bf51193b.svg"]: Refreshing state... [id=close.bf51193b.svg] +aws_s3_object.website_files["sourcesanspro-regular-webfont.a9d6b459.woff2"]: Refreshing state... [id=sourcesanspro-regular-webfont.a9d6b459.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-300.98978b08.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-300.98978b08.woff2] +aws_s3_object.website_files["calendar_today.3a803a7a.svg"]: Refreshing state... [id=calendar_today.3a803a7a.svg] +aws_s3_object.website_files["file-video.a3a38638.svg"]: Refreshing state... [id=file-video.a3a38638.svg] +aws_s3_object.website_files["ui.383343a4.js"]: Refreshing state... [id=ui.383343a4.js] +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.b61b42d2.woff2"]: Refreshing state... [id=sourcesanspro-bolditalic-webfont.b61b42d2.woff2] +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.05b42991.woff2"]: Refreshing state... [id=sourcesanspro-lightitalic-webfont.05b42991.woff2] +aws_s3_object.website_files["Latin-Merriweather-Regular.66d4c170.woff2"]: Refreshing state... [id=Latin-Merriweather-Regular.66d4c170.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.af4ea91a.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-300italic.af4ea91a.woff2] +aws_s3_object.website_files["ui.9f367d21.css.map"]: Refreshing state... [id=ui.9f367d21.css.map] +aws_s3_object.website_files["file.f51e21d7.svg"]: Refreshing state... [id=file.f51e21d7.svg] +aws_s3_object.website_files["GSA-logo.7a2abfc3.svg"]: Refreshing state... [id=GSA-logo.7a2abfc3.svg] +aws_s3_object.website_files["calendar_today.ce6eaa81.svg"]: Refreshing state... [id=calendar_today.ce6eaa81.svg] +aws_s3_object.website_files["ui.4e22b301.js"]: Refreshing state... [id=ui.4e22b301.js] +aws_s3_object.website_files["icon-https.b6eca6f6.svg"]: Refreshing state... [id=icon-https.b6eca6f6.svg] +aws_s3_object.website_files["sourcesanspro-bold-webfont.5436602a.woff2"]: Refreshing state... [id=sourcesanspro-bold-webfont.5436602a.woff2] +aws_s3_object.website_files["Latin-Merriweather-Italic.e2716ed3.woff2"]: Refreshing state... [id=Latin-Merriweather-Italic.e2716ed3.woff2] +aws_s3_object.website_files["ui.ee3dfc51.js.map"]: Refreshing state... [id=ui.ee3dfc51.js.map] +aws_s3_object.website_files["loader.7402c183.svg"]: Refreshing state... [id=loader.7402c183.svg] +aws_s3_object.website_files["navigate_next.bc7c1a4e.svg"]: Refreshing state... [id=navigate_next.bc7c1a4e.svg] +aws_s3_object.website_files["pdf.worker.min.mjs"]: Refreshing state... [id=pdf.worker.min.mjs] +aws_s3_object.website_files["check_circle.a3900be5.svg"]: Refreshing state... [id=check_circle.a3900be5.svg] +aws_s3_object.website_files["file-excel.323d5d7b.svg"]: Refreshing state... [id=file-excel.323d5d7b.svg] +aws_s3_object.website_files["GSA-logo.ece32ed3.svg"]: Refreshing state... [id=GSA-logo.ece32ed3.svg] +aws_s3_object.website_files["ui.394b89ad.css.map"]: Refreshing state... [id=ui.394b89ad.css.map] +aws_s3_object.website_files["expand_less.e2846957.svg"]: Refreshing state... [id=expand_less.e2846957.svg] +aws_s3_object.website_files["ui.394b89ad.css"]: Refreshing state... [id=ui.394b89ad.css] +aws_s3_object.website_files["launch--white.6c179f60.svg"]: Refreshing state... [id=launch--white.6c179f60.svg] +aws_s3_object.website_files["file-word.896caaf0.svg"]: Refreshing state... [id=file-word.896caaf0.svg] +aws_s3_object.website_files["error.1986e136.svg"]: Refreshing state... [id=error.1986e136.svg] +aws_s3_object.website_files["file-excel.c90d2036.svg"]: Refreshing state... [id=file-excel.c90d2036.svg] +aws_s3_object.website_files["icon-https.3b0ffef2.svg"]: Refreshing state... [id=icon-https.3b0ffef2.svg] +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.c14df9bc.woff2"]: Refreshing state... [id=sourcesanspro-lightitalic-webfont.c14df9bc.woff2] +aws_s3_object.website_files["Latin-Merriweather-Regular.04046ce8.woff2"]: Refreshing state... [id=Latin-Merriweather-Regular.04046ce8.woff2] +aws_s3_object.website_files["error.35f61f4b.svg"]: Refreshing state... [id=error.35f61f4b.svg] +aws_s3_object.website_files["warning.ce0fedc9.svg"]: Refreshing state... [id=warning.ce0fedc9.svg] +aws_s3_object.website_files["file-pdf.248e68e5.svg"]: Refreshing state... [id=file-pdf.248e68e5.svg] +aws_s3_object.website_files["ui.b82c2ba3.js.map"]: Refreshing state... [id=ui.b82c2ba3.js.map] +aws_s3_object.website_files["sourcesanspro-italic-webfont.82ff76d1.woff2"]: Refreshing state... [id=sourcesanspro-italic-webfont.82ff76d1.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-300.40a89411.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-300.40a89411.woff2] +aws_s3_object.website_files["icon-dot-gov.86228b6b.svg"]: Refreshing state... [id=icon-dot-gov.86228b6b.svg] +aws_s3_object.website_files["ui.9f367d21.css"]: Refreshing state... [id=ui.9f367d21.css] +aws_s3_object.website_files["add.d491396d.svg"]: Refreshing state... [id=add.d491396d.svg] +aws_s3_object.website_files["ui.4540ed92.js.map"]: Refreshing state... [id=ui.4540ed92.js.map] +aws_s3_object.website_files["checkbox-indeterminate-alt.a6b9b5ac.svg"]: Refreshing state... [id=checkbox-indeterminate-alt.a6b9b5ac.svg] +aws_s3_object.website_files["Latin-Merriweather-LightItalic.f4af3b7f.woff2"]: Refreshing state... [id=Latin-Merriweather-LightItalic.f4af3b7f.woff2] +aws_s3_object.website_files["ui.24de9d80.js.map"]: Refreshing state... [id=ui.24de9d80.js.map] +aws_s3_object.website_files["error--white.73d13870.svg"]: Refreshing state... [id=error--white.73d13870.svg] +aws_s3_object.website_files["navigate_far_next.37ce8b6e.svg"]: Refreshing state... [id=navigate_far_next.37ce8b6e.svg] +aws_s3_object.website_files["sourcesanspro-bold-webfont.21f8979c.woff2"]: Refreshing state... [id=sourcesanspro-bold-webfont.21f8979c.woff2] +aws_s3_object.website_files["hero.3538e009.jpg"]: Refreshing state... [id=hero.3538e009.jpg] +aws_s3_object.website_files["index.html"]: Refreshing state... [id=index.html] +aws_s3_object.website_files["file-video.d309c165.svg"]: Refreshing state... [id=file-video.d309c165.svg] +aws_s3_object.website_files["ui.d119b665.js"]: Refreshing state... [id=ui.d119b665.js] +aws_s3_object.website_files["arrow_back.c9ac1a0e.svg"]: Refreshing state... [id=arrow_back.c9ac1a0e.svg] +aws_s3_object.website_files["sourcesanspro-light-webfont.e490d910.woff2"]: Refreshing state... [id=sourcesanspro-light-webfont.e490d910.woff2] +aws_s3_object.website_files["remove.27bfc20f.svg"]: Refreshing state... [id=remove.27bfc20f.svg] +aws_s3_object.website_files["sourcesanspro-regular-webfont.76c0bde6.woff2"]: Refreshing state... [id=sourcesanspro-regular-webfont.76c0bde6.woff2] +aws_s3_object.website_files["correct8-alt.8f8dac23.svg"]: Refreshing state... [id=correct8-alt.8f8dac23.svg] +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.84e64ba8.woff2"]: Refreshing state... [id=sourcesanspro-bolditalic-webfont.84e64ba8.woff2] +aws_s3_object.website_files["info.85fe97af.svg"]: Refreshing state... [id=info.85fe97af.svg] +aws_s3_object.website_files["launch.f4de218c.svg"]: Refreshing state... [id=launch.f4de218c.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-700.df366d23.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-700.df366d23.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.03671595.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-700italic.03671595.woff2] +aws_s3_object.website_files["launch.2cde378c.svg"]: Refreshing state... [id=launch.2cde378c.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-700.d309a69d.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-700.d309a69d.woff2] +aws_s3_object.website_files["sourcesanspro-light-webfont.e70d4904.woff2"]: Refreshing state... [id=sourcesanspro-light-webfont.e70d4904.woff2] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.32dc0025.woff2"]: Refreshing state... [id=Latin-Merriweather-BoldItalic.32dc0025.woff2] +aws_s3_object.website_files["Latin-Merriweather-Bold.cdbf7e95.woff2"]: Refreshing state... [id=Latin-Merriweather-Bold.cdbf7e95.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-italic.9209c633.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-italic.9209c633.woff2] +aws_s3_object.website_files["expand_more.0d06b804.svg"]: Refreshing state... [id=expand_more.0d06b804.svg] +aws_s3_object.website_files["Latin-Merriweather-Italic.959c3872.woff2"]: Refreshing state... [id=Latin-Merriweather-Italic.959c3872.woff2] +aws_s3_object.website_files["ui.24de9d80.js"]: Refreshing state... [id=ui.24de9d80.js] +aws_s3_object.website_files["ui.31b563d9.js.map"]: Refreshing state... [id=ui.31b563d9.js.map] +aws_s3_object.website_files["navigate_before.7d672c1a.svg"]: Refreshing state... [id=navigate_before.7d672c1a.svg] +aws_s3_object.website_files["check--blue-60v.7604a0f6.svg"]: Refreshing state... [id=check--blue-60v.7604a0f6.svg] +aws_s3_object.website_files["check--blue-60v.982d9f95.svg"]: Refreshing state... [id=check--blue-60v.982d9f95.svg] +aws_s3_object.website_files["Latin-Merriweather-Bold.967b2108.woff2"]: Refreshing state... [id=Latin-Merriweather-Bold.967b2108.woff2] +aws_s3_object.website_files["add.29db0c6a.svg"]: Refreshing state... [id=add.29db0c6a.svg] +aws_s3_object.website_files["file-word.75e83f68.svg"]: Refreshing state... [id=file-word.75e83f68.svg] +aws_s3_object.website_files["launch--white.626c08f5.svg"]: Refreshing state... [id=launch--white.626c08f5.svg] +aws_s3_object.website_files["Latin-Merriweather-LightItalic.1c9df738.woff2"]: Refreshing state... [id=Latin-Merriweather-LightItalic.1c9df738.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.2b983f5d.woff2"]: Refreshing state... [id=roboto-mono-v5-latin-700italic.2b983f5d.woff2] +aws_s3_object.website_files["navigate_far_before.65b60266.svg"]: Refreshing state... [id=navigate_far_before.65b60266.svg] +aws_s3_object.website_files["check_circle.120511e4.svg"]: Refreshing state... [id=check_circle.120511e4.svg] +aws_s3_object.website_files["correct8.10e8a7ef.svg"]: Refreshing state... [id=correct8.10e8a7ef.svg] +aws_s3_object.website_files["ui.7c42bb32.css"]: Refreshing state... [id=ui.7c42bb32.css] +aws_s3_object.website_files["correct8.2dc455b8.svg"]: Refreshing state... [id=correct8.2dc455b8.svg] +aws_s3_object.website_files["search.e5ee4fc2.svg"]: Refreshing state... [id=search.e5ee4fc2.svg] +aws_s3_object.website_files["loader.e58cf242.svg"]: Refreshing state... [id=loader.e58cf242.svg] +aws_s3_bucket_notification.notify_on_input_data: Refreshing state... [id=document-extractor-dev-documents-328307993388] +aws_iam_role_policy_attachment.attach_s3_permission_to_role: Refreshing state... [id=document-extractor-dev-lambda-execution-role-20250522153114543600000008] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Refreshing state... [id=document-extractor-dev-create-document,6] +module.document_endpoints.aws_lambda_permission.api_gateway[0]: Refreshing state... [id=AllowExecutionFromApiGateway] +module.document_endpoints.aws_api_gateway_integration.lambda_integration[0]: Refreshing state... [id=agi-ic9avcgf3l-ljz2z9-POST] +module.document_id_endpoints.aws_lambda_permission.api_gateway[1]: Refreshing state... [id=AllowExecutionFromApiGateway] +module.document_id_endpoints.aws_lambda_permission.api_gateway[0]: Refreshing state... [id=AllowExecutionFromApiGateway] +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[0]: Refreshing state... [id=agi-ic9avcgf3l-g3ywx2-GET] +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[1]: Refreshing state... [id=agi-ic9avcgf3l-g3ywx2-PUT] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Refreshing state... [id=document-extractor-dev-get-document,6] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Refreshing state... [id=document-extractor-dev-update-document,6] +aws_api_gateway_deployment.api_deployment: Refreshing state... [id=ag6ofm] +aws_api_gateway_stage.stage: Refreshing state... [id=ags-ic9avcgf3l-v1] +aws_cloudfront_distribution.distribution: Refreshing state... [id=ENWTZLUASCDJZ] +null_resource.invalidate_cloudfront: Refreshing state... [id=450350717588313943] +data.aws_iam_policy_document.cf_read: Reading... +data.aws_iam_policy_document.cf_read: Read complete after 0s [id=3877928146] +aws_s3_bucket_policy.website_read: Refreshing state... [id=document-extractor-dev-website-328307993388] + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + - destroy + +Terraform will perform the following actions: + + # aws_api_gateway_authorizer.authorizer will be destroyed + - resource "aws_api_gateway_authorizer" "authorizer" { + - arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l/authorizers/bq0hho" -> null + - authorizer_result_ttl_in_seconds = 300 -> null + - authorizer_uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer/invocations" -> null + - id = "bq0hho" -> null + - identity_source = "method.request.header.Authorization" -> null + - name = "document-extractor-dev-authorizer" -> null + - provider_arns = [] -> null + - rest_api_id = "ic9avcgf3l" -> null + - type = "TOKEN" -> null + } + + # aws_api_gateway_deployment.api_deployment will be destroyed + - resource "aws_api_gateway_deployment" "api_deployment" { + - created_date = "2025-05-22T17:09:14Z" -> null + - execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/" -> null + - id = "ag6ofm" -> null + - invoke_url = "https://ic9avcgf3l.execute-api.us-west-1.amazonaws.com/" -> null + - rest_api_id = "ic9avcgf3l" -> null + - triggers = { + - "redeployment_for_document" = "df368280881120978a4c4656300fde0f324b8281bf99d7ba054eec9cc8bd6211f4e79278474c2d01aea853b44799e445dcaa6b21affb76afc9f42daabaa1b1f2" + - "redeployment_for_document_id" = "7dba33f74bbc4cd4db523d3160e633446810ebc4a2940bb1e800d0aafb37327407f2bd45b4e7eae6cd0fb5cf2b12ad4c605c11baf1ea64ee14f72c04ca4a4abf" + } -> null + } + + # aws_api_gateway_rest_api.api will be destroyed + - resource "aws_api_gateway_rest_api" "api" { + - api_key_source = "HEADER" -> null + - arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l" -> null + - binary_media_types = [] -> null + - created_date = "2025-05-22T15:30:42Z" -> null + - description = "document-extractor API" -> null + - disable_execute_api_endpoint = false -> null + - execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l" -> null + - id = "ic9avcgf3l" -> null + - name = "document-extractor-dev-api" -> null + - root_resource_id = "qdlfjof80i" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + + - endpoint_configuration { + - ip_address_type = "ipv4" -> null + - types = [ + - "EDGE", + ] -> null + - vpc_endpoint_ids = [] -> null + } + } + + # aws_api_gateway_stage.stage will be destroyed + - resource "aws_api_gateway_stage" "stage" { + - arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l/stages/v1" -> null + - cache_cluster_enabled = false -> null + - deployment_id = "ag6ofm" -> null + - execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/v1" -> null + - id = "ags-ic9avcgf3l-v1" -> null + - invoke_url = "https://ic9avcgf3l.execute-api.us-west-1.amazonaws.com/v1" -> null + - rest_api_id = "ic9avcgf3l" -> null + - stage_name = "v1" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - variables = {} -> null + - xray_tracing_enabled = false -> null + } + + # aws_cloudfront_distribution.distribution will be destroyed + - resource "aws_cloudfront_distribution" "distribution" { + - aliases = [] -> null + - arn = "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ" -> null + - caller_reference = "terraform-2025052215335454220000000c" -> null + - comment = "document-extractor dev website" -> null + - default_root_object = "index.html" -> null + - domain_name = "d1phjy5x6ttml2.cloudfront.net" -> null + - enabled = true -> null + - etag = "E4X7Q0PHSPAIJ" -> null + - hosted_zone_id = "Z2FDTNDATAQYW2" -> null + - http_version = "http2" -> null + - id = "ENWTZLUASCDJZ" -> null + - in_progress_validation_batches = 0 -> null + - is_ipv6_enabled = true -> null + - last_modified_time = "2025-05-22 17:11:46.344 +0000 UTC" -> null + - price_class = "PriceClass_100" -> null + - retain_on_delete = false -> null + - staging = false -> null + - status = "Deployed" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - trusted_key_groups = [ + - { + - enabled = false + - items = [] + }, + ] -> null + - trusted_signers = [ + - { + - enabled = false + - items = [] + }, + ] -> null + - wait_for_deployment = true -> null + + - default_cache_behavior { + - allowed_methods = [ + - "GET", + - "HEAD", + ] -> null + - cached_methods = [ + - "GET", + - "HEAD", + ] -> null + - compress = true -> null + - default_ttl = 86400 -> null + - max_ttl = 172800 -> null + - min_ttl = 0 -> null + - smooth_streaming = false -> null + - target_origin_id = "website-in-s3" -> null + - trusted_key_groups = [] -> null + - trusted_signers = [] -> null + - viewer_protocol_policy = "redirect-to-https" -> null + + - forwarded_values { + - headers = [] -> null + - query_string = false -> null + - query_string_cache_keys = [] -> null + + - cookies { + - forward = "none" -> null + - whitelisted_names = [] -> null + } + } + + - grpc_config { + - enabled = false -> null + } + } + + - ordered_cache_behavior { + - allowed_methods = [ + - "DELETE", + - "GET", + - "HEAD", + - "OPTIONS", + - "PATCH", + - "POST", + - "PUT", + ] -> null + - cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" -> null + - cached_methods = [ + - "GET", + - "HEAD", + ] -> null + - compress = true -> null + - default_ttl = 0 -> null + - max_ttl = 0 -> null + - min_ttl = 0 -> null + - origin_request_policy_id = "b689b0a8-53d0-40ab-baf2-68738e2966ac" -> null + - path_pattern = "/api/*" -> null + - smooth_streaming = false -> null + - target_origin_id = "api-in-gateway" -> null + - trusted_key_groups = [] -> null + - trusted_signers = [] -> null + - viewer_protocol_policy = "redirect-to-https" -> null + + - function_association { + - event_type = "viewer-request" -> null + - function_arn = "arn:aws:cloudfront::328307993388:function/document-extractor-dev-rewrite-request" -> null + } + + - grpc_config { + - enabled = false -> null + } + } + + - origin { + - connection_attempts = 3 -> null + - connection_timeout = 10 -> null + - domain_name = "ic9avcgf3l.execute-api.us-west-1.amazonaws.com" -> null + - origin_id = "api-in-gateway" -> null + - origin_path = "/v1" -> null + + - custom_origin_config { + - http_port = 80 -> null + - https_port = 443 -> null + - origin_keepalive_timeout = 5 -> null + - origin_protocol_policy = "https-only" -> null + - origin_read_timeout = 30 -> null + - origin_ssl_protocols = [ + - "TLSv1.2", + ] -> null + } + } + - origin { + - connection_attempts = 3 -> null + - connection_timeout = 10 -> null + - domain_name = "document-extractor-dev-website-328307993388.s3.us-west-1.amazonaws.com" -> null + - origin_access_control_id = "E5E4NY4IQRCTR" -> null + - origin_id = "website-in-s3" -> null + } + + - restrictions { + - geo_restriction { + - locations = [] -> null + - restriction_type = "none" -> null + } + } + + - viewer_certificate { + - cloudfront_default_certificate = true -> null + - minimum_protocol_version = "TLSv1" -> null + } + } + + # aws_cloudfront_function.rewrite_uri will be destroyed + - resource "aws_cloudfront_function" "rewrite_uri" { + - arn = "arn:aws:cloudfront::328307993388:function/document-extractor-dev-rewrite-request" -> null + - code = <<-EOT + function handler(event) { + var request = event.request; + request.uri = request.uri.replace(/^\/api\//, "/"); + return request; + } + EOT -> null + - etag = "ETVPDKIKX0DER" -> null + - id = "document-extractor-dev-rewrite-request" -> null + - key_value_store_associations = [] -> null + - live_stage_etag = "ETVPDKIKX0DER" -> null + - name = "document-extractor-dev-rewrite-request" -> null + - publish = true -> null + - runtime = "cloudfront-js-1.0" -> null + - status = "DEPLOYED" -> null + } + + # aws_cloudfront_origin_access_control.oac will be destroyed + - resource "aws_cloudfront_origin_access_control" "oac" { + - arn = "arn:aws:cloudfront::328307993388:origin-access-control/E5E4NY4IQRCTR" -> null + - description = "SigV4 control for private S3 website bucket" -> null + - etag = "ETVPDKIKX0DER" -> null + - id = "E5E4NY4IQRCTR" -> null + - name = "document-extractor-dev-oac" -> null + - origin_access_control_origin_type = "s3" -> null + - signing_behavior = "always" -> null + - signing_protocol = "sigv4" -> null + } + + # aws_dynamodb_table.extract_table will be destroyed + - resource "aws_dynamodb_table" "extract_table" { + - arn = "arn:aws:dynamodb:us-west-1:328307993388:table/document-extractor-dev-text-extract" -> null + - billing_mode = "PAY_PER_REQUEST" -> null + - deletion_protection_enabled = false -> null + - hash_key = "document_id" -> null + - id = "document-extractor-dev-text-extract" -> null + - name = "document-extractor-dev-text-extract" -> null + - read_capacity = 0 -> null + - stream_enabled = false -> null + - table_class = "STANDARD" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - write_capacity = 0 -> null + + - attribute { + - name = "document_id" -> null + - type = "S" -> null + } + + - point_in_time_recovery { + - enabled = false -> null + - recovery_period_in_days = 0 -> null + } + + - ttl { + - enabled = false -> null + } + } + + # aws_iam_policy.dynamodb_lambda_policy will be destroyed + - resource "aws_iam_policy" "dynamodb_lambda_policy" { + - arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy" -> null + - attachment_count = 1 -> null + - id = "arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy" -> null + - name = "document-extractor-dev-dynamodb-lambda-policy" -> null + - path = "/" -> null + - policy = jsonencode( + { + - Statement = [ + - { + - Action = "dynamodb:*" + - Effect = "Allow" + - Resource = "arn:aws:dynamodb:us-west-1:328307993388:table/document-extractor-dev-text-extract" + }, + ] + - Version = "2012-10-17" + } + ) -> null + - policy_id = "ANPAUY4FONMWOXCPMFZOI" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_iam_policy.kms_lambda_policy will be destroyed + - resource "aws_iam_policy" "kms_lambda_policy" { + - arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy" -> null + - attachment_count = 1 -> null + - id = "arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy" -> null + - name = "document-extractor-dev-kms-lambda-policy" -> null + - path = "/" -> null + - policy = jsonencode( + { + - Statement = [ + - { + - Action = [ + - "kms:GenerateDataKey", + - "kms:Encrypt", + - "kms:DescribeKey", + - "kms:Decrypt", + ] + - Effect = "Allow" + - Resource = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + }, + ] + - Version = "2012-10-17" + } + ) -> null + - policy_id = "ANPAUY4FONMWP3RCSG7LS" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_iam_policy.s3_lambda_policy will be destroyed + - resource "aws_iam_policy" "s3_lambda_policy" { + - arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy" -> null + - attachment_count = 1 -> null + - id = "arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy" -> null + - name = "document-extractor-dev-s3-lambda-policy" -> null + - path = "/" -> null + - policy = jsonencode( + { + - Statement = [ + - { + - Action = "s3:*" + - Effect = "Allow" + - Resource = [ + - "arn:aws:s3:::document-extractor-dev-documents-328307993388/*", + - "arn:aws:s3:::document-extractor-dev-documents-328307993388", + ] + }, + ] + - Version = "2012-10-17" + } + ) -> null + - policy_id = "ANPAUY4FONMWEETR66RD5" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_iam_policy.secrets_lambda_policy will be destroyed + - resource "aws_iam_policy" "secrets_lambda_policy" { + - arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy" -> null + - attachment_count = 1 -> null + - id = "arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy" -> null + - name = "document-extractor-dev-secrets-lambda-policy" -> null + - path = "/" -> null + - policy = jsonencode( + { + - Statement = [ + - { + - Action = "secretsmanager:*" + - Effect = "Allow" + - Resource = "*" + }, + ] + - Version = "2012-10-17" + } + ) -> null + - policy_id = "ANPAUY4FONMWH65CVSXAG" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_iam_policy.sqs_lambda_policy will be destroyed + - resource "aws_iam_policy" "sqs_lambda_policy" { + - arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy" -> null + - attachment_count = 1 -> null + - id = "arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy" -> null + - name = "document-extractor-dev-sqs-lambda-policy" -> null + - path = "/" -> null + - policy = jsonencode( + { + - Statement = [ + - { + - Action = "sqs:*" + - Effect = "Allow" + - Resource = "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb" + }, + ] + - Version = "2012-10-17" + } + ) -> null + - policy_id = "ANPAUY4FONMWOGMGQ5D3P" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_iam_role.execution_role will be destroyed + - resource "aws_iam_role" "execution_role" { + - arn = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" -> null + - assume_role_policy = jsonencode( + { + - Statement = [ + - { + - Action = "sts:AssumeRole" + - Effect = "Allow" + - Principal = { + - Service = "lambda.amazonaws.com" + } + }, + ] + - Version = "2012-10-17" + } + ) -> null + - create_date = "2025-05-22T15:30:43Z" -> null + - force_detach_policies = false -> null + - id = "document-extractor-dev-lambda-execution-role" -> null + - managed_policy_arns = [ + - "arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy", + - "arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy", + - "arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy", + - "arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy", + - "arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy", + - "arn:aws:iam::aws:policy/AmazonTextractFullAccess", + - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ] -> null + - max_session_duration = 3600 -> null + - name = "document-extractor-dev-lambda-execution-role" -> null + - path = "/" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - unique_id = "AROAUY4FONMWAAHANRCKS" -> null + } + + # aws_iam_role_policy_attachment.attach_basic_permission_to_role will be destroyed + - resource "aws_iam_role_policy_attachment" "attach_basic_permission_to_role" { + - id = "document-extractor-dev-lambda-execution-role-20250522153044909700000007" -> null + - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" -> null + - role = "document-extractor-dev-lambda-execution-role" -> null + } + + # aws_iam_role_policy_attachment.attach_dynamodb_permission_to_role will be destroyed + - resource "aws_iam_role_policy_attachment" "attach_dynamodb_permission_to_role" { + - id = "document-extractor-dev-lambda-execution-role-20250522153115634300000009" -> null + - policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy" -> null + - role = "document-extractor-dev-lambda-execution-role" -> null + } + + # aws_iam_role_policy_attachment.attach_kms_permission_to_role will be destroyed + - resource "aws_iam_role_policy_attachment" "attach_kms_permission_to_role" { + - id = "document-extractor-dev-lambda-execution-role-2025052215311566550000000a" -> null + - policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy" -> null + - role = "document-extractor-dev-lambda-execution-role" -> null + } + + # aws_iam_role_policy_attachment.attach_s3_permission_to_role will be destroyed + - resource "aws_iam_role_policy_attachment" "attach_s3_permission_to_role" { + - id = "document-extractor-dev-lambda-execution-role-20250522153114543600000008" -> null + - policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy" -> null + - role = "document-extractor-dev-lambda-execution-role" -> null + } + + # aws_iam_role_policy_attachment.attach_secrets_permission_to_role will be destroyed + - resource "aws_iam_role_policy_attachment" "attach_secrets_permission_to_role" { + - id = "document-extractor-dev-lambda-execution-role-20250522153044573200000005" -> null + - policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy" -> null + - role = "document-extractor-dev-lambda-execution-role" -> null + } + + # aws_iam_role_policy_attachment.attach_sqs_permission_to_role will be destroyed + - resource "aws_iam_role_policy_attachment" "attach_sqs_permission_to_role" { + - id = "document-extractor-dev-lambda-execution-role-2025052215314877690000000b" -> null + - policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy" -> null + - role = "document-extractor-dev-lambda-execution-role" -> null + } + + # aws_iam_role_policy_attachment.attach_textract_permission_to_role will be destroyed + - resource "aws_iam_role_policy_attachment" "attach_textract_permission_to_role" { + - id = "document-extractor-dev-lambda-execution-role-20250522153044578100000006" -> null + - policy_arn = "arn:aws:iam::aws:policy/AmazonTextractFullAccess" -> null + - role = "document-extractor-dev-lambda-execution-role" -> null + } + + # aws_kms_key.encryption will be destroyed + - resource "aws_kms_key" "encryption" { + - arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - bypass_policy_lockout_safety_check = false -> null + - customer_master_key_spec = "SYMMETRIC_DEFAULT" -> null + - deletion_window_in_days = 7 -> null + - description = "Data encryption for document-extractor" -> null + - enable_key_rotation = true -> null + - id = "2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - is_enabled = true -> null + - key_id = "2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - key_usage = "ENCRYPT_DECRYPT" -> null + - multi_region = false -> null + - policy = jsonencode( + { + - Id = "key-default-1" + - Statement = [ + - { + - Action = "kms:*" + - Effect = "Allow" + - Principal = { + - AWS = "arn:aws:iam::328307993388:root" + } + - Resource = "*" + - Sid = "Enable IAM User Permissions" + }, + ] + - Version = "2012-10-17" + } + ) -> null + - rotation_period_in_days = 365 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs will be destroyed + - resource "aws_lambda_event_source_mapping" "invoke_dynamodb_writer_from_sqs" { + - arn = "arn:aws:lambda:us-west-1:328307993388:event-source-mapping:b32111fd-5cd6-45fd-b193-ac415949bc48" -> null + - batch_size = 10 -> null + - bisect_batch_on_function_error = false -> null + - enabled = true -> null + - event_source_arn = "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb" -> null + - function_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb" -> null + - function_name = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb" -> null + - function_response_types = [] -> null + - id = "b32111fd-5cd6-45fd-b193-ac415949bc48" -> null + - last_modified = "2025-05-22T15:35:05Z" -> null + - maximum_batching_window_in_seconds = 0 -> null + - maximum_record_age_in_seconds = 0 -> null + - maximum_retry_attempts = 0 -> null + - parallelization_factor = 0 -> null + - queues = [] -> null + - state = "Enabled" -> null + - state_transition_reason = "USER_INITIATED" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - topics = [] -> null + - tumbling_window_in_seconds = 0 -> null + - uuid = "b32111fd-5cd6-45fd-b193-ac415949bc48" -> null + } + + # aws_lambda_function.authorizer will be destroyed + - resource "aws_lambda_function" "authorizer" { + - architectures = [ + - "arm64", + ] -> null + - arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer" -> null + - code_sha256 = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - filename = "./../backend/dist/lambda.zip" -> null + - function_name = "document-extractor-dev-authorizer" -> null + - handler = "src.external.aws.lambdas.authenticate.lambda_handler" -> null + - id = "document-extractor-dev-authorizer" -> null + - invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer/invocations" -> null + - kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - last_modified = "2025-05-29T15:25:32.000+0000" -> null + - layers = [] -> null + - memory_size = 256 -> null + - package_type = "Zip" -> null + - publish = true -> null + - qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer:6" -> null + - qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer:6/invocations" -> null + - reserved_concurrent_executions = -1 -> null + - role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" -> null + - runtime = "python3.13" -> null + - skip_destroy = false -> null + - source_code_hash = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - source_code_size = 19659547 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - timeout = 30 -> null + - version = "6" -> null + + - environment { + - variables = { + - "ENVIRONMENT" = "dev" + } -> null + } + + - ephemeral_storage { + - size = 512 -> null + } + + - logging_config { + - log_format = "Text" -> null + - log_group = "/aws/lambda/document-extractor-dev-authorizer" -> null + } + + - tracing_config { + - mode = "PassThrough" -> null + } + } + + # aws_lambda_function.text_extract will be destroyed + - resource "aws_lambda_function" "text_extract" { + - architectures = [ + - "arm64", + ] -> null + - arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract" -> null + - code_sha256 = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - filename = "./../backend/dist/lambda.zip" -> null + - function_name = "document-extractor-dev-text-extract" -> null + - handler = "src.external.aws.lambdas.text_extractor.lambda_handler" -> null + - id = "document-extractor-dev-text-extract" -> null + - invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract/invocations" -> null + - kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - last_modified = "2025-05-29T15:25:54.000+0000" -> null + - layers = [] -> null + - memory_size = 256 -> null + - package_type = "Zip" -> null + - publish = true -> null + - qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract:6" -> null + - qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract:6/invocations" -> null + - reserved_concurrent_executions = -1 -> null + - role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" -> null + - runtime = "python3.13" -> null + - skip_destroy = false -> null + - source_code_hash = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - source_code_size = 19659547 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - timeout = 30 -> null + - version = "6" -> null + + - environment { + - variables = (sensitive value) -> null + } + + - ephemeral_storage { + - size = 512 -> null + } + + - logging_config { + - log_format = "Text" -> null + - log_group = "/aws/lambda/document-extractor-dev-text-extract" -> null + } + + - tracing_config { + - mode = "PassThrough" -> null + } + } + + # aws_lambda_function.write_to_dynamodb will be destroyed + - resource "aws_lambda_function" "write_to_dynamodb" { + - architectures = [ + - "arm64", + ] -> null + - arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb" -> null + - code_sha256 = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - filename = "./../backend/dist/lambda.zip" -> null + - function_name = "document-extractor-dev-write-to-dynamodb" -> null + - handler = "src.external.aws.lambdas.sqs_dynamo_writer.lambda_handler" -> null + - id = "document-extractor-dev-write-to-dynamodb" -> null + - invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb/invocations" -> null + - kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - last_modified = "2025-05-29T15:25:43.000+0000" -> null + - layers = [] -> null + - memory_size = 256 -> null + - package_type = "Zip" -> null + - publish = true -> null + - qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb:6" -> null + - qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb:6/invocations" -> null + - reserved_concurrent_executions = -1 -> null + - role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" -> null + - runtime = "python3.13" -> null + - skip_destroy = false -> null + - source_code_hash = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - source_code_size = 19659547 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - timeout = 30 -> null + - version = "6" -> null + + - environment { + - variables = { + - "DYNAMODB_TABLE" = "document-extractor-dev-text-extract" + - "SQS_QUEUE_URL" = "https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb" + } -> null + } + + - ephemeral_storage { + - size = 512 -> null + } + + - logging_config { + - log_format = "Text" -> null + - log_group = "/aws/lambda/document-extractor-dev-write-to-dynamodb" -> null + } + + - tracing_config { + - mode = "PassThrough" -> null + } + } + + # aws_lambda_permission.allow_bucket_invoke will be destroyed + - resource "aws_lambda_permission" "allow_bucket_invoke" { + - action = "lambda:InvokeFunction" -> null + - function_name = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract" -> null + - id = "AllowExecutionFromS3Bucket" -> null + - principal = "s3.amazonaws.com" -> null + - source_arn = "arn:aws:s3:::document-extractor-dev-documents-328307993388" -> null + - statement_id = "AllowExecutionFromS3Bucket" -> null + } + + # aws_lambda_permission.api_gateway_invoke_authorizer will be destroyed + - resource "aws_lambda_permission" "api_gateway_invoke_authorizer" { + - action = "lambda:InvokeFunction" -> null + - function_name = "document-extractor-dev-authorizer" -> null + - id = "AllowExecutionFromApiGateway" -> null + - principal = "apigateway.amazonaws.com" -> null + - source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*" -> null + - statement_id = "AllowExecutionFromApiGateway" -> null + } + + # aws_lambda_provisioned_concurrency_config.authorizer_concurrency will be destroyed + - resource "aws_lambda_provisioned_concurrency_config" "authorizer_concurrency" { + - function_name = "document-extractor-dev-authorizer" -> null + - id = "document-extractor-dev-authorizer,6" -> null + - provisioned_concurrent_executions = 1 -> null + - qualifier = "6" -> null + - skip_destroy = false -> null + } + + # aws_lambda_provisioned_concurrency_config.text_extract_concurrency will be destroyed + - resource "aws_lambda_provisioned_concurrency_config" "text_extract_concurrency" { + - function_name = "document-extractor-dev-text-extract" -> null + - id = "document-extractor-dev-text-extract,6" -> null + - provisioned_concurrent_executions = 1 -> null + - qualifier = "6" -> null + - skip_destroy = false -> null + } + + # aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency will be destroyed + - resource "aws_lambda_provisioned_concurrency_config" "write_to_dynamodb_concurrency" { + - function_name = "document-extractor-dev-write-to-dynamodb" -> null + - id = "document-extractor-dev-write-to-dynamodb,6" -> null + - provisioned_concurrent_executions = 1 -> null + - qualifier = "6" -> null + - skip_destroy = false -> null + } + + # aws_s3_bucket.document_storage will be destroyed + - resource "aws_s3_bucket" "document_storage" { + - arn = "arn:aws:s3:::document-extractor-dev-documents-328307993388" -> null + - bucket = "document-extractor-dev-documents-328307993388" -> null + - bucket_domain_name = "document-extractor-dev-documents-328307993388.s3.amazonaws.com" -> null + - bucket_regional_domain_name = "document-extractor-dev-documents-328307993388.s3.us-west-1.amazonaws.com" -> null + - force_destroy = false -> null + - hosted_zone_id = "Z2F56UZL2M1ACD" -> null + - id = "document-extractor-dev-documents-328307993388" -> null + - object_lock_enabled = false -> null + - region = "us-west-1" -> null + - request_payer = "BucketOwner" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + + - grant { + - id = "8c3f17ff516da15e6f5aab48ecdb83925bea44ff492b5a28e507db8e94e3c531" -> null + - permissions = [ + - "FULL_CONTROL", + ] -> null + - type = "CanonicalUser" -> null + } + + - lifecycle_rule { + - abort_incomplete_multipart_upload_days = 0 -> null + - enabled = true -> null + - id = "delete-uploaded-documents" -> null + - prefix = "input/" -> null + - tags = {} -> null + + - expiration { + - days = 31 -> null + - expired_object_delete_marker = false -> null + } + } + + - server_side_encryption_configuration { + - rule { + - bucket_key_enabled = false -> null + + - apply_server_side_encryption_by_default { + - sse_algorithm = "AES256" -> null + } + } + } + + - versioning { + - enabled = true -> null + - mfa_delete = false -> null + } + } + + # aws_s3_bucket.website_storage will be destroyed + - resource "aws_s3_bucket" "website_storage" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_domain_name = "document-extractor-dev-website-328307993388.s3.amazonaws.com" -> null + - bucket_regional_domain_name = "document-extractor-dev-website-328307993388.s3.us-west-1.amazonaws.com" -> null + - force_destroy = true -> null + - hosted_zone_id = "Z2F56UZL2M1ACD" -> null + - id = "document-extractor-dev-website-328307993388" -> null + - object_lock_enabled = false -> null + - policy = jsonencode( + { + - Statement = [ + - { + - Action = "s3:GetObject" + - Condition = { + - StringEquals = { + - "AWS:SourceArn" = "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ" + } + } + - Effect = "Allow" + - Principal = { + - Service = "cloudfront.amazonaws.com" + } + - Resource = "arn:aws:s3:::document-extractor-dev-website-328307993388/*" + - Sid = "AllowCloudFront" + }, + ] + - Version = "2012-10-17" + } + ) -> null + - region = "us-west-1" -> null + - request_payer = "BucketOwner" -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - website_domain = "s3-website-us-west-1.amazonaws.com" -> null + - website_endpoint = "document-extractor-dev-website-328307993388.s3-website-us-west-1.amazonaws.com" -> null + + - grant { + - id = "8c3f17ff516da15e6f5aab48ecdb83925bea44ff492b5a28e507db8e94e3c531" -> null + - permissions = [ + - "FULL_CONTROL", + ] -> null + - type = "CanonicalUser" -> null + } + + - server_side_encryption_configuration { + - rule { + - bucket_key_enabled = false -> null + + - apply_server_side_encryption_by_default { + - sse_algorithm = "AES256" -> null + } + } + } + + - versioning { + - enabled = true -> null + - mfa_delete = false -> null + } + + - website { + - error_document = "index.html" -> null + - index_document = "index.html" -> null + } + } + + # aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles will be destroyed + - resource "aws_s3_bucket_lifecycle_configuration" "document_storage_lifecycles" { + - bucket = "document-extractor-dev-documents-328307993388" -> null + - id = "document-extractor-dev-documents-328307993388" -> null + - transition_default_minimum_object_size = "all_storage_classes_128K" -> null + + - rule { + - id = "delete-uploaded-documents" -> null + - status = "Enabled" -> null + + - expiration { + - days = 31 -> null + - expired_object_delete_marker = false -> null + } + + - filter { + - prefix = "input/" -> null + } + } + } + + # aws_s3_bucket_notification.notify_on_input_data will be destroyed + - resource "aws_s3_bucket_notification" "notify_on_input_data" { + - bucket = "document-extractor-dev-documents-328307993388" -> null + - eventbridge = false -> null + - id = "document-extractor-dev-documents-328307993388" -> null + + - lambda_function { + - events = [ + - "s3:ObjectCreated:*", + ] -> null + - filter_prefix = "input/" -> null + - id = "tf-s3-lambda-2025052215342622090000000d" -> null + - lambda_function_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract" -> null + } + } + + # aws_s3_bucket_policy.website_read will be destroyed + - resource "aws_s3_bucket_policy" "website_read" { + - bucket = "document-extractor-dev-website-328307993388" -> null + - id = "document-extractor-dev-website-328307993388" -> null + - policy = jsonencode( + { + - Statement = [ + - { + - Action = "s3:GetObject" + - Condition = { + - StringEquals = { + - "AWS:SourceArn" = "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ" + } + } + - Effect = "Allow" + - Principal = { + - Service = "cloudfront.amazonaws.com" + } + - Resource = "arn:aws:s3:::document-extractor-dev-website-328307993388/*" + - Sid = "AllowCloudFront" + }, + ] + - Version = "2012-10-17" + } + ) -> null + } + + # aws_s3_bucket_public_access_block.private_website will be destroyed + - resource "aws_s3_bucket_public_access_block" "private_website" { + - block_public_acls = true -> null + - block_public_policy = true -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - id = "document-extractor-dev-website-328307993388" -> null + - ignore_public_acls = true -> null + - restrict_public_buckets = false -> null + } + + # aws_s3_bucket_versioning.website_storage_versioning will be destroyed + - resource "aws_s3_bucket_versioning" "website_storage_versioning" { + - bucket = "document-extractor-dev-website-328307993388" -> null + - id = "document-extractor-dev-website-328307993388" -> null + + - versioning_configuration { + - status = "Enabled" -> null + } + } + + # aws_s3_bucket_website_configuration.website_configuration will be destroyed + - resource "aws_s3_bucket_website_configuration" "website_configuration" { + - bucket = "document-extractor-dev-website-328307993388" -> null + - id = "document-extractor-dev-website-328307993388" -> null + - website_domain = "s3-website-us-west-1.amazonaws.com" -> null + - website_endpoint = "document-extractor-dev-website-328307993388.s3-website-us-west-1.amazonaws.com" -> null + + - error_document { + - key = "index.html" -> null + } + + - index_document { + - suffix = "index.html" -> null + } + } + + # aws_s3_object.website_files["GSA-logo.7a2abfc3.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/GSA-logo.7a2abfc3.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "a5f5373f259f83f81912e6a91cf2c388" -> null + - force_destroy = false -> null + - id = "GSA-logo.7a2abfc3.svg" -> null + - key = "GSA-logo.7a2abfc3.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//GSA-logo.7a2abfc3.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["GSA-logo.ece32ed3.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/GSA-logo.ece32ed3.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "10d4fa294b895ffbd2709b3db2056484" -> null + - force_destroy = false -> null + - id = "GSA-logo.ece32ed3.svg" -> null + - key = "GSA-logo.ece32ed3.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//GSA-logo.ece32ed3.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-Bold.967b2108.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Bold.967b2108.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "115f5a1d96eaa67ffee687960dd770f3" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-Bold.967b2108.woff2" -> null + - key = "Latin-Merriweather-Bold.967b2108.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-Bold.967b2108.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-Bold.cdbf7e95.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Bold.cdbf7e95.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "115f5a1d96eaa67ffee687960dd770f3" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-Bold.cdbf7e95.woff2" -> null + - key = "Latin-Merriweather-Bold.cdbf7e95.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-Bold.cdbf7e95.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-BoldItalic.32dc0025.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-BoldItalic.32dc0025.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "2750a1e9d21f8b94f614bf36a3c53a0a" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-BoldItalic.32dc0025.woff2" -> null + - key = "Latin-Merriweather-BoldItalic.32dc0025.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-BoldItalic.32dc0025.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-BoldItalic.e58256dc.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-BoldItalic.e58256dc.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "2750a1e9d21f8b94f614bf36a3c53a0a" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-BoldItalic.e58256dc.woff2" -> null + - key = "Latin-Merriweather-BoldItalic.e58256dc.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-BoldItalic.e58256dc.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-Italic.959c3872.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Italic.959c3872.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "2412c72a898d4ed8f67e1351f7360067" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-Italic.959c3872.woff2" -> null + - key = "Latin-Merriweather-Italic.959c3872.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-Italic.959c3872.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-Italic.e2716ed3.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Italic.e2716ed3.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "2412c72a898d4ed8f67e1351f7360067" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-Italic.e2716ed3.woff2" -> null + - key = "Latin-Merriweather-Italic.e2716ed3.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-Italic.e2716ed3.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-Light.1ad1a1d8.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Light.1ad1a1d8.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "4f0fb5042c21b257ae0707783ac29626" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-Light.1ad1a1d8.woff2" -> null + - key = "Latin-Merriweather-Light.1ad1a1d8.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-Light.1ad1a1d8.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-Light.fbc4393c.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Light.fbc4393c.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "4f0fb5042c21b257ae0707783ac29626" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-Light.fbc4393c.woff2" -> null + - key = "Latin-Merriweather-Light.fbc4393c.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-Light.fbc4393c.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-LightItalic.1c9df738.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-LightItalic.1c9df738.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "08acae9c0b84b1ac1ea5cc76846bb3f4" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-LightItalic.1c9df738.woff2" -> null + - key = "Latin-Merriweather-LightItalic.1c9df738.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-LightItalic.1c9df738.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-LightItalic.f4af3b7f.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-LightItalic.f4af3b7f.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "08acae9c0b84b1ac1ea5cc76846bb3f4" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-LightItalic.f4af3b7f.woff2" -> null + - key = "Latin-Merriweather-LightItalic.f4af3b7f.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-LightItalic.f4af3b7f.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-Regular.04046ce8.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Regular.04046ce8.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "fa9b615a25bdf9c42a7f6ffa223c1eb0" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-Regular.04046ce8.woff2" -> null + - key = "Latin-Merriweather-Regular.04046ce8.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-Regular.04046ce8.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["Latin-Merriweather-Regular.66d4c170.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Regular.66d4c170.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "fa9b615a25bdf9c42a7f6ffa223c1eb0" -> null + - force_destroy = false -> null + - id = "Latin-Merriweather-Regular.66d4c170.woff2" -> null + - key = "Latin-Merriweather-Regular.66d4c170.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//Latin-Merriweather-Regular.66d4c170.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["add.29db0c6a.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/add.29db0c6a.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "0f088d7d3e8ada1b661f2736cb506e14" -> null + - force_destroy = false -> null + - id = "add.29db0c6a.svg" -> null + - key = "add.29db0c6a.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//add.29db0c6a.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["add.d491396d.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/add.d491396d.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "7efc1415746d7f0067eefcdc3321ba52" -> null + - force_destroy = false -> null + - id = "add.d491396d.svg" -> null + - key = "add.d491396d.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//add.d491396d.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["arrow_back.c9ac1a0e.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/arrow_back.c9ac1a0e.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "e57e9cb53111150e4f51e1121220a95a" -> null + - force_destroy = false -> null + - id = "arrow_back.c9ac1a0e.svg" -> null + - key = "arrow_back.c9ac1a0e.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//arrow_back.c9ac1a0e.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["arrow_back.e4913c69.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/arrow_back.e4913c69.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "2cd7c1dd5b9ac84ed414e4cc7f0ac828" -> null + - force_destroy = false -> null + - id = "arrow_back.e4913c69.svg" -> null + - key = "arrow_back.e4913c69.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//arrow_back.e4913c69.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["calendar_today.3a803a7a.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/calendar_today.3a803a7a.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "8b326af3e58921f55b0f7b91c15a3332" -> null + - force_destroy = false -> null + - id = "calendar_today.3a803a7a.svg" -> null + - key = "calendar_today.3a803a7a.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//calendar_today.3a803a7a.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["calendar_today.ce6eaa81.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/calendar_today.ce6eaa81.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "a873a20cec4307294405eb1ba4eb2e72" -> null + - force_destroy = false -> null + - id = "calendar_today.ce6eaa81.svg" -> null + - key = "calendar_today.ce6eaa81.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//calendar_today.ce6eaa81.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["check--blue-60v.7604a0f6.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/check--blue-60v.7604a0f6.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "313b2d5d8e007f70376ec216c87db68a" -> null + - force_destroy = false -> null + - id = "check--blue-60v.7604a0f6.svg" -> null + - key = "check--blue-60v.7604a0f6.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//check--blue-60v.7604a0f6.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["check--blue-60v.982d9f95.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/check--blue-60v.982d9f95.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "64bd8ec50798f10e75b0b52772a83acf" -> null + - force_destroy = false -> null + - id = "check--blue-60v.982d9f95.svg" -> null + - key = "check--blue-60v.982d9f95.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//check--blue-60v.982d9f95.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["check_circle.120511e4.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/check_circle.120511e4.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "8a6c0918758299283e904826d8bf66f5" -> null + - force_destroy = false -> null + - id = "check_circle.120511e4.svg" -> null + - key = "check_circle.120511e4.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//check_circle.120511e4.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["check_circle.a3900be5.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/check_circle.a3900be5.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "03c3ff80715e40101493a17f864bfa6e" -> null + - force_destroy = false -> null + - id = "check_circle.a3900be5.svg" -> null + - key = "check_circle.a3900be5.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//check_circle.a3900be5.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["checkbox-indeterminate-alt.5e7abfcf.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/checkbox-indeterminate-alt.5e7abfcf.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "d00c26327b052bb4cfe366292f996369" -> null + - force_destroy = false -> null + - id = "checkbox-indeterminate-alt.5e7abfcf.svg" -> null + - key = "checkbox-indeterminate-alt.5e7abfcf.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//checkbox-indeterminate-alt.5e7abfcf.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["checkbox-indeterminate-alt.a6b9b5ac.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/checkbox-indeterminate-alt.a6b9b5ac.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "93b4958c6c02d948cd8f495eb245b74b" -> null + - force_destroy = false -> null + - id = "checkbox-indeterminate-alt.a6b9b5ac.svg" -> null + - key = "checkbox-indeterminate-alt.a6b9b5ac.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//checkbox-indeterminate-alt.a6b9b5ac.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["checkbox-indeterminate.08aa88cd.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/checkbox-indeterminate.08aa88cd.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "8a80f6567e7aa6243704d7178569e3ca" -> null + - force_destroy = false -> null + - id = "checkbox-indeterminate.08aa88cd.svg" -> null + - key = "checkbox-indeterminate.08aa88cd.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//checkbox-indeterminate.08aa88cd.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["checkbox-indeterminate.db2c5d96.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/checkbox-indeterminate.db2c5d96.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "c60f49da349a00d84dc9fc7a4f5cc62b" -> null + - force_destroy = false -> null + - id = "checkbox-indeterminate.db2c5d96.svg" -> null + - key = "checkbox-indeterminate.db2c5d96.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//checkbox-indeterminate.db2c5d96.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["close.3962dfa0.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/close.3962dfa0.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "c0865955ca37002dd8e2883f888c07c7" -> null + - force_destroy = false -> null + - id = "close.3962dfa0.svg" -> null + - key = "close.3962dfa0.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//close.3962dfa0.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["close.bf51193b.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/close.bf51193b.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "036587ca3d855f3ac5fbd8f43b4122e7" -> null + - force_destroy = false -> null + - id = "close.bf51193b.svg" -> null + - key = "close.bf51193b.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//close.bf51193b.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["correct8-alt.8f8dac23.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/correct8-alt.8f8dac23.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "f5905b100ed5db0b8a1d60de8afeb676" -> null + - force_destroy = false -> null + - id = "correct8-alt.8f8dac23.svg" -> null + - key = "correct8-alt.8f8dac23.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//correct8-alt.8f8dac23.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["correct8-alt.c14d1430.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/correct8-alt.c14d1430.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "2b82d538111387f92a11249246553a91" -> null + - force_destroy = false -> null + - id = "correct8-alt.c14d1430.svg" -> null + - key = "correct8-alt.c14d1430.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//correct8-alt.c14d1430.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["correct8.10e8a7ef.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/correct8.10e8a7ef.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "4ba9644a1430fccf876129a9491c5eba" -> null + - force_destroy = false -> null + - id = "correct8.10e8a7ef.svg" -> null + - key = "correct8.10e8a7ef.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//correct8.10e8a7ef.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["correct8.2dc455b8.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/correct8.2dc455b8.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "d63df17ba88fd8bf61d256e3e874ba47" -> null + - force_destroy = false -> null + - id = "correct8.2dc455b8.svg" -> null + - key = "correct8.2dc455b8.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//correct8.2dc455b8.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["error--white.73d13870.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/error--white.73d13870.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "06a11ba59b006a7d492d169637bf1548" -> null + - force_destroy = false -> null + - id = "error--white.73d13870.svg" -> null + - key = "error--white.73d13870.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//error--white.73d13870.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["error--white.dcc8cafd.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/error--white.dcc8cafd.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "bfbcdca379210ce5cfc9db84be8f5dc1" -> null + - force_destroy = false -> null + - id = "error--white.dcc8cafd.svg" -> null + - key = "error--white.dcc8cafd.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//error--white.dcc8cafd.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["error.1986e136.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/error.1986e136.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "fde17cc10d5eef259b5bbe18e46fab0c" -> null + - force_destroy = false -> null + - id = "error.1986e136.svg" -> null + - key = "error.1986e136.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//error.1986e136.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["error.35f61f4b.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/error.35f61f4b.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "d13a59b1e5cd75dd20b7a028671532dc" -> null + - force_destroy = false -> null + - id = "error.35f61f4b.svg" -> null + - key = "error.35f61f4b.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//error.35f61f4b.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["expand_less.5bb6de10.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/expand_less.5bb6de10.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "dfebee63a045b8ccc14dbe5f31760a27" -> null + - force_destroy = false -> null + - id = "expand_less.5bb6de10.svg" -> null + - key = "expand_less.5bb6de10.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//expand_less.5bb6de10.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["expand_less.e2846957.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/expand_less.e2846957.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "625e5aef439c7c58811ff4311fbd9ad2" -> null + - force_destroy = false -> null + - id = "expand_less.e2846957.svg" -> null + - key = "expand_less.e2846957.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//expand_less.e2846957.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["expand_more.0d06b804.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/expand_more.0d06b804.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "42030f327d3bac1e3c1b28d6b6cdd39d" -> null + - force_destroy = false -> null + - id = "expand_more.0d06b804.svg" -> null + - key = "expand_more.0d06b804.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//expand_more.0d06b804.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["expand_more.3ac36c78.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/expand_more.3ac36c78.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "ef8f6951a107bd87f958f7da30cc055c" -> null + - force_destroy = false -> null + - id = "expand_more.3ac36c78.svg" -> null + - key = "expand_more.3ac36c78.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//expand_more.3ac36c78.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file-excel.323d5d7b.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-excel.323d5d7b.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "24e33cd8762e554e0e34c96374ea2590" -> null + - force_destroy = false -> null + - id = "file-excel.323d5d7b.svg" -> null + - key = "file-excel.323d5d7b.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file-excel.323d5d7b.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file-excel.c90d2036.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-excel.c90d2036.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "22c2afbcb82b6edd4a417ca47b6b5e19" -> null + - force_destroy = false -> null + - id = "file-excel.c90d2036.svg" -> null + - key = "file-excel.c90d2036.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file-excel.c90d2036.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file-pdf.248e68e5.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-pdf.248e68e5.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "966a8ca1993ba85a960c368b55ef4e66" -> null + - force_destroy = false -> null + - id = "file-pdf.248e68e5.svg" -> null + - key = "file-pdf.248e68e5.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file-pdf.248e68e5.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file-pdf.51e66388.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-pdf.51e66388.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "51e6e0e5d44a7b72c1177086adf034a8" -> null + - force_destroy = false -> null + - id = "file-pdf.51e66388.svg" -> null + - key = "file-pdf.51e66388.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file-pdf.51e66388.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file-video.a3a38638.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-video.a3a38638.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "7d37155a50361daba2e04e5350ee1d12" -> null + - force_destroy = false -> null + - id = "file-video.a3a38638.svg" -> null + - key = "file-video.a3a38638.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file-video.a3a38638.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file-video.d309c165.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-video.d309c165.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "cead665f630764bb98d255029d937b47" -> null + - force_destroy = false -> null + - id = "file-video.d309c165.svg" -> null + - key = "file-video.d309c165.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file-video.d309c165.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file-word.75e83f68.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-word.75e83f68.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "e351d03815ea43bdfef10273d193c898" -> null + - force_destroy = false -> null + - id = "file-word.75e83f68.svg" -> null + - key = "file-word.75e83f68.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file-word.75e83f68.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file-word.896caaf0.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-word.896caaf0.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "097753fc352f35b41d6bbaf468cceeb6" -> null + - force_destroy = false -> null + - id = "file-word.896caaf0.svg" -> null + - key = "file-word.896caaf0.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file-word.896caaf0.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file.057d5652.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file.057d5652.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "9fd817a14d685ce75a9c494c482de9f4" -> null + - force_destroy = false -> null + - id = "file.057d5652.svg" -> null + - key = "file.057d5652.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file.057d5652.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["file.f51e21d7.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file.f51e21d7.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "f203bc7af348e50a50f017a2f11458d4" -> null + - force_destroy = false -> null + - id = "file.f51e21d7.svg" -> null + - key = "file.f51e21d7.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//file.f51e21d7.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["hero.3538e009.jpg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/hero.3538e009.jpg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/jpeg" -> null + - etag = "3e76d9e4fadd5f14d44a7093c5633c93" -> null + - force_destroy = false -> null + - id = "hero.3538e009.jpg" -> null + - key = "hero.3538e009.jpg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//hero.3538e009.jpg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["hero.abe074f7.jpg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/hero.abe074f7.jpg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/jpeg" -> null + - etag = "07c4bca195b13a0e98bcb89dfce5372b" -> null + - force_destroy = false -> null + - id = "hero.abe074f7.jpg" -> null + - key = "hero.abe074f7.jpg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//hero.abe074f7.jpg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["icon-dot-gov.1cc2b2c5.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/icon-dot-gov.1cc2b2c5.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "1c9e4fc238c699bb23f13c7dc361ee57" -> null + - force_destroy = false -> null + - id = "icon-dot-gov.1cc2b2c5.svg" -> null + - key = "icon-dot-gov.1cc2b2c5.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//icon-dot-gov.1cc2b2c5.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["icon-dot-gov.86228b6b.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/icon-dot-gov.86228b6b.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "d239f0908996c922f3650b510d0d535f" -> null + - force_destroy = false -> null + - id = "icon-dot-gov.86228b6b.svg" -> null + - key = "icon-dot-gov.86228b6b.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//icon-dot-gov.86228b6b.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["icon-https.3b0ffef2.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/icon-https.3b0ffef2.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "68d04de8513589d73c2bb79e68297eb1" -> null + - force_destroy = false -> null + - id = "icon-https.3b0ffef2.svg" -> null + - key = "icon-https.3b0ffef2.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//icon-https.3b0ffef2.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["icon-https.b6eca6f6.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/icon-https.b6eca6f6.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "72f0c58199a2f225400d70968a9afc66" -> null + - force_destroy = false -> null + - id = "icon-https.b6eca6f6.svg" -> null + - key = "icon-https.b6eca6f6.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//icon-https.b6eca6f6.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["index.html"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/index.html" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "text/html; charset=utf-8" -> null + - etag = "2ccd3b4b1a5e848c82180b88d1d08e90" -> null + - force_destroy = false -> null + - id = "index.html" -> null + - key = "index.html" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//index.html" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "HZGqmpGf35Bv23Ra_pKk_uDMAHR39KsX" -> null + } + + # aws_s3_object.website_files["info.40b16694.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/info.40b16694.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "73ce9f949b72cc08795cbc8db357fb24" -> null + - force_destroy = false -> null + - id = "info.40b16694.svg" -> null + - key = "info.40b16694.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//info.40b16694.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["info.85fe97af.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/info.85fe97af.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "dad4bc60b84bb6fccf76651f10ef4b34" -> null + - force_destroy = false -> null + - id = "info.85fe97af.svg" -> null + - key = "info.85fe97af.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//info.85fe97af.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["launch--white.626c08f5.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/launch--white.626c08f5.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "caeb0df516eda2356247204bb01d2917" -> null + - force_destroy = false -> null + - id = "launch--white.626c08f5.svg" -> null + - key = "launch--white.626c08f5.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//launch--white.626c08f5.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["launch--white.6c179f60.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/launch--white.6c179f60.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "57ea618e288611d72609ebc341de3fdc" -> null + - force_destroy = false -> null + - id = "launch--white.6c179f60.svg" -> null + - key = "launch--white.6c179f60.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//launch--white.6c179f60.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["launch.2cde378c.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/launch.2cde378c.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "c8059dd52639b419d3303934a8ea9fd0" -> null + - force_destroy = false -> null + - id = "launch.2cde378c.svg" -> null + - key = "launch.2cde378c.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//launch.2cde378c.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["launch.f4de218c.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/launch.f4de218c.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "13a426a0665b8b448e3654a0cff206e7" -> null + - force_destroy = false -> null + - id = "launch.f4de218c.svg" -> null + - key = "launch.f4de218c.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//launch.f4de218c.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["loader.7402c183.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/loader.7402c183.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "584fad42f8bb8b5f739a50abfd94a314" -> null + - force_destroy = false -> null + - id = "loader.7402c183.svg" -> null + - key = "loader.7402c183.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//loader.7402c183.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["loader.e58cf242.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/loader.e58cf242.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "c1ce57d695c823c3ca5133b1c56606f7" -> null + - force_destroy = false -> null + - id = "loader.e58cf242.svg" -> null + - key = "loader.e58cf242.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//loader.e58cf242.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["navigate_before.7d672c1a.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_before.7d672c1a.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "d7aaa21052caa26866fbd0d372f73a99" -> null + - force_destroy = false -> null + - id = "navigate_before.7d672c1a.svg" -> null + - key = "navigate_before.7d672c1a.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//navigate_before.7d672c1a.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["navigate_before.be211c2e.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_before.be211c2e.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "248acd5ee98e1806e7d42ab947b2ba68" -> null + - force_destroy = false -> null + - id = "navigate_before.be211c2e.svg" -> null + - key = "navigate_before.be211c2e.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//navigate_before.be211c2e.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["navigate_far_before.65b60266.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_far_before.65b60266.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "2a1f57e70b9419e51c03877b8c0028f8" -> null + - force_destroy = false -> null + - id = "navigate_far_before.65b60266.svg" -> null + - key = "navigate_far_before.65b60266.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//navigate_far_before.65b60266.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["navigate_far_before.904e4c63.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_far_before.904e4c63.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "7321f53317b2771b0c81721e637bff60" -> null + - force_destroy = false -> null + - id = "navigate_far_before.904e4c63.svg" -> null + - key = "navigate_far_before.904e4c63.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//navigate_far_before.904e4c63.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["navigate_far_next.37ce8b6e.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_far_next.37ce8b6e.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "a4306f47c1f8155e74bc7edb5ea95487" -> null + - force_destroy = false -> null + - id = "navigate_far_next.37ce8b6e.svg" -> null + - key = "navigate_far_next.37ce8b6e.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//navigate_far_next.37ce8b6e.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["navigate_far_next.bd2c92a4.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_far_next.bd2c92a4.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "7aa4ae9f1d1397a64bb5f34aab3f3a95" -> null + - force_destroy = false -> null + - id = "navigate_far_next.bd2c92a4.svg" -> null + - key = "navigate_far_next.bd2c92a4.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//navigate_far_next.bd2c92a4.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["navigate_next.bc7c1a4e.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_next.bc7c1a4e.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "a8e5c097da5bdbe97880bfbd67b99d38" -> null + - force_destroy = false -> null + - id = "navigate_next.bc7c1a4e.svg" -> null + - key = "navigate_next.bc7c1a4e.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//navigate_next.bc7c1a4e.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["navigate_next.fb4be146.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_next.fb4be146.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "e4449ac35306cc39af51a48bf48a2f90" -> null + - force_destroy = false -> null + - id = "navigate_next.fb4be146.svg" -> null + - key = "navigate_next.fb4be146.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//navigate_next.fb4be146.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["pdf.worker.min.mjs"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/pdf.worker.min.mjs" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "213ab48603654c9423b7d45e2ee4a48a" -> null + - force_destroy = false -> null + - id = "pdf.worker.min.mjs" -> null + - key = "pdf.worker.min.mjs" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//pdf.worker.min.mjs" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "6JZajVCr5eHRzeES2PkEnwAjUV_Zd.3b" -> null + } + + # aws_s3_object.website_files["remove.27bfc20f.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/remove.27bfc20f.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "9fcf8726387df8b26f01dec005aa740a" -> null + - force_destroy = false -> null + - id = "remove.27bfc20f.svg" -> null + - key = "remove.27bfc20f.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//remove.27bfc20f.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["remove.ca324d27.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/remove.ca324d27.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "28fac28bf8d7a32df31759deac776400" -> null + - force_destroy = false -> null + - id = "remove.ca324d27.svg" -> null + - key = "remove.ca324d27.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//remove.ca324d27.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-300.40a89411.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-300.40a89411.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "db95b37ec6ef04cd349d098941e5f70b" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-300.40a89411.woff2" -> null + - key = "roboto-mono-v5-latin-300.40a89411.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-300.40a89411.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-300.98978b08.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-300.98978b08.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "db95b37ec6ef04cd349d098941e5f70b" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-300.98978b08.woff2" -> null + - key = "roboto-mono-v5-latin-300.98978b08.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-300.98978b08.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-300italic.1710c37a.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-300italic.1710c37a.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "18357ddaddc4d4f13c5f49985327a2db" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-300italic.1710c37a.woff2" -> null + - key = "roboto-mono-v5-latin-300italic.1710c37a.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-300italic.1710c37a.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-300italic.af4ea91a.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-300italic.af4ea91a.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "18357ddaddc4d4f13c5f49985327a2db" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-300italic.af4ea91a.woff2" -> null + - key = "roboto-mono-v5-latin-300italic.af4ea91a.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-300italic.af4ea91a.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-700.d309a69d.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-700.d309a69d.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "4bc0bd04b8b2f6730b5997503fa4ce5a" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-700.d309a69d.woff2" -> null + - key = "roboto-mono-v5-latin-700.d309a69d.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-700.d309a69d.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-700.df366d23.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-700.df366d23.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "4bc0bd04b8b2f6730b5997503fa4ce5a" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-700.df366d23.woff2" -> null + - key = "roboto-mono-v5-latin-700.df366d23.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-700.df366d23.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-700italic.03671595.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-700italic.03671595.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "5eca10b5e58c4e2a8164f89bef5dbd7e" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-700italic.03671595.woff2" -> null + - key = "roboto-mono-v5-latin-700italic.03671595.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-700italic.03671595.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-700italic.2b983f5d.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-700italic.2b983f5d.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "5eca10b5e58c4e2a8164f89bef5dbd7e" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-700italic.2b983f5d.woff2" -> null + - key = "roboto-mono-v5-latin-700italic.2b983f5d.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-700italic.2b983f5d.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-italic.7226ad2c.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-italic.7226ad2c.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "55befc28b6c06f712a38e2f4153967e1" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-italic.7226ad2c.woff2" -> null + - key = "roboto-mono-v5-latin-italic.7226ad2c.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-italic.7226ad2c.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-italic.9209c633.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-italic.9209c633.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "55befc28b6c06f712a38e2f4153967e1" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-italic.9209c633.woff2" -> null + - key = "roboto-mono-v5-latin-italic.9209c633.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-italic.9209c633.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-regular.e048516f.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-regular.e048516f.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "e92cc0fb9e1a7debc138224fd02a462a" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-regular.e048516f.woff2" -> null + - key = "roboto-mono-v5-latin-regular.e048516f.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-regular.e048516f.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["roboto-mono-v5-latin-regular.ec7832e5.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-regular.ec7832e5.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "e92cc0fb9e1a7debc138224fd02a462a" -> null + - force_destroy = false -> null + - id = "roboto-mono-v5-latin-regular.ec7832e5.woff2" -> null + - key = "roboto-mono-v5-latin-regular.ec7832e5.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//roboto-mono-v5-latin-regular.ec7832e5.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["search.a724f1bc.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/search.a724f1bc.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "2be400ec2879d462e871c0f8f255e3d6" -> null + - force_destroy = false -> null + - id = "search.a724f1bc.svg" -> null + - key = "search.a724f1bc.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//search.a724f1bc.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["search.e5ee4fc2.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/search.e5ee4fc2.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "6aa89b3d4533e41fc29c59209167d0a2" -> null + - force_destroy = false -> null + - id = "search.e5ee4fc2.svg" -> null + - key = "search.e5ee4fc2.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//search.e5ee4fc2.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-bold-webfont.21f8979c.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-bold-webfont.21f8979c.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "f12f6a2f439c99a103193981f69c3353" -> null + - force_destroy = false -> null + - id = "sourcesanspro-bold-webfont.21f8979c.woff2" -> null + - key = "sourcesanspro-bold-webfont.21f8979c.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-bold-webfont.21f8979c.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-bold-webfont.5436602a.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-bold-webfont.5436602a.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "f12f6a2f439c99a103193981f69c3353" -> null + - force_destroy = false -> null + - id = "sourcesanspro-bold-webfont.5436602a.woff2" -> null + - key = "sourcesanspro-bold-webfont.5436602a.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-bold-webfont.5436602a.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.84e64ba8.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-bolditalic-webfont.84e64ba8.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "dae8945d820658d0cb8f15764e441cc6" -> null + - force_destroy = false -> null + - id = "sourcesanspro-bolditalic-webfont.84e64ba8.woff2" -> null + - key = "sourcesanspro-bolditalic-webfont.84e64ba8.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-bolditalic-webfont.84e64ba8.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.b61b42d2.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-bolditalic-webfont.b61b42d2.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "dae8945d820658d0cb8f15764e441cc6" -> null + - force_destroy = false -> null + - id = "sourcesanspro-bolditalic-webfont.b61b42d2.woff2" -> null + - key = "sourcesanspro-bolditalic-webfont.b61b42d2.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-bolditalic-webfont.b61b42d2.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-italic-webfont.82ff76d1.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-italic-webfont.82ff76d1.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "8740838d2f0e9325e59b6e3c007a7130" -> null + - force_destroy = false -> null + - id = "sourcesanspro-italic-webfont.82ff76d1.woff2" -> null + - key = "sourcesanspro-italic-webfont.82ff76d1.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-italic-webfont.82ff76d1.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-italic-webfont.ab90d6d1.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-italic-webfont.ab90d6d1.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "8740838d2f0e9325e59b6e3c007a7130" -> null + - force_destroy = false -> null + - id = "sourcesanspro-italic-webfont.ab90d6d1.woff2" -> null + - key = "sourcesanspro-italic-webfont.ab90d6d1.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-italic-webfont.ab90d6d1.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-light-webfont.e490d910.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-light-webfont.e490d910.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "4572c51ee35f958fd2d4f04211c2ddc8" -> null + - force_destroy = false -> null + - id = "sourcesanspro-light-webfont.e490d910.woff2" -> null + - key = "sourcesanspro-light-webfont.e490d910.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-light-webfont.e490d910.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-light-webfont.e70d4904.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-light-webfont.e70d4904.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "4572c51ee35f958fd2d4f04211c2ddc8" -> null + - force_destroy = false -> null + - id = "sourcesanspro-light-webfont.e70d4904.woff2" -> null + - key = "sourcesanspro-light-webfont.e70d4904.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-light-webfont.e70d4904.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.05b42991.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-lightitalic-webfont.05b42991.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "ffb6369e594a83b16001ca58c1ee9039" -> null + - force_destroy = false -> null + - id = "sourcesanspro-lightitalic-webfont.05b42991.woff2" -> null + - key = "sourcesanspro-lightitalic-webfont.05b42991.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-lightitalic-webfont.05b42991.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.c14df9bc.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-lightitalic-webfont.c14df9bc.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "ffb6369e594a83b16001ca58c1ee9039" -> null + - force_destroy = false -> null + - id = "sourcesanspro-lightitalic-webfont.c14df9bc.woff2" -> null + - key = "sourcesanspro-lightitalic-webfont.c14df9bc.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-lightitalic-webfont.c14df9bc.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-regular-webfont.76c0bde6.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-regular-webfont.76c0bde6.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "d67b548b833d70dda3779916f5415e7e" -> null + - force_destroy = false -> null + - id = "sourcesanspro-regular-webfont.76c0bde6.woff2" -> null + - key = "sourcesanspro-regular-webfont.76c0bde6.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-regular-webfont.76c0bde6.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["sourcesanspro-regular-webfont.a9d6b459.woff2"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-regular-webfont.a9d6b459.woff2" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "font/woff2" -> null + - etag = "d67b548b833d70dda3779916f5415e7e" -> null + - force_destroy = false -> null + - id = "sourcesanspro-regular-webfont.a9d6b459.woff2" -> null + - key = "sourcesanspro-regular-webfont.a9d6b459.woff2" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//sourcesanspro-regular-webfont.a9d6b459.woff2" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.24de9d80.js"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.24de9d80.js" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/javascript" -> null + - etag = "988b70b40be2f0314481900a5c536520" -> null + - force_destroy = false -> null + - id = "ui.24de9d80.js" -> null + - key = "ui.24de9d80.js" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.24de9d80.js" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.24de9d80.js.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.24de9d80.js.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "d10b75eab24e3b17c82dfe793bb0c197-2" -> null + - force_destroy = false -> null + - id = "ui.24de9d80.js.map" -> null + - key = "ui.24de9d80.js.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.24de9d80.js.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "Fl3Qv6G9xdiJDQz3LQnsotLAeyh7xX.k" -> null + } + + # aws_s3_object.website_files["ui.31b563d9.js"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.31b563d9.js" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/javascript" -> null + - etag = "01f05bef6cafd19e0e1cb3bd102f0c1c" -> null + - force_destroy = false -> null + - id = "ui.31b563d9.js" -> null + - key = "ui.31b563d9.js" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.31b563d9.js" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.31b563d9.js.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.31b563d9.js.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "b4ab5047433ae9a15260c6280fab352f" -> null + - force_destroy = false -> null + - id = "ui.31b563d9.js.map" -> null + - key = "ui.31b563d9.js.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.31b563d9.js.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.383343a4.js"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.383343a4.js" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/javascript" -> null + - etag = "d3c3ac003d73acd50a8fced8fd15b181" -> null + - force_destroy = false -> null + - id = "ui.383343a4.js" -> null + - key = "ui.383343a4.js" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.383343a4.js" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.383343a4.js.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.383343a4.js.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "2061e6c7acb903ff425164499447f426-2" -> null + - force_destroy = false -> null + - id = "ui.383343a4.js.map" -> null + - key = "ui.383343a4.js.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.383343a4.js.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "mgw2l7fp8Ly.aZFG0D6FOdix_kMyZ_LU" -> null + } + + # aws_s3_object.website_files["ui.394b89ad.css"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.394b89ad.css" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "text/css; charset=utf-8" -> null + - etag = "07639841c3f2c11c538edbd93de57a6f" -> null + - force_destroy = false -> null + - id = "ui.394b89ad.css" -> null + - key = "ui.394b89ad.css" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.394b89ad.css" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.394b89ad.css.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.394b89ad.css.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "aaae7f6b549c1b425538ad3a5e8f1244" -> null + - force_destroy = false -> null + - id = "ui.394b89ad.css.map" -> null + - key = "ui.394b89ad.css.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.394b89ad.css.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.4540ed92.js"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.4540ed92.js" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/javascript" -> null + - etag = "b2a077c3c016057ede33fd7334f2ce63" -> null + - force_destroy = false -> null + - id = "ui.4540ed92.js" -> null + - key = "ui.4540ed92.js" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.4540ed92.js" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.4540ed92.js.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.4540ed92.js.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "700182aa0a2dcfdd93fc8291ecb10d29" -> null + - force_destroy = false -> null + - id = "ui.4540ed92.js.map" -> null + - key = "ui.4540ed92.js.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.4540ed92.js.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.4e22b301.js"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.4e22b301.js" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/javascript" -> null + - etag = "245a950194a7e0f0e041ab26cbc1d290" -> null + - force_destroy = false -> null + - id = "ui.4e22b301.js" -> null + - key = "ui.4e22b301.js" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.4e22b301.js" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.4e22b301.js.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.4e22b301.js.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "81b90eb36e2b718a3dad1f0a9d6c6a49" -> null + - force_destroy = false -> null + - id = "ui.4e22b301.js.map" -> null + - key = "ui.4e22b301.js.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.4e22b301.js.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.7c42bb32.css"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.7c42bb32.css" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "text/css; charset=utf-8" -> null + - etag = "c5a87ffc2db465beb40c24ba24485220" -> null + - force_destroy = false -> null + - id = "ui.7c42bb32.css" -> null + - key = "ui.7c42bb32.css" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.7c42bb32.css" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.7c42bb32.css.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.7c42bb32.css.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "d7e48f6e687b5c4e697c0e43999eec70" -> null + - force_destroy = false -> null + - id = "ui.7c42bb32.css.map" -> null + - key = "ui.7c42bb32.css.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.7c42bb32.css.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.9f367d21.css"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.9f367d21.css" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "text/css; charset=utf-8" -> null + - etag = "e7bd07d0eec9674884a3010871dbb5a2" -> null + - force_destroy = false -> null + - id = "ui.9f367d21.css" -> null + - key = "ui.9f367d21.css" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.9f367d21.css" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.9f367d21.css.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.9f367d21.css.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "348e434f681101e28ae799774cbc7220" -> null + - force_destroy = false -> null + - id = "ui.9f367d21.css.map" -> null + - key = "ui.9f367d21.css.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.9f367d21.css.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.b82c2ba3.js"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.b82c2ba3.js" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/javascript" -> null + - etag = "6cfa17821e273870c789a3b4daa991d0" -> null + - force_destroy = false -> null + - id = "ui.b82c2ba3.js" -> null + - key = "ui.b82c2ba3.js" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.b82c2ba3.js" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "XcX7iLmWxLkmtoc0JirYJGB9Xf.giweL" -> null + } + + # aws_s3_object.website_files["ui.b82c2ba3.js.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.b82c2ba3.js.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "bb5a0d39b9f9186f155cde01e3abccb9-2" -> null + - force_destroy = false -> null + - id = "ui.b82c2ba3.js.map" -> null + - key = "ui.b82c2ba3.js.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.b82c2ba3.js.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "6S66PjGYibtAny2mODFhQ1nI9gxbbaOs" -> null + } + + # aws_s3_object.website_files["ui.d119b665.js"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.d119b665.js" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/javascript" -> null + - etag = "5ac440bda819fad729551a3c52f8b9b9" -> null + - force_destroy = false -> null + - id = "ui.d119b665.js" -> null + - key = "ui.d119b665.js" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.d119b665.js" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "CCpTPeZi0Y5Mu6jTAQZOv.qxK1K53Laz" -> null + } + + # aws_s3_object.website_files["ui.d119b665.js.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.d119b665.js.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "517bef80e4a43d19feb4c2dad09b5710-2" -> null + - force_destroy = false -> null + - id = "ui.d119b665.js.map" -> null + - key = "ui.d119b665.js.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.d119b665.js.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "TdBNzNZqO4vd35nTak0MFotEEy.8etq7" -> null + } + + # aws_s3_object.website_files["ui.ee3dfc51.js"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.ee3dfc51.js" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/javascript" -> null + - etag = "50e0bef214f9a8064d4de2c15614670f" -> null + - force_destroy = false -> null + - id = "ui.ee3dfc51.js" -> null + - key = "ui.ee3dfc51.js" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.ee3dfc51.js" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["ui.ee3dfc51.js.map"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.ee3dfc51.js.map" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "application/octet-stream" -> null + - etag = "3ab313132e57709c6cb8c46431a386b5" -> null + - force_destroy = false -> null + - id = "ui.ee3dfc51.js.map" -> null + - key = "ui.ee3dfc51.js.map" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//ui.ee3dfc51.js.map" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["unfold_more.1f608602.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/unfold_more.1f608602.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "6020c06854b7cbe641066b9291ac9e77" -> null + - force_destroy = false -> null + - id = "unfold_more.1f608602.svg" -> null + - key = "unfold_more.1f608602.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//unfold_more.1f608602.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["unfold_more.46c00529.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/unfold_more.46c00529.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "acf8696ec8949f878b06bb19da32f1fb" -> null + - force_destroy = false -> null + - id = "unfold_more.46c00529.svg" -> null + - key = "unfold_more.46c00529.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//unfold_more.46c00529.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["us_flag_small.9c3c6ab8.png"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/us_flag_small.9c3c6ab8.png" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/png" -> null + - etag = "ab5090f5b92619c69a7dd2e2ee05b3e5" -> null + - force_destroy = false -> null + - id = "us_flag_small.9c3c6ab8.png" -> null + - key = "us_flag_small.9c3c6ab8.png" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//us_flag_small.9c3c6ab8.png" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["us_flag_small.e642a40d.png"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/us_flag_small.e642a40d.png" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/png" -> null + - etag = "ab5090f5b92619c69a7dd2e2ee05b3e5" -> null + - force_destroy = false -> null + - id = "us_flag_small.e642a40d.png" -> null + - key = "us_flag_small.e642a40d.png" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//us_flag_small.e642a40d.png" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["warning.4cc8d763.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/warning.4cc8d763.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "2d22a8d3f2aef0ce4599b699f3807e72" -> null + - force_destroy = false -> null + - id = "warning.4cc8d763.svg" -> null + - key = "warning.4cc8d763.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//warning.4cc8d763.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_s3_object.website_files["warning.ce0fedc9.svg"] will be destroyed + - resource "aws_s3_object" "website_files" { + - arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/warning.ce0fedc9.svg" -> null + - bucket = "document-extractor-dev-website-328307993388" -> null + - bucket_key_enabled = false -> null + - content_type = "image/svg+xml" -> null + - etag = "93b6b1635a5809f74f86425e3344a32d" -> null + - force_destroy = false -> null + - id = "warning.ce0fedc9.svg" -> null + - key = "warning.ce0fedc9.svg" -> null + - metadata = {} -> null + - server_side_encryption = "AES256" -> null + - source = "./../ui/dist//warning.ce0fedc9.svg" -> null + - storage_class = "STANDARD" -> null + - tags = { + - "project" = "document-extractor" + } -> null + - tags_all = { + - "project" = "document-extractor" + } -> null + - version_id = "null" -> null + } + + # aws_secretsmanager_secret.password will be destroyed + - resource "aws_secretsmanager_secret" "password" { + - arn = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-password-5KgJiD" -> null + - force_overwrite_replica_secret = false -> null + - id = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-password-5KgJiD" -> null + - name = "document-extractor-dev-password" -> null + - recovery_window_in_days = 30 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_secretsmanager_secret.private_key will be destroyed + - resource "aws_secretsmanager_secret" "private_key" { + - arn = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-private-key-PI2EsC" -> null + - force_overwrite_replica_secret = false -> null + - id = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-private-key-PI2EsC" -> null + - name = "document-extractor-dev-private-key" -> null + - recovery_window_in_days = 30 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_secretsmanager_secret.public_key will be destroyed + - resource "aws_secretsmanager_secret" "public_key" { + - arn = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-public-key-PI2EsC" -> null + - force_overwrite_replica_secret = false -> null + - id = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-public-key-PI2EsC" -> null + - name = "document-extractor-dev-public-key" -> null + - recovery_window_in_days = 30 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_secretsmanager_secret.username will be destroyed + - resource "aws_secretsmanager_secret" "username" { + - arn = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-username-gKcWPC" -> null + - force_overwrite_replica_secret = false -> null + - id = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-username-gKcWPC" -> null + - name = "document-extractor-dev-username" -> null + - recovery_window_in_days = 30 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + } + + # aws_sqs_queue.queue_to_dynamo will be destroyed + - resource "aws_sqs_queue" "queue_to_dynamo" { + - arn = "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb" -> null + - content_based_deduplication = false -> null + - delay_seconds = 0 -> null + - fifo_queue = false -> null + - id = "https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb" -> null + - kms_data_key_reuse_period_seconds = 300 -> null + - kms_master_key_id = "2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - max_message_size = 262144 -> null + - message_retention_seconds = 345600 -> null + - name = "document-extractor-dev-to-dynamodb" -> null + - receive_wait_time_seconds = 0 -> null + - sqs_managed_sse_enabled = false -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - url = "https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb" -> null + - visibility_timeout_seconds = 30 -> null + } + + # null_resource.invalidate_cloudfront will be destroyed + - resource "null_resource" "invalidate_cloudfront" { + - id = "450350717588313943" -> null + - triggers = { + - "always_run" = "2025-05-29T15:25:27Z" + } -> null + } + + # module.document_endpoints.aws_api_gateway_integration.lambda_integration[0] will be destroyed + - resource "aws_api_gateway_integration" "lambda_integration" { + - cache_key_parameters = [] -> null + - cache_namespace = "ljz2z9" -> null + - connection_type = "INTERNET" -> null + - http_method = "POST" -> null + - id = "agi-ic9avcgf3l-ljz2z9-POST" -> null + - integration_http_method = "POST" -> null + - passthrough_behavior = "WHEN_NO_MATCH" -> null + - request_parameters = {} -> null + - request_templates = {} -> null + - resource_id = "ljz2z9" -> null + - rest_api_id = "ic9avcgf3l" -> null + - timeout_milliseconds = 29000 -> null + - type = "AWS_PROXY" -> null + - uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document/invocations" -> null + } + + # module.document_endpoints.aws_api_gateway_method.http_method[0] will be destroyed + - resource "aws_api_gateway_method" "http_method" { + - api_key_required = false -> null + - authorization = "CUSTOM" -> null + - authorization_scopes = [] -> null + - authorizer_id = "bq0hho" -> null + - http_method = "POST" -> null + - id = "agm-ic9avcgf3l-ljz2z9-POST" -> null + - request_models = {} -> null + - request_parameters = {} -> null + - resource_id = "ljz2z9" -> null + - rest_api_id = "ic9avcgf3l" -> null + } + + # module.document_endpoints.aws_api_gateway_resource.resource_path will be destroyed + - resource "aws_api_gateway_resource" "resource_path" { + - id = "ljz2z9" -> null + - parent_id = "qdlfjof80i" -> null + - path = "/document" -> null + - path_part = "document" -> null + - rest_api_id = "ic9avcgf3l" -> null + } + + # module.document_endpoints.aws_lambda_function.function[0] will be destroyed + - resource "aws_lambda_function" "function" { + - architectures = [ + - "arm64", + ] -> null + - arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document" -> null + - code_sha256 = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - filename = "./../backend/dist/lambda.zip" -> null + - function_name = "document-extractor-dev-create-document" -> null + - handler = "src.external.aws.lambdas.s3_file_upload.lambda_handler" -> null + - id = "document-extractor-dev-create-document" -> null + - invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document/invocations" -> null + - kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - last_modified = "2025-05-29T15:26:04.000+0000" -> null + - layers = [] -> null + - memory_size = 256 -> null + - package_type = "Zip" -> null + - publish = true -> null + - qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document:6" -> null + - qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document:6/invocations" -> null + - reserved_concurrent_executions = -1 -> null + - role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" -> null + - runtime = "python3.13" -> null + - skip_destroy = false -> null + - source_code_hash = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - source_code_size = 19659547 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - timeout = 30 -> null + - version = "6" -> null + + - environment { + - variables = (sensitive value) -> null + } + + - ephemeral_storage { + - size = 512 -> null + } + + - logging_config { + - log_format = "Text" -> null + - log_group = "/aws/lambda/document-extractor-dev-create-document" -> null + } + + - tracing_config { + - mode = "PassThrough" -> null + } + } + + # module.document_endpoints.aws_lambda_permission.api_gateway[0] will be destroyed + - resource "aws_lambda_permission" "api_gateway" { + - action = "lambda:InvokeFunction" -> null + - function_name = "document-extractor-dev-create-document" -> null + - id = "AllowExecutionFromApiGateway" -> null + - principal = "apigateway.amazonaws.com" -> null + - source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*/*" -> null + - statement_id = "AllowExecutionFromApiGateway" -> null + } + + # module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0] will be destroyed + - resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + - function_name = "document-extractor-dev-create-document" -> null + - id = "document-extractor-dev-create-document,6" -> null + - provisioned_concurrent_executions = 1 -> null + - qualifier = "6" -> null + - skip_destroy = false -> null + } + + # module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[0] will be destroyed + - resource "aws_api_gateway_integration" "lambda_integration" { + - cache_key_parameters = [] -> null + - cache_namespace = "g3ywx2" -> null + - connection_type = "INTERNET" -> null + - http_method = "GET" -> null + - id = "agi-ic9avcgf3l-g3ywx2-GET" -> null + - integration_http_method = "POST" -> null + - passthrough_behavior = "WHEN_NO_MATCH" -> null + - request_parameters = {} -> null + - request_templates = {} -> null + - resource_id = "g3ywx2" -> null + - rest_api_id = "ic9avcgf3l" -> null + - timeout_milliseconds = 29000 -> null + - type = "AWS_PROXY" -> null + - uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document/invocations" -> null + } + + # module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[1] will be destroyed + - resource "aws_api_gateway_integration" "lambda_integration" { + - cache_key_parameters = [] -> null + - cache_namespace = "g3ywx2" -> null + - connection_type = "INTERNET" -> null + - http_method = "PUT" -> null + - id = "agi-ic9avcgf3l-g3ywx2-PUT" -> null + - integration_http_method = "POST" -> null + - passthrough_behavior = "WHEN_NO_MATCH" -> null + - request_parameters = {} -> null + - request_templates = {} -> null + - resource_id = "g3ywx2" -> null + - rest_api_id = "ic9avcgf3l" -> null + - timeout_milliseconds = 29000 -> null + - type = "AWS_PROXY" -> null + - uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document/invocations" -> null + } + + # module.document_id_endpoints.aws_api_gateway_method.http_method[0] will be destroyed + - resource "aws_api_gateway_method" "http_method" { + - api_key_required = false -> null + - authorization = "CUSTOM" -> null + - authorization_scopes = [] -> null + - authorizer_id = "bq0hho" -> null + - http_method = "GET" -> null + - id = "agm-ic9avcgf3l-g3ywx2-GET" -> null + - request_models = {} -> null + - request_parameters = {} -> null + - resource_id = "g3ywx2" -> null + - rest_api_id = "ic9avcgf3l" -> null + } + + # module.document_id_endpoints.aws_api_gateway_method.http_method[1] will be destroyed + - resource "aws_api_gateway_method" "http_method" { + - api_key_required = false -> null + - authorization = "CUSTOM" -> null + - authorization_scopes = [] -> null + - authorizer_id = "bq0hho" -> null + - http_method = "PUT" -> null + - id = "agm-ic9avcgf3l-g3ywx2-PUT" -> null + - request_models = {} -> null + - request_parameters = {} -> null + - resource_id = "g3ywx2" -> null + - rest_api_id = "ic9avcgf3l" -> null + } + + # module.document_id_endpoints.aws_api_gateway_resource.resource_path will be destroyed + - resource "aws_api_gateway_resource" "resource_path" { + - id = "g3ywx2" -> null + - parent_id = "ljz2z9" -> null + - path = "/document/{document_id}" -> null + - path_part = "{document_id}" -> null + - rest_api_id = "ic9avcgf3l" -> null + } + + # module.document_id_endpoints.aws_lambda_function.function[0] will be destroyed + - resource "aws_lambda_function" "function" { + - architectures = [ + - "arm64", + ] -> null + - arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document" -> null + - code_sha256 = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - filename = "./../backend/dist/lambda.zip" -> null + - function_name = "document-extractor-dev-get-document" -> null + - handler = "src.external.aws.lambdas.get_extracted_document.lambda_handler" -> null + - id = "document-extractor-dev-get-document" -> null + - invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document/invocations" -> null + - kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - last_modified = "2025-05-29T15:26:23.000+0000" -> null + - layers = [] -> null + - memory_size = 256 -> null + - package_type = "Zip" -> null + - publish = true -> null + - qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document:6" -> null + - qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document:6/invocations" -> null + - reserved_concurrent_executions = -1 -> null + - role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" -> null + - runtime = "python3.13" -> null + - skip_destroy = false -> null + - source_code_hash = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - source_code_size = 19659547 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - timeout = 30 -> null + - version = "6" -> null + + - environment { + - variables = (sensitive value) -> null + } + + - ephemeral_storage { + - size = 512 -> null + } + + - logging_config { + - log_format = "Text" -> null + - log_group = "/aws/lambda/document-extractor-dev-get-document" -> null + } + + - tracing_config { + - mode = "PassThrough" -> null + } + } + + # module.document_id_endpoints.aws_lambda_function.function[1] will be destroyed + - resource "aws_lambda_function" "function" { + - architectures = [ + - "arm64", + ] -> null + - arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document" -> null + - code_sha256 = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - filename = "./../backend/dist/lambda.zip" -> null + - function_name = "document-extractor-dev-update-document" -> null + - handler = "src.external.aws.lambdas.update_extracted_document.lambda_handler" -> null + - id = "document-extractor-dev-update-document" -> null + - invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document/invocations" -> null + - kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - last_modified = "2025-05-29T15:26:33.000+0000" -> null + - layers = [] -> null + - memory_size = 256 -> null + - package_type = "Zip" -> null + - publish = true -> null + - qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document:6" -> null + - qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document:6/invocations" -> null + - reserved_concurrent_executions = -1 -> null + - role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" -> null + - runtime = "python3.13" -> null + - skip_destroy = false -> null + - source_code_hash = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - source_code_size = 19659547 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - timeout = 30 -> null + - version = "6" -> null + + - environment { + - variables = (sensitive value) -> null + } + + - ephemeral_storage { + - size = 512 -> null + } + + - logging_config { + - log_format = "Text" -> null + - log_group = "/aws/lambda/document-extractor-dev-update-document" -> null + } + + - tracing_config { + - mode = "PassThrough" -> null + } + } + + # module.document_id_endpoints.aws_lambda_permission.api_gateway[0] will be destroyed + - resource "aws_lambda_permission" "api_gateway" { + - action = "lambda:InvokeFunction" -> null + - function_name = "document-extractor-dev-get-document" -> null + - id = "AllowExecutionFromApiGateway" -> null + - principal = "apigateway.amazonaws.com" -> null + - source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*/*" -> null + - statement_id = "AllowExecutionFromApiGateway" -> null + } + + # module.document_id_endpoints.aws_lambda_permission.api_gateway[1] will be destroyed + - resource "aws_lambda_permission" "api_gateway" { + - action = "lambda:InvokeFunction" -> null + - function_name = "document-extractor-dev-update-document" -> null + - id = "AllowExecutionFromApiGateway" -> null + - principal = "apigateway.amazonaws.com" -> null + - source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*/*" -> null + - statement_id = "AllowExecutionFromApiGateway" -> null + } + + # module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0] will be destroyed + - resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + - function_name = "document-extractor-dev-get-document" -> null + - id = "document-extractor-dev-get-document,6" -> null + - provisioned_concurrent_executions = 1 -> null + - qualifier = "6" -> null + - skip_destroy = false -> null + } + + # module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1] will be destroyed + - resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + - function_name = "document-extractor-dev-update-document" -> null + - id = "document-extractor-dev-update-document,6" -> null + - provisioned_concurrent_executions = 1 -> null + - qualifier = "6" -> null + - skip_destroy = false -> null + } + + # module.token_endpoints.aws_api_gateway_integration.lambda_integration[0] will be destroyed + - resource "aws_api_gateway_integration" "lambda_integration" { + - cache_key_parameters = [] -> null + - cache_namespace = "dmuib4" -> null + - connection_type = "INTERNET" -> null + - http_method = "POST" -> null + - id = "agi-ic9avcgf3l-dmuib4-POST" -> null + - integration_http_method = "POST" -> null + - passthrough_behavior = "WHEN_NO_MATCH" -> null + - request_parameters = {} -> null + - request_templates = {} -> null + - resource_id = "dmuib4" -> null + - rest_api_id = "ic9avcgf3l" -> null + - timeout_milliseconds = 29000 -> null + - type = "AWS_PROXY" -> null + - uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token/invocations" -> null + } + + # module.token_endpoints.aws_api_gateway_method.http_method[0] will be destroyed + - resource "aws_api_gateway_method" "http_method" { + - api_key_required = false -> null + - authorization = "NONE" -> null + - authorization_scopes = [] -> null + - http_method = "POST" -> null + - id = "agm-ic9avcgf3l-dmuib4-POST" -> null + - request_models = {} -> null + - request_parameters = {} -> null + - resource_id = "dmuib4" -> null + - rest_api_id = "ic9avcgf3l" -> null + } + + # module.token_endpoints.aws_api_gateway_resource.resource_path will be destroyed + - resource "aws_api_gateway_resource" "resource_path" { + - id = "dmuib4" -> null + - parent_id = "qdlfjof80i" -> null + - path = "/token" -> null + - path_part = "token" -> null + - rest_api_id = "ic9avcgf3l" -> null + } + + # module.token_endpoints.aws_lambda_function.function[0] will be destroyed + - resource "aws_lambda_function" "function" { + - architectures = [ + - "arm64", + ] -> null + - arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token" -> null + - code_sha256 = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - filename = "./../backend/dist/lambda.zip" -> null + - function_name = "document-extractor-dev-token" -> null + - handler = "src.external.aws.lambdas.token.lambda_handler" -> null + - id = "document-extractor-dev-token" -> null + - invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token/invocations" -> null + - kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" -> null + - last_modified = "2025-05-29T15:26:13.000+0000" -> null + - layers = [] -> null + - memory_size = 256 -> null + - package_type = "Zip" -> null + - publish = true -> null + - qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token:6" -> null + - qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token:6/invocations" -> null + - reserved_concurrent_executions = -1 -> null + - role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" -> null + - runtime = "python3.13" -> null + - skip_destroy = false -> null + - source_code_hash = "ekgy8a7TcDRaFnEB1fnqgFjx1wkKW4sEmjCr8BcYHWY=" -> null + - source_code_size = 19659547 -> null + - tags = {} -> null + - tags_all = { + - "project" = "document-extractor-dev" + } -> null + - timeout = 30 -> null + - version = "6" -> null + + - environment { + - variables = (sensitive value) -> null + } + + - ephemeral_storage { + - size = 512 -> null + } + + - logging_config { + - log_format = "Text" -> null + - log_group = "/aws/lambda/document-extractor-dev-token" -> null + } + + - tracing_config { + - mode = "PassThrough" -> null + } + } + + # module.token_endpoints.aws_lambda_permission.api_gateway[0] will be destroyed + - resource "aws_lambda_permission" "api_gateway" { + - action = "lambda:InvokeFunction" -> null + - function_name = "document-extractor-dev-token" -> null + - id = "AllowExecutionFromApiGateway" -> null + - principal = "apigateway.amazonaws.com" -> null + - source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*/*" -> null + - statement_id = "AllowExecutionFromApiGateway" -> null + } + + # module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0] will be destroyed + - resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + - function_name = "document-extractor-dev-token" -> null + - id = "document-extractor-dev-token,6" -> null + - provisioned_concurrent_executions = 1 -> null + - qualifier = "6" -> null + - skip_destroy = false -> null + } + +Plan: 0 to add, 0 to change, 200 to destroy. + +Changes to Outputs: + - distribution_id = "ENWTZLUASCDJZ" -> null + +Do you really want to destroy all resources? + Terraform will destroy all your managed infrastructure, as shown above. + There is no undo. Only 'yes' will be accepted to confirm. + + Enter a value: yes + +null_resource.invalidate_cloudfront: Destroying... [id=450350717588313943] +null_resource.invalidate_cloudfront: Destruction complete after 0s +aws_secretsmanager_secret.public_key: Destroying... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-public-key-PI2EsC] +aws_s3_object.website_files["close.bf51193b.svg"]: Destroying... [id=close.bf51193b.svg] +aws_s3_object.website_files["ui.ee3dfc51.js"]: Destroying... [id=ui.ee3dfc51.js] +aws_s3_object.website_files["roboto-mono-v5-latin-300.98978b08.woff2"]: Destroying... [id=roboto-mono-v5-latin-300.98978b08.woff2] +aws_s3_bucket_website_configuration.website_configuration: Destroying... [id=document-extractor-dev-website-328307993388] +aws_s3_object.website_files["arrow_back.c9ac1a0e.svg"]: Destroying... [id=arrow_back.c9ac1a0e.svg] +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.c14df9bc.woff2"]: Destroying... [id=sourcesanspro-lightitalic-webfont.c14df9bc.woff2] +aws_s3_object.website_files["checkbox-indeterminate-alt.5e7abfcf.svg"]: Destroying... [id=checkbox-indeterminate-alt.5e7abfcf.svg] +aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: Destroying... [id=b32111fd-5cd6-45fd-b193-ac415949bc48] +aws_s3_object.website_files["Latin-Merriweather-Light.fbc4393c.woff2"]: Destroying... [id=Latin-Merriweather-Light.fbc4393c.woff2] +aws_s3_bucket_website_configuration.website_configuration: Destruction complete after 1s +aws_s3_object.website_files["roboto-mono-v5-latin-italic.7226ad2c.woff2"]: Destroying... [id=roboto-mono-v5-latin-italic.7226ad2c.woff2] +aws_secretsmanager_secret.public_key: Destruction complete after 1s +aws_s3_object.website_files["checkbox-indeterminate.db2c5d96.svg"]: Destroying... [id=checkbox-indeterminate.db2c5d96.svg] +aws_s3_object.website_files["Latin-Merriweather-Light.fbc4393c.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["ui.b82c2ba3.js"]: Destroying... [id=ui.b82c2ba3.js] +aws_s3_object.website_files["ui.ee3dfc51.js"]: Destruction complete after 1s +aws_s3_object.website_files["ui.d119b665.js"]: Destroying... [id=ui.d119b665.js] +aws_s3_object.website_files["close.bf51193b.svg"]: Destruction complete after 1s +aws_s3_object.website_files["error.35f61f4b.svg"]: Destroying... [id=error.35f61f4b.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300.98978b08.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.c14df9bc.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["ui.b82c2ba3.js.map"]: Destroying... [id=ui.b82c2ba3.js.map] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.e58256dc.woff2"]: Destroying... [id=Latin-Merriweather-BoldItalic.e58256dc.woff2] +aws_s3_object.website_files["checkbox-indeterminate-alt.5e7abfcf.svg"]: Destruction complete after 1s +aws_s3_object.website_files["navigate_far_before.65b60266.svg"]: Destroying... [id=navigate_far_before.65b60266.svg] +aws_s3_object.website_files["arrow_back.c9ac1a0e.svg"]: Destruction complete after 1s +aws_s3_object.website_files["index.html"]: Destroying... [id=index.html] +aws_s3_object.website_files["roboto-mono-v5-latin-italic.7226ad2c.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["checkbox-indeterminate.db2c5d96.svg"]: Destruction complete after 0s +aws_s3_bucket_public_access_block.private_website: Destroying... [id=document-extractor-dev-website-328307993388] +aws_s3_object.website_files["search.e5ee4fc2.svg"]: Destroying... [id=search.e5ee4fc2.svg] +aws_s3_object.website_files["ui.b82c2ba3.js"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-Bold.967b2108.woff2"]: Destroying... [id=Latin-Merriweather-Bold.967b2108.woff2] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.e58256dc.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["error.35f61f4b.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.d119b665.js"]: Destruction complete after 0s +aws_secretsmanager_secret.private_key: Destroying... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-private-key-PI2EsC] +aws_s3_object.website_files["checkbox-indeterminate.08aa88cd.svg"]: Destroying... [id=checkbox-indeterminate.08aa88cd.svg] +aws_s3_object.website_files["sourcesanspro-bold-webfont.21f8979c.woff2"]: Destroying... [id=sourcesanspro-bold-webfont.21f8979c.woff2] +aws_s3_object.website_files["navigate_far_before.65b60266.svg"]: Destruction complete after 0s +aws_s3_object.website_files["file-excel.323d5d7b.svg"]: Destroying... [id=file-excel.323d5d7b.svg] +aws_s3_object.website_files["search.e5ee4fc2.svg"]: Destruction complete after 0s +aws_iam_role_policy_attachment.attach_s3_permission_to_role: Destroying... [id=document-extractor-dev-lambda-execution-role-20250522153114543600000008] +aws_secretsmanager_secret.private_key: Destruction complete after 0s +aws_s3_object.website_files["ui.394b89ad.css.map"]: Destroying... [id=ui.394b89ad.css.map] +aws_s3_object.website_files["ui.b82c2ba3.js.map"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-Bold.967b2108.woff2"]: Destruction complete after 0s +aws_iam_role_policy_attachment.attach_dynamodb_permission_to_role: Destroying... [id=document-extractor-dev-lambda-execution-role-20250522153115634300000009] +aws_s3_object.website_files["file-word.75e83f68.svg"]: Destroying... [id=file-word.75e83f68.svg] +aws_s3_object.website_files["sourcesanspro-bold-webfont.21f8979c.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["info.40b16694.svg"]: Destroying... [id=info.40b16694.svg] +aws_s3_object.website_files["checkbox-indeterminate.08aa88cd.svg"]: Destruction complete after 0s +aws_s3_object.website_files["navigate_far_before.904e4c63.svg"]: Destroying... [id=navigate_far_before.904e4c63.svg] +aws_s3_bucket_public_access_block.private_website: Destruction complete after 0s +aws_s3_object.website_files["ui.31b563d9.js"]: Destroying... [id=ui.31b563d9.js] +aws_iam_role_policy_attachment.attach_s3_permission_to_role: Destruction complete after 0s +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.b61b42d2.woff2"]: Destroying... [id=sourcesanspro-bolditalic-webfont.b61b42d2.woff2] +aws_s3_object.website_files["file-excel.323d5d7b.svg"]: Destruction complete after 1s +aws_s3_object.website_files["Latin-Merriweather-Regular.04046ce8.woff2"]: Destroying... [id=Latin-Merriweather-Regular.04046ce8.woff2] +aws_iam_role_policy_attachment.attach_dynamodb_permission_to_role: Destruction complete after 1s +aws_s3_object.website_files["file-word.896caaf0.svg"]: Destroying... [id=file-word.896caaf0.svg] +aws_s3_object.website_files["index.html"]: Destruction complete after 1s +aws_s3_object.website_files["launch.2cde378c.svg"]: Destroying... [id=launch.2cde378c.svg] +aws_s3_object.website_files["ui.394b89ad.css.map"]: Destruction complete after 1s +aws_s3_object.website_files["calendar_today.ce6eaa81.svg"]: Destroying... [id=calendar_today.ce6eaa81.svg] +aws_s3_object.website_files["file-word.75e83f68.svg"]: Destruction complete after 1s +aws_s3_object.website_files["check_circle.120511e4.svg"]: Destroying... [id=check_circle.120511e4.svg] +aws_s3_object.website_files["navigate_far_before.904e4c63.svg"]: Destruction complete after 1s +aws_s3_object.website_files["info.40b16694.svg"]: Destruction complete after 1s +aws_s3_object.website_files["sourcesanspro-regular-webfont.76c0bde6.woff2"]: Destroying... [id=sourcesanspro-regular-webfont.76c0bde6.woff2] +aws_s3_object.website_files["icon-https.b6eca6f6.svg"]: Destroying... [id=icon-https.b6eca6f6.svg] +aws_s3_object.website_files["ui.31b563d9.js"]: Destruction complete after 1s +aws_s3_object.website_files["ui.ee3dfc51.js.map"]: Destroying... [id=ui.ee3dfc51.js.map] +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.b61b42d2.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["Latin-Merriweather-Bold.cdbf7e95.woff2"]: Destroying... [id=Latin-Merriweather-Bold.cdbf7e95.woff2] +aws_s3_object.website_files["Latin-Merriweather-Regular.04046ce8.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["pdf.worker.min.mjs"]: Destroying... [id=pdf.worker.min.mjs] +aws_s3_object.website_files["file-word.896caaf0.svg"]: Destruction complete after 0s +aws_s3_object.website_files["sourcesanspro-light-webfont.e490d910.woff2"]: Destroying... [id=sourcesanspro-light-webfont.e490d910.woff2] +aws_s3_object.website_files["launch.2cde378c.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.24de9d80.js"]: Destroying... [id=ui.24de9d80.js] +aws_s3_object.website_files["calendar_today.ce6eaa81.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.31b563d9.js.map"]: Destroying... [id=ui.31b563d9.js.map] +aws_s3_object.website_files["check_circle.120511e4.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.d119b665.js.map"]: Destroying... [id=ui.d119b665.js.map] +aws_s3_object.website_files["icon-https.b6eca6f6.svg"]: Destruction complete after 0s +aws_s3_object.website_files["arrow_back.e4913c69.svg"]: Destroying... [id=arrow_back.e4913c69.svg] +aws_s3_object.website_files["ui.ee3dfc51.js.map"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-Light.1ad1a1d8.woff2"]: Destroying... [id=Latin-Merriweather-Light.1ad1a1d8.woff2] +aws_s3_object.website_files["sourcesanspro-regular-webfont.76c0bde6.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["ui.383343a4.js.map"]: Destroying... [id=ui.383343a4.js.map] +aws_s3_object.website_files["Latin-Merriweather-Bold.cdbf7e95.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["roboto-mono-v5-latin-regular.ec7832e5.woff2"]: Destroying... [id=roboto-mono-v5-latin-regular.ec7832e5.woff2] +aws_s3_object.website_files["pdf.worker.min.mjs"]: Destruction complete after 0s +aws_s3_object.website_files["navigate_next.bc7c1a4e.svg"]: Destroying... [id=navigate_next.bc7c1a4e.svg] +aws_s3_object.website_files["sourcesanspro-light-webfont.e490d910.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["ui.7c42bb32.css.map"]: Destroying... [id=ui.7c42bb32.css.map] +aws_s3_object.website_files["ui.24de9d80.js"]: Destruction complete after 0s +aws_s3_object.website_files["check--blue-60v.982d9f95.svg"]: Destroying... [id=check--blue-60v.982d9f95.svg] +aws_s3_object.website_files["ui.31b563d9.js.map"]: Destruction complete after 0s +aws_s3_object.website_files["expand_less.5bb6de10.svg"]: Destroying... [id=expand_less.5bb6de10.svg] +aws_s3_object.website_files["arrow_back.e4913c69.svg"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-Light.1ad1a1d8.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.03671595.woff2"]: Destroying... [id=roboto-mono-v5-latin-700italic.03671595.woff2] +aws_s3_object.website_files["loader.7402c183.svg"]: Destroying... [id=loader.7402c183.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-regular.ec7832e5.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.af4ea91a.woff2"]: Destroying... [id=roboto-mono-v5-latin-300italic.af4ea91a.woff2] +aws_s3_object.website_files["navigate_next.bc7c1a4e.svg"]: Destruction complete after 0s +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destroying... [id=document-extractor-dev-create-document,6] +aws_s3_object.website_files["ui.7c42bb32.css.map"]: Destruction complete after 0s +aws_s3_object.website_files["ui.9f367d21.css.map"]: Destroying... [id=ui.9f367d21.css.map] +aws_s3_object.website_files["ui.d119b665.js.map"]: Destruction complete after 1s +aws_s3_object.website_files["icon-dot-gov.86228b6b.svg"]: Destroying... [id=icon-dot-gov.86228b6b.svg] +aws_s3_object.website_files["check--blue-60v.982d9f95.svg"]: Destruction complete after 1s +aws_s3_object.website_files["navigate_next.fb4be146.svg"]: Destroying... [id=navigate_next.fb4be146.svg] +aws_s3_object.website_files["expand_less.5bb6de10.svg"]: Destruction complete after 1s +aws_s3_object.website_files["error--white.dcc8cafd.svg"]: Destroying... [id=error--white.dcc8cafd.svg] +module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destruction complete after 1s +module.document_endpoints.aws_lambda_permission.api_gateway[0]: Destroying... [id=AllowExecutionFromApiGateway] +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.03671595.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["loader.7402c183.svg"]: Destruction complete after 1s +aws_s3_object.website_files["file-video.d309c165.svg"]: Destroying... [id=file-video.d309c165.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.1710c37a.woff2"]: Destroying... [id=roboto-mono-v5-latin-300italic.1710c37a.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.af4ea91a.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["warning.4cc8d763.svg"]: Destroying... [id=warning.4cc8d763.svg] +aws_s3_object.website_files["ui.383343a4.js.map"]: Destruction complete after 1s +aws_s3_object.website_files["correct8.10e8a7ef.svg"]: Destroying... [id=correct8.10e8a7ef.svg] +aws_s3_object.website_files["ui.9f367d21.css.map"]: Destruction complete after 1s +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Destroying... [id=document-extractor-dev-text-extract,6] +module.document_endpoints.aws_lambda_permission.api_gateway[0]: Destruction complete after 0s +aws_s3_object.website_files["sourcesanspro-italic-webfont.ab90d6d1.woff2"]: Destroying... [id=sourcesanspro-italic-webfont.ab90d6d1.woff2] +aws_s3_object.website_files["icon-dot-gov.86228b6b.svg"]: Destruction complete after 0s +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Destroying... [id=document-extractor-dev-update-document,6] +aws_s3_object.website_files["navigate_next.fb4be146.svg"]: Destruction complete after 0s +aws_s3_object.website_files["error--white.dcc8cafd.svg"]: Destruction complete after 0s +aws_s3_object.website_files["check--blue-60v.7604a0f6.svg"]: Destroying... [id=check--blue-60v.7604a0f6.svg] +aws_s3_object.website_files["add.d491396d.svg"]: Destroying... [id=add.d491396d.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300italic.1710c37a.woff2"]: Destruction complete after 0s +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destroying... [id=document-extractor-dev-token,6] +aws_s3_object.website_files["file-video.d309c165.svg"]: Destruction complete after 0s +aws_s3_object.website_files["GSA-logo.ece32ed3.svg"]: Destroying... [id=GSA-logo.ece32ed3.svg] +aws_lambda_provisioned_concurrency_config.text_extract_concurrency: Destruction complete after 0s +aws_s3_object.website_files["correct8-alt.c14d1430.svg"]: Destroying... [id=correct8-alt.c14d1430.svg] +aws_s3_object.website_files["warning.4cc8d763.svg"]: Destruction complete after 0s +aws_s3_object.website_files["navigate_before.be211c2e.svg"]: Destroying... [id=navigate_before.be211c2e.svg] +aws_s3_object.website_files["correct8.10e8a7ef.svg"]: Destruction complete after 0s +aws_s3_object.website_files["hero.abe074f7.jpg"]: Destroying... [id=hero.abe074f7.jpg] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: Destruction complete after 0s +module.token_endpoints.aws_lambda_permission.api_gateway[0]: Destroying... [id=AllowExecutionFromApiGateway] +aws_s3_object.website_files["sourcesanspro-italic-webfont.ab90d6d1.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["file-pdf.51e66388.svg"]: Destroying... [id=file-pdf.51e66388.svg] +module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.32dc0025.woff2"]: Destroying... [id=Latin-Merriweather-BoldItalic.32dc0025.woff2] +aws_s3_object.website_files["check--blue-60v.7604a0f6.svg"]: Destruction complete after 0s +aws_s3_object.website_files["roboto-mono-v5-latin-italic.9209c633.woff2"]: Destroying... [id=roboto-mono-v5-latin-italic.9209c633.woff2] +aws_s3_object.website_files["add.d491396d.svg"]: Destruction complete after 0s +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.2b983f5d.woff2"]: Destroying... [id=roboto-mono-v5-latin-700italic.2b983f5d.woff2] +aws_s3_object.website_files["GSA-logo.ece32ed3.svg"]: Destruction complete after 0s +aws_s3_object.website_files["sourcesanspro-regular-webfont.a9d6b459.woff2"]: Destroying... [id=sourcesanspro-regular-webfont.a9d6b459.woff2] +aws_s3_object.website_files["correct8-alt.c14d1430.svg"]: Destruction complete after 0s +aws_s3_bucket_versioning.website_storage_versioning: Destroying... [id=document-extractor-dev-website-328307993388] +module.token_endpoints.aws_lambda_permission.api_gateway[0]: Destruction complete after 0s +aws_s3_object.website_files["check_circle.a3900be5.svg"]: Destroying... [id=check_circle.a3900be5.svg] +aws_s3_object.website_files["hero.abe074f7.jpg"]: Destruction complete after 0s +aws_s3_object.website_files["expand_more.0d06b804.svg"]: Destroying... [id=expand_more.0d06b804.svg] +aws_s3_object.website_files["navigate_before.be211c2e.svg"]: Destruction complete after 0s +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Destroying... [id=document-extractor-dev-authorizer,6] +aws_s3_object.website_files["file-pdf.51e66388.svg"]: Destruction complete after 0s +aws_s3_object.website_files["GSA-logo.7a2abfc3.svg"]: Destroying... [id=GSA-logo.7a2abfc3.svg] +aws_s3_object.website_files["Latin-Merriweather-BoldItalic.32dc0025.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-LightItalic.f4af3b7f.woff2"]: Destroying... [id=Latin-Merriweather-LightItalic.f4af3b7f.woff2] +aws_s3_object.website_files["roboto-mono-v5-latin-italic.9209c633.woff2"]: Destruction complete after 0s +aws_iam_role_policy_attachment.attach_secrets_permission_to_role: Destroying... [id=document-extractor-dev-lambda-execution-role-20250522153044573200000005] +aws_s3_object.website_files["roboto-mono-v5-latin-700italic.2b983f5d.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["roboto-mono-v5-latin-300.40a89411.woff2"]: Destroying... [id=roboto-mono-v5-latin-300.40a89411.woff2] +aws_lambda_provisioned_concurrency_config.authorizer_concurrency: Destruction complete after 0s +aws_s3_object.website_files["error.1986e136.svg"]: Destroying... [id=error.1986e136.svg] +aws_iam_role_policy_attachment.attach_secrets_permission_to_role: Destruction complete after 0s +aws_s3_object.website_files["file-pdf.248e68e5.svg"]: Destroying... [id=file-pdf.248e68e5.svg] +aws_s3_object.website_files["sourcesanspro-regular-webfont.a9d6b459.woff2"]: Destruction complete after 0s +aws_iam_role_policy_attachment.attach_kms_permission_to_role: Destroying... [id=document-extractor-dev-lambda-execution-role-2025052215311566550000000a] +aws_s3_object.website_files["check_circle.a3900be5.svg"]: Destruction complete after 1s +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Destroying... [id=document-extractor-dev-write-to-dynamodb,6] +aws_iam_role_policy_attachment.attach_kms_permission_to_role: Destruction complete after 1s +aws_s3_object.website_files["unfold_more.1f608602.svg"]: Destroying... [id=unfold_more.1f608602.svg] +aws_s3_object.website_files["expand_more.0d06b804.svg"]: Destruction complete after 1s +aws_s3_object.website_files["us_flag_small.e642a40d.png"]: Destroying... [id=us_flag_small.e642a40d.png] +aws_s3_bucket_versioning.website_storage_versioning: Destruction complete after 1s +aws_s3_object.website_files["navigate_far_next.bd2c92a4.svg"]: Destroying... [id=navigate_far_next.bd2c92a4.svg] +aws_s3_object.website_files["GSA-logo.7a2abfc3.svg"]: Destruction complete after 1s +module.document_id_endpoints.aws_lambda_permission.api_gateway[0]: Destroying... [id=AllowExecutionFromApiGateway] +aws_s3_object.website_files["Latin-Merriweather-LightItalic.f4af3b7f.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["correct8-alt.8f8dac23.svg"]: Destroying... [id=correct8-alt.8f8dac23.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-300.40a89411.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["sourcesanspro-bold-webfont.5436602a.woff2"]: Destroying... [id=sourcesanspro-bold-webfont.5436602a.woff2] +aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: Destruction complete after 0s +aws_s3_object.website_files["navigate_before.7d672c1a.svg"]: Destroying... [id=navigate_before.7d672c1a.svg] +aws_s3_object.website_files["error.1986e136.svg"]: Destruction complete after 1s +aws_s3_object.website_files["error--white.73d13870.svg"]: Destroying... [id=error--white.73d13870.svg] +aws_s3_object.website_files["file-pdf.248e68e5.svg"]: Destruction complete after 1s +aws_s3_object.website_files["info.85fe97af.svg"]: Destroying... [id=info.85fe97af.svg] +aws_s3_object.website_files["us_flag_small.e642a40d.png"]: Destruction complete after 0s +aws_s3_object.website_files["launch.f4de218c.svg"]: Destroying... [id=launch.f4de218c.svg] +aws_s3_object.website_files["unfold_more.1f608602.svg"]: Destruction complete after 0s +aws_s3_object.website_files["roboto-mono-v5-latin-700.d309a69d.woff2"]: Destroying... [id=roboto-mono-v5-latin-700.d309a69d.woff2] +module.document_id_endpoints.aws_lambda_permission.api_gateway[0]: Destruction complete after 0s +aws_s3_bucket_notification.notify_on_input_data: Destroying... [id=document-extractor-dev-documents-328307993388] +aws_s3_object.website_files["navigate_far_next.bd2c92a4.svg"]: Destruction complete after 0s +module.token_endpoints.aws_api_gateway_integration.lambda_integration[0]: Destroying... [id=agi-ic9avcgf3l-dmuib4-POST] +aws_s3_object.website_files["correct8-alt.8f8dac23.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.394b89ad.css"]: Destroying... [id=ui.394b89ad.css] +aws_s3_object.website_files["sourcesanspro-bold-webfont.5436602a.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["search.a724f1bc.svg"]: Destroying... [id=search.a724f1bc.svg] +aws_s3_object.website_files["navigate_before.7d672c1a.svg"]: Destruction complete after 0s +aws_s3_object.website_files["info.85fe97af.svg"]: Destruction complete after 0s +aws_secretsmanager_secret.username: Destroying... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-username-gKcWPC] +aws_s3_object.website_files["loader.e58cf242.svg"]: Destroying... [id=loader.e58cf242.svg] +aws_s3_object.website_files["error--white.73d13870.svg"]: Destruction complete after 0s +aws_s3_object.website_files["checkbox-indeterminate-alt.a6b9b5ac.svg"]: Destroying... [id=checkbox-indeterminate-alt.a6b9b5ac.svg] +aws_s3_object.website_files["launch.f4de218c.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.7c42bb32.css"]: Destroying... [id=ui.7c42bb32.css] +aws_s3_object.website_files["roboto-mono-v5-latin-700.d309a69d.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["remove.ca324d27.svg"]: Destroying... [id=remove.ca324d27.svg] +module.token_endpoints.aws_api_gateway_integration.lambda_integration[0]: Destruction complete after 0s +aws_s3_object.website_files["ui.4e22b301.js"]: Destroying... [id=ui.4e22b301.js] +aws_secretsmanager_secret.username: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-Italic.e2716ed3.woff2"]: Destroying... [id=Latin-Merriweather-Italic.e2716ed3.woff2] +aws_s3_object.website_files["ui.394b89ad.css"]: Destruction complete after 0s +aws_s3_bucket_policy.website_read: Destroying... [id=document-extractor-dev-website-328307993388] +aws_s3_object.website_files["search.a724f1bc.svg"]: Destruction complete after 0s +aws_s3_object.website_files["close.3962dfa0.svg"]: Destroying... [id=close.3962dfa0.svg] +aws_s3_object.website_files["loader.e58cf242.svg"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-LightItalic.1c9df738.woff2"]: Destroying... [id=Latin-Merriweather-LightItalic.1c9df738.woff2] +aws_s3_object.website_files["checkbox-indeterminate-alt.a6b9b5ac.svg"]: Destruction complete after 0s +aws_s3_object.website_files["icon-https.3b0ffef2.svg"]: Destroying... [id=icon-https.3b0ffef2.svg] +aws_s3_object.website_files["ui.7c42bb32.css"]: Destruction complete after 0s +aws_s3_object.website_files["navigate_far_next.37ce8b6e.svg"]: Destroying... [id=navigate_far_next.37ce8b6e.svg] +aws_s3_object.website_files["remove.ca324d27.svg"]: Destruction complete after 0s +aws_secretsmanager_secret.password: Destroying... [id=arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-password-5KgJiD] +aws_s3_bucket_notification.notify_on_input_data: Destruction complete after 0s +aws_s3_object.website_files["ui.4540ed92.js"]: Destroying... [id=ui.4540ed92.js] +aws_s3_object.website_files["ui.4e22b301.js"]: Destruction complete after 1s +aws_s3_object.website_files["file.f51e21d7.svg"]: Destroying... [id=file.f51e21d7.svg] +aws_s3_object.website_files["Latin-Merriweather-Italic.e2716ed3.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["add.29db0c6a.svg"]: Destroying... [id=add.29db0c6a.svg] +aws_s3_object.website_files["close.3962dfa0.svg"]: Destruction complete after 1s +aws_s3_object.website_files["icon-https.3b0ffef2.svg"]: Destruction complete after 1s +aws_s3_object.website_files["Latin-Merriweather-LightItalic.1c9df738.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["correct8.2dc455b8.svg"]: Destroying... [id=correct8.2dc455b8.svg] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destroying... [id=document-extractor-dev-get-document,6] +aws_s3_object.website_files["sourcesanspro-italic-webfont.82ff76d1.woff2"]: Destroying... [id=sourcesanspro-italic-webfont.82ff76d1.woff2] +aws_secretsmanager_secret.password: Destruction complete after 1s +aws_s3_object.website_files["roboto-mono-v5-latin-regular.e048516f.woff2"]: Destroying... [id=roboto-mono-v5-latin-regular.e048516f.woff2] +aws_s3_bucket_policy.website_read: Destruction complete after 1s +aws_s3_object.website_files["navigate_far_next.37ce8b6e.svg"]: Destruction complete after 1s +aws_s3_object.website_files["sourcesanspro-light-webfont.e70d4904.woff2"]: Destroying... [id=sourcesanspro-light-webfont.e70d4904.woff2] +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.84e64ba8.woff2"]: Destroying... [id=sourcesanspro-bolditalic-webfont.84e64ba8.woff2] +aws_s3_object.website_files["ui.4540ed92.js"]: Destruction complete after 1s +aws_s3_object.website_files["file.057d5652.svg"]: Destroying... [id=file.057d5652.svg] +aws_s3_object.website_files["file.f51e21d7.svg"]: Destruction complete after 0s +aws_s3_object.website_files["launch--white.626c08f5.svg"]: Destroying... [id=launch--white.626c08f5.svg] +aws_s3_object.website_files["add.29db0c6a.svg"]: Destruction complete after 0s +aws_s3_object.website_files["expand_less.e2846957.svg"]: Destroying... [id=expand_less.e2846957.svg] +module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: Destruction complete after 0s +aws_s3_object.website_files["expand_more.3ac36c78.svg"]: Destroying... [id=expand_more.3ac36c78.svg] +aws_s3_object.website_files["sourcesanspro-italic-webfont.82ff76d1.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["warning.ce0fedc9.svg"]: Destroying... [id=warning.ce0fedc9.svg] +aws_s3_object.website_files["correct8.2dc455b8.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.4e22b301.js.map"]: Destroying... [id=ui.4e22b301.js.map] +aws_s3_object.website_files["roboto-mono-v5-latin-regular.e048516f.woff2"]: Destruction complete after 0s +module.document_id_endpoints.aws_lambda_permission.api_gateway[1]: Destroying... [id=AllowExecutionFromApiGateway] +aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.84e64ba8.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["hero.3538e009.jpg"]: Destroying... [id=hero.3538e009.jpg] +aws_s3_object.website_files["sourcesanspro-light-webfont.e70d4904.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-Regular.66d4c170.woff2"]: Destroying... [id=Latin-Merriweather-Regular.66d4c170.woff2] +aws_s3_object.website_files["file.057d5652.svg"]: Destruction complete after 0s +aws_iam_role_policy_attachment.attach_textract_permission_to_role: Destroying... [id=document-extractor-dev-lambda-execution-role-20250522153044578100000006] +aws_iam_role_policy_attachment.attach_textract_permission_to_role: Destruction complete after 0s +aws_s3_object.website_files["unfold_more.46c00529.svg"]: Destroying... [id=unfold_more.46c00529.svg] +aws_s3_object.website_files["launch--white.626c08f5.svg"]: Destruction complete after 0s +aws_s3_object.website_files["expand_less.e2846957.svg"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-Italic.959c3872.woff2"]: Destroying... [id=Latin-Merriweather-Italic.959c3872.woff2] +aws_s3_object.website_files["launch--white.6c179f60.svg"]: Destroying... [id=launch--white.6c179f60.svg] +aws_s3_object.website_files["expand_more.3ac36c78.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.4540ed92.js.map"]: Destroying... [id=ui.4540ed92.js.map] +module.document_id_endpoints.aws_lambda_permission.api_gateway[1]: Destruction complete after 0s +aws_iam_role_policy_attachment.attach_basic_permission_to_role: Destroying... [id=document-extractor-dev-lambda-execution-role-20250522153044909700000007] +aws_iam_role_policy_attachment.attach_basic_permission_to_role: Destruction complete after 0s +aws_s3_object.website_files["remove.27bfc20f.svg"]: Destroying... [id=remove.27bfc20f.svg] +aws_s3_object.website_files["warning.ce0fedc9.svg"]: Destruction complete after 0s +aws_lambda_permission.api_gateway_invoke_authorizer: Destroying... [id=AllowExecutionFromApiGateway] +aws_s3_object.website_files["ui.4e22b301.js.map"]: Destruction complete after 0s +aws_s3_object.website_files["roboto-mono-v5-latin-700.df366d23.woff2"]: Destroying... [id=roboto-mono-v5-latin-700.df366d23.woff2] +aws_s3_object.website_files["hero.3538e009.jpg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.9f367d21.css"]: Destroying... [id=ui.9f367d21.css] +aws_s3_object.website_files["Latin-Merriweather-Regular.66d4c170.woff2"]: Destruction complete after 0s +aws_s3_object.website_files["ui.24de9d80.js.map"]: Destroying... [id=ui.24de9d80.js.map] +aws_s3_object.website_files["unfold_more.46c00529.svg"]: Destruction complete after 0s +aws_s3_object.website_files["launch--white.6c179f60.svg"]: Destruction complete after 0s +aws_s3_object.website_files["ui.4540ed92.js.map"]: Destruction complete after 0s +aws_s3_object.website_files["Latin-Merriweather-Italic.959c3872.woff2"]: Destruction complete after 0s +aws_lambda_permission.api_gateway_invoke_authorizer: Destruction complete after 0s +aws_s3_object.website_files["file-excel.c90d2036.svg"]: Destroying... [id=file-excel.c90d2036.svg] +aws_s3_object.website_files["us_flag_small.9c3c6ab8.png"]: Destroying... [id=us_flag_small.9c3c6ab8.png] +aws_s3_object.website_files["calendar_today.3a803a7a.svg"]: Destroying... [id=calendar_today.3a803a7a.svg] +aws_s3_object.website_files["ui.383343a4.js"]: Destroying... [id=ui.383343a4.js] +aws_s3_object.website_files["icon-dot-gov.1cc2b2c5.svg"]: Destroying... [id=icon-dot-gov.1cc2b2c5.svg] +aws_s3_object.website_files["roboto-mono-v5-latin-700.df366d23.woff2"]: Destruction complete after 1s +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.05b42991.woff2"]: Destroying... [id=sourcesanspro-lightitalic-webfont.05b42991.woff2] +aws_s3_object.website_files["remove.27bfc20f.svg"]: Destruction complete after 1s +aws_s3_object.website_files["ui.9f367d21.css"]: Destruction complete after 1s +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Destroying... [id=document-extractor-dev-documents-328307993388] +aws_s3_object.website_files["file-video.a3a38638.svg"]: Destroying... [id=file-video.a3a38638.svg] +aws_s3_object.website_files["us_flag_small.9c3c6ab8.png"]: Destruction complete after 1s +aws_s3_object.website_files["icon-dot-gov.1cc2b2c5.svg"]: Destruction complete after 1s +aws_iam_policy.s3_lambda_policy: Destroying... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy] +aws_iam_policy.dynamodb_lambda_policy: Destroying... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy] +aws_s3_object.website_files["calendar_today.3a803a7a.svg"]: Destruction complete after 1s +aws_s3_object.website_files["file-excel.c90d2036.svg"]: Destruction complete after 1s +aws_s3_object.website_files["ui.383343a4.js"]: Destruction complete after 1s +aws_iam_policy.secrets_lambda_policy: Destroying... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy] +aws_iam_policy.kms_lambda_policy: Destroying... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy] +module.token_endpoints.aws_lambda_function.function[0]: Destroying... [id=document-extractor-dev-token] +aws_s3_object.website_files["file-video.a3a38638.svg"]: Destruction complete after 0s +aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.05b42991.woff2"]: Destruction complete after 0s +module.token_endpoints.aws_api_gateway_method.http_method[0]: Destroying... [id=agm-ic9avcgf3l-dmuib4-POST] +aws_lambda_permission.allow_bucket_invoke: Destroying... [id=AllowExecutionFromS3Bucket] +aws_s3_object.website_files["ui.24de9d80.js.map"]: Destruction complete after 1s +aws_cloudfront_distribution.distribution: Destroying... [id=ENWTZLUASCDJZ] +aws_iam_policy.dynamodb_lambda_policy: Destruction complete after 0s +module.token_endpoints.aws_api_gateway_method.http_method[0]: Destruction complete after 0s +module.token_endpoints.aws_api_gateway_resource.resource_path: Destroying... [id=dmuib4] +aws_iam_policy.secrets_lambda_policy: Destruction complete after 0s +aws_iam_policy.kms_lambda_policy: Destruction complete after 0s +aws_iam_policy.s3_lambda_policy: Destruction complete after 0s +aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: Destruction complete after 0s +aws_lambda_permission.allow_bucket_invoke: Destruction complete after 0s +aws_lambda_function.text_extract: Destroying... [id=document-extractor-dev-text-extract] +module.token_endpoints.aws_api_gateway_resource.resource_path: Destruction complete after 0s +module.token_endpoints.aws_lambda_function.function[0]: Destruction complete after 0s +aws_lambda_function.text_extract: Destruction complete after 1s +aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: Destruction complete after 8s +aws_iam_role_policy_attachment.attach_sqs_permission_to_role: Destroying... [id=document-extractor-dev-lambda-execution-role-2025052215314877690000000b] +aws_lambda_function.write_to_dynamodb: Destroying... [id=document-extractor-dev-write-to-dynamodb] +aws_iam_role_policy_attachment.attach_sqs_permission_to_role: Destruction complete after 0s +aws_iam_policy.sqs_lambda_policy: Destroying... [id=arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy] +aws_iam_policy.sqs_lambda_policy: Destruction complete after 0s +aws_lambda_function.write_to_dynamodb: Destruction complete after 1s +aws_sqs_queue.queue_to_dynamo: Destroying... [id=https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb] +aws_sqs_queue.queue_to_dynamo: Destruction complete after 3s +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 10s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 20s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 30s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 40s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 50s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 1m0s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 1m10s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 1m20s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 1m30s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 1m40s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 1m50s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 2m0s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 2m10s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 2m20s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 2m30s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 2m40s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 2m50s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 3m0s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 3m10s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 3m20s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 3m30s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 3m40s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 3m50s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 4m0s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 4m10s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 4m20s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 4m30s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 4m40s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 4m50s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 5m0s elapsed] +aws_cloudfront_distribution.distribution: Still destroying... [id=ENWTZLUASCDJZ, 5m10s elapsed] +aws_cloudfront_distribution.distribution: Destruction complete after 5m18s +aws_cloudfront_function.rewrite_uri: Destroying... [id=document-extractor-dev-rewrite-request] +aws_cloudfront_origin_access_control.oac: Destroying... [id=E5E4NY4IQRCTR] +aws_api_gateway_stage.stage: Destroying... [id=ags-ic9avcgf3l-v1] +aws_s3_bucket.website_storage: Destroying... [id=document-extractor-dev-website-328307993388] +aws_cloudfront_origin_access_control.oac: Destruction complete after 0s +aws_api_gateway_stage.stage: Destruction complete after 0s +aws_api_gateway_deployment.api_deployment: Destroying... [id=ag6ofm] +aws_api_gateway_deployment.api_deployment: Destruction complete after 1s +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[0]: Destroying... [id=agi-ic9avcgf3l-g3ywx2-GET] +module.document_endpoints.aws_api_gateway_integration.lambda_integration[0]: Destroying... [id=agi-ic9avcgf3l-ljz2z9-POST] +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[1]: Destroying... [id=agi-ic9avcgf3l-g3ywx2-PUT] +aws_s3_bucket.website_storage: Destruction complete after 1s +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[1]: Destruction complete after 0s +module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[0]: Destruction complete after 0s +module.document_id_endpoints.aws_api_gateway_method.http_method[1]: Destroying... [id=agm-ic9avcgf3l-g3ywx2-PUT] +module.document_id_endpoints.aws_api_gateway_method.http_method[0]: Destroying... [id=agm-ic9avcgf3l-g3ywx2-GET] +module.document_endpoints.aws_api_gateway_integration.lambda_integration[0]: Destruction complete after 0s +module.document_id_endpoints.aws_lambda_function.function[0]: Destroying... [id=document-extractor-dev-get-document] +module.document_id_endpoints.aws_lambda_function.function[1]: Destroying... [id=document-extractor-dev-update-document] +module.document_endpoints.aws_api_gateway_method.http_method[0]: Destroying... [id=agm-ic9avcgf3l-ljz2z9-POST] +module.document_endpoints.aws_lambda_function.function[0]: Destroying... [id=document-extractor-dev-create-document] +module.document_id_endpoints.aws_api_gateway_method.http_method[0]: Destruction complete after 0s +module.document_id_endpoints.aws_api_gateway_method.http_method[1]: Destruction complete after 0s +module.document_id_endpoints.aws_api_gateway_resource.resource_path: Destroying... [id=g3ywx2] +module.document_endpoints.aws_api_gateway_method.http_method[0]: Destruction complete after 0s +aws_api_gateway_authorizer.authorizer: Destroying... [id=bq0hho] +module.document_id_endpoints.aws_api_gateway_resource.resource_path: Destruction complete after 0s +module.document_endpoints.aws_api_gateway_resource.resource_path: Destroying... [id=ljz2z9] +aws_api_gateway_authorizer.authorizer: Destruction complete after 0s +aws_lambda_function.authorizer: Destroying... [id=document-extractor-dev-authorizer] +module.document_endpoints.aws_api_gateway_resource.resource_path: Destruction complete after 0s +aws_cloudfront_function.rewrite_uri: Destruction complete after 1s +module.document_id_endpoints.aws_lambda_function.function[0]: Destruction complete after 0s +module.document_id_endpoints.aws_lambda_function.function[1]: Destruction complete after 0s +aws_dynamodb_table.extract_table: Destroying... [id=document-extractor-dev-text-extract] +aws_lambda_function.authorizer: Destruction complete after 0s +module.document_endpoints.aws_lambda_function.function[0]: Destruction complete after 1s +aws_api_gateway_rest_api.api: Destroying... [id=ic9avcgf3l] +aws_iam_role.execution_role: Destroying... [id=document-extractor-dev-lambda-execution-role] +aws_s3_bucket.document_storage: Destroying... [id=document-extractor-dev-documents-328307993388] +aws_kms_key.encryption: Destroying... [id=2c84ed0b-7d29-4d92-ab32-f0db5557a13c] +aws_iam_role.execution_role: Destruction complete after 0s +aws_api_gateway_rest_api.api: Destruction complete after 0s +aws_kms_key.encryption: Destruction complete after 0s +aws_dynamodb_table.extract_table: Destruction complete after 4s +╷ +│ Error: deleting S3 Bucket (document-extractor-dev-documents-328307993388): operation error S3: DeleteBucket, https response error StatusCode: 409, RequestID: VJK67R02DD6JCK0T, HostID: YDQZHwlo3zNU+KWAyiDBypSRmUvv50bMPDTxtqdog7WbhUEDuLIBwb5jZ+3N7/L2V1vbSvTXaRw=, api error BucketNotEmpty: The bucket you tried to delete is not empty. You must delete all versions in the bucket. +│ +│ +╵ +Releasing state lock. This may take a few moments... diff --git a/docs/terraform-show-output-5-29-26.md b/docs/terraform-show-output-5-29-26.md new file mode 100644 index 0000000..6c06921 --- /dev/null +++ b/docs/terraform-show-output-5-29-26.md @@ -0,0 +1,5050 @@ +# data.aws_caller_identity.current: +data "aws_caller_identity" "current" { + account_id = "328307993388" + arn = "arn:aws:sts::328307993388:assumed-role/AWSReservedSSO_AWSAdministratorAccess_6c478552c0a41d82/hmelo@codeforamerica.org" + id = "328307993388" + user_id = "AROAUY4FONMWHGL6OXPD2:hmelo@codeforamerica.org" +} + +# data.aws_iam_policy.lambda_basic_execution: +data "aws_iam_policy" "lambda_basic_execution" { + arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + attachment_count = 4 + description = "Provides write permissions to CloudWatch Logs." + id = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + name = "AWSLambdaBasicExecutionRole" + path = "/service-role/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + Effect = "Allow" + Resource = "*" + }, + ] + Version = "2012-10-17" + } + ) + policy_id = "ANPAJNCQGXC42545SKXIK" + tags = {} +} + +# data.aws_iam_policy.lambda_textract_execution: +data "aws_iam_policy" "lambda_textract_execution" { + arn = "arn:aws:iam::aws:policy/AmazonTextractFullAccess" + attachment_count = 1 + description = "Access to all Amazon Textract APIs" + id = "arn:aws:iam::aws:policy/AmazonTextractFullAccess" + name = "AmazonTextractFullAccess" + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "textract:*", + ] + Effect = "Allow" + Resource = "*" + }, + ] + Version = "2012-10-17" + } + ) + policy_id = "ANPAIQDD47A7H3GBVPWOQ" + tags = {} +} + +# data.aws_iam_policy_document.assume_role: +data "aws_iam_policy_document" "assume_role" { + id = "2690255455" + json = jsonencode( + { + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + Version = "2012-10-17" + } + ) + minified_json = jsonencode( + { + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + Version = "2012-10-17" + } + ) + version = "2012-10-17" + + statement { + actions = [ + "sts:AssumeRole", + ] + effect = "Allow" + not_actions = [] + not_resources = [] + resources = [] + + principals { + identifiers = [ + "lambda.amazonaws.com", + ] + type = "Service" + } + } +} + +# data.aws_iam_policy_document.cf_read: +data "aws_iam_policy_document" "cf_read" { + id = "3877928146" + json = jsonencode( + { + Statement = [ + { + Action = "s3:GetObject" + Condition = { + StringEquals = { + "AWS:SourceArn" = "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ" + } + } + Effect = "Allow" + Principal = { + Service = "cloudfront.amazonaws.com" + } + Resource = "arn:aws:s3:::document-extractor-dev-website-328307993388/*" + Sid = "AllowCloudFront" + }, + ] + Version = "2012-10-17" + } + ) + minified_json = jsonencode( + { + Statement = [ + { + Action = "s3:GetObject" + Condition = { + StringEquals = { + "AWS:SourceArn" = "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ" + } + } + Effect = "Allow" + Principal = { + Service = "cloudfront.amazonaws.com" + } + Resource = "arn:aws:s3:::document-extractor-dev-website-328307993388/*" + Sid = "AllowCloudFront" + }, + ] + Version = "2012-10-17" + } + ) + version = "2012-10-17" + + statement { + actions = [ + "s3:GetObject", + ] + effect = "Allow" + not_actions = [] + not_resources = [] + resources = [ + "arn:aws:s3:::document-extractor-dev-website-328307993388/*", + ] + sid = "AllowCloudFront" + + condition { + test = "StringEquals" + values = [ + "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ", + ] + variable = "AWS:SourceArn" + } + + principals { + identifiers = [ + "cloudfront.amazonaws.com", + ] + type = "Service" + } + } +} + +# data.aws_iam_policy_document.dynamodb_lambda_policy: +data "aws_iam_policy_document" "dynamodb_lambda_policy" { + id = "2781330629" + json = jsonencode( + { + Statement = [ + { + Action = "dynamodb:*" + Effect = "Allow" + Resource = "arn:aws:dynamodb:us-west-1:328307993388:table/document-extractor-dev-text-extract" + }, + ] + Version = "2012-10-17" + } + ) + minified_json = jsonencode( + { + Statement = [ + { + Action = "dynamodb:*" + Effect = "Allow" + Resource = "arn:aws:dynamodb:us-west-1:328307993388:table/document-extractor-dev-text-extract" + }, + ] + Version = "2012-10-17" + } + ) + version = "2012-10-17" + + statement { + actions = [ + "dynamodb:*", + ] + effect = "Allow" + not_actions = [] + not_resources = [] + resources = [ + "arn:aws:dynamodb:us-west-1:328307993388:table/document-extractor-dev-text-extract", + ] + } +} + +# data.aws_iam_policy_document.kms_lambda_policy: +data "aws_iam_policy_document" "kms_lambda_policy" { + id = "2825613304" + json = jsonencode( + { + Statement = [ + { + Action = [ + "kms:GenerateDataKey", + "kms:Encrypt", + "kms:DescribeKey", + "kms:Decrypt", + ] + Effect = "Allow" + Resource = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + }, + ] + Version = "2012-10-17" + } + ) + minified_json = jsonencode( + { + Statement = [ + { + Action = [ + "kms:GenerateDataKey", + "kms:Encrypt", + "kms:DescribeKey", + "kms:Decrypt", + ] + Effect = "Allow" + Resource = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + }, + ] + Version = "2012-10-17" + } + ) + version = "2012-10-17" + + statement { + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey", + ] + effect = "Allow" + not_actions = [] + not_resources = [] + resources = [ + "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c", + ] + } +} + +# data.aws_iam_policy_document.s3_lambda_policy: +data "aws_iam_policy_document" "s3_lambda_policy" { + id = "1929687036" + json = jsonencode( + { + Statement = [ + { + Action = "s3:*" + Effect = "Allow" + Resource = [ + "arn:aws:s3:::document-extractor-dev-documents-328307993388/*", + "arn:aws:s3:::document-extractor-dev-documents-328307993388", + ] + }, + ] + Version = "2012-10-17" + } + ) + minified_json = jsonencode( + { + Statement = [ + { + Action = "s3:*" + Effect = "Allow" + Resource = [ + "arn:aws:s3:::document-extractor-dev-documents-328307993388/*", + "arn:aws:s3:::document-extractor-dev-documents-328307993388", + ] + }, + ] + Version = "2012-10-17" + } + ) + version = "2012-10-17" + + statement { + actions = [ + "s3:*", + ] + effect = "Allow" + not_actions = [] + not_resources = [] + resources = [ + "arn:aws:s3:::document-extractor-dev-documents-328307993388", + "arn:aws:s3:::document-extractor-dev-documents-328307993388/*", + ] + } +} + +# data.aws_iam_policy_document.secrets_lambda_policy: +data "aws_iam_policy_document" "secrets_lambda_policy" { + id = "2856285183" + json = jsonencode( + { + Statement = [ + { + Action = "secretsmanager:*" + Effect = "Allow" + Resource = "*" + }, + ] + Version = "2012-10-17" + } + ) + minified_json = jsonencode( + { + Statement = [ + { + Action = "secretsmanager:*" + Effect = "Allow" + Resource = "*" + }, + ] + Version = "2012-10-17" + } + ) + version = "2012-10-17" + + statement { + actions = [ + "secretsmanager:*", + ] + effect = "Allow" + not_actions = [] + not_resources = [] + resources = [ + "*", + ] + } +} + +# data.aws_iam_policy_document.sqs_lambda_policy: +data "aws_iam_policy_document" "sqs_lambda_policy" { + id = "1807487604" + json = jsonencode( + { + Statement = [ + { + Action = "sqs:*" + Effect = "Allow" + Resource = "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb" + }, + ] + Version = "2012-10-17" + } + ) + minified_json = jsonencode( + { + Statement = [ + { + Action = "sqs:*" + Effect = "Allow" + Resource = "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb" + }, + ] + Version = "2012-10-17" + } + ) + version = "2012-10-17" + + statement { + actions = [ + "sqs:*", + ] + effect = "Allow" + not_actions = [] + not_resources = [] + resources = [ + "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb", + ] + } +} + +# aws_api_gateway_authorizer.authorizer: +resource "aws_api_gateway_authorizer" "authorizer" { + arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l/authorizers/bq0hho" + authorizer_result_ttl_in_seconds = 300 + authorizer_uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer/invocations" + id = "bq0hho" + identity_source = "method.request.header.Authorization" + name = "document-extractor-dev-authorizer" + provider_arns = [] + rest_api_id = "ic9avcgf3l" + type = "TOKEN" +} + +# aws_api_gateway_deployment.api_deployment: +resource "aws_api_gateway_deployment" "api_deployment" { + created_date = "2025-05-22T17:09:14Z" + execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/" + id = "ag6ofm" + invoke_url = "https://ic9avcgf3l.execute-api.us-west-1.amazonaws.com/" + rest_api_id = "ic9avcgf3l" + triggers = { + "redeployment_for_document" = "df368280881120978a4c4656300fde0f324b8281bf99d7ba054eec9cc8bd6211f4e79278474c2d01aea853b44799e445dcaa6b21affb76afc9f42daabaa1b1f2" + "redeployment_for_document_id" = "7dba33f74bbc4cd4db523d3160e633446810ebc4a2940bb1e800d0aafb37327407f2bd45b4e7eae6cd0fb5cf2b12ad4c605c11baf1ea64ee14f72c04ca4a4abf" + } +} + +# aws_api_gateway_rest_api.api: +resource "aws_api_gateway_rest_api" "api" { + api_key_source = "HEADER" + arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l" + binary_media_types = [] + created_date = "2025-05-22T15:30:42Z" + description = "document-extractor API" + disable_execute_api_endpoint = false + execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l" + id = "ic9avcgf3l" + name = "document-extractor-dev-api" + root_resource_id = "qdlfjof80i" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + + endpoint_configuration { + ip_address_type = "ipv4" + types = [ + "EDGE", + ] + vpc_endpoint_ids = [] + } +} + +# aws_api_gateway_stage.stage: +resource "aws_api_gateway_stage" "stage" { + arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l/stages/v1" + cache_cluster_enabled = false + deployment_id = "ag6ofm" + execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/v1" + id = "ags-ic9avcgf3l-v1" + invoke_url = "https://ic9avcgf3l.execute-api.us-west-1.amazonaws.com/v1" + rest_api_id = "ic9avcgf3l" + stage_name = "v1" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + variables = {} + xray_tracing_enabled = false +} + +# aws_cloudfront_distribution.distribution: +resource "aws_cloudfront_distribution" "distribution" { + aliases = [] + arn = "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ" + caller_reference = "terraform-2025052215335454220000000c" + comment = "document-extractor dev website" + default_root_object = "index.html" + domain_name = "d1phjy5x6ttml2.cloudfront.net" + enabled = true + etag = "E4X7Q0PHSPAIJ" + hosted_zone_id = "Z2FDTNDATAQYW2" + http_version = "http2" + id = "ENWTZLUASCDJZ" + in_progress_validation_batches = 0 + is_ipv6_enabled = true + last_modified_time = "2025-05-22 17:11:46.344 +0000 UTC" + price_class = "PriceClass_100" + retain_on_delete = false + staging = false + status = "Deployed" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + trusted_key_groups = [ + { + enabled = false + items = [] + }, + ] + trusted_signers = [ + { + enabled = false + items = [] + }, + ] + wait_for_deployment = true + + default_cache_behavior { + allowed_methods = [ + "GET", + "HEAD", + ] + cached_methods = [ + "GET", + "HEAD", + ] + compress = true + default_ttl = 86400 + max_ttl = 172800 + min_ttl = 0 + smooth_streaming = false + target_origin_id = "website-in-s3" + trusted_key_groups = [] + trusted_signers = [] + viewer_protocol_policy = "redirect-to-https" + + forwarded_values { + headers = [] + query_string = false + query_string_cache_keys = [] + + cookies { + forward = "none" + whitelisted_names = [] + } + } + + grpc_config { + enabled = false + } + } + + ordered_cache_behavior { + allowed_methods = [ + "DELETE", + "GET", + "HEAD", + "OPTIONS", + "PATCH", + "POST", + "PUT", + ] + cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" + cached_methods = [ + "GET", + "HEAD", + ] + compress = true + default_ttl = 0 + max_ttl = 0 + min_ttl = 0 + origin_request_policy_id = "b689b0a8-53d0-40ab-baf2-68738e2966ac" + path_pattern = "/api/*" + smooth_streaming = false + target_origin_id = "api-in-gateway" + trusted_key_groups = [] + trusted_signers = [] + viewer_protocol_policy = "redirect-to-https" + + function_association { + event_type = "viewer-request" + function_arn = "arn:aws:cloudfront::328307993388:function/document-extractor-dev-rewrite-request" + } + + grpc_config { + enabled = false + } + } + + origin { + connection_attempts = 3 + connection_timeout = 10 + domain_name = "ic9avcgf3l.execute-api.us-west-1.amazonaws.com" + origin_id = "api-in-gateway" + origin_path = "/v1" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_keepalive_timeout = 5 + origin_protocol_policy = "https-only" + origin_read_timeout = 30 + origin_ssl_protocols = [ + "TLSv1.2", + ] + } + } + origin { + connection_attempts = 3 + connection_timeout = 10 + domain_name = "document-extractor-dev-website-328307993388.s3.us-west-1.amazonaws.com" + origin_access_control_id = "E5E4NY4IQRCTR" + origin_id = "website-in-s3" + } + + restrictions { + geo_restriction { + locations = [] + restriction_type = "none" + } + } + + viewer_certificate { + cloudfront_default_certificate = true + minimum_protocol_version = "TLSv1" + } +} + +# aws_cloudfront_function.rewrite_uri: +resource "aws_cloudfront_function" "rewrite_uri" { + arn = "arn:aws:cloudfront::328307993388:function/document-extractor-dev-rewrite-request" + code = <<-EOT + function handler(event) { + var request = event.request; + request.uri = request.uri.replace(/^\/api\//, "/"); + return request; + } + EOT + etag = "ETVPDKIKX0DER" + id = "document-extractor-dev-rewrite-request" + key_value_store_associations = [] + live_stage_etag = "ETVPDKIKX0DER" + name = "document-extractor-dev-rewrite-request" + publish = true + runtime = "cloudfront-js-1.0" + status = "DEPLOYED" +} + +# aws_cloudfront_origin_access_control.oac: +resource "aws_cloudfront_origin_access_control" "oac" { + arn = "arn:aws:cloudfront::328307993388:origin-access-control/E5E4NY4IQRCTR" + description = "SigV4 control for private S3 website bucket" + etag = "ETVPDKIKX0DER" + id = "E5E4NY4IQRCTR" + name = "document-extractor-dev-oac" + origin_access_control_origin_type = "s3" + signing_behavior = "always" + signing_protocol = "sigv4" +} + +# aws_dynamodb_table.extract_table: +resource "aws_dynamodb_table" "extract_table" { + arn = "arn:aws:dynamodb:us-west-1:328307993388:table/document-extractor-dev-text-extract" + billing_mode = "PAY_PER_REQUEST" + deletion_protection_enabled = false + hash_key = "document_id" + id = "document-extractor-dev-text-extract" + name = "document-extractor-dev-text-extract" + read_capacity = 0 + stream_enabled = false + table_class = "STANDARD" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + write_capacity = 0 + + attribute { + name = "document_id" + type = "S" + } + + point_in_time_recovery { + enabled = false + recovery_period_in_days = 0 + } + + ttl { + enabled = false + } +} + +# aws_iam_policy.dynamodb_lambda_policy: +resource "aws_iam_policy" "dynamodb_lambda_policy" { + arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy" + attachment_count = 1 + id = "arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy" + name = "document-extractor-dev-dynamodb-lambda-policy" + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = "dynamodb:*" + Effect = "Allow" + Resource = "arn:aws:dynamodb:us-west-1:328307993388:table/document-extractor-dev-text-extract" + }, + ] + Version = "2012-10-17" + } + ) + policy_id = "ANPAUY4FONMWOXCPMFZOI" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_iam_policy.kms_lambda_policy: +resource "aws_iam_policy" "kms_lambda_policy" { + arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy" + attachment_count = 1 + id = "arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy" + name = "document-extractor-dev-kms-lambda-policy" + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "kms:GenerateDataKey", + "kms:Encrypt", + "kms:DescribeKey", + "kms:Decrypt", + ] + Effect = "Allow" + Resource = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + }, + ] + Version = "2012-10-17" + } + ) + policy_id = "ANPAUY4FONMWP3RCSG7LS" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_iam_policy.s3_lambda_policy: +resource "aws_iam_policy" "s3_lambda_policy" { + arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy" + attachment_count = 1 + id = "arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy" + name = "document-extractor-dev-s3-lambda-policy" + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = "s3:*" + Effect = "Allow" + Resource = [ + "arn:aws:s3:::document-extractor-dev-documents-328307993388/*", + "arn:aws:s3:::document-extractor-dev-documents-328307993388", + ] + }, + ] + Version = "2012-10-17" + } + ) + policy_id = "ANPAUY4FONMWEETR66RD5" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_iam_policy.secrets_lambda_policy: +resource "aws_iam_policy" "secrets_lambda_policy" { + arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy" + attachment_count = 1 + id = "arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy" + name = "document-extractor-dev-secrets-lambda-policy" + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = "secretsmanager:*" + Effect = "Allow" + Resource = "*" + }, + ] + Version = "2012-10-17" + } + ) + policy_id = "ANPAUY4FONMWH65CVSXAG" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_iam_policy.sqs_lambda_policy: +resource "aws_iam_policy" "sqs_lambda_policy" { + arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy" + attachment_count = 1 + id = "arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy" + name = "document-extractor-dev-sqs-lambda-policy" + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = "sqs:*" + Effect = "Allow" + Resource = "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb" + }, + ] + Version = "2012-10-17" + } + ) + policy_id = "ANPAUY4FONMWOGMGQ5D3P" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_iam_role.execution_role: +resource "aws_iam_role" "execution_role" { + arn = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" + assume_role_policy = jsonencode( + { + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + Version = "2012-10-17" + } + ) + create_date = "2025-05-22T15:30:43Z" + force_detach_policies = false + id = "document-extractor-dev-lambda-execution-role" + managed_policy_arns = [ + "arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy", + "arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy", + "arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy", + "arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy", + "arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy", + "arn:aws:iam::aws:policy/AmazonTextractFullAccess", + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ] + max_session_duration = 3600 + name = "document-extractor-dev-lambda-execution-role" + path = "/" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + unique_id = "AROAUY4FONMWAAHANRCKS" +} + +# aws_iam_role_policy_attachment.attach_basic_permission_to_role: +resource "aws_iam_role_policy_attachment" "attach_basic_permission_to_role" { + id = "document-extractor-dev-lambda-execution-role-20250522153044909700000007" + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = "document-extractor-dev-lambda-execution-role" +} + +# aws_iam_role_policy_attachment.attach_dynamodb_permission_to_role: +resource "aws_iam_role_policy_attachment" "attach_dynamodb_permission_to_role" { + id = "document-extractor-dev-lambda-execution-role-20250522153115634300000009" + policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-dynamodb-lambda-policy" + role = "document-extractor-dev-lambda-execution-role" +} + +# aws_iam_role_policy_attachment.attach_kms_permission_to_role: +resource "aws_iam_role_policy_attachment" "attach_kms_permission_to_role" { + id = "document-extractor-dev-lambda-execution-role-2025052215311566550000000a" + policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-kms-lambda-policy" + role = "document-extractor-dev-lambda-execution-role" +} + +# aws_iam_role_policy_attachment.attach_s3_permission_to_role: +resource "aws_iam_role_policy_attachment" "attach_s3_permission_to_role" { + id = "document-extractor-dev-lambda-execution-role-20250522153114543600000008" + policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-s3-lambda-policy" + role = "document-extractor-dev-lambda-execution-role" +} + +# aws_iam_role_policy_attachment.attach_secrets_permission_to_role: +resource "aws_iam_role_policy_attachment" "attach_secrets_permission_to_role" { + id = "document-extractor-dev-lambda-execution-role-20250522153044573200000005" + policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-secrets-lambda-policy" + role = "document-extractor-dev-lambda-execution-role" +} + +# aws_iam_role_policy_attachment.attach_sqs_permission_to_role: +resource "aws_iam_role_policy_attachment" "attach_sqs_permission_to_role" { + id = "document-extractor-dev-lambda-execution-role-2025052215314877690000000b" + policy_arn = "arn:aws:iam::328307993388:policy/document-extractor-dev-sqs-lambda-policy" + role = "document-extractor-dev-lambda-execution-role" +} + +# aws_iam_role_policy_attachment.attach_textract_permission_to_role: +resource "aws_iam_role_policy_attachment" "attach_textract_permission_to_role" { + id = "document-extractor-dev-lambda-execution-role-20250522153044578100000006" + policy_arn = "arn:aws:iam::aws:policy/AmazonTextractFullAccess" + role = "document-extractor-dev-lambda-execution-role" +} + +# aws_kms_key.encryption: +resource "aws_kms_key" "encryption" { + arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + bypass_policy_lockout_safety_check = false + customer_master_key_spec = "SYMMETRIC_DEFAULT" + deletion_window_in_days = 7 + description = "Data encryption for document-extractor" + enable_key_rotation = true + id = "2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + is_enabled = true + key_id = "2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + key_usage = "ENCRYPT_DECRYPT" + multi_region = false + policy = jsonencode( + { + Id = "key-default-1" + Statement = [ + { + Action = "kms:*" + Effect = "Allow" + Principal = { + AWS = "arn:aws:iam::328307993388:root" + } + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + ] + Version = "2012-10-17" + } + ) + rotation_period_in_days = 365 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_lambda_event_source_mapping.invoke_dynamodb_writer_from_sqs: +resource "aws_lambda_event_source_mapping" "invoke_dynamodb_writer_from_sqs" { + arn = "arn:aws:lambda:us-west-1:328307993388:event-source-mapping:b32111fd-5cd6-45fd-b193-ac415949bc48" + batch_size = 10 + bisect_batch_on_function_error = false + enabled = true + event_source_arn = "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb" + function_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb" + function_name = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb" + function_response_types = [] + id = "b32111fd-5cd6-45fd-b193-ac415949bc48" + last_modified = "2025-05-22T15:35:05Z" + maximum_batching_window_in_seconds = 0 + maximum_record_age_in_seconds = 0 + maximum_retry_attempts = 0 + parallelization_factor = 0 + queues = [] + state = "Enabled" + state_transition_reason = "USER_INITIATED" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + topics = [] + tumbling_window_in_seconds = 0 + uuid = "b32111fd-5cd6-45fd-b193-ac415949bc48" +} + +# aws_lambda_function.authorizer: +resource "aws_lambda_function" "authorizer" { + architectures = [ + "arm64", + ] + arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer" + code_sha256 = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + filename = "./../backend/dist/lambda.zip" + function_name = "document-extractor-dev-authorizer" + handler = "src.external.aws.lambdas.authenticate.lambda_handler" + id = "document-extractor-dev-authorizer" + invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer/invocations" + kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + last_modified = "2025-05-22T20:48:51.000+0000" + layers = [] + memory_size = 256 + package_type = "Zip" + publish = true + qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer:5" + qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-authorizer:5/invocations" + reserved_concurrent_executions = -1 + role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" + runtime = "python3.13" + skip_destroy = false + source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + source_code_size = 19659547 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + timeout = 30 + version = "5" + + environment { + variables = { + "ENVIRONMENT" = "dev" + } + } + + ephemeral_storage { + size = 512 + } + + logging_config { + log_format = "Text" + log_group = "/aws/lambda/document-extractor-dev-authorizer" + } + + tracing_config { + mode = "PassThrough" + } +} + +# aws_lambda_function.text_extract: +resource "aws_lambda_function" "text_extract" { + architectures = [ + "arm64", + ] + arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract" + code_sha256 = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + filename = "./../backend/dist/lambda.zip" + function_name = "document-extractor-dev-text-extract" + handler = "src.external.aws.lambdas.text_extractor.lambda_handler" + id = "document-extractor-dev-text-extract" + invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract/invocations" + kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + last_modified = "2025-05-22T20:48:30.000+0000" + layers = [] + memory_size = 256 + package_type = "Zip" + publish = true + qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract:5" + qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract:5/invocations" + reserved_concurrent_executions = -1 + role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" + runtime = "python3.13" + skip_destroy = false + source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + source_code_size = 19659547 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + timeout = 30 + version = "5" + + environment { + variables = (sensitive value) + } + + ephemeral_storage { + size = 512 + } + + logging_config { + log_format = "Text" + log_group = "/aws/lambda/document-extractor-dev-text-extract" + } + + tracing_config { + mode = "PassThrough" + } +} + +# aws_lambda_function.write_to_dynamodb: +resource "aws_lambda_function" "write_to_dynamodb" { + architectures = [ + "arm64", + ] + arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb" + code_sha256 = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + filename = "./../backend/dist/lambda.zip" + function_name = "document-extractor-dev-write-to-dynamodb" + handler = "src.external.aws.lambdas.sqs_dynamo_writer.lambda_handler" + id = "document-extractor-dev-write-to-dynamodb" + invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb/invocations" + kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + last_modified = "2025-05-22T20:48:11.000+0000" + layers = [] + memory_size = 256 + package_type = "Zip" + publish = true + qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb:5" + qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-write-to-dynamodb:5/invocations" + reserved_concurrent_executions = -1 + role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" + runtime = "python3.13" + skip_destroy = false + source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + source_code_size = 19659547 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + timeout = 30 + version = "5" + + environment { + variables = { + "DYNAMODB_TABLE" = "document-extractor-dev-text-extract" + "SQS_QUEUE_URL" = "https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb" + } + } + + ephemeral_storage { + size = 512 + } + + logging_config { + log_format = "Text" + log_group = "/aws/lambda/document-extractor-dev-write-to-dynamodb" + } + + tracing_config { + mode = "PassThrough" + } +} + +# aws_lambda_permission.allow_bucket_invoke: +resource "aws_lambda_permission" "allow_bucket_invoke" { + action = "lambda:InvokeFunction" + function_name = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract" + id = "AllowExecutionFromS3Bucket" + principal = "s3.amazonaws.com" + source_arn = "arn:aws:s3:::document-extractor-dev-documents-328307993388" + statement_id = "AllowExecutionFromS3Bucket" +} + +# aws_lambda_permission.api_gateway_invoke_authorizer: +resource "aws_lambda_permission" "api_gateway_invoke_authorizer" { + action = "lambda:InvokeFunction" + function_name = "document-extractor-dev-authorizer" + id = "AllowExecutionFromApiGateway" + principal = "apigateway.amazonaws.com" + source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*" + statement_id = "AllowExecutionFromApiGateway" +} + +# aws_lambda_provisioned_concurrency_config.authorizer_concurrency: +resource "aws_lambda_provisioned_concurrency_config" "authorizer_concurrency" { + function_name = "document-extractor-dev-authorizer" + id = "document-extractor-dev-authorizer,5" + provisioned_concurrent_executions = 1 + qualifier = "5" + skip_destroy = false +} + +# aws_lambda_provisioned_concurrency_config.text_extract_concurrency: +resource "aws_lambda_provisioned_concurrency_config" "text_extract_concurrency" { + function_name = "document-extractor-dev-text-extract" + id = "document-extractor-dev-text-extract,5" + provisioned_concurrent_executions = 1 + qualifier = "5" + skip_destroy = false +} + +# aws_lambda_provisioned_concurrency_config.write_to_dynamodb_concurrency: +resource "aws_lambda_provisioned_concurrency_config" "write_to_dynamodb_concurrency" { + function_name = "document-extractor-dev-write-to-dynamodb" + id = "document-extractor-dev-write-to-dynamodb,5" + provisioned_concurrent_executions = 1 + qualifier = "5" + skip_destroy = false +} + +# aws_s3_bucket.document_storage: +resource "aws_s3_bucket" "document_storage" { + arn = "arn:aws:s3:::document-extractor-dev-documents-328307993388" + bucket = "document-extractor-dev-documents-328307993388" + bucket_domain_name = "document-extractor-dev-documents-328307993388.s3.amazonaws.com" + bucket_regional_domain_name = "document-extractor-dev-documents-328307993388.s3.us-west-1.amazonaws.com" + force_destroy = false + hosted_zone_id = "Z2F56UZL2M1ACD" + id = "document-extractor-dev-documents-328307993388" + object_lock_enabled = false + region = "us-west-1" + request_payer = "BucketOwner" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + + grant { + id = "8c3f17ff516da15e6f5aab48ecdb83925bea44ff492b5a28e507db8e94e3c531" + permissions = [ + "FULL_CONTROL", + ] + type = "CanonicalUser" + } + + lifecycle_rule { + abort_incomplete_multipart_upload_days = 0 + enabled = true + id = "delete-uploaded-documents" + prefix = "input/" + tags = {} + + expiration { + days = 31 + expired_object_delete_marker = false + } + } + + server_side_encryption_configuration { + rule { + bucket_key_enabled = false + + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } + } + + versioning { + enabled = false + mfa_delete = false + } +} + +# aws_s3_bucket.website_storage: +resource "aws_s3_bucket" "website_storage" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388" + bucket = "document-extractor-dev-website-328307993388" + bucket_domain_name = "document-extractor-dev-website-328307993388.s3.amazonaws.com" + bucket_regional_domain_name = "document-extractor-dev-website-328307993388.s3.us-west-1.amazonaws.com" + force_destroy = true + hosted_zone_id = "Z2F56UZL2M1ACD" + id = "document-extractor-dev-website-328307993388" + object_lock_enabled = false + policy = jsonencode( + { + Statement = [ + { + Action = "s3:GetObject" + Condition = { + StringEquals = { + "AWS:SourceArn" = "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ" + } + } + Effect = "Allow" + Principal = { + Service = "cloudfront.amazonaws.com" + } + Resource = "arn:aws:s3:::document-extractor-dev-website-328307993388/*" + Sid = "AllowCloudFront" + }, + ] + Version = "2012-10-17" + } + ) + region = "us-west-1" + request_payer = "BucketOwner" + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + website_domain = "s3-website-us-west-1.amazonaws.com" + website_endpoint = "document-extractor-dev-website-328307993388.s3-website-us-west-1.amazonaws.com" + + grant { + id = "8c3f17ff516da15e6f5aab48ecdb83925bea44ff492b5a28e507db8e94e3c531" + permissions = [ + "FULL_CONTROL", + ] + type = "CanonicalUser" + } + + server_side_encryption_configuration { + rule { + bucket_key_enabled = false + + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } + } + + versioning { + enabled = true + mfa_delete = false + } + + website { + error_document = "index.html" + index_document = "index.html" + } +} + +# aws_s3_bucket_lifecycle_configuration.document_storage_lifecycles: +resource "aws_s3_bucket_lifecycle_configuration" "document_storage_lifecycles" { + bucket = "document-extractor-dev-documents-328307993388" + id = "document-extractor-dev-documents-328307993388" + transition_default_minimum_object_size = "all_storage_classes_128K" + + rule { + id = "delete-uploaded-documents" + status = "Enabled" + + expiration { + days = 31 + expired_object_delete_marker = false + } + + filter { + prefix = "input/" + } + } +} + +# aws_s3_bucket_notification.notify_on_input_data: +resource "aws_s3_bucket_notification" "notify_on_input_data" { + bucket = "document-extractor-dev-documents-328307993388" + eventbridge = false + id = "document-extractor-dev-documents-328307993388" + + lambda_function { + events = [ + "s3:ObjectCreated:*", + ] + filter_prefix = "input/" + id = "tf-s3-lambda-2025052215342622090000000d" + lambda_function_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-text-extract" + } +} + +# aws_s3_bucket_policy.website_read: +resource "aws_s3_bucket_policy" "website_read" { + bucket = "document-extractor-dev-website-328307993388" + id = "document-extractor-dev-website-328307993388" + policy = jsonencode( + { + Statement = [ + { + Action = "s3:GetObject" + Condition = { + StringEquals = { + "AWS:SourceArn" = "arn:aws:cloudfront::328307993388:distribution/ENWTZLUASCDJZ" + } + } + Effect = "Allow" + Principal = { + Service = "cloudfront.amazonaws.com" + } + Resource = "arn:aws:s3:::document-extractor-dev-website-328307993388/*" + Sid = "AllowCloudFront" + }, + ] + Version = "2012-10-17" + } + ) +} + +# aws_s3_bucket_public_access_block.private_website: +resource "aws_s3_bucket_public_access_block" "private_website" { + block_public_acls = true + block_public_policy = true + bucket = "document-extractor-dev-website-328307993388" + id = "document-extractor-dev-website-328307993388" + ignore_public_acls = true + restrict_public_buckets = false +} + +# aws_s3_bucket_versioning.website_storage_versioning: +resource "aws_s3_bucket_versioning" "website_storage_versioning" { + bucket = "document-extractor-dev-website-328307993388" + id = "document-extractor-dev-website-328307993388" + + versioning_configuration { + status = "Enabled" + } +} + +# aws_s3_bucket_website_configuration.website_configuration: +resource "aws_s3_bucket_website_configuration" "website_configuration" { + bucket = "document-extractor-dev-website-328307993388" + id = "document-extractor-dev-website-328307993388" + website_domain = "s3-website-us-west-1.amazonaws.com" + website_endpoint = "document-extractor-dev-website-328307993388.s3-website-us-west-1.amazonaws.com" + + error_document { + key = "index.html" + } + + index_document { + suffix = "index.html" + } +} + +# aws_s3_object.website_files["GSA-logo.7a2abfc3.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/GSA-logo.7a2abfc3.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "a5f5373f259f83f81912e6a91cf2c388" + force_destroy = false + id = "GSA-logo.7a2abfc3.svg" + key = "GSA-logo.7a2abfc3.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//GSA-logo.7a2abfc3.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["GSA-logo.ece32ed3.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/GSA-logo.ece32ed3.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "10d4fa294b895ffbd2709b3db2056484" + force_destroy = false + id = "GSA-logo.ece32ed3.svg" + key = "GSA-logo.ece32ed3.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//GSA-logo.ece32ed3.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-Bold.967b2108.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Bold.967b2108.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "115f5a1d96eaa67ffee687960dd770f3" + force_destroy = false + id = "Latin-Merriweather-Bold.967b2108.woff2" + key = "Latin-Merriweather-Bold.967b2108.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-Bold.967b2108.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-Bold.cdbf7e95.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Bold.cdbf7e95.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "115f5a1d96eaa67ffee687960dd770f3" + force_destroy = false + id = "Latin-Merriweather-Bold.cdbf7e95.woff2" + key = "Latin-Merriweather-Bold.cdbf7e95.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-Bold.cdbf7e95.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-BoldItalic.32dc0025.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-BoldItalic.32dc0025.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "2750a1e9d21f8b94f614bf36a3c53a0a" + force_destroy = false + id = "Latin-Merriweather-BoldItalic.32dc0025.woff2" + key = "Latin-Merriweather-BoldItalic.32dc0025.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-BoldItalic.32dc0025.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-BoldItalic.e58256dc.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-BoldItalic.e58256dc.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "2750a1e9d21f8b94f614bf36a3c53a0a" + force_destroy = false + id = "Latin-Merriweather-BoldItalic.e58256dc.woff2" + key = "Latin-Merriweather-BoldItalic.e58256dc.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-BoldItalic.e58256dc.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-Italic.959c3872.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Italic.959c3872.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "2412c72a898d4ed8f67e1351f7360067" + force_destroy = false + id = "Latin-Merriweather-Italic.959c3872.woff2" + key = "Latin-Merriweather-Italic.959c3872.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-Italic.959c3872.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-Italic.e2716ed3.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Italic.e2716ed3.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "2412c72a898d4ed8f67e1351f7360067" + force_destroy = false + id = "Latin-Merriweather-Italic.e2716ed3.woff2" + key = "Latin-Merriweather-Italic.e2716ed3.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-Italic.e2716ed3.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-Light.1ad1a1d8.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Light.1ad1a1d8.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "4f0fb5042c21b257ae0707783ac29626" + force_destroy = false + id = "Latin-Merriweather-Light.1ad1a1d8.woff2" + key = "Latin-Merriweather-Light.1ad1a1d8.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-Light.1ad1a1d8.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-Light.fbc4393c.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Light.fbc4393c.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "4f0fb5042c21b257ae0707783ac29626" + force_destroy = false + id = "Latin-Merriweather-Light.fbc4393c.woff2" + key = "Latin-Merriweather-Light.fbc4393c.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-Light.fbc4393c.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-LightItalic.1c9df738.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-LightItalic.1c9df738.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "08acae9c0b84b1ac1ea5cc76846bb3f4" + force_destroy = false + id = "Latin-Merriweather-LightItalic.1c9df738.woff2" + key = "Latin-Merriweather-LightItalic.1c9df738.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-LightItalic.1c9df738.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-LightItalic.f4af3b7f.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-LightItalic.f4af3b7f.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "08acae9c0b84b1ac1ea5cc76846bb3f4" + force_destroy = false + id = "Latin-Merriweather-LightItalic.f4af3b7f.woff2" + key = "Latin-Merriweather-LightItalic.f4af3b7f.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-LightItalic.f4af3b7f.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-Regular.04046ce8.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Regular.04046ce8.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "fa9b615a25bdf9c42a7f6ffa223c1eb0" + force_destroy = false + id = "Latin-Merriweather-Regular.04046ce8.woff2" + key = "Latin-Merriweather-Regular.04046ce8.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-Regular.04046ce8.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["Latin-Merriweather-Regular.66d4c170.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/Latin-Merriweather-Regular.66d4c170.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "fa9b615a25bdf9c42a7f6ffa223c1eb0" + force_destroy = false + id = "Latin-Merriweather-Regular.66d4c170.woff2" + key = "Latin-Merriweather-Regular.66d4c170.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//Latin-Merriweather-Regular.66d4c170.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["add.29db0c6a.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/add.29db0c6a.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "0f088d7d3e8ada1b661f2736cb506e14" + force_destroy = false + id = "add.29db0c6a.svg" + key = "add.29db0c6a.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//add.29db0c6a.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["add.d491396d.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/add.d491396d.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "7efc1415746d7f0067eefcdc3321ba52" + force_destroy = false + id = "add.d491396d.svg" + key = "add.d491396d.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//add.d491396d.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["arrow_back.c9ac1a0e.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/arrow_back.c9ac1a0e.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "e57e9cb53111150e4f51e1121220a95a" + force_destroy = false + id = "arrow_back.c9ac1a0e.svg" + key = "arrow_back.c9ac1a0e.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//arrow_back.c9ac1a0e.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["arrow_back.e4913c69.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/arrow_back.e4913c69.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "2cd7c1dd5b9ac84ed414e4cc7f0ac828" + force_destroy = false + id = "arrow_back.e4913c69.svg" + key = "arrow_back.e4913c69.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//arrow_back.e4913c69.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["calendar_today.3a803a7a.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/calendar_today.3a803a7a.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "8b326af3e58921f55b0f7b91c15a3332" + force_destroy = false + id = "calendar_today.3a803a7a.svg" + key = "calendar_today.3a803a7a.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//calendar_today.3a803a7a.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["calendar_today.ce6eaa81.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/calendar_today.ce6eaa81.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "a873a20cec4307294405eb1ba4eb2e72" + force_destroy = false + id = "calendar_today.ce6eaa81.svg" + key = "calendar_today.ce6eaa81.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//calendar_today.ce6eaa81.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["check--blue-60v.7604a0f6.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/check--blue-60v.7604a0f6.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "313b2d5d8e007f70376ec216c87db68a" + force_destroy = false + id = "check--blue-60v.7604a0f6.svg" + key = "check--blue-60v.7604a0f6.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//check--blue-60v.7604a0f6.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["check--blue-60v.982d9f95.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/check--blue-60v.982d9f95.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "64bd8ec50798f10e75b0b52772a83acf" + force_destroy = false + id = "check--blue-60v.982d9f95.svg" + key = "check--blue-60v.982d9f95.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//check--blue-60v.982d9f95.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["check_circle.120511e4.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/check_circle.120511e4.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "8a6c0918758299283e904826d8bf66f5" + force_destroy = false + id = "check_circle.120511e4.svg" + key = "check_circle.120511e4.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//check_circle.120511e4.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["check_circle.a3900be5.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/check_circle.a3900be5.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "03c3ff80715e40101493a17f864bfa6e" + force_destroy = false + id = "check_circle.a3900be5.svg" + key = "check_circle.a3900be5.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//check_circle.a3900be5.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["checkbox-indeterminate-alt.5e7abfcf.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/checkbox-indeterminate-alt.5e7abfcf.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "d00c26327b052bb4cfe366292f996369" + force_destroy = false + id = "checkbox-indeterminate-alt.5e7abfcf.svg" + key = "checkbox-indeterminate-alt.5e7abfcf.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//checkbox-indeterminate-alt.5e7abfcf.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["checkbox-indeterminate-alt.a6b9b5ac.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/checkbox-indeterminate-alt.a6b9b5ac.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "93b4958c6c02d948cd8f495eb245b74b" + force_destroy = false + id = "checkbox-indeterminate-alt.a6b9b5ac.svg" + key = "checkbox-indeterminate-alt.a6b9b5ac.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//checkbox-indeterminate-alt.a6b9b5ac.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["checkbox-indeterminate.08aa88cd.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/checkbox-indeterminate.08aa88cd.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "8a80f6567e7aa6243704d7178569e3ca" + force_destroy = false + id = "checkbox-indeterminate.08aa88cd.svg" + key = "checkbox-indeterminate.08aa88cd.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//checkbox-indeterminate.08aa88cd.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["checkbox-indeterminate.db2c5d96.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/checkbox-indeterminate.db2c5d96.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "c60f49da349a00d84dc9fc7a4f5cc62b" + force_destroy = false + id = "checkbox-indeterminate.db2c5d96.svg" + key = "checkbox-indeterminate.db2c5d96.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//checkbox-indeterminate.db2c5d96.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["close.3962dfa0.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/close.3962dfa0.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "c0865955ca37002dd8e2883f888c07c7" + force_destroy = false + id = "close.3962dfa0.svg" + key = "close.3962dfa0.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//close.3962dfa0.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["close.bf51193b.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/close.bf51193b.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "036587ca3d855f3ac5fbd8f43b4122e7" + force_destroy = false + id = "close.bf51193b.svg" + key = "close.bf51193b.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//close.bf51193b.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["correct8-alt.8f8dac23.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/correct8-alt.8f8dac23.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "f5905b100ed5db0b8a1d60de8afeb676" + force_destroy = false + id = "correct8-alt.8f8dac23.svg" + key = "correct8-alt.8f8dac23.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//correct8-alt.8f8dac23.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["correct8-alt.c14d1430.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/correct8-alt.c14d1430.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "2b82d538111387f92a11249246553a91" + force_destroy = false + id = "correct8-alt.c14d1430.svg" + key = "correct8-alt.c14d1430.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//correct8-alt.c14d1430.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["correct8.10e8a7ef.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/correct8.10e8a7ef.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "4ba9644a1430fccf876129a9491c5eba" + force_destroy = false + id = "correct8.10e8a7ef.svg" + key = "correct8.10e8a7ef.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//correct8.10e8a7ef.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["correct8.2dc455b8.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/correct8.2dc455b8.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "d63df17ba88fd8bf61d256e3e874ba47" + force_destroy = false + id = "correct8.2dc455b8.svg" + key = "correct8.2dc455b8.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//correct8.2dc455b8.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["error--white.73d13870.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/error--white.73d13870.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "06a11ba59b006a7d492d169637bf1548" + force_destroy = false + id = "error--white.73d13870.svg" + key = "error--white.73d13870.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//error--white.73d13870.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["error--white.dcc8cafd.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/error--white.dcc8cafd.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "bfbcdca379210ce5cfc9db84be8f5dc1" + force_destroy = false + id = "error--white.dcc8cafd.svg" + key = "error--white.dcc8cafd.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//error--white.dcc8cafd.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["error.1986e136.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/error.1986e136.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "fde17cc10d5eef259b5bbe18e46fab0c" + force_destroy = false + id = "error.1986e136.svg" + key = "error.1986e136.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//error.1986e136.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["error.35f61f4b.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/error.35f61f4b.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "d13a59b1e5cd75dd20b7a028671532dc" + force_destroy = false + id = "error.35f61f4b.svg" + key = "error.35f61f4b.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//error.35f61f4b.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["expand_less.5bb6de10.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/expand_less.5bb6de10.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "dfebee63a045b8ccc14dbe5f31760a27" + force_destroy = false + id = "expand_less.5bb6de10.svg" + key = "expand_less.5bb6de10.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//expand_less.5bb6de10.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["expand_less.e2846957.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/expand_less.e2846957.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "625e5aef439c7c58811ff4311fbd9ad2" + force_destroy = false + id = "expand_less.e2846957.svg" + key = "expand_less.e2846957.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//expand_less.e2846957.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["expand_more.0d06b804.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/expand_more.0d06b804.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "42030f327d3bac1e3c1b28d6b6cdd39d" + force_destroy = false + id = "expand_more.0d06b804.svg" + key = "expand_more.0d06b804.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//expand_more.0d06b804.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["expand_more.3ac36c78.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/expand_more.3ac36c78.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "ef8f6951a107bd87f958f7da30cc055c" + force_destroy = false + id = "expand_more.3ac36c78.svg" + key = "expand_more.3ac36c78.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//expand_more.3ac36c78.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file-excel.323d5d7b.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-excel.323d5d7b.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "24e33cd8762e554e0e34c96374ea2590" + force_destroy = false + id = "file-excel.323d5d7b.svg" + key = "file-excel.323d5d7b.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file-excel.323d5d7b.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file-excel.c90d2036.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-excel.c90d2036.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "22c2afbcb82b6edd4a417ca47b6b5e19" + force_destroy = false + id = "file-excel.c90d2036.svg" + key = "file-excel.c90d2036.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file-excel.c90d2036.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file-pdf.248e68e5.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-pdf.248e68e5.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "966a8ca1993ba85a960c368b55ef4e66" + force_destroy = false + id = "file-pdf.248e68e5.svg" + key = "file-pdf.248e68e5.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file-pdf.248e68e5.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file-pdf.51e66388.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-pdf.51e66388.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "51e6e0e5d44a7b72c1177086adf034a8" + force_destroy = false + id = "file-pdf.51e66388.svg" + key = "file-pdf.51e66388.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file-pdf.51e66388.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file-video.a3a38638.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-video.a3a38638.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "7d37155a50361daba2e04e5350ee1d12" + force_destroy = false + id = "file-video.a3a38638.svg" + key = "file-video.a3a38638.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file-video.a3a38638.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file-video.d309c165.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-video.d309c165.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "cead665f630764bb98d255029d937b47" + force_destroy = false + id = "file-video.d309c165.svg" + key = "file-video.d309c165.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file-video.d309c165.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file-word.75e83f68.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-word.75e83f68.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "e351d03815ea43bdfef10273d193c898" + force_destroy = false + id = "file-word.75e83f68.svg" + key = "file-word.75e83f68.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file-word.75e83f68.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file-word.896caaf0.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file-word.896caaf0.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "097753fc352f35b41d6bbaf468cceeb6" + force_destroy = false + id = "file-word.896caaf0.svg" + key = "file-word.896caaf0.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file-word.896caaf0.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file.057d5652.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file.057d5652.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "9fd817a14d685ce75a9c494c482de9f4" + force_destroy = false + id = "file.057d5652.svg" + key = "file.057d5652.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file.057d5652.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["file.f51e21d7.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/file.f51e21d7.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "f203bc7af348e50a50f017a2f11458d4" + force_destroy = false + id = "file.f51e21d7.svg" + key = "file.f51e21d7.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//file.f51e21d7.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["hero.3538e009.jpg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/hero.3538e009.jpg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/jpeg" + etag = "3e76d9e4fadd5f14d44a7093c5633c93" + force_destroy = false + id = "hero.3538e009.jpg" + key = "hero.3538e009.jpg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//hero.3538e009.jpg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["hero.abe074f7.jpg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/hero.abe074f7.jpg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/jpeg" + etag = "07c4bca195b13a0e98bcb89dfce5372b" + force_destroy = false + id = "hero.abe074f7.jpg" + key = "hero.abe074f7.jpg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//hero.abe074f7.jpg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["icon-dot-gov.1cc2b2c5.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/icon-dot-gov.1cc2b2c5.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "1c9e4fc238c699bb23f13c7dc361ee57" + force_destroy = false + id = "icon-dot-gov.1cc2b2c5.svg" + key = "icon-dot-gov.1cc2b2c5.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//icon-dot-gov.1cc2b2c5.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["icon-dot-gov.86228b6b.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/icon-dot-gov.86228b6b.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "d239f0908996c922f3650b510d0d535f" + force_destroy = false + id = "icon-dot-gov.86228b6b.svg" + key = "icon-dot-gov.86228b6b.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//icon-dot-gov.86228b6b.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["icon-https.3b0ffef2.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/icon-https.3b0ffef2.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "68d04de8513589d73c2bb79e68297eb1" + force_destroy = false + id = "icon-https.3b0ffef2.svg" + key = "icon-https.3b0ffef2.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//icon-https.3b0ffef2.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["icon-https.b6eca6f6.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/icon-https.b6eca6f6.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "72f0c58199a2f225400d70968a9afc66" + force_destroy = false + id = "icon-https.b6eca6f6.svg" + key = "icon-https.b6eca6f6.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//icon-https.b6eca6f6.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["index.html"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/index.html" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "text/html; charset=utf-8" + etag = "2ccd3b4b1a5e848c82180b88d1d08e90" + force_destroy = false + id = "index.html" + key = "index.html" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//index.html" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "HZGqmpGf35Bv23Ra_pKk_uDMAHR39KsX" +} + +# aws_s3_object.website_files["info.40b16694.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/info.40b16694.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "73ce9f949b72cc08795cbc8db357fb24" + force_destroy = false + id = "info.40b16694.svg" + key = "info.40b16694.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//info.40b16694.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["info.85fe97af.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/info.85fe97af.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "dad4bc60b84bb6fccf76651f10ef4b34" + force_destroy = false + id = "info.85fe97af.svg" + key = "info.85fe97af.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//info.85fe97af.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["launch--white.626c08f5.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/launch--white.626c08f5.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "caeb0df516eda2356247204bb01d2917" + force_destroy = false + id = "launch--white.626c08f5.svg" + key = "launch--white.626c08f5.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//launch--white.626c08f5.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["launch--white.6c179f60.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/launch--white.6c179f60.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "57ea618e288611d72609ebc341de3fdc" + force_destroy = false + id = "launch--white.6c179f60.svg" + key = "launch--white.6c179f60.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//launch--white.6c179f60.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["launch.2cde378c.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/launch.2cde378c.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "c8059dd52639b419d3303934a8ea9fd0" + force_destroy = false + id = "launch.2cde378c.svg" + key = "launch.2cde378c.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//launch.2cde378c.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["launch.f4de218c.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/launch.f4de218c.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "13a426a0665b8b448e3654a0cff206e7" + force_destroy = false + id = "launch.f4de218c.svg" + key = "launch.f4de218c.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//launch.f4de218c.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["loader.7402c183.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/loader.7402c183.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "584fad42f8bb8b5f739a50abfd94a314" + force_destroy = false + id = "loader.7402c183.svg" + key = "loader.7402c183.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//loader.7402c183.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["loader.e58cf242.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/loader.e58cf242.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "c1ce57d695c823c3ca5133b1c56606f7" + force_destroy = false + id = "loader.e58cf242.svg" + key = "loader.e58cf242.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//loader.e58cf242.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["navigate_before.7d672c1a.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_before.7d672c1a.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "d7aaa21052caa26866fbd0d372f73a99" + force_destroy = false + id = "navigate_before.7d672c1a.svg" + key = "navigate_before.7d672c1a.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//navigate_before.7d672c1a.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["navigate_before.be211c2e.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_before.be211c2e.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "248acd5ee98e1806e7d42ab947b2ba68" + force_destroy = false + id = "navigate_before.be211c2e.svg" + key = "navigate_before.be211c2e.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//navigate_before.be211c2e.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["navigate_far_before.65b60266.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_far_before.65b60266.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "2a1f57e70b9419e51c03877b8c0028f8" + force_destroy = false + id = "navigate_far_before.65b60266.svg" + key = "navigate_far_before.65b60266.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//navigate_far_before.65b60266.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["navigate_far_before.904e4c63.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_far_before.904e4c63.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "7321f53317b2771b0c81721e637bff60" + force_destroy = false + id = "navigate_far_before.904e4c63.svg" + key = "navigate_far_before.904e4c63.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//navigate_far_before.904e4c63.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["navigate_far_next.37ce8b6e.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_far_next.37ce8b6e.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "a4306f47c1f8155e74bc7edb5ea95487" + force_destroy = false + id = "navigate_far_next.37ce8b6e.svg" + key = "navigate_far_next.37ce8b6e.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//navigate_far_next.37ce8b6e.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["navigate_far_next.bd2c92a4.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_far_next.bd2c92a4.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "7aa4ae9f1d1397a64bb5f34aab3f3a95" + force_destroy = false + id = "navigate_far_next.bd2c92a4.svg" + key = "navigate_far_next.bd2c92a4.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//navigate_far_next.bd2c92a4.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["navigate_next.bc7c1a4e.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_next.bc7c1a4e.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "a8e5c097da5bdbe97880bfbd67b99d38" + force_destroy = false + id = "navigate_next.bc7c1a4e.svg" + key = "navigate_next.bc7c1a4e.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//navigate_next.bc7c1a4e.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["navigate_next.fb4be146.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/navigate_next.fb4be146.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "e4449ac35306cc39af51a48bf48a2f90" + force_destroy = false + id = "navigate_next.fb4be146.svg" + key = "navigate_next.fb4be146.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//navigate_next.fb4be146.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["pdf.worker.min.mjs"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/pdf.worker.min.mjs" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "213ab48603654c9423b7d45e2ee4a48a" + force_destroy = false + id = "pdf.worker.min.mjs" + key = "pdf.worker.min.mjs" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//pdf.worker.min.mjs" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "6JZajVCr5eHRzeES2PkEnwAjUV_Zd.3b" +} + +# aws_s3_object.website_files["remove.27bfc20f.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/remove.27bfc20f.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "9fcf8726387df8b26f01dec005aa740a" + force_destroy = false + id = "remove.27bfc20f.svg" + key = "remove.27bfc20f.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//remove.27bfc20f.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["remove.ca324d27.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/remove.ca324d27.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "28fac28bf8d7a32df31759deac776400" + force_destroy = false + id = "remove.ca324d27.svg" + key = "remove.ca324d27.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//remove.ca324d27.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-300.40a89411.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-300.40a89411.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "db95b37ec6ef04cd349d098941e5f70b" + force_destroy = false + id = "roboto-mono-v5-latin-300.40a89411.woff2" + key = "roboto-mono-v5-latin-300.40a89411.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-300.40a89411.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-300.98978b08.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-300.98978b08.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "db95b37ec6ef04cd349d098941e5f70b" + force_destroy = false + id = "roboto-mono-v5-latin-300.98978b08.woff2" + key = "roboto-mono-v5-latin-300.98978b08.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-300.98978b08.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-300italic.1710c37a.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-300italic.1710c37a.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "18357ddaddc4d4f13c5f49985327a2db" + force_destroy = false + id = "roboto-mono-v5-latin-300italic.1710c37a.woff2" + key = "roboto-mono-v5-latin-300italic.1710c37a.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-300italic.1710c37a.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-300italic.af4ea91a.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-300italic.af4ea91a.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "18357ddaddc4d4f13c5f49985327a2db" + force_destroy = false + id = "roboto-mono-v5-latin-300italic.af4ea91a.woff2" + key = "roboto-mono-v5-latin-300italic.af4ea91a.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-300italic.af4ea91a.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-700.d309a69d.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-700.d309a69d.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "4bc0bd04b8b2f6730b5997503fa4ce5a" + force_destroy = false + id = "roboto-mono-v5-latin-700.d309a69d.woff2" + key = "roboto-mono-v5-latin-700.d309a69d.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-700.d309a69d.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-700.df366d23.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-700.df366d23.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "4bc0bd04b8b2f6730b5997503fa4ce5a" + force_destroy = false + id = "roboto-mono-v5-latin-700.df366d23.woff2" + key = "roboto-mono-v5-latin-700.df366d23.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-700.df366d23.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-700italic.03671595.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-700italic.03671595.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "5eca10b5e58c4e2a8164f89bef5dbd7e" + force_destroy = false + id = "roboto-mono-v5-latin-700italic.03671595.woff2" + key = "roboto-mono-v5-latin-700italic.03671595.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-700italic.03671595.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-700italic.2b983f5d.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-700italic.2b983f5d.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "5eca10b5e58c4e2a8164f89bef5dbd7e" + force_destroy = false + id = "roboto-mono-v5-latin-700italic.2b983f5d.woff2" + key = "roboto-mono-v5-latin-700italic.2b983f5d.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-700italic.2b983f5d.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-italic.7226ad2c.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-italic.7226ad2c.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "55befc28b6c06f712a38e2f4153967e1" + force_destroy = false + id = "roboto-mono-v5-latin-italic.7226ad2c.woff2" + key = "roboto-mono-v5-latin-italic.7226ad2c.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-italic.7226ad2c.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-italic.9209c633.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-italic.9209c633.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "55befc28b6c06f712a38e2f4153967e1" + force_destroy = false + id = "roboto-mono-v5-latin-italic.9209c633.woff2" + key = "roboto-mono-v5-latin-italic.9209c633.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-italic.9209c633.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-regular.e048516f.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-regular.e048516f.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "e92cc0fb9e1a7debc138224fd02a462a" + force_destroy = false + id = "roboto-mono-v5-latin-regular.e048516f.woff2" + key = "roboto-mono-v5-latin-regular.e048516f.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-regular.e048516f.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["roboto-mono-v5-latin-regular.ec7832e5.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/roboto-mono-v5-latin-regular.ec7832e5.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "e92cc0fb9e1a7debc138224fd02a462a" + force_destroy = false + id = "roboto-mono-v5-latin-regular.ec7832e5.woff2" + key = "roboto-mono-v5-latin-regular.ec7832e5.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//roboto-mono-v5-latin-regular.ec7832e5.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["search.a724f1bc.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/search.a724f1bc.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "2be400ec2879d462e871c0f8f255e3d6" + force_destroy = false + id = "search.a724f1bc.svg" + key = "search.a724f1bc.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//search.a724f1bc.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["search.e5ee4fc2.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/search.e5ee4fc2.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "6aa89b3d4533e41fc29c59209167d0a2" + force_destroy = false + id = "search.e5ee4fc2.svg" + key = "search.e5ee4fc2.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//search.e5ee4fc2.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-bold-webfont.21f8979c.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-bold-webfont.21f8979c.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "f12f6a2f439c99a103193981f69c3353" + force_destroy = false + id = "sourcesanspro-bold-webfont.21f8979c.woff2" + key = "sourcesanspro-bold-webfont.21f8979c.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-bold-webfont.21f8979c.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-bold-webfont.5436602a.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-bold-webfont.5436602a.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "f12f6a2f439c99a103193981f69c3353" + force_destroy = false + id = "sourcesanspro-bold-webfont.5436602a.woff2" + key = "sourcesanspro-bold-webfont.5436602a.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-bold-webfont.5436602a.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.84e64ba8.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-bolditalic-webfont.84e64ba8.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "dae8945d820658d0cb8f15764e441cc6" + force_destroy = false + id = "sourcesanspro-bolditalic-webfont.84e64ba8.woff2" + key = "sourcesanspro-bolditalic-webfont.84e64ba8.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-bolditalic-webfont.84e64ba8.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-bolditalic-webfont.b61b42d2.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-bolditalic-webfont.b61b42d2.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "dae8945d820658d0cb8f15764e441cc6" + force_destroy = false + id = "sourcesanspro-bolditalic-webfont.b61b42d2.woff2" + key = "sourcesanspro-bolditalic-webfont.b61b42d2.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-bolditalic-webfont.b61b42d2.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-italic-webfont.82ff76d1.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-italic-webfont.82ff76d1.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "8740838d2f0e9325e59b6e3c007a7130" + force_destroy = false + id = "sourcesanspro-italic-webfont.82ff76d1.woff2" + key = "sourcesanspro-italic-webfont.82ff76d1.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-italic-webfont.82ff76d1.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-italic-webfont.ab90d6d1.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-italic-webfont.ab90d6d1.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "8740838d2f0e9325e59b6e3c007a7130" + force_destroy = false + id = "sourcesanspro-italic-webfont.ab90d6d1.woff2" + key = "sourcesanspro-italic-webfont.ab90d6d1.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-italic-webfont.ab90d6d1.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-light-webfont.e490d910.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-light-webfont.e490d910.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "4572c51ee35f958fd2d4f04211c2ddc8" + force_destroy = false + id = "sourcesanspro-light-webfont.e490d910.woff2" + key = "sourcesanspro-light-webfont.e490d910.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-light-webfont.e490d910.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-light-webfont.e70d4904.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-light-webfont.e70d4904.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "4572c51ee35f958fd2d4f04211c2ddc8" + force_destroy = false + id = "sourcesanspro-light-webfont.e70d4904.woff2" + key = "sourcesanspro-light-webfont.e70d4904.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-light-webfont.e70d4904.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.05b42991.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-lightitalic-webfont.05b42991.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "ffb6369e594a83b16001ca58c1ee9039" + force_destroy = false + id = "sourcesanspro-lightitalic-webfont.05b42991.woff2" + key = "sourcesanspro-lightitalic-webfont.05b42991.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-lightitalic-webfont.05b42991.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-lightitalic-webfont.c14df9bc.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-lightitalic-webfont.c14df9bc.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "ffb6369e594a83b16001ca58c1ee9039" + force_destroy = false + id = "sourcesanspro-lightitalic-webfont.c14df9bc.woff2" + key = "sourcesanspro-lightitalic-webfont.c14df9bc.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-lightitalic-webfont.c14df9bc.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-regular-webfont.76c0bde6.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-regular-webfont.76c0bde6.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "d67b548b833d70dda3779916f5415e7e" + force_destroy = false + id = "sourcesanspro-regular-webfont.76c0bde6.woff2" + key = "sourcesanspro-regular-webfont.76c0bde6.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-regular-webfont.76c0bde6.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["sourcesanspro-regular-webfont.a9d6b459.woff2"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/sourcesanspro-regular-webfont.a9d6b459.woff2" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "font/woff2" + etag = "d67b548b833d70dda3779916f5415e7e" + force_destroy = false + id = "sourcesanspro-regular-webfont.a9d6b459.woff2" + key = "sourcesanspro-regular-webfont.a9d6b459.woff2" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//sourcesanspro-regular-webfont.a9d6b459.woff2" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.24de9d80.js"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.24de9d80.js" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/javascript" + etag = "988b70b40be2f0314481900a5c536520" + force_destroy = false + id = "ui.24de9d80.js" + key = "ui.24de9d80.js" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.24de9d80.js" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.24de9d80.js.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.24de9d80.js.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "d10b75eab24e3b17c82dfe793bb0c197-2" + force_destroy = false + id = "ui.24de9d80.js.map" + key = "ui.24de9d80.js.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.24de9d80.js.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = ".S.34IQj0kAQhvdnmPlQEg1E61S8zYrN" +} + +# aws_s3_object.website_files["ui.31b563d9.js"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.31b563d9.js" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/javascript" + etag = "01f05bef6cafd19e0e1cb3bd102f0c1c" + force_destroy = false + id = "ui.31b563d9.js" + key = "ui.31b563d9.js" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.31b563d9.js" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.31b563d9.js.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.31b563d9.js.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "b4ab5047433ae9a15260c6280fab352f" + force_destroy = false + id = "ui.31b563d9.js.map" + key = "ui.31b563d9.js.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.31b563d9.js.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.383343a4.js"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.383343a4.js" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/javascript" + etag = "d3c3ac003d73acd50a8fced8fd15b181" + force_destroy = false + id = "ui.383343a4.js" + key = "ui.383343a4.js" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.383343a4.js" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.383343a4.js.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.383343a4.js.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "2061e6c7acb903ff425164499447f426-2" + force_destroy = false + id = "ui.383343a4.js.map" + key = "ui.383343a4.js.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.383343a4.js.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "ckEM6PjYr8zpOhywI.QjYXHQe27ztMmK" +} + +# aws_s3_object.website_files["ui.394b89ad.css"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.394b89ad.css" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "text/css; charset=utf-8" + etag = "07639841c3f2c11c538edbd93de57a6f" + force_destroy = false + id = "ui.394b89ad.css" + key = "ui.394b89ad.css" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.394b89ad.css" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.394b89ad.css.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.394b89ad.css.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "aaae7f6b549c1b425538ad3a5e8f1244" + force_destroy = false + id = "ui.394b89ad.css.map" + key = "ui.394b89ad.css.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.394b89ad.css.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.4540ed92.js"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.4540ed92.js" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/javascript" + etag = "b2a077c3c016057ede33fd7334f2ce63" + force_destroy = false + id = "ui.4540ed92.js" + key = "ui.4540ed92.js" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.4540ed92.js" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.4540ed92.js.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.4540ed92.js.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "700182aa0a2dcfdd93fc8291ecb10d29" + force_destroy = false + id = "ui.4540ed92.js.map" + key = "ui.4540ed92.js.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.4540ed92.js.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.4e22b301.js"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.4e22b301.js" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/javascript" + etag = "245a950194a7e0f0e041ab26cbc1d290" + force_destroy = false + id = "ui.4e22b301.js" + key = "ui.4e22b301.js" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.4e22b301.js" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.4e22b301.js.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.4e22b301.js.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "81b90eb36e2b718a3dad1f0a9d6c6a49" + force_destroy = false + id = "ui.4e22b301.js.map" + key = "ui.4e22b301.js.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.4e22b301.js.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.7c42bb32.css"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.7c42bb32.css" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "text/css; charset=utf-8" + etag = "c5a87ffc2db465beb40c24ba24485220" + force_destroy = false + id = "ui.7c42bb32.css" + key = "ui.7c42bb32.css" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.7c42bb32.css" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.7c42bb32.css.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.7c42bb32.css.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "d7e48f6e687b5c4e697c0e43999eec70" + force_destroy = false + id = "ui.7c42bb32.css.map" + key = "ui.7c42bb32.css.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.7c42bb32.css.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.9f367d21.css"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.9f367d21.css" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "text/css; charset=utf-8" + etag = "e7bd07d0eec9674884a3010871dbb5a2" + force_destroy = false + id = "ui.9f367d21.css" + key = "ui.9f367d21.css" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.9f367d21.css" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.9f367d21.css.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.9f367d21.css.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "348e434f681101e28ae799774cbc7220" + force_destroy = false + id = "ui.9f367d21.css.map" + key = "ui.9f367d21.css.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.9f367d21.css.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.b82c2ba3.js"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.b82c2ba3.js" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/javascript" + etag = "6cfa17821e273870c789a3b4daa991d0" + force_destroy = false + id = "ui.b82c2ba3.js" + key = "ui.b82c2ba3.js" + server_side_encryption = "AES256" + source = "./../ui/dist//ui.b82c2ba3.js" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "XcX7iLmWxLkmtoc0JirYJGB9Xf.giweL" +} + +# aws_s3_object.website_files["ui.b82c2ba3.js.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.b82c2ba3.js.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "bb5a0d39b9f9186f155cde01e3abccb9-2" + force_destroy = false + id = "ui.b82c2ba3.js.map" + key = "ui.b82c2ba3.js.map" + server_side_encryption = "AES256" + source = "./../ui/dist//ui.b82c2ba3.js.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "qlo6HXIVoHHmoRe7jTc2uPwe2g5FUFrx" +} + +# aws_s3_object.website_files["ui.d119b665.js"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.d119b665.js" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/javascript" + etag = "5ac440bda819fad729551a3c52f8b9b9" + force_destroy = false + id = "ui.d119b665.js" + key = "ui.d119b665.js" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.d119b665.js" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "CCpTPeZi0Y5Mu6jTAQZOv.qxK1K53Laz" +} + +# aws_s3_object.website_files["ui.d119b665.js.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.d119b665.js.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "517bef80e4a43d19feb4c2dad09b5710-2" + force_destroy = false + id = "ui.d119b665.js.map" + key = "ui.d119b665.js.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.d119b665.js.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "uSMwLmNaJa7b1xwghH5KAilsbAJziDu7" +} + +# aws_s3_object.website_files["ui.ee3dfc51.js"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.ee3dfc51.js" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/javascript" + etag = "50e0bef214f9a8064d4de2c15614670f" + force_destroy = false + id = "ui.ee3dfc51.js" + key = "ui.ee3dfc51.js" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.ee3dfc51.js" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["ui.ee3dfc51.js.map"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/ui.ee3dfc51.js.map" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "application/octet-stream" + etag = "3ab313132e57709c6cb8c46431a386b5" + force_destroy = false + id = "ui.ee3dfc51.js.map" + key = "ui.ee3dfc51.js.map" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//ui.ee3dfc51.js.map" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["unfold_more.1f608602.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/unfold_more.1f608602.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "6020c06854b7cbe641066b9291ac9e77" + force_destroy = false + id = "unfold_more.1f608602.svg" + key = "unfold_more.1f608602.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//unfold_more.1f608602.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["unfold_more.46c00529.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/unfold_more.46c00529.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "acf8696ec8949f878b06bb19da32f1fb" + force_destroy = false + id = "unfold_more.46c00529.svg" + key = "unfold_more.46c00529.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//unfold_more.46c00529.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["us_flag_small.9c3c6ab8.png"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/us_flag_small.9c3c6ab8.png" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/png" + etag = "ab5090f5b92619c69a7dd2e2ee05b3e5" + force_destroy = false + id = "us_flag_small.9c3c6ab8.png" + key = "us_flag_small.9c3c6ab8.png" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//us_flag_small.9c3c6ab8.png" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["us_flag_small.e642a40d.png"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/us_flag_small.e642a40d.png" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/png" + etag = "ab5090f5b92619c69a7dd2e2ee05b3e5" + force_destroy = false + id = "us_flag_small.e642a40d.png" + key = "us_flag_small.e642a40d.png" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//us_flag_small.e642a40d.png" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["warning.4cc8d763.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/warning.4cc8d763.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "2d22a8d3f2aef0ce4599b699f3807e72" + force_destroy = false + id = "warning.4cc8d763.svg" + key = "warning.4cc8d763.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//warning.4cc8d763.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_s3_object.website_files["warning.ce0fedc9.svg"]: +resource "aws_s3_object" "website_files" { + arn = "arn:aws:s3:::document-extractor-dev-website-328307993388/warning.ce0fedc9.svg" + bucket = "document-extractor-dev-website-328307993388" + bucket_key_enabled = false + content_type = "image/svg+xml" + etag = "93b6b1635a5809f74f86425e3344a32d" + force_destroy = false + id = "warning.ce0fedc9.svg" + key = "warning.ce0fedc9.svg" + metadata = {} + server_side_encryption = "AES256" + source = "./../ui/dist//warning.ce0fedc9.svg" + storage_class = "STANDARD" + tags = { + "project" = "document-extractor" + } + tags_all = { + "project" = "document-extractor" + } + version_id = "null" +} + +# aws_secretsmanager_secret.password: +resource "aws_secretsmanager_secret" "password" { + arn = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-password-5KgJiD" + force_overwrite_replica_secret = false + id = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-password-5KgJiD" + name = "document-extractor-dev-password" + recovery_window_in_days = 30 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_secretsmanager_secret.private_key: +resource "aws_secretsmanager_secret" "private_key" { + arn = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-private-key-PI2EsC" + force_overwrite_replica_secret = false + id = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-private-key-PI2EsC" + name = "document-extractor-dev-private-key" + recovery_window_in_days = 30 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_secretsmanager_secret.public_key: +resource "aws_secretsmanager_secret" "public_key" { + arn = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-public-key-PI2EsC" + force_overwrite_replica_secret = false + id = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-public-key-PI2EsC" + name = "document-extractor-dev-public-key" + recovery_window_in_days = 30 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_secretsmanager_secret.username: +resource "aws_secretsmanager_secret" "username" { + arn = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-username-gKcWPC" + force_overwrite_replica_secret = false + id = "arn:aws:secretsmanager:us-west-1:328307993388:secret:document-extractor-dev-username-gKcWPC" + name = "document-extractor-dev-username" + recovery_window_in_days = 30 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } +} + +# aws_sqs_queue.queue_to_dynamo: +resource "aws_sqs_queue" "queue_to_dynamo" { + arn = "arn:aws:sqs:us-west-1:328307993388:document-extractor-dev-to-dynamodb" + content_based_deduplication = false + delay_seconds = 0 + fifo_queue = false + id = "https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb" + kms_data_key_reuse_period_seconds = 300 + kms_master_key_id = "2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + max_message_size = 262144 + message_retention_seconds = 345600 + name = "document-extractor-dev-to-dynamodb" + receive_wait_time_seconds = 0 + sqs_managed_sse_enabled = false + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + url = "https://sqs.us-west-1.amazonaws.com/328307993388/document-extractor-dev-to-dynamodb" + visibility_timeout_seconds = 30 +} + +# null_resource.invalidate_cloudfront: +resource "null_resource" "invalidate_cloudfront" { + id = "8325867666043885233" + triggers = { + "always_run" = "2025-05-22T21:19:22Z" + } +} + + +# module.document_endpoints.data.aws_api_gateway_rest_api.api: +data "aws_api_gateway_rest_api" "api" { + api_key_source = "HEADER" + arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l" + binary_media_types = [] + description = "document-extractor API" + endpoint_configuration = [ + { + ip_address_type = "ipv4" + types = [ + "EDGE", + ] + vpc_endpoint_ids = [] + }, + ] + execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l" + id = "ic9avcgf3l" + name = "document-extractor-dev-api" + root_resource_id = "qdlfjof80i" + tags = { + "project" = "document-extractor-dev" + } +} + +# module.document_endpoints.aws_api_gateway_integration.lambda_integration[0]: +resource "aws_api_gateway_integration" "lambda_integration" { + cache_key_parameters = [] + cache_namespace = "ljz2z9" + connection_type = "INTERNET" + http_method = "POST" + id = "agi-ic9avcgf3l-ljz2z9-POST" + integration_http_method = "POST" + passthrough_behavior = "WHEN_NO_MATCH" + request_parameters = {} + request_templates = {} + resource_id = "ljz2z9" + rest_api_id = "ic9avcgf3l" + timeout_milliseconds = 29000 + type = "AWS_PROXY" + uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document/invocations" +} + +# module.document_endpoints.aws_api_gateway_method.http_method[0]: +resource "aws_api_gateway_method" "http_method" { + api_key_required = false + authorization = "CUSTOM" + authorization_scopes = [] + authorizer_id = "bq0hho" + http_method = "POST" + id = "agm-ic9avcgf3l-ljz2z9-POST" + request_models = {} + request_parameters = {} + resource_id = "ljz2z9" + rest_api_id = "ic9avcgf3l" +} + +# module.document_endpoints.aws_api_gateway_resource.resource_path: +resource "aws_api_gateway_resource" "resource_path" { + id = "ljz2z9" + parent_id = "qdlfjof80i" + path = "/document" + path_part = "document" + rest_api_id = "ic9avcgf3l" +} + +# module.document_endpoints.aws_lambda_function.function[0]: +resource "aws_lambda_function" "function" { + architectures = [ + "arm64", + ] + arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document" + code_sha256 = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + filename = "./../backend/dist/lambda.zip" + function_name = "document-extractor-dev-create-document" + handler = "src.external.aws.lambdas.s3_file_upload.lambda_handler" + id = "document-extractor-dev-create-document" + invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document/invocations" + kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + last_modified = "2025-05-22T20:50:25.000+0000" + layers = [] + memory_size = 256 + package_type = "Zip" + publish = true + qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document:5" + qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-create-document:5/invocations" + reserved_concurrent_executions = -1 + role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" + runtime = "python3.13" + skip_destroy = false + source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + source_code_size = 19659547 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + timeout = 30 + version = "5" + + environment { + variables = (sensitive value) + } + + ephemeral_storage { + size = 512 + } + + logging_config { + log_format = "Text" + log_group = "/aws/lambda/document-extractor-dev-create-document" + } + + tracing_config { + mode = "PassThrough" + } +} + +# module.document_endpoints.aws_lambda_permission.api_gateway[0]: +resource "aws_lambda_permission" "api_gateway" { + action = "lambda:InvokeFunction" + function_name = "document-extractor-dev-create-document" + id = "AllowExecutionFromApiGateway" + principal = "apigateway.amazonaws.com" + source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*/*" + statement_id = "AllowExecutionFromApiGateway" +} + +# module.document_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: +resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + function_name = "document-extractor-dev-create-document" + id = "document-extractor-dev-create-document,5" + provisioned_concurrent_executions = 1 + qualifier = "5" + skip_destroy = false +} + + +# module.document_id_endpoints.data.aws_api_gateway_rest_api.api: +data "aws_api_gateway_rest_api" "api" { + api_key_source = "HEADER" + arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l" + binary_media_types = [] + description = "document-extractor API" + endpoint_configuration = [ + { + ip_address_type = "ipv4" + types = [ + "EDGE", + ] + vpc_endpoint_ids = [] + }, + ] + execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l" + id = "ic9avcgf3l" + name = "document-extractor-dev-api" + root_resource_id = "qdlfjof80i" + tags = { + "project" = "document-extractor-dev" + } +} + +# module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[0]: +resource "aws_api_gateway_integration" "lambda_integration" { + cache_key_parameters = [] + cache_namespace = "g3ywx2" + connection_type = "INTERNET" + http_method = "GET" + id = "agi-ic9avcgf3l-g3ywx2-GET" + integration_http_method = "POST" + passthrough_behavior = "WHEN_NO_MATCH" + request_parameters = {} + request_templates = {} + resource_id = "g3ywx2" + rest_api_id = "ic9avcgf3l" + timeout_milliseconds = 29000 + type = "AWS_PROXY" + uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document/invocations" +} + +# module.document_id_endpoints.aws_api_gateway_integration.lambda_integration[1]: +resource "aws_api_gateway_integration" "lambda_integration" { + cache_key_parameters = [] + cache_namespace = "g3ywx2" + connection_type = "INTERNET" + http_method = "PUT" + id = "agi-ic9avcgf3l-g3ywx2-PUT" + integration_http_method = "POST" + passthrough_behavior = "WHEN_NO_MATCH" + request_parameters = {} + request_templates = {} + resource_id = "g3ywx2" + rest_api_id = "ic9avcgf3l" + timeout_milliseconds = 29000 + type = "AWS_PROXY" + uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document/invocations" +} + +# module.document_id_endpoints.aws_api_gateway_method.http_method[0]: +resource "aws_api_gateway_method" "http_method" { + api_key_required = false + authorization = "CUSTOM" + authorization_scopes = [] + authorizer_id = "bq0hho" + http_method = "GET" + id = "agm-ic9avcgf3l-g3ywx2-GET" + request_models = {} + request_parameters = {} + resource_id = "g3ywx2" + rest_api_id = "ic9avcgf3l" +} + +# module.document_id_endpoints.aws_api_gateway_method.http_method[1]: +resource "aws_api_gateway_method" "http_method" { + api_key_required = false + authorization = "CUSTOM" + authorization_scopes = [] + authorizer_id = "bq0hho" + http_method = "PUT" + id = "agm-ic9avcgf3l-g3ywx2-PUT" + request_models = {} + request_parameters = {} + resource_id = "g3ywx2" + rest_api_id = "ic9avcgf3l" +} + +# module.document_id_endpoints.aws_api_gateway_resource.resource_path: +resource "aws_api_gateway_resource" "resource_path" { + id = "g3ywx2" + parent_id = "ljz2z9" + path = "/document/{document_id}" + path_part = "{document_id}" + rest_api_id = "ic9avcgf3l" +} + +# module.document_id_endpoints.aws_lambda_function.function[0]: +resource "aws_lambda_function" "function" { + architectures = [ + "arm64", + ] + arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document" + code_sha256 = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + filename = "./../backend/dist/lambda.zip" + function_name = "document-extractor-dev-get-document" + handler = "src.external.aws.lambdas.get_extracted_document.lambda_handler" + id = "document-extractor-dev-get-document" + invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document/invocations" + kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + last_modified = "2025-05-22T20:49:55.000+0000" + layers = [] + memory_size = 256 + package_type = "Zip" + publish = true + qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document:5" + qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-get-document:5/invocations" + reserved_concurrent_executions = -1 + role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" + runtime = "python3.13" + skip_destroy = false + source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + source_code_size = 19659547 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + timeout = 30 + version = "5" + + environment { + variables = (sensitive value) + } + + ephemeral_storage { + size = 512 + } + + logging_config { + log_format = "Text" + log_group = "/aws/lambda/document-extractor-dev-get-document" + } + + tracing_config { + mode = "PassThrough" + } +} + +# module.document_id_endpoints.aws_lambda_function.function[1]: +resource "aws_lambda_function" "function" { + architectures = [ + "arm64", + ] + arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document" + code_sha256 = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + filename = "./../backend/dist/lambda.zip" + function_name = "document-extractor-dev-update-document" + handler = "src.external.aws.lambdas.update_extracted_document.lambda_handler" + id = "document-extractor-dev-update-document" + invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document/invocations" + kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + last_modified = "2025-05-22T20:49:31.000+0000" + layers = [] + memory_size = 256 + package_type = "Zip" + publish = true + qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document:5" + qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-update-document:5/invocations" + reserved_concurrent_executions = -1 + role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" + runtime = "python3.13" + skip_destroy = false + source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + source_code_size = 19659547 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + timeout = 30 + version = "5" + + environment { + variables = (sensitive value) + } + + ephemeral_storage { + size = 512 + } + + logging_config { + log_format = "Text" + log_group = "/aws/lambda/document-extractor-dev-update-document" + } + + tracing_config { + mode = "PassThrough" + } +} + +# module.document_id_endpoints.aws_lambda_permission.api_gateway[0]: +resource "aws_lambda_permission" "api_gateway" { + action = "lambda:InvokeFunction" + function_name = "document-extractor-dev-get-document" + id = "AllowExecutionFromApiGateway" + principal = "apigateway.amazonaws.com" + source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*/*" + statement_id = "AllowExecutionFromApiGateway" +} + +# module.document_id_endpoints.aws_lambda_permission.api_gateway[1]: +resource "aws_lambda_permission" "api_gateway" { + action = "lambda:InvokeFunction" + function_name = "document-extractor-dev-update-document" + id = "AllowExecutionFromApiGateway" + principal = "apigateway.amazonaws.com" + source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*/*" + statement_id = "AllowExecutionFromApiGateway" +} + +# module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: +resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + function_name = "document-extractor-dev-get-document" + id = "document-extractor-dev-get-document,5" + provisioned_concurrent_executions = 1 + qualifier = "5" + skip_destroy = false +} + +# module.document_id_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[1]: +resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + function_name = "document-extractor-dev-update-document" + id = "document-extractor-dev-update-document,5" + provisioned_concurrent_executions = 1 + qualifier = "5" + skip_destroy = false +} + + +# module.token_endpoints.data.aws_api_gateway_rest_api.api: +data "aws_api_gateway_rest_api" "api" { + api_key_source = "HEADER" + arn = "arn:aws:apigateway:us-west-1::/restapis/ic9avcgf3l" + binary_media_types = [] + description = "document-extractor API" + endpoint_configuration = [ + { + ip_address_type = "ipv4" + types = [ + "EDGE", + ] + vpc_endpoint_ids = [] + }, + ] + execution_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l" + id = "ic9avcgf3l" + name = "document-extractor-dev-api" + root_resource_id = "qdlfjof80i" + tags = { + "project" = "document-extractor-dev" + } +} + +# module.token_endpoints.aws_api_gateway_integration.lambda_integration[0]: +resource "aws_api_gateway_integration" "lambda_integration" { + cache_key_parameters = [] + cache_namespace = "dmuib4" + connection_type = "INTERNET" + http_method = "POST" + id = "agi-ic9avcgf3l-dmuib4-POST" + integration_http_method = "POST" + passthrough_behavior = "WHEN_NO_MATCH" + request_parameters = {} + request_templates = {} + resource_id = "dmuib4" + rest_api_id = "ic9avcgf3l" + timeout_milliseconds = 29000 + type = "AWS_PROXY" + uri = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token/invocations" +} + +# module.token_endpoints.aws_api_gateway_method.http_method[0]: +resource "aws_api_gateway_method" "http_method" { + api_key_required = false + authorization = "NONE" + authorization_scopes = [] + http_method = "POST" + id = "agm-ic9avcgf3l-dmuib4-POST" + request_models = {} + request_parameters = {} + resource_id = "dmuib4" + rest_api_id = "ic9avcgf3l" +} + +# module.token_endpoints.aws_api_gateway_resource.resource_path: +resource "aws_api_gateway_resource" "resource_path" { + id = "dmuib4" + parent_id = "qdlfjof80i" + path = "/token" + path_part = "token" + rest_api_id = "ic9avcgf3l" +} + +# module.token_endpoints.aws_lambda_function.function[0]: +resource "aws_lambda_function" "function" { + architectures = [ + "arm64", + ] + arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token" + code_sha256 = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + filename = "./../backend/dist/lambda.zip" + function_name = "document-extractor-dev-token" + handler = "src.external.aws.lambdas.token.lambda_handler" + id = "document-extractor-dev-token" + invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token/invocations" + kms_key_arn = "arn:aws:kms:us-west-1:328307993388:key/2c84ed0b-7d29-4d92-ab32-f0db5557a13c" + last_modified = "2025-05-22T20:49:10.000+0000" + layers = [] + memory_size = 256 + package_type = "Zip" + publish = true + qualified_arn = "arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token:5" + qualified_invoke_arn = "arn:aws:apigateway:us-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-1:328307993388:function:document-extractor-dev-token:5/invocations" + reserved_concurrent_executions = -1 + role = "arn:aws:iam::328307993388:role/document-extractor-dev-lambda-execution-role" + runtime = "python3.13" + skip_destroy = false + source_code_hash = "7hfzRPB44Pnra9oswy/9jJGr2iFhrg+RYqaU5cOuc9o=" + source_code_size = 19659547 + tags = {} + tags_all = { + "project" = "document-extractor-dev" + } + timeout = 30 + version = "5" + + environment { + variables = (sensitive value) + } + + ephemeral_storage { + size = 512 + } + + logging_config { + log_format = "Text" + log_group = "/aws/lambda/document-extractor-dev-token" + } + + tracing_config { + mode = "PassThrough" + } +} + +# module.token_endpoints.aws_lambda_permission.api_gateway[0]: +resource "aws_lambda_permission" "api_gateway" { + action = "lambda:InvokeFunction" + function_name = "document-extractor-dev-token" + id = "AllowExecutionFromApiGateway" + principal = "apigateway.amazonaws.com" + source_arn = "arn:aws:execute-api:us-west-1:328307993388:ic9avcgf3l/*/*/*" + statement_id = "AllowExecutionFromApiGateway" +} + +# module.token_endpoints.aws_lambda_provisioned_concurrency_config.api_function_concurrency[0]: +resource "aws_lambda_provisioned_concurrency_config" "api_function_concurrency" { + function_name = "document-extractor-dev-token" + id = "document-extractor-dev-token,5" + provisioned_concurrent_executions = 1 + qualifier = "5" + skip_destroy = false +} + + +Outputs: + +distribution_id = "ENWTZLUASCDJZ" diff --git a/iac/.terraform.lock.hcl b/iac/.terraform.lock.hcl index 60af035..8b748ee 100644 --- a/iac/.terraform.lock.hcl +++ b/iac/.terraform.lock.hcl @@ -1,25 +1,37 @@ -# This file is maintained automatically by "terraform init". +# This file is maintained automatically by "tofu init". # Manual edits may be lost in future updates. -provider "registry.terraform.io/hashicorp/aws" { - version = "5.97.0" - constraints = "5.97.0" +provider "registry.opentofu.org/hashicorp/aws" { + version = "5.99.1" + constraints = ">= 5.0.0" hashes = [ - "h1:rUDE0OgA+6IiEA+w0cPp3/QQNH4SpjFjYcQ6p7byKS4=", - "zh:02790ad98b767d8f24d28e8be623f348bcb45590205708334d52de2fb14f5a95", - "zh:088b4398a161e45762dc28784fcc41c4fa95bd6549cb708b82de577f2d39ffc7", - "zh:0c381a457b7af391c43fc0167919443f6105ad2702bde4d02ddea9fd7c9d3539", - "zh:1a4b57a5043dcca64d8b8bae8b30ef4f6b98ed2144f792f39c4e816d3f1e2c56", - "zh:1bf00a67f39e67664337bde065180d41d952242801ebcd1c777061d4ffaa1cc1", - "zh:24c549f53d6bd022af31426d3e78f21264d8a72409821669e7fd41966ae68b2b", - "zh:3abda50bbddb35d86081fe39522e995280aea7f004582c4af22112c03ac8b375", - "zh:7388ed7f21ce2eb46bd9066626ce5f3e2a5705f67f643acce8ae71972f66eaf6", - "zh:96740f2ff94e5df2b2d29a5035a1a1026fe821f61712b2099b224fb2c2277663", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9f399f8e8683a3a3a6d63a41c7c3a5a5f266eedef40ea69eba75bacf03699879", - "zh:bcf2b288d4706ebd198f75d2159663d657535483331107f2cdef381f10688baf", - "zh:cc76c8a9fc3bad05a8779c1f80fe8c388734f1ec1dd0affa863343490527b466", - "zh:de4359cf1b057bfe7a563be93829ec64bf72e7a2b85a72d075238081ef5eb1db", - "zh:e208fa77051a1f9fa1eff6c5c58aabdcab0de1695b97cdea7b8dd81df3e0ed73", + "h1:0jNckFqimGrHhRB88880ovIpmoE20xhjRb94GBdgjwo=", + "zh:13a07422f776dd97214dfa89d6a88340b99613cbb869013c756c1a68fd8cdd9d", + "zh:1841d422278afa25d42a8d3ea9197ad08cf092769bd2aa89056d25d4c2629df8", + "zh:269016c7ba09d76e42fbcf15de28f2de0595ff9a7304a0500011a4493d7a1551", + "zh:2b842c3d0f30e048c05a37752b9c07d316656f3caf79841d08a4f1b057555eb2", + "zh:6559eedc095f70a51460dc702613a9033734ba536c1de1ed86a735a3c8131e40", + "zh:6d43b2676630344db3a7d6ba8330d20993492168f124e19e040a0aa914ec832e", + "zh:7f5d5cb0c1a492080b668f456de50f5b91fc67018c05f12483added3faf703f6", + "zh:c3bb8094bf26565150229f1ca6014d41d1283b8a2b06a15b45cd5a6b4ce82e28", + "zh:e45bc994d0c6e1c0a0b70e8378f2f933e924f05c91061ed2a97ceaf282e08a25", + "zh:ee725d6fbc1dbaa5017e9eab6fa0aa7e107a4ed73a4a8e2acab6b5d3d54cd0e4", + ] +} + +provider "registry.opentofu.org/hashicorp/null" { + version = "3.2.4" + hashes = [ + "h1:i+WKhUHL2REY5EGmiHjfUljJB8UKZ9QdhdM5uTeUhC4=", + "zh:1769783386610bed8bb1e861a119fe25058be41895e3996d9216dd6bb8a7aee3", + "zh:32c62a9387ad0b861b5262b41c5e9ed6e940eda729c2a0e58100e6629af27ddb", + "zh:339bf8c2f9733fce068eb6d5612701144c752425cebeafab36563a16be460fb2", + "zh:36731f23343aee12a7e078067a98644c0126714c4fe9ac930eecb0f2361788c4", + "zh:3d106c7e32a929e2843f732625a582e562ff09120021e510a51a6f5d01175b8d", + "zh:74bcb3567708171ad83b234b92c9d63ab441ef882b770b0210c2b14fdbe3b1b6", + "zh:90b55bdbffa35df9204282251059e62c178b0ac7035958b93a647839643c0072", + "zh:ae24c0e5adc692b8f94cb23a000f91a316070fdc19418578dcf2134ff57cf447", + "zh:b5c10d4ad860c4c21273203d1de6d2f0286845edf1c64319fa2362df526b5f58", + "zh:e05bbd88e82e1d6234988c85db62fd66f11502645838fff594a2ec25352ecd80", ] } diff --git a/iac/api_gateway.tf b/iac/api_gateway.tf index 1a83a5d..6276c43 100644 --- a/iac/api_gateway.tf +++ b/iac/api_gateway.tf @@ -16,10 +16,106 @@ resource "aws_api_gateway_deployment" "api_deployment" { } } +# CloudWatch Log Group for API Gateway +resource "aws_cloudwatch_log_group" "api_gateway_logs" { + name = "API-Gateway-Execution-Logs_${aws_api_gateway_rest_api.api.id}/v1" + retention_in_days = 30 + kms_key_id = aws_kms_key.encryption.arn + + tags = { + Name = "${local.project}-${var.environment}-api-gateway-logs" + } +} + +# CloudWatch Log Group for API Gateway Access Logs +resource "aws_cloudwatch_log_group" "api_gateway_access_logs" { + name = "/aws/apigateway/${local.project}-${var.environment}-access-logs" + retention_in_days = 30 + kms_key_id = aws_kms_key.encryption.arn + + tags = { + Name = "${local.project}-${var.environment}-api-gateway-access-logs" + } +} + +# API Gateway Account (required for CloudWatch logging) +resource "aws_api_gateway_account" "api_gateway_account" { + cloudwatch_role_arn = aws_iam_role.api_gateway_cloudwatch.arn +} + +# IAM Role for API Gateway CloudWatch +resource "aws_iam_role" "api_gateway_cloudwatch" { + name = "${local.project}-${var.environment}-api-gateway-cloudwatch-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "apigateway.amazonaws.com" + } + } + ] + }) +} + +# IAM Policy Attachment for API Gateway CloudWatch +resource "aws_iam_role_policy_attachment" "api_gateway_cloudwatch" { + role = aws_iam_role.api_gateway_cloudwatch.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" +} + resource "aws_api_gateway_stage" "stage" { rest_api_id = aws_api_gateway_rest_api.api.id stage_name = "v1" deployment_id = aws_api_gateway_deployment.api_deployment.id + + # Enable X-Ray tracing + xray_tracing_enabled = true + + # Access logging configuration + access_log_settings { + destination_arn = aws_cloudwatch_log_group.api_gateway_access_logs.arn + format = jsonencode({ + requestId = "$context.requestId" + extendedRequestId = "$context.extendedRequestId" + ip = "$context.identity.sourceIp" + caller = "$context.identity.caller" + user = "$context.identity.user" + requestTime = "$context.requestTime" + httpMethod = "$context.httpMethod" + resourcePath = "$context.resourcePath" + status = "$context.status" + protocol = "$context.protocol" + responseLength = "$context.responseLength" + error_message = "$context.error.message" + error_message_string = "$context.error.messageString" + }) + } + + depends_on = [aws_api_gateway_account.api_gateway_account] +} + +# Method Settings for execution logging +resource "aws_api_gateway_method_settings" "all" { + rest_api_id = aws_api_gateway_rest_api.api.id + stage_name = aws_api_gateway_stage.stage.stage_name + method_path = "*/*" + + settings { + # Enable CloudWatch metrics + metrics_enabled = true + + # Enable execution logging + logging_level = "INFO" + data_trace_enabled = true + + # Throttling settings + throttling_rate_limit = 100 + throttling_burst_limit = 200 + } } resource "aws_api_gateway_authorizer" "authorizer" { diff --git a/iac/cloudfront.tf b/iac/cloudfront.tf index 142f21b..cabb120 100644 --- a/iac/cloudfront.tf +++ b/iac/cloudfront.tf @@ -4,24 +4,55 @@ locals { api_prefix_path = "api" } +# 1. Origin Access Control for CloudFront -> S3 +resource "aws_cloudfront_origin_access_control" "oac" { + name = "${local.project}-${var.environment}-oac" + description = "SigV4 control for private S3 website bucket" + origin_access_control_origin_type = "s3" + signing_behavior = "always" + signing_protocol = "sigv4" +} + +# Invalidate CloudFront cache on each deployment +resource "null_resource" "invalidate_cloudfront" { + # This will trigger on every apply + triggers = { + always_run = "${timestamp()}" + } + + # Local-exec provisioner to run AWS CLI command + provisioner "local-exec" { + command = <<EOT + aws cloudfront create-invalidation \ + --distribution-id ${aws_cloudfront_distribution.distribution.id} \ + --paths '/*' \ + --region ${var.region} + EOT + } + + depends_on = [aws_cloudfront_distribution.distribution] +} + resource "aws_cloudfront_distribution" "distribution" { enabled = true comment = "${local.project} ${var.environment} website" - http_version = "http2" - is_ipv6_enabled = true - price_class = "PriceClass_100" + http_version = "http2and3" + is_ipv6_enabled = true + price_class = "PriceClass_100" + default_root_object = "index.html" + + # Access logging configuration + logging_config { + include_cookies = false + bucket = aws_s3_bucket.access_logs.bucket_domain_name + prefix = "cloudfront-access-logs/" + } origin { - domain_name = aws_s3_bucket_website_configuration.website_configuration.website_endpoint + domain_name = aws_s3_bucket.website_storage.bucket_regional_domain_name origin_id = local.s3_origin_id - - custom_origin_config { - http_port = 80 - https_port = 443 - origin_protocol_policy = "http-only" - origin_ssl_protocols = ["TLSv1.2"] - } + origin_access_control_id = aws_cloudfront_origin_access_control.oac.id } origin { @@ -55,6 +86,12 @@ resource "aws_cloudfront_distribution" "distribution" { query_string = false } + + # Add security headers + function_association { + event_type = "viewer-response" + function_arn = aws_cloudfront_function.security_headers.arn + } } ordered_cache_behavior { @@ -100,3 +137,30 @@ function handler(event) { } EOF } + +# CloudFront Function for Security Headers +resource "aws_cloudfront_function" "security_headers" { + name = "${local.project}-${var.environment}-security-headers" + runtime = "cloudfront-js-1.0" + code = <<EOF +function handler(event) { + var response = event.response; + var headers = response.headers; + + // Security headers + headers['strict-transport-security'] = { value: 'max-age=31536000; includeSubDomains; preload' }; + headers['content-type-options'] = { value: 'nosniff' }; + headers['x-frame-options'] = { value: 'DENY' }; + headers['x-content-type-options'] = { value: 'nosniff' }; + headers['referrer-policy'] = { value: 'strict-origin-when-cross-origin' }; + headers['permissions-policy'] = { value: 'camera=(), microphone=(), geolocation=()' }; + + // Content Security Policy + headers['content-security-policy'] = { + value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" + }; + + return response; +} +EOF +} diff --git a/iac/dynamodb.tf b/iac/dynamodb.tf index 8dc624c..95f46b2 100644 --- a/iac/dynamodb.tf +++ b/iac/dynamodb.tf @@ -8,4 +8,109 @@ resource "aws_dynamodb_table" "extract_table" { name = "document_id" type = "S" } + + # Server-side encryption + server_side_encryption { + enabled = true + kms_key_arn = aws_kms_key.encryption.arn + } + + # Point-in-time recovery + point_in_time_recovery { + enabled = true + } + + # Enable deletion protection for production + deletion_protection_enabled = var.environment == "prod" ? true : false + + tags = { + Name = "${local.project}-${var.environment}-text-extract" + Environment = var.environment + } +} + +# DynamoDB Backup Vault +resource "aws_backup_vault" "dynamodb_backup_vault" { + name = "${local.project}-${var.environment}-dynamodb-backup-vault" + kms_key_arn = aws_kms_key.encryption.arn + + tags = { + Name = "${local.project}-${var.environment}-dynamodb-backup-vault" + } +} + +# IAM Role for AWS Backup +resource "aws_iam_role" "backup_role" { + name = "${local.project}-${var.environment}-backup-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "backup.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "backup_policy" { + role = aws_iam_role.backup_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup" +} + +# Backup Plan +resource "aws_backup_plan" "dynamodb_backup_plan" { + name = "${local.project}-${var.environment}-dynamodb-backup-plan" + + rule { + rule_name = "daily_backup" + target_vault_name = aws_backup_vault.dynamodb_backup_vault.name + schedule = "cron(0 5 ? * * *)" # Daily at 5 AM UTC + + lifecycle { + cold_storage_after = 30 + delete_after = 365 # 1 year retention + } + + recovery_point_tags = { + BackupType = "Daily" + } + } + + rule { + rule_name = "monthly_backup" + target_vault_name = aws_backup_vault.dynamodb_backup_vault.name + schedule = "cron(0 5 1 * ? *)" # Monthly on the 1st at 5 AM UTC + + lifecycle { + cold_storage_after = 30 + delete_after = 2555 # ~7 years retention + } + + recovery_point_tags = { + BackupType = "Monthly" + } + } +} + +# Backup Selection +resource "aws_backup_selection" "dynamodb_backup_selection" { + iam_role_arn = aws_iam_role.backup_role.arn + name = "${local.project}-${var.environment}-dynamodb-backup-selection" + plan_id = aws_backup_plan.dynamodb_backup_plan.id + + resources = [ + aws_dynamodb_table.extract_table.arn + ] + + condition { + string_equals { + key = "aws:ResourceTag/Environment" + value = var.environment + } + } } diff --git a/iac/iam.tf b/iac/iam.tf index 523ee3d..83f460e 100644 --- a/iac/iam.tf +++ b/iac/iam.tf @@ -88,9 +88,18 @@ resource "aws_iam_role_policy_attachment" "attach_kms_permission_to_role" { data "aws_iam_policy_document" "sqs_lambda_policy" { statement { - effect = "Allow" - actions = ["sqs:*"] - resources = [aws_sqs_queue.queue_to_dynamo.arn] + effect = "Allow" + actions = [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + resources = [ + aws_sqs_queue.queue_to_dynamo.arn, + aws_sqs_queue.queue_to_dynamo_dlq.arn + ] } } @@ -130,3 +139,39 @@ resource "aws_iam_role_policy_attachment" "attach_textract_permission_to_role" { role = aws_iam_role.execution_role.name policy_arn = data.aws_iam_policy.lambda_textract_execution.arn } + +# VPC Execution Policy for Lambda +data "aws_iam_policy" "lambda_vpc_execution" { + name = "AWSLambdaVPCAccessExecutionRole" +} + +resource "aws_iam_role_policy_attachment" "lambda_vpc_execution" { + role = aws_iam_role.execution_role.name + policy_arn = data.aws_iam_policy.lambda_vpc_execution.arn +} + +# DLQ Policy for Lambda Functions +data "aws_iam_policy_document" "dlq_lambda_policy" { + statement { + effect = "Allow" + actions = [ + "sqs:SendMessage", + "sqs:GetQueueAttributes" + ] + resources = [ + aws_sqs_queue.text_extract_dlq.arn, + aws_sqs_queue.write_to_dynamodb_dlq.arn, + aws_sqs_queue.authorizer_dlq.arn + ] + } +} + +resource "aws_iam_policy" "dlq_lambda_policy" { + name = "${local.project}-${var.environment}-dlq-lambda-policy" + policy = data.aws_iam_policy_document.dlq_lambda_policy.json +} + +resource "aws_iam_role_policy_attachment" "attach_dlq_permission_to_role" { + role = aws_iam_role.execution_role.name + policy_arn = aws_iam_policy.dlq_lambda_policy.arn +} diff --git a/iac/kms.tf b/iac/kms.tf index 06b21b4..dd62932 100644 --- a/iac/kms.tf +++ b/iac/kms.tf @@ -1,5 +1,88 @@ resource "aws_kms_key" "encryption" { - description = "Data encryption for ${local.project}" + description = "Data encryption for ${local.project}-${var.environment}" enable_key_rotation = true deletion_window_in_days = 7 + + policy = data.aws_iam_policy_document.kms_key_policy.json + + tags = { + Name = "${local.project}-${var.environment}-encryption-key" + Environment = var.environment + } +} + +# KMS Key Alias +resource "aws_kms_alias" "encryption" { + name = "alias/${local.project}-${var.environment}-encryption-key" + target_key_id = aws_kms_key.encryption.key_id +} + +# KMS Key Policy +data "aws_iam_policy_document" "kms_key_policy" { + # Enable IAM User Permissions + statement { + sid = "Enable IAM User Permissions" + effect = "Allow" + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + } + + actions = ["kms:*"] + resources = ["*"] + } + + # Allow key usage for AWS services + statement { + sid = "Allow use of the key for AWS services" + effect = "Allow" + + principals { + type = "Service" + identifiers = [ + "s3.amazonaws.com", + "lambda.amazonaws.com", + "dynamodb.amazonaws.com", + "logs.amazonaws.com", + "sqs.amazonaws.com", + "secretsmanager.amazonaws.com", + "backup.amazonaws.com" + ] + } + + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + "kms:CreateGrant", + "kms:ListGrants", + "kms:RevokeGrant" + ] + + resources = ["*"] + } + + # Allow Lambda execution role access + statement { + sid = "Allow Lambda execution role access" + effect = "Allow" + + principals { + type = "AWS" + identifiers = [aws_iam_role.execution_role.arn] + } + + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + + resources = ["*"] + } } diff --git a/iac/lambda.tf b/iac/lambda.tf index 5afdfc3..674d0df 100644 --- a/iac/lambda.tf +++ b/iac/lambda.tf @@ -17,7 +17,7 @@ resource "aws_lambda_function" "text_extract" { memory_size = 256 timeout = 30 runtime = "python3.13" - reserved_concurrent_executions = -1 + reserved_concurrent_executions = 10 # Set reasonable limit instead of unlimited publish = true architectures = ["arm64"] @@ -26,9 +26,22 @@ resource "aws_lambda_function" "text_extract" { role = aws_iam_role.execution_role.arn + vpc_config { + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.lambda.id] + } + + dead_letter_config { + target_arn = aws_sqs_queue.text_extract_dlq.arn + } + environment { variables = local.textract_environment_variables } + + depends_on = [ + aws_iam_role_policy_attachment.lambda_vpc_execution + ] } resource "aws_lambda_permission" "allow_bucket_invoke" { @@ -56,7 +69,7 @@ resource "aws_lambda_function" "write_to_dynamodb" { memory_size = 256 timeout = 30 runtime = "python3.13" - reserved_concurrent_executions = -1 + reserved_concurrent_executions = 10 # Set reasonable limit instead of unlimited publish = true architectures = ["arm64"] @@ -65,12 +78,25 @@ resource "aws_lambda_function" "write_to_dynamodb" { role = aws_iam_role.execution_role.arn + vpc_config { + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.lambda.id] + } + + dead_letter_config { + target_arn = aws_sqs_queue.write_to_dynamodb_dlq.arn + } + environment { variables = { SQS_QUEUE_URL = aws_sqs_queue.queue_to_dynamo.url DYNAMODB_TABLE = aws_dynamodb_table.extract_table.name } } + + depends_on = [ + aws_iam_role_policy_attachment.lambda_vpc_execution + ] } resource "aws_lambda_event_source_mapping" "invoke_dynamodb_writer_from_sqs" { @@ -98,7 +124,7 @@ resource "aws_lambda_function" "authorizer" { memory_size = 256 timeout = 30 runtime = "python3.13" - reserved_concurrent_executions = -1 + reserved_concurrent_executions = 10 # Set reasonable limit instead of unlimited publish = true architectures = ["arm64"] @@ -107,11 +133,24 @@ resource "aws_lambda_function" "authorizer" { role = aws_iam_role.execution_role.arn + vpc_config { + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.lambda.id] + } + + dead_letter_config { + target_arn = aws_sqs_queue.authorizer_dlq.arn + } + environment { variables = { ENVIRONMENT = var.environment } } + + depends_on = [ + aws_iam_role_policy_attachment.lambda_vpc_execution + ] } resource "aws_lambda_permission" "api_gateway_invoke_authorizer" { @@ -128,3 +167,65 @@ resource "aws_lambda_provisioned_concurrency_config" "authorizer_concurrency" { provisioned_concurrent_executions = 1 qualifier = aws_lambda_function.authorizer.version } + +# CloudWatch Log Groups for Lambda Functions +resource "aws_cloudwatch_log_group" "lambda_text_extract" { + name = "/aws/lambda/${local.project}-${var.environment}-text-extract" + retention_in_days = 30 + kms_key_id = aws_kms_key.encryption.arn + + tags = { + Name = "${local.project}-${var.environment}-text-extract-logs" + } +} + +resource "aws_cloudwatch_log_group" "lambda_write_to_dynamodb" { + name = "/aws/lambda/${local.project}-${var.environment}-write-to-dynamodb" + retention_in_days = 30 + kms_key_id = aws_kms_key.encryption.arn + + tags = { + Name = "${local.project}-${var.environment}-write-to-dynamodb-logs" + } +} + +resource "aws_cloudwatch_log_group" "lambda_authorizer" { + name = "/aws/lambda/${local.project}-${var.environment}-authorizer" + retention_in_days = 30 + kms_key_id = aws_kms_key.encryption.arn + + tags = { + Name = "${local.project}-${var.environment}-authorizer-logs" + } +} + +# Dead Letter Queues for Lambda Functions +resource "aws_sqs_queue" "text_extract_dlq" { + name = "${local.project}-${var.environment}-text-extract-dlq" + kms_master_key_id = aws_kms_key.encryption.arn + message_retention_seconds = 1209600 # 14 days + + tags = { + Name = "${local.project}-${var.environment}-text-extract-dlq" + } +} + +resource "aws_sqs_queue" "write_to_dynamodb_dlq" { + name = "${local.project}-${var.environment}-write-to-dynamodb-dlq" + kms_master_key_id = aws_kms_key.encryption.arn + message_retention_seconds = 1209600 # 14 days + + tags = { + Name = "${local.project}-${var.environment}-write-to-dynamodb-dlq" + } +} + +resource "aws_sqs_queue" "authorizer_dlq" { + name = "${local.project}-${var.environment}-authorizer-dlq" + kms_master_key_id = aws_kms_key.encryption.arn + message_retention_seconds = 1209600 # 14 days + + tags = { + Name = "${local.project}-${var.environment}-authorizer-dlq" + } +} diff --git a/iac/main.tf b/iac/main.tf index 5274351..545233c 100644 --- a/iac/main.tf +++ b/iac/main.tf @@ -2,13 +2,14 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "5.97.0" + version = ">= 5.0.0" } } } provider "aws" { - region = var.region + region = var.region + profile = "AWSAdministratorAccess-328307993388" default_tags { tags = { @@ -19,8 +20,12 @@ provider "aws" { terraform { backend "s3" { - region = "us-east-1" - use_lockfile = true + bucket = "document-extractor-dev-opentofu-state" + key = "document-extractor/terraform.tfstate" + region = "us-east-1" + dynamodb_table = "terraform-locks-dev" + encrypt = true + profile = "AWSAdministratorAccess-328307993388" } } diff --git a/iac/monitoring.tf b/iac/monitoring.tf new file mode 100644 index 0000000..9e2b13d --- /dev/null +++ b/iac/monitoring.tf @@ -0,0 +1,250 @@ +# AWS Security Hub +resource "aws_securityhub_account" "main" { + enable_default_standards = true +} + +# Enable Security Hub Standards +resource "aws_securityhub_standards_subscription" "aws_foundational" { + standards_arn = "arn:aws:securityhub:::ruleset/finding-format/aws-foundational-security-standard/v/1.0.0" + depends_on = [aws_securityhub_account.main] +} + +resource "aws_securityhub_standards_subscription" "cis" { + standards_arn = "arn:aws:securityhub:::ruleset/finding-format/cis-aws-foundations-benchmark/v/1.2.0" + depends_on = [aws_securityhub_account.main] +} + +# CloudWatch Alarms for Cost Monitoring +resource "aws_cloudwatch_metric_alarm" "billing_alarm" { + count = var.environment == "prod" ? 1 : 0 + + alarm_name = "${local.project}-${var.environment}-billing-alarm" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = "2" + metric_name = "EstimatedCharges" + namespace = "AWS/Billing" + period = "86400" # 24 hours + statistic = "Maximum" + threshold = "100" # $100 threshold + alarm_description = "This metric monitors aws billing" + alarm_actions = [aws_sns_topic.alerts[0].arn] + + dimensions = { + Currency = "USD" + } + + tags = { + Name = "${local.project}-${var.environment}-billing-alarm" + } +} + +# SNS Topic for Alerts +resource "aws_sns_topic" "alerts" { + count = var.environment == "prod" ? 1 : 0 + + name = "${local.project}-${var.environment}-alerts" + kms_master_key_id = aws_kms_key.encryption.arn + + tags = { + Name = "${local.project}-${var.environment}-alerts" + } +} + +# CloudWatch Alarms for Lambda Functions +resource "aws_cloudwatch_metric_alarm" "lambda_errors" { + for_each = { + text_extract = aws_lambda_function.text_extract.function_name + write_to_dynamodb = aws_lambda_function.write_to_dynamodb.function_name + authorizer = aws_lambda_function.authorizer.function_name + } + + alarm_name = "${each.key}-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = "2" + metric_name = "Errors" + namespace = "AWS/Lambda" + period = "300" + statistic = "Sum" + threshold = "5" + alarm_description = "This metric monitors lambda errors for ${each.key}" + + dimensions = { + FunctionName = each.value + } + + tags = { + Name = "${each.key}-errors-alarm" + } +} + +resource "aws_cloudwatch_metric_alarm" "lambda_duration" { + for_each = { + text_extract = aws_lambda_function.text_extract.function_name + write_to_dynamodb = aws_lambda_function.write_to_dynamodb.function_name + authorizer = aws_lambda_function.authorizer.function_name + } + + alarm_name = "${each.key}-duration" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = "2" + metric_name = "Duration" + namespace = "AWS/Lambda" + period = "300" + statistic = "Average" + threshold = "25000" # 25 seconds (near timeout) + alarm_description = "This metric monitors lambda duration for ${each.key}" + + dimensions = { + FunctionName = each.value + } + + tags = { + Name = "${each.key}-duration-alarm" + } +} + +# CloudWatch Alarms for API Gateway +resource "aws_cloudwatch_metric_alarm" "api_gateway_4xx_errors" { + alarm_name = "${local.project}-${var.environment}-api-4xx-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = "2" + metric_name = "4XXError" + namespace = "AWS/ApiGateway" + period = "300" + statistic = "Sum" + threshold = "10" + alarm_description = "This metric monitors API Gateway 4XX errors" + + dimensions = { + ApiName = aws_api_gateway_rest_api.api.name + Stage = aws_api_gateway_stage.stage.stage_name + } + + tags = { + Name = "${local.project}-${var.environment}-api-4xx-errors" + } +} + +resource "aws_cloudwatch_metric_alarm" "api_gateway_5xx_errors" { + alarm_name = "${local.project}-${var.environment}-api-5xx-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = "2" + metric_name = "5XXError" + namespace = "AWS/ApiGateway" + period = "300" + statistic = "Sum" + threshold = "5" + alarm_description = "This metric monitors API Gateway 5XX errors" + + dimensions = { + ApiName = aws_api_gateway_rest_api.api.name + Stage = aws_api_gateway_stage.stage.stage_name + } + + tags = { + Name = "${local.project}-${var.environment}-api-5xx-errors" + } +} + +# CloudWatch Alarms for DynamoDB +resource "aws_cloudwatch_metric_alarm" "dynamodb_throttled_requests" { + alarm_name = "${local.project}-${var.environment}-dynamodb-throttled-requests" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = "2" + metric_name = "ThrottledRequests" + namespace = "AWS/DynamoDB" + period = "300" + statistic = "Sum" + threshold = "0" + alarm_description = "This metric monitors DynamoDB throttled requests" + + dimensions = { + TableName = aws_dynamodb_table.extract_table.name + } + + tags = { + Name = "${local.project}-${var.environment}-dynamodb-throttled-requests" + } +} + +# CloudWatch Dashboard +resource "aws_cloudwatch_dashboard" "main" { + dashboard_name = "${local.project}-${var.environment}-dashboard" + + dashboard_body = jsonencode({ + widgets = [ + { + type = "metric" + x = 0 + y = 0 + width = 12 + height = 6 + + properties = { + metrics = [ + ["AWS/Lambda", "Duration", "FunctionName", aws_lambda_function.text_extract.function_name], + [".", "Errors", ".", "."], + [".", "Invocations", ".", "."] + ] + view = "timeSeries" + stacked = false + region = var.region + title = "Lambda Metrics - Text Extract" + period = 300 + } + }, + { + type = "metric" + x = 0 + y = 6 + width = 12 + height = 6 + + properties = { + metrics = [ + ["AWS/ApiGateway", "Count", "ApiName", aws_api_gateway_rest_api.api.name], + [".", "4XXError", ".", "."], + [".", "5XXError", ".", "."], + [".", "Latency", ".", "."] + ] + view = "timeSeries" + stacked = false + region = var.region + title = "API Gateway Metrics" + period = 300 + } + }, + { + type = "metric" + x = 0 + y = 12 + width = 12 + height = 6 + + properties = { + metrics = [ + ["AWS/DynamoDB", "ConsumedReadCapacityUnits", "TableName", aws_dynamodb_table.extract_table.name], + [".", "ConsumedWriteCapacityUnits", ".", "."], + [".", "ThrottledRequests", ".", "."] + ] + view = "timeSeries" + stacked = false + region = var.region + title = "DynamoDB Metrics" + period = 300 + } + } + ] + }) +} + +# Cost Anomaly Detection +# Note: AWS Cost Explorer anomaly detection resources are not yet available in Terraform/OpenTofu +# Configure these manually in the AWS Console: +# 1. Go to AWS Cost Management > Cost Anomaly Detection +# 2. Create anomaly detector for services: S3, Lambda, DynamoDB, API Gateway +# 3. Set up email notifications with $100 threshold +# 4. Enable daily frequency for alerts + +# Future: Replace with Terraform resources when available +# Tracking: https://github.com/hashicorp/terraform-provider-aws/issues/17200 diff --git a/iac/outputs.tf b/iac/outputs.tf index 89fb7ec..e8e90d1 100644 --- a/iac/outputs.tf +++ b/iac/outputs.tf @@ -1,3 +1,57 @@ output "distribution_id" { value = aws_cloudfront_distribution.distribution.id } + +output "cloudfront_distribution_domain_name" { + value = aws_cloudfront_distribution.distribution.domain_name +} + +# VPC Outputs +output "vpc_id" { + description = "ID of the VPC" + value = aws_vpc.main.id +} + +output "private_subnet_ids" { + description = "IDs of the private subnets" + value = aws_subnet.private[*].id +} + +output "public_subnet_ids" { + description = "IDs of the public subnets" + value = aws_subnet.public[*].id +} + +# Security Outputs +output "kms_key_id" { + description = "ID of the KMS encryption key" + value = aws_kms_key.encryption.key_id +} + +output "kms_key_alias" { + description = "Alias of the KMS encryption key" + value = aws_kms_alias.encryption.name +} + +# Monitoring Outputs +output "cloudwatch_dashboard_url" { + description = "URL of the CloudWatch dashboard" + value = "https://${var.region}.console.aws.amazon.com/cloudwatch/home?region=${var.region}#dashboards:name=${aws_cloudwatch_dashboard.main.dashboard_name}" +} + +# S3 Outputs +output "document_storage_bucket" { + description = "Name of the document storage bucket" + value = aws_s3_bucket.document_storage.bucket +} + +output "access_logs_bucket" { + description = "Name of the access logs bucket" + value = aws_s3_bucket.access_logs.bucket +} + +# API Gateway Output +output "api_gateway_url" { + description = "URL of the API Gateway" + value = aws_api_gateway_stage.stage.invoke_url +} diff --git a/iac/s3.tf b/iac/s3.tf index b132c20..c7dd8d0 100644 --- a/iac/s3.tf +++ b/iac/s3.tf @@ -2,6 +2,85 @@ resource "aws_s3_bucket" "document_storage" { bucket = "${local.project}-${var.environment}-documents-${data.aws_caller_identity.current.account_id}" force_destroy = false + + # Object Lock must be enabled at bucket creation + object_lock_enabled = true +} + +# Server-side encryption configuration +resource "aws_s3_bucket_server_side_encryption_configuration" "document_storage" { + bucket = aws_s3_bucket.document_storage.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.encryption.arn + sse_algorithm = "aws:kms" + } + bucket_key_enabled = true + } +} + +# Versioning configuration +resource "aws_s3_bucket_versioning" "document_storage" { + bucket = aws_s3_bucket.document_storage.id + versioning_configuration { + status = "Enabled" + } +} + +# Object Lock configuration +resource "aws_s3_bucket_object_lock_configuration" "document_storage" { + bucket = aws_s3_bucket.document_storage.id + + rule { + default_retention { + mode = "GOVERNANCE" + days = 90 + } + } + + depends_on = [aws_s3_bucket_versioning.document_storage] +} + +# Public access block +resource "aws_s3_bucket_public_access_block" "document_storage" { + bucket = aws_s3_bucket.document_storage.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +# SSL enforcement policy +resource "aws_s3_bucket_policy" "document_storage_ssl" { + bucket = aws_s3_bucket.document_storage.id + policy = data.aws_iam_policy_document.document_storage_ssl.json +} + +data "aws_iam_policy_document" "document_storage_ssl" { + statement { + sid = "DenyInsecureConnections" + effect = "Deny" + + principals { + type = "*" + identifiers = ["*"] + } + + actions = ["s3:*"] + + resources = [ + aws_s3_bucket.document_storage.arn, + "${aws_s3_bucket.document_storage.arn}/*" + ] + + condition { + test = "Bool" + variable = "aws:SecureTransport" + values = ["false"] + } + } } resource "aws_s3_bucket_notification" "notify_on_input_data" { @@ -33,12 +112,86 @@ resource "aws_s3_bucket_lifecycle_configuration" "document_storage_lifecycles" { } } +# S3 Access Logging Bucket +resource "aws_s3_bucket" "access_logs" { + bucket = "${local.project}-${var.environment}-access-logs-${data.aws_caller_identity.current.account_id}" +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "access_logs" { + bucket = aws_s3_bucket.access_logs.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.encryption.arn + sse_algorithm = "aws:kms" + } + bucket_key_enabled = true + } +} + +resource "aws_s3_bucket_versioning" "access_logs" { + bucket = aws_s3_bucket.access_logs.id + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_public_access_block" "access_logs" { + bucket = aws_s3_bucket.access_logs.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_lifecycle_configuration" "access_logs_lifecycle" { + bucket = aws_s3_bucket.access_logs.id + + rule { + id = "delete-access-logs" + status = "Enabled" + + filter { + prefix = "" + } + + expiration { + days = 90 + } + + noncurrent_version_expiration { + noncurrent_days = 30 + } + } +} + +# Website Storage Bucket resource "aws_s3_bucket" "website_storage" { bucket = "${local.project}-${var.environment}-website-${data.aws_caller_identity.current.account_id}" - force_destroy = true } +# Server-side encryption for website storage +resource "aws_s3_bucket_server_side_encryption_configuration" "website_storage" { + bucket = aws_s3_bucket.website_storage.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.encryption.arn + sse_algorithm = "aws:kms" + } + bucket_key_enabled = true + } +} + +resource "aws_s3_bucket_versioning" "website_storage_versioning" { + bucket = aws_s3_bucket.website_storage.id + versioning_configuration { + status = "Enabled" + } +} + resource "aws_s3_bucket_website_configuration" "website_configuration" { bucket = aws_s3_bucket.website_storage.id @@ -73,37 +226,78 @@ resource "aws_s3_object" "website_files" { } } -resource "aws_s3_bucket_public_access_block" "allow_website_public" { +# Updated Public Access Block: keep public blocked except allow CloudFront +resource "aws_s3_bucket_public_access_block" "private_website" { bucket = aws_s3_bucket.website_storage.id - block_public_acls = true - block_public_policy = false - ignore_public_acls = true - restrict_public_buckets = false + block_public_acls = true + ignore_public_acls = true + block_public_policy = true # stays ON + restrict_public_buckets = false # must be false to allow service principal } -resource "aws_s3_bucket_policy" "attach_cloudfront_read" { - bucket = aws_s3_bucket.website_storage.bucket - policy = data.aws_iam_policy_document.cloudfront_read.json +resource "aws_s3_bucket_policy" "website_read" { + bucket = aws_s3_bucket.website_storage.id + policy = data.aws_iam_policy_document.cf_read.json +} - depends_on = [aws_s3_bucket_public_access_block.allow_website_public] +# S3 Access Logging Configuration +resource "aws_s3_bucket_logging" "document_storage_logging" { + bucket = aws_s3_bucket.document_storage.id + + target_bucket = aws_s3_bucket.access_logs.id + target_prefix = "document-storage-access-logs/" } -data "aws_iam_policy_document" "cloudfront_read" { +resource "aws_s3_bucket_logging" "website_storage_logging" { + bucket = aws_s3_bucket.website_storage.id + + target_bucket = aws_s3_bucket.access_logs.id + target_prefix = "website-storage-access-logs/" +} + +data "aws_iam_policy_document" "cf_read" { + # Allow CloudFront access + statement { + sid = "AllowCloudFront" + effect = "Allow" + + principals { + type = "Service" + identifiers = ["cloudfront.amazonaws.com"] + } + + actions = ["s3:GetObject"] + resources = ["${aws_s3_bucket.website_storage.arn}/*"] + + condition { + test = "StringEquals" + variable = "AWS:SourceArn" + values = [aws_cloudfront_distribution.distribution.arn] + } + } + + # Deny insecure connections statement { - sid = "CloudFrontReadGetObject" - effect = "Allow" + sid = "DenyInsecureConnections" + effect = "Deny" + principals { + type = "*" identifiers = ["*"] - type = "AWS" } - actions = [ - "s3:GetObject", - "s3:ListBucket" - ] + + actions = ["s3:*"] + resources = [ - "${aws_s3_bucket.website_storage.arn}/*", - aws_s3_bucket.website_storage.arn + aws_s3_bucket.website_storage.arn, + "${aws_s3_bucket.website_storage.arn}/*" ] + + condition { + test = "Bool" + variable = "aws:SecureTransport" + values = ["false"] + } } } diff --git a/iac/secrets.tf b/iac/secrets.tf index 3b37f0b..4c76efe 100644 --- a/iac/secrets.tf +++ b/iac/secrets.tf @@ -1,15 +1,116 @@ resource "aws_secretsmanager_secret" "private_key" { - name = "${local.project}-${var.environment}-private-key" + name = "${local.project}-${var.environment}-private-key" + description = "Private key for ${local.project} ${var.environment}" + kms_key_id = aws_kms_key.encryption.arn + recovery_window_in_days = var.environment == "prod" ? 30 : 7 + + tags = { + Name = "${local.project}-${var.environment}-private-key" + Environment = var.environment + Type = "PrivateKey" + } +} + +resource "aws_secretsmanager_secret_policy" "private_key" { + secret_arn = aws_secretsmanager_secret.private_key.arn + policy = data.aws_iam_policy_document.secrets_policy.json } resource "aws_secretsmanager_secret" "public_key" { - name = "${local.project}-${var.environment}-public-key" + name = "${local.project}-${var.environment}-public-key" + description = "Public key for ${local.project} ${var.environment}" + kms_key_id = aws_kms_key.encryption.arn + recovery_window_in_days = var.environment == "prod" ? 30 : 7 + + tags = { + Name = "${local.project}-${var.environment}-public-key" + Environment = var.environment + Type = "PublicKey" + } +} + +resource "aws_secretsmanager_secret_policy" "public_key" { + secret_arn = aws_secretsmanager_secret.public_key.arn + policy = data.aws_iam_policy_document.secrets_policy.json } resource "aws_secretsmanager_secret" "username" { - name = "${local.project}-${var.environment}-username" + name = "${local.project}-${var.environment}-username" + description = "Username for ${local.project} ${var.environment}" + kms_key_id = aws_kms_key.encryption.arn + recovery_window_in_days = var.environment == "prod" ? 30 : 7 + + tags = { + Name = "${local.project}-${var.environment}-username" + Environment = var.environment + Type = "Credential" + } +} + +resource "aws_secretsmanager_secret_policy" "username" { + secret_arn = aws_secretsmanager_secret.username.arn + policy = data.aws_iam_policy_document.secrets_policy.json } resource "aws_secretsmanager_secret" "password" { - name = "${local.project}-${var.environment}-password" + name = "${local.project}-${var.environment}-password" + description = "Password for ${local.project} ${var.environment}" + kms_key_id = aws_kms_key.encryption.arn + recovery_window_in_days = var.environment == "prod" ? 30 : 7 + + tags = { + Name = "${local.project}-${var.environment}-password" + Environment = var.environment + Type = "Credential" + } +} + +resource "aws_secretsmanager_secret_policy" "password" { + secret_arn = aws_secretsmanager_secret.password.arn + policy = data.aws_iam_policy_document.secrets_policy.json +} + +# Secrets Manager Resource Policy +data "aws_iam_policy_document" "secrets_policy" { + statement { + sid = "AllowLambdaAccess" + effect = "Allow" + + principals { + type = "AWS" + identifiers = [aws_iam_role.execution_role.arn] + } + + actions = [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ] + + resources = ["*"] + + condition { + test = "StringEquals" + variable = "aws:SecureTransport" + values = ["true"] + } + } + + statement { + sid = "DenyInsecureConnections" + effect = "Deny" + + principals { + type = "*" + identifiers = ["*"] + } + + actions = ["secretsmanager:*"] + resources = ["*"] + + condition { + test = "Bool" + variable = "aws:SecureTransport" + values = ["false"] + } + } } diff --git a/iac/sqs.tf b/iac/sqs.tf index 7b5c808..8195321 100644 --- a/iac/sqs.tf +++ b/iac/sqs.tf @@ -1,5 +1,27 @@ +# Main SQS Queue DLQ +resource "aws_sqs_queue" "queue_to_dynamo_dlq" { + name = "${local.project}-${var.environment}-to-dynamodb-dlq" + kms_master_key_id = aws_kms_key.encryption.id + message_retention_seconds = 1209600 # 14 days + + tags = { + Name = "${local.project}-${var.environment}-to-dynamodb-dlq" + } +} + +# Main SQS Queue resource "aws_sqs_queue" "queue_to_dynamo" { - name = "${local.project}-${var.environment}-to-dynamodb" + name = "${local.project}-${var.environment}-to-dynamodb" + kms_master_key_id = aws_kms_key.encryption.id + visibility_timeout_seconds = 60 # 2x Lambda timeout + message_retention_seconds = 345600 # 4 days + + redrive_policy = jsonencode({ + deadLetterTargetArn = aws_sqs_queue.queue_to_dynamo_dlq.arn + maxReceiveCount = 3 + }) - kms_master_key_id = aws_kms_key.encryption.id + tags = { + Name = "${local.project}-${var.environment}-to-dynamodb" + } } diff --git a/iac/terraform.tfvars b/iac/terraform.tfvars new file mode 100644 index 0000000..5b1313b --- /dev/null +++ b/iac/terraform.tfvars @@ -0,0 +1,9 @@ +# Required: Choose a deployment environment name +environment = "dev" # Use a simple name like "dev", "test", or "prod" + +# Optional: Change AWS region if needed (defaults to "us-east-1" if not specified) +region = "us-east-1" + +# Advanced: Only needed if using custom form adapter configurations +# Uncomment and modify if you need custom form adapter configurations +textract_form_adapters_env_var_mapping = {} diff --git a/iac/vpc.tf b/iac/vpc.tf new file mode 100644 index 0000000..da38d49 --- /dev/null +++ b/iac/vpc.tf @@ -0,0 +1,370 @@ +# Data sources for AZs and region +data "aws_availability_zones" "available" { + state = "available" +} + +data "aws_region" "current" {} + +# VPC +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "${local.project}-${var.environment}-vpc" + } +} + +# Internet Gateway +resource "aws_internet_gateway" "main" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "${local.project}-${var.environment}-igw" + } +} + +# Public Subnets (3 AZs) +resource "aws_subnet" "public" { + count = 3 + + vpc_id = aws_vpc.main.id + cidr_block = "10.0.${count.index + 1}.0/24" + availability_zone = data.aws_availability_zones.available.names[count.index] + map_public_ip_on_launch = true + + tags = { + Name = "${local.project}-${var.environment}-public-subnet-${count.index + 1}" + Type = "Public" + } +} + +# Private Subnets (3 AZs) +resource "aws_subnet" "private" { + count = 3 + + vpc_id = aws_vpc.main.id + cidr_block = "10.0.${count.index + 10}.0/24" + availability_zone = data.aws_availability_zones.available.names[count.index] + + tags = { + Name = "${local.project}-${var.environment}-private-subnet-${count.index + 1}" + Type = "Private" + } +} + +# Elastic IPs for NAT Gateways +resource "aws_eip" "nat" { + count = var.environment == "prod" ? 3 : 1 + + domain = "vpc" + tags = { + Name = "${local.project}-${var.environment}-nat-eip-${count.index + 1}" + } + + depends_on = [aws_internet_gateway.main] +} + +# NAT Gateways +resource "aws_nat_gateway" "main" { + count = var.environment == "prod" ? 3 : 1 + + allocation_id = aws_eip.nat[count.index].id + subnet_id = aws_subnet.public[count.index].id + + tags = { + Name = "${local.project}-${var.environment}-nat-gateway-${count.index + 1}" + } + + depends_on = [aws_internet_gateway.main] +} + +# Route Tables - Public +resource "aws_route_table" "public" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id + } + + tags = { + Name = "${local.project}-${var.environment}-public-rt" + } +} + +# Route Tables - Private +resource "aws_route_table" "private" { + count = var.environment == "prod" ? 3 : 1 + + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.main[count.index].id + } + + tags = { + Name = "${local.project}-${var.environment}-private-rt-${count.index + 1}" + } +} + +# Route Table Associations - Public +resource "aws_route_table_association" "public" { + count = 3 + + subnet_id = aws_subnet.public[count.index].id + route_table_id = aws_route_table.public.id +} + +# Route Table Associations - Private +resource "aws_route_table_association" "private" { + count = 3 + + subnet_id = aws_subnet.private[count.index].id + route_table_id = aws_route_table.private[var.environment == "prod" ? count.index : 0].id +} + +# VPC Flow Logs +resource "aws_flow_log" "vpc_flow_log" { + iam_role_arn = aws_iam_role.flow_log.arn + log_destination = aws_cloudwatch_log_group.vpc_flow_log.arn + traffic_type = "ALL" + vpc_id = aws_vpc.main.id +} + +# CloudWatch Log Group for VPC Flow Logs +resource "aws_cloudwatch_log_group" "vpc_flow_log" { + name = "/aws/vpc/flowlogs" + retention_in_days = 30 + kms_key_id = aws_kms_key.encryption.arn + + tags = { + Name = "${local.project}-${var.environment}-vpc-flow-logs" + } +} + +# IAM Role for VPC Flow Logs +resource "aws_iam_role" "flow_log" { + name = "${local.project}-${var.environment}-flow-log-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "vpc-flow-logs.amazonaws.com" + } + } + ] + }) +} + +# IAM Policy for VPC Flow Logs +resource "aws_iam_role_policy" "flow_log" { + name = "${local.project}-${var.environment}-flow-log-policy" + role = aws_iam_role.flow_log.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams" + ] + Effect = "Allow" + Resource = "*" + } + ] + }) +} + +# Security Groups + +# Default Security Group - Deny All +resource "aws_default_security_group" "default" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "${local.project}-${var.environment}-default-sg-deny-all" + } +} + +# Lambda Security Group +resource "aws_security_group" "lambda" { + name_prefix = "${local.project}-${var.environment}-lambda-" + vpc_id = aws_vpc.main.id + + egress { + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + description = "HTTPS to AWS services" + } + + egress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + description = "HTTP for package downloads" + } + + tags = { + Name = "${local.project}-${var.environment}-lambda-sg" + } +} + +# VPC Endpoints Security Group +resource "aws_security_group" "vpc_endpoints" { + name_prefix = "${local.project}-${var.environment}-vpc-endpoints-" + vpc_id = aws_vpc.main.id + + ingress { + from_port = 443 + to_port = 443 + protocol = "tcp" + security_groups = [aws_security_group.lambda.id] + description = "HTTPS from Lambda" + } + + tags = { + Name = "${local.project}-${var.environment}-vpc-endpoints-sg" + } +} + +# VPC Endpoints +resource "aws_vpc_endpoint" "s3" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.s3" + vpc_endpoint_type = "Gateway" + route_table_ids = concat([aws_route_table.public.id], aws_route_table.private[*].id) + + tags = { + Name = "${local.project}-${var.environment}-s3-endpoint" + } +} + +resource "aws_vpc_endpoint" "dynamodb" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.dynamodb" + vpc_endpoint_type = "Gateway" + route_table_ids = concat([aws_route_table.public.id], aws_route_table.private[*].id) + + tags = { + Name = "${local.project}-${var.environment}-dynamodb-endpoint" + } +} + +# Interface VPC Endpoints +resource "aws_vpc_endpoint" "lambda" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.lambda" + vpc_endpoint_type = "Interface" + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.vpc_endpoints.id] + private_dns_enabled = true + + tags = { + Name = "${local.project}-${var.environment}-lambda-endpoint" + } +} + +resource "aws_vpc_endpoint" "sqs" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.sqs" + vpc_endpoint_type = "Interface" + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.vpc_endpoints.id] + private_dns_enabled = true + + tags = { + Name = "${local.project}-${var.environment}-sqs-endpoint" + } +} + +resource "aws_vpc_endpoint" "ssm" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ssm" + vpc_endpoint_type = "Interface" + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.vpc_endpoints.id] + private_dns_enabled = true + + tags = { + Name = "${local.project}-${var.environment}-ssm-endpoint" + } +} + +resource "aws_vpc_endpoint" "ssmmessages" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ssmmessages" + vpc_endpoint_type = "Interface" + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.vpc_endpoints.id] + private_dns_enabled = true + + tags = { + Name = "${local.project}-${var.environment}-ssmmessages-endpoint" + } +} + +resource "aws_vpc_endpoint" "ec2messages" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ec2messages" + vpc_endpoint_type = "Interface" + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.vpc_endpoints.id] + private_dns_enabled = true + + tags = { + Name = "${local.project}-${var.environment}-ec2messages-endpoint" + } +} + +resource "aws_vpc_endpoint" "kms" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.kms" + vpc_endpoint_type = "Interface" + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.vpc_endpoints.id] + private_dns_enabled = true + + tags = { + Name = "${local.project}-${var.environment}-kms-endpoint" + } +} + +resource "aws_vpc_endpoint" "secretsmanager" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.secretsmanager" + vpc_endpoint_type = "Interface" + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.vpc_endpoints.id] + private_dns_enabled = true + + tags = { + Name = "${local.project}-${var.environment}-secretsmanager-endpoint" + } +} + +resource "aws_vpc_endpoint" "logs" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.logs" + vpc_endpoint_type = "Interface" + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.vpc_endpoints.id] + private_dns_enabled = true + + tags = { + Name = "${local.project}-${var.environment}-logs-endpoint" + } +} diff --git a/scripts/aws_setup_and_login.py b/scripts/aws_setup_and_login.py new file mode 100755 index 0000000..41e4793 --- /dev/null +++ b/scripts/aws_setup_and_login.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python3 +""" +AWS Setup and Login Script for Document Extractor Project + +This script handles: +1. AWS SSO login process +2. Profile validation +3. S3 bucket creation for Terraform state +4. DynamoDB table creation for state locking +5. Pre-flight checks before OpenTofu/Terraform operations + +Usage: + python aws_setup_and_login.py --profile AWSAdministratorAccess-328307993388 + python aws_setup_and_login.py --profile AWSAdministratorAccess-328307993388 --region us-east-1 + python aws_setup_and_login.py --create-backend-resources +""" + +import argparse +import boto3 +import json +import subprocess +import sys +import time +from botocore.exceptions import ClientError, NoCredentialsError + + +class AWSSetupManager: + def __init__(self, profile_name=None, region='us-east-1'): + self.profile_name = profile_name + self.region = region + self.sso_start_url = "https://codeforamerica.awsapps.com/start/#" + + # Backend configuration + self.backend_config = { + 'bucket_name': f'document-extractor-dev-opentofu-state', + 'dynamodb_table': 'terraform-locks-dev', + 'region': region + } + + def check_sso_login_status(self): + """Check if AWS SSO login is active""" + try: + if self.profile_name: + session = boto3.Session(profile_name=self.profile_name) + else: + session = boto3.Session() + + sts_client = session.client('sts', region_name=self.region) + identity = sts_client.get_caller_identity() + + print(f"✅ AWS credentials are valid") + print(f" Account ID: {identity['Account']}") + print(f" User ARN: {identity['Arn']}") + return True + + except Exception as e: + print(f"❌ AWS credentials not valid: {e}") + return False + + def perform_sso_login(self): + """Perform AWS SSO login""" + print(f"🔐 Initiating AWS SSO login...") + print(f" SSO Start URL: {self.sso_start_url}") + + try: + if self.profile_name: + cmd = ['aws', 'sso', 'login', '--profile', self.profile_name] + else: + cmd = ['aws', 'sso', 'login'] + + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + print("✅ AWS SSO login successful") + return True + else: + print(f"❌ AWS SSO login failed: {result.stderr}") + return False + + except Exception as e: + print(f"❌ Error during SSO login: {e}") + return False + + def get_s3_client(self): + """Get S3 client with proper session""" + if self.profile_name: + session = boto3.Session(profile_name=self.profile_name) + return session.client('s3', region_name=self.region) + else: + return boto3.client('s3', region_name=self.region) + + def get_dynamodb_client(self): + """Get DynamoDB client with proper session""" + if self.profile_name: + session = boto3.Session(profile_name=self.profile_name) + return session.client('dynamodb', region_name=self.region) + else: + return boto3.client('dynamodb', region_name=self.region) + + def check_s3_bucket_exists(self, bucket_name=None): + """Check if S3 bucket exists and get its region""" + if bucket_name is None: + bucket_name = self.backend_config['bucket_name'] + + try: + s3_client = self.get_s3_client() + + # Check if bucket exists + s3_client.head_bucket(Bucket=bucket_name) + + # Get bucket location + location_response = s3_client.get_bucket_location(Bucket=bucket_name) + bucket_region = location_response.get('LocationConstraint') + if bucket_region is None: + bucket_region = 'us-east-1' + + print(f"✅ S3 bucket '{bucket_name}' exists in region '{bucket_region}'") + return True, bucket_region + + except ClientError as e: + error_code = e.response['Error']['Code'] + if error_code == '404': + print(f"❌ S3 bucket '{bucket_name}' does not exist") + return False, None + else: + print(f"❌ Error checking bucket: {e}") + return False, None + + def create_s3_bucket(self, bucket_name=None): + """Create S3 bucket for Terraform state""" + if bucket_name is None: + bucket_name = self.backend_config['bucket_name'] + + try: + s3_client = self.get_s3_client() + + print(f"🪣 Creating S3 bucket '{bucket_name}' in region '{self.region}'...") + + if self.region == 'us-east-1': + # us-east-1 doesn't need LocationConstraint + s3_client.create_bucket(Bucket=bucket_name) + else: + s3_client.create_bucket( + Bucket=bucket_name, + CreateBucketConfiguration={'LocationConstraint': self.region} + ) + + # Enable versioning + s3_client.put_bucket_versioning( + Bucket=bucket_name, + VersioningConfiguration={'Status': 'Enabled'} + ) + + # Enable encryption + s3_client.put_bucket_encryption( + Bucket=bucket_name, + ServerSideEncryptionConfiguration={ + 'Rules': [ + { + 'ApplyServerSideEncryptionByDefault': { + 'SSEAlgorithm': 'AES256' + } + } + ] + } + ) + + print(f"✅ S3 bucket '{bucket_name}' created successfully") + return True + + except ClientError as e: + print(f"❌ Error creating S3 bucket: {e}") + return False + + def check_dynamodb_table_exists(self, table_name=None): + """Check if DynamoDB table exists""" + if table_name is None: + table_name = self.backend_config['dynamodb_table'] + + try: + dynamodb_client = self.get_dynamodb_client() + + response = dynamodb_client.describe_table(TableName=table_name) + status = response['Table']['TableStatus'] + + print(f"✅ DynamoDB table '{table_name}' exists with status '{status}'") + return True, status + + except ClientError as e: + error_code = e.response['Error']['Code'] + if error_code == 'ResourceNotFoundException': + print(f"❌ DynamoDB table '{table_name}' does not exist") + return False, None + else: + print(f"❌ Error checking DynamoDB table: {e}") + return False, None + + def create_dynamodb_table(self, table_name=None): + """Create DynamoDB table for Terraform state locking""" + if table_name is None: + table_name = self.backend_config['dynamodb_table'] + + try: + dynamodb_client = self.get_dynamodb_client() + + print(f"🗃️ Creating DynamoDB table '{table_name}' in region '{self.region}'...") + + dynamodb_client.create_table( + TableName=table_name, + KeySchema=[ + { + 'AttributeName': 'LockID', + 'KeyType': 'HASH' + } + ], + AttributeDefinitions=[ + { + 'AttributeName': 'LockID', + 'AttributeType': 'S' + } + ], + BillingMode='PAY_PER_REQUEST', + Tags=[ + { + 'Key': 'Purpose', + 'Value': 'TerraformStateLocking' + }, + { + 'Key': 'Project', + 'Value': 'DocumentExtractor' + } + ] + ) + + print(f"⏳ Waiting for DynamoDB table to become active...") + waiter = dynamodb_client.get_waiter('table_exists') + waiter.wait(TableName=table_name, WaiterConfig={'Delay': 2, 'MaxAttempts': 30}) + + print(f"✅ DynamoDB table '{table_name}' created successfully") + return True + + except ClientError as e: + print(f"❌ Error creating DynamoDB table: {e}") + return False + + def setup_backend_resources(self): + """Create all required backend resources""" + print(f"\n🏗️ Setting up backend resources for region '{self.region}'...") + + # Check and create S3 bucket + bucket_exists, bucket_region = self.check_s3_bucket_exists() + if not bucket_exists: + if not self.create_s3_bucket(): + return False + elif bucket_region != self.region: + print(f"⚠️ Warning: Bucket exists in '{bucket_region}' but you're targeting '{self.region}'") + + # Check and create DynamoDB table + table_exists, table_status = self.check_dynamodb_table_exists() + if not table_exists: + if not self.create_dynamodb_table(): + return False + + return True + + def print_setup_summary(self): + """Print setup summary and next steps""" + print(f"\n📋 Setup Summary:") + print(f" Profile: {self.profile_name or 'default'}") + print(f" Region: {self.region}") + print(f" S3 Bucket: {self.backend_config['bucket_name']}") + print(f" DynamoDB Table: {self.backend_config['dynamodb_table']}") + + print(f"\n🚀 Ready for OpenTofu operations!") + print(f" You can now run: tofu init -reconfigure") + + # Print environment setup commands + if self.profile_name: + print(f"\n💡 Environment setup commands:") + print(f" export AWS_PROFILE={self.profile_name}") + print(f" export AWS_REGION={self.region}") + + +def main(): + parser = argparse.ArgumentParser( + description="AWS Setup and Login for Document Extractor Project", + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + '--profile', + default='AWSAdministratorAccess-328307993388', + help='AWS profile to use' + ) + + parser.add_argument( + '--region', + default='us-east-1', + help='AWS region to use' + ) + + parser.add_argument( + '--create-backend-resources', + action='store_true', + help='Create S3 bucket and DynamoDB table for Terraform backend' + ) + + parser.add_argument( + '--force-login', + action='store_true', + help='Force AWS SSO login even if credentials seem valid' + ) + + args = parser.parse_args() + + # Initialize setup manager + setup_manager = AWSSetupManager(profile_name=args.profile, region=args.region) + + print(f"🚀 AWS Setup for Document Extractor Project") + print(f" Profile: {args.profile}") + print(f" Region: {args.region}") + + # Check current login status + if not args.force_login and setup_manager.check_sso_login_status(): + print("✅ Already logged in to AWS SSO") + else: + # Perform SSO login + if not setup_manager.perform_sso_login(): + print("❌ Login failed. Exiting.") + sys.exit(1) + + # Verify login worked + if not setup_manager.check_sso_login_status(): + print("❌ Login verification failed. Exiting.") + sys.exit(1) + + # Set up backend resources if requested + if args.create_backend_resources: + if not setup_manager.setup_backend_resources(): + print("❌ Failed to setup backend resources. Exiting.") + sys.exit(1) + else: + # Just check if resources exist + print(f"\n🔍 Checking backend resources...") + setup_manager.check_s3_bucket_exists() + setup_manager.check_dynamodb_table_exists() + + # Print summary + setup_manager.print_setup_summary() + + +if __name__ == '__main__': + main() diff --git a/scripts/delete_aws_secrets.py b/scripts/delete_aws_secrets.py new file mode 100755 index 0000000..485d428 --- /dev/null +++ b/scripts/delete_aws_secrets.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 + +import boto3 +import argparse +import sys +from botocore.exceptions import NoCredentialsError, PartialCredentialsError, ClientError + +# Hardcoded list of allowed secret names to delete (for safety) +ALLOWED_SECRETS = ["private-key", "public-key", "username", "password"] + +def find_secrets_by_name(client, secret_name): + """ + Find all secrets that contain the given name. + + Args: + client: boto3 secretsmanager client + secret_name (str): The name to search for in secret names + + Returns: + list: List of matching secret names + """ + try: + paginator = client.get_paginator('list_secrets') + matching_secrets = [] + + for page in paginator.paginate(): + for secret in page.get('SecretList', []): + if secret_name in secret.get('Name', ''): + matching_secrets.append(secret.get('Name')) + + return matching_secrets + except Exception as e: + print(f"Error searching for secrets: {e}") + return [] + +def delete_secret(client, secret_name, force=True): + """ + Delete a secret from AWS Secrets Manager. + + Args: + client: boto3 secretsmanager client + secret_name (str): The exact name of the secret to delete + force (bool): Whether to force deletion without recovery period + + Returns: + bool: True if successful, False otherwise + """ + try: + response = client.delete_secret( + SecretId=secret_name, + ForceDeleteWithoutRecovery=force + ) + + print(f"✅ Successfully deleted secret: {secret_name}") + if force: + print(f" Secret was permanently deleted (no recovery possible)") + else: + deletion_date = response.get('DeletionDate') + print(f" Secret scheduled for deletion on: {deletion_date}") + + return True + + except ClientError as e: + error_code = e.response['Error']['Code'] + if error_code == 'ResourceNotFoundException': + print(f"❌ Secret not found: {secret_name}") + elif error_code == 'InvalidRequestException': + print(f"❌ Invalid request for secret: {secret_name} - {e.response['Error']['Message']}") + elif error_code == 'AccessDeniedException': + print(f"❌ Access denied for secret: {secret_name}") + else: + print(f"❌ Error deleting secret {secret_name}: {e}") + return False + except Exception as e: + print(f"❌ Unexpected error deleting secret {secret_name}: {e}") + return False + +def confirm_deletion(secret_names): + """ + Ask user to confirm deletion of secrets. + + Args: + secret_names (list): List of secret names to delete + + Returns: + bool: True if user confirms, False otherwise + """ + print("\n" + "="*60) + print("⚠️ WARNING: You are about to PERMANENTLY DELETE the following secrets:") + print("="*60) + + for name in secret_names: + print(f" • {name}") + + print("\n🔥 This action CANNOT be undone!") + print("🔥 Secrets will be PERMANENTLY DELETED with no recovery period!") + print("="*60) + + while True: + confirm = input("\nType 'DELETE' (all caps) to confirm deletion, or 'cancel' to abort: ").strip() + + if confirm == "DELETE": + return True + elif confirm.lower() == "cancel": + print("❌ Deletion cancelled.") + return False + else: + print("❌ Invalid input. Please type 'DELETE' to confirm or 'cancel' to abort.") + +def main(): + parser = argparse.ArgumentParser( + description="Delete specific AWS Secrets Manager secrets with force deletion. " + f"Only allows deletion of: {', '.join(ALLOWED_SECRETS)}" + ) + parser.add_argument( + "secret_name", + choices=ALLOWED_SECRETS, + help=f"Name of the secret to delete. Must be one of: {', '.join(ALLOWED_SECRETS)}" + ) + parser.add_argument( + "--profile", + help="AWS CLI profile to use. If not provided, uses the default profile." + ) + parser.add_argument( + "--region", + help="AWS region to use. If not provided, uses the default region from the profile." + ) + parser.add_argument( + "--no-force", + action="store_true", + help="Don't force deletion (use standard 30-day recovery period instead)" + ) + parser.add_argument( + "--yes", + action="store_true", + help="Skip confirmation prompt (dangerous!)" + ) + + args = parser.parse_args() + + try: + session_args = {} + if args.profile: + session_args['profile_name'] = args.profile + if args.region: + session_args['region_name'] = args.region + + session = boto3.Session(**session_args) + client = session.client('secretsmanager') + + print(f"Searching for secrets containing '{args.secret_name}'...") + print(f"Profile: {args.profile or 'default'}, Region: {client.meta.region_name or 'default'}") + + # Find all secrets that contain the specified name + matching_secrets = find_secrets_by_name(client, args.secret_name) + + if not matching_secrets: + print(f"❌ No secrets found containing '{args.secret_name}'") + return + + print(f"\nFound {len(matching_secrets)} secret(s) containing '{args.secret_name}':") + for secret in matching_secrets: + print(f" • {secret}") + + # Confirm deletion unless --yes flag is used + if not args.yes: + if not confirm_deletion(matching_secrets): + return + + # Delete each matching secret + print(f"\nDeleting secrets...") + success_count = 0 + force_delete = not args.no_force + + for secret_name in matching_secrets: + if delete_secret(client, secret_name, force=force_delete): + success_count += 1 + + print(f"\n📊 Summary: {success_count}/{len(matching_secrets)} secrets deleted successfully.") + + if success_count == len(matching_secrets): + print("🎉 All secrets deleted successfully!") + elif success_count > 0: + print("⚠️ Some secrets were deleted, but errors occurred with others.") + else: + print("❌ No secrets were deleted due to errors.") + + except (NoCredentialsError, PartialCredentialsError): + print("❌ Error: AWS credentials not found. Configure your credentials (e.g., via AWS CLI 'aws configure').") + if args.profile: + print(f"Ensure the profile '{args.profile}' is correctly configured.") + sys.exit(1) + except Exception as e: + print(f"❌ An unexpected error occurred: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/empty_s3_bucket.sh b/scripts/empty_s3_bucket.sh new file mode 100755 index 0000000..29bb07e --- /dev/null +++ b/scripts/empty_s3_bucket.sh @@ -0,0 +1,151 @@ +#!/bin/bash + +# Script to empty an S3 bucket completely +# Usage: ./empty_s3_bucket.sh <bucket-name> + +set -e + +BUCKET_NAME="${1:-document-extractor-dev-documents-328307993388}" + +if [ -z "$BUCKET_NAME" ]; then + echo "Usage: $0 <bucket-name>" + echo "Example: $0 document-extractor-dev-documents-328307993388" + exit 1 +fi + +echo "🗑️ Emptying S3 bucket: $BUCKET_NAME" + +# Check if bucket exists +if ! aws s3api head-bucket --bucket "$BUCKET_NAME" 2>/dev/null; then + echo "❌ Bucket $BUCKET_NAME does not exist or you don't have access to it" + exit 1 +fi + +echo "📋 Checking bucket versioning status..." +VERSIONING=$(aws s3api get-bucket-versioning --bucket "$BUCKET_NAME" --query 'Status' --output text 2>/dev/null || echo "null") +echo " Versioning status: $VERSIONING" + +# Function to delete objects in batches +delete_objects_batch() { + local version_flag="$1" + local batch_size=1000 + + echo "🔍 Finding objects to delete..." + + while true; do + # Get objects (with or without versions) + if [ "$version_flag" = "--versions" ]; then + objects=$(aws s3api list-object-versions --bucket "$BUCKET_NAME" \ + --query 'Versions[].{Key:Key,VersionId:VersionId}' \ + --max-items $batch_size --output json 2>/dev/null || echo '[]') + else + objects=$(aws s3api list-objects-v2 --bucket "$BUCKET_NAME" \ + --query 'Contents[].{Key:Key}' \ + --max-items $batch_size --output json 2>/dev/null || echo '[]') + fi + + # Check if we got any objects + object_count=$(echo "$objects" | jq '. | length') + + if [ "$object_count" -eq 0 ]; then + echo " No more objects found" + break + fi + + echo " Found $object_count objects to delete" + + # Create delete request + if [ "$version_flag" = "--versions" ]; then + delete_request=$(echo "$objects" | jq '{Objects: [.[] | {Key: .Key, VersionId: .VersionId}], Quiet: true}') + else + delete_request=$(echo "$objects" | jq '{Objects: [.[] | {Key: .Key}], Quiet: true}') + fi + + # Write to temp file + echo "$delete_request" > /tmp/delete_request.json + + # Delete the batch + echo " Deleting batch of $object_count objects..." + aws s3api delete-objects --bucket "$BUCKET_NAME" --delete file:///tmp/delete_request.json > /dev/null + + # If we got less than batch_size, we're done + if [ "$object_count" -lt $batch_size ]; then + break + fi + done + + # Clean up temp file + rm -f /tmp/delete_request.json +} + +# Function to delete delete markers +delete_delete_markers() { + echo "🗑️ Deleting delete markers..." + + while true; do + delete_markers=$(aws s3api list-object-versions --bucket "$BUCKET_NAME" \ + --query 'DeleteMarkers[].{Key:Key,VersionId:VersionId}' \ + --max-items 1000 --output json 2>/dev/null || echo '[]') + + marker_count=$(echo "$delete_markers" | jq '. | length') + + if [ "$marker_count" -eq 0 ]; then + echo " No delete markers found" + break + fi + + echo " Found $marker_count delete markers to remove" + + delete_request=$(echo "$delete_markers" | jq '{Objects: [.[] | {Key: .Key, VersionId: .VersionId}], Quiet: true}') + echo "$delete_request" > /tmp/delete_markers.json + + aws s3api delete-objects --bucket "$BUCKET_NAME" --delete file:///tmp/delete_markers.json > /dev/null + echo " Deleted $marker_count delete markers" + + if [ "$marker_count" -lt 1000 ]; then + break + fi + done + + rm -f /tmp/delete_markers.json +} + +echo "" +echo "🚀 Starting bucket cleanup..." + +# If versioning is enabled, we need to handle versions and delete markers +if [ "$VERSIONING" = "Enabled" ] || [ "$VERSIONING" = "Suspended" ]; then + echo "" + echo "📦 Deleting all object versions..." + delete_objects_batch "--versions" + + echo "" + delete_delete_markers +fi + +echo "" +echo "📄 Deleting current objects..." +delete_objects_batch + +echo "" +echo "🔍 Final verification..." +remaining_objects=$(aws s3api list-objects-v2 --bucket "$BUCKET_NAME" --query 'Contents | length(@)' --output text 2>/dev/null || echo "0") +remaining_versions=$(aws s3api list-object-versions --bucket "$BUCKET_NAME" --query 'Versions | length(@)' --output text 2>/dev/null || echo "0") +remaining_markers=$(aws s3api list-object-versions --bucket "$BUCKET_NAME" --query 'DeleteMarkers | length(@)' --output text 2>/dev/null || echo "0") + +echo " Objects: $remaining_objects" +echo " Versions: $remaining_versions" +echo " Delete markers: $remaining_markers" + +if [ "$remaining_objects" = "0" ] && [ "$remaining_versions" = "0" ] && [ "$remaining_markers" = "0" ]; then + echo "" + echo "✅ Bucket $BUCKET_NAME is now empty!" + echo "🎯 You can now run 'terraform destroy' again" +else + echo "" + echo "⚠️ Warning: Bucket may not be completely empty" + echo " You may need to check for incomplete multipart uploads or run this script again" +fi + +echo "" +echo "🏁 Done!" diff --git a/scripts/empty_s3_bucket_simple.sh b/scripts/empty_s3_bucket_simple.sh new file mode 100755 index 0000000..66ad96f --- /dev/null +++ b/scripts/empty_s3_bucket_simple.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Simple script to empty an S3 bucket using AWS CLI +# Usage: ./empty_s3_bucket_simple.sh [bucket-name] + +BUCKET_NAME="${1:-document-extractor-dev-documents-328307993388}" + +echo "🗑️ Emptying S3 bucket: $BUCKET_NAME" + +# Check if bucket exists +if ! aws s3api head-bucket --bucket "$BUCKET_NAME" 2>/dev/null; then + echo "❌ Bucket $BUCKET_NAME does not exist or you don't have access to it" + exit 1 +fi + +echo "🚀 Removing all objects and versions..." + +# Remove all objects and versions +aws s3 rm s3://"$BUCKET_NAME" --recursive + +# Also remove all versions if versioning is enabled +echo "🔄 Removing all object versions..." +aws s3api delete-objects \ + --bucket "$BUCKET_NAME" \ + --delete "$(aws s3api list-object-versions \ + --bucket "$BUCKET_NAME" \ + --output json \ + --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}, Quiet: true}' 2>/dev/null || echo '{Objects:[], Quiet: true}')" + +# Remove delete markers +echo "🗑️ Removing delete markers..." +aws s3api delete-objects \ + --bucket "$BUCKET_NAME" \ + --delete "$(aws s3api list-object-versions \ + --bucket "$BUCKET_NAME" \ + --output json \ + --query '{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}, Quiet: true}' 2>/dev/null || echo '{Objects:[], Quiet: true}')" + +echo "✅ Bucket emptying complete!" +echo "🎯 You can now run 'terraform destroy' again" diff --git a/scripts/example_prd.txt b/scripts/example_prd.txt new file mode 100644 index 0000000..194114d --- /dev/null +++ b/scripts/example_prd.txt @@ -0,0 +1,47 @@ +<context> +# Overview +[Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.] + +# Core Features +[List and describe the main features of your product. For each feature, include: +- What it does +- Why it's important +- How it works at a high level] + +# User Experience +[Describe the user journey and experience. Include: +- User personas +- Key user flows +- UI/UX considerations] +</context> +<PRD> +# Technical Architecture +[Outline the technical implementation details: +- System components +- Data models +- APIs and integrations +- Infrastructure requirements] + +# Development Roadmap +[Break down the development process into phases: +- MVP requirements +- Future enhancements +- Do not think about timelines whatsoever -- all that matters is scope and detailing exactly what needs to be build in each phase so it can later be cut up into tasks] + +# Logical Dependency Chain +[Define the logical order of development: +- Which features need to be built first (foundation) +- Getting as quickly as possible to something usable/visible front end that works +- Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches] + +# Risks and Mitigations +[Identify potential risks and how they'll be addressed: +- Technical challenges +- Figuring out the MVP that we can build upon +- Resource constraints] + +# Appendix +[Include any additional information: +- Research findings +- Technical specifications] +</PRD> \ No newline at end of file diff --git a/scripts/get_s3_bucket_location.py b/scripts/get_s3_bucket_location.py new file mode 100755 index 0000000..0f640da --- /dev/null +++ b/scripts/get_s3_bucket_location.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +Get S3 bucket location information. + +This script retrieves the AWS region where an S3 bucket is located. +Useful for verifying bucket locations before configuring Terraform backends. +""" + +import argparse +import boto3 +import json +import sys +from botocore.exceptions import ClientError, NoCredentialsError + + +def get_bucket_location(bucket_name, profile_name=None, output_format='json'): + """ + Get the location (region) of an S3 bucket. + + Args: + bucket_name (str): Name of the S3 bucket + profile_name (str, optional): AWS profile to use + output_format (str): Output format ('json', 'text', 'region-only') + + Returns: + dict: Bucket location information + """ + try: + # Create session with profile if specified + if profile_name: + session = boto3.Session(profile_name=profile_name) + s3_client = session.client('s3') + else: + s3_client = boto3.client('s3') + + # Get bucket location + response = s3_client.get_bucket_location(Bucket=bucket_name) + + # AWS returns None for us-east-1, so we need to handle that + location = response.get('LocationConstraint') + if location is None: + location = 'us-east-1' + + result = { + 'bucket_name': bucket_name, + 'region': location, + 'location_constraint': response.get('LocationConstraint') + } + + return result + + except ClientError as e: + error_code = e.response['Error']['Code'] + if error_code == 'NoSuchBucket': + print(f"Error: Bucket '{bucket_name}' does not exist.", file=sys.stderr) + elif error_code == 'AccessDenied': + print(f"Error: Access denied to bucket '{bucket_name}'. Check your permissions.", file=sys.stderr) + else: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + except NoCredentialsError: + print("Error: AWS credentials not found. Please configure your credentials.", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Unexpected error: {e}", file=sys.stderr) + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser( + description="Get S3 bucket location information", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python get_s3_bucket_location.py my-bucket + python get_s3_bucket_location.py my-bucket --profile my-profile + python get_s3_bucket_location.py my-bucket --output region-only + python get_s3_bucket_location.py my-bucket --profile AWSAdministratorAccess-328307993388 --output text + """ + ) + + parser.add_argument( + 'bucket_name', + help='Name of the S3 bucket to check' + ) + + parser.add_argument( + '--profile', + help='AWS profile to use (optional)' + ) + + parser.add_argument( + '--output', + choices=['json', 'text', 'region-only'], + default='json', + help='Output format (default: json)' + ) + + args = parser.parse_args() + + # Get bucket location + result = get_bucket_location(args.bucket_name, args.profile, args.output) + + # Output results based on format + if args.output == 'json': + print(json.dumps(result, indent=2)) + elif args.output == 'text': + print(f"Bucket: {result['bucket_name']}") + print(f"Region: {result['region']}") + print(f"Location Constraint: {result['location_constraint']}") + elif args.output == 'region-only': + print(result['region']) + + +if __name__ == '__main__': + main() diff --git a/scripts/list_aws_secrets.py b/scripts/list_aws_secrets.py new file mode 100755 index 0000000..375f8fc --- /dev/null +++ b/scripts/list_aws_secrets.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +import boto3 +import argparse +import json +from botocore.exceptions import NoCredentialsError, PartialCredentialsError, ClientError + +def list_all_secrets(profile_name=None, region_name=None): + """ + Lists all secrets in AWS Secrets Manager, including those pending deletion. + + Args: + profile_name (str, optional): The AWS CLI profile to use. + region_name (str, optional): The AWS region to use. + """ + try: + session_args = {} + if profile_name: + session_args['profile_name'] = profile_name + if region_name: + session_args['region_name'] = region_name + + session = boto3.Session(**session_args) + client = session.client('secretsmanager') + + print(f"Fetching secrets... (Profile: {profile_name or 'default'}, Region: {client.meta.region_name or 'default'})") + + paginator = client.get_paginator('list_secrets') + all_secrets = [] + + for page in paginator.paginate(IncludePlannedDeletion=True): + all_secrets.extend(page.get('SecretList', [])) + + if not all_secrets: + print("No secrets found.") + return + + print(f"\nFound {len(all_secrets)} secret(s):\n") + print(f"{'Name':<50} {'ARN':<100} {'Status':<15} {'Deletion Date'}") + print("-" * 180) + + for secret in all_secrets: + name = secret.get('Name', 'N/A') + arn = secret.get('ARN', 'N/A') + deleted_date = secret.get('DeletedDate') + + status = "Pending Deletion" if deleted_date else "Active" + deletion_date_str = deleted_date.strftime('%Y-%m-%d %H:%M:%S %Z') if deleted_date else "N/A" + + print(f"{name:<50} {arn:<100} {status:<15} {deletion_date_str}") + + print("\nDetailed JSON output of all secrets:") + print(json.dumps(all_secrets, default=str, indent=2)) + + + except (NoCredentialsError, PartialCredentialsError): + print("Error: AWS credentials not found. Configure your credentials (e.g., via AWS CLI 'aws configure').") + if profile_name: + print(f"Ensure the profile '{profile_name}' is correctly configured.") + except ClientError as e: + if e.response['Error']['Code'] == 'AccessDeniedException': + print(f"Error: Access Denied. The configured AWS credentials do not have permission to list secrets.") + print(f"Required IAM permission: 'secretsmanager:ListSecrets'") + else: + print(f"An AWS ClientError occurred: {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="List AWS Secrets Manager secrets, including those pending deletion.") + parser.add_argument( + "--profile", + help="AWS CLI profile to use. If not provided, uses the default profile." + ) + parser.add_argument( + "--region", + help="AWS region to use. If not provided, uses the default region from the profile or environment variables." + ) + args = parser.parse_args() + + list_all_secrets(profile_name=args.profile, region_name=args.region) diff --git a/scripts/prd.txt b/scripts/prd.txt new file mode 100644 index 0000000..28edb46 --- /dev/null +++ b/scripts/prd.txt @@ -0,0 +1,175 @@ +<context> +# Overview +This project implements comprehensive AWS infrastructure compliance based on the AWS Configuration Baseline Policy. The goal is to transform an existing non-compliant infrastructure (document extractor POC) into a secure, compliant AWS environment that meets organizational security and compliance requirements. The project addresses critical security gaps including missing VPC infrastructure, inadequate encryption, insufficient logging, and overly permissive IAM policies. + +# Core Features +The compliance implementation includes these essential security and operational features: + +**VPC Infrastructure Foundation** +- Complete network architecture with public/private subnet separation across multiple AZs +- Proper security groups, route tables, and VPC endpoints for secure AWS service communication +- NAT gateways for private subnet internet access and VPC Flow Logs for monitoring + +**Security & Encryption** +- Customer-managed KMS keys (CMK) for all encryption needs with proper key policies and aliases +- S3 bucket encryption, versioning, object locking, and SSL enforcement policies +- Secrets Manager with automatic rotation and CMK encryption + +**Identity & Access Management** +- Least privilege IAM policies replacing overly broad permissions (dynamodb:*, s3:*, etc.) +- Proper IAM user management with 90-day access key rotation +- Service account best practices and console access restrictions + +**Monitoring & Logging** +- CloudWatch log groups with 30-day retention and CMK encryption for all services +- API Gateway access logging and execution logging with X-Ray tracing +- CloudFront access logs and Datadog integration for log aggregation + +**Database & Storage Security** +- DynamoDB point-in-time recovery, backups, and CMK encryption +- S3 cross-region replication, access logging, and lifecycle policies +- Dead Letter Queues for Lambda and SQS error handling + +# User Experience +**Primary Users:** +- Infrastructure engineers implementing compliance requirements +- DevOps teams maintaining secure AWS environments +- Security teams auditing and monitoring compliance posture + +**Key User Flows:** +- Deploy foundational VPC infrastructure before any other resources +- Migrate existing Lambda functions from default VPC to private subnets +- Implement monitoring and alerting for compliance drift detection +- Execute regular security audits using AWS Security Hub and Inspector + +**Operational Considerations:** +- Zero-downtime migration approach where possible +- Comprehensive testing strategy for each compliance component +- Documentation and runbooks for ongoing maintenance +</context> +<PRD> +# Technical Architecture +**Infrastructure as Code Implementation:** +- Terraform/OpenTofu modules for all AWS resources following Code for America's baseline modules +- Modular design allowing incremental compliance implementation +- Environment-specific configurations (production vs non-production) + +**Network Architecture:** +- VPC with CIDR block allocation across 3 availability zones +- Public subnets (load balancers, NAT gateways) and private subnets (compute resources) +- VPC endpoints for ec2, ec2messages, ecr.api, ecr.dkr, guardduty-data, s3, ssm, ssm-contacts, ssm-incidents, ssmmessages +- Custom security groups with least privilege access patterns + +**Encryption & Key Management:** +- Customer-managed KMS keys with descriptive aliases and custom policies +- All services (S3, DynamoDB, Lambda, CloudWatch, Secrets Manager) using CMK encryption +- Automatic key rotation enabled with proper IAM permissions + +**Monitoring & Compliance Stack:** +- CloudWatch log groups with standardized naming and retention policies +- Datadog forwarder deployment for log aggregation +- AWS Security Hub for continuous compliance monitoring +- Cost anomaly detection and monitoring setup + +# Development Roadmap +**Phase 1: Foundation Infrastructure (Critical)** +- Create VPC with public/private subnet architecture across multiple AZs +- Deploy Internet Gateway, NAT Gateways, and route tables +- Configure VPC Flow Logs with CloudWatch integration +- Set up customer-managed KMS key with proper policies and aliases +- Create VPC endpoints for essential AWS services + +**Phase 2: Network Security & Access Control** +- Create custom security groups with least privilege rules +- Disable default security group (deny all traffic) +- Implement proper IAM policies with specific resource access +- Configure IAM user management and access key rotation processes +- Deploy security groups for Lambda, API Gateway, and other services + +**Phase 3: Service Migration & Security** +- Migrate Lambda functions to private subnets with VPC configuration +- Configure Dead Letter Queues for Lambda functions and SQS +- Implement CloudWatch log groups with CMK encryption and retention +- Set appropriate Lambda reserved concurrency limits +- Enable API Gateway logging and X-Ray tracing + +**Phase 4: Storage & Database Security** +- Add S3 bucket CMK encryption and versioning to all buckets +- Configure S3 object locking and SSL enforcement policies +- Set up S3 access logging and cross-region replication +- Enable DynamoDB point-in-time recovery and backup configuration +- Configure Secrets Manager automatic rotation and CMK encryption + +**Phase 5: Monitoring & Compliance Tools** +- Deploy Datadog forwarder to all regions +- Configure CloudFront access logging and security headers +- Enable AWS Security Hub and Inspector (when applicable) +- Set up Cost Explorer monitoring and anomaly detection +- Request Amazon Macie enablement for sensitive data detection + +**Phase 6: Final Compliance & Documentation** +- Implement lifecycle policies for all S3 buckets +- Configure backup vault for long-term DynamoDB retention +- Set up quarterly backup restore testing procedures +- Create compliance documentation and exception tracking +- Deploy static analysis tools (trivy) for ongoing IaC scanning + +# Logical Dependency Chain +**Critical Path Dependencies:** +1. **VPC Infrastructure First**: All other resources depend on proper network foundation +2. **KMS Key Setup**: Required before configuring encryption for any service +3. **Security Groups**: Must exist before deploying resources to private subnets +4. **Lambda VPC Migration**: Requires VPC, subnets, and security groups to be ready +5. **Logging Infrastructure**: CloudWatch log groups needed before enabling service logging +6. **Monitoring Setup**: Datadog forwarder deployment enables comprehensive log aggregation + +**Implementation Order:** +- Start with VPC, subnets, gateways, and routing (cannot proceed without network foundation) +- Immediately follow with KMS key setup for encryption capabilities +- Deploy security groups and IAM policies in parallel +- Migrate services to VPC and enable encryption simultaneously +- Add monitoring and logging as each service is migrated +- Final compliance tooling and documentation once core infrastructure is secure + +**Atomic Feature Boundaries:** +- Each AWS service compliance (S3, Lambda, DynamoDB) can be implemented independently after foundation +- Monitoring components can be added incrementally +- Backup and disaster recovery features are additive enhancements + +# Risks and Mitigations +**Critical Risks:** +- **Service Downtime During Migration**: Mitigation includes blue-green deployment strategy and careful VPC migration planning +- **Lambda Cold Starts in VPC**: Accept performance trade-off for security, consider provisioned concurrency +- **Cost Increases**: NAT gateways and VPC endpoints add costs, budget accordingly for compliance requirements + +**Technical Challenges:** +- **Existing Resource Dependencies**: Current Lambda functions may have hard-coded assumptions about default VPC +- **IAM Permission Scope**: Reducing from wildcard permissions requires thorough testing of application functionality +- **Cross-Region Replication Setup**: Requires coordination with backup and disaster recovery planning + +**Compliance Risks:** +- **Partial Implementation**: Must ensure complete compliance implementation, not just partial fixes +- **Configuration Drift**: Need ongoing monitoring to prevent manual changes that break compliance +- **Documentation Gaps**: Proper documentation essential for audit and ongoing maintenance + +# Appendix +**AWS Configuration Baseline Policy Requirements:** +- Reference to original baseline document for detailed compliance specifications +- Mapping of each requirement to specific implementation tasks +- Exception handling process for any baseline deviations + +**Current Infrastructure Analysis:** +- Complete audit findings showing specific non-compliance areas +- Resource inventory and current configuration documentation +- Migration impact analysis for each service component + +**Code for America OpenTofu Modules:** +- Recommended modules: aws-vpc, aws-logging, aws-serverless-database +- Module customization requirements for specific use cases +- Integration patterns with existing infrastructure + +**Compliance Verification:** +- Testing procedures for each compliance requirement +- Automated compliance checking with tools like trivy +- Quarterly compliance review and audit procedures +</PRD> diff --git a/terraform.tfstate b/terraform.tfstate new file mode 100644 index 0000000..58774fa --- /dev/null +++ b/terraform.tfstate @@ -0,0 +1,9 @@ +{ + "version": 4, + "terraform_version": "1.5.7", + "serial": 1, + "lineage": "0c6c2093-c2b1-1742-8fe4-c50bc794bff5", + "outputs": {}, + "resources": [], + "check_results": null +} diff --git a/ui/package-lock.json b/ui/package-lock.json index 9d3d388..66dd5dc 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -12,12 +12,14 @@ "@uswds/uswds": "^3.12.0", "axios": "^1.9.0", "dotenv": "^16.5.0", + "pdfjs-dist": "^5.2.133", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router": "^7.5.3" }, "devDependencies": { "@eslint/js": "^9.26.0", + "buffer": "^6.0.3", "eslint": "^9.26.0", "eslint-plugin-react": "^7.37.5", "globals": "^16.0.0", @@ -494,6 +496,188 @@ "win32" ] }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.70.tgz", + "integrity": "sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.70", + "@napi-rs/canvas-darwin-arm64": "0.1.70", + "@napi-rs/canvas-darwin-x64": "0.1.70", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.70", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.70", + "@napi-rs/canvas-linux-arm64-musl": "0.1.70", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.70", + "@napi-rs/canvas-linux-x64-gnu": "0.1.70", + "@napi-rs/canvas-linux-x64-musl": "0.1.70", + "@napi-rs/canvas-win32-x64-msvc": "0.1.70" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.70.tgz", + "integrity": "sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.70.tgz", + "integrity": "sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.70.tgz", + "integrity": "sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.70.tgz", + "integrity": "sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.70.tgz", + "integrity": "sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.70.tgz", + "integrity": "sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.70.tgz", + "integrity": "sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.70.tgz", + "integrity": "sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.70.tgz", + "integrity": "sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.70.tgz", + "integrity": "sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@parcel/bundler-default": { "version": "2.14.4", "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.14.4.tgz", @@ -2606,6 +2790,27 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -2691,6 +2896,31 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4420,6 +4650,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5906,6 +6157,18 @@ "node": ">=16" } }, + "node_modules/pdfjs-dist": { + "version": "5.2.133", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.2.133.tgz", + "integrity": "sha512-abE6ZWDxztt+gGFzfm4bX2ggfxUk9wsDEoFzIJm9LozaY3JdXR7jyLK4Bjs+XLXplCduuWS1wGhPC4tgTn/kzg==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.67" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/ui/package.json b/ui/package.json index 59c02c4..7a72f97 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,7 +7,7 @@ }, "scripts": { "dev": "parcel src/index.html", - "build": "parcel build src/index.html", + "build": "parcel build src/index.html && cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs dist/", "lint": "eslint . --ext .js", "format": "prettier --write .", "validate": "npm run lint && npm run format", @@ -19,12 +19,14 @@ "@uswds/uswds": "^3.12.0", "axios": "^1.9.0", "dotenv": "^16.5.0", + "pdfjs-dist": "^5.2.133", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router": "^7.5.3" }, "devDependencies": { "@eslint/js": "^9.26.0", + "buffer": "^6.0.3", "eslint": "^9.26.0", "eslint-plugin-react": "^7.37.5", "globals": "^16.0.0", diff --git a/ui/src/pages/VerifyPage.js b/ui/src/pages/VerifyPage.js index 7df80d9..b2e61eb 100644 --- a/ui/src/pages/VerifyPage.js +++ b/ui/src/pages/VerifyPage.js @@ -1,13 +1,25 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import Layout from '../components/Layout'; import { authorizedFetch } from '../utils/api'; import { useNavigate } from 'react-router'; +import * as pdfjsLib from 'pdfjs-dist'; +import '../pdf-viewer.css'; export default function VerifyPage({ signOut }) { + // Enable PDF.js fake worker mode to avoid CORS/MIME type issues + // This is not as performant but will work in all environments + pdfjsLib.GlobalWorkerOptions.workerSrc = ''; + const [documentId] = useState(() => sessionStorage.getItem('documentId')); const [responseData, setResponseData] = useState(null); // API response const [loading, setLoading] = useState(true); // tracks if page is loading const [error, setError] = useState(false); // tracks when there is an error + const [activeBoundingBox, setActiveBoundingBox] = useState(null); // tracks the currently focused field's bounding box + const previewContainerRef = useRef(null); + const pdfCanvasRef = useRef(null); + const [currentPage, setCurrentPage] = useState(1); + const [numPages, setNumPages] = useState(null); + const [pdfDocument, setPdfDocument] = useState(null); const navigate = useNavigate(); @@ -114,6 +126,22 @@ export default function VerifyPage({ signOut }) { })); } + function handleInputFocus(field) { + if (field.boundingBox) { + setActiveBoundingBox(field.boundingBox); + + // If we have a PDF open, re-render with the bounding box + if (responseData?.document_key?.split('.')?.pop()?.toLowerCase() === 'pdf' && + pdfDocument && pdfCanvasRef.current) { + // The PDF will re-render automatically due to the useEffect dependency on activeBoundingBox + } + } + } + + function handleInputBlur() { + setActiveBoundingBox(null); + } + function shouldUseTextarea(value) { if (typeof value !== 'string') return false; return value.includes('\n'); @@ -147,6 +175,8 @@ export default function VerifyPage({ signOut }) { rows={2} value={field.value || ''} onChange={(event) => handleInputChange(event, key, field)} + onFocus={() => handleInputFocus(field)} + onBlur={handleInputBlur} /> ) : ( <input @@ -155,6 +185,8 @@ export default function VerifyPage({ signOut }) { name={`field-${key}`} value={field.value || ''} onChange={(event) => handleInputChange(event, key, field)} + onFocus={() => handleInputFocus(field)} + onBlur={handleInputBlur} /> )} </div> @@ -162,6 +194,95 @@ export default function VerifyPage({ signOut }) { }); } + // Function to render a PDF page with bounding box overlay + const renderPdf = async (pdfData, pageNum) => { + if (!pdfCanvasRef.current) return; + + try { + // Load the PDF data + const loadingTask = pdfjsLib.getDocument({ data: atob(pdfData) }); + const pdf = await loadingTask.promise; + setPdfDocument(pdf); + setNumPages(pdf.numPages); + + // Get the page + const page = await pdf.getPage(pageNum); + + // Get the canvas context + const canvas = pdfCanvasRef.current; + const context = canvas.getContext('2d'); + + // Calculate the scale to fit the canvas + const viewport = page.getViewport({ scale: 1 }); + const containerWidth = previewContainerRef.current.clientWidth; + const scale = containerWidth / viewport.width; + const scaledViewport = page.getViewport({ scale }); + + // Set canvas dimensions + canvas.height = scaledViewport.height; + canvas.width = scaledViewport.width; + + // Render the PDF page + const renderContext = { + canvasContext: context, + viewport: scaledViewport + }; + + await page.render(renderContext).promise; + + // If there's an active bounding box, draw it on the canvas + if (activeBoundingBox) { + drawBoundingBoxOnCanvas(context, activeBoundingBox, scaledViewport); + } + } catch (error) { + console.error('Error rendering PDF:', error); + } + }; + + // Function to draw a bounding box on the canvas + const drawBoundingBoxOnCanvas = (context, boundingBox, viewport) => { + if (!boundingBox) return; + + const { Left, Top, Width, Height } = boundingBox; + + // Calculate coordinates based on the viewport + const x = Left * viewport.width; + const y = Top * viewport.height; + const width = Width * viewport.width; + const height = Height * viewport.height; + + // Save current context state + context.save(); + + // Set bounding box styles + context.strokeStyle = '#0050d8'; + context.lineWidth = 2; + context.setLineDash([5, 3]); // Dotted line + context.fillStyle = 'rgba(0, 80, 216, 0.1)'; + + // Draw rectangle + context.fillRect(x, y, width, height); + context.strokeRect(x, y, width, height); + + // Restore context state + context.restore(); + }; + + // Function to handle page navigation + const changePage = (newPage) => { + if (newPage >= 1 && newPage <= numPages) { + setCurrentPage(newPage); + } + }; + + // Effect to re-render PDF when active bounding box changes + useEffect(() => { + if (responseData?.base64_encoded_file && + responseData.document_key.split('.').pop().toLowerCase() === 'pdf') { + renderPdf(responseData.base64_encoded_file, currentPage); + } + }, [activeBoundingBox, currentPage, responseData]); + function displayFilePreview() { if (!responseData || !responseData.base64_encoded_file) return null; @@ -176,20 +297,58 @@ export default function VerifyPage({ signOut }) { const base64Src = `data:${mimeType};base64,${responseData.base64_encoded_file}`; return ( - <div id="file-display-container"> + <div id="file-display-container" ref={previewContainerRef} className="position-relative"> {fileExtension === 'pdf' ? ( - <iframe - src={base64Src} - width="100%" - height="600px" - title="Document Preview" - ></iframe> + <div className="pdf-container"> + <canvas ref={pdfCanvasRef} className="pdf-canvas" /> + + {numPages > 1 && ( + <div className="pdf-controls usa-pagination"> + <button + onClick={() => changePage(currentPage - 1)} + disabled={currentPage <= 1} + className="usa-button usa-button--outline" + > + Previous + </button> + <span className="page-info"> + Page {currentPage} of {numPages} + </span> + <button + onClick={() => changePage(currentPage + 1)} + disabled={currentPage >= numPages} + className="usa-button usa-button--outline" + > + Next + </button> + </div> + )} + </div> ) : ( - <img - src={base64Src} - alt="Uploaded Document" - style={{ maxWidth: '100%', height: 'auto' }} - /> + <div className="position-relative"> + <img + src={base64Src} + alt="Uploaded Document" + style={{ maxWidth: '100%', height: 'auto' }} + /> + {activeBoundingBox && ( + <div + className="bounding-box-highlight" + style={{ + position: 'absolute', + marginTop: "-4px", + left: `${activeBoundingBox.Left * 100}%`, + top: `${activeBoundingBox.Top * 100}%`, + width: `${activeBoundingBox.Width * 100}%`, + height: `${activeBoundingBox.Height * 100}%`, + border: '1px dotted #0050d8', + backgroundColor: 'rgba(0, 80, 216, 0.1)', + pointerEvents: 'none', + zIndex: 10, + }} + ></div> + )} + </div> )} </div> ); @@ -274,7 +433,6 @@ export default function VerifyPage({ signOut }) { <li className="usa-card width-full"> <div className="usa-card__container file-preview-col"> <div className="usa-card__body"> - <div id="file-display-container"></div> <div>{displayFilePreview()}</div> <p>{displayFileName()}</p> </div> diff --git a/ui/src/pdf-viewer.css b/ui/src/pdf-viewer.css new file mode 100644 index 0000000..5545edd --- /dev/null +++ b/ui/src/pdf-viewer.css @@ -0,0 +1,28 @@ +.pdf-container { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + height: 600px; + position: relative; +} + +.pdf-canvas { + max-width: 100%; + height: auto; + margin-bottom: 15px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.pdf-controls { + display: flex; + justify-content: center; + align-items: center; + gap: 15px; + margin-top: 10px; + padding: 10px; +} + +.page-info { + font-weight: 500; +} diff --git a/ui/src/pdf.worker.js b/ui/src/pdf.worker.js new file mode 100644 index 0000000..d9dbe78 --- /dev/null +++ b/ui/src/pdf.worker.js @@ -0,0 +1,4 @@ +// This file is used as a fallback worker for PDF.js +import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min'; + +export default pdfjsWorker;