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
65 changes: 65 additions & 0 deletions samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,71 @@ else
exit 1
fi

# Make sure the CosmosDB account runs the MongoDB server version of MONGODB_API_VERSION. The Azure CLI sends the
# --server-version of az cosmosdb create and az cosmosdb update as a ServerVersion property, which the service
# ignores, so the account keeps the default server version, 3.6, which the MongoDB driver of the web app refuses.
# The script sets the serverVersion property itself, with the API version the Azure CLI uses, and waits until the
# account reports the new server version, which takes about a minute. The MongoDB endpoint can go on serving the old
# version for a while after that: the web app starts without the database and connects once the endpoint switched.
echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account MongoDB server version in the [$RESOURCE_GROUP_NAME] resource group..."
SERVER_VERSION=$(az cosmosdb show \
--name $COSMOSDB_ACCOUNT_NAME \
--resource-group $RESOURCE_GROUP_NAME \
--query "apiProperties.serverVersion" \
--output tsv \
--only-show-errors)

if [ -n "$SERVER_VERSION" ]; then
echo "MongoDB server version retrieved successfully: $SERVER_VERSION"
else
echo "Failed to retrieve MongoDB server version."
exit 1
fi

if [[ "$SERVER_VERSION" != "$MONGODB_API_VERSION" ]]; then
echo "Upgrading [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]..."
az rest \
--method patch \
--url "${COSMOSDB_ACCOUNT_ID}?api-version=2026-03-15" \
--body "{\"properties\": {\"apiProperties\": {\"serverVersion\": \"$MONGODB_API_VERSION\"}}}" \
--only-show-errors 1>/dev/null

if [ $? -ne 0 ]; then
echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]"
exit 1
fi

for ATTEMPT in $(seq 1 60); do
SERVER_VERSION=$(az cosmosdb show \
--name $COSMOSDB_ACCOUNT_NAME \
--resource-group $RESOURCE_GROUP_NAME \
--query "apiProperties.serverVersion" \
--output tsv \
--only-show-errors)
PROVISIONING_STATE=$(az cosmosdb show \
--name $COSMOSDB_ACCOUNT_NAME \
--resource-group $RESOURCE_GROUP_NAME \
--query "provisioningState" \
--output tsv \
--only-show-errors)

if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then
break
fi
echo "Waiting for the upgrade of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account (attempt $ATTEMPT of 60)..."
sleep 10
done

if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then
echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully upgraded to MongoDB server version [$MONGODB_API_VERSION]"
else
echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account: it still runs MongoDB server version [$SERVER_VERSION]"
exit 1
fi
else
echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already runs MongoDB server version [$MONGODB_API_VERSION]"
fi

# Retrieve document endpoint
echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account document endpoint in the [$RESOURCE_GROUP_NAME] resource group..."
DOCUMENT_ENDPOINT=$(az cosmosdb show \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ public async Task<IActionResult> OnPostAsync(string id, CancellationToken cancel
{
if (!string.IsNullOrWhiteSpace(id))
{
await store.DeleteAsync(id, cancellationToken);
logger.LogInformation("Activity deleted: {Id}", id);
TempData["Flash"] = "Activity deleted.";
try
{
await store.DeleteAsync(id, cancellationToken);
logger.LogInformation("Activity deleted: {Id}", id);
TempData["Flash"] = "Activity deleted.";
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
logger.LogError(ex, "Could not delete the activity");
TempData["Flash"] = IndexModel.DatabaseUnavailable;
}
}

return RedirectToPage("/Index");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ namespace VacationPlanner.Pages;

public class IndexModel(IActivityStore store, ILogger<IndexModel> logger) : PageModel
{
/// <summary>Shown instead of an error page while the database is not reachable; the next request tries again.</summary>
internal const string DatabaseUnavailable = "The database is not available. Try again in a minute.";

public IReadOnlyList<Activity> Activities { get; private set; } = [];

/// <summary>Flash messages set by the previous request (the equivalent of Flask's <c>flash()</c>).</summary>
Expand All @@ -20,7 +23,15 @@ public class IndexModel(IActivityStore store, ILogger<IndexModel> logger) : Page

public async Task OnGetAsync(CancellationToken cancellationToken)
{
Activities = await store.ListAsync(cancellationToken);
try
{
Activities = await store.ListAsync(cancellationToken);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
logger.LogError(ex, "Could not read the activities");
TempData["Flash"] = DatabaseUnavailable;
}
}

public async Task<IActionResult> OnPostAsync(CancellationToken cancellationToken)
Expand All @@ -29,17 +40,25 @@ public async Task<IActionResult> OnPostAsync(CancellationToken cancellationToken
var id = RowId?.Trim();
if (!string.IsNullOrEmpty(text))
{
if (!string.IsNullOrEmpty(id))
try
{
await store.UpdateAsync(id, text, cancellationToken);
logger.LogInformation("Activity updated: {Id}", id);
TempData["Flash"] = "Activity updated!";
if (!string.IsNullOrEmpty(id))
{
await store.UpdateAsync(id, text, cancellationToken);
logger.LogInformation("Activity updated: {Id}", id);
TempData["Flash"] = "Activity updated!";
}
else
{
await store.AddAsync(text, cancellationToken);
logger.LogInformation("Activity added: {Activity}", text);
TempData["Flash"] = "Activity added!";
}
}
else
catch (Exception ex) when (ex is not OperationCanceledException)
{
await store.AddAsync(text, cancellationToken);
logger.LogInformation("Activity added: {Activity}", text);
TempData["Flash"] = "Activity added!";
logger.LogError(ex, "Could not save the activity");
TempData["Flash"] = DatabaseUnavailable;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
namespace VacationPlanner.Services;

/// <summary>
/// Runs <see cref="IActivityStore.InitializeAsync"/> at startup with a bounded retry, so the app fails fast
/// (and the container exits) when the backing service never becomes reachable.
/// Runs <see cref="IActivityStore.InitializeAsync"/> in the background and retries it until it succeeds, so the app
/// starts even when the database is not reachable yet, for example while Azure Cosmos DB for MongoDB upgrades the
/// server version of the account. Until then, the pages show an error and <c>/health</c> answers 503.
/// </summary>
public sealed class StoreInitializer(
IActivityStore store,
ILogger<StoreInitializer> logger,
int attempts = 1,
TimeSpan delay = default) : IHostedService
TimeSpan delay = default) : BackgroundService
{
public async Task StartAsync(CancellationToken cancellationToken)
private readonly TimeSpan _delay = delay > TimeSpan.Zero ? delay : TimeSpan.FromSeconds(15);

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
for (var attempt = 1; ; attempt++)
try
{
try
{
await store.InitializeAsync(cancellationToken);
logger.LogInformation("Activity store initialized after {Attempts} attempt(s).", attempt);
return;
}
catch (Exception ex) when (attempt < attempts && !cancellationToken.IsCancellationRequested)
for (var attempt = 1; !stoppingToken.IsCancellationRequested; attempt++)
{
logger.LogWarning(ex, "Activity store not ready (attempt {Attempt}/{Attempts}); retrying in {Delay}s.",
attempt, attempts, delay.TotalSeconds);
await Task.Delay(delay, cancellationToken);
try
{
await store.InitializeAsync(stoppingToken);
logger.LogInformation("Activity store initialized after {Attempts} attempt(s).", attempt);
return;
}
catch (Exception ex) when (!stoppingToken.IsCancellationRequested)
{
logger.LogWarning(ex, "Activity store not ready (attempt {Attempt}); retrying in {Delay}s.",
attempt, _delay.TotalSeconds);
await Task.Delay(_delay, stoppingToken);
}
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
// The app is stopping before the store was initialized: nothing left to do.
}
}

public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
65 changes: 65 additions & 0 deletions samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,71 @@ else
exit 1
fi

# Make sure the CosmosDB account runs the MongoDB server version of MONGODB_API_VERSION. The Azure CLI sends the
# --server-version of az cosmosdb create and az cosmosdb update as a ServerVersion property, which the service
# ignores, so the account keeps the default server version, 3.6, which the MongoDB driver of the web app refuses.
# The script sets the serverVersion property itself, with the API version the Azure CLI uses, and waits until the
# account reports the new server version, which takes about a minute. The MongoDB endpoint can go on serving the old
# version for a while after that: the web app starts without the database and connects once the endpoint switched.
echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account MongoDB server version in the [$RESOURCE_GROUP_NAME] resource group..."
SERVER_VERSION=$(az cosmosdb show \
--name $COSMOSDB_ACCOUNT_NAME \
--resource-group $RESOURCE_GROUP_NAME \
--query "apiProperties.serverVersion" \
--output tsv \
--only-show-errors)

if [ -n "$SERVER_VERSION" ]; then
echo "MongoDB server version retrieved successfully: $SERVER_VERSION"
else
echo "Failed to retrieve MongoDB server version."
exit 1
fi

if [[ "$SERVER_VERSION" != "$MONGODB_API_VERSION" ]]; then
echo "Upgrading [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]..."
az rest \
--method patch \
--url "${COSMOSDB_ACCOUNT_ID}?api-version=2026-03-15" \
--body "{\"properties\": {\"apiProperties\": {\"serverVersion\": \"$MONGODB_API_VERSION\"}}}" \
--only-show-errors 1>/dev/null

if [ $? -ne 0 ]; then
echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]"
exit 1
fi

for ATTEMPT in $(seq 1 60); do
SERVER_VERSION=$(az cosmosdb show \
--name $COSMOSDB_ACCOUNT_NAME \
--resource-group $RESOURCE_GROUP_NAME \
--query "apiProperties.serverVersion" \
--output tsv \
--only-show-errors)
PROVISIONING_STATE=$(az cosmosdb show \
--name $COSMOSDB_ACCOUNT_NAME \
--resource-group $RESOURCE_GROUP_NAME \
--query "provisioningState" \
--output tsv \
--only-show-errors)

if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then
break
fi
echo "Waiting for the upgrade of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account (attempt $ATTEMPT of 60)..."
sleep 10
done

if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then
echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully upgraded to MongoDB server version [$MONGODB_API_VERSION]"
else
echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account: it still runs MongoDB server version [$SERVER_VERSION]"
exit 1
fi
else
echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already runs MongoDB server version [$MONGODB_API_VERSION]"
fi

# Retrieve document endpoint
echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account document endpoint in the [$RESOURCE_GROUP_NAME] resource group..."
DOCUMENT_ENDPOINT=$(az cosmosdb show \
Expand Down
Loading
Loading