Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project Overview

PlanetScale Discovery Tools analyze PostgreSQL databases and cloud infrastructure (AWS, GCP, Supabase, Heroku) to assess migration complexity to PlanetScale. The tool collects metadata only -- never actual customer data. It runs locally in customer environments with minimal permissions.
PlanetScale Discovery Tools analyze PostgreSQL and MySQL databases and cloud infrastructure (AWS, GCP, Supabase, Heroku, Neon) to assess migration complexity to PlanetScale. The tool collects metadata only -- never actual customer data. It runs locally in customer environments with minimal permissions.

## Project Structure

Expand Down Expand Up @@ -34,6 +34,8 @@ planetscale_discovery/
gcp_analyzer.py # Cloud SQL, AlloyDB, VPC networks
supabase_analyzer.py # Supabase managed PostgreSQL
heroku_analyzer.py # Heroku Postgres add-ons
neon_analyzer.py # Neon serverless Postgres projects, branches, endpoints
database/mysql_analyzers/ # MySQL/Vitess analyzers (config, schema, performance, replication, security, features)
tests/
conftest.py # Shared pytest fixtures
fixtures/ # Mock data (database_responses.py, aws_responses.py)
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.0] - 2026-05-26

### Added

- **MySQL and Vitess database discovery** alongside PostgreSQL, with engine selection via `--engine`. MySQL engine recognition in AWS and GCP cloud discovery (RDS MySQL, Aurora MySQL, Cloud SQL MySQL) ([guide](docs/mysql.md))
- **Neon** cloud discovery provider for projects, branches, and compute endpoints ([guide](docs/providers/neon.md))

### Fixed

- AWS Aurora: exclude `rdsadmin` from the database catalog to avoid permission errors
- Skip per-backend temporary schemas during schema analysis on clusters with very large `pg_namespace`

## [1.1.0] - 2026-03-09

Initial public release of PlanetScale Discovery Tools — a comprehensive suite for analyzing PostgreSQL databases and cloud infrastructure environments to support migration planning.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ Both tools can be used independently or together for a complete environment asse

```bash
# Download and extract the release
tar -xzf ps-discovery-1.1.0.tar.gz
cd ps-discovery-1.1.0
tar -xzf ps-discovery-1.2.0.tar.gz
cd ps-discovery-1.2.0

# Run the interactive setup script
./setup.sh
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.0
1.2.0
33 changes: 33 additions & 0 deletions docs/providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,27 @@ providers:
discover_all: true
```

### [Neon](neon.md)
Neon serverless PostgreSQL project discovery.

**Key Features:**
- Project, branch, and compute endpoint inventory across all accessible orgs
- Branch topology (default + child branches with `parent_id` lineage, logical_size, physical_size)
- Compute endpoint details (type, autoscaling min/max CU, suspend timeout, current state)
- Connection pooling configuration (`pooler_enabled` + `pooler_mode`)
- Compute spec enrichment from CU size (vCPU, RAM, estimated max_connections)
- Per-org plan tier classification (free, launch, scale, business)
- Auto-discovers orgs when `--neon-org-id` is not supplied

**Quick Start:**
```yaml
providers:
neon:
enabled: true
api_key: "your-neon-api-key"
discover_all: true
```

## Common Setup Patterns

### Authentication
Expand All @@ -96,6 +117,7 @@ Each provider has different authentication methods:
- **GCP**: Service account keys, application default credentials
- **Supabase**: Personal access tokens, service role keys
- **Heroku**: Platform API keys (from Dashboard or CLI)
- **Neon**: Personal, organization, or project-scoped API keys (from the Neon console)

See individual provider documentation for detailed authentication setup.

Expand Down Expand Up @@ -125,6 +147,10 @@ providers:
enabled: true
api_key: "your-heroku-api-key"

neon:
enabled: true
api_key: "your-neon-api-key"

output:
output_dir: ./multi_cloud_discovery
```
Expand Down Expand Up @@ -157,6 +183,13 @@ export HEROKU_API_KEY=your-heroku-api-key
export HEROKU_TARGET_APP=my-production-app # Optional: target specific app
```

**Neon:**
```bash
export NEON_API_KEY=your-neon-api-key
export NEON_TARGET_PROJECT=ep-broad-frost-12345 # Optional: target specific project
export NEON_ORG_ID=org-xxxxxxxxxxxxxxxxx # Optional: scope to a specific org
```

### Discovery Scope

Control what resources are discovered:
Expand Down
2 changes: 1 addition & 1 deletion planetscale_discovery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
across PostgreSQL databases and cloud infrastructure.
"""

__version__ = "1.1.0"
__version__ = "1.2.0"
__author__ = "PlanetScale"
2 changes: 1 addition & 1 deletion planetscale_discovery/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def create_main_parser() -> argparse.ArgumentParser:
)
config_parser.add_argument(
"--providers",
help="Comma-separated list of cloud providers to include (aws,gcp,supabase,heroku). If not specified, only database and output sections are generated.",
help="Comma-separated list of cloud providers to include (aws,gcp,supabase,heroku,neon). If not specified, only database and output sections are generated.",
default=None,
)
config_parser.add_argument(
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"

[project]
name = "planetscale-discovery-tools"
version = "1.1.0"
version = "1.2.0"
description = "Comprehensive database and cloud infrastructure discovery tools"
authors = [{name = "PlanetScale", email = "engineering@planetscale.com"}]
readme = "README.md"
license = {text = "Apache-2.0"}
requires-python = ">=3.9"
keywords = ["postgresql", "mysql", "database", "discovery", "cloud", "aws", "gcp", "supabase", "heroku"]
keywords = ["postgresql", "mysql", "database", "discovery", "cloud", "aws", "gcp", "supabase", "heroku", "neon"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: System Administrators",
Expand Down Expand Up @@ -53,6 +53,9 @@ supabase = [
heroku = [
"requests>=2.32.5",
]
neon = [
"requests>=2.32.5",
]
all = [
"psycopg2-binary>=2.9",
"PyMySQL>=1.1.0",
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

setup(
name="planetscale-discovery-tools",
version="1.1.0",
version="1.2.0",
description="Comprehensive database and cloud infrastructure discovery tools",
long_description=README,
long_description_content_type="text/markdown",
Expand Down
30 changes: 15 additions & 15 deletions tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,20 +524,20 @@ def test_header_present(self):
"""Test that header with timestamp and version is generated."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": None,
"cloud_results": None,
}
content = self._write_and_read(results)
assert "# Discovery Summary" in content
assert "1.1.0" in content
assert "1.2.0" in content
assert "2025-01-15" in content

def test_database_section_with_full_data(self):
"""Test database section with realistic analysis data."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": {
"connection_info": {
"current_database": "production_db",
Expand Down Expand Up @@ -594,7 +594,7 @@ def test_cloud_section_with_aws(self):
"""Test cloud section with AWS provider data."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": None,
"cloud_results": {
"providers": {
Expand Down Expand Up @@ -667,7 +667,7 @@ def test_cloud_section_with_gcp(self):
"""Test cloud section with GCP provider data."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": None,
"cloud_results": {
"providers": {
Expand Down Expand Up @@ -721,7 +721,7 @@ def test_cloud_section_with_heroku(self):
"""Test cloud section with Heroku provider data."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": None,
"cloud_results": {
"providers": {
Expand Down Expand Up @@ -752,7 +752,7 @@ def test_cloud_section_with_supabase(self):
"""Test cloud section with Supabase provider data."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": None,
"cloud_results": {
"providers": {
Expand All @@ -777,7 +777,7 @@ def test_gaps_section(self):
"""Test that analysis gaps are included in the output."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": {
"connection_info": {},
"analysis_results": {},
Expand Down Expand Up @@ -808,7 +808,7 @@ def test_errors_section(self):
"""Test that cloud errors are included in the output."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": None,
"cloud_results": {
"providers": {},
Expand All @@ -828,7 +828,7 @@ def test_combined_database_and_cloud(self):
"""Test report with both database and cloud results."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": {
"connection_info": {"current_database": "mydb"},
"analysis_results": {
Expand Down Expand Up @@ -863,7 +863,7 @@ def test_no_results(self):
"""Test report generation with no database or cloud results."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": None,
"cloud_results": None,
}
Expand All @@ -877,7 +877,7 @@ def test_no_migration_language(self):
"""Verify no migration-specific language in output."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": {
"connection_info": {"current_database": "mydb"},
"analysis_results": {
Expand Down Expand Up @@ -917,7 +917,7 @@ def test_replication_primary_with_replicas(self):
"""Test replication status for primary with replicas."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": {
"connection_info": {},
"analysis_results": {
Expand All @@ -939,7 +939,7 @@ def test_replication_standby(self):
"""Test replication status for standby."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": {
"connection_info": {},
"analysis_results": {
Expand All @@ -960,7 +960,7 @@ def test_overview_table(self):
"""Test the overview module status table."""
results = {
"timestamp": "2025-01-15T10:00:00Z",
"discovery_version": "1.1.0",
"discovery_version": "1.2.0",
"database_results": {
"connection_info": {},
"analysis_results": {},
Expand Down
Loading