diff --git a/.github/workflows/itests.yml b/.github/workflows/itests.yml index 2c97343ed..00121c9f5 100644 --- a/.github/workflows/itests.yml +++ b/.github/workflows/itests.yml @@ -43,8 +43,8 @@ jobs: GOARCH: amd64 GOPROXY: https://proxy.golang.org DAPR_CLI_VER: 1.13.0 - DAPR_RUNTIME_VER: 1.13.0 - DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/release-1.13/install/install.sh + DAPR_RUNTIME_VER: 1.13.2 + DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/release-1.12/install/install.sh DAPR_CLI_REF: '' steps: - name: Set up Dapr CLI diff --git a/all.sln b/all.sln index 0b95478f3..228047852 100644 --- a/all.sln +++ b/all.sln @@ -32,6 +32,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1BD1276E-D28A-45EA-89B1-6AD48471500D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + README.md = README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.AspNetCore.Test", "test\Dapr.Actors.AspNetCore.Test\Dapr.Actors.AspNetCore.Test.csproj", "{9C1D6ABA-5EDE-4FA0-A8A9-0AB98CB74737}" @@ -347,16 +348,3 @@ Global SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} EndGlobalSection EndGlobal -8-446B-AECD-DCC2CC871F73} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} - EndGlobalSection -EndGlobal -C991940} = {DD020B34-460F-455F-8D17-CF4A949F100B} - {C74FBA78-13E8-407F-A173-4555AEE41FF3} = {A7F41094-8648-446B-AECD-DCC2CC871F73} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} - EndGlobalSection -EndGlobal diff --git a/examples/Actor/DemoActor/DemoActor.csproj b/examples/Actor/DemoActor/DemoActor.csproj index 1ee37fdbe..24a42ee0e 100644 --- a/examples/Actor/DemoActor/DemoActor.csproj +++ b/examples/Actor/DemoActor/DemoActor.csproj @@ -1,13 +1,24 @@  - - net6 - - - - - - - + + net6 + + + + true + true + demo-actor + + + + + + + + + + + + diff --git a/examples/Actor/DemoActor/Startup.cs b/examples/Actor/DemoActor/Startup.cs index c04dfdcba..da2b9e764 100644 --- a/examples/Actor/DemoActor/Startup.cs +++ b/examples/Actor/DemoActor/Startup.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/examples/Actor/DemoActor/demo-actor.yaml b/examples/Actor/DemoActor/demo-actor.yaml new file mode 100644 index 000000000..99a8abd34 --- /dev/null +++ b/examples/Actor/DemoActor/demo-actor.yaml @@ -0,0 +1,67 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.in-memory + version: v1 + metadata: + - name: actorStateStore + value: "true" +--- +kind: Service +apiVersion: v1 +metadata: + name: demoactor + labels: + app: demoactor +spec: + selector: + app: demoactor + ports: + - name: app-port + protocol: TCP + port: 5010 + targetPort: app-port + - name: dapr-http + protocol: TCP + port: 3500 + targetPort: 3500 + type: LoadBalancer +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demoactor + labels: + app: demoactor +spec: + replicas: 1 + selector: + matchLabels: + app: demoactor + template: + metadata: + labels: + app: demoactor + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "demoactor" + dapr.io/app-port: "5010" + dapr.io/enable-api-logging: "true" + dapr.io/sidecar-listen-addresses: "0.0.0.0" + spec: + containers: + - name: demoactor + # image: /demo-actor:latest + image: demo-actor:latest + # if you are using docker desktop, you can use imagePullPolicy: Never to use local image + imagePullPolicy: Never + env: + - name: APP_PORT + value: "5010" + - name: ASPNETCORE_URLS + value: "http://+:5010" + ports: + - name: app-port + containerPort: 5010 diff --git a/examples/Actor/README.md b/examples/Actor/README.md index ddc42cecf..a7bb46c03 100644 --- a/examples/Actor/README.md +++ b/examples/Actor/README.md @@ -80,3 +80,80 @@ On Windows: ```sh curl -X POST http://127.0.0.1:3500/v1.0/actors/DemoActor/abc/method/GetData ``` + +### Build and push Docker image +You can build the docker image of `DemoActor` service by running the following commands in the `DemoActor` project directory: + +``` Bash +dotnet publish --os linux --arch x64 /t:PublishContainer -p ContainerImageTags='"latest"' --self-contained +``` + +The build produce and image with tag `demo-actor:latest` and load it in the local registry. +Now the image can be pushed to your remote Docker registry by running the following commands: + +``` Bash +# Replace with your Docker registry +docker tag demo-actor:latest /demo-actor:latest + +# Push the image to your Docker registry +docker push /demo-actor:latest +``` + +### Deploy the Actor service to Kubernetes +#### Prerequisites +- A Kubernetes cluster with `kubectl` configured to access it. +- Dapr v1.13+ installed on the Kubernetes cluster. Follow the instructions [here](https://docs.dapr.io/getting-started/install-dapr-kubernetes/). +- A Docker registry where you pushed the `DemoActor` image. + +#### Deploy the Actor service +For quick deployment you can install dapr in dev mode using the following command: + +``` Bash +dapr init -k --dev +``` + +To deploy the `DemoActor` service to Kubernetes, you can use the provided Kubernetes manifest file `demo-actor.yaml` in the `DemoActor` project directory. +Before applying the manifest file, replace the image name in the manifest file with the image name you pushed to your Docker registry. + +Part to update in `demo-actor.yaml`: +``` YAML +image: /demoactor:latest +``` + +To install the application in `default` namespace, run the following command: + +``` Bash +kubectl apply -f demo-actor.yaml +``` + +This will deploy the `DemoActor` service to Kubernetes. You can check the status of the deployment by running: + +``` Bash +kubectl get pods -n default --watch +``` + +The manifest create 2 services: + +- `demoactor` service: The service that hosts the `DemoActor` actor. +- `demoactor-dapr` service: The service that hosts the Dapr sidecar for the `DemoActor` actor. + +### Make client calls to the deployed Actor service +To make client calls to the deployed `DemoActor` service, you can use the `ActorClient` project. +Before running the client, update the `DAPR_HTTP_PORT` environment variable in the `ActorClient` project directory to the port on which Dapr is running in the Kubernetes cluster. + +On Linux, MacOS: +``` Bash +export DAPR_HTTP_PORT=3500 +``` + +Than port-forward the `DemoActor` service to your local machine: + +``` Bash +kubectl port-forward svc/demoactor 3500:3500 +``` + +Now you can run the client project from the `ActorClient` directory: + +``` Bash +dotnet run +``` \ No newline at end of file diff --git a/src/Dapr.AspNetCore/DaprServiceCollectionExtensions.cs b/src/Dapr.AspNetCore/DaprServiceCollectionExtensions.cs index 8491cb9b2..388015b80 100644 --- a/src/Dapr.AspNetCore/DaprServiceCollectionExtensions.cs +++ b/src/Dapr.AspNetCore/DaprServiceCollectionExtensions.cs @@ -31,18 +31,32 @@ public static class DaprServiceCollectionExtensions /// public static void AddDaprClient(this IServiceCollection services, Action configure = null) { - if (services is null) - { - throw new ArgumentNullException(nameof(services)); - } + ArgumentNullException.ThrowIfNull(services, nameof(services)); services.TryAddSingleton(_ => { var builder = new DaprClientBuilder(); - if (configure != null) - { - configure.Invoke(builder); - } + configure?.Invoke(builder); + + return builder.Build(); + }); + } + + /// + /// Adds Dapr client services to the provided . This does not include integration + /// with ASP.NET Core MVC. Use the AddDapr() extension method on IMvcBuilder to register MVC integration. + /// + /// The . + /// + public static void AddDaprClient(this IServiceCollection services, + Action configure) + { + ArgumentNullException.ThrowIfNull(services, nameof(services)); + + services.TryAddSingleton(serviceProvider => + { + var builder = new DaprClientBuilder(); + configure?.Invoke(serviceProvider, builder); return builder.Build(); }); diff --git a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs index 5991a7dad..ecd0ac91b 100644 --- a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs +++ b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs @@ -227,8 +227,15 @@ private async Task LoadAsync() $"A duplicate key '{key}' was found in the secret store '{store}'. Please remove any duplicates from your secret store."); } - data.Add(normalizeKey ? NormalizeKey(secretDescriptor.SecretName) : secretDescriptor.SecretName, - result[key]); + // The name of the key "as desired" by the user based on the descriptor. + // + // NOTE: This should vary only if a single secret of the same name is returned. + string desiredKey = StringComparer.Ordinal.Equals(key, secretDescriptor.SecretKey) ? secretDescriptor.SecretName : key; + + // The name of the key normalized based on the configured delimiters. + string normalizedKey = normalizeKey ? NormalizeKey(desiredKey) : desiredKey; + + data.Add(normalizedKey, result[key]); } } diff --git a/test/Dapr.AspNetCore.Test/DaprServiceCollectionExtensionsTest.cs b/test/Dapr.AspNetCore.Test/DaprServiceCollectionExtensionsTest.cs index 614faf5e4..a82948cf3 100644 --- a/test/Dapr.AspNetCore.Test/DaprServiceCollectionExtensionsTest.cs +++ b/test/Dapr.AspNetCore.Test/DaprServiceCollectionExtensionsTest.cs @@ -48,6 +48,31 @@ public void AddDaprClient_RegistersDaprClientOnlyOnce() Assert.True(daprClient.JsonSerializerOptions.PropertyNameCaseInsensitive); } + [Fact] + public void AddDaprClient_RegistersUsingDependencyFromIServiceProvider() + { + + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddDaprClient((provider, builder) => + { + var configProvider = provider.GetRequiredService(); + var caseSensitivity = configProvider.GetCaseSensitivity(); + + builder.UseJsonSerializationOptions(new JsonSerializerOptions + { + PropertyNameCaseInsensitive = caseSensitivity + }); + }); + + var serviceProvider = services.BuildServiceProvider(); + + DaprClientGrpc client = serviceProvider.GetRequiredService() as DaprClientGrpc; + + //Registers with case-insensitive as true by default, but we set as false above + Assert.False(client.JsonSerializerOptions.PropertyNameCaseInsensitive); + } + #if NET8_0_OR_GREATER [Fact] public void AddDaprClient_WithKeyedServices() @@ -65,5 +90,10 @@ public void AddDaprClient_WithKeyedServices() Assert.NotNull(daprClient); } #endif + + private class TestConfigurationProvider + { + public bool GetCaseSensitivity() => false; + } } } diff --git a/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs b/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs index d35275dd1..9bac31352 100644 --- a/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs +++ b/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs @@ -198,6 +198,35 @@ public void LoadSecrets_FromSecretStoreThatCanReturnsMultipleValues() config[secondSecretKey].Should().Be(secondSecretValue); } + [Fact] + public void LoadSecrets_FromSecretStoreThatCanReturnsMultivaluedValues() + { + var storeName = "store"; + var parentSecretKey = "connectionStrings"; + var firstSecretKey = "first_secret"; + var secondSecretKey = "second_secret"; + var firstSecretValue = "secret1"; + var secondSecretValue = "secret2"; + + var secretDescriptors = new[] + { + new DaprSecretDescriptor(parentSecretKey) + }; + + var daprClient = new Mock(); + + daprClient.Setup(c => c.GetSecretAsync(storeName, parentSecretKey, + It.IsAny>(), default)) + .ReturnsAsync(new Dictionary { { firstSecretKey, firstSecretValue }, { secondSecretKey, secondSecretValue } }); + + var config = CreateBuilder() + .AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object) + .Build(); + + config[firstSecretKey].Should().Be(firstSecretValue); + config[secondSecretKey].Should().Be(secondSecretValue); + } + [Fact] public void LoadSecrets_FromSecretStoreWithADifferentSecretKeyAndName() {