added new gemini creations

This commit is contained in:
2026-01-03 14:06:04 -05:00
parent 1dee6556c3
commit bbaa0daa7b
18 changed files with 449 additions and 0 deletions
@@ -0,0 +1,2 @@
TRUENAS_API_URL=http://your-truenas-ip/api/v2.0
TRUENAS_API_KEY=your-api-key-here
@@ -0,0 +1,83 @@
# TrueNAS Scale Storage Tracker
A CLI tool to list storage usage for each data pool in TrueNAS SCALE using the TrueNAS API.
## Features
- Lists all data pools with total size, used size, free size, and percentage used.
- Supports output in Text (default), JSON, and CSV formats.
- Configurable via `.env` file.
- Robust error handling.
## Prerequisites
- Python 3.x
- `requests`
- `python-dotenv`
## Installation
1. Clone the repository or navigate to the project directory.
2. Install dependencies:
```bash
pip install -r requirements.txt
```
## Configuration
1. Copy the `.env.template` to `.env`:
```bash
cp .env.template .env
```
2. Edit `.env` and provide your TrueNAS Scale credentials:
```ini
TRUENAS_API_URL=http://your-truenas-ip/api/v2.0
TRUENAS_API_KEY=your-api-key-here
```
* **TRUENAS_API_URL**: The URL to your TrueNAS API (e.g., `http://192.168.1.100/api/v2.0`).
* **TRUENAS_API_KEY**: An API Key generated from the TrueNAS web interface (Settings -> API Keys).
## Usage
Run the tool from the command line:
```bash
python3 src/tracker.py
```
### Options
* `--format`: Specify the output format (`text`, `json`, `csv`). Default is `text`.
### Examples
**Default Text Output:**
```bash
python3 src/tracker.py
```
**JSON Output:**
```bash
python3 src/tracker.py --format json
```
**CSV Output:**
```bash
python3 src/tracker.py --format csv
```
## Testing
Run the unit tests:
```bash
python3 -m unittest discover tests
```
## Project Structure
* `src/tracker.py`: Main application logic.
* `tests/test_tracker.py`: Unit tests.
* `.env.template`: Template for environment variables.
* `requirements.txt`: Python dependencies.
@@ -0,0 +1,3 @@
requests
python-dotenv
argparse
@@ -0,0 +1,109 @@
import os
import requests
import json
import csv
import sys
import argparse
try:
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
except ImportError:
print("Warning: python-dotenv not installed. Environment variables from .env file will not be loaded.", file=sys.stderr)
def get_headers():
TRUENAS_API_KEY = os.getenv("TRUENAS_API_KEY")
if not TRUENAS_API_KEY:
raise ValueError("TRUENAS_API_KEY not found in environment variables.")
return {
"Authorization": f"Bearer {TRUENAS_API_KEY}",
"Content-Type": "application/json"
}
def fetch_pool_data():
TRUENAS_API_URL = os.getenv("TRUENAS_API_URL")
if not TRUENAS_API_URL:
raise ValueError("TRUENAS_API_URL not found in environment variables.")
url = f"{TRUENAS_API_URL}/pool"
try:
response = requests.get(url, headers=get_headers(), timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error connecting to TrueNAS API: {e}", file=sys.stderr)
sys.exit(1)
def process_pool_data(raw_data):
processed_data = []
for pool in raw_data:
# Extract relevant metrics.
# Note: TrueNAS API structure varies slightly, but usually:
# 'name' is the pool name.
# 'size' is total size in bytes.
# 'allocated' is used size in bytes.
# 'free' is free size in bytes.
# Values might be nested differently depending on version,
# but typically top-level or under 'scan' or similar.
# We will assume standard keys based on typical v2.0 response.
# Adjusting based on common TrueNAS Scale API response for /pool
# It often returns a list where each item has 'name' and 'scan' or standard ZFS properties.
# Let's try to find the size properties. often found in 'used', 'available', 'size' (top level for ZFS properties usually don't exist directly, usually inside 'properties' or computed).
# Actually, /pool response usually has 'name' and 'size' (total), 'allocated' (used), 'free' (available).
# Fallback/Safe extraction
name = pool.get('name', 'Unknown')
# Some versions use 'size', 'allocated', 'free' directly
total_size = pool.get('size', 0)
used_size = pool.get('allocated', 0)
free_size = pool.get('free', 0)
# Calculate percentage if not present (sometimes it is not)
if total_size > 0:
percent_used = (used_size / total_size) * 100
else:
percent_used = 0.0
processed_data.append({
"pool_name": name,
"total_size_bytes": total_size,
"used_size_bytes": used_size,
"free_size_bytes": free_size,
"percent_used": round(percent_used, 2)
})
return processed_data
def format_output(data, output_format):
if output_format == 'json':
print(json.dumps(data, indent=4))
elif output_format == 'csv':
writer = csv.DictWriter(sys.stdout, fieldnames=["pool_name", "total_size_bytes", "used_size_bytes", "free_size_bytes", "percent_used"])
writer.writeheader()
writer.writerows(data)
else:
# Default text/human-readable
print(f"{'Pool Name':<20} {'Total Size':<15} {'Used Size':<15} {'Free Size':<15} {'% Used':<10}")
print("-" * 75)
for item in data:
print(f"{item['pool_name']:<20} {item['total_size_bytes']:<15} {item['used_size_bytes']:<15} {item['free_size_bytes']:<15} {item['percent_used']:<10}%")
def main():
parser = argparse.ArgumentParser(description="TrueNAS Scale Storage Tracker")
parser.add_argument("--format", choices=['json', 'csv', 'text'], default='text', help="Output format (json, csv, text)")
args = parser.parse_args()
try:
raw_data = fetch_pool_data()
processed_data = process_pool_data(raw_data)
format_output(processed_data, args.format)
except ValueError as e:
print(f"Configuration Error: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"An unexpected error occurred: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,71 @@
import unittest
from unittest.mock import patch, MagicMock
import sys
import os
# Add src to python path to import tracker
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src'))
import tracker
class TestTrueNASStorageTracker(unittest.TestCase):
def setUp(self):
self.mock_pool_data = [
{
"name": "tank",
"size": 10995116277760, # 10 TB
"allocated": 5497558138880, # 5 TB
"free": 5497558138880 # 5 TB
},
{
"name": "boot-pool",
"size": 256000000000, # 256 GB
"allocated": 64000000000, # 64 GB
"free": 192000000000 # 192 GB
}
]
@patch('tracker.requests.get')
@patch('tracker.os.getenv')
def test_fetch_pool_data_success(self, mock_getenv, mock_get):
# Configure mocks
mock_getenv.side_effect = lambda key: {
'TRUENAS_API_URL': 'http://mock-url',
'TRUENAS_API_KEY': 'mock-key'
}.get(key)
mock_response = MagicMock()
mock_response.json.return_value = self.mock_pool_data
mock_response.raise_for_status.return_value = None
mock_get.return_value = mock_response
# Call function
data = tracker.fetch_pool_data()
# Assertions
self.assertEqual(data, self.mock_pool_data)
mock_get.assert_called_once()
def test_process_pool_data(self):
processed = tracker.process_pool_data(self.mock_pool_data)
self.assertEqual(len(processed), 2)
# Check first pool
self.assertEqual(processed[0]['pool_name'], "tank")
self.assertEqual(processed[0]['total_size_bytes'], 10995116277760)
self.assertEqual(processed[0]['percent_used'], 50.0)
# Check second pool
self.assertEqual(processed[1]['pool_name'], "boot-pool")
self.assertEqual(processed[1]['percent_used'], 25.0)
@patch('tracker.os.getenv')
def test_missing_env_vars(self, mock_getenv):
mock_getenv.return_value = None
with self.assertRaises(ValueError):
tracker.fetch_pool_data()
if __name__ == '__main__':
unittest.main()