# EasySAM - Opinionated Modular Cloud Deployment Tool

EasySAM is a simple, opinionated tool for deploying cloud resources with a focus on simplicity and modularity. It provides a streamlined way to define and deploy AWS resources using YAML configuration files, making cloud infrastructure management more accessible and maintainable.

## Features

- Simple YAML-based resource definitions
- Modular architecture with import support
- Comprehensive AWS resource support:
  - Lambda functions
  - DynamoDB tables with stream support
  - S3 buckets
  - SQS queues
  - Kinesis streams
  - API Gateway integrations
  - MQTT/IoT Core with custom authorizers
- Event-driven architecture:
  - DynamoDB Streams for table change notifications
  - SQS polling
  - Kinesis stream processing
- Easy initialization of new projects

## Installation

## Quick Start (using uv)

1. Initialize a new project with uv:
```pwsh
uv init
uv add --dev pip
uv add --dev easysam
```

2. Initialize EasySAM in the current directory:

```pwsh
uv run easysam init
```

Or with Prismarine support:

```pwsh
uv run easysam init --prismarine
```

3. Make sure that AWS credentials are configured. The recommended way is to use a named profile and use the `--aws-profile` option.

4. Deploy your application:

```pwsh
uv run easysam deploy --tag my-tag=my-value --environment my-environment-name .
```

For more options, use the `--help` flag:
```pwsh
uv run easysam --help
```

## Prerequisites  

* uv 0.5 or higher
* AWS Credentials Configured

## Resource Definitions

The entry point for all cloud resources definitions in the `resources.yaml` file. See [example applications](./example) for how applications are structures.

### Table Definitions

```yaml
tables:
  - name: String (e.g., Items)
    attributes:
      - name: String (e.g., ItemID)
        type: String (e.g., S), dynamodb type
        hash: Boolean Optional (e.g., true), means Partition Key
        range: Boolean Optional (e.g., true) means Sort Key
    indices:
      - name: String
        attributes:
          - name: String
            type: String
            hash: Boolean Optional
            range: Boolean Optional
    trigger: String or Object - lambda function to trigger on table changes
      # Simple form (just function name, uses defaults):
      # trigger: my-lambda
      # Advanced form (with options):
      # trigger:
      #   function: my-lambda
      #   viewtype: new-and-old  # Optional: keys-only, new, old, new-and-old (default: new-and-old)
      #   batchsize: 10          # Optional: number of records per batch
      #   batchwindow: 5         # Optional: seconds to wait for batch
      #   startingposition: latest  # Optional: trim-horizon, latest (default: latest)
```

### Bucket Definitions

```yaml
buckets:
  - name: String (e.g., my-bucket)
    public Boolean Optional (e.g., true), means Public read policy
```

### Queue Definitions

```yaml
queues:
  - name: String (e.g., my-queue)
```

### Stream Definitions

```yaml
streams:
  - name: String (e.g., my-stream)
```

## Lambda Definition

```yaml
  - name: String (e.g., my-lambda)
    uri: String (i.e., local path to the source)
    tables:
      - String (e.g., Items)
    polls:
      - String (e.g., my-stream) - incoming stream's name
    buckets:
      - String (e.g., my-bucket)
    send:
      - String (e.g., my-queue) - outgoing queue's name
    services:
      - comprehend  # Grants ComprehendBasicAccessPolicy
      - bedrock     # Grants bedrock:InvokeModel permission
      - mqtt        # Grants iot:Publish and iot:DescribeEndpoint permissions
```

### MQTT Definition

```yaml
mqtt:
  authorizer:
    function: String  # Lambda function name for custom IoT authorizer
  topics:  # Optional - only needed if not using authorizer-returned policies
    - String  # Topic patterns for client subscribe/receive (e.g., "channels/*")
```

The MQTT configuration provisions:
- An IoT Core custom authorizer linked to the specified Lambda function
- Lambda permissions for IoT to invoke the authorizer
- (Optional) An IoT client policy if `topics` is specified - typically not needed since custom authorizers return their own policy documents

Lambda functions that need to publish to IoT topics should include `mqtt` in their `services` list.

### API Gateway Definition

#### Lambda Function Integration

```yaml
  path: # (e.g., /my-lambda)
    function: String # (e.g., my-lambda)
    authorizer: String # (e.g., my-authorizer)
    greedy: Boolean # (e.g., false)
```

#### Direct DynamoDB Integration

```yaml
  path: # (e.g., /my-lambda)
    integration: dynamo
    method: String # (e.g., get)
    parameters: [String] # (e.g., [channel])
    role: GatewayDynamoRole
    action: String # (e.g., GetItem)
    requestTemplate: VTL Template 
    responseTemplateFile: VTL File Path
```

#### Direct SQS Integration

```yaml
  path: # (e.g., /my-lambda)
    integration: sqs
    method: String # (e.g., post)
    role: GatewaySQSRole
    queue: String # (e.g., my-queue)
    requestTemplate: String # VTL Template
    responseTemplateFile: String # VTL File Path
    authorizer: String # (e.g., my-authorizer)
```

### Import

```yaml
import:
  - <directory>
```

The `import` directive searches recursively for `easysam.yaml` files (local definitions) in the specified directory and merges them into the current template.

#### Local Lambda Definition

```yaml
lambda:
  name: <name>
  resources:
    tables:
      - <table>
    buckets:
      - <bucket>
    send:
      - <queue>
    polls:
      - <stream>
  integration:
    path: <path>
    open: <boolean>
    greedy: <boolean>
    authorizer: <authorizer-lambda-name>
```

Locally-defined lambda URI is set to the path of the `easysam.yaml` file.

#### Local Import

```yaml
import:
  - <file>
```

### Prismarine Support

```yaml
prismarine:
  default-base: <base-path>
  access-module: <access-module-path>
  extra-imports:
    - <path.to.module:ClassName>
  modelling: <typed-dict|pydantic>  # Optional (default: typed-dict)
  tables:
    - package: <package-to-import>
      base: <optional-base-path>
```

For more information, see [Prismarine README](https://github.com/adsight-app/prismarine/blob/main/README.md).

Set `modelling: pydantic` to generate Prisma clients backed by Pydantic models (see `example/prismapydantic`). Omit or set `modelling: typed-dict` to generate the default TypedDict-based clients.

### Conditional Resources

Conditional resources are defined using the `!Conditional` tag.

```yaml
? !Conditional
  key: my-bucket
  environment: prod
  region: eu-west-2
:
  extaccesspolicy: ProdPolicy
  public: true
```

#### Negation

The `~` prefix negates the condition.

```yaml
? !Conditional
  key: my-bucket
  environment: ~prod
  region: ~eu-west-2
```

### Deployment Context File

The deployment context file is used to further control resources, especially in CI. This version has the following features:

* override the resources.yaml file with the values in CI with `<path>: <value>` pairs.

```yaml
overrides:
  buckets/my-bucket/public: true
```

Use the `--context-file` option to specify the deploy context file.

```pwsh
easysam deploy <app-directory> --environment <aws-environment-name> --context-file deploy-context.yaml
```
The deploy context file is a YAML file that contains the overrides.

## Development

### Setting up the development environment

1. Clone the repository:
```bash
git clone https://github.com/adsight-app/easysam.git
cd easysam
```

2. Install development dependencies and activate the virtual environment:
```bash
uv sync
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
```

## Examples

See `example` folder for examples:

* `myapp`- a simple [application](example/myapp) with a lambda function and a table.
* `prismarine`- a simple [application](example/prismarine) with a lambda function and a table, using Prismarine.
* `appwitherrors`- an [application](example/appwitherrors) with some errors in the resources.yaml file, to test the error handling.
* `conditionals`- an [application](example/conditionals) with conditional resources.
* `aoss`- an [application](example/aoss) with Amazon OpenSearch Serverless and DynamoDB Streams integration.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Support

If you encounter any issues or have questions, please:  
1. Search [existing issues](https://github.com/adsight-app/easysam/issues)  
2. Create a new issue if needed

## Changelog

See [CHANGELOG.md](https://github.com/adsight-app/easysam/blob/main/CHANGELOG.md) for a list of changes between versions.
