Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d795688
🙈 chore: add examples/config.yml to .gitignore
Dec 16, 2025
bc70516
✨ feat: add Azure Blob Storage support and refactor S3 client
Jan 16, 2026
af46908
✨ feat: add Google Cloud Storage support
Jan 16, 2026
adcef01
✨ feat: add config file support for storage provider credentials
Jan 16, 2026
29e98b8
📝 docs: update documentation and tests for multi-provider support
Jan 21, 2026
e389110
🐛 fix: use correct Azure SDK parameter name for list_containers
Jan 21, 2026
dc3658d
✨ feat: prompt to convert underscores in Azure container names
Jan 21, 2026
6358003
✨ feat: add provider-specific error messages for repository setup
Jan 21, 2026
f2ac482
♻️ refactor: move provider-specific error info to client classes
Jan 21, 2026
d946d30
🐛 fix: correct error messages for index and bucket deletion
Jan 21, 2026
a11ea81
🐛 fix: make unexpected error message provider-aware
Jan 21, 2026
f6ae4e3
🐛 fix: support Azure and GCP repository types in create_repo
Jan 21, 2026
5a84fa5
🐛 fix: include storage account name in Azure error messages
Jan 21, 2026
1890d91
🐛 fix: remove hardcoded AWS/S3 references throughout codebase
Jan 21, 2026
81e8c64
🐛 fix: Azure archiving and add storage tier to status display
Jan 22, 2026
f212130
✨ feat: delete orphaned ILM policies during rotation
Jan 22, 2026
0b3bebe
🐛 fix: unmount_repo to handle Azure container setting
Jan 22, 2026
015489c
🐛 fix: update date range before archiving to archive tier
Jan 22, 2026
c42e4ae
🐛 fix: check for active indices before archiving repos
Jan 22, 2026
e5d770c
✨ feat: add date range repair to repair-metadata command
Jan 23, 2026
102cd3c
🐛 fix: don't treat active repos as thawed based on storage class
Jan 23, 2026
be2a344
🐛 fix: add logging and ES verification to date range updates
Jan 23, 2026
d2c7230
🐛 fix: raise ActionError on template PUT failure instead of silently …
Jan 26, 2026
2a19a41
🐛 fix: strip system-managed created_date before PUT on index templates
Jan 26, 2026
55b8c66
🐛 fix: whitelist PUT fields for composable template updates
Jan 26, 2026
844458b
🐛 fix: capture date ranges for all mounted repos during rotation
Jan 27, 2026
5d4df68
🥅 fix: handle null aggregation results in get_timestamp_range
Jan 28, 2026
b59f411
🐛 fix: strip fm-clone prefix when matching snapshot indices to mounte…
Jan 28, 2026
ac3379c
🔥 refactor: remove Phase column from ILM policies status table
Jan 28, 2026
8cf1f27
feat: add --time/-rt flag to status command
Jan 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,4 @@ Thumbs.db
*.local
.env.local
deepfreeze-blog-post.md
examples/config.yml
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,23 @@ pip install git+https://github.com/elastic/deepfreeze.git#subdirectory=packages/

[View deepfreeze-cli documentation](packages/deepfreeze-cli/README.md)

## Supported Cloud Providers

Deepfreeze supports multiple cloud storage providers:

| Provider | Storage Type | Archive Tier |
|----------|--------------|--------------|
| **AWS** | S3 | Glacier, Glacier Deep Archive |
| **Azure** | Blob Storage | Archive tier |
| **GCP** | Cloud Storage | Archive storage class |

## Features

- **Setup**: Configure ILM policies, index templates, and S3 buckets for deepfreeze
- **Setup**: Configure ILM policies, index templates, and storage buckets for deepfreeze
- **Rotate**: Create new snapshot repositories on a schedule (weekly/monthly/yearly)
- **Status**: View the current state of all deepfreeze components
- **Thaw**: Restore data from Glacier for analysis
- **Refreeze**: Return thawed data to Glacier storage
- **Thaw**: Restore data from archive storage for analysis
- **Refreeze**: Return thawed data to archive storage
- **Cleanup**: Remove expired thaw requests and associated resources
- **Repair Metadata**: Fix inconsistencies in the deepfreeze status index

Expand All @@ -53,11 +63,22 @@ pip install git+https://github.com/elastic/deepfreeze.git#subdirectory=packages/
username: elastic
password: changeme

deepfreeze:
provider: aws
bucket_name_prefix: my-deepfreeze
repo_name_prefix: deepfreeze
rotate_by: week
# Storage provider credentials (optional - can also use environment variables)
storage:
# AWS S3
aws:
region: us-east-1
# profile: my-profile # Or use access_key_id + secret_access_key

# Azure Blob Storage
azure:
connection_string: "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=..."
# Or use account_name + account_key

# Google Cloud Storage
gcp:
project: my-gcp-project
credentials_file: /path/to/service-account.json
```

3. Initialize deepfreeze:
Expand Down
89 changes: 79 additions & 10 deletions packages/deepfreeze-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
# Deepfreeze

Standalone Elasticsearch S3 Glacier archival and lifecycle management tool.
Standalone Elasticsearch cloud storage archival and lifecycle management tool.

## Overview

Deepfreeze provides cost-effective S3 Glacier archival and lifecycle management for Elasticsearch snapshot repositories without requiring full Curator installation. It is a lightweight, focused tool for managing long-term data retention in AWS S3 Glacier storage.
Deepfreeze provides cost-effective cloud storage archival and lifecycle management for Elasticsearch snapshot repositories without requiring full Curator installation. It is a lightweight, focused tool for managing long-term data retention in cloud archive storage.

## Supported Cloud Providers

| Provider | Storage Type | Archive Tier |
|----------|--------------|--------------|
| **AWS** | S3 | Glacier, Glacier Deep Archive |
| **Azure** | Blob Storage | Archive tier |
| **GCP** | Cloud Storage | Archive storage class |

## Features

- S3 Glacier archival for Elasticsearch snapshot repositories
- Cloud archive storage for Elasticsearch snapshot repositories
- Repository rotation with configurable retention
- Thaw frozen repositories for data retrieval
- Automatic refreeze after data access
Expand Down Expand Up @@ -52,14 +60,20 @@ deepfreeze --help

- Python 3.8 or higher
- Elasticsearch 8.x cluster
- AWS credentials configured (for S3 access)
- Cloud provider credentials (one of):
- **AWS**: AWS credentials via environment, config file, or IAM role
- **Azure**: Connection string or account name + key
- **GCP**: Application Default Credentials or service account JSON
- Required Python packages (installed automatically):
- elasticsearch8
- boto3
- boto3 (for AWS)
- click
- rich
- voluptuous
- pyyaml
- Optional packages for additional providers:
- azure-storage-blob (for Azure): `pip install deepfreeze-cli[azure]`
- google-cloud-storage (for GCP): `pip install deepfreeze-cli[gcp]`

## Configuration

Expand Down Expand Up @@ -88,7 +102,7 @@ vim ~/.deepfreeze/config.yml

### Configuration Format

Create a YAML configuration file to specify Elasticsearch connection settings:
Create a YAML configuration file to specify Elasticsearch connection and storage provider settings:

```yaml
elasticsearch:
Expand All @@ -103,6 +117,29 @@ elasticsearch:
# Or use Elastic Cloud
# cloud_id: deployment:base64string

# Storage provider credentials (optional - can also use environment variables)
storage:
# AWS S3 configuration
aws:
region: us-east-1
# profile: my-profile # Use named profile from ~/.aws/credentials
# Or explicit credentials:
# access_key_id: AKIA...
# secret_access_key: ...

# Azure Blob Storage configuration
azure:
connection_string: "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=..."
# Or use account name + key:
# account_name: mystorageaccount
# account_key: ...

# Google Cloud Storage configuration
gcp:
project: my-gcp-project
credentials_file: /path/to/service-account.json
location: US

logging:
loglevel: INFO
# logfile: /var/log/deepfreeze.log
Expand All @@ -112,13 +149,30 @@ logging:

Configuration can also be provided via environment variables:

**Elasticsearch:**
- `DEEPFREEZE_ES_HOSTS` - Elasticsearch hosts (comma-separated)
- `DEEPFREEZE_ES_USERNAME` - Elasticsearch username
- `DEEPFREEZE_ES_PASSWORD` - Elasticsearch password
- `DEEPFREEZE_ES_API_KEY` - Elasticsearch API key
- `DEEPFREEZE_ES_CLOUD_ID` - Elastic Cloud ID

Environment variables override file configuration.
**AWS S3:**
- `AWS_ACCESS_KEY_ID` - AWS access key
- `AWS_SECRET_ACCESS_KEY` - AWS secret key
- `AWS_DEFAULT_REGION` - AWS region
- `AWS_PROFILE` - AWS profile name

**Azure Blob Storage:**
- `AZURE_STORAGE_CONNECTION_STRING` - Full connection string
- `AZURE_STORAGE_ACCOUNT` - Storage account name (with AZURE_STORAGE_KEY)
- `AZURE_STORAGE_KEY` - Storage account key

**Google Cloud Storage:**
- `GOOGLE_APPLICATION_CREDENTIALS` - Path to service account JSON
- `GOOGLE_CLOUD_PROJECT` - GCP project ID
- `GOOGLE_CLOUD_LOCATION` - Default bucket location

Environment variables are used as fallback when config file credentials are not provided.

## Usage

Expand All @@ -127,11 +181,26 @@ Environment variables override file configuration.
Set up deepfreeze with ILM policy and index template configuration:

```bash
# AWS (default)
deepfreeze --config config.yaml setup \
--ilm_policy_name my-ilm-policy \
--index_template_name my-template \
--bucket_name_prefix my-deepfreeze \
--repo_name_prefix my-deepfreeze

# Azure
deepfreeze --config config.yaml setup \
--provider azure \
--ilm_policy_name my-ilm-policy \
--bucket_name_prefix my-deepfreeze \
--repo_name_prefix my-deepfreeze

# GCP
deepfreeze --config config.yaml setup \
--provider gcp \
--ilm_policy_name my-ilm-policy \
--bucket_name_prefix my-deepfreeze \
--repo_name_prefix my-deepfreeze
```

### Check Status
Expand Down Expand Up @@ -216,11 +285,11 @@ deepfreeze --config config.yaml --dry-run repair-metadata

| Command | Description |
|---------|-------------|
| `setup` | Initialize deepfreeze environment |
| `setup` | Initialize deepfreeze environment (supports `--provider` option) |
| `status` | Show current status of repositories and requests |
| `rotate` | Create new repository and archive old ones |
| `thaw` | Initiate or check Glacier restore operations |
| `refreeze` | Return thawed repositories to Glacier |
| `thaw` | Initiate or check archive restore operations |
| `refreeze` | Return thawed repositories to archive storage |
| `cleanup` | Remove expired repositories and old requests |
| `repair-metadata` | Scan and repair metadata discrepancies |

Expand Down
56 changes: 48 additions & 8 deletions packages/deepfreeze-cli/deepfreeze/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,9 @@ def cli(ctx, config_path, dry_run):
@click.option(
"-o",
"--provider",
type=click.Choice(
[
"aws",
# "gcp",
# "azure",
]
),
type=click.Choice(["aws", "azure", "gcp"]),
default="aws",
help="What provider to use (AWS only for now)",
help="Cloud storage provider to use (aws, azure, or gcp)",
)
@click.option(
"-t",
Expand Down Expand Up @@ -306,6 +300,42 @@ def setup(

client = get_client_from_context(ctx)

# Azure container names don't allow underscores - offer to convert them
if provider == "azure":
names_to_check = {
"bucket_name_prefix": bucket_name_prefix,
"repo_name_prefix": repo_name_prefix,
"base_path_prefix": base_path_prefix,
}
names_with_underscores = {
name: value
for name, value in names_to_check.items()
if value and "_" in value
}
if names_with_underscores:
converted = {
name: value.replace("_", "-")
for name, value in names_with_underscores.items()
}
click.echo(
"Azure container names cannot contain underscores. "
"The following names would be converted:"
)
for name, value in names_with_underscores.items():
click.echo(f" {name}: {value} -> {converted[name]}")

if not click.confirm("Do you want to proceed with these converted names?"):
click.echo("Aborted. Please provide names without underscores.")
ctx.exit(1)

# Apply conversions
if "bucket_name_prefix" in converted:
bucket_name_prefix = converted["bucket_name_prefix"]
if "repo_name_prefix" in converted:
repo_name_prefix = converted["repo_name_prefix"]
if "base_path_prefix" in converted:
base_path_prefix = converted["base_path_prefix"]

action = Setup(
client=client,
year=year,
Expand Down Expand Up @@ -448,6 +478,14 @@ def rotate(
default=False,
help="Output plain text without formatting (suitable for scripting)",
)
@click.option(
"-rt",
"--time",
"show_time",
is_flag=True,
default=False,
help="Show full date+time in tables",
)
@click.pass_context
def status(
ctx,
Expand All @@ -458,6 +496,7 @@ def status(
ilm,
show_config_flag,
porcelain,
show_time,
):
"""
Show the status of deepfreeze
Expand All @@ -479,6 +518,7 @@ def status(
show_buckets=buckets,
show_ilm=ilm,
show_config=show_config_flag,
show_time=show_time,
)

try:
Expand Down
2 changes: 1 addition & 1 deletion packages/deepfreeze-cli/deepfreeze/defaults/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def provider():
"""
Cloud provider for deepfreeze.
"""
return {Optional("provider", default="aws"): Any("aws")}
return {Optional("provider", default="aws"): Any("aws", "azure", "gcp")}


def rotate_by():
Expand Down
Loading
Loading