aws-samples / aws-samples/aws-organizations-tool

Implement AWS Organizations API response caching

Open
#3 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Python
Stars
18
Forks
3
PR merge metrics
No merged PRs in 30d

Description

# Implement AWS Organizations API response caching

## Summary
Add intelligent caching for AWS Organizations API calls to improve performance, reduce costs, and minimize rate limiting issues. The caching system should be transparent to users and automatically manage required AWS resources based on configuration.

## Background
AWS Organizations API calls can be expensive and slow when managing large organizations with many accounts and organizational units. Repeated calls to the same APIs during a single execution or across multiple executions create unnecessary overhead.

**Current issues:**
- Slow execution for large organizations (hundreds of accounts/OUs)
- Repeated API calls for the same data within single execution
- AWS API rate limiting with large organizations
- Increased AWS costs from redundant API calls
- No persistence of organization structure between executions

## Reference Implementation
This feature exists in the AXA fork at `/Users/delhom/gitwork/axa/aws-cc-foundations-main-v3/prj/aws-orgs-fork/awsorgs/cache.py`. Key code to backport:

### 1. Cache Infrastructure (from fork `awsorgs/cache.py`)
```python
"""Cache utility used to cache read calls to organizations in Foundation V3 DynamoDB cache table"""

import json
import boto3
import datetime
import hashlib

TABLE_NAME = "cf-cache"
CACHED_METHODS = [
"describe_policy",
"list_accounts_for_parent",
"list_children",
"list_organizational_units_for_parent",
"list_parents",
"list_policies_for_target",
"list_roots",
"list_tags_for_resource",
]

def init_cache_table(log):
log.info("Initialising cache on table %s", TABLE_NAME)
dyn_res = boto3.resource('dynamodb')
try:
table = dyn_res.Table(TABLE_NAME)
table.table_id
return table
except:
# Create table logic here
pass

def generate_cache_key(method_name, **kwargs):
"""Generate cache key from method name and parameters"""
key_data = f"{method_name}#{json.dumps(kwargs, sort_keys=True)}"
return hashlib.md5(key_data.encode()).hexdigest()
```

### 2. Cache Manager Class (adapt from fork)
```python
class CacheManager:
def __init__(self, table_name="orgtool-cache", ttl_hours=24):
self.table_name = table_name
self.ttl_hours = ttl_hours
self.table = None

def get_or_call(self, method_name, params, api_call_func):
"""Get from cache or call API and cache result"""
if method_name not in CACHED_METHODS:
return api_call_func()

cache_key = generate_cache_key(method_name, **params)

# Try cache first
cached_result = self._get_from_cache(cache_key)
if cached_result:
return cached_result

# Call API and cache result
result = api_call_func()
self._put_to_cache(cache_key, result)
return result
```

## Proposed Solution

### 1. Transparent Caching System
- **Automatic resource management**: Create/delete DynamoDB cache table based on configuration
- **Transparent operation**: No changes to existing CLI commands or workflows
- **Configurable**: Enable/disable via configuration file
- **Smart invalidation**: Automatic cache expiration and selective invalidation

### 2. Configuration-Driven Approach
```yaml
# config.yaml
cache:
enabled: true
table_name: "orgtool-cache" # Optional, defaults to "orgtool-cache"
ttl_hours: 24 # Optional, defaults to 24 hours
region: "us-east-1" # Optional, defaults to current region
```

### 3. Cached API Methods (from fork reference)
Target high-frequency, read-only AWS Organizations API calls:
- `describe_policy`
- `list_accounts_for_parent`
- `list_children`
- `list_organizational_units_for_parent`
- `list_parents`
- `list_policies_for_target`
- `list_roots`
- `list_tags_for_resource`

## Technical Implementation

### Files to create/modify:

#### 1. **`orgtool/cache.py`** (new file - backport from fork)
**Source:** `awsorgs/cache.py` in AXA fork
**Key functions to adapt:**
- `init_cache_table()` - DynamoDB table management
- `generate_cache_key()` - Cache key generation
- `CACHED_METHODS` list - Methods to cache
- Cache get/put operations with TTL

#### 2. **`orgtool/utils.py`** (modify existing)
**Add cache-aware client wrapper:**
```python
def get_org_client(args, cache_config=None):
"""Get Organizations client with optional caching"""
org_client = boto3.client('organizations')

if cache_config and cache_config.get('enabled', False):
return CachedOrganizationsClient(org_client, cache_config)
return org_client

class CachedOrganizationsClient:
"""Wrapper that adds caching to Organizations client"""
def __init__(self, org_client, cache_config):
self.org_client = org_client
self.cache = CacheManager(cache_config)

def __getattr__(self, name):
"""Intercept API calls and add caching"""
original_method = getattr(self.org_client, name)

if name in CACHED_METHODS:
def cached_method(**kwargs):
return self.cache.get_or_call(name, kwargs, lambda: original_method(**kwargs))
return cached_method

return original_method
```

#### 3. **`orgtool/spec.py`** (modify existing)
**Integrate cache configuration:**
```python
def load_config(self):
"""Load configuration including cache settings"""
config = super().load_config()

# Initialize cache infrastructure based on config
cache_config = config.get('cache', {})
if cache_config.get('enabled', False):
self._ensure_cache_table(cache_config)

return config

def _ensure_cache_table(self, cache_config):
"""Create cache table if it doesn't exist"""
# Adapt from fork's init_cache_table()
pass
```

### 4. Code Backporting Checklist

**From fork `awsorgs/cache.py`:**
- [ ] `TABLE_NAME` constant → make configurable
- [ ] `CACHED_METHODS` list → copy as-is
- [ ] `init_cache_table()` → adapt for configurable table name
- [ ] `generate_cache_key()` → copy hash logic
- [ ] Cache get/put operations → adapt DynamoDB operations
- [ ] TTL handling → make configurable

**Integration points:**
- [ ] Modify client creation in `orgtool/utils.py`
- [ ] Add cache config loading in `orgtool/spec.py`
- [ ] Add cache table management functions
- [ ] Add graceful fallback for cache failures

## DynamoDB Table Schema (from fork)
```json
{
"TableName": "orgtool-cache",
"KeySchema": [
{"AttributeName": "cache_key", "KeyType": "HASH"}
],
"AttributeDefinitions": [
{"AttributeName": "cache_key", "AttributeType": "S"}
],
"TimeToLiveSpecification": {
"AttributeName": "ttl",
"Enabled": true
}
}
```

## Benefits

1. **Performance**: 50-90% reduction in execution time for large organizations
2. **Cost Reduction**: Minimize redundant AWS API calls
3. **Rate Limit Avoidance**: Reduce likelihood of hitting AWS API limits
4. **Transparent Operation**: No changes to existing workflows
5. **Automatic Management**: Infrastructure created/destroyed based on config
6. **Proven Implementation**: Based on working code from AXA fork

## Requirements

- DynamoDB permissions in the account where orgtool runs
- Configurable cache table name to avoid conflicts
- Backward compatibility with existing configurations
- No breaking changes to existing CLI commands

## Acceptance Criteria

- [ ] Backport cache infrastructure from AXA fork
- [ ] Cache configuration section in `config.yaml`
- [ ] Automatic DynamoDB table creation when cache enabled
- [ ] Automatic DynamoDB table deletion when cache disabled
- [ ] Transparent caching for target API methods
- [ ] Configurable TTL for cached responses
- [ ] Graceful fallback when cache unavailable
- [ ] Performance metrics/logging for cache hit/miss rates
- [ ] Documentation for cache configuration options
- [ ] Unit tests for cache functionality
- [ ] Integration tests with real AWS Organizations API

## Configuration Example

```yaml
# Minimal configuration - enable with defaults
cache:
enabled: true

# Full configuration - all options
cache:
enabled: true
table_name: "my-org-cache"
ttl_hours: 12
region: "us-west-2"

# Disable caching (default)
cache:
enabled: false
```

## Priority
**High** - Significant performance improvement for large organizations with existing proven implementation.

## Issue Type
**Feature**

## Labels
`enhancement`

Contributor guide

Open the contributing guide

Research direction

Start by reading orgtool/utils.py and orgtool/spec.py, then compare the referenced AXA fork file awsorgs/cache.py with the current repository. Trace how configuration and Organizations clients are created before defining the cache integration. Done means the listed cache settings, DynamoDB lifecycle, target API methods, fallback behavior, documentation, and unit and integration tests are covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, python, yaml
Domain
backend, cloud, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.