Pulumi
miniblue works with Pulumi using dynamic providers. No Azure credentials needed -- the providers make HTTP calls directly to miniblue's REST API.
Prerequisites
- miniblue running (
./bin/miniblueordocker run -p 4566:4566 moabukar/miniblue:latest) - Python 3.8+
- Pulumi CLI installed
Quick start
# Install Pulumi
curl -fsSL https://get.pulumi.com | sh
# Use local state (no Pulumi Cloud account needed)
export PULUMI_CONFIG_PASSPHRASE=""
pulumi login --local
# Clone and run the example
cd examples/pulumi-python
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Deploy
pulumi stack init dev
pulumi up --yes --skip-preview
How it works
The official pulumi-azure-native provider requires real Azure credentials and HTTPS endpoints. Instead, miniblue uses Pulumi dynamic providers -- lightweight Python classes that make HTTP calls to http://localhost:4566.
Each provider implements create() and delete() methods that call the miniblue REST API directly.
Example
The full working example is in examples/pulumi-python/. Here are the key providers:
ResourceGroup
import requests
import pulumi
from pulumi.dynamic import Resource, ResourceProvider, CreateResult
MINIBLUE_URL = "http://localhost:4566"
SUBSCRIPTION = "sub1"
class ResourceGroupProvider(ResourceProvider):
def create(self, props):
name = props["name"]
location = props.get("location", "eastus")
resp = requests.put(
f"{MINIBLUE_URL}/subscriptions/{SUBSCRIPTION}/resourcegroups/{name}",
json={"location": location, "tags": props.get("tags", {})},
)
resp.raise_for_status()
return CreateResult(id_=name, outs=dict(props))
def delete(self, id, props):
requests.delete(
f"{MINIBLUE_URL}/subscriptions/{SUBSCRIPTION}/resourcegroups/{id}"
)
class ResourceGroup(Resource):
def __init__(self, resource_name, name, location="eastus", tags=None, opts=None):
super().__init__(ResourceGroupProvider(), resource_name, {
"name": name, "location": location, "tags": tags or {},
}, opts)
KeyVault secret
class KeyVaultSecretProvider(ResourceProvider):
def create(self, props):
vault = props["vault"]
secret_name = props["secret_name"]
resp = requests.put(
f"{MINIBLUE_URL}/keyvault/{vault}/secrets/{secret_name}",
json={"value": props["value"]},
)
resp.raise_for_status()
return CreateResult(id_=f"{vault}/{secret_name}", outs=dict(props))
def delete(self, id, props):
requests.delete(
f"{MINIBLUE_URL}/keyvault/{props['vault']}/secrets/{props['secret_name']}"
)
class KeyVaultSecret(Resource):
def __init__(self, resource_name, vault, secret_name, value, opts=None):
super().__init__(KeyVaultSecretProvider(), resource_name, {
"vault": vault, "secret_name": secret_name, "value": value,
}, opts)
Blob storage
class BlobContainerProvider(ResourceProvider):
def create(self, props):
resp = requests.put(
f"{MINIBLUE_URL}/blob/{props['account']}/{props['container']}"
)
resp.raise_for_status()
return CreateResult(id_=f"{props['account']}/{props['container']}", outs=dict(props))
def delete(self, id, props):
requests.delete(
f"{MINIBLUE_URL}/blob/{props['account']}/{props['container']}"
)
class BlobProvider(ResourceProvider):
def create(self, props):
resp = requests.put(
f"{MINIBLUE_URL}/blob/{props['account']}/{props['container']}/{props['blob_name']}",
json=props.get("content", {}),
)
resp.raise_for_status()
return CreateResult(
id_=f"{props['account']}/{props['container']}/{props['blob_name']}",
outs=dict(props),
)
def delete(self, id, props):
requests.delete(
f"{MINIBLUE_URL}/blob/{props['account']}/{props['container']}/{props['blob_name']}"
)
CosmosDB document
class CosmosDBDocumentProvider(ResourceProvider):
def create(self, props):
resp = requests.post(
f"{MINIBLUE_URL}/cosmosdb/{props['account']}/dbs/{props['db']}/colls/{props['collection']}/docs",
json=props["document"],
)
resp.raise_for_status()
doc_id = props["document"].get("id", "unknown")
outs = dict(props)
outs["doc_id"] = doc_id
return CreateResult(
id_=f"{props['account']}/{props['db']}/{props['collection']}/{doc_id}",
outs=outs,
)
def delete(self, id, props):
doc_id = props.get("doc_id", props["document"].get("id"))
requests.delete(
f"{MINIBLUE_URL}/cosmosdb/{props['account']}/dbs/{props['db']}/colls/{props['collection']}/docs/{doc_id}"
)
Stack definition
rg = ResourceGroup("my-rg", name="pulumi-rg", location="eastus",
tags={"managed-by": "pulumi"})
secret = KeyVaultSecret("db-pass", vault="pulumi-vault",
secret_name="db-password", value="pulumi-secret-42",
opts=pulumi.ResourceOptions(depends_on=[rg]))
# Export outputs
pulumi.export("resource_group", "pulumi-rg")
pulumi.export("miniblue_url", MINIBLUE_URL)
Supported resources
| Provider class | miniblue API | Operations |
|---|---|---|
ResourceGroup |
ARM resource groups | Create, delete |
KeyVaultSecret |
Key Vault secrets | Create, delete |
BlobContainer |
Blob containers | Create, delete |
Blob |
Blob objects | Create, delete |
CosmosDBDocument |
Cosmos DB documents | Create, delete |
Deploy and destroy
# Deploy all resources
pulumi up --yes --skip-preview
# Verify with azlocal
azlocal group list
azlocal keyvault secret list --vault pulumi-vault
# Tear down
pulumi destroy --yes --skip-preview
Test with azlocal
After pulumi up, verify that Pulumi-created resources exist in miniblue:
# Resource group
azlocal group show --name pulumi-rg
# Key Vault secret
azlocal keyvault secret show --vault pulumi-vault --name db-password
# Blob
azlocal storage blob list --account pulumiaccount --container appdata
# CosmosDB document
azlocal cosmosdb doc show --account pulumiaccount --database appdb \
--collection users --id user1
Or verify with curl:
# Resource group
curl -s http://localhost:4566/subscriptions/sub1/resourcegroups/pulumi-rg | jq .
# Secret
curl -s http://localhost:4566/keyvault/pulumi-vault/secrets/db-password | jq .value
# Blob
curl -s http://localhost:4566/blob/pulumiaccount/appdata/config.json | jq .
Pulumi.yaml
name: miniblue-pulumi-python
runtime: python
description: Pulumi Python example for miniblue (local Azure emulator)