diff --git a/README.md b/README.md index b4bc96489..bd7cf7b55 100644 --- a/README.md +++ b/README.md @@ -681,6 +681,10 @@ NOTE There's [no Apple Silicon emulator available as yet](https://github.com/Azu NOTE Have not tested with the Windows Emulator, but it should work with analogous steps. ```bash +# magic connection string value that CosmosStoreConnector supports to +# a) avoid having to copy values around +# b) having to add code to trust cert and/or assent to elevation primpts +export EQUINOX_COSMOS_CONNECTION="TrustLocalEmulator=true" docker compose up equinox-cosmos -d bash docker-compose-cosmos.sh ``` diff --git a/docker-compose-cosmos.sh b/docker-compose-cosmos.sh index 9c86158d1..7e96e2ec8 100644 --- a/docker-compose-cosmos.sh +++ b/docker-compose-cosmos.sh @@ -2,9 +2,14 @@ set -e # exit on any non-zero exit code -tmpf=$(mktemp) -curl -k https://localhost:8081/_explorer/emulator.pem > $tmpf -sudo security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain $tmpf +if [ "$EQUINOX_COSMOS_CONNECTION" == "TrustLocalEmulator=true" ]; then + echo "Skipping downloading/trusting CosmosDb Emulator Certificate as \$EQUINOX_COSMOS_CONNECTION == \"TrustLocalEmulator=true\"" +else + echo "Downloading/trusting CosmosDb Emulator Certificate as \$EQUINOX_COSMOS_CONNECTION is not \"TrustLocalEmulator=true\"" + tmpf=$(mktemp) + curl -k https://localhost:8081/_explorer/emulator.pem > $tmpf + sudo security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain $tmpf +fi dotnet run --project tools/Equinox.Tool -- init cosmos dotnet run --project tools/Equinox.Tool -- init cosmos -c equinox-test-archive diff --git a/src/Equinox.CosmosStore/CosmosStore.fs b/src/Equinox.CosmosStore/CosmosStore.fs index 35ee6dc0c..87cf544f9 100644 --- a/src/Equinox.CosmosStore/CosmosStore.fs +++ b/src/Equinox.CosmosStore/CosmosStore.fs @@ -1102,6 +1102,8 @@ type internal StoreCategory<'event, 'state, 'req> module ConnectionString = + let [] DefaultEmulatorEndpoint = "https://localhost:8081" + let [] DefaultEmulatorAccountKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" let (|AccountEndpoint|) connectionString = match System.Data.Common.DbConnectionStringBuilder(ConnectionString = connectionString).TryGetValue "AccountEndpoint" with | true, (:? string as s) when not (String.IsNullOrEmpty s) -> s @@ -1143,19 +1145,31 @@ open Equinox.CosmosStore.Core open Microsoft.Azure.Cosmos open System +module Discovery = + + /// Use Equinox.CosmosStore.Core.ConnectionString.defaultEmulatorEndpoint and Equinox.CosmosStore.Core.ConnectionString.defaultEmulatorAccountKey; force bypassCertificateValidation. + let [] TrustLocalEmulatorConnectionString = "TrustLocalEmulator=true" + [] type Discovery = /// Separated Account Uri and Key (for interop with previous versions) | AccountUriAndKey of accountUri: Uri * key: string - /// Cosmos SDK Connection String + /// Cosmos SDK Connection String
+ /// NOTE the magic value TrustLocalEmulator=true overrides the mode to Discovery.TrustLocalEmulator
| ConnectionString of connectionString: string + /// Use Equinox.CosmosStore.Core.ConnectionString.DefaultEmulatorEndpoint and Equinox.CosmosStore.Core.ConnectionString.DefaultEmulatorAccountKey; force bypassCertificateValidation.
+ /// See https://learn.microsoft.com/en-us/azure/cosmos-db/local-emulator for details.
+ | TrustLocalEmulator member x.ToDiscoveryMode() = x |> function | Discovery.AccountUriAndKey (u, k) -> DiscoveryMode.AccountUriAndKey (string u, k) + | Discovery.ConnectionString Discovery.TrustLocalEmulatorConnectionString + | Discovery.TrustLocalEmulator -> DiscoveryMode.AccountUriAndKey (Core.ConnectionString.DefaultEmulatorEndpoint, Core.ConnectionString.DefaultEmulatorAccountKey) | Discovery.ConnectionString c -> DiscoveryMode.ConnectionString c /// Manages establishing a CosmosClient, which is used by CosmosStoreClient to read from the underlying Cosmos DB Container. type CosmosStoreConnector ( // CosmosDB endpoint/credentials specification. + // NOTE using the Default Local Emulator endpoint causes bypassCertificateValidation to default to true discovery: Discovery, // Timeout to apply to individual reads/write round-trips going to CosmosDB. CosmosDB Default: 1m. requestTimeout: TimeSpan, @@ -1168,16 +1182,20 @@ type CosmosStoreConnector [] ?mode: ConnectionMode, // consistency mode (default: use configuration specified for Database) [] ?defaultConsistencyLevel: ConsistencyLevel, - // Inhibits certificate verification when set to `true`. Default: false. + // Inhibits certificate verification when set to `true`. + // Defaults to `true` when targeting Local Emulator, otherwise `false`. [] ?bypassCertificateValidation: bool, [] ?customize: Action) = let discoveryMode = discovery.ToDiscoveryMode() + let bypassCertificateValidation = + defaultArg bypassCertificateValidation false + || (match discoveryMode with DiscoveryMode.AccountUriAndKey (ConnectionString.DefaultEmulatorEndpoint, _) -> true | _ -> false) let factory = let o = CosmosClientFactory.CreateDefaultOptions(requestTimeout, maxRetryAttemptsOnRateLimitedRequests, maxRetryWaitTimeOnRateLimitedRequests) mode |> Option.iter (fun x -> o.ConnectionMode <- x) defaultConsistencyLevel |> Option.iter (fun x -> o.ConsistencyLevel <- x) // https://github.com/Azure/azure-cosmos-dotnet-v3/blob/1ef6e399f114a0fd580272d4cdca86b9f8732cf3/Microsoft.Azure.Cosmos.Samples/Usage/HttpClientFactory/Program.cs#L96 - if defaultArg bypassCertificateValidation false then + if bypassCertificateValidation then let cb = System.Net.Http.HttpClientHandler.DangerousAcceptAnyServerCertificateValidator let ch = new System.Net.Http.HttpClientHandler(ServerCertificateCustomValidationCallback = cb) o.HttpClientFactory <- fun () -> new System.Net.Http.HttpClient(ch) diff --git a/tests/Equinox.CosmosStore.Integration/CosmosFixtures.fs b/tests/Equinox.CosmosStore.Integration/CosmosFixtures.fs index 322da61f6..54e0fd74e 100644 --- a/tests/Equinox.CosmosStore.Integration/CosmosFixtures.fs +++ b/tests/Equinox.CosmosStore.Integration/CosmosFixtures.fs @@ -83,7 +83,7 @@ let private archiveContainerId = tryRead "EQUINOX_COSMOS_CONTAINER_ARCHIVE" |> O // see https://github.com/jet/equinox#provisioning-cosmosdb for details of what's expected in terms of provisioned containers etc let discoverConnection () = match tryRead "EQUINOX_COSMOS_CONNECTION" with - | None -> "localDocDbSim", Discovery.AccountUriAndKey(Uri "https://localhost:8081", "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==") + | None -> "localDocDbSim", Discovery.TrustLocalEmulator | Some connectionString -> "EQUINOX_COSMOS_CONNECTION", Discovery.ConnectionString connectionString let createConnector (log: Serilog.ILogger) =