Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c357b94
feat(azure): full parity for Azure MySQL Flexible Server sub-resources
thzgajendra Jul 29, 2026
c582db0
feat(azure): full parity for Azure PostgreSQL Flexible Server sub-res…
thzgajendra Jul 29, 2026
8982917
feat(azure): full parity for Azure SQL server sub-resources
thzgajendra Jul 29, 2026
eed4e14
feat(gcp): full parity for Cloud SQL databases, users, certs and inst…
thzgajendra Jul 29, 2026
ab82573
feat(discovery): surface managed SQL servers in Resource Graph and Cl…
thzgajendra Jul 29, 2026
a6c4639
feat(cost): add managed relational-database rates
thzgajendra Jul 29, 2026
3584c7f
docs: document full managed-SQL parity (sub-resources, discovery, cost)
thzgajendra Jul 29, 2026
dba2651
Merge branch 'development' of https://github.com/stackshy/cloudemu in…
thzgajendra Jul 29, 2026
36d8af0
feat(sql): Cloud SQL tiers/flags catalogs + elastic-pool metrics
thzgajendra Jul 29, 2026
1b06c82
feat(azure): add SQL Managed Instance family to Azure SQL
thzgajendra Jul 29, 2026
06402c7
docs: document managed instances, tiers/flags and elastic-pool metrics
thzgajendra Jul 29, 2026
0922fa2
fix(sql): clone ManagedInstance tags on read; deterministic list orde…
thzgajendra Jul 29, 2026
a58f2d4
fix(azure): real update semantics + discoverable managed instances
thzgajendra Jul 29, 2026
36cb365
fix(azure): model Azure SQL database elastic-pool membership
thzgajendra Jul 29, 2026
69bd068
fix(gcp): real Cloud SQL read-replica and failover semantics
thzgajendra Jul 30, 2026
d84976b
fix(sql): review LOW items — determinism, validation, replica fidelit…
thzgajendra Jul 30, 2026
ba51c9d
fix(sql): address second review — MI lifecycle, in-place restore, val…
thzgajendra Jul 30, 2026
267ea84
fix(sql): address third review — output aliasing, config catalog, val…
thzgajendra Jul 30, 2026
26dda37
Merge branch 'development' of https://github.com/stackshy/cloudemu in…
thzgajendra Jul 30, 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
2 changes: 1 addition & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ all, _ := aws.ResourceDiscovery.ListAll(ctx)
// for every bucket, instance, VPC, subnet, security group, table, and function
```

The same field exists on Azure and GCP providers (`azure.ResourceDiscovery`, `gcp.ResourceDiscovery`). Internally, the engine reads from the existing Compute, Networking, Storage, Database, Serverless, Databricks, Kubernetes, and Relational Database drivers — any field that's nil is silently skipped, so partial test wirings work.
The same field exists on Azure and GCP providers (`azure.ResourceDiscovery`, `gcp.ResourceDiscovery`). Internally, the engine reads from the Compute, Networking, Storage, Database, Serverless, Databricks, Kubernetes, and Relational Database drivers — any field that's nil is silently skipped, so partial test wirings work. Managed relational servers (AWS RDS, Azure SQL, the MySQL/PostgreSQL Flexible Servers, and Cloud SQL) surface through their cloud's inventory type strings.

### Engine API

Expand Down
57 changes: 49 additions & 8 deletions docs/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -1236,12 +1236,53 @@ A proxy has a single implicit `default` target group; targets are RDS instances
- `Metadata`: `DescribeDBEngineVersions`, `DescribeOrderableDBInstanceOptions` — representative per-engine catalogs (2).
- `Tagging`: `AddTagsToResource`, `RemoveTagsFromResource`, `ListTagsForResource` — addressed by resource ARN over the tag-bearing stores (instances, clusters, instance/cluster snapshots) (3).

**Total: 21 core operations + 58 optional across 12 type-asserted capability
interfaces (`SubnetGroups`, `ParameterGroups`, `OptionGroups`, `ReadReplicas`,
`AdvancedRestore`, `DBProxies`, `EventSubscriptions`, `ClusterEndpoints`,
`ClusterFailover`, `GlobalClusters`, `Metadata`, `Tagging`).** AWS RDS wires all
of them; other clouds implement the subset that maps to a real resource and
answer `InvalidAction` otherwise.
### Azure & GCP managed-SQL native sub-resources (optional capabilities)

Each managed-SQL service also exposes its cloud's own child resources and
actions. Like the RDS capabilities above, these are kept out of the core
`RelationalDB` interface and discovered by type assertion, so a driver only
answers for the resources its cloud actually has; others return `InvalidAction`.
The server handlers reach them the way real SDK clients do (ARM sub-resource
routes for Azure, sqladmin sub-collections for Cloud SQL), and the mocks
cascade-delete children when their parent server/instance is deleted.

| Capability | Operations | Implemented by |
|-----------|-----------|----------------|
| `Databases` | Create / Get / List / Delete | `mysqlflex`, `postgresflex`, `cloudsql` |
| `FirewallRules` | Create / Get / List / Delete | `mysqlflex`, `postgresflex`, `azuresql` |
| `Configurations` | Set / Get / List (server parameters) | `mysqlflex`, `postgresflex` |
| `Failover` | `FailoverInstance` | `mysqlflex`, `cloudsql` |
| `VNetRules` | Create / Get / List / Delete | `azuresql` |
| `ElasticPools` | Create / Get / List / Delete | `azuresql` |
| `FailoverGroups` | Create / Get / List / Delete / Failover | `azuresql` |
| `AADAdmins` | Set / Get / List / Delete | `azuresql` |
| `Users` | Create / Get / List / Update / Delete | `cloudsql` |
| `SslCerts` | Create / Get / List / Delete | `cloudsql` |
| `Clonable` | `CloneInstance` | `cloudsql` |
| `ReplicaPromotion` | `PromoteReplica` | `cloudsql` |
| `ManagedInstances` | managed-instance CRUD + Start/Stop/Failover, managed-database CRUD/List | `azuresql` |

Cloud SQL also serves the `startReplica`/`stopReplica` instance actions (mapped
onto Start/Stop) and the static `tiers` (`/v1/projects/{p}/tiers`) and `flags`
(`/v1/flags`) reference catalogs. Azure SQL adds the SQL Managed Instance family
(`Microsoft.Sql/managedInstances` + managed databases) alongside the
single-database logical server. Managed relational servers surface in
cross-service discovery (Azure Resource Graph as `microsoft.sql/servers`,
`microsoft.dbformysql/flexibleservers`,
`microsoft.dbforpostgresql/flexibleservers`; GCP Cloud Asset as
`sqladmin.googleapis.com/Instance`), are billed per instance-hour via the
`relationaldb:*` cost catalog, and emit their cloud's monitoring metrics
(including the `Microsoft.Sql/servers/elasticpools` pool namespace).

**Total: 21 core operations + 109 optional across 25 type-asserted capability
interfaces** — the 12 RDS-oriented ones (`SubnetGroups`, `ParameterGroups`,
`OptionGroups`, `ReadReplicas`, `AdvancedRestore`, `DBProxies`,
`EventSubscriptions`, `ClusterEndpoints`, `ClusterFailover`, `GlobalClusters`,
`Metadata`, `Tagging`) plus the 13 Azure/GCP managed-SQL ones (`Databases`,
`FirewallRules`, `Configurations`, `Failover`, `VNetRules`, `ElasticPools`,
`FailoverGroups`, `AADAdmins`, `Users`, `SslCerts`, `Clonable`,
`ReplicaPromotion`, `ManagedInstances`). Each cloud implements the subset that
maps to a real resource and answers `InvalidAction` otherwise.

---

Expand Down Expand Up @@ -1823,7 +1864,7 @@ still sees success.
| Notification | 8 |
| Container Registry | 14 |
| Event Bus | 15 |
| Relational Database | 21 (+58 optional) |
| Relational Database | 21 (+109 optional) |
| Kubernetes — AWS EKS (control plane) | 21 |
| Kubernetes — Azure AKS (control plane) | 18 |
| Kubernetes — GCP GKE (control plane) | 26 |
Expand All @@ -1837,7 +1878,7 @@ still sees success.
| Machine Learning — GCP Vertex AI (Go API/driver) | 128 |
| AI Search — Azure AI Search (control + data plane) | 53 |
| Container Orchestration — AWS ECS | 37 |
| **Grand Total** | **1084** (+67 optional) |
| **Grand Total** | **1084** (+116 optional) |

Optional operations are capabilities a driver may implement but is not required
to; see the sections marked "optional capability". They are counted separately
Expand Down
84 changes: 77 additions & 7 deletions providers/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/stackshy/cloudemu/v2/providers/azure/tablestorage"
"github.com/stackshy/cloudemu/v2/providers/azure/virtualmachines"
"github.com/stackshy/cloudemu/v2/providers/azure/vnet"
rdsdriver "github.com/stackshy/cloudemu/v2/services/relationaldb/driver"
"github.com/stackshy/cloudemu/v2/services/resourcediscovery"
)

Expand Down Expand Up @@ -153,15 +154,84 @@ func New(opts ...config.Option) *Provider {
p.ResourceDiscovery = resourcediscovery.New(
resourcediscovery.ProviderAzure, o.AccountID, o.Region,
&resourcediscovery.Drivers{
Compute: p.VirtualMachines,
Networking: p.VNet,
Storage: p.BlobStorage,
Database: p.CosmosDB,
Serverless: p.Functions,
Databricks: p.Databricks,
Kubernetes: aksDiscovery{p.AKS},
Compute: p.VirtualMachines,
Networking: p.VNet,
Storage: p.BlobStorage,
Database: p.CosmosDB,
Serverless: p.Functions,
Databricks: p.Databricks,
Kubernetes: aksDiscovery{p.AKS},
RelationalDB: sqlDiscovery{sql: p.SQL, mysql: p.MySQLFlex, pg: p.PostgresFlex},
},
)

return p
}

// sqlDiscovery adapts the Azure relational mocks (SQL logical servers plus
// MySQL/PostgreSQL Flexible Servers) to the resourcediscovery
// RelationalDatabases capability, so they surface in Resource Graph.
type sqlDiscovery struct {
sql *azuresql.Mock
mysql *mysqlflex.Mock
pg *postgresflex.Mock
}

func (d sqlDiscovery) DiscoverDatabases(
ctx context.Context,
) ([]resourcediscovery.DiscoveredDatabase, error) {
clusters, err := d.sql.DescribeClusters(ctx, nil)
if err != nil {
return nil, err
}

out := make([]resourcediscovery.DiscoveredDatabase, 0, len(clusters))

for i := range clusters {
out = append(out, resourcediscovery.DiscoveredDatabase{
Name: clusters[i].ID, Type: resourcediscovery.TypeSQLServer,
ARN: clusters[i].ARN, Tags: clusters[i].Tags,
})
}

myInsts, err := d.mysql.DescribeInstances(ctx, nil)
if err != nil {
return nil, err
}

out = appendFlexServers(out, myInsts, resourcediscovery.TypeMySQLFlex)

pgInsts, err := d.pg.DescribeInstances(ctx, nil)
if err != nil {
return nil, err
}

out = appendFlexServers(out, pgInsts, resourcediscovery.TypePostgresFlex)

mis, err := d.sql.ListManagedInstances(ctx)
if err != nil {
return nil, err
}

for i := range mis {
out = append(out, resourcediscovery.DiscoveredDatabase{
Name: mis[i].Name, Type: resourcediscovery.TypeManagedInstance,
Region: mis[i].Location, ARN: mis[i].ARN, Tags: mis[i].Tags,
})
}

return out, nil
}

func appendFlexServers(
out []resourcediscovery.DiscoveredDatabase, insts []rdsdriver.Instance, typ string,
) []resourcediscovery.DiscoveredDatabase {
for i := range insts {
out = append(out, resourcediscovery.DiscoveredDatabase{
Name: insts[i].ID, Type: typ, Region: insts[i].AvailabilityZone,
ARN: insts[i].ARN, Tags: insts[i].Tags,
})
}

return out
}
Loading
Loading